{"version":3,"file":"menus-BGF9qbzE.mjs","names":["isRecord","isRecord","convertList","convertCodeBlock","convertImage","convertListItem","EXCLUSIVE_HOOK_KEY_PREFIX","resolveExclusiveHooksShared"],"sources":["../src/fields/image.ts","../src/fields/file.ts","../src/fields/reference.ts","../src/fields/portable-text.ts","../src/content/converters/gallery.ts","../src/content/converters/mark-safety.ts","../src/content/converters/numbered-list.ts","../src/content/converters/prosemirror-to-portable-text.ts","../src/content/converters/portable-text-to-prosemirror.ts","../src/plugins/define-plugin.ts","../src/plugins/hooks.ts","../src/plugins/email.ts","../src/plugins/routes.ts","../src/plugins/manager.ts","../src/plugins/scheduler/node.ts","../src/plugins/sandbox/noop.ts","../src/plugins/sandbox/types.ts","../src/comments/ranking.ts","../src/comments/query.ts","../src/menus/index.ts"],"sourcesContent":["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().optional(),\n\talt: z.string().optional(),\n\twidth: z.number().optional(),\n\theight: z.number().optional(),\n\tfilename: z.string().optional(),\n\tmimeType: z.string().optional(),\n\tblurhash: z.string().optional(),\n\tdominantColor: z.string().optional(),\n\tprovider: z.string().optional(),\n\tpreviewUrl: z.string().optional(),\n\tmeta: z.record(z.string(), z.unknown()).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().optional(),\n\t\tsrc: z.string().optional(),\n\t\tfilename: z.string().optional(),\n\t\tmimeType: z.string().optional(),\n\t\tsize: z.number().optional(),\n\t\tprovider: z.string().optional(),\n\t\tmeta: z.record(z.string(), z.unknown()).optional(),\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","/**\n * Shared sanitization for gallery block images, used by both converters so\n * the editor round-trip and the stored shape stay in lockstep.\n */\n\nimport type { PortableTextGalleryImage } from \"./types.js\";\n\n/**\n * Normalize an untrusted `images` value into well-formed gallery images.\n * Non-object entries and entries without an asset object are dropped.\n * Missing `_key`s are filled via `generateKey` when provided (PM → PT);\n * left empty otherwise (PT → PM keeps whatever the block carried).\n */\nexport function sanitizeGalleryImages(\n\tvalue: unknown,\n\tgenerateKey?: () => string,\n): PortableTextGalleryImage[] {\n\tif (!Array.isArray(value)) return [];\n\n\tconst images: PortableTextGalleryImage[] = [];\n\tfor (const entry of value as unknown[]) {\n\t\tif (!isRecord(entry)) continue;\n\t\tconst record = entry;\n\t\tconst asset = record.asset;\n\t\tif (!isRecord(asset)) continue;\n\t\tconst assetRecord = asset;\n\n\t\tconst image: PortableTextGalleryImage = {\n\t\t\t_type: \"image\",\n\t\t\t_key:\n\t\t\t\ttypeof record._key === \"string\" && record._key\n\t\t\t\t\t? record._key\n\t\t\t\t\t: generateKey\n\t\t\t\t\t\t? generateKey()\n\t\t\t\t\t\t: \"\",\n\t\t\tasset: {\n\t\t\t\t_type: \"reference\",\n\t\t\t\t_ref: typeof assetRecord._ref === \"string\" ? assetRecord._ref : \"\",\n\t\t\t\t...(typeof assetRecord.url === \"string\" && assetRecord.url ? { url: assetRecord.url } : {}),\n\t\t\t\t...(typeof assetRecord.provider === \"string\" && assetRecord.provider\n\t\t\t\t\t? { provider: assetRecord.provider }\n\t\t\t\t\t: {}),\n\t\t\t},\n\t\t};\n\t\tif (typeof record.alt === \"string\" && record.alt) image.alt = record.alt;\n\t\tif (typeof record.caption === \"string\" && record.caption) image.caption = record.caption;\n\t\tif (typeof record.width === \"number\") image.width = record.width;\n\t\tif (typeof record.height === \"number\") image.height = record.height;\n\t\tif (typeof record.blurhash === \"string\" && record.blurhash) image.blurhash = record.blurhash;\n\t\tif (typeof record.dominantColor === \"string\" && record.dominantColor)\n\t\t\timage.dominantColor = record.dominantColor;\n\n\t\timages.push(image);\n\t}\n\n\treturn images;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","import type { PortableTextBlock, ProseMirrorDocument, ProseMirrorNode } from \"./types.js\";\n\nconst SUPPORTED_PORTABLE_TEXT_DECORATORS = new Set([\n\t\"strong\",\n\t\"em\",\n\t\"underline\",\n\t\"strike-through\",\n\t\"subscript\",\n\t\"superscript\",\n\t\"code\",\n]);\n\nconst SUPPORTED_PROSEMIRROR_MARKS = new Set([\n\t\"bold\",\n\t\"strong\",\n\t\"italic\",\n\t\"em\",\n\t\"underline\",\n\t\"strike\",\n\t\"strikethrough\",\n\t\"subscript\",\n\t\"superscript\",\n\t\"code\",\n\t\"link\",\n]);\n\ntype UnknownRecord = Record<string, unknown>;\n\nfunction isRecord(value: unknown): value is UnknownRecord {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction collectPortableTextMarks(\n\tvalue: unknown,\n\tinheritedMarkDefs: ReadonlyMap<string, UnknownRecord>,\n\tunsupported: Set<string>,\n): void {\n\tif (Array.isArray(value)) {\n\t\tfor (const item of value) {\n\t\t\tcollectPortableTextMarks(item, inheritedMarkDefs, unsupported);\n\t\t}\n\t\treturn;\n\t}\n\tif (!isRecord(value)) return;\n\n\tlet markDefs: ReadonlyMap<string, UnknownRecord> = inheritedMarkDefs;\n\tif (Array.isArray(value.markDefs)) {\n\t\tconst localMarkDefs = new Map(inheritedMarkDefs);\n\t\tfor (const markDef of value.markDefs) {\n\t\t\tif (isRecord(markDef) && typeof markDef._key === \"string\") {\n\t\t\t\tlocalMarkDefs.set(markDef._key, markDef);\n\t\t\t}\n\t\t}\n\t\tmarkDefs = localMarkDefs;\n\t}\n\n\tif (value._type === \"span\" && Array.isArray(value.marks)) {\n\t\tfor (const mark of value.marks) {\n\t\t\tif (typeof mark !== \"string\" || SUPPORTED_PORTABLE_TEXT_DECORATORS.has(mark)) continue;\n\t\t\tconst markDef = markDefs.get(mark);\n\t\t\tif (markDef?._type === \"link\") continue;\n\t\t\tunsupported.add(typeof markDef?._type === \"string\" ? markDef._type : mark);\n\t\t}\n\t}\n\n\tfor (const [key, child] of Object.entries(value)) {\n\t\tif (key !== \"markDefs\") {\n\t\t\tcollectPortableTextMarks(child, markDefs, unsupported);\n\t\t}\n\t}\n}\n\nfunction collectProseMirrorMarks(node: ProseMirrorNode, unsupported: Set<string>): void {\n\tfor (const mark of node.marks ?? []) {\n\t\tif (!SUPPORTED_PROSEMIRROR_MARKS.has(mark.type)) {\n\t\t\tunsupported.add(mark.type);\n\t\t}\n\t}\n\tfor (const child of node.content ?? []) {\n\t\tcollectProseMirrorMarks(child, unsupported);\n\t}\n}\n\nexport class UnsupportedPortableTextMarksError extends Error {\n\treadonly marks: string[];\n\n\tconstructor(marks: Iterable<string>) {\n\t\tconst sortedMarks = [...new Set(marks)].toSorted();\n\t\tsuper(`Unsupported Portable Text marks: ${sortedMarks.join(\", \")}`);\n\t\tthis.name = \"UnsupportedPortableTextMarksError\";\n\t\tthis.marks = sortedMarks;\n\t}\n}\n\nexport function findUnsupportedPortableTextMarks(blocks: PortableTextBlock[]): string[] {\n\tconst unsupported = new Set<string>();\n\tfor (const block of blocks) {\n\t\tif (block._type === \"block\" || block._type === \"table\") {\n\t\t\tcollectPortableTextMarks(block, new Map(), unsupported);\n\t\t}\n\t}\n\treturn [...unsupported].toSorted();\n}\n\nexport function assertPortableTextMarksSupported(blocks: PortableTextBlock[]): void {\n\tconst unsupported = findUnsupportedPortableTextMarks(blocks);\n\tif (unsupported.length > 0) {\n\t\tthrow new UnsupportedPortableTextMarksError(unsupported);\n\t}\n}\n\nexport function assertProseMirrorMarksSupported(document: ProseMirrorDocument): void {\n\tconst unsupported = new Set<string>();\n\tfor (const node of document.content) {\n\t\tcollectProseMirrorMarks(node, unsupported);\n\t}\n\tif (unsupported.size > 0) {\n\t\tthrow new UnsupportedPortableTextMarksError(unsupported);\n\t}\n}\n","import type { ProseMirrorDocument, ProseMirrorNode } from \"./types.js\";\n\nexport const MAX_ORDERED_LIST_START = 2_147_483_647;\n\nexport interface OrderedListMetadata {\n\tlistId: string;\n\tlistStart: number;\n}\n\nexport function normalizeListId(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") return undefined;\n\tconst normalized = value.trim();\n\treturn normalized.length > 0 && normalized.length <= 128 ? normalized : undefined;\n}\n\nexport function normalizeListStart(value: unknown): number | undefined {\n\treturn typeof value === \"number\" &&\n\t\tNumber.isInteger(value) &&\n\t\tvalue >= 1 &&\n\t\tvalue <= MAX_ORDERED_LIST_START\n\t\t? value\n\t\t: undefined;\n}\n\nexport function deriveLegacyListId(seed: string): string {\n\tconst readable = `legacy:${seed}`;\n\tif (readable.length <= 128) return readable;\n\tlet hash = 2_166_136_261;\n\tfor (let i = 0; i < seed.length; i++) {\n\t\thash ^= seed.charCodeAt(i);\n\t\thash = Math.imul(hash, 16_777_619);\n\t}\n\treturn `legacy:${seed.slice(0, 96)}:${(hash >>> 0).toString(36)}:${seed.length.toString(36)}`;\n}\n\nexport function readOrderedListMetadata(\n\tattrs: Record<string, unknown> | undefined,\n\tfallbackId: string,\n): OrderedListMetadata {\n\treturn {\n\t\tlistId: normalizeListId(attrs?.listId) ?? deriveLegacyListId(fallbackId),\n\t\tlistStart: normalizeListStart(attrs?.listStart) ?? normalizeListStart(attrs?.start) ?? 1,\n\t};\n}\n\ninterface ProseMirrorListDescriptor {\n\tnode: ProseMirrorNode;\n\tpath: string;\n\tdepth: number;\n\tcontext: string;\n}\n\nfunction collectProseMirrorOrderedLists(doc: ProseMirrorDocument): ProseMirrorListDescriptor[] {\n\tconst lists: ProseMirrorListDescriptor[] = [];\n\tconst visit = (node: ProseMirrorNode, path: string, depth: number, context: string) => {\n\t\tif (node.type === \"orderedList\") lists.push({ node, path, depth, context });\n\t\tfor (const [index, child] of (node.content ?? []).entries()) {\n\t\t\tconst childPath = `${path}:${index}`;\n\t\t\tvisit(\n\t\t\t\tchild,\n\t\t\t\tchildPath,\n\t\t\t\tdepth + 1,\n\t\t\t\tchild.type === \"listItem\" ? `listItem:${childPath}` : context,\n\t\t\t);\n\t\t}\n\t};\n\tfor (const [index, node] of doc.content.entries()) {\n\t\tvisit(node, `root:${index}`, 0, \"root\");\n\t}\n\treturn lists;\n}\n\nfunction cloneProseMirrorNode(node: ProseMirrorNode): ProseMirrorNode {\n\treturn {\n\t\t...node,\n\t\tattrs: node.attrs ? { ...node.attrs } : undefined,\n\t\tcontent: node.content?.map(cloneProseMirrorNode),\n\t\tmarks: node.marks?.map((mark) => ({\n\t\t\t...mark,\n\t\t\tattrs: mark.attrs ? { ...mark.attrs } : undefined,\n\t\t})),\n\t};\n}\n\nfunction createRepairId(sourceId: string, scope: string, attempt: number): string {\n\tconst base = deriveLegacyListId(`repair:${sourceId}:${scope}`);\n\tconst suffix = `:${attempt.toString(36)}`;\n\treturn `${base.slice(0, 128 - suffix.length)}${suffix}`;\n}\n\nexport function normalizeProseMirrorOrderedListJson(doc: ProseMirrorDocument): ProseMirrorDocument {\n\tconst normalized = {\n\t\t...doc,\n\t\tcontent: doc.content.map(cloneProseMirrorNode),\n\t};\n\tconst canonicalBySourceScope = new Map<string, string>();\n\tconst assignedCanonicalIds = new Set<string>();\n\tconst lists = collectProseMirrorOrderedLists(normalized).map((list) => {\n\t\tconst sourceId =\n\t\t\tnormalizeListId(list.node.attrs?.listId) ??\n\t\t\tderiveLegacyListId(`pm-json:${list.path}:${list.depth}:${list.context}`);\n\t\tconst scope = JSON.stringify([list.depth, list.context]);\n\t\tconst sourceScope = JSON.stringify([sourceId, scope]);\n\t\tlet listId = canonicalBySourceScope.get(sourceScope);\n\t\tif (!listId) {\n\t\t\tif (!assignedCanonicalIds.has(sourceId)) {\n\t\t\t\tlistId = sourceId;\n\t\t\t} else {\n\t\t\t\tlet attempt = 0;\n\t\t\t\tdo {\n\t\t\t\t\tlistId = createRepairId(sourceId, scope, attempt++);\n\t\t\t\t} while (assignedCanonicalIds.has(listId));\n\t\t\t}\n\t\t\tcanonicalBySourceScope.set(sourceScope, listId);\n\t\t\tassignedCanonicalIds.add(listId);\n\t\t}\n\t\treturn {\n\t\t\t...list,\n\t\t\tlistId,\n\t\t\tscopeKey: JSON.stringify([listId, list.depth, list.context]),\n\t\t};\n\t});\n\n\tconst bases = new Map<string, number>();\n\tfor (const list of lists) {\n\t\tconst listStart = normalizeListStart(list.node.attrs?.listStart);\n\t\tif (listStart !== undefined && !bases.has(list.scopeKey)) {\n\t\t\tbases.set(list.scopeKey, listStart);\n\t\t}\n\t}\n\tfor (const list of lists) {\n\t\tif (!bases.has(list.scopeKey)) {\n\t\t\tbases.set(list.scopeKey, normalizeListStart(list.node.attrs?.start) ?? 1);\n\t\t}\n\t}\n\n\tconst counts = new Map<string, number>();\n\tfor (const list of lists) {\n\t\tconst listStart = bases.get(list.scopeKey)!;\n\t\tconst count = counts.get(list.scopeKey) ?? 0;\n\t\tconst start = normalizeListStart(listStart + count) ?? 1;\n\t\tconst directItemCount =\n\t\t\tlist.node.content?.filter((node) => node.type === \"listItem\").length ?? 0;\n\t\tcounts.set(list.scopeKey, count + directItemCount);\n\t\tlist.node.attrs = { ...list.node.attrs, listId: list.listId, listStart, start };\n\t}\n\treturn normalized;\n}\n","/**\n * ProseMirror to Portable Text Converter\n *\n * Converts TipTap's ProseMirror JSON format to Portable Text for storage.\n */\n\nimport { sanitizeGalleryImages } from \"./gallery.js\";\nimport {\n\tUnsupportedPortableTextMarksError,\n\tassertProseMirrorMarksSupported,\n} from \"./mark-safety.js\";\nimport { readOrderedListMetadata, type OrderedListMetadata } from \"./numbered-list.js\";\nimport type {\n\tProseMirrorDocument,\n\tProseMirrorNode,\n\tProseMirrorMark,\n\tPortableTextBlock,\n\tPortableTextTextBlock,\n\tPortableTextSpan,\n\tPortableTextMarkDef,\n\tPortableTextImageBlock,\n\tPortableTextGalleryBlock,\n\tPortableTextCodeBlock,\n\tPortableTextHtmlBlock,\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\tassertProseMirrorMarksSupported(doc);\n\n\tconst blocks: PortableTextBlock[] = [];\n\n\tfor (const [i, node] of doc.content.entries()) {\n\t\tconst converted = convertNode(node, `root:${i}`);\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(\n\tnode: ProseMirrorNode,\n\tpath: string,\n): 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\", path);\n\n\t\tcase \"orderedList\":\n\t\t\treturn convertList(node, \"number\", path);\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 \"htmlBlock\":\n\t\t\treturn convertHtmlBlock(node);\n\n\t\tcase \"image\":\n\t\t\treturn convertImage(node);\n\n\t\tcase \"gallery\":\n\t\t\treturn convertGallery(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\tconst ta = node.attrs?.textAlign;\n\tconst textAlign = ta === \"center\" || ta === \"right\" || ta === \"justify\" ? ta : undefined;\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\t...(textAlign ? { textAlign } : {}),\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\tconst ta = node.attrs?.textAlign;\n\tconst textAlign = ta === \"center\" || ta === \"right\" || ta === \"justify\" ? ta : undefined;\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\t...(textAlign ? { textAlign } : {}),\n\t};\n}\n\n/**\n * Convert list to Portable Text blocks\n */\nfunction convertList(\n\tnode: ProseMirrorNode,\n\tlistItem: \"bullet\" | \"number\",\n\tpath: string,\n): PortableTextTextBlock[] {\n\tconst blocks: PortableTextTextBlock[] = [];\n\tconst metadata = listItem === \"number\" ? readOrderedListMetadata(node.attrs, path) : undefined;\n\n\tfor (const [i, item] of (node.content ?? []).entries()) {\n\t\tif (item.type === \"listItem\") {\n\t\t\tconst itemBlocks = convertListItem(item, listItem, 1, `${path}:${i}`, metadata);\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\tpath: string,\n\tmetadata?: OrderedListMetadata,\n): PortableTextTextBlock[] {\n\tconst blocks: PortableTextTextBlock[] = [];\n\n\tfor (const [i, child] of (item.content ?? []).entries()) {\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\t...metadata,\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, `${path}:${i}`));\n\t\t} else if (child.type === \"orderedList\") {\n\t\t\tblocks.push(...convertListItemNested(child, \"number\", level + 1, `${path}:${i}`));\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\tpath: string,\n): PortableTextTextBlock[] {\n\tconst blocks: PortableTextTextBlock[] = [];\n\tconst metadata = listItem === \"number\" ? readOrderedListMetadata(node.attrs, path) : undefined;\n\n\tfor (const [i, item] of (node.content ?? []).entries()) {\n\t\tif (item.type === \"listItem\") {\n\t\t\tblocks.push(...convertListItem(item, listItem, level, `${path}:${i}`, metadata));\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 HTML block to Portable Text\n */\nfunction convertHtmlBlock(node: ProseMirrorNode): PortableTextHtmlBlock {\n\tconst rawHtml = node.attrs?.html;\n\treturn {\n\t\t_type: \"htmlBlock\",\n\t\t_key: generateKey(),\n\t\thtml: typeof rawHtml === \"string\" ? rawHtml : \"\",\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 gallery node to Portable Text\n */\nfunction convertGallery(node: ProseMirrorNode): PortableTextGalleryBlock {\n\tconst columns = node.attrs?.columns;\n\treturn {\n\t\t_type: \"gallery\",\n\t\t_key: generateKey(),\n\t\timages: sanitizeGalleryImages(node.attrs?.images, generateKey),\n\t\t...(typeof columns === \"number\" ? { columns } : {}),\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 \"subscript\":\n\t\t\treturn \"subscript\";\n\n\t\tcase \"superscript\":\n\t\t\treturn \"superscript\";\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\tthrow new UnsupportedPortableTextMarksError([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 { sanitizeGalleryImages } from \"./gallery.js\";\nimport {\n\tUnsupportedPortableTextMarksError,\n\tassertPortableTextMarksSupported,\n} from \"./mark-safety.js\";\nimport {\n\tderiveLegacyListId,\n\tnormalizeProseMirrorOrderedListJson,\n\tnormalizeListId,\n\tnormalizeListStart,\n} from \"./numbered-list.js\";\nimport type {\n\tProseMirrorDocument,\n\tProseMirrorNode,\n\tProseMirrorMark,\n\tPortableTextBlock,\n\tPortableTextTextBlock,\n\tPortableTextSpan,\n\tPortableTextMarkDef,\n\tPortableTextImageBlock,\n\tPortableTextGalleryBlock,\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\tassertPortableTextMarksSupported(blocks);\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 a list \"run\": the level=1 anchor plus everything that\n\t\t\t// nests under it (level > 1, regardless of listItem type — a number\n\t\t\t// child under a bullet parent is still part of the same tree). A\n\t\t\t// level=1 block with a different listItem ends the run. Without the\n\t\t\t// `level > 1` carve-out the run breaks on the first nested type\n\t\t\t// switch and the descendant subtree leaks out as its own top-level\n\t\t\t// list (e.g. `[bullet L1, number L2, bullet L1]` would render as\n\t\t\t// three sibling lists instead of one bullet list with a numbered\n\t\t\t// child).\n\t\t\tconst listBlocks: PortableTextTextBlock[] = [];\n\t\t\tconst listType = block.listItem;\n\t\t\tconst runStart = i;\n\t\t\tconst rootId = listType === \"number\" ? normalizeListId(block.listId) : undefined;\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) break;\n\t\t\t\tconst level = current.level || 1;\n\t\t\t\tconst currentId =\n\t\t\t\t\tcurrent.listItem === \"number\" ? normalizeListId(current.listId) : undefined;\n\t\t\t\tconst sameRootIdentity =\n\t\t\t\t\tlistType !== \"number\" ||\n\t\t\t\t\tlevel > 1 ||\n\t\t\t\t\t(rootId ? currentId === rootId : currentId === undefined);\n\t\t\t\tif (level > 1 || (current.listItem === listType && sameRootIdentity)) {\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, `root:${runStart}`));\n\t\t} else if (isTextBlock(block) && block.style === \"blockquote\") {\n\t\t\t// Collect a blockquote \"run\": Portable Text is flat, so a\n\t\t\t// multi-paragraph quote is stored as consecutive blocks with\n\t\t\t// style \"blockquote\" (that's what the Gutenberg importer emits\n\t\t\t// and what prosemirrorToPortableText serializes back to).\n\t\t\t// Without this grouping each paragraph became its own quote\n\t\t\t// node, and editor merges reverted on reload.\n\t\t\tconst quoteBlocks: PortableTextTextBlock[] = [];\n\t\t\twhile (i < blocks.length) {\n\t\t\t\tconst current = blocks[i];\n\t\t\t\tif (\n\t\t\t\t\t!isTextBlock(current) ||\n\t\t\t\t\tcurrent.style !== \"blockquote\" ||\n\t\t\t\t\tcurrent.listItem !== undefined\n\t\t\t\t) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tquoteBlocks.push(current);\n\t\t\t\ti++;\n\t\t\t}\n\n\t\t\tcontent.push({\n\t\t\t\ttype: \"blockquote\",\n\t\t\t\tcontent: quoteBlocks.map((quoteBlock) => {\n\t\t\t\t\tconst paragraph = convertSpans(quoteBlock.children, quoteBlock.markDefs || []);\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: \"paragraph\",\n\t\t\t\t\t\tcontent: paragraph.length > 0 ? paragraph : undefined,\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t\t});\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 normalizeProseMirrorOrderedListJson({\n\t\ttype: \"doc\",\n\t\tcontent: content.length > 0 ? content : [{ type: \"paragraph\" }],\n\t});\n}\n\nfunction getListMetadata(\n\titem: PortableTextTextBlock,\n\tfallbackSeed: string,\n): { listId: string; listStart?: number } {\n\tconst listId = normalizeListId(item.listId) ?? deriveLegacyListId(fallbackSeed);\n\tconst listStart = normalizeListStart(item.listStart);\n\treturn {\n\t\tlistId,\n\t\t...(listStart === undefined ? {} : { listStart }),\n\t};\n}\n\nfunction belongsToNestedGroup(\n\titem: PortableTextTextBlock,\n\tminLevel: number,\n\tparentListType: \"bullet\" | \"number\",\n\tanchorType: \"bullet\" | \"number\",\n\tanchorId: string | undefined,\n): boolean {\n\tif ((item.level || 2) > minLevel) return true;\n\tif ((item.listItem || parentListType) !== anchorType) return false;\n\tif (anchorType !== \"number\") return true;\n\tconst itemId = normalizeListId(item.listId);\n\treturn anchorId ? itemId === anchorId : itemId === undefined;\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 gallery blocks. Requires an `images` array — a gallery\n * without one is malformed and falls through to the unknown-block path.\n */\nfunction isGalleryBlock(block: PortableTextBlock): block is PortableTextGalleryBlock {\n\treturn block._type === \"gallery\" && \"images\" in block && Array.isArray(block.images);\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 (isGalleryBlock(block)) {\n\t\treturn {\n\t\t\ttype: \"gallery\",\n\t\t\tattrs: {\n\t\t\t\timages: sanitizeGalleryImages(block.images),\n\t\t\t\tcolumns: typeof block.columns === \"number\" ? block.columns : undefined,\n\t\t\t},\n\t\t};\n\t}\n\tif (isCodeBlock(block)) {\n\t\treturn convertCodeBlock(block);\n\t}\n\tif (block._type === \"htmlBlock\") {\n\t\tconst hb = block as PortableTextBlock & { html?: string };\n\t\treturn {\n\t\t\ttype: \"htmlBlock\",\n\t\t\tattrs: { html: hb.html || \"\" },\n\t\t};\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: {\n\t\t\t\t\tlevel,\n\t\t\t\t\t...(block.textAlign ? { textAlign: block.textAlign } : {}),\n\t\t\t\t},\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\tattrs: block.textAlign ? { textAlign: block.textAlign } : undefined,\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\tcontext: string,\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(\n\t\t\t\tconvertListItem(item, nestedItems, listType, `${context}:${rootItems.length}`),\n\t\t\t);\n\t\t} else {\n\t\t\t// Orphan nested item - treat as root\n\t\t\trootItems.push(convertListItem(item, [], listType, `${context}:${rootItems.length}`));\n\t\t\ti++;\n\t\t}\n\t}\n\n\tconst metadata =\n\t\tlistType === \"number\" ? getListMetadata(items[0], `${context}:${items[0]._key}`) : undefined;\n\n\treturn {\n\t\ttype: listType === \"bullet\" ? \"bulletList\" : \"orderedList\",\n\t\tattrs: metadata ? { ...metadata, start: metadata.listStart ?? 1 } : undefined,\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\tcontext: string,\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// The shallowest level in `nestedItems` is the effective root of this\n\t\t// item's nested subtree. A new sub-list only starts when we hit\n\t\t// another block at that root level with a different `listItem` type;\n\t\t// deeper blocks (level > minLevel) belong to the current group as\n\t\t// descendants regardless of their own `listItem`. The previous\n\t\t// grouping broke on any type change at any depth, so a deep mixed\n\t\t// tree like `bullet L1 → number L2 → bullet L3 → number L2` would\n\t\t// emit C(L3) as a sibling list under A(L1) instead of nesting it\n\t\t// under B(L2), then degrade C to L2 on round-trip.\n\t\tlet minLevel = Infinity;\n\t\tfor (const ni of nestedItems) {\n\t\t\tconst level = ni.level || 2;\n\t\t\tif (level < minLevel) minLevel = level;\n\t\t}\n\n\t\tlet j = 0;\n\t\twhile (j < nestedItems.length) {\n\t\t\tconst anchorType: \"bullet\" | \"number\" = nestedItems[j].listItem || parentListType;\n\t\t\tconst anchorId = anchorType === \"number\" ? normalizeListId(nestedItems[j].listId) : undefined;\n\t\t\tconst nestedGroup: PortableTextTextBlock[] = [];\n\n\t\t\tdo {\n\t\t\t\tnestedGroup.push(nestedItems[j]);\n\t\t\t\tj++;\n\t\t\t} while (\n\t\t\t\tj < nestedItems.length &&\n\t\t\t\tbelongsToNestedGroup(nestedItems[j], minLevel, parentListType, anchorType, anchorId)\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(\n\t\t\t\t\tconvertList(adjustedGroup, anchorType, `${context}:nested:${j - nestedGroup.length}`),\n\t\t\t\t);\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 \"subscript\":\n\t\t\t\tpmMarks.push({ type: \"subscript\" });\n\t\t\t\tbreak;\n\n\t\t\tcase \"superscript\":\n\t\t\t\tpmMarks.push({ type: \"superscript\" });\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?._type === \"link\") {\n\t\t\t\t\tpmMarks.push({\n\t\t\t\t\t\ttype: \"link\",\n\t\t\t\t\t\tattrs: {\n\t\t\t\t\t\t\thref: markDef.href,\n\t\t\t\t\t\t\ttarget: markDef.blank ? \"_blank\" : null,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tthrow new UnsupportedPortableTextMarksError([\n\t\t\t\t\t\ttypeof markDef?._type === \"string\" ? markDef._type : mark,\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 * definePlugin() Helper\n *\n * Native plugin authoring entry. Returns a fully-resolved\n * `ResolvedPlugin` ready for the host integration to mount.\n *\n * Sandboxed plugins do NOT use this function. They default-export\n * a bare `{ hooks?, routes? }` object with a `satisfies SandboxedPlugin`\n * annotation from `emdash/plugin`. See the `emdash` changeset for the\n * authoring shape.\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} from \"./types.js\";\n\nconst MCP_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;\n\n/**\n * Define a native EmDash plugin.\n *\n * Native plugins ship as regular npm modules, get installed via\n * `pnpm add` + an `astro.config.mjs` edit, and run in the host\n * process. They have full access to the runtime — capabilities are\n * still enforced by `PluginContextFactory`, but there is no isolation\n * boundary.\n *\n * @example\n * ```typescript\n * import { definePlugin } from \"@premium-cms/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 * Sandboxed-format plugins do not use `definePlugin`. They\n * default-export a bare `{ hooks?, routes? }` object with a\n * `satisfies SandboxedPlugin` annotation from `emdash/plugin`. Calling\n * `definePlugin` with an object that has no `id` throws at runtime\n * (the type system already rejects it at compile time — this check is\n * for callers that bypass typechecking).\n */\nexport function definePlugin<TStorage extends PluginStorageConfig>(\n\tdefinition: PluginDefinition<TStorage>,\n): ResolvedPlugin<TStorage> {\n\t// Semantic check, not a structural one: `id` is what makes this a\n\t// native definition. Sandboxed plugins (the only other shape that\n\t// might land here at runtime) intentionally never have an `id` —\n\t// identity comes from the manifest's `slug` + `publisher`, computed\n\t// at install time. So \"no id\" is the unambiguous signal that the\n\t// caller meant the sandboxed authoring flow.\n\tif (typeof definition.id !== \"string\" || definition.id.length === 0) {\n\t\tthrow new Error(\n\t\t\t`definePlugin() requires \\`id\\` (got ${typeof definition.id}). ` +\n\t\t\t\t\"For native plugins, make sure your definition has both `id` and \" +\n\t\t\t\t\"`version`. For sandboxed plugins, drop `definePlugin()` entirely \" +\n\t\t\t\t\"and `export default { hooks, routes } satisfies SandboxedPlugin` \" +\n\t\t\t\t'from \"@premium-cms/emdash/plugin\" — identity comes from `emdash-plugin.jsonc`.',\n\t\t);\n\t}\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\t// Declared function-local (not module scope) on purpose. Under\n\t// `ssr.noExternal` the worker entry can instantiate native plugins during a\n\t// circular module init, reaching this function before module-scope consts\n\t// initialize -> \"Cannot access 'SIMPLE_ID' before initialization\" -> every\n\t// route 500s on Cloudflare Workers. Call-time consts evaluate after the\n\t// literals are parsed, so the temporal dead zone cannot occur regardless of\n\t// bundle ordering. See #1370.\n\t// oxlint-disable-next-line e18e/prefer-static-regex -- call-time on purpose (see #1370)\n\tconst SIMPLE_ID = /^[a-z0-9-]+$/;\n\t// oxlint-disable-next-line e18e/prefer-static-regex -- call-time on purpose (see #1370)\n\tconst SCOPED_ID = /^@[a-z0-9-]+\\/[a-z0-9-]+$/;\n\t// oxlint-disable-next-line e18e/prefer-static-regex -- call-time on purpose (see #1370)\n\tconst SEMVER_PATTERN = /^\\d+\\.\\d+\\.\\d+/;\n\n\tconst {\n\t\tid,\n\t\tversion,\n\t\tcapabilities = [],\n\t\tallowedHosts = [],\n\t\thooks = {},\n\t\troutes = {},\n\t\tmcp = { tools: {} },\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\tfor (const [name, tool] of Object.entries(mcp.tools)) {\n\t\tif (!MCP_TOOL_NAME_PATTERN.test(name)) {\n\t\t\tthrow new Error(`Invalid MCP tool name \"${name}\" in plugin \"${id}\".`);\n\t\t}\n\t\tconst route = routes[tool.route];\n\t\tif (!route) throw new Error(`MCP tool \"${name}\" references unknown route \"${tool.route}\".`);\n\t\tif (route.public) throw new Error(`MCP tool \"${name}\" cannot reference a public route.`);\n\t\tif (!route.permission) {\n\t\t\tthrow new Error(`MCP route \"${tool.route}\" must declare a permission.`);\n\t\t}\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\"taxonomies:read\",\n\t\t\"media:read\",\n\t\t\"media:write\",\n\t\t\"users:read\",\n\t\t\"github:connection\",\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\tmcp,\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 * 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\tContentStateChangeEvent,\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\tContentAfterRestoreHandler,\n\tContentAfterScheduleHandler,\n\tContentAfterUnpublishHandler,\n\tContentAfterUnscheduleHandler,\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| \"content:afterRestore\"\n\t| \"content:afterSchedule\"\n\t| \"content:afterUnschedule\"\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 */\ntype ContentStateChangeHookName =\n\t| \"content:afterPublish\"\n\t| \"content:afterUnpublish\"\n\t| \"content:afterRestore\"\n\t| \"content:afterSchedule\"\n\t| \"content:afterUnschedule\";\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\"content:afterRestore\": ContentAfterRestoreHandler;\n\t\"content:afterSchedule\": ContentAfterScheduleHandler;\n\t\"content:afterUnschedule\": ContentAfterUnscheduleHandler;\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, \"content:afterRestore\");\n\t\t\tthis.registerPluginHook(plugin, \"content:afterSchedule\");\n\t\t\tthis.registerPluginHook(plugin, \"content:afterUnschedule\");\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[\"content:afterRestore\", \"content:read\"],\n\t\t[\"content:afterSchedule\", \"content:read\"],\n\t\t[\"content:afterUnschedule\", \"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 state-change hooks that all share the same event shape.\n\t */\n\tprivate async runContentStateChangeHook(\n\t\tname: ContentStateChangeHookName,\n\t\tcontent: Record<string, unknown>,\n\t\tcollection: string,\n\t): Promise<HookResult<void>[]> {\n\t\tconst hooks = this.getTypedHooks(name);\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: ContentStateChangeEvent = { 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: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\treturn this.runContentStateChangeHook(\"content:afterPublish\", content, collection);\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\treturn this.runContentStateChangeHook(\"content:afterUnpublish\", content, collection);\n\t}\n\n\t/**\n\t * Run content:afterRestore hooks (fire-and-forget).\n\t */\n\tasync runContentAfterRestore(\n\t\tcontent: Record<string, unknown>,\n\t\tcollection: string,\n\t): Promise<HookResult<void>[]> {\n\t\treturn this.runContentStateChangeHook(\"content:afterRestore\", content, collection);\n\t}\n\n\t/**\n\t * Run content:afterSchedule hooks (fire-and-forget).\n\t */\n\tasync runContentAfterSchedule(\n\t\tcontent: Record<string, unknown>,\n\t\tcollection: string,\n\t): Promise<HookResult<void>[]> {\n\t\treturn this.runContentStateChangeHook(\"content:afterSchedule\", content, collection);\n\t}\n\n\t/**\n\t * Run content:afterUnschedule hooks (fire-and-forget).\n\t */\n\tasync runContentAfterUnschedule(\n\t\tcontent: Record<string, unknown>,\n\t\tcollection: string,\n\t): Promise<HookResult<void>[]> {\n\t\treturn this.runContentStateChangeHook(\"content:afterUnschedule\", content, collection);\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/**\n\t * Batch-read option values for many keys in a single round trip.\n\t * When provided, resolution reads all current selections through this\n\t * instead of one getOption() call per hook. Keys absent from the\n\t * returned map are treated as unset.\n\t */\n\tgetOptions?: (keys: string[]) => Promise<ReadonlyMap<string, string>>;\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, getOptions, setOption, deleteOption, preferredHints } =\n\t\topts;\n\tconst exclusiveHookNames = pipeline.getRegisteredExclusiveHooks();\n\tif (exclusiveHookNames.length === 0) return;\n\n\t// Batch-read current selections in one round trip when the caller\n\t// provides a batch reader (1 query instead of N sequential gets).\n\tlet batchedSelections: ReadonlyMap<string, string> | undefined;\n\tif (getOptions) {\n\t\ttry {\n\t\t\tbatchedSelections = await getOptions(\n\t\t\t\texclusiveHookNames.map((hookName) => `${EXCLUSIVE_HOOK_KEY_PREFIX}${hookName}`),\n\t\t\t);\n\t\t} catch {\n\t\t\t// Options table may not be ready. Matches the per-key tolerance\n\t\t\t// below: every hook's read would fail, so resolution is skipped\n\t\t\t// entirely without touching any selection.\n\t\t\treturn;\n\t\t}\n\t}\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\tif (batchedSelections) {\n\t\t\tcurrentSelection = batchedSelections.get(key) ?? null;\n\t\t} else {\n\t\t\ttry {\n\t\t\t\tcurrentSelection = await getOption(key);\n\t\t\t} catch {\n\t\t\t\t// Options table may not be ready\n\t\t\t\tcontinue;\n\t\t\t}\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 * 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 { MediaUsageActivationWriteBlockedError } from \"../api/media-usage-write-fence.js\";\nimport { PluginContextFactory, type PluginContextFactoryOptions } from \"./context.js\";\nimport { extractRequestMeta } from \"./request-meta.js\";\nimport type { ResolvedPlugin, RouteContext, PluginRoute, UserInfo } from \"./types.js\";\n\n/**\n * Body-reading methods on `Request`. EmDash parses the request body once before\n * the handler runs and exposes the result as `ctx.input`, leaving the underlying\n * stream consumed. Calling any of these on `ctx.request` would re-read a spent\n * stream and throw an opaque platform error (\"Body is unusable: Body has already\n * been read\") with no hint about `ctx.input` — so the guard replaces them with an\n * actionable message instead (#1293).\n */\nconst CONSUMED_BODY_METHODS = new Set([\"json\", \"text\", \"arrayBuffer\", \"blob\", \"formData\", \"bytes\"]);\n\n/**\n * Wrap the request handed to a plugin route handler so an accidental\n * `ctx.request.json()` (or `.text()`, `.formData()`, …) fails with a message\n * pointing at `ctx.input` rather than the runtime's cryptic \"body already read\"\n * error. Every non-body member passes through unchanged; function members are\n * bound to the underlying request so methods like `clone()` don't throw an\n * \"Illegal invocation\" when called on the proxy.\n */\nfunction guardConsumedRequestBody(request: Request): Request {\n\treturn new Proxy(request, {\n\t\tget(target, prop) {\n\t\t\tif (typeof prop === \"string\" && CONSUMED_BODY_METHODS.has(prop)) {\n\t\t\t\treturn () => {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`[emdash] ctx.request.${prop}() is not available inside a plugin route handler: ` +\n\t\t\t\t\t\t\t`EmDash has already parsed the request body and exposes it as ctx.input. ` +\n\t\t\t\t\t\t\t`Read ctx.input instead of ctx.request.${prop}().`,\n\t\t\t\t\t);\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst value = Reflect.get(target, prop, target);\n\t\t\treturn typeof value === \"function\" ? value.bind(target) : value;\n\t\t},\n\t});\n}\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\tpermission?: string;\n\t/**\n\t * Cache-Control value for successful GET responses. Only ever set for\n\t * public routes — authenticated responses must stay `private, no-store`.\n\t */\n\tcacheControl?: string;\n}\n\n/**\n * Build RouteMeta from a route's `public`/`cacheControl` flags. Single source\n * of truth for the \"cacheControl is only ever exposed on public routes\"\n * invariant — used for trusted routes and manifest-declared sandboxed routes.\n */\nexport function buildRouteMeta(route: {\n\tpublic?: boolean;\n\tpermission?: string;\n\tcacheControl?: string;\n}): RouteMeta {\n\tconst meta: RouteMeta = { public: route.public === true };\n\tif (route.permission !== undefined) meta.permission = route.permission;\n\t// Private responses are per-user and must never become cacheable, even if\n\t// a route sets both flags.\n\tif (meta.public && typeof route.cacheControl === \"string\" && route.cacheControl.length > 0) {\n\t\tmeta.cacheControl = route.cacheControl;\n\t}\n\treturn meta;\n}\n\n/**\n * HTTP methods that carry a request body. Everything else (GET, HEAD, DELETE)\n * takes its route input from the URL query string.\n */\nconst BODY_METHODS = new Set([\"POST\", \"PUT\", \"PATCH\"]);\n\n/**\n * Parse a plugin route's input from the request, by method.\n *\n * Body methods (POST/PUT/PATCH) parse the JSON body as before. Bodyless\n * methods (GET/HEAD/DELETE) have no body, so `request.json()` resolves to\n * undefined and fails schema validation (#2146) — parse the query string into\n * an object instead. Repeated keys (`?tag=a&tag=b`) become an array so array\n * schemas work; a single key stays a scalar.\n */\nexport async function parseRouteInput(request: Request): Promise<unknown> {\n\tif (BODY_METHODS.has(request.method.toUpperCase())) {\n\t\ttry {\n\t\t\treturn await request.json();\n\t\t} catch {\n\t\t\t// No body or not JSON\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tconst params = new URL(request.url).searchParams;\n\tconst input: Record<string, string | string[]> = {};\n\tfor (const key of new Set(params.keys())) {\n\t\tconst values = params.getAll(key);\n\t\tinput[key] = values.length > 1 ? values : values[0];\n\t}\n\treturn input;\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 * Host-side user shape accepted when dispatching a plugin route. Structurally\n * matches `User` from `@premium-cms/auth` so hosts can pass `locals.user`\n * directly without the plugin layer depending on the auth package.\n */\nexport interface RouteCallerInput {\n\tid: string;\n\temail: string;\n\tname: string | null;\n\trole: number;\n\troleId?: string | null;\n\tcreatedAt: Date | string;\n\ttokenAuth?: boolean;\n}\n\n/**\n * Convert the host's authenticated user into the read-only `UserInfo` shape\n * exposed to plugins as `ctx.user`. Strips sensitive/irrelevant fields and\n * keeps the value structured-clone-safe for sandboxed plugins.\n */\nexport function toRouteCallerInfo(user: RouteCallerInput): 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\troleId: user.roleId ?? null,\n\t\tcreatedAt: typeof user.createdAt === \"string\" ? user.createdAt : user.createdAt.toISOString(),\n\t\ttokenAuth: user.tokenAuth === true,\n\t};\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\t/**\n\t * Authenticated caller resolved by the host, exposed to the handler as\n\t * `ctx.user`. Undefined for public routes and unbound machine tokens.\n\t */\n\tuser?: UserInfo;\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\t// The body is already parsed into `input`; guard `ctx.request`'s\n\t\t\t// body-reading methods so a re-read fails with an actionable message\n\t\t\t// (#1293). Metadata extraction uses the original request (headers only).\n\t\t\trequest: guardConsumedRequestBody(options.request),\n\t\t\trequestMeta: extractRequestMeta(options.request, this.trustedProxyHeaders),\n\t\t\tuser: options.user,\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\tif (error instanceof MediaUsageActivationWriteBlockedError) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: { code: error.code, message: error.message },\n\t\t\t\t\tstatus: error.status,\n\t\t\t\t};\n\t\t\t}\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 buildRouteMeta(route);\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 content:afterRestore hooks across all active plugins\n\t */\n\tasync runContentAfterRestore(\n\t\tcontent: Record<string, unknown>,\n\t\tcollection: string,\n\t): Promise<HookResult<void>[]> {\n\t\tthis.ensureInitialized();\n\t\treturn this.hookPipeline!.runContentAfterRestore(content, collection);\n\t}\n\n\t/**\n\t * Run content:afterSchedule hooks across all active plugins\n\t */\n\tasync runContentAfterSchedule(\n\t\tcontent: Record<string, unknown>,\n\t\tcollection: string,\n\t): Promise<HookResult<void>[]> {\n\t\tthis.ensureInitialized();\n\t\treturn this.hookPipeline!.runContentAfterSchedule(content, collection);\n\t}\n\n\t/**\n\t * Run content:afterUnschedule hooks across all active plugins\n\t */\n\tasync runContentAfterUnschedule(\n\t\tcontent: Record<string, unknown>,\n\t\tcollection: string,\n\t): Promise<HookResult<void>[]> {\n\t\tthis.ensureInitialized();\n\t\treturn this.hookPipeline!.runContentAfterUnschedule(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\tgetOptions: (keys) => optionsRepo.getMany<string>(keys),\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 * Node.js cron scheduler — setTimeout-based.\n *\n * Queries the executor for the next due time and sets a timeout. Re-arms\n * after each tick and when reschedule() is called (new task scheduled or\n * cancelled).\n *\n * Suitable for single-process deployments (local dev, single-node).\n *\n */\n\nimport type { CronExecutor } from \"../cron.js\";\nimport type { CronScheduler, SystemCleanupFn } from \"./types.js\";\n\n/** Minimum polling interval (ms) — prevents tight loops if next_run_at is in the past */\nconst MIN_INTERVAL_MS = 1000;\n\n/**\n * Maximum polling interval (ms). Each wake runs the maintenance pass — stale\n * lock recovery *and* the scheduled-publishing sweep + system cleanup. The cap\n * is the worst-case latency for scheduled content when no plugin cron task is\n * due sooner (`getNextDueTime()` only knows about cron tasks, not content\n * `scheduled_at`). Held at 60s so Node publish latency matches the Cloudflare\n * Cron Trigger cadence (`* * * * *`) rather than lagging up to five minutes.\n */\nconst MAX_INTERVAL_MS = 60 * 1000;\n\nexport class NodeCronScheduler implements CronScheduler {\n\tprivate timer: ReturnType<typeof setTimeout> | null = null;\n\tprivate running = false;\n\tprivate systemCleanup: SystemCleanupFn | null = null;\n\tprivate mediaUsageMaintenance: SystemCleanupFn | null = null;\n\n\tconstructor(private executor: CronExecutor) {}\n\n\tsetSystemCleanup(fn: SystemCleanupFn): void {\n\t\tthis.systemCleanup = fn;\n\t}\n\n\tsetMediaUsageMaintenance(fn: SystemCleanupFn): void {\n\t\tthis.mediaUsageMaintenance = fn;\n\t}\n\n\tstart(): void {\n\t\tthis.running = true;\n\t\tthis.arm();\n\t}\n\n\tstop(): void {\n\t\tthis.running = false;\n\t\tif (this.timer) {\n\t\t\tclearTimeout(this.timer);\n\t\t\tthis.timer = null;\n\t\t}\n\t}\n\n\treschedule(): void {\n\t\tif (!this.running) return;\n\t\t// Clear existing timer and re-arm with fresh next due time\n\t\tif (this.timer) {\n\t\t\tclearTimeout(this.timer);\n\t\t\tthis.timer = null;\n\t\t}\n\t\tthis.arm();\n\t}\n\n\tprivate arm(): void {\n\t\tif (!this.running) return;\n\n\t\t// Query the next due time, then schedule a wake-up\n\t\tvoid this.executor\n\t\t\t.getNextDueTime()\n\t\t\t.then((nextDue) => {\n\t\t\t\tif (!this.running) return undefined;\n\n\t\t\t\tlet delayMs: number;\n\t\t\t\tif (nextDue) {\n\t\t\t\t\tconst dueAt = new Date(nextDue).getTime();\n\t\t\t\t\tdelayMs = Math.max(dueAt - Date.now(), MIN_INTERVAL_MS);\n\t\t\t\t\tdelayMs = Math.min(delayMs, MAX_INTERVAL_MS);\n\t\t\t\t} else {\n\t\t\t\t\t// No tasks scheduled — poll at max interval for stale lock recovery\n\t\t\t\t\tdelayMs = MAX_INTERVAL_MS;\n\t\t\t\t}\n\n\t\t\t\tthis.timer = setTimeout(() => {\n\t\t\t\t\tif (!this.running) return;\n\t\t\t\t\tthis.executeTick();\n\t\t\t\t}, delayMs);\n\n\t\t\t\t// Don't prevent process exit\n\t\t\t\tif (this.timer && typeof this.timer === \"object\" && \"unref\" in this.timer) {\n\t\t\t\t\tthis.timer.unref();\n\t\t\t\t}\n\n\t\t\t\treturn undefined;\n\t\t\t})\n\t\t\t.catch((error: unknown) => {\n\t\t\t\tconsole.error(\"[cron:node] Failed to get next due time:\", error);\n\t\t\t\t// Retry after max interval\n\t\t\t\tif (this.running) {\n\t\t\t\t\tthis.timer = setTimeout(() => this.arm(), MAX_INTERVAL_MS);\n\t\t\t\t\tif (this.timer && typeof this.timer === \"object\" && \"unref\" in this.timer) {\n\t\t\t\t\t\tthis.timer.unref();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t}\n\n\tprivate executeTick(): void {\n\t\tif (!this.running) return;\n\n\t\t// Run tick + stale lock recovery + system cleanup, then re-arm\n\t\tconst tasks: Promise<unknown>[] = [this.executor.tick(), this.executor.recoverStaleLocks()];\n\t\tif (this.systemCleanup) {\n\t\t\ttasks.push(this.systemCleanup());\n\t\t}\n\n\t\tvoid Promise.allSettled(tasks)\n\t\t\t.then(async (results) => {\n\t\t\t\tfor (const r of results) {\n\t\t\t\t\tif (r.status === \"rejected\") {\n\t\t\t\t\t\tconsole.error(\"[cron:node] Tick task failed:\", r.reason);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.mediaUsageMaintenance) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.mediaUsageMaintenance();\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconsole.error(\"[cron:node] Media Usage maintenance failed:\", error);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tif (this.running) {\n\t\t\t\t\tthis.arm();\n\t\t\t\t}\n\t\t\t});\n\t}\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, SandboxedPluginInstance, 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. \" +\n\t\t\t\t\"Configure a sandbox runner: use @premium-cms/cloudflare/sandbox on Cloudflare, \" +\n\t\t\t\t\"or @premium-cms/sandbox-workerd/sandbox on Node.js (requires workerd). \" +\n\t\t\t\t\"Without sandboxing, use trusted plugins (from config) instead.\",\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 returns false - no sandbox runtime to be healthy.\n\t */\n\tisHealthy(): 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<SandboxedPluginInstance> {\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 * Plugin Sandbox Types\n *\n * Defines interfaces for running plugins in sandboxed V8 isolates.\n * The SandboxRunner interface is implemented by platform adapters\n * (e.g., Cloudflare Worker Loader) to provide isolation.\n *\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport type { Database } from \"../../database/types.js\";\nimport type { PluginManifest, RequestMeta, UserInfo } from \"../types.js\";\n\n/**\n * Resource limits for sandboxed plugins.\n * Enforced by the sandbox runtime (e.g., Worker Loader).\n */\nexport interface ResourceLimits {\n\t/** CPU time per invocation in milliseconds (default: 200ms) */\n\tcpuMs?: number;\n\t/** Memory limit in MB (default: 128MB) */\n\tmemoryMb?: number;\n\t/** Maximum subrequests per invocation — every bridge call counts (default: 64) */\n\tsubrequests?: number;\n\t/** Wall-clock time limit in milliseconds (default: 60000ms) */\n\twallTimeMs?: number;\n}\n\n/**\n * Storage interface for loading plugin code.\n * Could be R2, local filesystem, or any other storage backend.\n */\nexport interface PluginCodeStorage {\n\t/** Get plugin bundle code by path */\n\tget(path: string): Promise<string | null>;\n\t/** Check if a bundle exists */\n\texists(path: string): Promise<boolean>;\n}\n\n/**\n * Serialized email message for sandbox RPC transport.\n * Matches the core EmailMessage type but uses only serializable fields.\n */\nexport interface SandboxEmailMessage {\n\tto: string;\n\tsubject: string;\n\ttext: string;\n\thtml?: string;\n}\n\n/**\n * Callback for sending email from a sandboxed plugin.\n * The sandbox runner wires this up from the EmailPipeline.\n *\n * @param message - The email message to send\n * @param pluginId - The sending plugin's ID (used as source)\n */\nexport type SandboxEmailSendCallback = (\n\tmessage: SandboxEmailMessage,\n\tpluginId: string,\n) => Promise<void>;\n\n/**\n * Options for creating a sandbox runner\n */\nexport interface SandboxOptions {\n\t/** Storage interface for loading plugin code */\n\tstorage?: PluginCodeStorage;\n\t/** Database for bridge operations */\n\tdb: Kysely<Database>;\n\t/** Called immediately before a sandboxed plugin content mutation. */\n\tbeforeContentWrite?: () => Promise<void>;\n\t/** Default resource limits */\n\tlimits?: ResourceLimits;\n\t/** Site info for plugin context (injected into wrapper at generation time) */\n\tsiteInfo?: {\n\t\tname: string;\n\t\turl: string;\n\t\tlocale: string;\n\t\ttrailingSlash?: \"always\" | \"never\" | \"ignore\";\n\t};\n\t/** Email send callback, wired from the EmailPipeline by the runtime */\n\temailSend?: SandboxEmailSendCallback;\n\t/**\n\t * Media storage adapter for sandboxed plugin uploads and deletes.\n\t * When provided, plugins with write:media can upload and delete files\n\t * via ctx.media.upload() and ctx.media.delete().\n\t */\n\tmediaStorage?: {\n\t\tupload(options: { key: string; body: Uint8Array; contentType: string }): Promise<unknown>;\n\t\tdelete(key: string): Promise<unknown>;\n\t};\n}\n\n/**\n * Handle to a sandboxed plugin running inside an isolate. Returned\n * by `SandboxRunner.load` and held by the runtime's cache so hooks /\n * routes can be invoked across the isolate boundary. Distinct from\n * the author-facing `SandboxedPlugin` type in `emdash/plugin`, which\n * describes the source-level shape of a plugin's default export.\n */\nexport interface SandboxedPluginInstance {\n\t/** Unique identifier: `${manifest.id}:${manifest.version}` */\n\treadonly id: string;\n\n\t/**\n\t * Invoke a hook in the sandboxed plugin.\n\t *\n\t * @param hookName - Name of the hook (e.g., \"content:beforeSave\")\n\t * @param event - Event data to pass to the hook\n\t * @returns Hook result (transformed content, void, etc.)\n\t */\n\tinvokeHook(hookName: string, event: unknown): Promise<unknown>;\n\n\t/**\n\t * Invoke an API route in the sandboxed plugin.\n\t *\n\t * @param routeName - Name of the route\n\t * @param input - Validated input data\n\t * @param request - Serialized request info for context\n\t * @returns Route response data\n\t */\n\tinvokeRoute(routeName: string, input: unknown, request: SerializedRequest): Promise<unknown>;\n\n\t/**\n\t * Terminate the sandboxed plugin.\n\t * Releases resources and prevents further invocations.\n\t */\n\tterminate(): Promise<void>;\n}\n\n/**\n * Serialized request for RPC transport.\n * Worker Loader can't pass Request objects directly.\n */\nexport interface SerializedRequest {\n\turl: string;\n\tmethod: string;\n\theaders: Record<string, string>;\n\t/** Normalized request metadata extracted before RPC serialization */\n\tmeta: RequestMeta;\n\t/**\n\t * Authenticated caller for private routes, resolved by the host before\n\t * dispatch. Undefined for public routes and unbound machine tokens.\n\t */\n\tuser?: UserInfo;\n}\n\nconst SANDBOX_ROUTE_ERROR_DEFINITIONS = {\n\tMEDIA_USAGE_ACTIVATION_IN_PROGRESS: {\n\t\tmessage: \"Media usage activation is in progress\",\n\t\tstatus: 503,\n\t},\n\tMEDIA_USAGE_ACTIVATION_CHECK_FAILED: {\n\t\tmessage: \"Unable to verify media usage activation state\",\n\t\tstatus: 503,\n\t},\n} as const;\n\nexport type SandboxRouteErrorCode = keyof typeof SANDBOX_ROUTE_ERROR_DEFINITIONS;\n\nexport interface SandboxRouteErrorDetails {\n\tcode: SandboxRouteErrorCode;\n\tmessage: string;\n\tstatus: 503;\n}\n\nexport interface SandboxRouteErrorEnvelope {\n\t__emdashSandboxRouteError: true;\n\terror: SandboxRouteErrorDetails;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction isSandboxRouteErrorCode(value: unknown): value is SandboxRouteErrorCode {\n\treturn typeof value === \"string\" && value in SANDBOX_ROUTE_ERROR_DEFINITIONS;\n}\n\nexport function getSandboxRouteErrorDetails(error: unknown): SandboxRouteErrorDetails | null {\n\tif (!isRecord(error)) return null;\n\n\tconst propertyCode = isSandboxRouteErrorCode(error.code) ? error.code : null;\n\tconst nameCode =\n\t\terror instanceof Error && isSandboxRouteErrorCode(error.name) ? error.name : null;\n\tif (propertyCode && nameCode && propertyCode !== nameCode) return null;\n\n\tconst code = propertyCode ?? nameCode;\n\tif (!code || (error.status !== undefined && error.status !== 503)) return null;\n\n\treturn {\n\t\tcode,\n\t\t...SANDBOX_ROUTE_ERROR_DEFINITIONS[code],\n\t};\n}\n\nexport function createSandboxRouteError(\n\tcode: SandboxRouteErrorCode,\n): Error & SandboxRouteErrorDetails {\n\tconst details: SandboxRouteErrorDetails = {\n\t\tcode,\n\t\t...SANDBOX_ROUTE_ERROR_DEFINITIONS[code],\n\t};\n\treturn Object.assign(new Error(details.message), details, { name: code });\n}\n\nexport function createSandboxRouteErrorEnvelope(error: unknown): SandboxRouteErrorEnvelope | null {\n\tconst details = getSandboxRouteErrorDetails(error);\n\treturn details ? { __emdashSandboxRouteError: true, error: details } : null;\n}\n\nexport function getSandboxRouteErrorEnvelope(value: unknown): SandboxRouteErrorEnvelope | null {\n\tif (!isRecord(value) || value.__emdashSandboxRouteError !== true) return null;\n\tconst details = getSandboxRouteErrorDetails(value.error);\n\treturn details ? { __emdashSandboxRouteError: true, error: details } : null;\n}\n\n/**\n * Sandbox runner interface.\n * Platform adapters implement this to provide plugin isolation.\n */\nexport interface SandboxRunner {\n\t/**\n\t * Check if sandboxing is available on this platform.\n\t * Returns false for platforms that don't support isolation.\n\t */\n\tisAvailable(): boolean;\n\n\t/**\n\t * Check if the sandbox runtime is currently healthy.\n\t * For in-process runners this always returns true.\n\t * For sidecar-based runners (workerd), returns false if the\n\t * child process has crashed and hasn't been restarted yet.\n\t */\n\tisHealthy(): boolean;\n\n\t/**\n\t * Load a sandboxed plugin from code.\n\t *\n\t * @param manifest - Plugin manifest with metadata and capabilities\n\t * @param code - The bundled plugin JavaScript code\n\t * @returns A sandboxed plugin instance\n\t * @throws If sandboxing is not available or plugin can't be loaded\n\t */\n\tload(manifest: PluginManifest, code: string): Promise<SandboxedPluginInstance>;\n\n\t/**\n\t * Set the email send callback for sandboxed plugins.\n\t * Called after the EmailPipeline is created, since the pipeline\n\t * doesn't exist when the sandbox runner is constructed.\n\t */\n\tsetEmailSend(callback: SandboxEmailSendCallback | null): void;\n\n\t/**\n\t * Terminate all loaded sandboxed plugins.\n\t * Called during shutdown or when reconfiguring.\n\t */\n\tterminateAll(): Promise<void>;\n}\n\n/**\n * Error thrown when the sandbox runtime is unavailable.\n * This happens when the sidecar process has crashed or hasn't started.\n */\nexport class SandboxUnavailableError extends Error {\n\tconstructor(pluginId: string, reason: string) {\n\t\tsuper(`Plugin sandbox unavailable for ${pluginId}: ${reason}`);\n\t\tthis.name = \"SandboxUnavailableError\";\n\t}\n}\n\n/**\n * Factory function type for creating sandbox runners.\n * Exported by platform adapters (e.g., @premium-cms/adapter-cloudflare/sandbox).\n *\n * @example\n * ```typescript\n * // In @premium-cms/adapter-cloudflare/sandbox.ts\n * export const createSandboxRunner: SandboxRunnerFactory = (options) => {\n *   return new CloudflareSandboxRunner(options);\n * };\n * ```\n */\nexport type SandboxRunnerFactory = (options: SandboxOptions) => SandboxRunner;\n","/**\n * Comment ranking utilities (Tier 1 of the best-in-class comments RFC).\n *\n * Wilson score lower-bound (95% confidence) — the same primitive Reddit uses\n * for its \"Best\" comment sort. Ranks by the statistical lower bound of the\n * positive-reaction proportion rather than the raw count, so a comment with a\n * couple of reactions can't outrank a heavily-reacted one until it earns\n * confidence, and a late-but-popular comment still rises (submission time is\n * irrelevant).\n *\n * Positive-only reactions (the recommended default) degrade gracefully: with\n * `down = 0` the score still increases monotonically with `up` while penalising\n * low-sample comments — wilson(1,0) ≈ 0.21, wilson(10,0) ≈ 0.73,\n * wilson(200,0) ≈ 0.98.\n */\n\n/** z for a 95% two-sided confidence interval. */\nconst DEFAULT_Z = 1.96;\n\n/** Reactions that count against a comment when both signals are in use. */\nconst NEGATIVE_REACTIONS: ReadonlySet<string> = new Set([\"dislike\", \"down\"]);\n\n/**\n * Wilson score lower bound of the positive proportion.\n *\n * @param up   count of positive reactions\n * @param down count of negative reactions\n * @returns a score in [0, 1]; 0 when there are no reactions\n */\nexport function wilsonLowerBound(up: number, down: number, z: number = DEFAULT_Z): number {\n\tconst n = up + down;\n\tif (n <= 0) return 0;\n\tconst phat = up / n;\n\tconst z2 = z * z;\n\tconst denom = 1 + z2 / n;\n\tconst centre = phat + z2 / (2 * n);\n\tconst margin = z * Math.sqrt((phat * (1 - phat) + z2 / (4 * n)) / n);\n\treturn (centre - margin) / denom;\n}\n\n/**\n * Reduce a per-reaction count map to a single rank score via the Wilson\n * lower bound. Any reaction not in {@link NEGATIVE_REACTIONS} is treated as\n * positive, so the positive-only default (just `like`) works without special\n * casing.\n */\nexport function reactionScore(counts: Record<string, number>): number {\n\tlet up = 0;\n\tlet down = 0;\n\tfor (const [reaction, count] of Object.entries(counts)) {\n\t\tif (NEGATIVE_REACTIONS.has(reaction)) {\n\t\t\tdown += count;\n\t\t} else {\n\t\t\tup += count;\n\t\t}\n\t}\n\treturn wilsonLowerBound(up, down);\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 { CommentReactionRepository } from \"../database/repositories/comment-reaction.js\";\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\";\nimport { cachedQuery, CacheNamespace } from \"../object-cache/index.js\";\nimport { reactionScore } from \"./ranking.js\";\n\nexport interface GetCommentsOptions {\n\tcollection: string;\n\tcontentId: string;\n\tthreaded?: boolean;\n\t/** Attach aggregate reaction counts to each comment. */\n\treactions?: boolean;\n\t/**\n\t * Order of top-level comments. `oldest` (default) preserves the existing\n\t * chronological display; `best` ranks by Wilson-scored reactions (implies\n\t * `reactions`). Replies always stay chronological.\n\t */\n\tsort?: \"oldest\" | \"best\";\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 \"@premium-cms/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\t// The result varies by every option getCommentsWithDb branches on, so all of\n\t// them belong in the key. `best` implies reactions, so normalize the reaction\n\t// flag — `{ sort: \"best\" }` and `{ sort: \"best\", reactions: true }` produce\n\t// identical output and should share one entry.\n\tconst sort = options.sort ?? \"oldest\";\n\tconst withReactions = options.reactions || sort === \"best\";\n\tconst threaded = options.threaded ? \"t\" : \"f\";\n\treturn cachedQuery({\n\t\tnamespace: CacheNamespace.COMMENTS,\n\t\tkey: `comments:${options.collection}:${options.contentId}:${threaded}:${withReactions ? \"r\" : \"n\"}:${sort}`,\n\t\tload: async () => {\n\t\t\tconst db = await getDb();\n\t\t\treturn getCommentsWithDb(db, options);\n\t\t},\n\t});\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\tconst items: PublicComment[] = options.threaded\n\t\t? CommentRepository.assembleThreads(result.items).map((c) =>\n\t\t\t\tCommentRepository.toPublicComment(c),\n\t\t\t)\n\t\t: result.items.map((c) => CommentRepository.toPublicComment(c));\n\n\t// `best` sort needs reaction data, so it implies reactions.\n\tif (options.reactions || options.sort === \"best\") {\n\t\tawait attachReactions(db, items);\n\t\tif (options.sort === \"best\") {\n\t\t\tsortByBest(items);\n\t\t}\n\t}\n\n\treturn { items, total };\n}\n\n/**\n * Attach aggregate reaction counts to a list of public comments (and their\n * replies), in a single batched query.\n */\nasync function attachReactions(db: Kysely<Database>, items: PublicComment[]): Promise<void> {\n\tconst ids: string[] = [];\n\tfor (const comment of items) {\n\t\tids.push(comment.id);\n\t\tif (comment.replies) {\n\t\t\tfor (const reply of comment.replies) ids.push(reply.id);\n\t\t}\n\t}\n\tif (ids.length === 0) return;\n\n\tconst counts = await new CommentReactionRepository(db).countsForComments(ids);\n\n\tconst assign = (comment: PublicComment) => {\n\t\tconst reactions = counts.get(comment.id);\n\t\tif (reactions) comment.reactions = reactions;\n\t};\n\tfor (const comment of items) {\n\t\tassign(comment);\n\t\tcomment.replies?.forEach(assign);\n\t}\n}\n\n/**\n * Sort top-level comments by Wilson-scored reactions (descending), tie-broken\n * by oldest-first to keep ordering stable.\n */\nfunction sortByBest(items: PublicComment[]): void {\n\titems.sort((a, b) => {\n\t\tconst scoreDelta = reactionScore(b.reactions ?? {}) - reactionScore(a.reactions ?? {});\n\t\tif (scoreDelta !== 0) return scoreDelta;\n\t\tif (a.createdAt < b.createdAt) return -1;\n\t\tif (a.createdAt > b.createdAt) return 1;\n\t\treturn 0;\n\t});\n}\n\n/**\n * Get the count of approved comments for a content item.\n *\n * @example\n * ```ts\n * import { getCommentCount } from \"@premium-cms/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 { cachedQuery, CacheNamespace } from \"../object-cache/index.js\";\nimport { requestCached } from \"../request-cache.js\";\nimport { chunks, SQL_BATCH_SIZE } from \"../utils/chunks.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 ?? \"*\"}`, () =>\n\t\tcachedQuery({\n\t\t\tnamespace: CacheNamespace.MENUS,\n\t\t\tkey: `${name}:${locale ?? \"*\"}`,\n\t\t\tload: async () => {\n\t\t\t\tconst db = await getDb();\n\t\t\t\treturn getMenuWithDb(name, db, { locale });\n\t\t\t},\n\t\t}),\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 contentReferences = collectContentReferences(items);\n\tconst taxonomyReferences = new Set(\n\t\titems.flatMap((item) =>\n\t\t\titem.type === \"taxonomy\" && item.reference_id ? [item.reference_id] : [],\n\t\t),\n\t);\n\tconst [urlPatterns, contentLookup, taxonomyLookup] = await Promise.all([\n\t\tcontentReferences.size > 0\n\t\t\t? getCollectionUrlPatterns(db, new Set(contentReferences.keys()))\n\t\t\t: new Map<string, string | null>(),\n\t\tresolveContentReferences(db, contentReferences, locale),\n\t\tresolveTaxonomyReferences(db, taxonomyReferences, locale),\n\t]);\n\n\tconst resolvedItems = items.map((item) =>\n\t\tresolveMenuItem(item, urlPatterns, contentLookup, taxonomyLookup),\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\nfunction collectContentReferences(items: MenuItemRow[]): Map<string, Set<string>> {\n\tconst references = new Map<string, Set<string>>();\n\tfor (const item of items) {\n\t\tconst reference = getContentReference(item);\n\t\tif (!reference) continue;\n\t\tlet ids = references.get(reference.collection);\n\t\tif (!ids) {\n\t\t\tids = new Set();\n\t\t\treferences.set(reference.collection, ids);\n\t\t}\n\t\tids.add(reference.id);\n\t}\n\treturn references;\n}\n\nfunction getContentReference(item: MenuItemRow): { collection: string; id: string } | null {\n\tif (item.type === \"page\" || item.type === \"post\") {\n\t\tif (!item.reference_id) return null;\n\t\treturn {\n\t\t\tcollection: item.reference_collection || `${item.type}s`,\n\t\t\tid: item.reference_id,\n\t\t};\n\t}\n\tif (item.type === \"collection\") {\n\t\tif (!item.reference_collection || !item.reference_id) return null;\n\t\treturn { collection: item.reference_collection, id: item.reference_id };\n\t}\n\tif (\n\t\titem.type !== \"custom\" &&\n\t\titem.type !== \"taxonomy\" &&\n\t\titem.reference_collection &&\n\t\titem.reference_id\n\t) {\n\t\treturn { collection: item.reference_collection, id: item.reference_id };\n\t}\n\treturn null;\n}\n\n/**\n * Look up the `url_pattern` for a set of collection slugs, request-cached so\n * a page rendering several menus (header, footer, ...) only pays for the\n * lookup once per distinct slug set. Callers must treat the returned map as\n * read-only — it is shared across cache hits within the request.\n */\nfunction getCollectionUrlPatterns(\n\tdb: Kysely<Database>,\n\tcollectionSlugs: Set<string>,\n): Promise<Map<string, string | null>> {\n\tconst key = `menu-collection-patterns:${[...collectionSlugs].toSorted().join(\",\")}`;\n\treturn requestCached(key, async () => {\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\tconst urlPatterns = new Map<string, string | null>();\n\t\tfor (const row of rows) urlPatterns.set(row.slug, row.url_pattern);\n\t\treturn urlPatterns;\n\t});\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 look it up against\n * the per-locale ec_* row or per-locale taxonomy row.\n */\nfunction resolveMenuItem(\n\titem: MenuItemRow,\n\turlPatterns: Map<string, string | null>,\n\tcontentLookup: ContentReferenceLookup,\n\ttaxonomyLookup: TaxonomyReferenceLookup,\n): MenuItem | null {\n\tlet url: string | null;\n\n\tswitch (item.type) {\n\t\tcase \"custom\":\n\t\t\turl = item.custom_url || \"#\";\n\t\t\tbreak;\n\n\t\tcase \"page\":\n\t\tcase \"post\":\n\t\t\turl = resolveContentUrl(\n\t\t\t\titem.reference_collection || `${item.type}s`,\n\t\t\t\titem.reference_id,\n\t\t\t\turlPatterns,\n\t\t\t\tcontentLookup,\n\t\t\t);\n\t\t\tif (url === null) return null;\n\t\t\tbreak;\n\n\t\tcase \"taxonomy\":\n\t\t\turl = resolveTaxonomyUrl(item.reference_id, taxonomyLookup);\n\t\t\tif (url === null) return null;\n\t\t\tbreak;\n\n\t\tcase \"collection\":\n\t\t\t// Two shapes share this type: the admin content picker stores\n\t\t\t// entries from custom collections as \"collection\" with a\n\t\t\t// reference_id, while archive links carry only the collection\n\t\t\t// slug. Entry references resolve like page/post items.\n\t\t\tif (!item.reference_collection) return null;\n\t\t\tif (item.reference_id) {\n\t\t\t\turl = resolveContentUrl(\n\t\t\t\t\titem.reference_collection,\n\t\t\t\t\titem.reference_id,\n\t\t\t\t\turlPatterns,\n\t\t\t\t\tcontentLookup,\n\t\t\t\t);\n\t\t\t\tif (url === null) return null;\n\t\t\t} else {\n\t\t\t\turl = `/${item.reference_collection}/`;\n\t\t\t}\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tif (item.reference_collection && item.reference_id) {\n\t\t\t\turl = resolveContentUrl(\n\t\t\t\t\titem.reference_collection,\n\t\t\t\t\titem.reference_id,\n\t\t\t\t\turlPatterns,\n\t\t\t\t\tcontentLookup,\n\t\t\t\t);\n\t\t\t\tif (url === null) return null;\n\t\t\t} else {\n\t\t\t\turl = \"#\";\n\t\t\t}\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\ninterface ContentReferenceRow {\n\tid: string;\n\tslug: string;\n\tlocale: string;\n\ttranslation_group: string;\n}\n\ninterface ResolvedContentReference {\n\tid: string;\n\tslug: string;\n}\n\ntype ContentReferenceLookup = Map<string, Map<string, ResolvedContentReference>>;\n\nasync function resolveContentReferences(\n\tdb: Kysely<Database>,\n\treferences: Map<string, Set<string>>,\n\tlocale: string,\n): Promise<ContentReferenceLookup> {\n\tconst entries = await Promise.all(\n\t\tArray.from(references, async ([collection, referenceGroups]) => {\n\t\t\tconst lookup = new Map<string, ResolvedContentReference>();\n\t\t\tconst localized = new Map<string, ContentReferenceRow>();\n\t\t\ttry {\n\t\t\t\tvalidateIdentifier(collection, \"menu item collection\");\n\t\t\t\tfor (const batch of chunks([...referenceGroups], SQL_BATCH_SIZE)) {\n\t\t\t\t\tconst result = await sql<ContentReferenceRow>`\n\t\t\t\t\t\tSELECT id, slug, locale, translation_group\n\t\t\t\t\t\tFROM ${sql.ref(`ec_${collection}`)}\n\t\t\t\t\t\tWHERE translation_group IN (${sql.join(batch)})\n\t\t\t\t\t`.execute(db);\n\t\t\t\t\tfor (const row of result.rows) {\n\t\t\t\t\t\tconst existing = localized.get(row.translation_group);\n\t\t\t\t\t\tif (shouldPreferLocalizedRow(row, existing, locale)) {\n\t\t\t\t\t\t\tlocalized.set(row.translation_group, row);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor (const [referenceGroup, row] of localized) {\n\t\t\t\t\tlookup.set(referenceGroup, { id: row.id, slug: row.slug });\n\t\t\t\t}\n\n\t\t\t\tconst unresolved = [...referenceGroups].filter((id) => !lookup.has(id));\n\t\t\t\tfor (const batch of chunks(unresolved, SQL_BATCH_SIZE)) {\n\t\t\t\t\tconst result = await sql<ResolvedContentReference>`\n\t\t\t\t\t\tSELECT id, slug FROM ${sql.ref(`ec_${collection}`)}\n\t\t\t\t\t\tWHERE id IN (${sql.join(batch)})\n\t\t\t\t\t`.execute(db);\n\t\t\t\t\tfor (const row of result.rows) lookup.set(row.id, row);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(`Failed to resolve content URLs for ${collection}:`, error);\n\t\t\t}\n\t\t\treturn [collection, lookup] as const;\n\t\t}),\n\t);\n\treturn new Map(entries);\n}\n\ninterface LocalizedReference {\n\tid: string;\n\tlocale: string;\n}\n\nfunction shouldPreferLocalizedRow(\n\tcandidate: LocalizedReference,\n\texisting: LocalizedReference | undefined,\n\tlocale: string,\n): boolean {\n\tif (!existing) return true;\n\tif (candidate.locale === locale) return existing.locale !== locale || candidate.id < existing.id;\n\tif (existing.locale === locale) return false;\n\treturn (\n\t\tcandidate.locale < existing.locale ||\n\t\t(candidate.locale === existing.locale && candidate.id < existing.id)\n\t);\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 */\nfunction resolveContentUrl(\n\tcollection: string,\n\treferenceGroup: string | null,\n\turlPatterns: Map<string, string | null>,\n\tcontentLookup: ContentReferenceLookup,\n): string | null {\n\tif (!referenceGroup) return null;\n\tconst row = contentLookup.get(collection)?.get(referenceGroup);\n\tif (!row) return null;\n\tconst pattern = urlPatterns.get(collection);\n\tif (pattern) return interpolateUrlPattern(pattern, row.slug, row.id);\n\treturn `/${collection}/${row.slug}`;\n}\n\ninterface TaxonomyReferenceRow {\n\tid: string;\n\tname: string;\n\tslug: string;\n\tlocale: string;\n\ttranslation_group: string;\n}\n\ninterface ResolvedTaxonomyReference {\n\tname: string;\n\tslug: string;\n}\n\ntype TaxonomyReferenceLookup = Map<string, ResolvedTaxonomyReference>;\n\nasync function resolveTaxonomyReferences(\n\tdb: Kysely<Database>,\n\treferenceGroups: Set<string>,\n\tlocale: string,\n): Promise<TaxonomyReferenceLookup> {\n\tconst lookup = new Map<string, ResolvedTaxonomyReference>();\n\tconst localized = new Map<string, TaxonomyReferenceRow>();\n\ttry {\n\t\tfor (const batch of chunks([...referenceGroups], SQL_BATCH_SIZE)) {\n\t\t\tconst rows = await db\n\t\t\t\t.selectFrom(\"taxonomies\")\n\t\t\t\t.select([\"id\", \"name\", \"slug\", \"locale\", \"translation_group\"])\n\t\t\t\t.where(\"translation_group\", \"in\", batch)\n\t\t\t\t.$narrowType<{ translation_group: string }>()\n\t\t\t\t.execute();\n\t\t\tfor (const row of rows) {\n\t\t\t\tconst existing = localized.get(row.translation_group);\n\t\t\t\tif (shouldPreferLocalizedRow(row, existing, locale)) {\n\t\t\t\t\tlocalized.set(row.translation_group, row);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor (const [referenceGroup, row] of localized) {\n\t\t\tlookup.set(referenceGroup, { name: row.name, slug: row.slug });\n\t\t}\n\n\t\tconst unresolved = [...referenceGroups].filter((id) => !lookup.has(id));\n\t\tfor (const batch of chunks(unresolved, SQL_BATCH_SIZE)) {\n\t\t\tconst rows = await db\n\t\t\t\t.selectFrom(\"taxonomies\")\n\t\t\t\t.select([\"id\", \"name\", \"slug\"])\n\t\t\t\t.where(\"id\", \"in\", batch)\n\t\t\t\t.execute();\n\t\t\tfor (const row of rows) lookup.set(row.id, { name: row.name, slug: row.slug });\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(\"Failed to resolve taxonomy URLs:\", error);\n\t}\n\treturn lookup;\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 */\nfunction resolveTaxonomyUrl(\n\treferenceGroup: string | null,\n\ttaxonomyLookup: TaxonomyReferenceLookup,\n): string | null {\n\tif (!referenceGroup) return null;\n\tconst taxonomy = taxonomyLookup.get(referenceGroup);\n\tif (!taxonomy) return null;\n\treturn `/${taxonomy.name}/${taxonomy.slug}`;\n}\n"],"mappings":"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"}