@character-foundry/character-foundry 0.4.1 → 0.4.2-dev.1765997746
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/charx.cjs +17 -38
- package/dist/charx.cjs.map +1 -1
- package/dist/charx.d.cts +27 -18
- package/dist/charx.d.ts +27 -18
- package/dist/charx.js +17 -38
- package/dist/charx.js.map +1 -1
- package/dist/exporter.cjs +36 -40
- package/dist/exporter.cjs.map +1 -1
- package/dist/exporter.d.cts +27 -18
- package/dist/exporter.d.ts +27 -18
- package/dist/exporter.js +36 -40
- package/dist/exporter.js.map +1 -1
- package/dist/federation.cjs +104 -36
- package/dist/federation.cjs.map +1 -1
- package/dist/federation.d.cts +62 -18
- package/dist/federation.d.ts +62 -18
- package/dist/federation.js +104 -36
- package/dist/federation.js.map +1 -1
- package/dist/index.cjs +36 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +63 -42
- package/dist/index.d.ts +63 -42
- package/dist/index.js +36 -40
- package/dist/index.js.map +1 -1
- package/dist/loader.cjs +103 -17
- package/dist/loader.cjs.map +1 -1
- package/dist/loader.d.cts +56 -28
- package/dist/loader.d.ts +56 -28
- package/dist/loader.js +103 -17
- package/dist/loader.js.map +1 -1
- package/dist/lorebook.d.cts +51 -34
- package/dist/lorebook.d.ts +51 -34
- package/dist/normalizer.cjs +4 -4
- package/dist/normalizer.cjs.map +1 -1
- package/dist/normalizer.d.cts +90 -60
- package/dist/normalizer.d.ts +90 -60
- package/dist/normalizer.js +4 -4
- package/dist/normalizer.js.map +1 -1
- package/dist/png.cjs +4 -4
- package/dist/png.cjs.map +1 -1
- package/dist/png.d.cts +48 -32
- package/dist/png.d.ts +48 -32
- package/dist/png.js +4 -4
- package/dist/png.js.map +1 -1
- package/dist/schemas.cjs +9 -9
- package/dist/schemas.cjs.map +1 -1
- package/dist/schemas.d.cts +144 -96
- package/dist/schemas.d.ts +144 -96
- package/dist/schemas.js +9 -9
- package/dist/schemas.js.map +1 -1
- package/dist/voxta.cjs +23 -6
- package/dist/voxta.cjs.map +1 -1
- package/dist/voxta.d.cts +42 -28
- package/dist/voxta.d.ts +42 -28
- package/dist/voxta.js +23 -6
- package/dist/voxta.js.map +1 -1
- package/package.json +6 -6
package/dist/federation.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/federation.ts","../../core/src/binary.ts","../../core/src/base64.ts","../../core/src/errors.ts","../../core/src/data-url.ts","../../core/src/uri.ts","../../core/src/image.ts","../../core/src/uuid.ts","../../federation/src/activitypub.ts","../../federation/src/sync-engine.ts","../../federation/src/state-store.ts","../../federation/src/d1-store.ts","../../federation/src/adapters/base.ts","../../federation/src/adapters/http.ts","../../federation/src/adapters/sillytavern.ts","../../federation/src/routes/webfinger.ts","../../federation/src/routes/nodeinfo.ts","../../federation/src/routes/actor.ts","../../federation/src/moderation/activities.ts","../../federation/src/http-signatures.ts","../../federation/src/routes/inbox.ts","../../federation/src/moderation/store.ts","../../federation/src/moderation/d1-store.ts","../../federation/src/moderation/policy-engine.ts","../../federation/src/moderation/rate-limiter.ts","../../federation/src/index.ts"],"sourcesContent":["export * from '@character-foundry/federation';\n","/**\n * Binary Data Utilities\n *\n * Universal binary data operations using Uint8Array.\n * Works in both Node.js and browser environments.\n */\n\n/**\n * Universal binary data type (works in both environments)\n */\nexport type BinaryData = Uint8Array;\n\n/**\n * Read a 32-bit big-endian unsigned integer\n */\nexport function readUInt32BE(data: BinaryData, offset: number): number {\n return (\n (data[offset]! << 24) |\n (data[offset + 1]! << 16) |\n (data[offset + 2]! << 8) |\n data[offset + 3]!\n ) >>> 0;\n}\n\n/**\n * Write a 32-bit big-endian unsigned integer\n */\nexport function writeUInt32BE(data: BinaryData, value: number, offset: number): void {\n data[offset] = (value >>> 24) & 0xff;\n data[offset + 1] = (value >>> 16) & 0xff;\n data[offset + 2] = (value >>> 8) & 0xff;\n data[offset + 3] = value & 0xff;\n}\n\n/**\n * Read a 16-bit big-endian unsigned integer\n */\nexport function readUInt16BE(data: BinaryData, offset: number): number {\n return ((data[offset]! << 8) | data[offset + 1]!) >>> 0;\n}\n\n/**\n * Write a 16-bit big-endian unsigned integer\n */\nexport function writeUInt16BE(data: BinaryData, value: number, offset: number): void {\n data[offset] = (value >>> 8) & 0xff;\n data[offset + 1] = value & 0xff;\n}\n\n/**\n * Find a byte sequence in binary data\n */\nexport function indexOf(data: BinaryData, search: BinaryData, fromIndex = 0): number {\n outer: for (let i = fromIndex; i <= data.length - search.length; i++) {\n for (let j = 0; j < search.length; j++) {\n if (data[i + j] !== search[j]) continue outer;\n }\n return i;\n }\n return -1;\n}\n\n/**\n * Concatenate multiple binary arrays\n */\nexport function concat(...arrays: BinaryData[]): BinaryData {\n const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const arr of arrays) {\n result.set(arr, offset);\n offset += arr.length;\n }\n return result;\n}\n\n/**\n * Slice binary data (returns a view, not a copy)\n */\nexport function slice(data: BinaryData, start: number, end?: number): BinaryData {\n return data.subarray(start, end);\n}\n\n/**\n * Copy a portion of binary data (returns a new array)\n */\nexport function copy(data: BinaryData, start: number, end?: number): BinaryData {\n return data.slice(start, end);\n}\n\n/**\n * Convert string to binary (UTF-8)\n */\nexport function fromString(str: string): BinaryData {\n return new TextEncoder().encode(str);\n}\n\n/**\n * Convert binary to string (UTF-8)\n */\nexport function toString(data: BinaryData): string {\n return new TextDecoder().decode(data);\n}\n\n/**\n * Convert string to binary (Latin1 - for PNG keywords and similar)\n */\nexport function fromLatin1(str: string): BinaryData {\n const result = new Uint8Array(str.length);\n for (let i = 0; i < str.length; i++) {\n result[i] = str.charCodeAt(i) & 0xff;\n }\n return result;\n}\n\n/**\n * Convert binary to string (Latin1)\n */\nexport function toLatin1(data: BinaryData): string {\n let result = '';\n for (let i = 0; i < data.length; i++) {\n result += String.fromCharCode(data[i]!);\n }\n return result;\n}\n\n/**\n * Compare two binary arrays for equality\n */\nexport function equals(a: BinaryData, b: BinaryData): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n\n/**\n * Create a new Uint8Array filled with zeros\n */\nexport function alloc(size: number): BinaryData {\n return new Uint8Array(size);\n}\n\n/**\n * Create a Uint8Array from an array of numbers\n */\nexport function from(data: number[] | ArrayBuffer | BinaryData): BinaryData {\n if (data instanceof Uint8Array) {\n return data;\n }\n if (data instanceof ArrayBuffer) {\n return new Uint8Array(data);\n }\n return new Uint8Array(data);\n}\n\n/**\n * Check if value is a Uint8Array\n */\nexport function isBinaryData(value: unknown): value is BinaryData {\n return value instanceof Uint8Array;\n}\n\n/**\n * Convert Node.js Buffer to Uint8Array (no-op if already Uint8Array)\n * This provides compatibility when interfacing with Node.js code\n */\nexport function toUint8Array(data: BinaryData | Buffer): BinaryData {\n if (data instanceof Uint8Array) {\n // Buffer extends Uint8Array, but we want a plain Uint8Array\n // This ensures we get a proper Uint8Array in all cases\n if (Object.getPrototypeOf(data).constructor.name === 'Buffer') {\n return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);\n }\n return data;\n }\n return new Uint8Array(data);\n}\n\n/**\n * Convert binary data to hex string\n */\nexport function toHex(data: BinaryData): string {\n return Array.from(data)\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\n/**\n * Convert hex string to binary data\n */\nexport function fromHex(hex: string): BinaryData {\n const bytes = new Uint8Array(hex.length / 2);\n for (let i = 0; i < bytes.length; i++) {\n bytes[i] = parseInt(hex.substr(i * 2, 2), 16);\n }\n return bytes;\n}\n","/**\n * Universal Base64 Encoding/Decoding\n *\n * Works in both Node.js and browser environments.\n */\n\nimport type { BinaryData } from './binary.js';\n\n/**\n * Check if we're in a Node.js environment\n */\nconst isNode = typeof process !== 'undefined' &&\n process.versions != null &&\n process.versions.node != null;\n\n/**\n * Threshold for switching to chunked encoding in browsers (1MB)\n * Below this, simple string concatenation is fast enough.\n * Above this, quadratic string growth becomes a problem.\n */\nconst LARGE_BUFFER_THRESHOLD = 1024 * 1024;\n\n/**\n * Encode binary data to base64 string\n *\n * PERFORMANCE: For large buffers (>1MB) in browsers, this automatically\n * uses the chunked implementation to avoid quadratic string concatenation.\n */\nexport function encode(data: BinaryData): string {\n if (isNode) {\n // Node.js: Buffer handles large data efficiently\n return Buffer.from(data).toString('base64');\n }\n\n // Browser: use chunked encoding for large buffers to avoid O(n²) string growth\n if (data.length > LARGE_BUFFER_THRESHOLD) {\n return encodeChunked(data);\n }\n\n // Small buffers: simple approach is fast enough\n let binary = '';\n for (let i = 0; i < data.length; i++) {\n binary += String.fromCharCode(data[i]!);\n }\n return btoa(binary);\n}\n\n/**\n * Decode base64 string to binary data\n */\nexport function decode(base64: string): BinaryData {\n if (isNode) {\n // Node.js: use Buffer\n return new Uint8Array(Buffer.from(base64, 'base64'));\n }\n\n // Browser: use atob\n const binary = atob(base64);\n const result = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n result[i] = binary.charCodeAt(i);\n }\n return result;\n}\n\n/**\n * Check if a string is valid base64\n */\nexport function isBase64(str: string): boolean {\n if (str.length === 0) return false;\n // Base64 regex: only valid base64 characters, length multiple of 4 (with padding)\n const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;\n return base64Regex.test(str) && str.length % 4 === 0;\n}\n\n/**\n * Encode binary data to URL-safe base64 string\n * Replaces + with -, / with _, and removes padding\n */\nexport function encodeUrlSafe(data: BinaryData): string {\n return encode(data)\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n}\n\n/**\n * Decode URL-safe base64 string to binary data\n */\nexport function decodeUrlSafe(base64: string): BinaryData {\n // Add back padding if needed\n let padded = base64\n .replace(/-/g, '+')\n .replace(/_/g, '/');\n\n while (padded.length % 4 !== 0) {\n padded += '=';\n }\n\n return decode(padded);\n}\n\n/**\n * Chunk size for encoding large buffers (64KB)\n * Prevents stack overflow when using String.fromCharCode with spread operator\n */\nconst ENCODE_CHUNK_SIZE = 64 * 1024;\n\n/**\n * Encode binary data to base64 string with chunking for large buffers.\n * Handles buffers >10MB without stack overflow.\n *\n * @param data - Binary data to encode\n * @returns Base64 encoded string\n *\n * @example\n * ```typescript\n * const largeBuffer = new Uint8Array(20 * 1024 * 1024); // 20MB\n * const base64 = encodeChunked(largeBuffer); // No stack overflow\n * ```\n */\nexport function encodeChunked(data: BinaryData): string {\n if (isNode) {\n // Node.js: Buffer handles large data efficiently\n return Buffer.from(data).toString('base64');\n }\n\n // Browser: process in chunks to avoid stack overflow\n const chunks: string[] = [];\n\n for (let i = 0; i < data.length; i += ENCODE_CHUNK_SIZE) {\n const chunk = data.subarray(i, Math.min(i + ENCODE_CHUNK_SIZE, data.length));\n let binary = '';\n for (let j = 0; j < chunk.length; j++) {\n binary += String.fromCharCode(chunk[j]!);\n }\n chunks.push(binary);\n }\n\n return btoa(chunks.join(''));\n}\n","/**\n * Error Classes\n *\n * Specific error types for character card operations.\n * All errors extend FoundryError for consistent handling.\n */\n\n/** Symbol to identify FoundryError instances across ESM/CJS boundaries */\nconst FOUNDRY_ERROR_MARKER = Symbol.for('@character-foundry/core:FoundryError');\n\n/**\n * Base error class for all Character Foundry errors\n */\nexport class FoundryError extends Error {\n /** @internal Marker for cross-module identification */\n readonly [FOUNDRY_ERROR_MARKER] = true;\n\n constructor(message: string, public readonly code: string) {\n super(message);\n this.name = 'FoundryError';\n // Maintains proper stack trace in V8 environments\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, this.constructor);\n }\n }\n}\n\n/**\n * Error during card parsing\n */\nexport class ParseError extends FoundryError {\n constructor(message: string, public readonly format?: string) {\n super(message, 'PARSE_ERROR');\n this.name = 'ParseError';\n }\n}\n\n/**\n * Error during card validation\n */\nexport class ValidationError extends FoundryError {\n constructor(message: string, public readonly field?: string) {\n super(message, 'VALIDATION_ERROR');\n this.name = 'ValidationError';\n }\n}\n\n/**\n * Asset not found in card or archive\n */\nexport class AssetNotFoundError extends FoundryError {\n constructor(public readonly uri: string) {\n super(`Asset not found: ${uri}`, 'ASSET_NOT_FOUND');\n this.name = 'AssetNotFoundError';\n }\n}\n\n/**\n * Format not supported for operation\n */\nexport class FormatNotSupportedError extends FoundryError {\n constructor(public readonly format: string, operation?: string) {\n const msg = operation\n ? `Format '${format}' not supported for ${operation}`\n : `Format not supported: ${format}`;\n super(msg, 'FORMAT_NOT_SUPPORTED');\n this.name = 'FormatNotSupportedError';\n }\n}\n\n/**\n * File size exceeds limits\n */\nexport class SizeLimitError extends FoundryError {\n constructor(\n public readonly actualSize: number,\n public readonly maxSize: number,\n context?: string\n ) {\n const actualMB = (actualSize / 1024 / 1024).toFixed(2);\n const maxMB = (maxSize / 1024 / 1024).toFixed(2);\n const msg = context\n ? `${context}: Size ${actualMB}MB exceeds limit ${maxMB}MB`\n : `Size ${actualMB}MB exceeds limit ${maxMB}MB`;\n super(msg, 'SIZE_LIMIT_EXCEEDED');\n this.name = 'SizeLimitError';\n }\n}\n\n/**\n * Path traversal or unsafe path detected\n */\nexport class PathTraversalError extends FoundryError {\n constructor(public readonly path: string) {\n super(`Unsafe path detected: ${path}`, 'PATH_TRAVERSAL');\n this.name = 'PathTraversalError';\n }\n}\n\n/**\n * Export operation would lose data\n */\nexport class DataLossError extends FoundryError {\n constructor(\n public readonly lostFields: string[],\n public readonly targetFormat: string\n ) {\n const fields = lostFields.slice(0, 3).join(', ');\n const more = lostFields.length > 3 ? ` and ${lostFields.length - 3} more` : '';\n super(\n `Export to ${targetFormat} would lose: ${fields}${more}`,\n 'DATA_LOSS'\n );\n this.name = 'DataLossError';\n }\n}\n\n/**\n * Check if an error is a FoundryError\n *\n * Uses Symbol.for() marker instead of instanceof to handle dual ESM/CJS package loading.\n * In dual-package environments, instanceof can fail if the error comes from a different\n * module instance (e.g., ESM vs CJS version of the same package). Symbol.for() creates\n * a global symbol shared across all module instances.\n */\nexport function isFoundryError(error: unknown): error is FoundryError {\n return (\n error instanceof Error &&\n FOUNDRY_ERROR_MARKER in error &&\n (error as Record<symbol, unknown>)[FOUNDRY_ERROR_MARKER] === true\n );\n}\n\n/**\n * Wrap unknown errors in a FoundryError\n */\nexport function wrapError(error: unknown, context?: string): FoundryError {\n if (isFoundryError(error)) {\n return error;\n }\n\n const message = error instanceof Error\n ? error.message\n : String(error);\n\n return new FoundryError(\n context ? `${context}: ${message}` : message,\n 'UNKNOWN_ERROR'\n );\n}\n","/**\n * Data URL Utilities\n *\n * Convert between Uint8Array buffers and data URLs.\n * Handles large buffers (>10MB) without stack overflow by processing in chunks.\n */\n\nimport type { BinaryData } from './binary.js';\nimport { encodeChunked as base64Encode, decode as base64Decode } from './base64.js';\nimport { ValidationError } from './errors.js';\n\n/**\n * Convert Uint8Array to data URL.\n * Handles large buffers (>10MB) without stack overflow by processing in chunks.\n *\n * @param buffer - Binary data to encode\n * @param mimeType - MIME type for the data URL (e.g., 'image/png', 'application/octet-stream')\n * @returns Data URL string\n *\n * @example\n * ```typescript\n * const png = new Uint8Array([...]);\n * const dataUrl = toDataURL(png, 'image/png');\n * // => \"data:image/png;base64,iVBORw0KGgo...\"\n * ```\n */\nexport function toDataURL(buffer: BinaryData, mimeType: string): string {\n // Use chunked encoding to handle large buffers without stack overflow\n const base64 = base64Encode(buffer);\n return `data:${mimeType};base64,${base64}`;\n}\n\n/**\n * Parse a data URL back to buffer and MIME type.\n * Validates the data URL format before parsing.\n *\n * @param dataUrl - Data URL string to parse\n * @returns Object containing the decoded buffer and MIME type\n * @throws Error if the data URL format is invalid\n *\n * @example\n * ```typescript\n * const { buffer, mimeType } = fromDataURL('data:image/png;base64,iVBORw0KGgo...');\n * // buffer: Uint8Array\n * // mimeType: 'image/png'\n * ```\n */\nexport function fromDataURL(dataUrl: string): { buffer: Uint8Array; mimeType: string } {\n // Validate data URL format\n if (!dataUrl.startsWith('data:')) {\n throw new ValidationError('Invalid data URL: must start with \"data:\"', 'dataUrl');\n }\n\n const commaIndex = dataUrl.indexOf(',');\n if (commaIndex === -1) {\n throw new ValidationError('Invalid data URL: missing comma separator', 'dataUrl');\n }\n\n const header = dataUrl.slice(5, commaIndex); // Skip 'data:'\n const data = dataUrl.slice(commaIndex + 1);\n\n // Parse header: [<mediatype>][;base64]\n let mimeType = 'text/plain';\n let isBase64 = false;\n\n const parts = header.split(';');\n for (const part of parts) {\n if (part === 'base64') {\n isBase64 = true;\n } else if (part && !part.includes('=')) {\n // MIME type (not a parameter like charset=utf-8)\n mimeType = part;\n }\n }\n\n if (!isBase64) {\n // URL-encoded text data\n throw new ValidationError('Non-base64 data URLs are not supported', 'dataUrl');\n }\n\n const buffer = base64Decode(data);\n return { buffer, mimeType };\n}\n\n/**\n * Check if a string is a valid data URL\n *\n * @param str - String to check\n * @returns true if the string is a valid data URL format\n */\nexport function isDataURL(str: string): boolean {\n if (!str.startsWith('data:')) return false;\n const commaIndex = str.indexOf(',');\n if (commaIndex === -1) return false;\n const header = str.slice(5, commaIndex);\n return header.includes('base64');\n}\n","/**\n * URI Utilities\n *\n * Handles different asset URI schemes used in character cards.\n * Supports: embeded://, embedded://, ccdefault:, https://, http://,\n * data:, file://, __asset:, asset:, chara-ext-asset_\n */\n\nexport type URIScheme =\n | 'embeded' // embeded:// (CharX standard, note intentional typo)\n | 'ccdefault' // ccdefault:\n | 'https' // https://\n | 'http' // http://\n | 'data' // data:mime;base64,...\n | 'file' // file://\n | 'internal' // Internal asset ID (UUID/string)\n | 'pngchunk' // PNG chunk reference (__asset:, asset:, chara-ext-asset_)\n | 'unknown';\n\nexport interface ParsedURI {\n scheme: URIScheme;\n originalUri: string;\n normalizedUri: string; // Normalized form of the URI\n path?: string; // For embeded://, file://\n url?: string; // For http://, https://\n data?: string; // For data: URIs\n mimeType?: string; // For data: URIs\n encoding?: string; // For data: URIs (e.g., base64)\n chunkKey?: string; // For pngchunk - the key/index to look up\n chunkCandidates?: string[]; // For pngchunk - all possible chunk keys to search\n}\n\n/**\n * Normalize a URI to its canonical form\n * Handles common typos and variant formats\n */\nexport function normalizeURI(uri: string): string {\n const trimmed = uri.trim();\n\n // Fix embedded:// -> embeded:// (common typo, CharX spec uses single 'd')\n if (trimmed.startsWith('embedded://')) {\n return 'embeded://' + trimmed.substring('embedded://'.length);\n }\n\n // Normalize PNG chunk references to pngchunk: scheme\n if (trimmed.startsWith('__asset:')) {\n const id = trimmed.substring('__asset:'.length);\n return `pngchunk:${id}`;\n }\n if (trimmed.startsWith('asset:')) {\n const id = trimmed.substring('asset:'.length);\n return `pngchunk:${id}`;\n }\n if (trimmed.startsWith('chara-ext-asset_:')) {\n const id = trimmed.substring('chara-ext-asset_:'.length);\n return `pngchunk:${id}`;\n }\n if (trimmed.startsWith('chara-ext-asset_')) {\n const id = trimmed.substring('chara-ext-asset_'.length);\n return `pngchunk:${id}`;\n }\n\n return trimmed;\n}\n\n/**\n * Parse a URI and determine its scheme and components\n */\nexport function parseURI(uri: string): ParsedURI {\n const trimmed = uri.trim();\n const normalized = normalizeURI(trimmed);\n\n // PNG chunk references (__asset:, asset:, chara-ext-asset_, pngchunk:)\n if (\n trimmed.startsWith('__asset:') ||\n trimmed.startsWith('asset:') ||\n trimmed.startsWith('chara-ext-asset_') ||\n trimmed.startsWith('pngchunk:')\n ) {\n let assetId: string;\n if (trimmed.startsWith('__asset:')) {\n assetId = trimmed.substring('__asset:'.length);\n } else if (trimmed.startsWith('asset:')) {\n assetId = trimmed.substring('asset:'.length);\n } else if (trimmed.startsWith('chara-ext-asset_:')) {\n assetId = trimmed.substring('chara-ext-asset_:'.length);\n } else if (trimmed.startsWith('pngchunk:')) {\n assetId = trimmed.substring('pngchunk:'.length);\n } else {\n assetId = trimmed.substring('chara-ext-asset_'.length);\n }\n\n // Generate all possible chunk key variations for lookup\n const candidates = [\n assetId, // \"0\" or \"filename.png\"\n trimmed, // Original URI\n `asset:${assetId}`, // \"asset:0\"\n `__asset:${assetId}`, // \"__asset:0\"\n `__asset_${assetId}`, // \"__asset_0\"\n `chara-ext-asset_${assetId}`, // \"chara-ext-asset_0\"\n `chara-ext-asset_:${assetId}`, // \"chara-ext-asset_:0\"\n `pngchunk:${assetId}`, // \"pngchunk:0\"\n ];\n\n return {\n scheme: 'pngchunk',\n originalUri: uri,\n normalizedUri: normalized,\n chunkKey: assetId,\n chunkCandidates: candidates,\n };\n }\n\n // ccdefault: - use default asset\n if (trimmed === 'ccdefault:' || trimmed.startsWith('ccdefault:')) {\n return {\n scheme: 'ccdefault',\n originalUri: uri,\n normalizedUri: normalized,\n };\n }\n\n // embeded:// or embedded:// (normalize typo)\n if (trimmed.startsWith('embeded://') || trimmed.startsWith('embedded://')) {\n const path = trimmed.startsWith('embeded://')\n ? trimmed.substring('embeded://'.length)\n : trimmed.substring('embedded://'.length);\n return {\n scheme: 'embeded',\n originalUri: uri,\n normalizedUri: normalized,\n path,\n };\n }\n\n // https://\n if (trimmed.startsWith('https://')) {\n return {\n scheme: 'https',\n originalUri: uri,\n normalizedUri: normalized,\n url: trimmed,\n };\n }\n\n // http://\n if (trimmed.startsWith('http://')) {\n return {\n scheme: 'http',\n originalUri: uri,\n normalizedUri: normalized,\n url: trimmed,\n };\n }\n\n // data: URIs\n if (trimmed.startsWith('data:')) {\n const parsed = parseDataURI(trimmed);\n return {\n scheme: 'data',\n originalUri: uri,\n normalizedUri: normalized,\n ...parsed,\n };\n }\n\n // file://\n if (trimmed.startsWith('file://')) {\n const path = trimmed.substring('file://'.length);\n return {\n scheme: 'file',\n originalUri: uri,\n normalizedUri: normalized,\n path,\n };\n }\n\n // Internal asset ID (alphanumeric/UUID format)\n if (/^[a-zA-Z0-9_-]+$/.test(trimmed)) {\n return {\n scheme: 'internal',\n originalUri: uri,\n normalizedUri: normalized,\n path: trimmed,\n };\n }\n\n // Unknown scheme\n return {\n scheme: 'unknown',\n originalUri: uri,\n normalizedUri: normalized,\n };\n}\n\n/**\n * Parse a data URI into its components\n * Format: data:[<mediatype>][;base64],<data>\n */\nfunction parseDataURI(uri: string): { mimeType?: string; encoding?: string; data?: string } {\n const match = uri.match(/^data:([^;,]+)?(;base64)?,(.*)$/);\n\n if (!match) {\n return {};\n }\n\n return {\n mimeType: match[1] || 'text/plain',\n encoding: match[2] ? 'base64' : undefined,\n data: match[3],\n };\n}\n\n/**\n * Check if extension is an image format\n */\nexport function isImageExt(ext: string): boolean {\n const imageExts = ['png', 'jpg', 'jpeg', 'webp', 'gif', 'avif', 'bmp', 'svg'];\n return imageExts.includes(ext.toLowerCase());\n}\n\n/**\n * Check if extension is an audio format\n */\nexport function isAudioExt(ext: string): boolean {\n const audioExts = ['mp3', 'wav', 'ogg', 'flac', 'm4a', 'aac'];\n return audioExts.includes(ext.toLowerCase());\n}\n\n/**\n * Check if extension is a video format\n */\nexport function isVideoExt(ext: string): boolean {\n const videoExts = ['mp4', 'webm', 'avi', 'mov', 'mkv'];\n return videoExts.includes(ext.toLowerCase());\n}\n\n/** Safe MIME types for data: URIs that can be used in href/src */\nconst SAFE_DATA_URI_MIME_TYPES = new Set([\n // Images (safe for img src)\n 'image/png',\n 'image/jpeg',\n 'image/gif',\n 'image/webp',\n 'image/avif',\n 'image/bmp',\n 'image/x-icon',\n // Audio (safe for audio src)\n 'audio/mpeg',\n 'audio/wav',\n 'audio/ogg',\n 'audio/flac',\n 'audio/mp4',\n 'audio/aac',\n // Video (safe for video src)\n 'video/mp4',\n 'video/webm',\n // Text/data (generally safe)\n 'text/plain',\n 'application/json',\n 'application/octet-stream',\n]);\n\n/** Potentially dangerous MIME types that should NOT be used in href/src */\nconst DANGEROUS_DATA_URI_MIME_TYPES = new Set([\n // Executable/script content\n 'text/html',\n 'text/javascript',\n 'application/javascript',\n 'application/x-javascript',\n 'text/css',\n 'image/svg+xml', // SVG can contain scripts\n 'application/xhtml+xml',\n 'application/xml',\n]);\n\n/**\n * Options for URI safety validation\n */\nexport interface URISafetyOptions {\n /** Allow http:// URIs (default: false) */\n allowHttp?: boolean;\n /** Allow file:// URIs (default: false) */\n allowFile?: boolean;\n /**\n * Allowed MIME types for data: URIs (default: all safe types).\n * Set to empty array to reject all data: URIs.\n * Set to undefined to use default safe list.\n */\n allowedDataMimes?: string[];\n}\n\n/**\n * Result of URI safety check with detailed information\n */\nexport interface URISafetyResult {\n /** Whether the URI is safe to use */\n safe: boolean;\n /** Reason if unsafe */\n reason?: string;\n /** Detected scheme */\n scheme: URIScheme;\n /** MIME type for data: URIs */\n mimeType?: string;\n}\n\n/**\n * Validate if a URI is safe to use (detailed version)\n *\n * @param uri - URI to validate\n * @param options - Safety options\n * @returns Detailed safety result\n */\nexport function checkURISafety(uri: string, options: URISafetyOptions = {}): URISafetyResult {\n const parsed = parseURI(uri);\n\n switch (parsed.scheme) {\n case 'embeded':\n case 'ccdefault':\n case 'internal':\n case 'https':\n case 'pngchunk':\n return { safe: true, scheme: parsed.scheme };\n\n case 'data': {\n const mimeType = parsed.mimeType || 'text/plain';\n\n // Check for explicitly dangerous MIME types\n if (DANGEROUS_DATA_URI_MIME_TYPES.has(mimeType)) {\n return {\n safe: false,\n scheme: parsed.scheme,\n mimeType,\n reason: `Data URI with potentially dangerous MIME type: ${mimeType}`,\n };\n }\n\n // If custom allowed list is provided, check against it\n if (options.allowedDataMimes !== undefined) {\n if (options.allowedDataMimes.length === 0) {\n return {\n safe: false,\n scheme: parsed.scheme,\n mimeType,\n reason: 'Data URIs are not allowed',\n };\n }\n if (!options.allowedDataMimes.includes(mimeType)) {\n return {\n safe: false,\n scheme: parsed.scheme,\n mimeType,\n reason: `Data URI MIME type not in allowed list: ${mimeType}`,\n };\n }\n }\n\n // Otherwise use default safe list\n if (!SAFE_DATA_URI_MIME_TYPES.has(mimeType)) {\n return {\n safe: false,\n scheme: parsed.scheme,\n mimeType,\n reason: `Unknown data URI MIME type: ${mimeType}`,\n };\n }\n\n return { safe: true, scheme: parsed.scheme, mimeType };\n }\n\n case 'http':\n if (options.allowHttp === true) {\n return { safe: true, scheme: parsed.scheme };\n }\n return { safe: false, scheme: parsed.scheme, reason: 'HTTP URIs are not allowed' };\n\n case 'file':\n if (options.allowFile === true) {\n return { safe: true, scheme: parsed.scheme };\n }\n return { safe: false, scheme: parsed.scheme, reason: 'File URIs are not allowed' };\n\n case 'unknown':\n default:\n return { safe: false, scheme: parsed.scheme, reason: 'Unknown URI scheme' };\n }\n}\n\n/**\n * Validate if a URI is safe to use (simple boolean version for backwards compatibility)\n *\n * @deprecated Use checkURISafety() for detailed safety information\n */\nexport function isURISafe(uri: string, options: { allowHttp?: boolean; allowFile?: boolean } = {}): boolean {\n return checkURISafety(uri, options).safe;\n}\n\n/**\n * Extract file extension from URI\n */\nexport function getExtensionFromURI(uri: string): string {\n const parsed = parseURI(uri);\n\n if (parsed.path) {\n const parts = parsed.path.split('.');\n if (parts.length > 1) {\n return parts[parts.length - 1]!.toLowerCase();\n }\n }\n\n if (parsed.url) {\n const urlParts = parsed.url.split('?')[0]!.split('.');\n if (urlParts.length > 1) {\n return urlParts[urlParts.length - 1]!.toLowerCase();\n }\n }\n\n if (parsed.mimeType) {\n return getExtFromMimeType(parsed.mimeType);\n }\n\n return 'unknown';\n}\n\n/**\n * Get MIME type from file extension\n */\nexport function getMimeTypeFromExt(ext: string): string {\n const extToMime: Record<string, string> = {\n // Images\n 'png': 'image/png',\n 'jpg': 'image/jpeg',\n 'jpeg': 'image/jpeg',\n 'webp': 'image/webp',\n 'gif': 'image/gif',\n 'avif': 'image/avif',\n 'svg': 'image/svg+xml',\n 'bmp': 'image/bmp',\n 'ico': 'image/x-icon',\n\n // Audio\n 'mp3': 'audio/mpeg',\n 'wav': 'audio/wav',\n 'ogg': 'audio/ogg',\n 'flac': 'audio/flac',\n 'm4a': 'audio/mp4',\n 'aac': 'audio/aac',\n\n // Video\n 'mp4': 'video/mp4',\n 'webm': 'video/webm',\n 'avi': 'video/x-msvideo',\n 'mov': 'video/quicktime',\n 'mkv': 'video/x-matroska',\n\n // Text/Data\n 'json': 'application/json',\n 'txt': 'text/plain',\n 'html': 'text/html',\n 'css': 'text/css',\n 'js': 'application/javascript',\n };\n\n return extToMime[ext.toLowerCase()] || 'application/octet-stream';\n}\n\n/**\n * Get file extension from MIME type\n */\nexport function getExtFromMimeType(mimeType: string): string {\n const mimeToExt: Record<string, string> = {\n 'image/png': 'png',\n 'image/jpeg': 'jpg',\n 'image/webp': 'webp',\n 'image/gif': 'gif',\n 'image/avif': 'avif',\n 'image/svg+xml': 'svg',\n 'image/bmp': 'bmp',\n 'image/x-icon': 'ico',\n 'audio/mpeg': 'mp3',\n 'audio/wav': 'wav',\n 'audio/ogg': 'ogg',\n 'audio/flac': 'flac',\n 'audio/mp4': 'm4a',\n 'audio/aac': 'aac',\n 'video/mp4': 'mp4',\n 'video/webm': 'webm',\n 'video/x-msvideo': 'avi',\n 'video/quicktime': 'mov',\n 'video/x-matroska': 'mkv',\n 'application/json': 'json',\n 'text/plain': 'txt',\n 'text/html': 'html',\n 'text/css': 'css',\n 'application/javascript': 'js',\n };\n\n return mimeToExt[mimeType] || 'bin';\n}\n\n/**\n * Build a data URI from binary data and MIME type\n */\nexport function buildDataURI(data: string, mimeType: string, isBase64 = true): string {\n if (isBase64) {\n return `data:${mimeType};base64,${data}`;\n }\n return `data:${mimeType},${encodeURIComponent(data)}`;\n}\n","/**\n * Image Analysis Utilities\n *\n * Detect properties of image files from binary data.\n */\n\nimport {\n type BinaryData,\n indexOf,\n fromLatin1,\n} from './binary.js';\n\n/**\n * Check if an image buffer contains animation data.\n * Supports: APNG, WebP (Animated), GIF\n */\nexport function isAnimatedImage(data: BinaryData, _mimeType?: string): boolean {\n // 1. WebP Detection\n // RIFF .... WEBP\n if (\n data.length > 12 &&\n data[0] === 0x52 && data[1] === 0x49 && data[2] === 0x46 && data[3] === 0x46 && // RIFF\n data[8] === 0x57 && data[9] === 0x45 && data[10] === 0x42 && data[11] === 0x50 // WEBP\n ) {\n // Check for VP8X chunk\n // VP8X chunk header: 'VP8X' (bytes 12-15)\n if (\n data[12] === 0x56 && data[13] === 0x50 && data[14] === 0x38 && data[15] === 0x58\n ) {\n // Flags byte is at offset 20 (16 + 4 bytes chunk size)\n // Animation bit is bit 1 (0x02)\n const flags = data[20];\n return (flags! & 0x02) !== 0;\n }\n return false;\n }\n\n // 2. PNG/APNG Detection\n // Signature: 89 50 4E 47 0D 0A 1A 0A\n if (\n data.length > 8 &&\n data[0] === 0x89 && data[1] === 0x50 && data[2] === 0x4E && data[3] === 0x47\n ) {\n // Search for 'acTL' chunk (Animation Control)\n // It must appear before IDAT.\n // Simple search: indexOf('acTL')\n // Note: theoretically 'acTL' string could appear in other data, but highly unlikely in valid PNG structure before IDAT\n // We can iterate chunks to be safe, but indexOf is faster for a quick check\n const actlSig = fromLatin1('acTL');\n const idatSig = fromLatin1('IDAT');\n \n const actlIndex = indexOf(data, actlSig);\n if (actlIndex === -1) return false;\n\n const idatIndex = indexOf(data, idatSig);\n // If acTL exists and is before the first IDAT (or IDAT not found yet), it's APNG\n return idatIndex === -1 || actlIndex < idatIndex;\n }\n\n // 3. GIF Detection\n // Signature: GIF87a or GIF89a\n if (\n data.length > 6 &&\n data[0] === 0x47 && data[1] === 0x49 && data[2] === 0x46 // GIF\n ) {\n // Check for NETSCAPE2.0 extension (looping animation)\n // This is a heuristic. Static GIFs are rare in this domain but possible.\n // Full frame counting is expensive. Presence of NETSCAPE block is a strong indicator.\n const netscape = fromLatin1('NETSCAPE2.0');\n return indexOf(data, netscape) !== -1;\n }\n\n return false;\n}\n","/**\n * UUID Generation Utilities\n *\n * Provides crypto-grade UUID v4 generation that works in Node.js,\n * browsers (secure contexts), and falls back gracefully.\n */\n\n/**\n * Format 16 random bytes as a UUID v4 string\n */\nfunction formatUUID(bytes: Uint8Array): string {\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;\n}\n\n/**\n * Fallback UUID generation using Math.random()\n * Only used when crypto APIs are unavailable (rare)\n */\nfunction mathRandomUUID(): string {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\n/**\n * Generate a cryptographically secure UUID v4.\n *\n * Uses crypto.randomUUID() when available (Node.js 19+, modern browsers).\n * Falls back to crypto.getRandomValues() if randomUUID is unavailable.\n * Last resort uses Math.random() (non-secure, emits warning in dev).\n *\n * @returns A valid RFC 4122 UUID v4 string\n *\n * @example\n * ```typescript\n * const id = generateUUID();\n * // => \"550e8400-e29b-41d4-a716-446655440000\"\n * ```\n */\nexport function generateUUID(): string {\n // Node.js 19+ or browser with secure context\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n\n // Fallback using crypto.getRandomValues (older Node/browsers)\n if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n // Set version (4) and variant (RFC 4122)\n bytes[6] = (bytes[6]! & 0x0f) | 0x40; // Version 4\n bytes[8] = (bytes[8]! & 0x3f) | 0x80; // Variant 1\n return formatUUID(bytes);\n }\n\n // Last resort - non-secure fallback\n if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'development') {\n console.warn('[character-foundry/core] generateUUID: Using insecure Math.random() fallback');\n }\n return mathRandomUUID();\n}\n\n/**\n * Validate if a string is a valid UUID v4\n *\n * @param uuid - String to validate\n * @returns true if valid UUID v4 format\n */\nexport function isValidUUID(uuid: string): boolean {\n return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(uuid);\n}\n","/**\n * ActivityPub Utilities\n *\n * Convert character cards to/from ActivityPub format.\n */\n\nimport type { CCv3Data } from '@character-foundry/schemas';\nimport { generateUUID } from '@character-foundry/core';\nimport type {\n FederatedCard,\n FederatedActivity,\n FederatedActor,\n PlatformId,\n ForkActivity,\n InstallActivity,\n} from './types.js';\n\n/**\n * ActivityPub context for character cards\n */\nexport const ACTIVITY_CONTEXT = [\n 'https://www.w3.org/ns/activitystreams',\n {\n 'character': 'https://character-foundry.dev/ns#',\n 'character:version': { '@id': 'character:version' },\n 'character:spec': { '@id': 'character:spec' },\n },\n];\n\n/**\n * Extended ActivityPub context for Fork activities\n * Includes custom character-foundry namespace for fork semantics\n */\nexport const FORK_ACTIVITY_CONTEXT = [\n 'https://www.w3.org/ns/activitystreams',\n {\n 'character': 'https://character-foundry.dev/ns#',\n 'character:version': { '@id': 'character:version' },\n 'character:spec': { '@id': 'character:spec' },\n 'Fork': 'character:Fork',\n 'forkedFrom': { '@id': 'character:forkedFrom', '@type': '@id' },\n },\n];\n\n/**\n * Extended ActivityPub context for Install activities\n * Used by consumers (SillyTavern, Voxta) to notify hub about installations\n */\nexport const INSTALL_ACTIVITY_CONTEXT = [\n 'https://www.w3.org/ns/activitystreams',\n {\n 'character': 'https://character-foundry.dev/ns#',\n 'Install': 'character:Install',\n },\n];\n\n/**\n * Generate a unique ID for a federated card\n *\n * @security localId is URI-encoded to prevent path traversal and query injection.\n * Characters like '/', '?', '#', and spaces are safely escaped.\n */\nexport function generateCardId(baseUrl: string, localId: string): string {\n // Encode localId to prevent path confusion and injection attacks\n // e.g., \"foo/bar\" → \"foo%2Fbar\", \"foo?x=1\" → \"foo%3Fx%3D1\"\n const encodedId = encodeURIComponent(localId);\n return `${baseUrl}/cards/${encodedId}`;\n}\n\n/**\n * Generate a unique ID for an activity using crypto-grade randomness\n */\nexport function generateActivityId(baseUrl: string): string {\n const timestamp = Date.now();\n const random = generateUUID().split('-')[0]; // Use first segment of UUID\n return `${baseUrl}/activities/${timestamp}-${random}`;\n}\n\n/**\n * Convert a CCv3 card to ActivityPub FederatedCard format\n */\nexport function cardToActivityPub(\n card: CCv3Data,\n options: {\n id: string;\n actorId: string;\n published?: string;\n updated?: string;\n sourcePlatform?: PlatformId;\n sourceId?: string;\n sourceUrl?: string;\n attachments?: Array<{\n type: 'Image' | 'Audio' | 'Document';\n mediaType: string;\n url: string;\n name?: string;\n }>;\n }\n): FederatedCard {\n const now = new Date().toISOString();\n const cardData = card.data;\n\n // Convert tags to ActivityPub hashtags\n const tags = cardData.tags?.map((tag) => ({\n type: 'Hashtag' as const,\n name: `#${tag.replace(/\\s+/g, '_')}`,\n }));\n\n const federatedCard: FederatedCard = {\n '@context': ACTIVITY_CONTEXT,\n id: options.id,\n type: 'Note',\n name: cardData.name,\n summary: cardData.description?.substring(0, 500),\n content: JSON.stringify(card),\n mediaType: 'application/json',\n attributedTo: options.actorId,\n published: options.published || now,\n updated: options.updated,\n tag: tags,\n attachment: options.attachments,\n 'character:version': cardData.character_version,\n 'character:spec': card.spec_version,\n };\n\n // Add source info if provided\n if (options.sourcePlatform && options.sourceId) {\n federatedCard.source = {\n platform: options.sourcePlatform,\n id: options.sourceId,\n url: options.sourceUrl,\n };\n }\n\n return federatedCard;\n}\n\n/**\n * Extract CCv3 card from ActivityPub FederatedCard\n */\nexport function cardFromActivityPub(federatedCard: FederatedCard): CCv3Data {\n try {\n const card = JSON.parse(federatedCard.content) as CCv3Data;\n\n // Validate it's a proper CCv3 card\n if (card.spec !== 'chara_card_v3') {\n throw new Error('Invalid card spec');\n }\n\n return card;\n } catch (err) {\n throw new Error(\n `Failed to parse card from ActivityPub: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n}\n\n/**\n * Create a Create activity for a new card\n */\nexport function createCreateActivity(\n card: FederatedCard,\n actorId: string,\n baseUrl: string,\n recipients?: { to?: string[]; cc?: string[] }\n): FederatedActivity {\n return {\n '@context': ACTIVITY_CONTEXT,\n id: generateActivityId(baseUrl),\n type: 'Create',\n actor: actorId,\n object: card,\n published: new Date().toISOString(),\n to: recipients?.to || ['https://www.w3.org/ns/activitystreams#Public'],\n cc: recipients?.cc,\n };\n}\n\n/**\n * Create an Update activity for a modified card\n */\nexport function createUpdateActivity(\n card: FederatedCard,\n actorId: string,\n baseUrl: string,\n recipients?: { to?: string[]; cc?: string[] }\n): FederatedActivity {\n return {\n '@context': ACTIVITY_CONTEXT,\n id: generateActivityId(baseUrl),\n type: 'Update',\n actor: actorId,\n object: card,\n published: new Date().toISOString(),\n to: recipients?.to || ['https://www.w3.org/ns/activitystreams#Public'],\n cc: recipients?.cc,\n };\n}\n\n/**\n * Create a Delete activity for a removed card\n */\nexport function createDeleteActivity(\n cardId: string,\n actorId: string,\n baseUrl: string,\n recipients?: { to?: string[]; cc?: string[] }\n): FederatedActivity {\n return {\n '@context': ACTIVITY_CONTEXT,\n id: generateActivityId(baseUrl),\n type: 'Delete',\n actor: actorId,\n object: cardId,\n published: new Date().toISOString(),\n to: recipients?.to || ['https://www.w3.org/ns/activitystreams#Public'],\n cc: recipients?.cc,\n };\n}\n\n/**\n * Create an Announce (reshare) activity\n */\nexport function createAnnounceActivity(\n cardId: string,\n actorId: string,\n baseUrl: string,\n recipients?: { to?: string[]; cc?: string[] }\n): FederatedActivity {\n return {\n '@context': ACTIVITY_CONTEXT,\n id: generateActivityId(baseUrl),\n type: 'Announce',\n actor: actorId,\n object: cardId,\n published: new Date().toISOString(),\n to: recipients?.to || ['https://www.w3.org/ns/activitystreams#Public'],\n cc: recipients?.cc,\n };\n}\n\n/**\n * Create a Like activity\n */\nexport function createLikeActivity(\n cardId: string,\n actorId: string,\n baseUrl: string\n): FederatedActivity {\n return {\n '@context': ACTIVITY_CONTEXT,\n id: generateActivityId(baseUrl),\n type: 'Like',\n actor: actorId,\n object: cardId,\n published: new Date().toISOString(),\n };\n}\n\n/**\n * Create an Undo activity\n */\nexport function createUndoActivity(\n originalActivityId: string,\n actorId: string,\n baseUrl: string\n): FederatedActivity {\n return {\n '@context': ACTIVITY_CONTEXT,\n id: generateActivityId(baseUrl),\n type: 'Undo',\n actor: actorId,\n object: originalActivityId,\n published: new Date().toISOString(),\n };\n}\n\n/**\n * Create a Fork activity for card derivation\n *\n * Fork activities notify the source instance that a card was forked.\n * The forked card contains a reference to the source in its extensions.\n */\nexport function createForkActivity(\n sourceCardId: string,\n forkedCard: FederatedCard,\n actorId: string,\n baseUrl: string,\n recipients?: { to?: string[]; cc?: string[] }\n): ForkActivity {\n return {\n '@context': FORK_ACTIVITY_CONTEXT,\n id: generateActivityId(baseUrl),\n type: 'Fork',\n actor: actorId,\n object: sourceCardId,\n result: forkedCard,\n published: new Date().toISOString(),\n to: recipients?.to || ['https://www.w3.org/ns/activitystreams#Public'],\n cc: recipients?.cc,\n };\n}\n\n/**\n * Parse an incoming Fork activity\n *\n * @returns Parsed fork data or null if invalid\n */\nexport function parseForkActivity(activity: unknown): {\n sourceCardId: string;\n forkedCard: FederatedCard;\n actor: string;\n activityId: string;\n} | null {\n if (!activity || typeof activity !== 'object') {\n return null;\n }\n\n const act = activity as Record<string, unknown>;\n\n // Verify it's a Fork activity\n if (act.type !== 'Fork') {\n return null;\n }\n\n // Validate required fields\n if (\n typeof act.actor !== 'string' ||\n typeof act.object !== 'string' ||\n typeof act.id !== 'string' ||\n !act.result ||\n typeof act.result !== 'object'\n ) {\n return null;\n }\n\n const result = act.result as Record<string, unknown>;\n\n // Validate the forked card has required fields\n if (\n typeof result.id !== 'string' ||\n typeof result.content !== 'string' ||\n typeof result.attributedTo !== 'string'\n ) {\n return null;\n }\n\n return {\n sourceCardId: act.object,\n forkedCard: result as unknown as FederatedCard,\n actor: act.actor,\n activityId: act.id,\n };\n}\n\n/**\n * Create an Install activity for notifying hub about card installation\n *\n * Consumers (SillyTavern, Voxta) send this when a card is installed/saved.\n */\nexport function createInstallActivity(\n cardId: string,\n actorId: string,\n baseUrl: string,\n platform: PlatformId,\n recipients?: { to?: string[]; cc?: string[] }\n): InstallActivity {\n return {\n '@context': INSTALL_ACTIVITY_CONTEXT,\n id: generateActivityId(baseUrl),\n type: 'Install',\n actor: actorId,\n object: cardId,\n target: {\n type: 'Application',\n name: platform,\n },\n published: new Date().toISOString(),\n to: recipients?.to,\n cc: recipients?.cc,\n };\n}\n\n/**\n * Parse an incoming Install activity\n *\n * @returns Parsed install data or null if invalid\n */\nexport function parseInstallActivity(activity: unknown): {\n cardId: string;\n actor: string;\n platform: PlatformId | null;\n activityId: string;\n} | null {\n if (!activity || typeof activity !== 'object') {\n return null;\n }\n\n const act = activity as Record<string, unknown>;\n\n // Verify it's an Install activity\n if (act.type !== 'Install') {\n return null;\n }\n\n // Validate required fields\n if (\n typeof act.actor !== 'string' ||\n typeof act.object !== 'string' ||\n typeof act.id !== 'string'\n ) {\n return null;\n }\n\n // Extract platform from target if present\n let platform: PlatformId | null = null;\n if (act.target && typeof act.target === 'object') {\n const target = act.target as Record<string, unknown>;\n if (target.type === 'Application' && typeof target.name === 'string') {\n platform = target.name as PlatformId;\n }\n }\n\n return {\n cardId: act.object,\n actor: act.actor,\n platform,\n activityId: act.id,\n };\n}\n\n/**\n * Create a minimal actor object\n */\nexport function createActor(options: {\n id: string;\n username: string;\n displayName: string;\n summary?: string;\n icon?: string;\n baseUrl: string;\n publicKeyPem?: string;\n}): FederatedActor {\n const actor: FederatedActor = {\n id: options.id,\n type: 'Person',\n name: options.displayName,\n preferredUsername: options.username,\n summary: options.summary,\n icon: options.icon,\n inbox: `${options.id}/inbox`,\n outbox: `${options.id}/outbox`,\n followers: `${options.id}/followers`,\n following: `${options.id}/following`,\n };\n\n if (options.publicKeyPem) {\n actor.publicKey = {\n id: `${options.id}#main-key`,\n owner: options.id,\n publicKeyPem: options.publicKeyPem,\n };\n }\n\n return actor;\n}\n\n/**\n * Parse an incoming activity\n */\nexport function parseActivity(data: unknown): FederatedActivity {\n if (!data || typeof data !== 'object') {\n throw new Error('Invalid activity: not an object');\n }\n\n const activity = data as Record<string, unknown>;\n\n if (!activity.type || !activity.actor || !activity.object) {\n throw new Error('Invalid activity: missing required fields');\n }\n\n return activity as unknown as FederatedActivity;\n}\n\n/**\n * @deprecated Use validateHttpSignature from http-signatures.ts instead.\n *\n * This stub exists only for backwards compatibility and always throws.\n * For actual HTTP signature verification, use:\n *\n * ```typescript\n * import { validateHttpSignature } from '@character-foundry/federation';\n * ```\n *\n * Or use handleInbox() with strictMode: true for automatic verification.\n */\nexport function validateActivitySignature(\n _activity: FederatedActivity,\n _signature: string,\n _publicKey: string\n): never {\n throw new Error(\n 'validateActivitySignature is deprecated and does not work. ' +\n 'Use validateHttpSignature from http-signatures.ts instead, or use handleInbox() with strictMode: true. ' +\n 'See: https://github.com/character-foundry/character-foundry/blob/master/docs/federation.md#http-signatures'\n );\n}\n","/**\n * Sync Engine\n *\n * Handles synchronization of character cards across platforms.\n */\n\nimport type { CCv3Data } from '@character-foundry/schemas';\nimport { generateUUID } from '@character-foundry/core';\nimport type {\n PlatformId,\n PlatformAdapter,\n CardSyncState,\n SyncOperation,\n SyncResult,\n SyncStateStore,\n FederationEvent,\n FederationEventListener,\n FederationEventType,\n ForkReference,\n ForkResult,\n ForkActivity,\n ForkNotification,\n InstallActivity,\n CardStats,\n} from './types.js';\nimport { generateCardId, parseForkActivity, parseInstallActivity } from './activitypub.js';\nimport { assertFederationEnabled } from './index.js';\n\n/**\n * Generate a simple hash of card content for change detection (32-bit djb2-style).\n *\n * Fast but has collision potential (~2^16 birthday bound). Suitable for\n * local change detection where collisions are unlikely and recoverable.\n *\n * @see hashCardSecure for cryptographic alternative\n */\nfunction hashCardFast(card: CCv3Data): string {\n const content = JSON.stringify(card);\n let hash = 0;\n for (let i = 0; i < content.length; i++) {\n const char = content.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n return Math.abs(hash).toString(36);\n}\n\n/**\n * Generate a SHA-256 hash of card content for secure change detection.\n *\n * Cryptographically secure with negligible collision probability.\n * Recommended for federation sync where hash collisions could cause\n * data loss or missed updates across systems.\n *\n * @security Use this for cross-system sync to prevent collision attacks\n */\nasync function hashCardSecure(card: CCv3Data): Promise<string> {\n const content = JSON.stringify(card);\n const data = new TextEncoder().encode(content);\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');\n}\n\n/**\n * Sync Engine Options\n */\nexport interface SyncEngineOptions {\n /** Base URL for this federation instance */\n baseUrl: string;\n /** Actor ID for this instance */\n actorId: string;\n /** State storage */\n stateStore: SyncStateStore;\n /** Auto-sync interval in ms (0 to disable) */\n autoSyncInterval?: number;\n /**\n * Use SHA-256 for change detection instead of fast 32-bit hash.\n *\n * Default: false (fast hash for backwards compatibility)\n * Recommended: true for federation sync across untrusted systems\n *\n * @security Fast hash has collision potential (~2^16 birthday bound).\n * SHA-256 is cryptographically secure but slightly slower.\n */\n secureHashing?: boolean;\n}\n\n/**\n * Sync Engine\n * Coordinates synchronization between registered platforms\n */\nexport class SyncEngine {\n private platforms: Map<PlatformId, PlatformAdapter> = new Map();\n private stateStore: SyncStateStore;\n private baseUrl: string;\n private actorId: string;\n private listeners: Map<FederationEventType, Set<FederationEventListener>> = new Map();\n private autoSyncTimer?: ReturnType<typeof setInterval>;\n private secureHashing: boolean;\n\n /**\n * Mutex flag to prevent concurrent syncAll() executions.\n * When autoSyncInterval triggers while a sync is in progress,\n * the new sync is skipped to prevent race conditions.\n */\n private syncInProgress = false;\n\n constructor(options: SyncEngineOptions) {\n assertFederationEnabled('SyncEngine');\n\n this.baseUrl = options.baseUrl;\n this.actorId = options.actorId;\n this.stateStore = options.stateStore;\n this.secureHashing = options.secureHashing ?? false;\n\n if (options.autoSyncInterval && options.autoSyncInterval > 0) {\n this.autoSyncTimer = setInterval(\n () => void this.syncAll(), // void to handle Promise without blocking\n options.autoSyncInterval\n );\n }\n }\n\n /**\n * Generate a hash for change detection.\n * Uses SHA-256 if secureHashing is enabled, otherwise fast 32-bit hash.\n */\n private async hashCard(card: CCv3Data): Promise<string> {\n if (this.secureHashing) {\n return hashCardSecure(card);\n }\n return hashCardFast(card);\n }\n\n /**\n * Register a platform adapter\n */\n registerPlatform(adapter: PlatformAdapter): void {\n this.platforms.set(adapter.platform, adapter);\n }\n\n /**\n * Unregister a platform adapter\n */\n unregisterPlatform(platform: PlatformId): void {\n this.platforms.delete(platform);\n }\n\n /**\n * Get registered platforms\n */\n getPlatforms(): PlatformId[] {\n return Array.from(this.platforms.keys());\n }\n\n /**\n * Add event listener\n */\n on(event: FederationEventType, listener: FederationEventListener): void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(listener);\n }\n\n /**\n * Remove event listener\n */\n off(event: FederationEventType, listener: FederationEventListener): void {\n this.listeners.get(event)?.delete(listener);\n }\n\n /**\n * Emit an event\n */\n private emit(type: FederationEventType, data: unknown): void {\n const event: FederationEvent = {\n type,\n timestamp: new Date().toISOString(),\n data,\n };\n\n const listeners = this.listeners.get(type);\n if (listeners) {\n for (const listener of listeners) {\n try {\n listener(event);\n } catch (err) {\n console.error(`Event listener error:`, err);\n }\n }\n }\n }\n\n /**\n * Push a card from one platform to another\n */\n async pushCard(\n sourcePlatform: PlatformId,\n sourceId: string,\n targetPlatform: PlatformId\n ): Promise<SyncResult> {\n const operation: SyncOperation = {\n type: 'push',\n cardId: `${sourcePlatform}:${sourceId}`,\n sourcePlatform,\n targetPlatform,\n timestamp: new Date().toISOString(),\n };\n\n try {\n // Get source adapter\n const sourceAdapter = this.platforms.get(sourcePlatform);\n if (!sourceAdapter) {\n throw new Error(`Platform not registered: ${sourcePlatform}`);\n }\n\n // Get target adapter\n const targetAdapter = this.platforms.get(targetPlatform);\n if (!targetAdapter) {\n throw new Error(`Platform not registered: ${targetPlatform}`);\n }\n\n // Check availability\n if (!await sourceAdapter.isAvailable()) {\n throw new Error(`Source platform unavailable: ${sourcePlatform}`);\n }\n if (!await targetAdapter.isAvailable()) {\n throw new Error(`Target platform unavailable: ${targetPlatform}`);\n }\n\n // Get the card\n const card = await sourceAdapter.getCard(sourceId);\n if (!card) {\n throw new Error(`Card not found: ${sourceId}`);\n }\n\n // Check for existing sync state\n const federatedId = generateCardId(this.baseUrl, `${sourcePlatform}-${sourceId}`);\n const syncState = await this.stateStore.get(federatedId);\n\n // Check for conflicts\n if (syncState?.platformIds[targetPlatform]) {\n const existingCard = await targetAdapter.getCard(syncState.platformIds[targetPlatform]!);\n if (existingCard) {\n const existingHash = await this.hashCard(existingCard);\n const newHash = await this.hashCard(card);\n\n if (existingHash !== syncState.versionHash && newHash !== syncState.versionHash) {\n // Both sides changed - conflict!\n syncState.status = 'conflict';\n syncState.conflict = {\n localVersion: newHash,\n remoteVersion: existingHash,\n remotePlatform: targetPlatform,\n };\n await this.stateStore.set(syncState);\n\n this.emit('card:conflict', { syncState, sourcePlatform, targetPlatform });\n\n return {\n success: false,\n operation,\n newState: syncState,\n error: 'Sync conflict detected',\n };\n }\n }\n }\n\n // Save to target platform\n const targetId = await targetAdapter.saveCard(\n card,\n syncState?.platformIds[targetPlatform]\n );\n\n // Update sync state\n const newHash = await this.hashCard(card);\n const now = new Date().toISOString();\n\n // Create new state or use existing\n const updatedState: CardSyncState = syncState ?? {\n localId: sourceId,\n federatedId,\n platformIds: {},\n lastSync: {},\n versionHash: newHash,\n status: 'synced',\n };\n\n updatedState.platformIds[sourcePlatform] = sourceId;\n updatedState.platformIds[targetPlatform] = targetId;\n updatedState.lastSync[sourcePlatform] = now;\n updatedState.lastSync[targetPlatform] = now;\n updatedState.versionHash = newHash;\n updatedState.status = 'synced';\n updatedState.conflict = undefined;\n\n await this.stateStore.set(updatedState);\n\n this.emit('card:synced', { syncState: updatedState, sourcePlatform, targetPlatform });\n\n return {\n success: true,\n operation,\n newState: updatedState,\n };\n } catch (err) {\n this.emit('sync:failed', { operation, error: err });\n\n return {\n success: false,\n operation,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n\n /**\n * Pull a card from a remote platform to local\n */\n async pullCard(\n remotePlatform: PlatformId,\n remoteId: string,\n localPlatform: PlatformId\n ): Promise<SyncResult> {\n // Pull is just push in reverse\n return this.pushCard(remotePlatform, remoteId, localPlatform);\n }\n\n /**\n * Sync a card across all registered platforms\n */\n async syncCardToAll(\n sourcePlatform: PlatformId,\n sourceId: string\n ): Promise<SyncResult[]> {\n const results: SyncResult[] = [];\n\n for (const [platform] of this.platforms) {\n if (platform === sourcePlatform) continue;\n\n const result = await this.pushCard(sourcePlatform, sourceId, platform);\n results.push(result);\n }\n\n return results;\n }\n\n /**\n * Sync all cards from one platform to another\n */\n async syncPlatform(\n sourcePlatform: PlatformId,\n targetPlatform: PlatformId\n ): Promise<SyncResult[]> {\n const sourceAdapter = this.platforms.get(sourcePlatform);\n if (!sourceAdapter) {\n throw new Error(`Platform not registered: ${sourcePlatform}`);\n }\n\n this.emit('sync:started', { sourcePlatform, targetPlatform });\n\n const cards = await sourceAdapter.listCards();\n const results: SyncResult[] = [];\n\n for (const { id } of cards) {\n const result = await this.pushCard(sourcePlatform, id, targetPlatform);\n results.push(result);\n }\n\n this.emit('sync:completed', {\n sourcePlatform,\n targetPlatform,\n total: results.length,\n successful: results.filter((r) => r.success).length,\n failed: results.filter((r) => !r.success).length,\n });\n\n return results;\n }\n\n /**\n * Sync all platforms with each other.\n *\n * Includes mutex protection to prevent concurrent executions when\n * triggered by autoSyncInterval. If a sync is already in progress,\n * subsequent calls are skipped and emit a 'sync:skipped' event.\n */\n async syncAll(): Promise<Map<string, SyncResult[]>> {\n // Mutex: prevent concurrent sync operations\n if (this.syncInProgress) {\n this.emit('sync:skipped', { reason: 'already_in_progress', timestamp: new Date().toISOString() });\n return new Map();\n }\n\n this.syncInProgress = true;\n try {\n const results = new Map<string, SyncResult[]>();\n const platforms = Array.from(this.platforms.keys());\n\n for (let i = 0; i < platforms.length; i++) {\n for (let j = i + 1; j < platforms.length; j++) {\n const source = platforms[i]!;\n const target = platforms[j]!;\n const key = `${source}->${target}`;\n\n results.set(key, await this.syncPlatform(source, target));\n }\n }\n\n return results;\n } finally {\n this.syncInProgress = false;\n }\n }\n\n /**\n * Get sync state for a card\n */\n async getSyncState(federatedId: string): Promise<CardSyncState | null> {\n return this.stateStore.get(federatedId);\n }\n\n /**\n * Find sync state by platform ID\n */\n async findSyncState(\n platform: PlatformId,\n platformId: string\n ): Promise<CardSyncState | null> {\n return this.stateStore.findByPlatformId(platform, platformId);\n }\n\n /**\n * Resolve a sync conflict by choosing a version\n */\n async resolveConflict(\n federatedId: string,\n resolution: 'local' | 'remote' | 'merge',\n mergedCard?: CCv3Data\n ): Promise<SyncResult> {\n const syncState = await this.stateStore.get(federatedId);\n if (!syncState || syncState.status !== 'conflict' || !syncState.conflict) {\n throw new Error('No conflict to resolve');\n }\n\n const operation: SyncOperation = {\n type: 'resolve',\n cardId: federatedId,\n sourcePlatform: 'archive', // Will be updated\n targetPlatform: syncState.conflict.remotePlatform,\n timestamp: new Date().toISOString(),\n };\n\n try {\n let sourceCard: CCv3Data | null = null;\n let _sourcePlatform: PlatformId | null = null;\n\n if (resolution === 'merge' && mergedCard) {\n sourceCard = mergedCard;\n } else if (resolution === 'local') {\n // Find local platform\n for (const [platform, id] of Object.entries(syncState.platformIds)) {\n if (platform !== syncState.conflict.remotePlatform) {\n const adapter = this.platforms.get(platform as PlatformId);\n if (adapter) {\n sourceCard = await adapter.getCard(id);\n _sourcePlatform = platform as PlatformId;\n break;\n }\n }\n }\n } else if (resolution === 'remote') {\n const adapter = this.platforms.get(syncState.conflict.remotePlatform);\n if (adapter) {\n const remoteId = syncState.platformIds[syncState.conflict.remotePlatform];\n if (remoteId) {\n sourceCard = await adapter.getCard(remoteId);\n _sourcePlatform = syncState.conflict.remotePlatform;\n }\n }\n }\n\n if (!sourceCard) {\n throw new Error('Could not resolve conflict: source card not found');\n }\n\n // Push resolved card to all platforms\n const newHash = await this.hashCard(sourceCard);\n const now = new Date().toISOString();\n\n for (const [platform, id] of Object.entries(syncState.platformIds)) {\n const adapter = this.platforms.get(platform as PlatformId);\n if (adapter) {\n await adapter.saveCard(sourceCard, id);\n syncState.lastSync[platform as PlatformId] = now;\n }\n }\n\n syncState.versionHash = newHash;\n syncState.status = 'synced';\n syncState.conflict = undefined;\n\n await this.stateStore.set(syncState);\n\n this.emit('card:synced', { syncState, resolution });\n\n return {\n success: true,\n operation,\n newState: syncState,\n };\n } catch (err) {\n return {\n success: false,\n operation,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n\n /**\n * Fork a card from a remote source\n *\n * Creates a local copy of a remote card with fork metadata stored in\n * the card's extensions and sync state. Optionally notifies the source\n * instance about the fork.\n *\n * @param sourceFederatedId - Federated URI of the source card\n * @param sourcePlatform - Platform where the source card resides\n * @param targetPlatform - Platform to save the fork to\n * @param options - Fork options\n */\n async forkCard(\n sourceFederatedId: string,\n sourcePlatform: PlatformId,\n targetPlatform: PlatformId,\n options?: {\n modifications?: Partial<CCv3Data['data']>;\n notifySource?: boolean;\n sourceInbox?: string;\n }\n ): Promise<ForkResult> {\n const operation: SyncOperation = {\n type: 'fork',\n cardId: sourceFederatedId,\n sourcePlatform,\n targetPlatform,\n timestamp: new Date().toISOString(),\n };\n\n try {\n // Get source adapter\n const sourceAdapter = this.platforms.get(sourcePlatform);\n if (!sourceAdapter) {\n throw new Error(`Platform not registered: ${sourcePlatform}`);\n }\n\n // Get target adapter\n const targetAdapter = this.platforms.get(targetPlatform);\n if (!targetAdapter) {\n throw new Error(`Platform not registered: ${targetPlatform}`);\n }\n\n // Find source card by looking up sync state or trying to get directly\n const sourceState = await this.stateStore.get(sourceFederatedId);\n const sourceLocalId = sourceState?.platformIds[sourcePlatform];\n\n if (!sourceLocalId) {\n throw new Error(`Cannot find source card: ${sourceFederatedId}`);\n }\n\n const sourceCard = await sourceAdapter.getCard(sourceLocalId);\n if (!sourceCard) {\n throw new Error(`Source card not found: ${sourceLocalId}`);\n }\n\n // Create forked card with modifications and metadata\n const forkedCard = this.createForkedCard(\n sourceCard,\n sourceFederatedId,\n sourcePlatform,\n sourceState?.versionHash,\n options?.modifications\n );\n\n // Save fork to target platform\n const forkLocalId = await targetAdapter.saveCard(forkedCard);\n\n // Create sync state for the fork\n const forkFederatedId = generateCardId(this.baseUrl, `${targetPlatform}-${forkLocalId}`);\n const forkHash = await this.hashCard(forkedCard);\n const now = new Date().toISOString();\n\n const forkReference: ForkReference = {\n federatedId: sourceFederatedId,\n platform: sourcePlatform,\n forkedAt: now,\n sourceVersionHash: sourceState?.versionHash,\n };\n\n const forkState: CardSyncState = {\n localId: forkLocalId,\n federatedId: forkFederatedId,\n platformIds: { [targetPlatform]: forkLocalId },\n lastSync: { [targetPlatform]: now },\n versionHash: forkHash,\n status: 'synced',\n forkedFrom: forkReference,\n };\n\n await this.stateStore.set(forkState);\n\n this.emit('card:forked', {\n forkState,\n sourceFederatedId,\n sourcePlatform,\n targetPlatform,\n });\n\n return {\n success: true,\n operation,\n forkState,\n sourceFederatedId,\n forkFederatedId,\n };\n } catch (err) {\n this.emit('sync:failed', { operation, error: err });\n\n return {\n success: false,\n operation,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n\n /**\n * Create a forked card with fork metadata in extensions\n */\n private createForkedCard(\n source: CCv3Data,\n sourceFederatedId: string,\n sourcePlatform: PlatformId,\n sourceVersionHash?: string,\n modifications?: Partial<CCv3Data['data']>\n ): CCv3Data {\n const now = new Date().toISOString();\n\n // Deep clone the source card\n const forked: CCv3Data = JSON.parse(JSON.stringify(source));\n\n // Apply modifications if provided\n if (modifications) {\n Object.assign(forked.data, modifications);\n }\n\n // Ensure extensions object exists\n if (!forked.data.extensions) {\n forked.data.extensions = {};\n }\n\n // Add fork metadata to character-foundry extension\n const cfExtension = (forked.data.extensions['character-foundry'] as Record<string, unknown>) || {};\n cfExtension.forkedFrom = {\n federatedId: sourceFederatedId,\n platform: sourcePlatform,\n forkedAt: now,\n sourceVersionHash,\n };\n forked.data.extensions['character-foundry'] = cfExtension;\n\n // Update version info\n forked.data.character_version = generateUUID();\n\n return forked;\n }\n\n /**\n * Handle incoming fork notification\n *\n * Called when another instance notifies us that they forked one of our cards.\n * Increments the fork count and stores the notification.\n */\n async handleForkNotification(activity: ForkActivity): Promise<void> {\n const parsed = parseForkActivity(activity);\n if (!parsed) {\n throw new Error('Invalid fork activity');\n }\n\n const { sourceCardId, actor } = parsed;\n\n // Find our sync state for the source card\n const sourceState = await this.stateStore.get(sourceCardId);\n if (!sourceState) {\n // We don't have this card tracked - might be from before federation\n // or the card was deleted. Just ignore.\n return;\n }\n\n // Determine the forking platform from the actor URL\n // Actor URLs are typically like: https://hub.example.com/users/alice\n let forkPlatform: PlatformId = 'custom';\n if (actor.includes('archive')) forkPlatform = 'archive';\n else if (actor.includes('hub')) forkPlatform = 'hub';\n else if (actor.includes('editor')) forkPlatform = 'editor';\n else if (actor.includes('chub')) forkPlatform = 'chub';\n else if (actor.includes('risu')) forkPlatform = 'risu';\n\n const notification: ForkNotification = {\n forkId: parsed.forkedCard.id,\n actorId: actor,\n platform: forkPlatform,\n timestamp: new Date().toISOString(),\n };\n\n // Update state (increment count, add notification)\n const notifications = sourceState.forkNotifications || [];\n if (notifications.length < 100) {\n notifications.push(notification);\n }\n\n sourceState.forksCount = (sourceState.forksCount || 0) + 1;\n sourceState.forkNotifications = notifications;\n\n await this.stateStore.set(sourceState);\n\n this.emit('card:fork-received', {\n sourceCardId,\n notification,\n newForkCount: sourceState.forksCount,\n });\n }\n\n /**\n * Handle incoming install notification\n *\n * Called when a consumer (SillyTavern, Voxta) notifies us that they installed one of our cards.\n * Increments the install count and stores the notification.\n */\n async handleInstallNotification(activity: InstallActivity): Promise<void> {\n const parsed = parseInstallActivity(activity);\n if (!parsed) {\n throw new Error('Invalid install activity');\n }\n\n const { cardId, actor, platform } = parsed;\n\n // Find our sync state for the card\n const cardState = await this.stateStore.get(cardId);\n if (!cardState) {\n // We don't have this card tracked - might be from before federation\n // or the card was deleted. Just ignore.\n return;\n }\n\n // Determine the installing platform\n const installPlatform: PlatformId = platform || 'custom';\n\n // Initialize stats if needed\n if (!cardState.stats) {\n cardState.stats = {\n installCount: 0,\n installsByPlatform: {},\n forkCount: cardState.forksCount || 0,\n likeCount: 0,\n lastUpdated: new Date().toISOString(),\n };\n }\n\n // Increment counts\n cardState.stats.installCount++;\n cardState.stats.installsByPlatform[installPlatform] =\n (cardState.stats.installsByPlatform[installPlatform] || 0) + 1;\n cardState.stats.lastUpdated = new Date().toISOString();\n\n await this.stateStore.set(cardState);\n\n this.emit('card:install-received', {\n cardId,\n platform: installPlatform,\n actorId: actor,\n newInstallCount: cardState.stats.installCount,\n });\n }\n\n /**\n * Get stats for a card\n */\n async getCardStats(federatedId: string): Promise<CardStats | null> {\n const state = await this.stateStore.get(federatedId);\n return state?.stats || null;\n }\n\n /**\n * Get fork count for a card\n */\n async getForkCount(federatedId: string): Promise<number> {\n const state = await this.stateStore.get(federatedId);\n return state?.forksCount || 0;\n }\n\n /**\n * Get install count for a card\n */\n async getInstallCount(federatedId: string): Promise<number> {\n const state = await this.stateStore.get(federatedId);\n return state?.stats?.installCount || 0;\n }\n\n /**\n * Find all local forks of a source card\n */\n async findForks(sourceFederatedId: string): Promise<CardSyncState[]> {\n const allStates = await this.stateStore.list();\n return allStates.filter(\n (state) => state.forkedFrom?.federatedId === sourceFederatedId\n );\n }\n\n /**\n * Stop the sync engine\n */\n dispose(): void {\n if (this.autoSyncTimer) {\n clearInterval(this.autoSyncTimer);\n }\n this.listeners.clear();\n }\n}\n","/**\n * Sync State Stores\n *\n * Implementations for storing sync state.\n */\n\nimport type {\n SyncStateStore,\n CardSyncState,\n PlatformId,\n ForkNotification,\n} from './types.js';\n\n/**\n * In-memory sync state store\n * Useful for testing and single-session sync\n */\nexport class MemorySyncStateStore implements SyncStateStore {\n private states: Map<string, CardSyncState> = new Map();\n\n async get(federatedId: string): Promise<CardSyncState | null> {\n return this.states.get(federatedId) || null;\n }\n\n async set(state: CardSyncState): Promise<void> {\n this.states.set(state.federatedId, { ...state });\n }\n\n async delete(federatedId: string): Promise<void> {\n this.states.delete(federatedId);\n }\n\n async list(): Promise<CardSyncState[]> {\n return Array.from(this.states.values());\n }\n\n async findByPlatformId(\n platform: PlatformId,\n platformId: string\n ): Promise<CardSyncState | null> {\n for (const state of this.states.values()) {\n if (state.platformIds[platform] === platformId) {\n return state;\n }\n }\n return null;\n }\n\n /**\n * Clear all states (for testing)\n */\n clear(): void {\n this.states.clear();\n }\n\n /**\n * Increment fork count and add notification\n */\n async incrementForkCount(\n federatedId: string,\n notification: ForkNotification\n ): Promise<void> {\n const state = this.states.get(federatedId);\n if (!state) return;\n\n // Cap notifications at 100\n const notifications = state.forkNotifications || [];\n if (notifications.length < 100) {\n notifications.push(notification);\n }\n\n state.forksCount = (state.forksCount || 0) + 1;\n state.forkNotifications = notifications;\n this.states.set(federatedId, state);\n }\n\n /**\n * Get fork count for a card\n */\n async getForkCount(federatedId: string): Promise<number> {\n return this.states.get(federatedId)?.forksCount || 0;\n }\n\n /**\n * Find all cards that are forks of a given source card\n */\n async findForks(sourceFederatedId: string): Promise<CardSyncState[]> {\n const forks: CardSyncState[] = [];\n for (const state of this.states.values()) {\n if (state.forkedFrom?.federatedId === sourceFederatedId) {\n forks.push(state);\n }\n }\n return forks;\n }\n}\n\n/**\n * JSON file-based sync state store\n * Persists state to a JSON file\n */\nexport class FileSyncStateStore implements SyncStateStore {\n private states: Map<string, CardSyncState> = new Map();\n private filePath: string;\n private saveDebounce?: ReturnType<typeof setTimeout>;\n private fs: {\n readFile: (path: string, encoding: string) => Promise<string>;\n writeFile: (path: string, data: string) => Promise<void>;\n mkdir: (path: string, options: { recursive: boolean }) => Promise<void>;\n };\n\n constructor(\n filePath: string,\n fs: {\n readFile: (path: string, encoding: string) => Promise<string>;\n writeFile: (path: string, data: string) => Promise<void>;\n mkdir: (path: string, options: { recursive: boolean }) => Promise<void>;\n }\n ) {\n this.filePath = filePath;\n this.fs = fs;\n }\n\n /**\n * Load state from file\n */\n async load(): Promise<void> {\n try {\n const content = await this.fs.readFile(this.filePath, 'utf-8');\n const data = JSON.parse(content) as CardSyncState[];\n this.states.clear();\n for (const state of data) {\n this.states.set(state.federatedId, state);\n }\n } catch {\n // File doesn't exist or is invalid - start fresh\n this.states.clear();\n }\n }\n\n /**\n * Save state to file (debounced)\n */\n private scheduleSave(): void {\n if (this.saveDebounce) {\n clearTimeout(this.saveDebounce);\n }\n this.saveDebounce = setTimeout(() => this.saveNow(), 1000);\n }\n\n /**\n * Save state to file immediately\n */\n async saveNow(): Promise<void> {\n const data = Array.from(this.states.values());\n const json = JSON.stringify(data, null, 2);\n\n // Ensure directory exists\n const dir = this.filePath.substring(0, this.filePath.lastIndexOf('/'));\n if (dir) {\n await this.fs.mkdir(dir, { recursive: true });\n }\n\n await this.fs.writeFile(this.filePath, json);\n }\n\n async get(federatedId: string): Promise<CardSyncState | null> {\n return this.states.get(federatedId) || null;\n }\n\n async set(state: CardSyncState): Promise<void> {\n this.states.set(state.federatedId, { ...state });\n this.scheduleSave();\n }\n\n async delete(federatedId: string): Promise<void> {\n this.states.delete(federatedId);\n this.scheduleSave();\n }\n\n async list(): Promise<CardSyncState[]> {\n return Array.from(this.states.values());\n }\n\n async findByPlatformId(\n platform: PlatformId,\n platformId: string\n ): Promise<CardSyncState | null> {\n for (const state of this.states.values()) {\n if (state.platformIds[platform] === platformId) {\n return state;\n }\n }\n return null;\n }\n\n /**\n * Increment fork count and add notification\n */\n async incrementForkCount(\n federatedId: string,\n notification: ForkNotification\n ): Promise<void> {\n const state = this.states.get(federatedId);\n if (!state) return;\n\n // Cap notifications at 100\n const notifications = state.forkNotifications || [];\n if (notifications.length < 100) {\n notifications.push(notification);\n }\n\n state.forksCount = (state.forksCount || 0) + 1;\n state.forkNotifications = notifications;\n this.states.set(federatedId, state);\n this.scheduleSave();\n }\n\n /**\n * Get fork count for a card\n */\n async getForkCount(federatedId: string): Promise<number> {\n return this.states.get(federatedId)?.forksCount || 0;\n }\n\n /**\n * Find all cards that are forks of a given source card\n */\n async findForks(sourceFederatedId: string): Promise<CardSyncState[]> {\n const forks: CardSyncState[] = [];\n for (const state of this.states.values()) {\n if (state.forkedFrom?.federatedId === sourceFederatedId) {\n forks.push(state);\n }\n }\n return forks;\n }\n}\n\n/**\n * Storage interface (compatible with Web Storage API)\n */\nexport interface StorageInterface {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\n/**\n * Create a state store backed by a Storage interface (e.g., localStorage in browser)\n *\n * @param key - Prefix key for storage entries\n * @param storage - Storage interface (pass localStorage in browser, or a polyfill in Node.js)\n */\nexport function createLocalStorageStore(\n key: string,\n storage: StorageInterface\n): SyncStateStore {\n return {\n async get(federatedId: string): Promise<CardSyncState | null> {\n const data = storage.getItem(`${key}:${federatedId}`);\n return data ? JSON.parse(data) : null;\n },\n\n async set(state: CardSyncState): Promise<void> {\n storage.setItem(`${key}:${state.federatedId}`, JSON.stringify(state));\n // Also update index\n const indexKey = `${key}:__index__`;\n const index = JSON.parse(storage.getItem(indexKey) || '[]') as string[];\n if (!index.includes(state.federatedId)) {\n index.push(state.federatedId);\n storage.setItem(indexKey, JSON.stringify(index));\n }\n },\n\n async delete(federatedId: string): Promise<void> {\n storage.removeItem(`${key}:${federatedId}`);\n // Update index\n const indexKey = `${key}:__index__`;\n const index = JSON.parse(storage.getItem(indexKey) || '[]') as string[];\n const newIndex = index.filter((id) => id !== federatedId);\n storage.setItem(indexKey, JSON.stringify(newIndex));\n },\n\n async list(): Promise<CardSyncState[]> {\n const indexKey = `${key}:__index__`;\n const index = JSON.parse(storage.getItem(indexKey) || '[]') as string[];\n const states: CardSyncState[] = [];\n for (const id of index) {\n const data = storage.getItem(`${key}:${id}`);\n if (data) {\n states.push(JSON.parse(data));\n }\n }\n return states;\n },\n\n async findByPlatformId(\n platform: PlatformId,\n platformId: string\n ): Promise<CardSyncState | null> {\n const states = await this.list();\n return states.find((s) => s.platformIds[platform] === platformId) || null;\n },\n };\n}\n","/**\n * D1 Sync State Store\n *\n * Cloudflare D1-compatible implementation of SyncStateStore for production\n * federation support on Cloudflare Workers.\n */\n\nimport type { SyncStateStore, CardSyncState, PlatformId, ForkNotification, CardStats } from './types.js';\n\n/**\n * Minimal D1Database interface\n * Compatible with Cloudflare Workers D1 API\n */\nexport interface D1Database {\n prepare(query: string): D1PreparedStatement;\n exec(query: string): Promise<D1ExecResult>;\n batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;\n}\n\nexport interface D1PreparedStatement {\n bind(...values: unknown[]): D1PreparedStatement;\n first<T = unknown>(column?: string): Promise<T | null>;\n run(): Promise<D1Result>;\n all<T = unknown>(): Promise<D1Result<T>>;\n}\n\nexport interface D1Result<T = unknown> {\n results: T[];\n success: boolean;\n meta: {\n duration: number;\n changes: number;\n last_row_id: number;\n served_by?: string;\n };\n}\n\nexport interface D1ExecResult {\n count: number;\n duration: number;\n}\n\n/**\n * Database row representation for CardSyncState\n */\ninterface SyncStateRow {\n federated_id: string;\n local_id: string;\n platform_ids: string;\n last_sync: string;\n version_hash: string;\n status: 'synced' | 'pending' | 'conflict' | 'error';\n conflict: string | null;\n forked_from: string | null;\n forks_count: number;\n fork_notifications: string | null;\n stats: string | null;\n created_at: number;\n updated_at: number;\n}\n\n/**\n * Validate table name to prevent SQL injection\n *\n * Table names must be alphanumeric with underscores only.\n * This is critical since table names cannot be parameterized in SQL.\n */\nfunction validateTableName(name: string): void {\n // Only allow alphanumeric and underscores, must start with letter\n const validPattern = /^[a-zA-Z][a-zA-Z0-9_]*$/;\n if (!validPattern.test(name)) {\n throw new Error(\n `Invalid table name \"${name}\": must start with a letter and contain only alphanumeric characters and underscores`\n );\n }\n // Reasonable length limit\n if (name.length > 64) {\n throw new Error(`Invalid table name \"${name}\": must be 64 characters or less`);\n }\n}\n\n/**\n * D1-compatible implementation of SyncStateStore\n *\n * Stores federation sync state in Cloudflare D1 (SQLite).\n *\n * @example\n * ```typescript\n * const store = new D1SyncStateStore(env.DB);\n * await store.init();\n *\n * // Use with SyncEngine\n * const engine = new SyncEngine({\n * stateStore: store,\n * // ...\n * });\n * ```\n */\nexport class D1SyncStateStore implements SyncStateStore {\n private db: D1Database;\n private tableName: string;\n\n /**\n * Create a new D1SyncStateStore\n *\n * @param db - D1Database instance (from env.DB in Workers)\n * @param tableName - Table name for storing sync state (default: 'federation_sync_state')\n * @throws If tableName contains invalid characters (SQL injection prevention)\n */\n constructor(db: D1Database, tableName = 'federation_sync_state') {\n validateTableName(tableName);\n this.db = db;\n this.tableName = tableName;\n }\n\n /**\n * Initialize the database table\n *\n * Creates the sync state table if it doesn't exist.\n * Safe to call multiple times (idempotent).\n */\n async init(): Promise<void> {\n await this.db.exec(`\n CREATE TABLE IF NOT EXISTS ${this.tableName} (\n federated_id TEXT PRIMARY KEY,\n local_id TEXT NOT NULL,\n platform_ids TEXT NOT NULL,\n last_sync TEXT NOT NULL,\n version_hash TEXT NOT NULL,\n status TEXT NOT NULL CHECK (status IN ('synced', 'pending', 'conflict', 'error')),\n conflict TEXT,\n forked_from TEXT,\n forks_count INTEGER DEFAULT 0,\n fork_notifications TEXT,\n stats TEXT,\n created_at INTEGER DEFAULT (unixepoch()),\n updated_at INTEGER DEFAULT (unixepoch())\n )\n `);\n\n // Create index for platform ID lookups\n await this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_${this.tableName}_local_id ON ${this.tableName}(local_id)\n `);\n\n // Create index for finding forks of a source card\n await this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_${this.tableName}_forked_from\n ON ${this.tableName}(json_extract(forked_from, '$.federatedId'))\n `);\n }\n\n /**\n * Get sync state for a federated card ID\n */\n async get(federatedId: string): Promise<CardSyncState | null> {\n const row = await this.db\n .prepare(`SELECT * FROM ${this.tableName} WHERE federated_id = ?`)\n .bind(federatedId)\n .first<SyncStateRow>();\n\n return row ? this.rowToState(row) : null;\n }\n\n /**\n * Save or update sync state\n */\n async set(state: CardSyncState): Promise<void> {\n const platformIds = JSON.stringify(state.platformIds);\n const lastSync = JSON.stringify(state.lastSync);\n const conflict = state.conflict ? JSON.stringify(state.conflict) : null;\n const forkedFrom = state.forkedFrom ? JSON.stringify(state.forkedFrom) : null;\n const forkNotifications = state.forkNotifications\n ? JSON.stringify(state.forkNotifications)\n : null;\n const stats = state.stats ? JSON.stringify(state.stats) : null;\n\n await this.db\n .prepare(\n `INSERT INTO ${this.tableName} (federated_id, local_id, platform_ids, last_sync, version_hash, status, conflict, forked_from, forks_count, fork_notifications, stats, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, unixepoch())\n ON CONFLICT(federated_id) DO UPDATE SET\n local_id = excluded.local_id,\n platform_ids = excluded.platform_ids,\n last_sync = excluded.last_sync,\n version_hash = excluded.version_hash,\n status = excluded.status,\n conflict = excluded.conflict,\n forked_from = excluded.forked_from,\n forks_count = excluded.forks_count,\n fork_notifications = excluded.fork_notifications,\n stats = excluded.stats,\n updated_at = unixepoch()`\n )\n .bind(\n state.federatedId,\n state.localId,\n platformIds,\n lastSync,\n state.versionHash,\n state.status,\n conflict,\n forkedFrom,\n state.forksCount ?? 0,\n forkNotifications,\n stats\n )\n .run();\n }\n\n /**\n * Delete sync state for a federated card ID\n */\n async delete(federatedId: string): Promise<void> {\n await this.db\n .prepare(`DELETE FROM ${this.tableName} WHERE federated_id = ?`)\n .bind(federatedId)\n .run();\n }\n\n /**\n * List all sync states\n */\n async list(): Promise<CardSyncState[]> {\n const result = await this.db\n .prepare(`SELECT * FROM ${this.tableName} ORDER BY updated_at DESC`)\n .all<SyncStateRow>();\n\n return result.results.map((row) => this.rowToState(row));\n }\n\n /**\n * Find sync state by platform-specific ID\n *\n * @param platform - Platform identifier\n * @param platformId - Platform-specific card ID\n * @returns Sync state if found, null otherwise\n */\n async findByPlatformId(\n platform: PlatformId,\n platformId: string\n ): Promise<CardSyncState | null> {\n // D1 supports JSON extraction with json_extract()\n const result = await this.db\n .prepare(\n `SELECT * FROM ${this.tableName}\n WHERE json_extract(platform_ids, ?) = ?`\n )\n .bind(`$.${platform}`, platformId)\n .first<SyncStateRow>();\n\n return result ? this.rowToState(result) : null;\n }\n\n /**\n * Find sync state by local ID\n *\n * @param localId - Local card ID\n * @returns Sync state if found, null otherwise\n */\n async findByLocalId(localId: string): Promise<CardSyncState | null> {\n const row = await this.db\n .prepare(`SELECT * FROM ${this.tableName} WHERE local_id = ?`)\n .bind(localId)\n .first<SyncStateRow>();\n\n return row ? this.rowToState(row) : null;\n }\n\n /**\n * Get count of all sync states\n */\n async count(): Promise<number> {\n const result = await this.db\n .prepare(`SELECT COUNT(*) as count FROM ${this.tableName}`)\n .first<{ count: number }>();\n\n return result?.count ?? 0;\n }\n\n /**\n * List sync states by status\n *\n * @param status - Status to filter by\n * @returns Array of sync states with matching status\n */\n async listByStatus(\n status: CardSyncState['status']\n ): Promise<CardSyncState[]> {\n const result = await this.db\n .prepare(\n `SELECT * FROM ${this.tableName} WHERE status = ? ORDER BY updated_at DESC`\n )\n .bind(status)\n .all<SyncStateRow>();\n\n return result.results.map((row) => this.rowToState(row));\n }\n\n /**\n * Clear all sync states (for testing)\n *\n * ⚠️ Use with caution - this deletes all data\n */\n async clear(): Promise<void> {\n await this.db.prepare(`DELETE FROM ${this.tableName}`).run();\n }\n\n /**\n * Increment fork count and add notification for a source card\n *\n * Used when receiving a Fork activity to track that someone forked this card.\n * Notifications are capped at 100 to prevent unbounded growth.\n */\n async incrementForkCount(\n federatedId: string,\n notification: ForkNotification\n ): Promise<void> {\n const state = await this.get(federatedId);\n if (!state) {\n return;\n }\n\n // Cap notifications at 100\n const notifications = state.forkNotifications || [];\n if (notifications.length < 100) {\n notifications.push(notification);\n }\n\n state.forksCount = (state.forksCount || 0) + 1;\n state.forkNotifications = notifications;\n\n await this.set(state);\n }\n\n /**\n * Get fork count for a card\n */\n async getForkCount(federatedId: string): Promise<number> {\n const result = await this.db\n .prepare(`SELECT forks_count FROM ${this.tableName} WHERE federated_id = ?`)\n .bind(federatedId)\n .first<{ forks_count: number }>();\n\n return result?.forks_count ?? 0;\n }\n\n /**\n * Find all cards that are forks of a given source card\n */\n async findForks(sourceFederatedId: string): Promise<CardSyncState[]> {\n const result = await this.db\n .prepare(\n `SELECT * FROM ${this.tableName}\n WHERE json_extract(forked_from, '$.federatedId') = ?\n ORDER BY updated_at DESC`\n )\n .bind(sourceFederatedId)\n .all<SyncStateRow>();\n\n return result.results.map((row) => this.rowToState(row));\n }\n\n /**\n * Convert database row to CardSyncState\n */\n private rowToState(row: SyncStateRow): CardSyncState {\n const state: CardSyncState = {\n federatedId: row.federated_id,\n localId: row.local_id,\n platformIds: JSON.parse(row.platform_ids) as Partial<Record<PlatformId, string>>,\n lastSync: JSON.parse(row.last_sync) as Partial<Record<PlatformId, string>>,\n versionHash: row.version_hash,\n status: row.status,\n conflict: row.conflict\n ? (JSON.parse(row.conflict) as CardSyncState['conflict'])\n : undefined,\n };\n\n // Add fork fields if present\n if (row.forked_from) {\n state.forkedFrom = JSON.parse(row.forked_from) as CardSyncState['forkedFrom'];\n }\n if (row.forks_count > 0) {\n state.forksCount = row.forks_count;\n }\n if (row.fork_notifications) {\n state.forkNotifications = JSON.parse(row.fork_notifications) as ForkNotification[];\n }\n // Add stats if present\n if (row.stats) {\n state.stats = JSON.parse(row.stats) as CardStats;\n }\n\n return state;\n }\n}\n","/**\n * Base Platform Adapter\n *\n * Abstract base class for platform adapters.\n */\n\nimport type { CCv3Data } from '@character-foundry/schemas';\nimport { generateUUID } from '@character-foundry/core';\nimport type { PlatformId, PlatformAdapter } from '../types.js';\n\n/**\n * Card with metadata from adapter\n */\nexport interface AdapterCard {\n id: string;\n card: CCv3Data;\n updatedAt: string;\n createdAt?: string;\n}\n\n/**\n * Asset from adapter\n */\nexport interface AdapterAsset {\n name: string;\n type: string;\n data: Uint8Array;\n mimeType?: string;\n}\n\n/**\n * Abstract base adapter with common functionality\n */\nexport abstract class BasePlatformAdapter implements PlatformAdapter {\n abstract readonly platform: PlatformId;\n abstract readonly displayName: string;\n\n /**\n * Check if platform is available\n */\n abstract isAvailable(): Promise<boolean>;\n\n /**\n * Get a card by local ID\n */\n abstract getCard(localId: string): Promise<CCv3Data | null>;\n\n /**\n * List all cards\n */\n abstract listCards(options?: {\n limit?: number;\n offset?: number;\n since?: string;\n }): Promise<AdapterCard[]>;\n\n /**\n * Save/update a card\n */\n abstract saveCard(card: CCv3Data, localId?: string): Promise<string>;\n\n /**\n * Delete a card\n */\n abstract deleteCard(localId: string): Promise<boolean>;\n\n /**\n * Get card assets\n */\n abstract getAssets(localId: string): Promise<AdapterAsset[]>;\n\n /**\n * Get last modified timestamp\n */\n abstract getLastModified(localId: string): Promise<string | null>;\n\n /**\n * Generate a new local ID using crypto-grade randomness\n */\n protected generateId(): string {\n const timestamp = Date.now();\n const random = generateUUID().split('-')[0]; // Use first segment of UUID\n return `${timestamp}-${random}`;\n }\n}\n\n/**\n * In-memory adapter for testing\n */\nexport class MemoryPlatformAdapter extends BasePlatformAdapter {\n readonly platform: PlatformId;\n readonly displayName: string;\n\n private cards: Map<string, AdapterCard> = new Map();\n private assets: Map<string, AdapterAsset[]> = new Map();\n\n constructor(platform: PlatformId = 'custom', displayName = 'Memory Store') {\n super();\n this.platform = platform;\n this.displayName = displayName;\n }\n\n async isAvailable(): Promise<boolean> {\n return true;\n }\n\n async getCard(localId: string): Promise<CCv3Data | null> {\n const entry = this.cards.get(localId);\n return entry?.card || null;\n }\n\n async listCards(options?: {\n limit?: number;\n offset?: number;\n since?: string;\n }): Promise<AdapterCard[]> {\n let cards = Array.from(this.cards.values());\n\n // Filter by since\n if (options?.since) {\n const sinceDate = new Date(options.since);\n cards = cards.filter((c) => new Date(c.updatedAt) > sinceDate);\n }\n\n // Sort by updated time (newest first)\n cards.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());\n\n // Apply pagination\n const offset = options?.offset || 0;\n const limit = options?.limit || cards.length;\n cards = cards.slice(offset, offset + limit);\n\n return cards;\n }\n\n async saveCard(card: CCv3Data, localId?: string): Promise<string> {\n const id = localId || this.generateId();\n const now = new Date().toISOString();\n\n const existing = this.cards.get(id);\n\n this.cards.set(id, {\n id,\n card,\n updatedAt: now,\n createdAt: existing?.createdAt || now,\n });\n\n return id;\n }\n\n async deleteCard(localId: string): Promise<boolean> {\n const existed = this.cards.has(localId);\n this.cards.delete(localId);\n this.assets.delete(localId);\n return existed;\n }\n\n async getAssets(localId: string): Promise<AdapterAsset[]> {\n return this.assets.get(localId) || [];\n }\n\n async getLastModified(localId: string): Promise<string | null> {\n const entry = this.cards.get(localId);\n return entry?.updatedAt || null;\n }\n\n /**\n * Set assets for a card (for testing)\n */\n setAssets(localId: string, assets: AdapterAsset[]): void {\n this.assets.set(localId, assets);\n }\n\n /**\n * Clear all data (for testing)\n */\n clear(): void {\n this.cards.clear();\n this.assets.clear();\n }\n\n /**\n * Get card count\n */\n count(): number {\n return this.cards.size;\n }\n}\n","/**\n * HTTP Platform Adapter\n *\n * Adapter for platforms accessible via HTTP API.\n */\n\nimport type { CCv3Data } from '@character-foundry/schemas';\nimport type { PlatformId } from '../types.js';\nimport { BasePlatformAdapter, type AdapterCard, type AdapterAsset } from './base.js';\n\n/**\n * HTTP fetch function type\n */\nexport type FetchFn = (url: string, init?: RequestInit) => Promise<Response>;\n\n/**\n * HTTP adapter configuration\n */\nexport interface HttpAdapterConfig {\n /** Platform ID */\n platform: PlatformId;\n /** Display name */\n displayName: string;\n /** Base URL of the API */\n baseUrl: string;\n /** API endpoints */\n endpoints: {\n /** List cards: GET */\n list: string;\n /** Get card: GET (append /:id) */\n get: string;\n /** Create card: POST */\n create: string;\n /** Update card: PUT/PATCH (append /:id) */\n update: string;\n /** Delete card: DELETE (append /:id) */\n delete: string;\n /** Get assets: GET (append /:id) */\n assets?: string;\n /** Health check: GET */\n health?: string;\n };\n /** Authentication header */\n auth?: {\n type: 'bearer' | 'api-key' | 'basic';\n token: string;\n header?: string; // Custom header name for api-key\n };\n /** Custom fetch function (for Node.js or testing) */\n fetch?: FetchFn;\n /**\n * Request timeout in milliseconds.\n * Default: 30000 (30 seconds)\n *\n * @security Prevents hanging connections that could cause resource exhaustion.\n */\n timeout?: number;\n /** Response transformers */\n transformers?: {\n /** Transform list response to AdapterCard[] */\n list?: (response: unknown) => AdapterCard[];\n /** Transform get response to CCv3Data */\n get?: (response: unknown) => CCv3Data;\n /** Transform card to create request body */\n create?: (card: CCv3Data) => unknown;\n /** Transform card to update request body */\n update?: (card: CCv3Data) => unknown;\n /** Extract ID from create response */\n extractId?: (response: unknown) => string;\n };\n}\n\n/**\n * Error thrown when resource ID validation fails\n */\nexport class InvalidResourceIdError extends Error {\n constructor(\n public readonly id: string,\n public readonly reason: string\n ) {\n super(`Invalid resource ID \"${id}\": ${reason}`);\n this.name = 'InvalidResourceIdError';\n }\n}\n\n/**\n * Validate and encode a resource ID to prevent SSRF and path traversal attacks.\n *\n * @security This prevents:\n * - Path traversal (../ sequences)\n * - Absolute path injection (/etc/passwd)\n * - Protocol injection (https://evil.com)\n * - URL encoding attacks (%2e%2e)\n *\n * @param id - The resource ID to validate\n * @returns URL-encoded safe ID\n * @throws InvalidResourceIdError if ID contains malicious patterns\n */\nfunction validateAndEncodeId(id: string): string {\n // Decode any URL encoding first to catch %2e%2e (encoded ..)\n let decoded: string;\n try {\n decoded = decodeURIComponent(id);\n } catch {\n // Invalid URL encoding - reject\n throw new InvalidResourceIdError(id, 'invalid URL encoding');\n }\n\n // Check decoded value for path traversal\n if (decoded.includes('..')) {\n throw new InvalidResourceIdError(id, 'path traversal detected');\n }\n\n // Check for absolute paths\n if (decoded.startsWith('/')) {\n throw new InvalidResourceIdError(id, 'absolute path not allowed');\n }\n\n // Check for protocol injection (http://, https://, file://, etc.)\n if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(decoded)) {\n throw new InvalidResourceIdError(id, 'protocol injection detected');\n }\n\n // Check for backslashes (Windows-style paths)\n if (decoded.includes('\\\\')) {\n throw new InvalidResourceIdError(id, 'backslash not allowed');\n }\n\n // URL-encode the ID for safe concatenation\n return encodeURIComponent(id);\n}\n\n/** Default timeout for HTTP requests (30 seconds) */\nconst DEFAULT_TIMEOUT_MS = 30000;\n\n/**\n * HTTP-based platform adapter\n */\nexport class HttpPlatformAdapter extends BasePlatformAdapter {\n readonly platform: PlatformId;\n readonly displayName: string;\n\n private config: HttpAdapterConfig;\n private fetchFn: FetchFn;\n private timeoutMs: number;\n\n constructor(config: HttpAdapterConfig) {\n super();\n this.platform = config.platform;\n this.displayName = config.displayName;\n this.config = config;\n this.fetchFn = config.fetch || globalThis.fetch.bind(globalThis);\n this.timeoutMs = config.timeout ?? DEFAULT_TIMEOUT_MS;\n }\n\n /**\n * Execute fetch with timeout using AbortController\n *\n * @security Prevents hanging connections that could cause resource exhaustion\n */\n private async fetchWithTimeout(url: string, init?: RequestInit): Promise<Response> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);\n\n try {\n const response = await this.fetchFn(url, {\n ...init,\n signal: controller.signal,\n });\n return response;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Build headers for requests\n */\n private buildHeaders(contentType?: string): Record<string, string> {\n const headers: Record<string, string> = {};\n\n if (contentType) {\n headers['Content-Type'] = contentType;\n }\n\n if (this.config.auth) {\n switch (this.config.auth.type) {\n case 'bearer':\n headers['Authorization'] = `Bearer ${this.config.auth.token}`;\n break;\n case 'api-key':\n headers[this.config.auth.header || 'X-API-Key'] = this.config.auth.token;\n break;\n case 'basic':\n headers['Authorization'] = `Basic ${this.config.auth.token}`;\n break;\n }\n }\n\n return headers;\n }\n\n /**\n * Build full URL with optional resource ID.\n *\n * @security ID is validated and encoded to prevent SSRF/path traversal.\n * @throws InvalidResourceIdError if ID contains malicious patterns\n */\n private buildUrl(endpoint: string, id?: string): string {\n let url = `${this.config.baseUrl}${endpoint}`;\n if (id) {\n // Validate and encode ID to prevent SSRF and path traversal\n const safeId = validateAndEncodeId(id);\n url = `${url}/${safeId}`;\n }\n return url;\n }\n\n async isAvailable(): Promise<boolean> {\n try {\n const endpoint = this.config.endpoints.health || this.config.endpoints.list;\n const response = await this.fetchWithTimeout(\n this.buildUrl(endpoint),\n {\n method: 'GET',\n headers: this.buildHeaders(),\n }\n );\n return response.ok;\n } catch {\n return false;\n }\n }\n\n async getCard(localId: string): Promise<CCv3Data | null> {\n try {\n const response = await this.fetchWithTimeout(\n this.buildUrl(this.config.endpoints.get, localId),\n {\n method: 'GET',\n headers: this.buildHeaders(),\n }\n );\n\n if (!response.ok) {\n if (response.status === 404) return null;\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const data = await response.json();\n return this.config.transformers?.get\n ? this.config.transformers.get(data)\n : data as CCv3Data;\n } catch (err) {\n console.error(`Failed to get card ${localId}:`, err);\n return null;\n }\n }\n\n async listCards(options?: {\n limit?: number;\n offset?: number;\n since?: string;\n }): Promise<AdapterCard[]> {\n const url = new URL(this.buildUrl(this.config.endpoints.list));\n\n if (options?.limit) {\n url.searchParams.set('limit', String(options.limit));\n }\n if (options?.offset) {\n url.searchParams.set('offset', String(options.offset));\n }\n if (options?.since) {\n url.searchParams.set('since', options.since);\n }\n\n const response = await this.fetchWithTimeout(\n url.toString(),\n {\n method: 'GET',\n headers: this.buildHeaders(),\n }\n );\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const data = await response.json();\n return this.config.transformers?.list\n ? this.config.transformers.list(data)\n : data as AdapterCard[];\n }\n\n async saveCard(card: CCv3Data, localId?: string): Promise<string> {\n if (localId) {\n // Update existing\n const body = this.config.transformers?.update\n ? this.config.transformers.update(card)\n : card;\n\n const response = await this.fetchWithTimeout(\n this.buildUrl(this.config.endpoints.update, localId),\n {\n method: 'PUT',\n headers: this.buildHeaders('application/json'),\n body: JSON.stringify(body),\n }\n );\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n return localId;\n } else {\n // Create new\n const body = this.config.transformers?.create\n ? this.config.transformers.create(card)\n : card;\n\n const response = await this.fetchWithTimeout(\n this.buildUrl(this.config.endpoints.create),\n {\n method: 'POST',\n headers: this.buildHeaders('application/json'),\n body: JSON.stringify(body),\n }\n );\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const data = await response.json();\n return this.config.transformers?.extractId\n ? this.config.transformers.extractId(data)\n : (data as { id: string }).id;\n }\n }\n\n async deleteCard(localId: string): Promise<boolean> {\n const response = await this.fetchWithTimeout(\n this.buildUrl(this.config.endpoints.delete, localId),\n {\n method: 'DELETE',\n headers: this.buildHeaders(),\n }\n );\n\n return response.ok;\n }\n\n async getAssets(localId: string): Promise<AdapterAsset[]> {\n if (!this.config.endpoints.assets) {\n return [];\n }\n\n try {\n const response = await this.fetchWithTimeout(\n this.buildUrl(this.config.endpoints.assets, localId),\n {\n method: 'GET',\n headers: this.buildHeaders(),\n }\n );\n\n if (!response.ok) {\n return [];\n }\n\n return (await response.json()) as AdapterAsset[];\n } catch {\n return [];\n }\n }\n\n async getLastModified(localId: string): Promise<string | null> {\n try {\n const response = await this.fetchWithTimeout(\n this.buildUrl(this.config.endpoints.get, localId),\n {\n method: 'HEAD',\n headers: this.buildHeaders(),\n }\n );\n\n if (!response.ok) {\n return null;\n }\n\n return response.headers.get('Last-Modified');\n } catch {\n return null;\n }\n }\n}\n\n/**\n * Create an HTTP adapter for Character Archive API\n */\nexport function createArchiveAdapter(\n baseUrl: string,\n apiKey?: string\n): HttpPlatformAdapter {\n return new HttpPlatformAdapter({\n platform: 'archive',\n displayName: 'Character Archive',\n baseUrl,\n endpoints: {\n list: '/api/characters',\n get: '/api/characters',\n create: '/api/characters',\n update: '/api/characters',\n delete: '/api/characters',\n assets: '/api/characters/assets',\n health: '/api/health',\n },\n auth: apiKey ? { type: 'api-key', token: apiKey } : undefined,\n transformers: {\n list: (data) => {\n const response = data as { characters: Array<{ id: string; data: CCv3Data; updatedAt: string }> };\n return response.characters.map((c) => ({\n id: c.id,\n card: c.data,\n updatedAt: c.updatedAt,\n }));\n },\n get: (data) => (data as { data: CCv3Data }).data,\n extractId: (data) => (data as { id: string }).id,\n },\n });\n}\n\n/**\n * Create an HTTP adapter for CardsHub API\n */\nexport function createHubAdapter(\n baseUrl: string,\n apiKey?: string\n): HttpPlatformAdapter {\n return new HttpPlatformAdapter({\n platform: 'hub',\n displayName: 'CardsHub',\n baseUrl,\n endpoints: {\n list: '/api/cards',\n get: '/api/cards',\n create: '/api/cards',\n update: '/api/cards',\n delete: '/api/cards',\n assets: '/api/cards/assets',\n health: '/api/health',\n },\n auth: apiKey ? { type: 'bearer', token: apiKey } : undefined,\n });\n}\n","/**\n * SillyTavern Platform Adapter\n *\n * Adapter for SillyTavern integration via plugin.\n * This provides the interface that a SillyTavern plugin would implement.\n */\n\nimport type { CCv3Data } from '@character-foundry/schemas';\nimport type { PlatformId } from '../types.js';\nimport { BasePlatformAdapter, type AdapterCard, type AdapterAsset } from './base.js';\n\n/**\n * SillyTavern character format (simplified)\n */\nexport interface STCharacter {\n /** Character filename/ID */\n name: string;\n /** Avatar filename */\n avatar: string;\n /** Character data (TavernCard format) */\n data: {\n name: string;\n description: string;\n personality: string;\n scenario: string;\n first_mes: string;\n mes_example: string;\n creator_notes?: string;\n system_prompt?: string;\n post_history_instructions?: string;\n alternate_greetings?: string[];\n character_book?: {\n entries: Array<{\n keys: string[];\n content: string;\n enabled: boolean;\n [key: string]: unknown;\n }>;\n [key: string]: unknown;\n };\n tags?: string[];\n creator?: string;\n character_version?: string;\n extensions?: Record<string, unknown>;\n };\n}\n\n/**\n * Usage stats for a character in SillyTavern\n */\nexport interface STCharacterStats {\n /** Number of chats with this character */\n chatCount?: number;\n /** Total messages exchanged */\n messageCount?: number;\n /** Last time this character was used */\n lastUsed?: string;\n /** When the character was first added */\n installedAt?: string;\n}\n\n/**\n * Interface that SillyTavern plugin must implement\n */\nexport interface SillyTavernBridge {\n /** Get all characters */\n getCharacters(): Promise<STCharacter[]>;\n\n /** Get a character by name/ID */\n getCharacter(name: string): Promise<STCharacter | null>;\n\n /** Save a character */\n saveCharacter(character: STCharacter): Promise<string>;\n\n /** Delete a character */\n deleteCharacter(name: string): Promise<boolean>;\n\n /** Get character avatar */\n getAvatar(name: string): Promise<Uint8Array | null>;\n\n /** Check if SillyTavern is available */\n isConnected(): Promise<boolean>;\n\n /**\n * Get usage stats for a character (optional)\n * SillyTavern plugins can implement this to report usage data back to hub\n */\n getCharacterStats?(name: string): Promise<STCharacterStats | null>;\n\n /**\n * Get stats for all characters (optional)\n * Returns a map of character name to stats\n */\n getAllStats?(): Promise<Map<string, STCharacterStats>>;\n\n /**\n * Notify hub that a card was installed (optional)\n * Called when a new character is added from federation\n * @param federatedId - The federated URI of the installed card\n * @param hubInbox - The hub's inbox URL to notify\n */\n notifyInstall?(federatedId: string, hubInbox: string): Promise<void>;\n}\n\n/**\n * Convert STCharacter to CCv3Data\n */\nexport function stCharacterToCCv3(st: STCharacter): CCv3Data {\n return {\n spec: 'chara_card_v3',\n spec_version: '3.0',\n data: {\n name: st.data.name,\n description: st.data.description,\n personality: st.data.personality,\n scenario: st.data.scenario,\n first_mes: st.data.first_mes,\n mes_example: st.data.mes_example,\n creator_notes: st.data.creator_notes || '',\n system_prompt: st.data.system_prompt || '',\n post_history_instructions: st.data.post_history_instructions || '',\n alternate_greetings: st.data.alternate_greetings || [],\n group_only_greetings: [],\n tags: st.data.tags || [],\n creator: st.data.creator || '',\n character_version: st.data.character_version || '',\n character_book: st.data.character_book as CCv3Data['data']['character_book'],\n extensions: st.data.extensions || {},\n },\n };\n}\n\n/**\n * Convert CCv3Data to STCharacter\n */\nexport function ccv3ToSTCharacter(card: CCv3Data, filename?: string): STCharacter {\n const data = card.data;\n const name = filename || data.name.replace(/[^a-zA-Z0-9]/g, '_');\n\n return {\n name,\n avatar: `${name}.png`,\n data: {\n name: data.name,\n description: data.description,\n personality: data.personality ?? '', // Coerce null to empty string\n scenario: data.scenario,\n first_mes: data.first_mes,\n mes_example: data.mes_example ?? '', // Coerce null to empty string\n creator_notes: data.creator_notes,\n system_prompt: data.system_prompt,\n post_history_instructions: data.post_history_instructions,\n alternate_greetings: data.alternate_greetings,\n // Cast character_book - ST format is compatible but types differ slightly\n character_book: data.character_book as STCharacter['data']['character_book'],\n tags: data.tags,\n creator: data.creator,\n character_version: data.character_version,\n extensions: data.extensions,\n },\n };\n}\n\n/**\n * SillyTavern adapter\n * Requires a bridge implementation from the SillyTavern plugin\n */\nexport class SillyTavernAdapter extends BasePlatformAdapter {\n readonly platform: PlatformId = 'sillytavern';\n readonly displayName = 'SillyTavern';\n\n private bridge: SillyTavernBridge;\n\n constructor(bridge: SillyTavernBridge) {\n super();\n this.bridge = bridge;\n }\n\n async isAvailable(): Promise<boolean> {\n return this.bridge.isConnected();\n }\n\n async getCard(localId: string): Promise<CCv3Data | null> {\n const st = await this.bridge.getCharacter(localId);\n if (!st) return null;\n return stCharacterToCCv3(st);\n }\n\n async listCards(options?: {\n limit?: number;\n offset?: number;\n since?: string;\n }): Promise<AdapterCard[]> {\n const characters = await this.bridge.getCharacters();\n\n let cards = characters.map((st) => ({\n id: st.name,\n card: stCharacterToCCv3(st),\n updatedAt: new Date().toISOString(), // ST doesn't track this\n }));\n\n // Apply pagination\n const offset = options?.offset || 0;\n const limit = options?.limit || cards.length;\n cards = cards.slice(offset, offset + limit);\n\n return cards;\n }\n\n async saveCard(card: CCv3Data, localId?: string): Promise<string> {\n const stChar = ccv3ToSTCharacter(card, localId);\n return this.bridge.saveCharacter(stChar);\n }\n\n async deleteCard(localId: string): Promise<boolean> {\n return this.bridge.deleteCharacter(localId);\n }\n\n async getAssets(localId: string): Promise<AdapterAsset[]> {\n const avatar = await this.bridge.getAvatar(localId);\n if (!avatar) return [];\n\n return [{\n name: 'avatar',\n type: 'icon',\n data: avatar,\n mimeType: 'image/png',\n }];\n }\n\n async getLastModified(localId: string): Promise<string | null> {\n // SillyTavern doesn't track modification times\n const char = await this.bridge.getCharacter(localId);\n return char ? new Date().toISOString() : null;\n }\n\n /**\n * Get stats for a character (if bridge supports it)\n */\n async getStats(localId: string): Promise<STCharacterStats | null> {\n if (!this.bridge.getCharacterStats) {\n return null;\n }\n return this.bridge.getCharacterStats(localId);\n }\n\n /**\n * Get stats for all characters (if bridge supports it)\n */\n async getAllStats(): Promise<Map<string, STCharacterStats> | null> {\n if (!this.bridge.getAllStats) {\n return null;\n }\n return this.bridge.getAllStats();\n }\n\n /**\n * Notify hub about installation (if bridge supports it)\n */\n async notifyInstall(federatedId: string, hubInbox: string): Promise<boolean> {\n if (!this.bridge.notifyInstall) {\n return false;\n }\n await this.bridge.notifyInstall(federatedId, hubInbox);\n return true;\n }\n}\n\n/**\n * Create a mock SillyTavern bridge for testing\n */\nexport function createMockSTBridge(): SillyTavernBridge & {\n characters: Map<string, STCharacter>;\n avatars: Map<string, Uint8Array>;\n stats: Map<string, STCharacterStats>;\n installNotifications: Array<{ federatedId: string; hubInbox: string }>;\n} {\n const characters = new Map<string, STCharacter>();\n const avatars = new Map<string, Uint8Array>();\n const stats = new Map<string, STCharacterStats>();\n const installNotifications: Array<{ federatedId: string; hubInbox: string }> = [];\n\n return {\n characters,\n avatars,\n stats,\n installNotifications,\n\n async getCharacters() {\n return Array.from(characters.values());\n },\n\n async getCharacter(name) {\n return characters.get(name) || null;\n },\n\n async saveCharacter(character) {\n characters.set(character.name, character);\n // Auto-create initial stats on save\n if (!stats.has(character.name)) {\n stats.set(character.name, {\n installedAt: new Date().toISOString(),\n chatCount: 0,\n messageCount: 0,\n });\n }\n return character.name;\n },\n\n async deleteCharacter(name) {\n const existed = characters.has(name);\n characters.delete(name);\n avatars.delete(name);\n stats.delete(name);\n return existed;\n },\n\n async getAvatar(name) {\n return avatars.get(name) || null;\n },\n\n async isConnected() {\n return true;\n },\n\n async getCharacterStats(name) {\n return stats.get(name) || null;\n },\n\n async getAllStats() {\n return new Map(stats);\n },\n\n async notifyInstall(federatedId, hubInbox) {\n installNotifications.push({ federatedId, hubInbox });\n },\n };\n}\n","import type { FederationConfig } from '../types.js';\nimport { assertFederationEnabled } from '../index.js';\n\nexport interface WebFingerLink {\n rel: string;\n type?: string;\n href: string;\n template?: string;\n}\n\nexport interface WebFingerResponse {\n subject: string;\n aliases?: string[];\n properties?: Record<string, string>;\n links: WebFingerLink[];\n}\n\n/**\n * Handle WebFinger discovery request\n * \n * @param resource - The resource URI being requested (e.g. acct:user@domain)\n * @param config - Federation configuration containing actor details\n * @returns The WebFinger response object or null if not found\n */\nexport function handleWebFinger(\n resource: string,\n config: FederationConfig\n): WebFingerResponse | null {\n assertFederationEnabled('handleWebFinger');\n if (!resource) return null;\n\n const { actor } = config;\n \n // Derive domain from actor ID\n let domain: string;\n try {\n domain = new URL(actor.id).host;\n } catch {\n return null;\n }\n\n const validResources = [\n `acct:${actor.preferredUsername}@${domain}`,\n actor.id\n ];\n\n if (!validResources.includes(resource)) {\n return null;\n }\n\n return {\n subject: `acct:${actor.preferredUsername}@${domain}`,\n aliases: [actor.id],\n links: [\n {\n rel: 'self',\n type: 'application/activity+json',\n href: actor.id,\n },\n {\n rel: 'http://webfinger.net/rel/profile-page',\n type: 'text/html',\n href: actor.id, // Often the actor ID resolves to a profile page in browsers\n }\n ],\n };\n}\n","import type { FederationConfig } from '../types.js';\nimport { assertFederationEnabled } from '../index.js';\n\nexport interface NodeInfoLink {\n rel: string;\n href: string;\n}\n\nexport interface NodeInfoDiscoveryResponse {\n links: NodeInfoLink[];\n}\n\nexport interface NodeInfoUsage {\n users: {\n total: number;\n activeMonth?: number;\n activeHalfyear?: number;\n };\n localPosts?: number;\n localComments?: number;\n}\n\nexport interface NodeInfoResponse {\n version: string;\n software: {\n name: string;\n version: string;\n };\n protocols: string[];\n services: {\n inbound: string[];\n outbound: string[];\n };\n openRegistrations: boolean;\n usage: NodeInfoUsage;\n metadata: Record<string, unknown>;\n}\n\n/**\n * Handle NodeInfo discovery request (/.well-known/nodeinfo)\n * \n * @param baseUrl - The base URL of the server (e.g. https://hub.example.com)\n * @returns The NodeInfo discovery response\n */\nexport function handleNodeInfoDiscovery(baseUrl: string): NodeInfoDiscoveryResponse {\n assertFederationEnabled('handleNodeInfoDiscovery');\n // Ensure no trailing slash\n const origin = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n \n return {\n links: [\n {\n rel: 'http://nodeinfo.diaspora.foundation/ns/schema/2.0',\n href: `${origin}/api/federation/nodeinfo/2.0`,\n },\n {\n rel: 'http://nodeinfo.diaspora.foundation/ns/schema/2.1',\n href: `${origin}/api/federation/nodeinfo/2.1`,\n },\n ],\n };\n}\n\n/**\n * Handle NodeInfo data request\n * \n * @param config - Federation configuration\n * @param version - NodeInfo version ('2.0' or '2.1')\n * @returns The NodeInfo response\n */\nexport function handleNodeInfo(\n config: FederationConfig,\n version: '2.0' | '2.1' = '2.1'\n): NodeInfoResponse {\n assertFederationEnabled('handleNodeInfo');\n return {\n version,\n software: {\n name: 'character-foundry',\n version: '0.1.0', \n },\n protocols: [\n 'activitypub',\n ],\n services: {\n inbound: [],\n outbound: [],\n },\n openRegistrations: false,\n usage: {\n users: {\n total: 1, // TODO: Count actual users if multi-tenant\n },\n },\n metadata: {\n nodeName: config.actor.name,\n nodeDescription: config.actor.summary,\n },\n };\n}\n","import type { FederationConfig, FederatedActor } from '../types.js';\nimport { assertFederationEnabled } from '../index.js';\n\n/**\n * Handle Actor profile request\n * \n * @param config - Federation configuration\n * @returns The Actor JSON-LD object\n */\nexport function handleActor(config: FederationConfig): FederatedActor {\n assertFederationEnabled('handleActor');\n // Ensure @context is present (it should be in the actor object, but good to verify)\n return {\n ...config.actor,\n '@context': config.actor['@context'] || [\n 'https://www.w3.org/ns/activitystreams',\n 'https://w3id.org/security/v1'\n ],\n } as FederatedActor;\n}\n","/**\n * Moderation ActivityPub Activities\n *\n * Create and parse Flag/Block activities per ActivityPub spec.\n */\n\nimport { generateActivityId } from '../activitypub.js';\nimport type { FlagActivity, BlockActivity, ReportCategory } from './types.js';\n\n/**\n * ActivityPub context for moderation activities\n */\nexport const MODERATION_ACTIVITY_CONTEXT = [\n 'https://www.w3.org/ns/activitystreams',\n {\n moderation: 'https://character-foundry.dev/ns/moderation#',\n category: { '@id': 'moderation:category' },\n },\n] as const;\n\n/**\n * Create a Flag activity for reporting content/actors\n *\n * Per ActivityPub spec, Flag is delivered directly to the target's instance inbox,\n * NOT wrapped in a Create activity.\n *\n * @example\n * ```typescript\n * const flag = createFlagActivity(\n * 'https://myinstance.com/users/reporter',\n * 'https://other.com/cards/problematic-card',\n * 'https://myinstance.com',\n * {\n * content: 'This card contains spam',\n * category: 'spam',\n * to: ['https://other.com/inbox'],\n * }\n * );\n * ```\n */\nexport function createFlagActivity(\n reporterActorId: string,\n targetIds: string | string[],\n baseUrl: string,\n options?: {\n /** Report description/reason */\n content?: string;\n /** Report category (flexible string) */\n category?: ReportCategory;\n /** Target inbox(es) */\n to?: string[];\n }\n): FlagActivity {\n const targets = Array.isArray(targetIds) ? targetIds : [targetIds];\n\n return {\n '@context': [...MODERATION_ACTIVITY_CONTEXT],\n id: generateActivityId(baseUrl),\n type: 'Flag',\n actor: reporterActorId,\n object: targets.length === 1 ? targets[0]! : targets,\n content: options?.content,\n category: options?.category,\n published: new Date().toISOString(),\n to: options?.to,\n };\n}\n\n/**\n * Parse incoming Flag activity\n *\n * @returns Parsed flag data or null if invalid\n */\nexport function parseFlagActivity(activity: unknown): {\n actorId: string;\n targetIds: string[];\n content?: string;\n category?: string;\n activityId: string;\n} | null {\n if (!activity || typeof activity !== 'object') return null;\n\n const act = activity as Record<string, unknown>;\n\n // Must be a Flag activity\n if (act.type !== 'Flag') return null;\n\n // Required fields\n if (typeof act.actor !== 'string') return null;\n if (typeof act.id !== 'string') return null;\n if (!act.object) return null;\n\n // Parse targets\n const targets = Array.isArray(act.object)\n ? act.object.filter((t): t is string => typeof t === 'string')\n : typeof act.object === 'string'\n ? [act.object]\n : [];\n\n if (targets.length === 0) return null;\n\n return {\n actorId: act.actor,\n targetIds: targets,\n content: typeof act.content === 'string' ? act.content : undefined,\n category: typeof act.category === 'string' ? act.category : undefined,\n activityId: act.id,\n };\n}\n\n/**\n * Validate a Flag activity has all required fields\n */\nexport function validateFlagActivity(activity: unknown): {\n valid: boolean;\n error?: string;\n} {\n const parsed = parseFlagActivity(activity);\n if (!parsed) {\n return {\n valid: false,\n error: 'Invalid flag activity: missing required fields',\n };\n }\n\n // Validate all target URIs\n for (const target of parsed.targetIds) {\n try {\n new URL(target);\n } catch {\n return {\n valid: false,\n error: `Invalid flag activity: target \"${target}\" is not a valid URI`,\n };\n }\n }\n\n // Validate content length if provided\n if (parsed.content && parsed.content.length > 5000) {\n return {\n valid: false,\n error: 'Flag content too long (max 5000 chars)',\n };\n }\n\n return { valid: true };\n}\n\n/**\n * Create a Block activity for instance-level blocking\n *\n * Block activities are typically:\n * 1. Stored locally for enforcement\n * 2. Optionally announced to trusted federation peers\n *\n * @example\n * ```typescript\n * const block = createBlockActivity(\n * 'https://myinstance.com/actor',\n * 'evil.example.com',\n * 'https://myinstance.com',\n * { summary: 'Spam and harassment' }\n * );\n * ```\n */\nexport function createBlockActivity(\n adminActorId: string,\n blockedTarget: string,\n baseUrl: string,\n options?: {\n /** Block reason */\n summary?: string;\n /** Recipients for federation announcement */\n to?: string[];\n }\n): BlockActivity {\n return {\n '@context': [...MODERATION_ACTIVITY_CONTEXT],\n id: generateActivityId(baseUrl),\n type: 'Block',\n actor: adminActorId,\n object: blockedTarget,\n summary: options?.summary,\n published: new Date().toISOString(),\n };\n}\n\n/**\n * Parse incoming Block activity\n *\n * @returns Parsed block data or null if invalid\n */\nexport function parseBlockActivity(activity: unknown): {\n actorId: string;\n targetId: string;\n summary?: string;\n activityId: string;\n} | null {\n if (!activity || typeof activity !== 'object') return null;\n\n const act = activity as Record<string, unknown>;\n\n // Must be a Block activity\n if (act.type !== 'Block') return null;\n\n // Required fields\n if (typeof act.actor !== 'string') return null;\n if (typeof act.object !== 'string') return null;\n if (typeof act.id !== 'string') return null;\n\n return {\n actorId: act.actor,\n targetId: act.object,\n summary: typeof act.summary === 'string' ? act.summary : undefined,\n activityId: act.id,\n };\n}\n\n/**\n * Validate a Block activity has all required fields\n */\nexport function validateBlockActivity(activity: unknown): {\n valid: boolean;\n error?: string;\n} {\n const parsed = parseBlockActivity(activity);\n if (!parsed) {\n return {\n valid: false,\n error: 'Invalid block activity: missing required fields',\n };\n }\n\n // Block target should be a domain or actor URI\n // Domains won't parse as URLs, actor URIs will\n const target = parsed.targetId;\n const isDomain = /^[a-zA-Z0-9][a-zA-Z0-9-]*(\\.[a-zA-Z0-9][a-zA-Z0-9-]*)+$/.test(target);\n let isValidUri = false;\n try {\n new URL(target);\n isValidUri = true;\n } catch {\n // Not a URI\n }\n\n if (!isDomain && !isValidUri) {\n return {\n valid: false,\n error: 'Invalid block activity: target must be a domain or valid URI',\n };\n }\n\n return { valid: true };\n}\n","/**\n * HTTP Signatures\n *\n * Implements HTTP message signing per draft-cavage-http-signatures\n * for ActivityPub federation.\n *\n * Works in both Node.js and browser/Workers environments using\n * the Web Crypto API.\n */\n\nimport type { FederatedActivity, FederatedActor } from './types.js';\n\n/**\n * Parsed HTTP signature header\n */\nexport interface ParsedSignature {\n keyId: string;\n algorithm: string;\n headers: string[];\n signature: string;\n}\n\n/**\n * Required headers for strict signature validation.\n * These headers MUST be included in the signed header list when strictMode is enabled.\n */\nexport const REQUIRED_SIGNED_HEADERS = ['(request-target)', 'host', 'date'] as const;\n\n/**\n * Signature validation options\n */\nexport interface SignatureValidationOptions {\n /** HTTP method (GET, POST, etc.) */\n method: string;\n /** Request path (e.g., /inbox) */\n path: string;\n /** Function to fetch actor by ID */\n fetchActor: (actorId: string) => Promise<FederatedActor | null>;\n /** Maximum age in seconds for Date header. Default: 300 (5 minutes) */\n maxAge?: number;\n /**\n * Enable strict signature validation mode.\n *\n * When enabled, signatures MUST include these headers: (request-target), host, date.\n * This prevents replay attacks and cross-host request reuse.\n *\n * Default: false (for backwards compatibility with existing federation peers)\n * Recommended: true for new deployments\n *\n * @security Without strict mode, attackers can:\n * - Replay captured requests if Date is not signed\n * - Reuse signatures across different hosts if host is not signed\n */\n strictMode?: boolean;\n}\n\n/**\n * Signature validation result\n */\nexport interface SignatureValidationResult {\n valid: boolean;\n error?: string;\n actor?: FederatedActor;\n keyId?: string;\n}\n\n/**\n * Request signing options\n */\nexport interface SigningOptions {\n /** Private key in PEM format */\n privateKeyPem: string;\n /** Key ID (usually actor#main-key) */\n keyId: string;\n /** HTTP method */\n method: string;\n /** Request path */\n path: string;\n /** Optional: target host header */\n host?: string;\n /** Optional: digest header */\n digest?: string;\n /** Optional: content-type header */\n contentType?: string;\n}\n\n/**\n * Parse an HTTP Signature header\n *\n * Format: keyId=\"...\",algorithm=\"...\",headers=\"...\",signature=\"...\"\n */\nexport function parseSignatureHeader(header: string): ParsedSignature | null {\n const params: Record<string, string> = {};\n\n // Parse key=\"value\" pairs\n const regex = /(\\w+)=\"([^\"]+)\"/g;\n let match;\n while ((match = regex.exec(header)) !== null) {\n params[match[1]!] = match[2]!;\n }\n\n if (!params.keyId || !params.signature) {\n return null;\n }\n\n return {\n keyId: params.keyId,\n algorithm: params.algorithm || 'rsa-sha256',\n headers: (params.headers || '(request-target) host date').split(' '),\n signature: params.signature,\n };\n}\n\n/**\n * Successful signing string result\n */\nexport interface SigningStringSuccess {\n success: true;\n signingString: string;\n}\n\n/**\n * Failed signing string result (missing headers)\n */\nexport interface SigningStringFailure {\n success: false;\n error: string;\n missingHeaders: string[];\n}\n\n/**\n * Result of building a signing string\n */\nexport type SigningStringResult = SigningStringSuccess | SigningStringFailure;\n\n/**\n * Build the signing string from headers\n *\n * @security If a header is listed in headerNames but missing from the request,\n * this function returns an error. This prevents downgrade attacks where an\n * attacker claims to sign headers that aren't actually present.\n *\n * Synthetic headers ((request-target), (created), (expires)) are always allowed.\n */\nexport function buildSigningString(\n method: string,\n path: string,\n headers: Headers,\n headerNames: string[]\n): string {\n const result = buildSigningStringStrict(method, path, headers, headerNames);\n if (!result.success) {\n // For backwards compatibility, log warning but return partial string\n // Callers using verifyHttpSignature will get signature mismatch\n console.warn(`[federation] Signature verification may fail: ${result.error}`);\n // Build partial string for backwards compat\n const lines: string[] = [];\n for (const name of headerNames) {\n if (name === '(request-target)') {\n lines.push(`(request-target): ${method.toLowerCase()} ${path}`);\n } else if (name === '(created)' || name === '(expires)') {\n // Synthetic headers - skip if not implemented\n } else {\n const value = headers.get(name);\n if (value !== null) {\n lines.push(`${name.toLowerCase()}: ${value}`);\n }\n }\n }\n return lines.join('\\n');\n }\n return result.signingString;\n}\n\n/**\n * Build signing string with strict header validation\n *\n * Returns an error if any non-synthetic header in headerNames is missing.\n * Use this for new code that wants strict validation.\n */\nexport function buildSigningStringStrict(\n method: string,\n path: string,\n headers: Headers,\n headerNames: string[]\n): SigningStringResult {\n const lines: string[] = [];\n const missingHeaders: string[] = [];\n\n // Synthetic headers that don't need to be present in the request\n const syntheticHeaders = new Set(['(request-target)', '(created)', '(expires)']);\n\n for (const name of headerNames) {\n if (name === '(request-target)') {\n lines.push(`(request-target): ${method.toLowerCase()} ${path}`);\n } else if (name === '(created)') {\n // Skip for now - optional synthetic header\n } else if (name === '(expires)') {\n // Skip for now - optional synthetic header\n } else {\n const value = headers.get(name);\n if (value !== null) {\n lines.push(`${name.toLowerCase()}: ${value}`);\n } else if (!syntheticHeaders.has(name)) {\n // Header is listed as signed but not present - this is an error\n missingHeaders.push(name);\n }\n }\n }\n\n if (missingHeaders.length > 0) {\n return {\n success: false,\n error: `Signature claims to sign headers that are missing from request: ${missingHeaders.join(', ')}`,\n missingHeaders,\n };\n }\n\n return {\n success: true,\n signingString: lines.join('\\n'),\n };\n}\n\n/**\n * Options for HTTP signature verification\n */\nexport interface VerifyHttpSignatureOptions {\n /**\n * If true, verification fails when any claimed signed header is missing.\n * This prevents downgrade attacks where signatures claim to cover headers\n * that aren't actually present in the request.\n *\n * @security Enable this for production deployments to ensure signatures\n * actually cover all claimed headers.\n */\n strictHeaders?: boolean;\n}\n\n/**\n * Verify an HTTP signature using Web Crypto API\n *\n * @param options.strictHeaders - If true, fails when claimed headers are missing\n */\nexport async function verifyHttpSignature(\n parsed: ParsedSignature,\n publicKeyPem: string,\n method: string,\n path: string,\n headers: Headers,\n options: VerifyHttpSignatureOptions = {}\n): Promise<boolean> {\n try {\n // In strict mode, fail if any claimed header is missing from the request\n if (options.strictHeaders) {\n const strictResult = buildSigningStringStrict(method, path, headers, parsed.headers);\n if (!strictResult.success) {\n console.warn(`[federation] Strict header verification failed: ${strictResult.error}`);\n return false;\n }\n }\n\n // Build the signing string\n const signingString = buildSigningString(method, path, headers, parsed.headers);\n\n // Import the public key\n const publicKey = await importPublicKey(publicKeyPem);\n if (!publicKey) {\n return false;\n }\n\n // Decode the signature from base64\n const signatureBytes = base64ToArrayBuffer(parsed.signature);\n\n // Verify the signature\n const encoder = new TextEncoder();\n const data = encoder.encode(signingString);\n\n const algorithm = parsed.algorithm === 'hs2019' ? 'RSASSA-PKCS1-v1_5' : 'RSASSA-PKCS1-v1_5';\n\n return await crypto.subtle.verify(\n { name: algorithm, hash: 'SHA-256' },\n publicKey,\n signatureBytes,\n data\n );\n } catch (error) {\n console.error('Signature verification failed:', error);\n return false;\n }\n}\n\n/**\n * Validate an incoming activity's HTTP signature\n *\n * @security Enable `strictMode` for production deployments to prevent:\n * - Replay attacks (requires Date header in signature)\n * - Cross-host request reuse (requires host header in signature)\n */\nexport async function validateActivitySignature(\n activity: FederatedActivity,\n headers: Headers,\n options: SignatureValidationOptions\n): Promise<SignatureValidationResult> {\n // Check for Signature header\n const signatureHeader = headers.get('Signature');\n if (!signatureHeader) {\n return { valid: false, error: 'Missing Signature header' };\n }\n\n // Parse the signature\n const parsed = parseSignatureHeader(signatureHeader);\n if (!parsed) {\n return { valid: false, error: 'Invalid Signature header format' };\n }\n\n // STRICT MODE: Enforce required headers in signature\n // This prevents replay attacks and cross-host request reuse\n if (options.strictMode) {\n const missingHeaders = REQUIRED_SIGNED_HEADERS.filter(\n (h) => !parsed.headers.includes(h)\n );\n if (missingHeaders.length > 0) {\n return {\n valid: false,\n error: `Strict mode: signature missing required headers: ${missingHeaders.join(', ')}`,\n };\n }\n\n // In strict mode, Date header MUST be present (not just signed)\n if (!headers.get('Date')) {\n return { valid: false, error: 'Strict mode: Date header required' };\n }\n\n // In strict mode, host header MUST be present\n if (!headers.get('host') && !headers.get('Host')) {\n return { valid: false, error: 'Strict mode: host header required' };\n }\n }\n\n // Verify Date header freshness (if present)\n const dateHeader = headers.get('Date');\n if (dateHeader) {\n const requestDate = new Date(dateHeader);\n const maxAge = options.maxAge ?? 300; // 5 minutes\n const now = Date.now();\n const requestTime = requestDate.getTime();\n\n if (isNaN(requestTime)) {\n return { valid: false, error: 'Invalid Date header' };\n }\n\n if (Math.abs(now - requestTime) > maxAge * 1000) {\n return { valid: false, error: 'Request too old or in future' };\n }\n }\n\n // Verify the keyId matches the activity actor using strict URL equality\n // Key ID format: \"https://example.com/actors/alice#main-key\"\n // Actor format: \"https://example.com/actors/alice\"\n // @security Strict equality prevents URL confusion attacks where:\n // - actor: https://evil.com/victim\n // - keyId: https://evil.com/victim-fake#main-key (would match with startsWith)\n let keyIdBase: string;\n let actorBase: string;\n try {\n const keyIdUrl = new URL(parsed.keyId);\n const actorUrl = new URL(activity.actor);\n // Extract base (origin + pathname) - key ID has fragment, actor doesn't\n keyIdBase = `${keyIdUrl.origin}${keyIdUrl.pathname}`;\n actorBase = `${actorUrl.origin}${actorUrl.pathname}`;\n } catch {\n return { valid: false, error: 'Invalid key ID or actor URL' };\n }\n\n if (keyIdBase !== actorBase) {\n return { valid: false, error: 'Key ID does not match activity actor' };\n }\n\n // Extract actor ID from keyId for fetching\n const actorId = keyIdBase;\n\n // Fetch the actor to get their public key\n const actor = await options.fetchActor(actorId);\n if (!actor) {\n return { valid: false, error: 'Could not fetch actor' };\n }\n\n if (!actor.publicKey?.publicKeyPem) {\n return { valid: false, error: 'Actor has no public key' };\n }\n\n // Verify the key ID matches\n if (actor.publicKey.id !== parsed.keyId) {\n return { valid: false, error: 'Key ID mismatch' };\n }\n\n // Verify the signature\n // In strict mode, also enforce that claimed headers are actually present\n const valid = await verifyHttpSignature(\n parsed,\n actor.publicKey.publicKeyPem,\n options.method,\n options.path,\n headers,\n { strictHeaders: options.strictMode }\n );\n\n if (!valid) {\n return { valid: false, error: 'Invalid signature' };\n }\n\n return { valid: true, actor, keyId: parsed.keyId };\n}\n\n/**\n * Sign an outgoing HTTP request\n *\n * @param options.host - Required. The target host for the request.\n * @throws If host is not provided (required for valid signatures)\n */\nexport async function signRequest(\n options: SigningOptions\n): Promise<{ signature: string; date: string; host: string }> {\n // Host is required for valid signatures\n if (!options.host) {\n throw new Error('signRequest requires options.host - cannot generate valid signature without it');\n }\n\n const now = new Date();\n const dateString = now.toUTCString();\n\n // Build headers for signing\n const headersToSign = ['(request-target)', 'host', 'date'];\n const headerValues = new Headers();\n headerValues.set('date', dateString);\n headerValues.set('host', options.host);\n\n if (options.digest) {\n headersToSign.push('digest');\n headerValues.set('digest', options.digest);\n }\n\n if (options.contentType) {\n headersToSign.push('content-type');\n headerValues.set('content-type', options.contentType);\n }\n\n // Build signing string\n const signingString = buildSigningString(\n options.method,\n options.path,\n headerValues,\n headersToSign\n );\n\n // Import private key and sign\n const privateKey = await importPrivateKey(options.privateKeyPem);\n if (!privateKey) {\n throw new Error('Failed to import private key');\n }\n\n const encoder = new TextEncoder();\n const data = encoder.encode(signingString);\n\n const signatureBuffer = await crypto.subtle.sign(\n { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },\n privateKey,\n data\n );\n\n const signatureBase64 = arrayBufferToBase64(signatureBuffer);\n\n // Build Signature header\n const signatureHeader =\n `keyId=\"${options.keyId}\",` +\n `algorithm=\"rsa-sha256\",` +\n `headers=\"${headersToSign.join(' ')}\",` +\n `signature=\"${signatureBase64}\"`;\n\n return { signature: signatureHeader, date: dateString, host: options.host };\n}\n\n/**\n * Calculate SHA-256 digest for request body\n */\nexport async function calculateDigest(body: string | ArrayBuffer): Promise<string> {\n const encoder = new TextEncoder();\n const data = typeof body === 'string' ? encoder.encode(body) : body;\n\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n const hashBase64 = arrayBufferToBase64(hashBuffer);\n\n return `SHA-256=${hashBase64}`;\n}\n\n// ============ Crypto Utilities ============\n\n/**\n * Import a PEM-encoded public key\n */\nasync function importPublicKey(pem: string): Promise<CryptoKey | null> {\n try {\n // Remove PEM headers and convert to binary\n const pemContents = pem\n .replace(/-----BEGIN PUBLIC KEY-----/, '')\n .replace(/-----END PUBLIC KEY-----/, '')\n .replace(/-----BEGIN RSA PUBLIC KEY-----/, '')\n .replace(/-----END RSA PUBLIC KEY-----/, '')\n .replace(/\\s/g, '');\n\n const binaryDer = base64ToArrayBuffer(pemContents);\n\n return await crypto.subtle.importKey(\n 'spki',\n binaryDer,\n { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },\n false,\n ['verify']\n );\n } catch (error) {\n console.error('Failed to import public key:', error);\n return null;\n }\n}\n\n/**\n * Import a PEM-encoded private key\n */\nasync function importPrivateKey(pem: string): Promise<CryptoKey | null> {\n try {\n // Remove PEM headers and convert to binary\n const pemContents = pem\n .replace(/-----BEGIN PRIVATE KEY-----/, '')\n .replace(/-----END PRIVATE KEY-----/, '')\n .replace(/-----BEGIN RSA PRIVATE KEY-----/, '')\n .replace(/-----END RSA PRIVATE KEY-----/, '')\n .replace(/\\s/g, '');\n\n const binaryDer = base64ToArrayBuffer(pemContents);\n\n return await crypto.subtle.importKey(\n 'pkcs8',\n binaryDer,\n { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },\n false,\n ['sign']\n );\n } catch (error) {\n console.error('Failed to import private key:', error);\n return null;\n }\n}\n\n/**\n * Convert base64 string to ArrayBuffer\n */\nfunction base64ToArrayBuffer(base64: string): ArrayBuffer {\n // Handle URL-safe base64\n const normalized = base64.replace(/-/g, '+').replace(/_/g, '/');\n const binary = atob(normalized);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes.buffer;\n}\n\n/**\n * Convert ArrayBuffer to base64 string\n */\nfunction arrayBufferToBase64(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = '';\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]!);\n }\n return btoa(binary);\n}\n","/**\n * Inbox Handler\n *\n * Process incoming ActivityPub activities.\n */\n\nimport type {\n FederatedActivity,\n InboxResult,\n InboxHandlerOptions,\n ForkActivity,\n InstallActivity,\n ActivityType,\n} from '../types.js';\nimport type {\n ModerationStore,\n FlagActivity,\n BlockActivity,\n} from '../moderation/types.js';\nimport { parseActivity, parseForkActivity, parseInstallActivity } from '../activitypub.js';\nimport { parseFlagActivity, parseBlockActivity } from '../moderation/activities.js';\nimport { assertFederationEnabled } from '../index.js';\nimport { parseSignatureHeader, verifyHttpSignature, calculateDigest, REQUIRED_SIGNED_HEADERS } from '../http-signatures.js';\n\n/**\n * Extract host from an actor ID URL\n */\nfunction extractHostFromActorId(actorId: string): string | null {\n try {\n const url = new URL(actorId);\n return url.host;\n } catch {\n return null;\n }\n}\n\n/**\n * Handle incoming ActivityPub activities\n *\n * Parses and routes incoming activities to the appropriate handlers.\n * Currently supports Fork activities for fork notification handling.\n *\n * @example\n * ```typescript\n * // In your web framework (e.g., Hono, Express)\n * app.post('/inbox', async (req) => {\n * // IMPORTANT: Capture raw body BEFORE parsing for Digest verification\n * const rawBody = await req.text();\n * const body = JSON.parse(rawBody);\n *\n * const result = await handleInbox(body, req.headers, {\n * rawBody, // Required for body integrity verification\n * method: 'POST',\n * path: '/inbox', // Must match your actual route path\n * strictMode: true,\n * fetchActor: async (id) => fetchActorFromNetwork(id),\n * onFork: async (activity) => {\n * await syncEngine.handleForkNotification(activity);\n * },\n * });\n *\n * if (result.accepted) {\n * return new Response(null, { status: 202 });\n * } else {\n * return new Response(result.error, { status: 400 });\n * }\n * });\n * ```\n */\nexport async function handleInbox(\n body: unknown,\n headers: Headers | Record<string, string>,\n options: InboxHandlerOptions & {\n /** HTTP method (required for signature verification in strictMode) */\n method?: string;\n /** Request path (required for signature verification in strictMode) */\n path?: string;\n /**\n * Raw request body for Digest header verification.\n *\n * @security REQUIRED in strictMode when Digest header is present.\n * Without this, an attacker could modify the JSON body while keeping a valid signature\n * (since signatures only cover headers, not the body).\n *\n * Pass the raw bytes before JSON parsing:\n * ```typescript\n * const rawBody = await req.text();\n * const body = JSON.parse(rawBody);\n * handleInbox(body, headers, { rawBody, ... });\n * ```\n */\n rawBody?: string | ArrayBuffer;\n onFork?: (activity: ForkActivity) => Promise<void>;\n onInstall?: (activity: InstallActivity) => Promise<void>;\n onCreate?: (activity: FederatedActivity) => Promise<void>;\n onUpdate?: (activity: FederatedActivity) => Promise<void>;\n onDelete?: (activity: FederatedActivity) => Promise<void>;\n onLike?: (activity: FederatedActivity) => Promise<void>;\n onAnnounce?: (activity: FederatedActivity) => Promise<void>;\n onUndo?: (activity: FederatedActivity) => Promise<void>;\n // Moderation handlers\n onFlag?: (activity: FlagActivity, parsedReport: ReturnType<typeof parseFlagActivity>) => Promise<void>;\n onBlock?: (activity: BlockActivity, parsedBlock: ReturnType<typeof parseBlockActivity>) => Promise<void>;\n /**\n * Moderation store for instance blocking.\n * If provided, blocked instances are rejected BEFORE signature verification (saves CPU).\n */\n moderationStore?: ModerationStore;\n }\n): Promise<InboxResult> {\n assertFederationEnabled('handleInbox');\n\n try {\n // Early instance block check - BEFORE signature verification (saves CPU for blocked instances)\n // We extract actor from the raw body to avoid full parsing costs\n if (options.moderationStore) {\n const actorId = typeof body === 'object' && body !== null && 'actor' in body\n ? String((body as Record<string, unknown>).actor)\n : null;\n\n if (actorId) {\n const host = extractHostFromActorId(actorId);\n if (host && await options.moderationStore.isInstanceBlocked(host)) {\n return {\n accepted: false,\n error: `Instance ${host} is blocked`,\n };\n }\n }\n }\n\n // Parse the activity\n let activity: FederatedActivity;\n try {\n activity = parseActivity(body);\n } catch (err) {\n return {\n accepted: false,\n error: `Invalid activity: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n\n // Verify HTTP signature in strict mode\n if (options.strictMode) {\n // Get signature header\n const signatureHeader = headers instanceof Headers\n ? headers.get('signature')\n : headers['signature'] || headers['Signature'];\n\n if (!signatureHeader) {\n return {\n accepted: false,\n error: 'Missing Signature header (required in strict mode)',\n };\n }\n\n // Parse the signature\n const parsedSig = parseSignatureHeader(signatureHeader);\n if (!parsedSig) {\n return {\n accepted: false,\n error: 'Invalid Signature header format',\n };\n }\n\n // STRICT MODE: Enforce required headers in signature\n // This matches validateActivitySignature behavior for consistency\n const missingSignedHeaders = REQUIRED_SIGNED_HEADERS.filter(\n (h) => !parsedSig.headers.includes(h)\n );\n if (missingSignedHeaders.length > 0) {\n return {\n accepted: false,\n error: `Strict mode: signature missing required headers: ${missingSignedHeaders.join(', ')}`,\n };\n }\n\n // STRICT MODE: Require Date header to be present (not just signed)\n const dateHeader = headers instanceof Headers\n ? headers.get('date')\n : headers['date'] || headers['Date'];\n if (!dateHeader) {\n return {\n accepted: false,\n error: 'Strict mode: Date header required',\n };\n }\n\n // STRICT MODE: Require host header to be present\n const hostHeader = headers instanceof Headers\n ? headers.get('host')\n : headers['host'] || headers['Host'];\n if (!hostHeader) {\n return {\n accepted: false,\n error: 'Strict mode: host header required',\n };\n }\n\n // Fetch the actor to get their public key\n const actor = await options.fetchActor(activity.actor);\n if (!actor) {\n return {\n accepted: false,\n error: `Unknown actor: ${activity.actor}`,\n };\n }\n\n // Verify the key belongs to the actor (strict equality on URL origin + path)\n if (!actor.publicKey?.publicKeyPem) {\n return {\n accepted: false,\n error: `Actor ${activity.actor} has no public key`,\n };\n }\n\n // Verify key ID matches actor (strict equality on base URL)\n // Key ID format: \"https://example.com/actors/alice#main-key\"\n // Actor format: \"https://example.com/actors/alice\"\n try {\n const keyIdUrl = new URL(parsedSig.keyId);\n const actorUrl = new URL(activity.actor);\n\n // Extract base (origin + pathname) - key ID has fragment, actor doesn't\n const keyIdBase = `${keyIdUrl.origin}${keyIdUrl.pathname}`;\n const actorBase = `${actorUrl.origin}${actorUrl.pathname}`;\n\n // Strict equality required - no startsWith which could allow:\n // - actor: https://evil.com/victim\n // - keyId: https://evil.com/victim-fake#main-key (would match with startsWith)\n if (keyIdBase !== actorBase) {\n return {\n accepted: false,\n error: `Key ID ${parsedSig.keyId} does not match actor ${activity.actor}`,\n };\n }\n\n // Also verify the fetched actor's key ID matches the signature's key ID\n if (actor.publicKey.id !== parsedSig.keyId) {\n return {\n accepted: false,\n error: `Actor's key ID ${actor.publicKey.id} does not match signature key ID ${parsedSig.keyId}`,\n };\n }\n } catch {\n return {\n accepted: false,\n error: `Invalid key ID or actor URL`,\n };\n }\n\n // Verify Digest header BEFORE signature verification\n // @security This prevents body tampering even if signature is valid (signatures only cover headers)\n // Order: Digest (body integrity, cheap hash) → Signature (header integrity, crypto)\n const digestHeader = headers instanceof Headers\n ? headers.get('digest')\n : headers['digest'] || headers['Digest'];\n\n if (digestHeader) {\n // Digest header present - MUST verify\n if (!options.rawBody) {\n return {\n accepted: false,\n error: 'Digest header present but rawBody not provided for verification',\n };\n }\n\n // Parse digest format: \"SHA-256=base64hash\" or \"sha-256=base64hash\"\n const digestMatch = digestHeader.match(/^(SHA-256|sha-256)=(.+)$/i);\n if (!digestMatch) {\n return {\n accepted: false,\n error: 'Invalid Digest header format (expected SHA-256=...)',\n };\n }\n\n // Calculate actual digest\n const expectedDigest = await calculateDigest(options.rawBody);\n const actualDigest = `SHA-256=${digestMatch[2]}`;\n\n // Compare (case-insensitive for algorithm, exact for hash)\n if (expectedDigest !== actualDigest) {\n return {\n accepted: false,\n error: 'Digest mismatch - body may have been tampered with',\n };\n }\n } else if (parsedSig.headers.includes('digest')) {\n // Signature claims to have signed a digest, but no Digest header present\n return {\n accepted: false,\n error: 'Signature includes digest but no Digest header present',\n };\n }\n\n // Verify the signature (covers headers including Digest)\n const method = options.method || 'POST';\n const path = options.path || '/inbox';\n\n // Normalize headers to Headers object for signature verification\n const normalizedHeaders = headers instanceof Headers\n ? headers\n : new Headers(headers);\n\n const isValid = await verifyHttpSignature(\n parsedSig,\n actor.publicKey.publicKeyPem,\n method,\n path,\n normalizedHeaders\n );\n\n if (!isValid) {\n return {\n accepted: false,\n error: 'Invalid HTTP signature',\n };\n }\n\n // Check date header for replay protection\n // In strict mode, Date header is required (checked above), so always verify freshness\n // Default maxAge: 300 seconds (5 minutes) matches validateActivitySignature behavior\n const effectiveMaxAge = options.maxAge ?? 300;\n const requestDate = new Date(dateHeader);\n const now = new Date();\n const ageMs = Math.abs(now.getTime() - requestDate.getTime());\n\n if (isNaN(requestDate.getTime())) {\n return {\n accepted: false,\n error: 'Invalid Date header format',\n };\n }\n\n if (ageMs > effectiveMaxAge * 1000) {\n return {\n accepted: false,\n error: `Request too old or in future (${Math.round(ageMs / 1000)}s, max ${effectiveMaxAge}s)`,\n };\n }\n }\n\n // Route to appropriate handler\n const activityType = activity.type as ActivityType;\n\n switch (activityType) {\n case 'Fork': {\n const parsed = parseForkActivity(activity);\n if (!parsed) {\n return {\n accepted: false,\n error: 'Invalid Fork activity: missing or malformed required fields',\n };\n }\n if (options.onFork) {\n await options.onFork(activity as ForkActivity);\n }\n return { accepted: true, activityType: 'Fork' };\n }\n\n case 'Install': {\n const parsed = parseInstallActivity(activity);\n if (!parsed) {\n return {\n accepted: false,\n error: 'Invalid Install activity: missing or malformed required fields',\n };\n }\n if (options.onInstall) {\n await options.onInstall(activity as InstallActivity);\n }\n return { accepted: true, activityType: 'Install' };\n }\n\n case 'Create': {\n if (options.onCreate) {\n await options.onCreate(activity);\n }\n return { accepted: true, activityType: 'Create' };\n }\n\n case 'Update': {\n if (options.onUpdate) {\n await options.onUpdate(activity);\n }\n return { accepted: true, activityType: 'Update' };\n }\n\n case 'Delete': {\n if (options.onDelete) {\n await options.onDelete(activity);\n }\n return { accepted: true, activityType: 'Delete' };\n }\n\n case 'Like': {\n if (options.onLike) {\n await options.onLike(activity);\n }\n return { accepted: true, activityType: 'Like' };\n }\n\n case 'Announce': {\n if (options.onAnnounce) {\n await options.onAnnounce(activity);\n }\n return { accepted: true, activityType: 'Announce' };\n }\n\n case 'Undo': {\n if (options.onUndo) {\n await options.onUndo(activity);\n }\n return { accepted: true, activityType: 'Undo' };\n }\n\n case 'Flag': {\n const parsed = parseFlagActivity(activity);\n if (options.onFlag && parsed) {\n await options.onFlag(activity as unknown as FlagActivity, parsed);\n }\n return { accepted: true, activityType: 'Flag' };\n }\n\n case 'Block': {\n const parsed = parseBlockActivity(activity);\n if (options.onBlock && parsed) {\n await options.onBlock(activity as unknown as BlockActivity, parsed);\n }\n return { accepted: true, activityType: 'Block' };\n }\n\n default: {\n // Unknown activity type - accept but ignore\n return {\n accepted: true,\n activityType: activityType,\n };\n }\n }\n } catch (err) {\n return {\n accepted: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\n/**\n * Validate that a Fork activity has all required fields\n */\nexport function validateForkActivity(activity: unknown): {\n valid: boolean;\n error?: string;\n} {\n const parsed = parseForkActivity(activity);\n if (!parsed) {\n return {\n valid: false,\n error: 'Invalid fork activity: missing required fields',\n };\n }\n\n // Verify the forked card has content\n try {\n JSON.parse(parsed.forkedCard.content);\n } catch {\n return {\n valid: false,\n error: 'Invalid fork activity: forked card content is not valid JSON',\n };\n }\n\n return { valid: true };\n}\n\n/**\n * Validate that an Install activity has all required fields\n */\nexport function validateInstallActivity(activity: unknown): {\n valid: boolean;\n error?: string;\n} {\n const parsed = parseInstallActivity(activity);\n if (!parsed) {\n return {\n valid: false,\n error: 'Invalid install activity: missing required fields',\n };\n }\n\n // Verify the card ID is a valid URI\n try {\n new URL(parsed.cardId);\n } catch {\n return {\n valid: false,\n error: 'Invalid install activity: cardId is not a valid URI',\n };\n }\n\n return { valid: true };\n}\n","/**\n * In-Memory Moderation Store\n *\n * Memory implementation for testing and single-session use.\n * Follows the same pattern as MemorySyncStateStore.\n */\n\nimport { generateUUID } from '@character-foundry/core';\nimport type {\n ModerationStore,\n ModerationReport,\n ModerationAction,\n InstanceBlock,\n ContentPolicy,\n RateLimitBucket,\n ReportStatus,\n ReportCategory,\n ActionType,\n} from './types.js';\n\n/**\n * In-memory implementation of ModerationStore\n *\n * Suitable for testing, development, and single-session use cases.\n * Data is lost when the process ends.\n */\nexport class MemoryModerationStore implements ModerationStore {\n private reports: Map<string, ModerationReport> = new Map();\n private actions: Map<string, ModerationAction> = new Map();\n private blocks: Map<string, InstanceBlock> = new Map();\n private policies: Map<string, ContentPolicy> = new Map();\n private rateLimits: Map<string, RateLimitBucket> = new Map();\n\n // ============ Report Operations ============\n\n async createReport(report: Omit<ModerationReport, 'id'>): Promise<ModerationReport> {\n const id = generateUUID();\n const full: ModerationReport = { ...report, id };\n this.reports.set(id, full);\n return full;\n }\n\n async getReport(id: string): Promise<ModerationReport | null> {\n return this.reports.get(id) || null;\n }\n\n async updateReport(id: string, updates: Partial<ModerationReport>): Promise<void> {\n const existing = this.reports.get(id);\n if (existing) {\n this.reports.set(id, {\n ...existing,\n ...updates,\n updatedAt: new Date().toISOString(),\n });\n }\n }\n\n async listReports(filters?: {\n status?: ReportStatus;\n category?: ReportCategory;\n targetId?: string;\n reporterActorId?: string;\n since?: string;\n limit?: number;\n offset?: number;\n }): Promise<ModerationReport[]> {\n let results = Array.from(this.reports.values());\n\n if (filters?.status) {\n results = results.filter((r) => r.status === filters.status);\n }\n if (filters?.category) {\n results = results.filter((r) => r.category === filters.category);\n }\n if (filters?.targetId) {\n results = results.filter((r) => r.targetIds.includes(filters.targetId!));\n }\n if (filters?.reporterActorId) {\n results = results.filter((r) => r.reporterActorId === filters.reporterActorId);\n }\n if (filters?.since) {\n results = results.filter((r) => r.createdAt >= filters.since!);\n }\n\n // Sort by creation date descending (newest first)\n results.sort((a, b) => b.createdAt.localeCompare(a.createdAt));\n\n const offset = filters?.offset || 0;\n const limit = filters?.limit || 100;\n return results.slice(offset, offset + limit);\n }\n\n async countReports(filters?: { status?: ReportStatus }): Promise<number> {\n let count = 0;\n for (const report of this.reports.values()) {\n if (!filters?.status || report.status === filters.status) {\n count++;\n }\n }\n return count;\n }\n\n // ============ Action Operations ============\n\n async createAction(action: Omit<ModerationAction, 'id'>): Promise<ModerationAction> {\n const id = generateUUID();\n const full: ModerationAction = { ...action, id };\n this.actions.set(id, full);\n return full;\n }\n\n async getAction(id: string): Promise<ModerationAction | null> {\n return this.actions.get(id) || null;\n }\n\n async listActions(filters?: {\n targetId?: string;\n moderatorActorId?: string;\n actionType?: ActionType;\n active?: boolean;\n since?: string;\n limit?: number;\n }): Promise<ModerationAction[]> {\n let results = Array.from(this.actions.values());\n\n if (filters?.targetId) {\n results = results.filter((a) => a.targetId === filters.targetId);\n }\n if (filters?.moderatorActorId) {\n results = results.filter((a) => a.moderatorActorId === filters.moderatorActorId);\n }\n if (filters?.actionType) {\n results = results.filter((a) => a.actionType === filters.actionType);\n }\n if (filters?.active !== undefined) {\n results = results.filter((a) => a.active === filters.active);\n }\n if (filters?.since) {\n results = results.filter((a) => a.timestamp >= filters.since!);\n }\n\n // Sort by timestamp descending (newest first)\n results.sort((a, b) => b.timestamp.localeCompare(a.timestamp));\n return results.slice(0, filters?.limit || 100);\n }\n\n async deactivateAction(id: string): Promise<void> {\n const action = this.actions.get(id);\n if (action) {\n this.actions.set(id, { ...action, active: false });\n }\n }\n\n // ============ Instance Block Operations ============\n\n async createBlock(block: Omit<InstanceBlock, 'id'>): Promise<InstanceBlock> {\n const id = generateUUID();\n const full: InstanceBlock = { ...block, id };\n this.blocks.set(id, full);\n return full;\n }\n\n async getBlock(id: string): Promise<InstanceBlock | null> {\n return this.blocks.get(id) || null;\n }\n\n async getBlockByDomain(domain: string): Promise<InstanceBlock | null> {\n const normalized = domain.toLowerCase();\n for (const block of this.blocks.values()) {\n if (block.blockedDomain.toLowerCase() === normalized && block.active) {\n return block;\n }\n }\n return null;\n }\n\n async listBlocks(filters?: { active?: boolean }): Promise<InstanceBlock[]> {\n let results = Array.from(this.blocks.values());\n if (filters?.active !== undefined) {\n results = results.filter((b) => b.active === filters.active);\n }\n // Sort by creation date descending\n results.sort((a, b) => b.createdAt.localeCompare(a.createdAt));\n return results;\n }\n\n async updateBlock(id: string, updates: Partial<InstanceBlock>): Promise<void> {\n const existing = this.blocks.get(id);\n if (existing) {\n this.blocks.set(id, { ...existing, ...updates });\n }\n }\n\n async isInstanceBlocked(domain: string): Promise<boolean> {\n const block = await this.getBlockByDomain(domain);\n return block !== null;\n }\n\n // ============ Content Policy Operations ============\n\n async createPolicy(policy: Omit<ContentPolicy, 'id'>): Promise<ContentPolicy> {\n const id = generateUUID();\n const full: ContentPolicy = { ...policy, id };\n this.policies.set(id, full);\n return full;\n }\n\n async getPolicy(id: string): Promise<ContentPolicy | null> {\n return this.policies.get(id) || null;\n }\n\n async listPolicies(filters?: { enabled?: boolean }): Promise<ContentPolicy[]> {\n let results = Array.from(this.policies.values());\n if (filters?.enabled !== undefined) {\n results = results.filter((p) => p.enabled === filters.enabled);\n }\n return results;\n }\n\n async updatePolicy(id: string, updates: Partial<ContentPolicy>): Promise<void> {\n const existing = this.policies.get(id);\n if (existing) {\n this.policies.set(id, {\n ...existing,\n ...updates,\n updatedAt: new Date().toISOString(),\n });\n }\n }\n\n async deletePolicy(id: string): Promise<void> {\n this.policies.delete(id);\n }\n\n // ============ Rate Limit Operations ============\n\n async getRateLimitBucket(actorId: string): Promise<RateLimitBucket | null> {\n return this.rateLimits.get(actorId) || null;\n }\n\n async updateRateLimitBucket(bucket: RateLimitBucket): Promise<void> {\n this.rateLimits.set(bucket.actorId, bucket);\n }\n\n // ============ Audit ============\n\n async getAuditLog(filters?: {\n targetId?: string;\n actorId?: string;\n since?: string;\n until?: string;\n limit?: number;\n }): Promise<ModerationAction[]> {\n let results = await this.listActions({\n targetId: filters?.targetId,\n moderatorActorId: filters?.actorId,\n since: filters?.since,\n limit: filters?.limit,\n });\n\n if (filters?.until) {\n results = results.filter((a) => a.timestamp <= filters.until!);\n }\n\n return results;\n }\n\n // ============ Utility Methods ============\n\n /**\n * Clear all data (useful for testing)\n */\n clear(): void {\n this.reports.clear();\n this.actions.clear();\n this.blocks.clear();\n this.policies.clear();\n this.rateLimits.clear();\n }\n\n /**\n * Get counts for dashboard/stats\n */\n async getStats(): Promise<{\n reports: { total: number; pending: number };\n actions: { total: number; active: number };\n blocks: { total: number; active: number };\n policies: { total: number; enabled: number };\n }> {\n const pendingReports = await this.countReports({ status: 'pending' });\n const activeActions = (await this.listActions({ active: true })).length;\n const activeBlocks = (await this.listBlocks({ active: true })).length;\n const enabledPolicies = (await this.listPolicies({ enabled: true })).length;\n\n return {\n reports: {\n total: this.reports.size,\n pending: pendingReports,\n },\n actions: {\n total: this.actions.size,\n active: activeActions,\n },\n blocks: {\n total: this.blocks.size,\n active: activeBlocks,\n },\n policies: {\n total: this.policies.size,\n enabled: enabledPolicies,\n },\n };\n }\n}\n","/**\n * D1 Moderation Store\n *\n * Cloudflare D1-compatible implementation of ModerationStore for production\n * moderation support on Cloudflare Workers.\n */\n\nimport type { D1Database } from '../d1-store.js';\nimport type {\n ModerationStore,\n ModerationReport,\n ModerationAction,\n InstanceBlock,\n ContentPolicy,\n RateLimitBucket,\n ReportStatus,\n ReportCategory,\n ActionType,\n InstanceBlockLevel,\n PolicyAction,\n} from './types.js';\n\n/**\n * Database row for ModerationReport\n */\ninterface ReportRow {\n id: string;\n reporter_actor_id: string;\n reporter_instance: string;\n target_ids: string; // JSON array\n category: string;\n description: string;\n status: string;\n activity_id: string;\n created_at: string;\n updated_at: string;\n receiving_instance: string;\n federated_to_target: number; // SQLite boolean\n metadata: string | null;\n}\n\n/**\n * Database row for ModerationAction\n */\ninterface ActionRow {\n id: string;\n report_id: string | null;\n moderator_actor_id: string;\n target_id: string;\n action_type: string;\n reason: string;\n timestamp: string;\n expires_at: string | null;\n active: number; // SQLite boolean\n reverses_action_id: string | null;\n approved_by: string | null; // JSON array\n metadata: string | null;\n}\n\n/**\n * Database row for InstanceBlock\n */\ninterface BlockRow {\n id: string;\n blocked_domain: string;\n level: string;\n reason: string;\n created_by: string;\n created_at: string;\n active: number;\n public_comment: string | null;\n federate: number;\n}\n\n/**\n * Database row for ContentPolicy\n */\ninterface PolicyRow {\n id: string;\n name: string;\n description: string;\n rules: string; // JSON array\n enabled: number;\n default_action: string;\n updated_at: string;\n}\n\n/**\n * Database row for RateLimitBucket\n */\ninterface RateLimitRow {\n actor_id: string;\n tokens: number;\n max_tokens: number;\n last_refill: string;\n refill_rate: number;\n}\n\n/**\n * Validate table name prefix to prevent SQL injection\n */\nfunction validateTablePrefix(prefix: string): void {\n const validPattern = /^[a-zA-Z][a-zA-Z0-9_]*$/;\n if (!validPattern.test(prefix)) {\n throw new Error(\n `Invalid table prefix \"${prefix}\": must start with a letter and contain only alphanumeric characters and underscores`\n );\n }\n if (prefix.length > 32) {\n throw new Error(`Invalid table prefix \"${prefix}\": must be 32 characters or less`);\n }\n}\n\n/**\n * D1-compatible implementation of ModerationStore\n *\n * @example\n * ```typescript\n * const store = new D1ModerationStore(env.DB);\n * await store.init();\n *\n * // Create a report\n * const report = await store.createReport({\n * reporterActorId: 'https://example.com/users/alice',\n * reporterInstance: 'example.com',\n * targetIds: ['https://bad.com/cards/spam'],\n * category: 'spam',\n * description: 'This card is spam',\n * status: 'pending',\n * activityId: 'https://example.com/activities/123',\n * createdAt: new Date().toISOString(),\n * updatedAt: new Date().toISOString(),\n * receivingInstance: 'bad.com',\n * federatedToTarget: true,\n * });\n * ```\n */\nexport class D1ModerationStore implements ModerationStore {\n private db: D1Database;\n private prefix: string;\n\n constructor(db: D1Database, tablePrefix = 'moderation') {\n validateTablePrefix(tablePrefix);\n this.db = db;\n this.prefix = tablePrefix;\n }\n\n /**\n * Initialize all moderation tables\n */\n async init(): Promise<void> {\n // Reports table\n await this.db.exec(`\n CREATE TABLE IF NOT EXISTS ${this.prefix}_reports (\n id TEXT PRIMARY KEY,\n reporter_actor_id TEXT NOT NULL,\n reporter_instance TEXT NOT NULL,\n target_ids TEXT NOT NULL,\n category TEXT NOT NULL,\n description TEXT NOT NULL,\n status TEXT NOT NULL,\n activity_id TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n receiving_instance TEXT NOT NULL,\n federated_to_target INTEGER NOT NULL DEFAULT 0,\n metadata TEXT\n )\n `);\n\n await this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_${this.prefix}_reports_status\n ON ${this.prefix}_reports(status)\n `);\n\n await this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_${this.prefix}_reports_reporter\n ON ${this.prefix}_reports(reporter_actor_id)\n `);\n\n // Actions table\n await this.db.exec(`\n CREATE TABLE IF NOT EXISTS ${this.prefix}_actions (\n id TEXT PRIMARY KEY,\n report_id TEXT,\n moderator_actor_id TEXT NOT NULL,\n target_id TEXT NOT NULL,\n action_type TEXT NOT NULL,\n reason TEXT NOT NULL,\n timestamp TEXT NOT NULL,\n expires_at TEXT,\n active INTEGER NOT NULL DEFAULT 1,\n reverses_action_id TEXT,\n approved_by TEXT,\n metadata TEXT\n )\n `);\n\n await this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_${this.prefix}_actions_target\n ON ${this.prefix}_actions(target_id)\n `);\n\n await this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_${this.prefix}_actions_active\n ON ${this.prefix}_actions(active)\n `);\n\n // Instance blocks table\n await this.db.exec(`\n CREATE TABLE IF NOT EXISTS ${this.prefix}_blocks (\n id TEXT PRIMARY KEY,\n blocked_domain TEXT NOT NULL UNIQUE,\n level TEXT NOT NULL CHECK (level IN ('suspend', 'silence', 'reject_media')),\n reason TEXT NOT NULL,\n created_by TEXT NOT NULL,\n created_at TEXT NOT NULL,\n active INTEGER NOT NULL DEFAULT 1,\n public_comment TEXT,\n federate INTEGER NOT NULL DEFAULT 0\n )\n `);\n\n await this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_${this.prefix}_blocks_domain\n ON ${this.prefix}_blocks(blocked_domain)\n `);\n\n // Content policies table\n await this.db.exec(`\n CREATE TABLE IF NOT EXISTS ${this.prefix}_policies (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE,\n description TEXT NOT NULL,\n rules TEXT NOT NULL,\n enabled INTEGER NOT NULL DEFAULT 1,\n default_action TEXT NOT NULL,\n updated_at TEXT NOT NULL\n )\n `);\n\n // Rate limits table\n await this.db.exec(`\n CREATE TABLE IF NOT EXISTS ${this.prefix}_rate_limits (\n actor_id TEXT PRIMARY KEY,\n tokens REAL NOT NULL,\n max_tokens INTEGER NOT NULL,\n last_refill TEXT NOT NULL,\n refill_rate REAL NOT NULL\n )\n `);\n }\n\n // ============ Report Operations ============\n\n async createReport(report: Omit<ModerationReport, 'id'>): Promise<ModerationReport> {\n const id = crypto.randomUUID();\n const full: ModerationReport = { ...report, id };\n\n await this.db\n .prepare(\n `INSERT INTO ${this.prefix}_reports\n (id, reporter_actor_id, reporter_instance, target_ids, category, description, status,\n activity_id, created_at, updated_at, receiving_instance, federated_to_target, metadata)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`\n )\n .bind(\n id,\n report.reporterActorId,\n report.reporterInstance,\n JSON.stringify(report.targetIds),\n report.category,\n report.description,\n report.status,\n report.activityId,\n report.createdAt,\n report.updatedAt,\n report.receivingInstance,\n report.federatedToTarget ? 1 : 0,\n report.metadata ? JSON.stringify(report.metadata) : null\n )\n .run();\n\n return full;\n }\n\n async getReport(id: string): Promise<ModerationReport | null> {\n const row = await this.db\n .prepare(`SELECT * FROM ${this.prefix}_reports WHERE id = ?`)\n .bind(id)\n .first<ReportRow>();\n\n return row ? this.rowToReport(row) : null;\n }\n\n async updateReport(id: string, updates: Partial<ModerationReport>): Promise<void> {\n const existing = await this.getReport(id);\n if (!existing) return;\n\n const merged = { ...existing, ...updates, updatedAt: new Date().toISOString() };\n\n await this.db\n .prepare(\n `UPDATE ${this.prefix}_reports SET\n reporter_actor_id = ?, reporter_instance = ?, target_ids = ?, category = ?,\n description = ?, status = ?, activity_id = ?, updated_at = ?,\n receiving_instance = ?, federated_to_target = ?, metadata = ?\n WHERE id = ?`\n )\n .bind(\n merged.reporterActorId,\n merged.reporterInstance,\n JSON.stringify(merged.targetIds),\n merged.category,\n merged.description,\n merged.status,\n merged.activityId,\n merged.updatedAt,\n merged.receivingInstance,\n merged.federatedToTarget ? 1 : 0,\n merged.metadata ? JSON.stringify(merged.metadata) : null,\n id\n )\n .run();\n }\n\n async listReports(filters?: {\n status?: ReportStatus;\n category?: ReportCategory;\n targetId?: string;\n reporterActorId?: string;\n since?: string;\n limit?: number;\n offset?: number;\n }): Promise<ModerationReport[]> {\n const conditions: string[] = ['1=1'];\n const bindings: unknown[] = [];\n\n if (filters?.status) {\n conditions.push('status = ?');\n bindings.push(filters.status);\n }\n if (filters?.category) {\n conditions.push('category = ?');\n bindings.push(filters.category);\n }\n if (filters?.targetId) {\n conditions.push('target_ids LIKE ?');\n bindings.push(`%${filters.targetId}%`);\n }\n if (filters?.reporterActorId) {\n conditions.push('reporter_actor_id = ?');\n bindings.push(filters.reporterActorId);\n }\n if (filters?.since) {\n conditions.push('created_at >= ?');\n bindings.push(filters.since);\n }\n\n const limit = filters?.limit ?? 100;\n const offset = filters?.offset ?? 0;\n\n let stmt = this.db.prepare(\n `SELECT * FROM ${this.prefix}_reports\n WHERE ${conditions.join(' AND ')}\n ORDER BY created_at DESC\n LIMIT ? OFFSET ?`\n );\n\n for (const binding of bindings) {\n stmt = stmt.bind(binding);\n }\n stmt = stmt.bind(limit, offset);\n\n const result = await stmt.all<ReportRow>();\n return result.results.map((row) => this.rowToReport(row));\n }\n\n async countReports(filters?: { status?: ReportStatus }): Promise<number> {\n let query = `SELECT COUNT(*) as count FROM ${this.prefix}_reports`;\n const bindings: unknown[] = [];\n\n if (filters?.status) {\n query += ' WHERE status = ?';\n bindings.push(filters.status);\n }\n\n let stmt = this.db.prepare(query);\n for (const binding of bindings) {\n stmt = stmt.bind(binding);\n }\n\n const result = await stmt.first<{ count: number }>();\n return result?.count ?? 0;\n }\n\n // ============ Action Operations ============\n\n async createAction(action: Omit<ModerationAction, 'id'>): Promise<ModerationAction> {\n const id = crypto.randomUUID();\n const full: ModerationAction = { ...action, id };\n\n await this.db\n .prepare(\n `INSERT INTO ${this.prefix}_actions\n (id, report_id, moderator_actor_id, target_id, action_type, reason, timestamp,\n expires_at, active, reverses_action_id, approved_by, metadata)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`\n )\n .bind(\n id,\n action.reportId ?? null,\n action.moderatorActorId,\n action.targetId,\n action.actionType,\n action.reason,\n action.timestamp,\n action.expiresAt ?? null,\n action.active ? 1 : 0,\n action.reversesActionId ?? null,\n action.approvedBy ? JSON.stringify(action.approvedBy) : null,\n action.metadata ? JSON.stringify(action.metadata) : null\n )\n .run();\n\n return full;\n }\n\n async getAction(id: string): Promise<ModerationAction | null> {\n const row = await this.db\n .prepare(`SELECT * FROM ${this.prefix}_actions WHERE id = ?`)\n .bind(id)\n .first<ActionRow>();\n\n return row ? this.rowToAction(row) : null;\n }\n\n async listActions(filters?: {\n targetId?: string;\n moderatorActorId?: string;\n actionType?: ActionType;\n active?: boolean;\n since?: string;\n limit?: number;\n }): Promise<ModerationAction[]> {\n const conditions: string[] = ['1=1'];\n const bindings: unknown[] = [];\n\n if (filters?.targetId) {\n conditions.push('target_id = ?');\n bindings.push(filters.targetId);\n }\n if (filters?.moderatorActorId) {\n conditions.push('moderator_actor_id = ?');\n bindings.push(filters.moderatorActorId);\n }\n if (filters?.actionType) {\n conditions.push('action_type = ?');\n bindings.push(filters.actionType);\n }\n if (filters?.active !== undefined) {\n conditions.push('active = ?');\n bindings.push(filters.active ? 1 : 0);\n }\n if (filters?.since) {\n conditions.push('timestamp >= ?');\n bindings.push(filters.since);\n }\n\n const limit = filters?.limit ?? 100;\n\n let stmt = this.db.prepare(\n `SELECT * FROM ${this.prefix}_actions\n WHERE ${conditions.join(' AND ')}\n ORDER BY timestamp DESC\n LIMIT ?`\n );\n\n for (const binding of bindings) {\n stmt = stmt.bind(binding);\n }\n stmt = stmt.bind(limit);\n\n const result = await stmt.all<ActionRow>();\n return result.results.map((row) => this.rowToAction(row));\n }\n\n async deactivateAction(id: string): Promise<void> {\n await this.db\n .prepare(`UPDATE ${this.prefix}_actions SET active = 0 WHERE id = ?`)\n .bind(id)\n .run();\n }\n\n // ============ Instance Block Operations ============\n\n async createBlock(block: Omit<InstanceBlock, 'id'>): Promise<InstanceBlock> {\n const id = crypto.randomUUID();\n const full: InstanceBlock = { ...block, id };\n\n await this.db\n .prepare(\n `INSERT INTO ${this.prefix}_blocks\n (id, blocked_domain, level, reason, created_by, created_at, active, public_comment, federate)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`\n )\n .bind(\n id,\n block.blockedDomain.toLowerCase(),\n block.level,\n block.reason,\n block.createdBy,\n block.createdAt,\n block.active ? 1 : 0,\n block.publicComment ?? null,\n block.federate ? 1 : 0\n )\n .run();\n\n return full;\n }\n\n async getBlock(id: string): Promise<InstanceBlock | null> {\n const row = await this.db\n .prepare(`SELECT * FROM ${this.prefix}_blocks WHERE id = ?`)\n .bind(id)\n .first<BlockRow>();\n\n return row ? this.rowToBlock(row) : null;\n }\n\n async getBlockByDomain(domain: string): Promise<InstanceBlock | null> {\n const row = await this.db\n .prepare(`SELECT * FROM ${this.prefix}_blocks WHERE blocked_domain = ? AND active = 1`)\n .bind(domain.toLowerCase())\n .first<BlockRow>();\n\n return row ? this.rowToBlock(row) : null;\n }\n\n async listBlocks(filters?: { active?: boolean }): Promise<InstanceBlock[]> {\n let query = `SELECT * FROM ${this.prefix}_blocks`;\n const bindings: unknown[] = [];\n\n if (filters?.active !== undefined) {\n query += ' WHERE active = ?';\n bindings.push(filters.active ? 1 : 0);\n }\n\n query += ' ORDER BY created_at DESC';\n\n let stmt = this.db.prepare(query);\n for (const binding of bindings) {\n stmt = stmt.bind(binding);\n }\n\n const result = await stmt.all<BlockRow>();\n return result.results.map((row) => this.rowToBlock(row));\n }\n\n async updateBlock(id: string, updates: Partial<InstanceBlock>): Promise<void> {\n const existing = await this.getBlock(id);\n if (!existing) return;\n\n const merged = { ...existing, ...updates };\n\n await this.db\n .prepare(\n `UPDATE ${this.prefix}_blocks SET\n blocked_domain = ?, level = ?, reason = ?, active = ?, public_comment = ?, federate = ?\n WHERE id = ?`\n )\n .bind(\n merged.blockedDomain.toLowerCase(),\n merged.level,\n merged.reason,\n merged.active ? 1 : 0,\n merged.publicComment ?? null,\n merged.federate ? 1 : 0,\n id\n )\n .run();\n }\n\n async isInstanceBlocked(domain: string): Promise<boolean> {\n const block = await this.getBlockByDomain(domain);\n return block !== null;\n }\n\n // ============ Content Policy Operations ============\n\n async createPolicy(policy: Omit<ContentPolicy, 'id'>): Promise<ContentPolicy> {\n const id = crypto.randomUUID();\n const full: ContentPolicy = { ...policy, id };\n\n await this.db\n .prepare(\n `INSERT INTO ${this.prefix}_policies\n (id, name, description, rules, enabled, default_action, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)`\n )\n .bind(\n id,\n policy.name,\n policy.description,\n JSON.stringify(policy.rules),\n policy.enabled ? 1 : 0,\n policy.defaultAction,\n policy.updatedAt\n )\n .run();\n\n return full;\n }\n\n async getPolicy(id: string): Promise<ContentPolicy | null> {\n const row = await this.db\n .prepare(`SELECT * FROM ${this.prefix}_policies WHERE id = ?`)\n .bind(id)\n .first<PolicyRow>();\n\n return row ? this.rowToPolicy(row) : null;\n }\n\n async listPolicies(filters?: { enabled?: boolean }): Promise<ContentPolicy[]> {\n let query = `SELECT * FROM ${this.prefix}_policies`;\n const bindings: unknown[] = [];\n\n if (filters?.enabled !== undefined) {\n query += ' WHERE enabled = ?';\n bindings.push(filters.enabled ? 1 : 0);\n }\n\n query += ' ORDER BY name ASC';\n\n let stmt = this.db.prepare(query);\n for (const binding of bindings) {\n stmt = stmt.bind(binding);\n }\n\n const result = await stmt.all<PolicyRow>();\n return result.results.map((row) => this.rowToPolicy(row));\n }\n\n async updatePolicy(id: string, updates: Partial<ContentPolicy>): Promise<void> {\n const existing = await this.getPolicy(id);\n if (!existing) return;\n\n const merged = { ...existing, ...updates, updatedAt: new Date().toISOString() };\n\n await this.db\n .prepare(\n `UPDATE ${this.prefix}_policies SET\n name = ?, description = ?, rules = ?, enabled = ?, default_action = ?, updated_at = ?\n WHERE id = ?`\n )\n .bind(\n merged.name,\n merged.description,\n JSON.stringify(merged.rules),\n merged.enabled ? 1 : 0,\n merged.defaultAction,\n merged.updatedAt,\n id\n )\n .run();\n }\n\n async deletePolicy(id: string): Promise<void> {\n await this.db.prepare(`DELETE FROM ${this.prefix}_policies WHERE id = ?`).bind(id).run();\n }\n\n // ============ Rate Limit Operations ============\n\n async getRateLimitBucket(actorId: string): Promise<RateLimitBucket | null> {\n const row = await this.db\n .prepare(`SELECT * FROM ${this.prefix}_rate_limits WHERE actor_id = ?`)\n .bind(actorId)\n .first<RateLimitRow>();\n\n return row ? this.rowToRateLimit(row) : null;\n }\n\n async updateRateLimitBucket(bucket: RateLimitBucket): Promise<void> {\n await this.db\n .prepare(\n `INSERT INTO ${this.prefix}_rate_limits (actor_id, tokens, max_tokens, last_refill, refill_rate)\n VALUES (?, ?, ?, ?, ?)\n ON CONFLICT(actor_id) DO UPDATE SET\n tokens = excluded.tokens,\n max_tokens = excluded.max_tokens,\n last_refill = excluded.last_refill,\n refill_rate = excluded.refill_rate`\n )\n .bind(bucket.actorId, bucket.tokens, bucket.maxTokens, bucket.lastRefill, bucket.refillRate)\n .run();\n }\n\n // ============ Audit ============\n\n async getAuditLog(filters?: {\n targetId?: string;\n actorId?: string;\n since?: string;\n until?: string;\n limit?: number;\n }): Promise<ModerationAction[]> {\n const conditions: string[] = ['1=1'];\n const bindings: unknown[] = [];\n\n if (filters?.targetId) {\n conditions.push('target_id = ?');\n bindings.push(filters.targetId);\n }\n if (filters?.actorId) {\n conditions.push('moderator_actor_id = ?');\n bindings.push(filters.actorId);\n }\n if (filters?.since) {\n conditions.push('timestamp >= ?');\n bindings.push(filters.since);\n }\n if (filters?.until) {\n conditions.push('timestamp <= ?');\n bindings.push(filters.until);\n }\n\n const limit = filters?.limit ?? 100;\n\n let stmt = this.db.prepare(\n `SELECT * FROM ${this.prefix}_actions\n WHERE ${conditions.join(' AND ')}\n ORDER BY timestamp DESC\n LIMIT ?`\n );\n\n for (const binding of bindings) {\n stmt = stmt.bind(binding);\n }\n stmt = stmt.bind(limit);\n\n const result = await stmt.all<ActionRow>();\n return result.results.map((row) => this.rowToAction(row));\n }\n\n // ============ Utility Methods ============\n\n /**\n * Clear all moderation data (for testing)\n */\n async clear(): Promise<void> {\n await this.db.prepare(`DELETE FROM ${this.prefix}_reports`).run();\n await this.db.prepare(`DELETE FROM ${this.prefix}_actions`).run();\n await this.db.prepare(`DELETE FROM ${this.prefix}_blocks`).run();\n await this.db.prepare(`DELETE FROM ${this.prefix}_policies`).run();\n await this.db.prepare(`DELETE FROM ${this.prefix}_rate_limits`).run();\n }\n\n /**\n * Get stats for dashboard\n */\n async getStats(): Promise<{\n reports: { total: number; pending: number };\n actions: { total: number; active: number };\n blocks: { total: number; active: number };\n policies: { total: number; enabled: number };\n }> {\n const [reportsTotal, reportsPending, actionsTotal, actionsActive, blocksTotal, blocksActive, policiesTotal, policiesEnabled] = await Promise.all([\n this.db.prepare(`SELECT COUNT(*) as count FROM ${this.prefix}_reports`).first<{ count: number }>(),\n this.db.prepare(`SELECT COUNT(*) as count FROM ${this.prefix}_reports WHERE status = 'pending'`).first<{ count: number }>(),\n this.db.prepare(`SELECT COUNT(*) as count FROM ${this.prefix}_actions`).first<{ count: number }>(),\n this.db.prepare(`SELECT COUNT(*) as count FROM ${this.prefix}_actions WHERE active = 1`).first<{ count: number }>(),\n this.db.prepare(`SELECT COUNT(*) as count FROM ${this.prefix}_blocks`).first<{ count: number }>(),\n this.db.prepare(`SELECT COUNT(*) as count FROM ${this.prefix}_blocks WHERE active = 1`).first<{ count: number }>(),\n this.db.prepare(`SELECT COUNT(*) as count FROM ${this.prefix}_policies`).first<{ count: number }>(),\n this.db.prepare(`SELECT COUNT(*) as count FROM ${this.prefix}_policies WHERE enabled = 1`).first<{ count: number }>(),\n ]);\n\n return {\n reports: { total: reportsTotal?.count ?? 0, pending: reportsPending?.count ?? 0 },\n actions: { total: actionsTotal?.count ?? 0, active: actionsActive?.count ?? 0 },\n blocks: { total: blocksTotal?.count ?? 0, active: blocksActive?.count ?? 0 },\n policies: { total: policiesTotal?.count ?? 0, enabled: policiesEnabled?.count ?? 0 },\n };\n }\n\n // ============ Row Converters ============\n\n private rowToReport(row: ReportRow): ModerationReport {\n return {\n id: row.id,\n reporterActorId: row.reporter_actor_id,\n reporterInstance: row.reporter_instance,\n targetIds: JSON.parse(row.target_ids) as string[],\n category: row.category as ReportCategory,\n description: row.description,\n status: row.status as ReportStatus,\n activityId: row.activity_id,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n receivingInstance: row.receiving_instance,\n federatedToTarget: row.federated_to_target === 1,\n metadata: row.metadata ? (JSON.parse(row.metadata) as Record<string, unknown>) : undefined,\n };\n }\n\n private rowToAction(row: ActionRow): ModerationAction {\n return {\n id: row.id,\n reportId: row.report_id ?? undefined,\n moderatorActorId: row.moderator_actor_id,\n targetId: row.target_id,\n actionType: row.action_type as ActionType,\n reason: row.reason,\n timestamp: row.timestamp,\n expiresAt: row.expires_at ?? undefined,\n active: row.active === 1,\n reversesActionId: row.reverses_action_id ?? undefined,\n approvedBy: row.approved_by ? (JSON.parse(row.approved_by) as string[]) : undefined,\n metadata: row.metadata ? (JSON.parse(row.metadata) as Record<string, unknown>) : undefined,\n };\n }\n\n private rowToBlock(row: BlockRow): InstanceBlock {\n return {\n id: row.id,\n blockedDomain: row.blocked_domain,\n level: row.level as InstanceBlockLevel,\n reason: row.reason,\n createdBy: row.created_by,\n createdAt: row.created_at,\n active: row.active === 1,\n publicComment: row.public_comment ?? undefined,\n federate: row.federate === 1,\n };\n }\n\n private rowToPolicy(row: PolicyRow): ContentPolicy {\n return {\n id: row.id,\n name: row.name,\n description: row.description,\n rules: JSON.parse(row.rules) as ContentPolicy['rules'],\n enabled: row.enabled === 1,\n defaultAction: row.default_action as PolicyAction,\n updatedAt: row.updated_at,\n };\n }\n\n private rowToRateLimit(row: RateLimitRow): RateLimitBucket {\n return {\n actorId: row.actor_id,\n tokens: row.tokens,\n maxTokens: row.max_tokens,\n lastRefill: row.last_refill,\n refillRate: row.refill_rate,\n };\n }\n}\n","/**\n * Content Policy Engine\n *\n * Evaluates incoming cards against content policies.\n * Provides tools for community-defined moderation rules.\n */\n\nimport type { CCv3Data } from '@character-foundry/schemas';\nimport type {\n ContentPolicy,\n ContentPolicyRule,\n PolicyAction,\n PolicyEvaluationResult,\n ModerationStore,\n} from './types.js';\n\n/**\n * Default fields to check for keyword/regex rules\n */\nconst DEFAULT_TARGET_FIELDS = ['name', 'description', 'personality', 'scenario', 'first_mes'];\n\n/**\n * Maximum input length for regex matching (prevents ReDoS on large inputs)\n */\nconst MAX_REGEX_INPUT_LENGTH = 100_000;\n\n/**\n * Maximum regex pattern length\n */\nconst MAX_REGEX_PATTERN_LENGTH = 1000;\n\n/**\n * Common ReDoS-prone patterns to detect and warn about\n * These patterns can cause catastrophic backtracking\n */\nconst REDOS_WARNING_PATTERNS = [\n /\\(\\.\\*\\)\\+/, // (.*)+\n /\\(\\.\\+\\)\\+/, // (.+)+\n /\\([^)]*\\+[^)]*\\)\\+/, // (a+)+ style nested quantifiers\n /\\([^)]*\\*[^)]*\\)\\*/, // (a*)* style nested quantifiers\n /\\(\\[.*?\\]\\+\\)\\+/, // ([a-z]+)+ character class with nested quantifiers\n];\n\n/**\n * Check if a regex pattern might be vulnerable to ReDoS\n * Returns a warning message if the pattern looks dangerous, null if safe\n *\n * @security This is a heuristic check, not a guarantee.\n * Admin-only patterns should still be audited manually.\n */\nexport function checkRegexSafety(pattern: string): string | null {\n if (pattern.length > MAX_REGEX_PATTERN_LENGTH) {\n return `Pattern too long (${pattern.length} chars, max ${MAX_REGEX_PATTERN_LENGTH})`;\n }\n\n for (const dangerousPattern of REDOS_WARNING_PATTERNS) {\n if (dangerousPattern.test(pattern)) {\n return `Pattern may be vulnerable to ReDoS (catastrophic backtracking): matches ${dangerousPattern}`;\n }\n }\n\n return null;\n}\n\n/**\n * Content Policy Engine\n *\n * Evaluates cards against configured policies and returns the appropriate action.\n *\n * @example\n * ```typescript\n * const engine = new PolicyEngine(moderationStore);\n *\n * const result = await engine.evaluateCard(card, 'source.example.com');\n * if (result.action === 'reject') {\n * return new Response('Content policy violation', { status: 403 });\n * }\n * if (result.action === 'review') {\n * await queueForReview(card, result.matchedRules);\n * }\n * ```\n */\nexport class PolicyEngine {\n private store: ModerationStore;\n private compiledRegexCache: Map<string, RegExp> = new Map();\n\n constructor(store: ModerationStore) {\n this.store = store;\n }\n\n /**\n * Evaluate a card against all active policies\n *\n * @param card - The CCv3 card to evaluate\n * @param sourceInstance - Optional source instance domain for instance rules\n * @returns Evaluation result with action and matched rules\n */\n async evaluateCard(card: CCv3Data, sourceInstance?: string): Promise<PolicyEvaluationResult> {\n const policies = await this.store.listPolicies({ enabled: true });\n const allMatches: PolicyEvaluationResult['matchedRules'] = [];\n\n // Track the highest priority (lowest number) action found\n let finalAction: PolicyAction = 'allow';\n let lowestPriority = Infinity;\n\n for (const policy of policies) {\n const result = this.evaluateAgainstPolicy(card, policy, sourceInstance);\n allMatches.push(...result.matchedRules);\n\n // Check each matched rule's priority\n for (const match of result.matchedRules) {\n const rule = policy.rules.find((r) => r.id === match.ruleId);\n if (rule && rule.priority < lowestPriority) {\n lowestPriority = rule.priority;\n finalAction = rule.action;\n\n // 'allow' rules act as whitelist - immediate return\n if (rule.action === 'allow') {\n return {\n action: 'allow',\n matchedRules: allMatches,\n hasMatch: true,\n };\n }\n }\n }\n }\n\n // If no matches, use default action from first policy or allow\n if (allMatches.length === 0) {\n const defaultAction = policies.length > 0 ? policies[0]!.defaultAction : 'allow';\n return {\n action: defaultAction,\n matchedRules: [],\n hasMatch: false,\n };\n }\n\n return {\n action: finalAction,\n matchedRules: allMatches,\n hasMatch: true,\n };\n }\n\n /**\n * Evaluate card against a single policy\n */\n private evaluateAgainstPolicy(\n card: CCv3Data,\n policy: ContentPolicy,\n sourceInstance?: string\n ): PolicyEvaluationResult {\n const matches: PolicyEvaluationResult['matchedRules'] = [];\n\n // Sort rules by priority (lower = checked first)\n const enabledRules = policy.rules.filter((r) => r.enabled).sort((a, b) => a.priority - b.priority);\n\n for (const rule of enabledRules) {\n const match = this.evaluateRule(card, rule, sourceInstance);\n if (match) {\n matches.push({\n ruleId: rule.id,\n ruleName: rule.name,\n matchedField: match.field,\n matchedValue: match.value,\n });\n\n // If rule is 'allow', this is a whitelist - skip remaining rules\n if (rule.action === 'allow') {\n return { action: 'allow', matchedRules: matches, hasMatch: true };\n }\n }\n }\n\n return {\n action:\n matches.length > 0\n ? (enabledRules.find((r) => matches.some((m) => m.ruleId === r.id))?.action ??\n policy.defaultAction)\n : policy.defaultAction,\n matchedRules: matches,\n hasMatch: matches.length > 0,\n };\n }\n\n /**\n * Evaluate a single rule against a card\n */\n private evaluateRule(\n card: CCv3Data,\n rule: ContentPolicyRule,\n sourceInstance?: string\n ): { field?: string; value?: string } | null {\n switch (rule.type) {\n case 'keyword':\n return this.evaluateKeywordRule(card, rule);\n case 'regex':\n return this.evaluateRegexRule(card, rule);\n case 'tag':\n return this.evaluateTagRule(card, rule);\n case 'creator':\n return this.evaluateCreatorRule(card, rule);\n case 'instance':\n return this.evaluateInstanceRule(sourceInstance, rule);\n default:\n return null;\n }\n }\n\n /**\n * Keyword rule - case-insensitive text search\n */\n private evaluateKeywordRule(\n card: CCv3Data,\n rule: ContentPolicyRule\n ): { field?: string; value?: string } | null {\n const keyword = rule.pattern.toLowerCase();\n const fields = rule.targetFields || DEFAULT_TARGET_FIELDS;\n\n for (const field of fields) {\n const value = this.getCardField(card, field);\n if (typeof value === 'string' && value.toLowerCase().includes(keyword)) {\n return { field, value: keyword };\n }\n }\n return null;\n }\n\n /**\n * Regex rule - pattern matching with compiled cache\n *\n * @security Input is truncated to MAX_REGEX_INPUT_LENGTH to prevent ReDoS.\n * Patterns should be admin-audited. Use checkRegexSafety() when creating rules.\n */\n private evaluateRegexRule(\n card: CCv3Data,\n rule: ContentPolicyRule\n ): { field?: string; value?: string } | null {\n let regex = this.compiledRegexCache.get(rule.id);\n if (!regex) {\n // Check pattern safety\n const safetyWarning = checkRegexSafety(rule.pattern);\n if (safetyWarning) {\n console.warn(`[moderation] Rule \"${rule.name}\": ${safetyWarning}`);\n }\n\n try {\n regex = new RegExp(rule.pattern, 'i');\n this.compiledRegexCache.set(rule.id, regex);\n } catch {\n // Invalid regex pattern\n return null;\n }\n }\n\n const fields = rule.targetFields || DEFAULT_TARGET_FIELDS;\n\n for (const field of fields) {\n const value = this.getCardField(card, field);\n if (typeof value === 'string') {\n // Truncate input to prevent ReDoS on very large fields\n const truncatedValue = value.length > MAX_REGEX_INPUT_LENGTH\n ? value.slice(0, MAX_REGEX_INPUT_LENGTH)\n : value;\n\n const match = truncatedValue.match(regex);\n if (match) {\n return { field, value: match[0] };\n }\n }\n }\n return null;\n }\n\n /**\n * Tag rule - check card tags array\n */\n private evaluateTagRule(\n card: CCv3Data,\n rule: ContentPolicyRule\n ): { field?: string; value?: string } | null {\n const tags = card.data.tags || [];\n const targetTag = rule.pattern.toLowerCase();\n\n const found = tags.find((t) => t.toLowerCase() === targetTag);\n if (found) {\n return { field: 'tags', value: found };\n }\n return null;\n }\n\n /**\n * Creator rule - match card creator\n */\n private evaluateCreatorRule(\n card: CCv3Data,\n rule: ContentPolicyRule\n ): { field?: string; value?: string } | null {\n const creator = card.data.creator?.toLowerCase() || '';\n if (creator === rule.pattern.toLowerCase()) {\n return { field: 'creator', value: creator };\n }\n return null;\n }\n\n /**\n * Instance rule - match source domain\n * Supports wildcard patterns like \"*.evil.com\"\n */\n private evaluateInstanceRule(\n sourceInstance: string | undefined,\n rule: ContentPolicyRule\n ): { field?: string; value?: string } | null {\n if (!sourceInstance) return null;\n\n const domain = sourceInstance.toLowerCase();\n const pattern = rule.pattern.toLowerCase();\n\n // Support wildcard: \"*.evil.com\" matches \"sub.evil.com\"\n if (pattern.startsWith('*.')) {\n const suffix = pattern.slice(2); // Remove \"*.\"\n if (domain.endsWith(suffix) || domain === suffix) {\n return { field: 'instance', value: domain };\n }\n } else if (domain === pattern) {\n return { field: 'instance', value: domain };\n }\n return null;\n }\n\n /**\n * Get a field value from card data\n */\n private getCardField(card: CCv3Data, field: string): unknown {\n // Handle nested fields like 'extensions.something'\n if (field.includes('.')) {\n const parts = field.split('.');\n let value: unknown = card.data;\n for (const part of parts) {\n if (value && typeof value === 'object' && part in value) {\n value = (value as Record<string, unknown>)[part];\n } else {\n return undefined;\n }\n }\n return value;\n }\n\n return (card.data as Record<string, unknown>)[field];\n }\n\n /**\n * Clear regex cache (call when rules change)\n */\n clearCache(): void {\n this.compiledRegexCache.clear();\n }\n\n /**\n * Pre-compile regexes for a policy (optimization)\n *\n * @returns errors for invalid patterns, warnings for potentially unsafe patterns\n */\n precompilePolicy(policy: ContentPolicy): { errors: string[]; warnings: string[] } {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n for (const rule of policy.rules) {\n if (rule.type === 'regex' && rule.enabled) {\n // Check for ReDoS-prone patterns\n const safetyWarning = checkRegexSafety(rule.pattern);\n if (safetyWarning) {\n warnings.push(`Rule \"${rule.name}\": ${safetyWarning}`);\n }\n\n try {\n const regex = new RegExp(rule.pattern, 'i');\n this.compiledRegexCache.set(rule.id, regex);\n } catch (err) {\n errors.push(`Rule \"${rule.name}\" has invalid regex: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n }\n\n return { errors, warnings };\n }\n}\n","/**\n * Rate Limiter\n *\n * Token bucket rate limiting for moderation actions.\n * Prevents abuse of report/flag functionality.\n */\n\nimport type { ModerationStore, RateLimitBucket, RateLimitResult } from './types.js';\n\n/**\n * Rate limiter configuration\n */\nexport interface RateLimiterConfig {\n /** Max tokens per bucket (default: 5) */\n maxTokens: number;\n /** Tokens per hour refill rate (default: 1) */\n refillRate: number;\n}\n\nconst DEFAULT_CONFIG: RateLimiterConfig = {\n maxTokens: 5,\n refillRate: 1,\n};\n\n/**\n * Token bucket rate limiter\n *\n * @example\n * ```typescript\n * const limiter = new RateLimiter(moderationStore, {\n * maxTokens: 10,\n * refillRate: 2, // 2 tokens per hour\n * });\n *\n * const result = await limiter.checkAndConsume('https://example.com/users/alice');\n * if (!result.allowed) {\n * return new Response('Too many reports', {\n * status: 429,\n * headers: { 'Retry-After': String(result.retryAfter) },\n * });\n * }\n * ```\n */\nexport class RateLimiter {\n private store: ModerationStore;\n private config: RateLimiterConfig;\n\n constructor(store: ModerationStore, config?: Partial<RateLimiterConfig>) {\n this.store = store;\n this.config = { ...DEFAULT_CONFIG, ...config };\n }\n\n /**\n * Check if action is allowed and consume a token if so\n *\n * @param actorId - Actor URI to check\n * @returns Rate limit result with remaining tokens and reset time\n */\n async checkAndConsume(actorId: string): Promise<RateLimitResult> {\n let bucket = await this.store.getRateLimitBucket(actorId);\n\n if (!bucket) {\n // Create new bucket for actor\n bucket = {\n actorId,\n tokens: this.config.maxTokens,\n maxTokens: this.config.maxTokens,\n lastRefill: new Date().toISOString(),\n refillRate: this.config.refillRate,\n };\n }\n\n // Refill tokens based on time elapsed\n bucket = this.refillBucket(bucket);\n\n if (bucket.tokens < 1) {\n const resetAt = this.calculateResetTime(bucket);\n const retryAfter = Math.ceil((new Date(resetAt).getTime() - Date.now()) / 1000);\n\n // Update bucket even if denied (to persist refill time)\n await this.store.updateRateLimitBucket(bucket);\n\n return {\n allowed: false,\n remaining: 0,\n resetAt,\n retryAfter: Math.max(0, retryAfter),\n };\n }\n\n // Consume token\n bucket.tokens -= 1;\n await this.store.updateRateLimitBucket(bucket);\n\n return {\n allowed: true,\n remaining: Math.floor(bucket.tokens),\n resetAt: this.calculateResetTime(bucket),\n };\n }\n\n /**\n * Check remaining tokens without consuming\n *\n * @param actorId - Actor URI to check\n * @returns Current rate limit status\n */\n async check(actorId: string): Promise<RateLimitResult> {\n let bucket = await this.store.getRateLimitBucket(actorId);\n\n if (!bucket) {\n return {\n allowed: true,\n remaining: this.config.maxTokens,\n resetAt: new Date().toISOString(),\n };\n }\n\n bucket = this.refillBucket(bucket);\n\n return {\n allowed: bucket.tokens >= 1,\n remaining: Math.floor(bucket.tokens),\n resetAt: this.calculateResetTime(bucket),\n retryAfter:\n bucket.tokens < 1\n ? Math.ceil((new Date(this.calculateResetTime(bucket)).getTime() - Date.now()) / 1000)\n : undefined,\n };\n }\n\n /**\n * Reset rate limit for an actor (admin function)\n *\n * @param actorId - Actor URI to reset\n */\n async reset(actorId: string): Promise<void> {\n const bucket: RateLimitBucket = {\n actorId,\n tokens: this.config.maxTokens,\n maxTokens: this.config.maxTokens,\n lastRefill: new Date().toISOString(),\n refillRate: this.config.refillRate,\n };\n await this.store.updateRateLimitBucket(bucket);\n }\n\n /**\n * Refill tokens based on time elapsed since last refill\n */\n private refillBucket(bucket: RateLimitBucket): RateLimitBucket {\n const now = new Date();\n const lastRefill = new Date(bucket.lastRefill);\n const hoursSinceRefill = (now.getTime() - lastRefill.getTime()) / (1000 * 60 * 60);\n\n const tokensToAdd = hoursSinceRefill * bucket.refillRate;\n\n if (tokensToAdd >= 1) {\n // Only update if we're adding at least 1 token (avoid floating point issues)\n bucket.tokens = Math.min(bucket.maxTokens, bucket.tokens + Math.floor(tokensToAdd));\n bucket.lastRefill = now.toISOString();\n }\n\n return bucket;\n }\n\n /**\n * Calculate when bucket will have at least 1 token\n */\n private calculateResetTime(bucket: RateLimitBucket): string {\n if (bucket.tokens >= bucket.maxTokens) {\n return new Date().toISOString();\n }\n\n // Calculate time until 1 token is available\n const tokensNeeded = Math.max(0, 1 - bucket.tokens);\n const hoursUntilToken = tokensNeeded / bucket.refillRate;\n const msUntilToken = hoursUntilToken * 60 * 60 * 1000;\n\n const lastRefill = new Date(bucket.lastRefill);\n return new Date(lastRefill.getTime() + msUntilToken).toISOString();\n }\n\n /**\n * Get current configuration\n */\n getConfig(): RateLimiterConfig {\n return { ...this.config };\n }\n}\n","/**\n * @character-foundry/federation\n *\n * Federation layer for syncing character cards across platforms via ActivityPub.\n *\n * ⚠️ WARNING: This package is experimental and incomplete.\n *\n * Security-critical features (signature validation, inbox handling) are stubbed.\n * Do NOT use in production without explicit opt-in.\n *\n * To enable federation features, you MUST:\n * 1. Set FEDERATION_ENABLED=true in your environment (Node.js) or call enableFederation({ skipEnvCheck: true }) in browser/Workers\n * 2. Call enableFederation() before using SyncEngine or route handlers\n *\n * Both steps are required as a dual opt-in safety mechanism (except in environments without process.env).\n */\n\nlet explicitlyEnabled = false;\nlet envCheckSkipped = false;\n\n/**\n * Safe check for environment variable - works in Node.js, browser, and Workers\n */\nfunction getEnvVar(name: string): string | undefined {\n // Check if process exists and has env (Node.js)\n if (typeof process !== 'undefined' && process?.env) {\n return process.env[name];\n }\n // In browser/Workers, return undefined (env var check will be skipped)\n return undefined;\n}\n\n/**\n * Enable federation features. Must be called before using SyncEngine or route handlers.\n * This is a safety gate - federation is experimental and has incomplete security.\n *\n * @param options.skipEnvCheck - Skip FEDERATION_ENABLED env var check (required for browser/Workers)\n *\n * Note: In Node.js, both this call AND FEDERATION_ENABLED=true are required (dual opt-in).\n * In browser/Workers, use skipEnvCheck: true since env vars aren't available.\n */\nexport function enableFederation(options?: { skipEnvCheck?: boolean }): void {\n explicitlyEnabled = true;\n envCheckSkipped = options?.skipEnvCheck ?? false;\n\n const nodeEnv = getEnvVar('NODE_ENV');\n if (nodeEnv === 'development' || nodeEnv === 'test') {\n console.warn(\n '[character-foundry/federation] Federation enabled. ' +\n 'WARNING: Verify HTTP signatures in production. ' +\n 'Do NOT use in production with untrusted inputs without signature validation.'\n );\n }\n}\n\n/**\n * Check if federation is enabled\n *\n * In Node.js: requires BOTH env var AND enableFederation() call\n * In browser/Workers with skipEnvCheck: requires only enableFederation() call\n */\nexport function isFederationEnabled(): boolean {\n if (!explicitlyEnabled) {\n return false;\n }\n\n // If env check was skipped (browser/Workers), only check explicit enable\n if (envCheckSkipped) {\n return true;\n }\n\n // In Node.js, also require env var\n const envEnabled = getEnvVar('FEDERATION_ENABLED') === 'true';\n return envEnabled;\n}\n\n/**\n * Assert federation is enabled, throw if not\n * @internal\n */\nexport function assertFederationEnabled(feature: string): void {\n if (!explicitlyEnabled) {\n const hasProcess = typeof process !== 'undefined' && process?.env;\n const envHint = hasProcess\n ? 'You must BOTH call enableFederation() AND set FEDERATION_ENABLED=true.'\n : 'You must call enableFederation({ skipEnvCheck: true }) in browser/Workers environments.';\n\n throw new Error(\n `Federation is not enabled. ${feature} requires federation to be explicitly enabled. ` +\n `${envHint} ` +\n `WARNING: Federation security features are incomplete.`\n );\n }\n\n // If env check was skipped, we're done\n if (envCheckSkipped) {\n return;\n }\n\n // In Node.js, also check env var\n const envEnabled = getEnvVar('FEDERATION_ENABLED') === 'true';\n if (!envEnabled) {\n throw new Error(\n `FEDERATION_ENABLED environment variable not set. ${feature} requires FEDERATION_ENABLED=true. ` +\n `Code opt-in alone is not sufficient (dual opt-in required). ` +\n `In browser/Workers, use enableFederation({ skipEnvCheck: true }) instead.`\n );\n }\n}\n\n// Core types\nexport type {\n FederatedCardId,\n PlatformId,\n PlatformRole,\n FederatedActor,\n FederatedCard,\n ActivityType,\n FederatedActivity,\n CardSyncState,\n SyncOperation,\n SyncResult,\n PlatformAdapter,\n FederationConfig,\n SyncStateStore,\n FederationEventType,\n FederationEvent,\n FederationEventListener,\n // Fork types\n ForkReference,\n ForkNotification,\n ForkActivity,\n ForkResult,\n InboxResult,\n InboxHandlerOptions,\n // Install/stats types\n InstallNotification,\n InstallActivity,\n CardStats,\n} from './types.js';\n\n// ActivityPub utilities\nexport {\n ACTIVITY_CONTEXT,\n FORK_ACTIVITY_CONTEXT,\n INSTALL_ACTIVITY_CONTEXT,\n generateCardId,\n generateActivityId,\n cardToActivityPub,\n cardFromActivityPub,\n createCreateActivity,\n createUpdateActivity,\n createDeleteActivity,\n createAnnounceActivity,\n createLikeActivity,\n createUndoActivity,\n createForkActivity,\n parseForkActivity,\n createInstallActivity,\n parseInstallActivity,\n createActor,\n parseActivity,\n // validateActivitySignature is deprecated - use validateHttpSignature instead\n} from './activitypub.js';\n\n// Sync engine\nexport {\n SyncEngine,\n type SyncEngineOptions,\n} from './sync-engine.js';\n\n// State stores\nexport {\n MemorySyncStateStore,\n FileSyncStateStore,\n createLocalStorageStore,\n} from './state-store.js';\n\nexport {\n D1SyncStateStore,\n type D1Database,\n type D1PreparedStatement,\n type D1Result,\n type D1ExecResult,\n} from './d1-store.js';\n\n// Platform adapters\nexport {\n BasePlatformAdapter,\n MemoryPlatformAdapter,\n HttpPlatformAdapter,\n InvalidResourceIdError,\n SillyTavernAdapter,\n createArchiveAdapter,\n createHubAdapter,\n stCharacterToCCv3,\n ccv3ToSTCharacter,\n createMockSTBridge,\n type AdapterCard,\n type AdapterAsset,\n type HttpAdapterConfig,\n type FetchFn,\n type SillyTavernBridge,\n type STCharacter,\n type STCharacterStats,\n} from './adapters/index.js';\n\n// Routes\nexport {\n handleWebFinger,\n type WebFingerResponse,\n type WebFingerLink,\n handleNodeInfoDiscovery,\n handleNodeInfo,\n type NodeInfoDiscoveryResponse,\n type NodeInfoResponse,\n handleActor,\n handleInbox,\n validateForkActivity,\n validateInstallActivity,\n} from './routes/index.js';\n\n// HTTP Signatures\nexport {\n REQUIRED_SIGNED_HEADERS,\n type ParsedSignature,\n type SignatureValidationOptions,\n type SignatureValidationResult,\n type SigningOptions,\n type SigningStringResult,\n type VerifyHttpSignatureOptions,\n parseSignatureHeader,\n buildSigningString,\n buildSigningStringStrict,\n verifyHttpSignature,\n validateActivitySignature as validateHttpSignature,\n signRequest,\n calculateDigest,\n} from './http-signatures.js';\n\n// Moderation - community-based content moderation tools\nexport {\n // Activity types\n type FlagActivity,\n type BlockActivity,\n // Report types\n type ModerationReport,\n type ReportStatus,\n type ReportCategory,\n // Action types\n type ModerationAction,\n type ActionType,\n // Block types\n type InstanceBlock,\n type InstanceBlockLevel,\n // Policy types\n type ContentPolicy,\n type ContentPolicyRule,\n type PolicyRuleType,\n type PolicyAction,\n type PolicyEvaluationResult,\n // Rate limit types\n type RateLimitBucket,\n type RateLimitResult,\n // Store interface\n type ModerationStore,\n // Event types\n type ModerationEvent,\n type ModerationEventType,\n type ModerationEventListener,\n // Activities\n MODERATION_ACTIVITY_CONTEXT,\n createFlagActivity,\n parseFlagActivity,\n validateFlagActivity as validateFlagActivityFields,\n createBlockActivity,\n parseBlockActivity,\n validateBlockActivity as validateBlockActivityFields,\n // Stores\n MemoryModerationStore,\n D1ModerationStore,\n // Engines\n PolicyEngine,\n checkRegexSafety,\n RateLimiter,\n type RateLimiterConfig,\n} from './moderation/index.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AEWA,IAAM,SAAS,OAAO,YAAY,eAChC,QAAQ,YAAY,QACpB,QAAQ,SAAS,QAAQ;AAO3B,IAAM,yBAAyB,OAAO;AAsFtC,IAAM,oBAAoB,KAAK;AClG/B,IAAM,uBAAuB,uBAAO,IAAI,sCAAsC;AAKvE,IAAM,eAAN,cAA2B,MAAM;EAItC,YAAY,SAAiC,MAAc;AACzD,UAAM,OAAO;AAD8B,SAAA,OAAA;AAE3C,SAAK,OAAO;AAEZ,QAAI,MAAM,mBAAmB;AAC3B,YAAM,kBAAkB,MAAM,KAAK,WAAW;IAChD;EACF;;EATA,CAAU,oBAAoB,IAAI;AAUpC;AIfA,SAAS,WAAW,OAA2B;AAC7C,QAAM,MAAM,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAC7E,SAAO,GAAG,IAAI,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI,MAAM,GAAG,EAAE,CAAC,IAAI,IAAI,MAAM,IAAI,EAAE,CAAC,IAAI,IAAI,MAAM,IAAI,EAAE,CAAC,IAAI,IAAI,MAAM,IAAI,EAAE,CAAC;AAC9G;AAMA,SAAS,iBAAyB;AAChC,SAAO,uCAAuC,QAAQ,SAAS,CAAC,MAAM;AACpE,UAAM,IAAK,KAAK,OAAO,IAAI,KAAM;AACjC,UAAM,IAAI,MAAM,MAAM,IAAK,IAAI,IAAO;AACtC,WAAO,EAAE,SAAS,EAAE;EACtB,CAAC;AACH;AAiBO,SAAS,eAAuB;AAErC,MAAI,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe,YAAY;AAC5E,WAAO,OAAO,WAAW;EAC3B;AAGA,MAAI,OAAO,WAAW,eAAe,OAAO,OAAO,oBAAoB,YAAY;AACjF,UAAM,QAAQ,IAAI,WAAW,EAAE;AAC/B,WAAO,gBAAgB,KAAK;AAE5B,UAAM,CAAC,IAAK,MAAM,CAAC,IAAK,KAAQ;AAChC,UAAM,CAAC,IAAK,MAAM,CAAC,IAAK,KAAQ;AAChC,WAAO,WAAW,KAAK;EACzB;AAGA,MAAI,OAAO,YAAY,eAAe,QAAQ,KAAK,aAAa,eAAe;AAC7E,YAAQ,KAAK,8EAA8E;EAC7F;AACA,SAAO,eAAe;AACxB;;;AC3CO,IAAM,mBAAmB;EAC9B;EACA;IACE,aAAa;IACb,qBAAqB,EAAE,OAAO,oBAAoB;IAClD,kBAAkB,EAAE,OAAO,iBAAiB;EAC9C;AACF;AAMO,IAAM,wBAAwB;EACnC;EACA;IACE,aAAa;IACb,qBAAqB,EAAE,OAAO,oBAAoB;IAClD,kBAAkB,EAAE,OAAO,iBAAiB;IAC5C,QAAQ;IACR,cAAc,EAAE,OAAO,wBAAwB,SAAS,MAAM;EAChE;AACF;AAMO,IAAM,2BAA2B;EACtC;EACA;IACE,aAAa;IACb,WAAW;EACb;AACF;AAQO,SAAS,eAAe,SAAiB,SAAyB;AAGvE,QAAM,YAAY,mBAAmB,OAAO;AAC5C,SAAO,GAAG,OAAO,UAAU,SAAS;AACtC;AAKO,SAAS,mBAAmB,SAAyB;AAC1D,QAAM,YAAY,KAAK,IAAI;AAC3B,QAAM,SAAS,aAAa,EAAE,MAAM,GAAG,EAAE,CAAC;AAC1C,SAAO,GAAG,OAAO,eAAe,SAAS,IAAI,MAAM;AACrD;AAKO,SAAS,kBACd,MACA,SAee;AACf,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,QAAM,WAAW,KAAK;AAGtB,QAAM,OAAO,SAAS,MAAM,IAAI,CAAC,SAAS;IACxC,MAAM;IACN,MAAM,IAAI,IAAI,QAAQ,QAAQ,GAAG,CAAC;EACpC,EAAE;AAEF,QAAM,gBAA+B;IACnC,YAAY;IACZ,IAAI,QAAQ;IACZ,MAAM;IACN,MAAM,SAAS;IACf,SAAS,SAAS,aAAa,UAAU,GAAG,GAAG;IAC/C,SAAS,KAAK,UAAU,IAAI;IAC5B,WAAW;IACX,cAAc,QAAQ;IACtB,WAAW,QAAQ,aAAa;IAChC,SAAS,QAAQ;IACjB,KAAK;IACL,YAAY,QAAQ;IACpB,qBAAqB,SAAS;IAC9B,kBAAkB,KAAK;EACzB;AAGA,MAAI,QAAQ,kBAAkB,QAAQ,UAAU;AAC9C,kBAAc,SAAS;MACrB,UAAU,QAAQ;MAClB,IAAI,QAAQ;MACZ,KAAK,QAAQ;IACf;EACF;AAEA,SAAO;AACT;AAKO,SAAS,oBAAoB,eAAwC;AAC1E,MAAI;AACF,UAAM,OAAO,KAAK,MAAM,cAAc,OAAO;AAG7C,QAAI,KAAK,SAAS,iBAAiB;AACjC,YAAM,IAAI,MAAM,mBAAmB;IACrC;AAEA,WAAO;EACT,SAAS,KAAK;AACZ,UAAM,IAAI;MACR,0CAA0C,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;IAC5F;EACF;AACF;AAKO,SAAS,qBACd,MACA,SACA,SACA,YACmB;AACnB,SAAO;IACL,YAAY;IACZ,IAAI,mBAAmB,OAAO;IAC9B,MAAM;IACN,OAAO;IACP,QAAQ;IACR,YAAW,oBAAI,KAAK,GAAE,YAAY;IAClC,IAAI,YAAY,MAAM,CAAC,8CAA8C;IACrE,IAAI,YAAY;EAClB;AACF;AAKO,SAAS,qBACd,MACA,SACA,SACA,YACmB;AACnB,SAAO;IACL,YAAY;IACZ,IAAI,mBAAmB,OAAO;IAC9B,MAAM;IACN,OAAO;IACP,QAAQ;IACR,YAAW,oBAAI,KAAK,GAAE,YAAY;IAClC,IAAI,YAAY,MAAM,CAAC,8CAA8C;IACrE,IAAI,YAAY;EAClB;AACF;AAKO,SAAS,qBACd,QACA,SACA,SACA,YACmB;AACnB,SAAO;IACL,YAAY;IACZ,IAAI,mBAAmB,OAAO;IAC9B,MAAM;IACN,OAAO;IACP,QAAQ;IACR,YAAW,oBAAI,KAAK,GAAE,YAAY;IAClC,IAAI,YAAY,MAAM,CAAC,8CAA8C;IACrE,IAAI,YAAY;EAClB;AACF;AAKO,SAAS,uBACd,QACA,SACA,SACA,YACmB;AACnB,SAAO;IACL,YAAY;IACZ,IAAI,mBAAmB,OAAO;IAC9B,MAAM;IACN,OAAO;IACP,QAAQ;IACR,YAAW,oBAAI,KAAK,GAAE,YAAY;IAClC,IAAI,YAAY,MAAM,CAAC,8CAA8C;IACrE,IAAI,YAAY;EAClB;AACF;AAKO,SAAS,mBACd,QACA,SACA,SACmB;AACnB,SAAO;IACL,YAAY;IACZ,IAAI,mBAAmB,OAAO;IAC9B,MAAM;IACN,OAAO;IACP,QAAQ;IACR,YAAW,oBAAI,KAAK,GAAE,YAAY;EACpC;AACF;AAKO,SAAS,mBACd,oBACA,SACA,SACmB;AACnB,SAAO;IACL,YAAY;IACZ,IAAI,mBAAmB,OAAO;IAC9B,MAAM;IACN,OAAO;IACP,QAAQ;IACR,YAAW,oBAAI,KAAK,GAAE,YAAY;EACpC;AACF;AAQO,SAAS,mBACd,cACA,YACA,SACA,SACA,YACc;AACd,SAAO;IACL,YAAY;IACZ,IAAI,mBAAmB,OAAO;IAC9B,MAAM;IACN,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,YAAW,oBAAI,KAAK,GAAE,YAAY;IAClC,IAAI,YAAY,MAAM,CAAC,8CAA8C;IACrE,IAAI,YAAY;EAClB;AACF;AAOO,SAAS,kBAAkB,UAKzB;AACP,MAAI,CAAC,YAAY,OAAO,aAAa,UAAU;AAC7C,WAAO;EACT;AAEA,QAAM,MAAM;AAGZ,MAAI,IAAI,SAAS,QAAQ;AACvB,WAAO;EACT;AAGA,MACE,OAAO,IAAI,UAAU,YACrB,OAAO,IAAI,WAAW,YACtB,OAAO,IAAI,OAAO,YAClB,CAAC,IAAI,UACL,OAAO,IAAI,WAAW,UACtB;AACA,WAAO;EACT;AAEA,QAAM,SAAS,IAAI;AAGnB,MACE,OAAO,OAAO,OAAO,YACrB,OAAO,OAAO,YAAY,YAC1B,OAAO,OAAO,iBAAiB,UAC/B;AACA,WAAO;EACT;AAEA,SAAO;IACL,cAAc,IAAI;IAClB,YAAY;IACZ,OAAO,IAAI;IACX,YAAY,IAAI;EAClB;AACF;AAOO,SAAS,sBACd,QACA,SACA,SACA,UACA,YACiB;AACjB,SAAO;IACL,YAAY;IACZ,IAAI,mBAAmB,OAAO;IAC9B,MAAM;IACN,OAAO;IACP,QAAQ;IACR,QAAQ;MACN,MAAM;MACN,MAAM;IACR;IACA,YAAW,oBAAI,KAAK,GAAE,YAAY;IAClC,IAAI,YAAY;IAChB,IAAI,YAAY;EAClB;AACF;AAOO,SAAS,qBAAqB,UAK5B;AACP,MAAI,CAAC,YAAY,OAAO,aAAa,UAAU;AAC7C,WAAO;EACT;AAEA,QAAM,MAAM;AAGZ,MAAI,IAAI,SAAS,WAAW;AAC1B,WAAO;EACT;AAGA,MACE,OAAO,IAAI,UAAU,YACrB,OAAO,IAAI,WAAW,YACtB,OAAO,IAAI,OAAO,UAClB;AACA,WAAO;EACT;AAGA,MAAI,WAA8B;AAClC,MAAI,IAAI,UAAU,OAAO,IAAI,WAAW,UAAU;AAChD,UAAM,SAAS,IAAI;AACnB,QAAI,OAAO,SAAS,iBAAiB,OAAO,OAAO,SAAS,UAAU;AACpE,iBAAW,OAAO;IACpB;EACF;AAEA,SAAO;IACL,QAAQ,IAAI;IACZ,OAAO,IAAI;IACX;IACA,YAAY,IAAI;EAClB;AACF;AAKO,SAAS,YAAY,SAQT;AACjB,QAAM,QAAwB;IAC5B,IAAI,QAAQ;IACZ,MAAM;IACN,MAAM,QAAQ;IACd,mBAAmB,QAAQ;IAC3B,SAAS,QAAQ;IACjB,MAAM,QAAQ;IACd,OAAO,GAAG,QAAQ,EAAE;IACpB,QAAQ,GAAG,QAAQ,EAAE;IACrB,WAAW,GAAG,QAAQ,EAAE;IACxB,WAAW,GAAG,QAAQ,EAAE;EAC1B;AAEA,MAAI,QAAQ,cAAc;AACxB,UAAM,YAAY;MAChB,IAAI,GAAG,QAAQ,EAAE;MACjB,OAAO,QAAQ;MACf,cAAc,QAAQ;IACxB;EACF;AAEA,SAAO;AACT;AAKO,SAAS,cAAc,MAAkC;AAC9D,MAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC,UAAM,IAAI,MAAM,iCAAiC;EACnD;AAEA,QAAM,WAAW;AAEjB,MAAI,CAAC,SAAS,QAAQ,CAAC,SAAS,SAAS,CAAC,SAAS,QAAQ;AACzD,UAAM,IAAI,MAAM,2CAA2C;EAC7D;AAEA,SAAO;AACT;AC9bA,SAAS,aAAa,MAAwB;AAC5C,QAAM,UAAU,KAAK,UAAU,IAAI;AACnC,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,UAAM,OAAO,QAAQ,WAAW,CAAC;AACjC,YAAS,QAAQ,KAAK,OAAQ;AAC9B,WAAO,OAAO;EAChB;AACA,SAAO,KAAK,IAAI,IAAI,EAAE,SAAS,EAAE;AACnC;AAWA,eAAe,eAAe,MAAiC;AAC7D,QAAM,UAAU,KAAK,UAAU,IAAI;AACnC,QAAM,OAAO,IAAI,YAAY,EAAE,OAAO,OAAO;AAC7C,QAAM,aAAa,MAAM,OAAO,OAAO,OAAO,WAAW,IAAI;AAC7D,QAAM,YAAY,MAAM,KAAK,IAAI,WAAW,UAAU,CAAC;AACvD,SAAO,UAAU,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AACtE;AA8BO,IAAM,aAAN,MAAiB;EACd,YAA8C,oBAAI,IAAI;EACtD;EACA;EACA;EACA,YAAoE,oBAAI,IAAI;EAC5E;EACA;;;;;;EAOA,iBAAiB;EAEzB,YAAY,SAA4B;AACtC,4BAAwB,YAAY;AAEpC,SAAK,UAAU,QAAQ;AACvB,SAAK,UAAU,QAAQ;AACvB,SAAK,aAAa,QAAQ;AAC1B,SAAK,gBAAgB,QAAQ,iBAAiB;AAE9C,QAAI,QAAQ,oBAAoB,QAAQ,mBAAmB,GAAG;AAC5D,WAAK,gBAAgB;QACnB,MAAM,KAAK,KAAK,QAAQ;;QACxB,QAAQ;MACV;IACF;EACF;;;;;EAMA,MAAc,SAAS,MAAiC;AACtD,QAAI,KAAK,eAAe;AACtB,aAAO,eAAe,IAAI;IAC5B;AACA,WAAO,aAAa,IAAI;EAC1B;;;;EAKA,iBAAiB,SAAgC;AAC/C,SAAK,UAAU,IAAI,QAAQ,UAAU,OAAO;EAC9C;;;;EAKA,mBAAmB,UAA4B;AAC7C,SAAK,UAAU,OAAO,QAAQ;EAChC;;;;EAKA,eAA6B;AAC3B,WAAO,MAAM,KAAK,KAAK,UAAU,KAAK,CAAC;EACzC;;;;EAKA,GAAG,OAA4B,UAAyC;AACtE,QAAI,CAAC,KAAK,UAAU,IAAI,KAAK,GAAG;AAC9B,WAAK,UAAU,IAAI,OAAO,oBAAI,IAAI,CAAC;IACrC;AACA,SAAK,UAAU,IAAI,KAAK,EAAG,IAAI,QAAQ;EACzC;;;;EAKA,IAAI,OAA4B,UAAyC;AACvE,SAAK,UAAU,IAAI,KAAK,GAAG,OAAO,QAAQ;EAC5C;;;;EAKQ,KAAK,MAA2B,MAAqB;AAC3D,UAAM,QAAyB;MAC7B;MACA,YAAW,oBAAI,KAAK,GAAE,YAAY;MAClC;IACF;AAEA,UAAM,YAAY,KAAK,UAAU,IAAI,IAAI;AACzC,QAAI,WAAW;AACb,iBAAW,YAAY,WAAW;AAChC,YAAI;AACF,mBAAS,KAAK;QAChB,SAAS,KAAK;AACZ,kBAAQ,MAAM,yBAAyB,GAAG;QAC5C;MACF;IACF;EACF;;;;EAKA,MAAM,SACJ,gBACA,UACA,gBACqB;AACrB,UAAM,YAA2B;MAC/B,MAAM;MACN,QAAQ,GAAG,cAAc,IAAI,QAAQ;MACrC;MACA;MACA,YAAW,oBAAI,KAAK,GAAE,YAAY;IACpC;AAEA,QAAI;AAEF,YAAM,gBAAgB,KAAK,UAAU,IAAI,cAAc;AACvD,UAAI,CAAC,eAAe;AAClB,cAAM,IAAI,MAAM,4BAA4B,cAAc,EAAE;MAC9D;AAGA,YAAM,gBAAgB,KAAK,UAAU,IAAI,cAAc;AACvD,UAAI,CAAC,eAAe;AAClB,cAAM,IAAI,MAAM,4BAA4B,cAAc,EAAE;MAC9D;AAGA,UAAI,CAAC,MAAM,cAAc,YAAY,GAAG;AACtC,cAAM,IAAI,MAAM,gCAAgC,cAAc,EAAE;MAClE;AACA,UAAI,CAAC,MAAM,cAAc,YAAY,GAAG;AACtC,cAAM,IAAI,MAAM,gCAAgC,cAAc,EAAE;MAClE;AAGA,YAAM,OAAO,MAAM,cAAc,QAAQ,QAAQ;AACjD,UAAI,CAAC,MAAM;AACT,cAAM,IAAI,MAAM,mBAAmB,QAAQ,EAAE;MAC/C;AAGA,YAAM,cAAc,eAAe,KAAK,SAAS,GAAG,cAAc,IAAI,QAAQ,EAAE;AAChF,YAAM,YAAY,MAAM,KAAK,WAAW,IAAI,WAAW;AAGvD,UAAI,WAAW,YAAY,cAAc,GAAG;AAC1C,cAAM,eAAe,MAAM,cAAc,QAAQ,UAAU,YAAY,cAAc,CAAE;AACvF,YAAI,cAAc;AAChB,gBAAM,eAAe,MAAM,KAAK,SAAS,YAAY;AACrD,gBAAMA,WAAU,MAAM,KAAK,SAAS,IAAI;AAExC,cAAI,iBAAiB,UAAU,eAAeA,aAAY,UAAU,aAAa;AAE/E,sBAAU,SAAS;AACnB,sBAAU,WAAW;cACnB,cAAcA;cACd,eAAe;cACf,gBAAgB;YAClB;AACA,kBAAM,KAAK,WAAW,IAAI,SAAS;AAEnC,iBAAK,KAAK,iBAAiB,EAAE,WAAW,gBAAgB,eAAe,CAAC;AAExE,mBAAO;cACL,SAAS;cACT;cACA,UAAU;cACV,OAAO;YACT;UACF;QACF;MACF;AAGA,YAAM,WAAW,MAAM,cAAc;QACnC;QACA,WAAW,YAAY,cAAc;MACvC;AAGA,YAAM,UAAU,MAAM,KAAK,SAAS,IAAI;AACxC,YAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AAGnC,YAAM,eAA8B,aAAa;QAC/C,SAAS;QACT;QACA,aAAa,CAAC;QACd,UAAU,CAAC;QACX,aAAa;QACb,QAAQ;MACV;AAEA,mBAAa,YAAY,cAAc,IAAI;AAC3C,mBAAa,YAAY,cAAc,IAAI;AAC3C,mBAAa,SAAS,cAAc,IAAI;AACxC,mBAAa,SAAS,cAAc,IAAI;AACxC,mBAAa,cAAc;AAC3B,mBAAa,SAAS;AACtB,mBAAa,WAAW;AAExB,YAAM,KAAK,WAAW,IAAI,YAAY;AAEtC,WAAK,KAAK,eAAe,EAAE,WAAW,cAAc,gBAAgB,eAAe,CAAC;AAEpF,aAAO;QACL,SAAS;QACT;QACA,UAAU;MACZ;IACF,SAAS,KAAK;AACZ,WAAK,KAAK,eAAe,EAAE,WAAW,OAAO,IAAI,CAAC;AAElD,aAAO;QACL,SAAS;QACT;QACA,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;MACxD;IACF;EACF;;;;EAKA,MAAM,SACJ,gBACA,UACA,eACqB;AAErB,WAAO,KAAK,SAAS,gBAAgB,UAAU,aAAa;EAC9D;;;;EAKA,MAAM,cACJ,gBACA,UACuB;AACvB,UAAM,UAAwB,CAAC;AAE/B,eAAW,CAAC,QAAQ,KAAK,KAAK,WAAW;AACvC,UAAI,aAAa,eAAgB;AAEjC,YAAM,SAAS,MAAM,KAAK,SAAS,gBAAgB,UAAU,QAAQ;AACrE,cAAQ,KAAK,MAAM;IACrB;AAEA,WAAO;EACT;;;;EAKA,MAAM,aACJ,gBACA,gBACuB;AACvB,UAAM,gBAAgB,KAAK,UAAU,IAAI,cAAc;AACvD,QAAI,CAAC,eAAe;AAClB,YAAM,IAAI,MAAM,4BAA4B,cAAc,EAAE;IAC9D;AAEA,SAAK,KAAK,gBAAgB,EAAE,gBAAgB,eAAe,CAAC;AAE5D,UAAM,QAAQ,MAAM,cAAc,UAAU;AAC5C,UAAM,UAAwB,CAAC;AAE/B,eAAW,EAAE,GAAG,KAAK,OAAO;AAC1B,YAAM,SAAS,MAAM,KAAK,SAAS,gBAAgB,IAAI,cAAc;AACrE,cAAQ,KAAK,MAAM;IACrB;AAEA,SAAK,KAAK,kBAAkB;MAC1B;MACA;MACA,OAAO,QAAQ;MACf,YAAY,QAAQ,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE;MAC7C,QAAQ,QAAQ,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE;IAC5C,CAAC;AAED,WAAO;EACT;;;;;;;;EASA,MAAM,UAA8C;AAElD,QAAI,KAAK,gBAAgB;AACvB,WAAK,KAAK,gBAAgB,EAAE,QAAQ,uBAAuB,YAAW,oBAAI,KAAK,GAAE,YAAY,EAAE,CAAC;AAChG,aAAO,oBAAI,IAAI;IACjB;AAEA,SAAK,iBAAiB;AACtB,QAAI;AACF,YAAM,UAAU,oBAAI,IAA0B;AAC9C,YAAM,YAAY,MAAM,KAAK,KAAK,UAAU,KAAK,CAAC;AAElD,eAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AACzC,iBAAS,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AAC7C,gBAAM,SAAS,UAAU,CAAC;AAC1B,gBAAM,SAAS,UAAU,CAAC;AAC1B,gBAAM,MAAM,GAAG,MAAM,KAAK,MAAM;AAEhC,kBAAQ,IAAI,KAAK,MAAM,KAAK,aAAa,QAAQ,MAAM,CAAC;QAC1D;MACF;AAEA,aAAO;IACT,UAAA;AACE,WAAK,iBAAiB;IACxB;EACF;;;;EAKA,MAAM,aAAa,aAAoD;AACrE,WAAO,KAAK,WAAW,IAAI,WAAW;EACxC;;;;EAKA,MAAM,cACJ,UACA,YAC+B;AAC/B,WAAO,KAAK,WAAW,iBAAiB,UAAU,UAAU;EAC9D;;;;EAKA,MAAM,gBACJ,aACA,YACA,YACqB;AACrB,UAAM,YAAY,MAAM,KAAK,WAAW,IAAI,WAAW;AACvD,QAAI,CAAC,aAAa,UAAU,WAAW,cAAc,CAAC,UAAU,UAAU;AACxE,YAAM,IAAI,MAAM,wBAAwB;IAC1C;AAEA,UAAM,YAA2B;MAC/B,MAAM;MACN,QAAQ;MACR,gBAAgB;;MAChB,gBAAgB,UAAU,SAAS;MACnC,YAAW,oBAAI,KAAK,GAAE,YAAY;IACpC;AAEA,QAAI;AACF,UAAI,aAA8B;AAClC,UAAI,kBAAqC;AAEzC,UAAI,eAAe,WAAW,YAAY;AACxC,qBAAa;MACf,WAAW,eAAe,SAAS;AAEjC,mBAAW,CAAC,UAAU,EAAE,KAAK,OAAO,QAAQ,UAAU,WAAW,GAAG;AAClE,cAAI,aAAa,UAAU,SAAS,gBAAgB;AAClD,kBAAM,UAAU,KAAK,UAAU,IAAI,QAAsB;AACzD,gBAAI,SAAS;AACX,2BAAa,MAAM,QAAQ,QAAQ,EAAE;AACrC,gCAAkB;AAClB;YACF;UACF;QACF;MACF,WAAW,eAAe,UAAU;AAClC,cAAM,UAAU,KAAK,UAAU,IAAI,UAAU,SAAS,cAAc;AACpE,YAAI,SAAS;AACX,gBAAM,WAAW,UAAU,YAAY,UAAU,SAAS,cAAc;AACxE,cAAI,UAAU;AACZ,yBAAa,MAAM,QAAQ,QAAQ,QAAQ;AAC3C,8BAAkB,UAAU,SAAS;UACvC;QACF;MACF;AAEA,UAAI,CAAC,YAAY;AACf,cAAM,IAAI,MAAM,mDAAmD;MACrE;AAGA,YAAM,UAAU,MAAM,KAAK,SAAS,UAAU;AAC9C,YAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AAEnC,iBAAW,CAAC,UAAU,EAAE,KAAK,OAAO,QAAQ,UAAU,WAAW,GAAG;AAClE,cAAM,UAAU,KAAK,UAAU,IAAI,QAAsB;AACzD,YAAI,SAAS;AACX,gBAAM,QAAQ,SAAS,YAAY,EAAE;AACrC,oBAAU,SAAS,QAAsB,IAAI;QAC/C;MACF;AAEA,gBAAU,cAAc;AACxB,gBAAU,SAAS;AACnB,gBAAU,WAAW;AAErB,YAAM,KAAK,WAAW,IAAI,SAAS;AAEnC,WAAK,KAAK,eAAe,EAAE,WAAW,WAAW,CAAC;AAElD,aAAO;QACL,SAAS;QACT;QACA,UAAU;MACZ;IACF,SAAS,KAAK;AACZ,aAAO;QACL,SAAS;QACT;QACA,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;MACxD;IACF;EACF;;;;;;;;;;;;;EAcA,MAAM,SACJ,mBACA,gBACA,gBACA,SAKqB;AACrB,UAAM,YAA2B;MAC/B,MAAM;MACN,QAAQ;MACR;MACA;MACA,YAAW,oBAAI,KAAK,GAAE,YAAY;IACpC;AAEA,QAAI;AAEF,YAAM,gBAAgB,KAAK,UAAU,IAAI,cAAc;AACvD,UAAI,CAAC,eAAe;AAClB,cAAM,IAAI,MAAM,4BAA4B,cAAc,EAAE;MAC9D;AAGA,YAAM,gBAAgB,KAAK,UAAU,IAAI,cAAc;AACvD,UAAI,CAAC,eAAe;AAClB,cAAM,IAAI,MAAM,4BAA4B,cAAc,EAAE;MAC9D;AAGA,YAAM,cAAc,MAAM,KAAK,WAAW,IAAI,iBAAiB;AAC/D,YAAM,gBAAgB,aAAa,YAAY,cAAc;AAE7D,UAAI,CAAC,eAAe;AAClB,cAAM,IAAI,MAAM,4BAA4B,iBAAiB,EAAE;MACjE;AAEA,YAAM,aAAa,MAAM,cAAc,QAAQ,aAAa;AAC5D,UAAI,CAAC,YAAY;AACf,cAAM,IAAI,MAAM,0BAA0B,aAAa,EAAE;MAC3D;AAGA,YAAM,aAAa,KAAK;QACtB;QACA;QACA;QACA,aAAa;QACb,SAAS;MACX;AAGA,YAAM,cAAc,MAAM,cAAc,SAAS,UAAU;AAG3D,YAAM,kBAAkB,eAAe,KAAK,SAAS,GAAG,cAAc,IAAI,WAAW,EAAE;AACvF,YAAM,WAAW,MAAM,KAAK,SAAS,UAAU;AAC/C,YAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AAEnC,YAAM,gBAA+B;QACnC,aAAa;QACb,UAAU;QACV,UAAU;QACV,mBAAmB,aAAa;MAClC;AAEA,YAAM,YAA2B;QAC/B,SAAS;QACT,aAAa;QACb,aAAa,EAAE,CAAC,cAAc,GAAG,YAAY;QAC7C,UAAU,EAAE,CAAC,cAAc,GAAG,IAAI;QAClC,aAAa;QACb,QAAQ;QACR,YAAY;MACd;AAEA,YAAM,KAAK,WAAW,IAAI,SAAS;AAEnC,WAAK,KAAK,eAAe;QACvB;QACA;QACA;QACA;MACF,CAAC;AAED,aAAO;QACL,SAAS;QACT;QACA;QACA;QACA;MACF;IACF,SAAS,KAAK;AACZ,WAAK,KAAK,eAAe,EAAE,WAAW,OAAO,IAAI,CAAC;AAElD,aAAO;QACL,SAAS;QACT;QACA,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;MACxD;IACF;EACF;;;;EAKQ,iBACN,QACA,mBACA,gBACA,mBACA,eACU;AACV,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AAGnC,UAAM,SAAmB,KAAK,MAAM,KAAK,UAAU,MAAM,CAAC;AAG1D,QAAI,eAAe;AACjB,aAAO,OAAO,OAAO,MAAM,aAAa;IAC1C;AAGA,QAAI,CAAC,OAAO,KAAK,YAAY;AAC3B,aAAO,KAAK,aAAa,CAAC;IAC5B;AAGA,UAAM,cAAe,OAAO,KAAK,WAAW,mBAAmB,KAAiC,CAAC;AACjG,gBAAY,aAAa;MACvB,aAAa;MACb,UAAU;MACV,UAAU;MACV;IACF;AACA,WAAO,KAAK,WAAW,mBAAmB,IAAI;AAG9C,WAAO,KAAK,oBAAoBC,aAAa;AAE7C,WAAO;EACT;;;;;;;EAQA,MAAM,uBAAuB,UAAuC;AAClE,UAAM,SAAS,kBAAkB,QAAQ;AACzC,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,uBAAuB;IACzC;AAEA,UAAM,EAAE,cAAc,MAAM,IAAI;AAGhC,UAAM,cAAc,MAAM,KAAK,WAAW,IAAI,YAAY;AAC1D,QAAI,CAAC,aAAa;AAGhB;IACF;AAIA,QAAI,eAA2B;AAC/B,QAAI,MAAM,SAAS,SAAS,EAAG,gBAAe;aACrC,MAAM,SAAS,KAAK,EAAG,gBAAe;aACtC,MAAM,SAAS,QAAQ,EAAG,gBAAe;aACzC,MAAM,SAAS,MAAM,EAAG,gBAAe;aACvC,MAAM,SAAS,MAAM,EAAG,gBAAe;AAEhD,UAAM,eAAiC;MACrC,QAAQ,OAAO,WAAW;MAC1B,SAAS;MACT,UAAU;MACV,YAAW,oBAAI,KAAK,GAAE,YAAY;IACpC;AAGA,UAAM,gBAAgB,YAAY,qBAAqB,CAAC;AACxD,QAAI,cAAc,SAAS,KAAK;AAC9B,oBAAc,KAAK,YAAY;IACjC;AAEA,gBAAY,cAAc,YAAY,cAAc,KAAK;AACzD,gBAAY,oBAAoB;AAEhC,UAAM,KAAK,WAAW,IAAI,WAAW;AAErC,SAAK,KAAK,sBAAsB;MAC9B;MACA;MACA,cAAc,YAAY;IAC5B,CAAC;EACH;;;;;;;EAQA,MAAM,0BAA0B,UAA0C;AACxE,UAAM,SAAS,qBAAqB,QAAQ;AAC5C,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,0BAA0B;IAC5C;AAEA,UAAM,EAAE,QAAQ,OAAO,SAAS,IAAI;AAGpC,UAAM,YAAY,MAAM,KAAK,WAAW,IAAI,MAAM;AAClD,QAAI,CAAC,WAAW;AAGd;IACF;AAGA,UAAM,kBAA8B,YAAY;AAGhD,QAAI,CAAC,UAAU,OAAO;AACpB,gBAAU,QAAQ;QAChB,cAAc;QACd,oBAAoB,CAAC;QACrB,WAAW,UAAU,cAAc;QACnC,WAAW;QACX,cAAa,oBAAI,KAAK,GAAE,YAAY;MACtC;IACF;AAGA,cAAU,MAAM;AAChB,cAAU,MAAM,mBAAmB,eAAe,KAC/C,UAAU,MAAM,mBAAmB,eAAe,KAAK,KAAK;AAC/D,cAAU,MAAM,eAAc,oBAAI,KAAK,GAAE,YAAY;AAErD,UAAM,KAAK,WAAW,IAAI,SAAS;AAEnC,SAAK,KAAK,yBAAyB;MACjC;MACA,UAAU;MACV,SAAS;MACT,iBAAiB,UAAU,MAAM;IACnC,CAAC;EACH;;;;EAKA,MAAM,aAAa,aAAgD;AACjE,UAAM,QAAQ,MAAM,KAAK,WAAW,IAAI,WAAW;AACnD,WAAO,OAAO,SAAS;EACzB;;;;EAKA,MAAM,aAAa,aAAsC;AACvD,UAAM,QAAQ,MAAM,KAAK,WAAW,IAAI,WAAW;AACnD,WAAO,OAAO,cAAc;EAC9B;;;;EAKA,MAAM,gBAAgB,aAAsC;AAC1D,UAAM,QAAQ,MAAM,KAAK,WAAW,IAAI,WAAW;AACnD,WAAO,OAAO,OAAO,gBAAgB;EACvC;;;;EAKA,MAAM,UAAU,mBAAqD;AACnE,UAAM,YAAY,MAAM,KAAK,WAAW,KAAK;AAC7C,WAAO,UAAU;MACf,CAAC,UAAU,MAAM,YAAY,gBAAgB;IAC/C;EACF;;;;EAKA,UAAgB;AACd,QAAI,KAAK,eAAe;AACtB,oBAAc,KAAK,aAAa;IAClC;AACA,SAAK,UAAU,MAAM;EACvB;AACF;AC/yBO,IAAM,uBAAN,MAAqD;EAClD,SAAqC,oBAAI,IAAI;EAErD,MAAM,IAAI,aAAoD;AAC5D,WAAO,KAAK,OAAO,IAAI,WAAW,KAAK;EACzC;EAEA,MAAM,IAAI,OAAqC;AAC7C,SAAK,OAAO,IAAI,MAAM,aAAa,EAAE,GAAG,MAAM,CAAC;EACjD;EAEA,MAAM,OAAO,aAAoC;AAC/C,SAAK,OAAO,OAAO,WAAW;EAChC;EAEA,MAAM,OAAiC;AACrC,WAAO,MAAM,KAAK,KAAK,OAAO,OAAO,CAAC;EACxC;EAEA,MAAM,iBACJ,UACA,YAC+B;AAC/B,eAAW,SAAS,KAAK,OAAO,OAAO,GAAG;AACxC,UAAI,MAAM,YAAY,QAAQ,MAAM,YAAY;AAC9C,eAAO;MACT;IACF;AACA,WAAO;EACT;;;;EAKA,QAAc;AACZ,SAAK,OAAO,MAAM;EACpB;;;;EAKA,MAAM,mBACJ,aACA,cACe;AACf,UAAM,QAAQ,KAAK,OAAO,IAAI,WAAW;AACzC,QAAI,CAAC,MAAO;AAGZ,UAAM,gBAAgB,MAAM,qBAAqB,CAAC;AAClD,QAAI,cAAc,SAAS,KAAK;AAC9B,oBAAc,KAAK,YAAY;IACjC;AAEA,UAAM,cAAc,MAAM,cAAc,KAAK;AAC7C,UAAM,oBAAoB;AAC1B,SAAK,OAAO,IAAI,aAAa,KAAK;EACpC;;;;EAKA,MAAM,aAAa,aAAsC;AACvD,WAAO,KAAK,OAAO,IAAI,WAAW,GAAG,cAAc;EACrD;;;;EAKA,MAAM,UAAU,mBAAqD;AACnE,UAAM,QAAyB,CAAC;AAChC,eAAW,SAAS,KAAK,OAAO,OAAO,GAAG;AACxC,UAAI,MAAM,YAAY,gBAAgB,mBAAmB;AACvD,cAAM,KAAK,KAAK;MAClB;IACF;AACA,WAAO;EACT;AACF;AAMO,IAAM,qBAAN,MAAmD;EAChD,SAAqC,oBAAI,IAAI;EAC7C;EACA;EACA;EAMR,YACE,UACA,IAKA;AACA,SAAK,WAAW;AAChB,SAAK,KAAK;EACZ;;;;EAKA,MAAM,OAAsB;AAC1B,QAAI;AACF,YAAM,UAAU,MAAM,KAAK,GAAG,SAAS,KAAK,UAAU,OAAO;AAC7D,YAAM,OAAO,KAAK,MAAM,OAAO;AAC/B,WAAK,OAAO,MAAM;AAClB,iBAAW,SAAS,MAAM;AACxB,aAAK,OAAO,IAAI,MAAM,aAAa,KAAK;MAC1C;IACF,QAAQ;AAEN,WAAK,OAAO,MAAM;IACpB;EACF;;;;EAKQ,eAAqB;AAC3B,QAAI,KAAK,cAAc;AACrB,mBAAa,KAAK,YAAY;IAChC;AACA,SAAK,eAAe,WAAW,MAAM,KAAK,QAAQ,GAAG,GAAI;EAC3D;;;;EAKA,MAAM,UAAyB;AAC7B,UAAM,OAAO,MAAM,KAAK,KAAK,OAAO,OAAO,CAAC;AAC5C,UAAM,OAAO,KAAK,UAAU,MAAM,MAAM,CAAC;AAGzC,UAAM,MAAM,KAAK,SAAS,UAAU,GAAG,KAAK,SAAS,YAAY,GAAG,CAAC;AACrE,QAAI,KAAK;AACP,YAAM,KAAK,GAAG,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;IAC9C;AAEA,UAAM,KAAK,GAAG,UAAU,KAAK,UAAU,IAAI;EAC7C;EAEA,MAAM,IAAI,aAAoD;AAC5D,WAAO,KAAK,OAAO,IAAI,WAAW,KAAK;EACzC;EAEA,MAAM,IAAI,OAAqC;AAC7C,SAAK,OAAO,IAAI,MAAM,aAAa,EAAE,GAAG,MAAM,CAAC;AAC/C,SAAK,aAAa;EACpB;EAEA,MAAM,OAAO,aAAoC;AAC/C,SAAK,OAAO,OAAO,WAAW;AAC9B,SAAK,aAAa;EACpB;EAEA,MAAM,OAAiC;AACrC,WAAO,MAAM,KAAK,KAAK,OAAO,OAAO,CAAC;EACxC;EAEA,MAAM,iBACJ,UACA,YAC+B;AAC/B,eAAW,SAAS,KAAK,OAAO,OAAO,GAAG;AACxC,UAAI,MAAM,YAAY,QAAQ,MAAM,YAAY;AAC9C,eAAO;MACT;IACF;AACA,WAAO;EACT;;;;EAKA,MAAM,mBACJ,aACA,cACe;AACf,UAAM,QAAQ,KAAK,OAAO,IAAI,WAAW;AACzC,QAAI,CAAC,MAAO;AAGZ,UAAM,gBAAgB,MAAM,qBAAqB,CAAC;AAClD,QAAI,cAAc,SAAS,KAAK;AAC9B,oBAAc,KAAK,YAAY;IACjC;AAEA,UAAM,cAAc,MAAM,cAAc,KAAK;AAC7C,UAAM,oBAAoB;AAC1B,SAAK,OAAO,IAAI,aAAa,KAAK;AAClC,SAAK,aAAa;EACpB;;;;EAKA,MAAM,aAAa,aAAsC;AACvD,WAAO,KAAK,OAAO,IAAI,WAAW,GAAG,cAAc;EACrD;;;;EAKA,MAAM,UAAU,mBAAqD;AACnE,UAAM,QAAyB,CAAC;AAChC,eAAW,SAAS,KAAK,OAAO,OAAO,GAAG;AACxC,UAAI,MAAM,YAAY,gBAAgB,mBAAmB;AACvD,cAAM,KAAK,KAAK;MAClB;IACF;AACA,WAAO;EACT;AACF;AAiBO,SAAS,wBACd,KACA,SACgB;AAChB,SAAO;IACL,MAAM,IAAI,aAAoD;AAC5D,YAAM,OAAO,QAAQ,QAAQ,GAAG,GAAG,IAAI,WAAW,EAAE;AACpD,aAAO,OAAO,KAAK,MAAM,IAAI,IAAI;IACnC;IAEA,MAAM,IAAI,OAAqC;AAC7C,cAAQ,QAAQ,GAAG,GAAG,IAAI,MAAM,WAAW,IAAI,KAAK,UAAU,KAAK,CAAC;AAEpE,YAAM,WAAW,GAAG,GAAG;AACvB,YAAM,QAAQ,KAAK,MAAM,QAAQ,QAAQ,QAAQ,KAAK,IAAI;AAC1D,UAAI,CAAC,MAAM,SAAS,MAAM,WAAW,GAAG;AACtC,cAAM,KAAK,MAAM,WAAW;AAC5B,gBAAQ,QAAQ,UAAU,KAAK,UAAU,KAAK,CAAC;MACjD;IACF;IAEA,MAAM,OAAO,aAAoC;AAC/C,cAAQ,WAAW,GAAG,GAAG,IAAI,WAAW,EAAE;AAE1C,YAAM,WAAW,GAAG,GAAG;AACvB,YAAM,QAAQ,KAAK,MAAM,QAAQ,QAAQ,QAAQ,KAAK,IAAI;AAC1D,YAAM,WAAW,MAAM,OAAO,CAAC,OAAO,OAAO,WAAW;AACxD,cAAQ,QAAQ,UAAU,KAAK,UAAU,QAAQ,CAAC;IACpD;IAEA,MAAM,OAAiC;AACrC,YAAM,WAAW,GAAG,GAAG;AACvB,YAAM,QAAQ,KAAK,MAAM,QAAQ,QAAQ,QAAQ,KAAK,IAAI;AAC1D,YAAM,SAA0B,CAAC;AACjC,iBAAW,MAAM,OAAO;AACtB,cAAM,OAAO,QAAQ,QAAQ,GAAG,GAAG,IAAI,EAAE,EAAE;AAC3C,YAAI,MAAM;AACR,iBAAO,KAAK,KAAK,MAAM,IAAI,CAAC;QAC9B;MACF;AACA,aAAO;IACT;IAEA,MAAM,iBACJ,UACA,YAC+B;AAC/B,YAAM,SAAS,MAAM,KAAK,KAAK;AAC/B,aAAO,OAAO,KAAK,CAAC,MAAM,EAAE,YAAY,QAAQ,MAAM,UAAU,KAAK;IACvE;EACF;AACF;AC9OA,SAAS,kBAAkB,MAAoB;AAE7C,QAAM,eAAe;AACrB,MAAI,CAAC,aAAa,KAAK,IAAI,GAAG;AAC5B,UAAM,IAAI;MACR,uBAAuB,IAAI;IAC7B;EACF;AAEA,MAAI,KAAK,SAAS,IAAI;AACpB,UAAM,IAAI,MAAM,uBAAuB,IAAI,kCAAkC;EAC/E;AACF;AAmBO,IAAM,mBAAN,MAAiD;EAC9C;EACA;;;;;;;;EASR,YAAY,IAAgB,YAAY,yBAAyB;AAC/D,sBAAkB,SAAS;AAC3B,SAAK,KAAK;AACV,SAAK,YAAY;EACnB;;;;;;;EAQA,MAAM,OAAsB;AAC1B,UAAM,KAAK,GAAG,KAAK;mCACY,KAAK,SAAS;;;;;;;;;;;;;;;KAe5C;AAGD,UAAM,KAAK,GAAG,KAAK;uCACgB,KAAK,SAAS,gBAAgB,KAAK,SAAS;KAC9E;AAGD,UAAM,KAAK,GAAG,KAAK;uCACgB,KAAK,SAAS;WAC1C,KAAK,SAAS;KACpB;EACH;;;;EAKA,MAAM,IAAI,aAAoD;AAC5D,UAAM,MAAM,MAAM,KAAK,GACpB,QAAQ,iBAAiB,KAAK,SAAS,yBAAyB,EAChE,KAAK,WAAW,EAChB,MAAoB;AAEvB,WAAO,MAAM,KAAK,WAAW,GAAG,IAAI;EACtC;;;;EAKA,MAAM,IAAI,OAAqC;AAC7C,UAAM,cAAc,KAAK,UAAU,MAAM,WAAW;AACpD,UAAM,WAAW,KAAK,UAAU,MAAM,QAAQ;AAC9C,UAAM,WAAW,MAAM,WAAW,KAAK,UAAU,MAAM,QAAQ,IAAI;AACnE,UAAM,aAAa,MAAM,aAAa,KAAK,UAAU,MAAM,UAAU,IAAI;AACzE,UAAM,oBAAoB,MAAM,oBAC5B,KAAK,UAAU,MAAM,iBAAiB,IACtC;AACJ,UAAM,QAAQ,MAAM,QAAQ,KAAK,UAAU,MAAM,KAAK,IAAI;AAE1D,UAAM,KAAK,GACR;MACC,eAAe,KAAK,SAAS;;;;;;;;;;;;;;IAc/B,EACC;MACC,MAAM;MACN,MAAM;MACN;MACA;MACA,MAAM;MACN,MAAM;MACN;MACA;MACA,MAAM,cAAc;MACpB;MACA;IACF,EACC,IAAI;EACT;;;;EAKA,MAAM,OAAO,aAAoC;AAC/C,UAAM,KAAK,GACR,QAAQ,eAAe,KAAK,SAAS,yBAAyB,EAC9D,KAAK,WAAW,EAChB,IAAI;EACT;;;;EAKA,MAAM,OAAiC;AACrC,UAAM,SAAS,MAAM,KAAK,GACvB,QAAQ,iBAAiB,KAAK,SAAS,2BAA2B,EAClE,IAAkB;AAErB,WAAO,OAAO,QAAQ,IAAI,CAAC,QAAQ,KAAK,WAAW,GAAG,CAAC;EACzD;;;;;;;;EASA,MAAM,iBACJ,UACA,YAC+B;AAE/B,UAAM,SAAS,MAAM,KAAK,GACvB;MACC,iBAAiB,KAAK,SAAS;;IAEjC,EACC,KAAK,KAAK,QAAQ,IAAI,UAAU,EAChC,MAAoB;AAEvB,WAAO,SAAS,KAAK,WAAW,MAAM,IAAI;EAC5C;;;;;;;EAQA,MAAM,cAAc,SAAgD;AAClE,UAAM,MAAM,MAAM,KAAK,GACpB,QAAQ,iBAAiB,KAAK,SAAS,qBAAqB,EAC5D,KAAK,OAAO,EACZ,MAAoB;AAEvB,WAAO,MAAM,KAAK,WAAW,GAAG,IAAI;EACtC;;;;EAKA,MAAM,QAAyB;AAC7B,UAAM,SAAS,MAAM,KAAK,GACvB,QAAQ,iCAAiC,KAAK,SAAS,EAAE,EACzD,MAAyB;AAE5B,WAAO,QAAQ,SAAS;EAC1B;;;;;;;EAQA,MAAM,aACJ,QAC0B;AAC1B,UAAM,SAAS,MAAM,KAAK,GACvB;MACC,iBAAiB,KAAK,SAAS;IACjC,EACC,KAAK,MAAM,EACX,IAAkB;AAErB,WAAO,OAAO,QAAQ,IAAI,CAAC,QAAQ,KAAK,WAAW,GAAG,CAAC;EACzD;;;;;;EAOA,MAAM,QAAuB;AAC3B,UAAM,KAAK,GAAG,QAAQ,eAAe,KAAK,SAAS,EAAE,EAAE,IAAI;EAC7D;;;;;;;EAQA,MAAM,mBACJ,aACA,cACe;AACf,UAAM,QAAQ,MAAM,KAAK,IAAI,WAAW;AACxC,QAAI,CAAC,OAAO;AACV;IACF;AAGA,UAAM,gBAAgB,MAAM,qBAAqB,CAAC;AAClD,QAAI,cAAc,SAAS,KAAK;AAC9B,oBAAc,KAAK,YAAY;IACjC;AAEA,UAAM,cAAc,MAAM,cAAc,KAAK;AAC7C,UAAM,oBAAoB;AAE1B,UAAM,KAAK,IAAI,KAAK;EACtB;;;;EAKA,MAAM,aAAa,aAAsC;AACvD,UAAM,SAAS,MAAM,KAAK,GACvB,QAAQ,2BAA2B,KAAK,SAAS,yBAAyB,EAC1E,KAAK,WAAW,EAChB,MAA+B;AAElC,WAAO,QAAQ,eAAe;EAChC;;;;EAKA,MAAM,UAAU,mBAAqD;AACnE,UAAM,SAAS,MAAM,KAAK,GACvB;MACC,iBAAiB,KAAK,SAAS;;;IAGjC,EACC,KAAK,iBAAiB,EACtB,IAAkB;AAErB,WAAO,OAAO,QAAQ,IAAI,CAAC,QAAQ,KAAK,WAAW,GAAG,CAAC;EACzD;;;;EAKQ,WAAW,KAAkC;AACnD,UAAM,QAAuB;MAC3B,aAAa,IAAI;MACjB,SAAS,IAAI;MACb,aAAa,KAAK,MAAM,IAAI,YAAY;MACxC,UAAU,KAAK,MAAM,IAAI,SAAS;MAClC,aAAa,IAAI;MACjB,QAAQ,IAAI;MACZ,UAAU,IAAI,WACT,KAAK,MAAM,IAAI,QAAQ,IACxB;IACN;AAGA,QAAI,IAAI,aAAa;AACnB,YAAM,aAAa,KAAK,MAAM,IAAI,WAAW;IAC/C;AACA,QAAI,IAAI,cAAc,GAAG;AACvB,YAAM,aAAa,IAAI;IACzB;AACA,QAAI,IAAI,oBAAoB;AAC1B,YAAM,oBAAoB,KAAK,MAAM,IAAI,kBAAkB;IAC7D;AAEA,QAAI,IAAI,OAAO;AACb,YAAM,QAAQ,KAAK,MAAM,IAAI,KAAK;IACpC;AAEA,WAAO;EACT;AACF;AC3WO,IAAe,sBAAf,MAA8D;;;;EA8CzD,aAAqB;AAC7B,UAAM,YAAY,KAAK,IAAI;AAC3B,UAAM,SAASA,aAAa,EAAE,MAAM,GAAG,EAAE,CAAC;AAC1C,WAAO,GAAG,SAAS,IAAI,MAAM;EAC/B;AACF;AAKO,IAAM,wBAAN,cAAoC,oBAAoB;EACpD;EACA;EAED,QAAkC,oBAAI,IAAI;EAC1C,SAAsC,oBAAI,IAAI;EAEtD,YAAY,WAAuB,UAAU,cAAc,gBAAgB;AACzE,UAAM;AACN,SAAK,WAAW;AAChB,SAAK,cAAc;EACrB;EAEA,MAAM,cAAgC;AACpC,WAAO;EACT;EAEA,MAAM,QAAQ,SAA2C;AACvD,UAAM,QAAQ,KAAK,MAAM,IAAI,OAAO;AACpC,WAAO,OAAO,QAAQ;EACxB;EAEA,MAAM,UAAU,SAIW;AACzB,QAAI,QAAQ,MAAM,KAAK,KAAK,MAAM,OAAO,CAAC;AAG1C,QAAI,SAAS,OAAO;AAClB,YAAM,YAAY,IAAI,KAAK,QAAQ,KAAK;AACxC,cAAQ,MAAM,OAAO,CAAC,MAAM,IAAI,KAAK,EAAE,SAAS,IAAI,SAAS;IAC/D;AAGA,UAAM,KAAK,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAC;AAGtF,UAAM,SAAS,SAAS,UAAU;AAClC,UAAM,QAAQ,SAAS,SAAS,MAAM;AACtC,YAAQ,MAAM,MAAM,QAAQ,SAAS,KAAK;AAE1C,WAAO;EACT;EAEA,MAAM,SAAS,MAAgB,SAAmC;AAChE,UAAM,KAAK,WAAW,KAAK,WAAW;AACtC,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AAEnC,UAAM,WAAW,KAAK,MAAM,IAAI,EAAE;AAElC,SAAK,MAAM,IAAI,IAAI;MACjB;MACA;MACA,WAAW;MACX,WAAW,UAAU,aAAa;IACpC,CAAC;AAED,WAAO;EACT;EAEA,MAAM,WAAW,SAAmC;AAClD,UAAM,UAAU,KAAK,MAAM,IAAI,OAAO;AACtC,SAAK,MAAM,OAAO,OAAO;AACzB,SAAK,OAAO,OAAO,OAAO;AAC1B,WAAO;EACT;EAEA,MAAM,UAAU,SAA0C;AACxD,WAAO,KAAK,OAAO,IAAI,OAAO,KAAK,CAAC;EACtC;EAEA,MAAM,gBAAgB,SAAyC;AAC7D,UAAM,QAAQ,KAAK,MAAM,IAAI,OAAO;AACpC,WAAO,OAAO,aAAa;EAC7B;;;;EAKA,UAAU,SAAiB,QAA8B;AACvD,SAAK,OAAO,IAAI,SAAS,MAAM;EACjC;;;;EAKA,QAAc;AACZ,SAAK,MAAM,MAAM;AACjB,SAAK,OAAO,MAAM;EACpB;;;;EAKA,QAAgB;AACd,WAAO,KAAK,MAAM;EACpB;AACF;ACjHO,IAAM,yBAAN,cAAqC,MAAM;EAChD,YACkB,IACA,QAChB;AACA,UAAM,wBAAwB,EAAE,MAAM,MAAM,EAAE;AAH9B,SAAA,KAAA;AACA,SAAA,SAAA;AAGhB,SAAK,OAAO;EACd;AACF;AAeA,SAAS,oBAAoB,IAAoB;AAE/C,MAAI;AACJ,MAAI;AACF,cAAU,mBAAmB,EAAE;EACjC,QAAQ;AAEN,UAAM,IAAI,uBAAuB,IAAI,sBAAsB;EAC7D;AAGA,MAAI,QAAQ,SAAS,IAAI,GAAG;AAC1B,UAAM,IAAI,uBAAuB,IAAI,yBAAyB;EAChE;AAGA,MAAI,QAAQ,WAAW,GAAG,GAAG;AAC3B,UAAM,IAAI,uBAAuB,IAAI,2BAA2B;EAClE;AAGA,MAAI,4BAA4B,KAAK,OAAO,GAAG;AAC7C,UAAM,IAAI,uBAAuB,IAAI,6BAA6B;EACpE;AAGA,MAAI,QAAQ,SAAS,IAAI,GAAG;AAC1B,UAAM,IAAI,uBAAuB,IAAI,uBAAuB;EAC9D;AAGA,SAAO,mBAAmB,EAAE;AAC9B;AAGA,IAAM,qBAAqB;AAKpB,IAAM,sBAAN,cAAkC,oBAAoB;EAClD;EACA;EAED;EACA;EACA;EAER,YAAY,QAA2B;AACrC,UAAM;AACN,SAAK,WAAW,OAAO;AACvB,SAAK,cAAc,OAAO;AAC1B,SAAK,SAAS;AACd,SAAK,UAAU,OAAO,SAAS,WAAW,MAAM,KAAK,UAAU;AAC/D,SAAK,YAAY,OAAO,WAAW;EACrC;;;;;;EAOA,MAAc,iBAAiB,KAAa,MAAuC;AACjF,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,YAAY,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,SAAS;AAErE,QAAI;AACF,YAAM,WAAW,MAAM,KAAK,QAAQ,KAAK;QACvC,GAAG;QACH,QAAQ,WAAW;MACrB,CAAC;AACD,aAAO;IACT,UAAA;AACE,mBAAa,SAAS;IACxB;EACF;;;;EAKQ,aAAa,aAA8C;AACjE,UAAM,UAAkC,CAAC;AAEzC,QAAI,aAAa;AACf,cAAQ,cAAc,IAAI;IAC5B;AAEA,QAAI,KAAK,OAAO,MAAM;AACpB,cAAQ,KAAK,OAAO,KAAK,MAAM;QAC7B,KAAK;AACH,kBAAQ,eAAe,IAAI,UAAU,KAAK,OAAO,KAAK,KAAK;AAC3D;QACF,KAAK;AACH,kBAAQ,KAAK,OAAO,KAAK,UAAU,WAAW,IAAI,KAAK,OAAO,KAAK;AACnE;QACF,KAAK;AACH,kBAAQ,eAAe,IAAI,SAAS,KAAK,OAAO,KAAK,KAAK;AAC1D;MACJ;IACF;AAEA,WAAO;EACT;;;;;;;EAQQ,SAAS,UAAkB,IAAqB;AACtD,QAAI,MAAM,GAAG,KAAK,OAAO,OAAO,GAAG,QAAQ;AAC3C,QAAI,IAAI;AAEN,YAAM,SAAS,oBAAoB,EAAE;AACrC,YAAM,GAAG,GAAG,IAAI,MAAM;IACxB;AACA,WAAO;EACT;EAEA,MAAM,cAAgC;AACpC,QAAI;AACF,YAAM,WAAW,KAAK,OAAO,UAAU,UAAU,KAAK,OAAO,UAAU;AACvE,YAAM,WAAW,MAAM,KAAK;QAC1B,KAAK,SAAS,QAAQ;QACtB;UACE,QAAQ;UACR,SAAS,KAAK,aAAa;QAC7B;MACF;AACA,aAAO,SAAS;IAClB,QAAQ;AACN,aAAO;IACT;EACF;EAEA,MAAM,QAAQ,SAA2C;AACvD,QAAI;AACF,YAAM,WAAW,MAAM,KAAK;QAC1B,KAAK,SAAS,KAAK,OAAO,UAAU,KAAK,OAAO;QAChD;UACE,QAAQ;UACR,SAAS,KAAK,aAAa;QAC7B;MACF;AAEA,UAAI,CAAC,SAAS,IAAI;AAChB,YAAI,SAAS,WAAW,IAAK,QAAO;AACpC,cAAM,IAAI,MAAM,QAAQ,SAAS,MAAM,KAAK,SAAS,UAAU,EAAE;MACnE;AAEA,YAAM,OAAO,MAAM,SAAS,KAAK;AACjC,aAAO,KAAK,OAAO,cAAc,MAC7B,KAAK,OAAO,aAAa,IAAI,IAAI,IACjC;IACN,SAAS,KAAK;AACZ,cAAQ,MAAM,sBAAsB,OAAO,KAAK,GAAG;AACnD,aAAO;IACT;EACF;EAEA,MAAM,UAAU,SAIW;AACzB,UAAM,MAAM,IAAI,IAAI,KAAK,SAAS,KAAK,OAAO,UAAU,IAAI,CAAC;AAE7D,QAAI,SAAS,OAAO;AAClB,UAAI,aAAa,IAAI,SAAS,OAAO,QAAQ,KAAK,CAAC;IACrD;AACA,QAAI,SAAS,QAAQ;AACnB,UAAI,aAAa,IAAI,UAAU,OAAO,QAAQ,MAAM,CAAC;IACvD;AACA,QAAI,SAAS,OAAO;AAClB,UAAI,aAAa,IAAI,SAAS,QAAQ,KAAK;IAC7C;AAEA,UAAM,WAAW,MAAM,KAAK;MAC1B,IAAI,SAAS;MACb;QACE,QAAQ;QACR,SAAS,KAAK,aAAa;MAC7B;IACF;AAEA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,MAAM,QAAQ,SAAS,MAAM,KAAK,SAAS,UAAU,EAAE;IACnE;AAEA,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,WAAO,KAAK,OAAO,cAAc,OAC7B,KAAK,OAAO,aAAa,KAAK,IAAI,IAClC;EACN;EAEA,MAAM,SAAS,MAAgB,SAAmC;AAChE,QAAI,SAAS;AAEX,YAAM,OAAO,KAAK,OAAO,cAAc,SACnC,KAAK,OAAO,aAAa,OAAO,IAAI,IACpC;AAEJ,YAAM,WAAW,MAAM,KAAK;QAC1B,KAAK,SAAS,KAAK,OAAO,UAAU,QAAQ,OAAO;QACnD;UACE,QAAQ;UACR,SAAS,KAAK,aAAa,kBAAkB;UAC7C,MAAM,KAAK,UAAU,IAAI;QAC3B;MACF;AAEA,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,IAAI,MAAM,QAAQ,SAAS,MAAM,KAAK,SAAS,UAAU,EAAE;MACnE;AAEA,aAAO;IACT,OAAO;AAEL,YAAM,OAAO,KAAK,OAAO,cAAc,SACnC,KAAK,OAAO,aAAa,OAAO,IAAI,IACpC;AAEJ,YAAM,WAAW,MAAM,KAAK;QAC1B,KAAK,SAAS,KAAK,OAAO,UAAU,MAAM;QAC1C;UACE,QAAQ;UACR,SAAS,KAAK,aAAa,kBAAkB;UAC7C,MAAM,KAAK,UAAU,IAAI;QAC3B;MACF;AAEA,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,IAAI,MAAM,QAAQ,SAAS,MAAM,KAAK,SAAS,UAAU,EAAE;MACnE;AAEA,YAAM,OAAO,MAAM,SAAS,KAAK;AACjC,aAAO,KAAK,OAAO,cAAc,YAC7B,KAAK,OAAO,aAAa,UAAU,IAAI,IACtC,KAAwB;IAC/B;EACF;EAEA,MAAM,WAAW,SAAmC;AAClD,UAAM,WAAW,MAAM,KAAK;MAC1B,KAAK,SAAS,KAAK,OAAO,UAAU,QAAQ,OAAO;MACnD;QACE,QAAQ;QACR,SAAS,KAAK,aAAa;MAC7B;IACF;AAEA,WAAO,SAAS;EAClB;EAEA,MAAM,UAAU,SAA0C;AACxD,QAAI,CAAC,KAAK,OAAO,UAAU,QAAQ;AACjC,aAAO,CAAC;IACV;AAEA,QAAI;AACF,YAAM,WAAW,MAAM,KAAK;QAC1B,KAAK,SAAS,KAAK,OAAO,UAAU,QAAQ,OAAO;QACnD;UACE,QAAQ;UACR,SAAS,KAAK,aAAa;QAC7B;MACF;AAEA,UAAI,CAAC,SAAS,IAAI;AAChB,eAAO,CAAC;MACV;AAEA,aAAQ,MAAM,SAAS,KAAK;IAC9B,QAAQ;AACN,aAAO,CAAC;IACV;EACF;EAEA,MAAM,gBAAgB,SAAyC;AAC7D,QAAI;AACF,YAAM,WAAW,MAAM,KAAK;QAC1B,KAAK,SAAS,KAAK,OAAO,UAAU,KAAK,OAAO;QAChD;UACE,QAAQ;UACR,SAAS,KAAK,aAAa;QAC7B;MACF;AAEA,UAAI,CAAC,SAAS,IAAI;AAChB,eAAO;MACT;AAEA,aAAO,SAAS,QAAQ,IAAI,eAAe;IAC7C,QAAQ;AACN,aAAO;IACT;EACF;AACF;AAKO,SAAS,qBACd,SACA,QACqB;AACrB,SAAO,IAAI,oBAAoB;IAC7B,UAAU;IACV,aAAa;IACb;IACA,WAAW;MACT,MAAM;MACN,KAAK;MACL,QAAQ;MACR,QAAQ;MACR,QAAQ;MACR,QAAQ;MACR,QAAQ;IACV;IACA,MAAM,SAAS,EAAE,MAAM,WAAW,OAAO,OAAO,IAAI;IACpD,cAAc;MACZ,MAAM,CAAC,SAAS;AACd,cAAM,WAAW;AACjB,eAAO,SAAS,WAAW,IAAI,CAAC,OAAO;UACrC,IAAI,EAAE;UACN,MAAM,EAAE;UACR,WAAW,EAAE;QACf,EAAE;MACJ;MACA,KAAK,CAAC,SAAU,KAA4B;MAC5C,WAAW,CAAC,SAAU,KAAwB;IAChD;EACF,CAAC;AACH;AAKO,SAAS,iBACd,SACA,QACqB;AACrB,SAAO,IAAI,oBAAoB;IAC7B,UAAU;IACV,aAAa;IACb;IACA,WAAW;MACT,MAAM;MACN,KAAK;MACL,QAAQ;MACR,QAAQ;MACR,QAAQ;MACR,QAAQ;MACR,QAAQ;IACV;IACA,MAAM,SAAS,EAAE,MAAM,UAAU,OAAO,OAAO,IAAI;EACrD,CAAC;AACH;AC7VO,SAAS,kBAAkB,IAA2B;AAC3D,SAAO;IACL,MAAM;IACN,cAAc;IACd,MAAM;MACJ,MAAM,GAAG,KAAK;MACd,aAAa,GAAG,KAAK;MACrB,aAAa,GAAG,KAAK;MACrB,UAAU,GAAG,KAAK;MAClB,WAAW,GAAG,KAAK;MACnB,aAAa,GAAG,KAAK;MACrB,eAAe,GAAG,KAAK,iBAAiB;MACxC,eAAe,GAAG,KAAK,iBAAiB;MACxC,2BAA2B,GAAG,KAAK,6BAA6B;MAChE,qBAAqB,GAAG,KAAK,uBAAuB,CAAC;MACrD,sBAAsB,CAAC;MACvB,MAAM,GAAG,KAAK,QAAQ,CAAC;MACvB,SAAS,GAAG,KAAK,WAAW;MAC5B,mBAAmB,GAAG,KAAK,qBAAqB;MAChD,gBAAgB,GAAG,KAAK;MACxB,YAAY,GAAG,KAAK,cAAc,CAAC;IACrC;EACF;AACF;AAKO,SAAS,kBAAkB,MAAgB,UAAgC;AAChF,QAAM,OAAO,KAAK;AAClB,QAAM,OAAO,YAAY,KAAK,KAAK,QAAQ,iBAAiB,GAAG;AAE/D,SAAO;IACL;IACA,QAAQ,GAAG,IAAI;IACf,MAAM;MACJ,MAAM,KAAK;MACX,aAAa,KAAK;MAClB,aAAa,KAAK,eAAe;;MACjC,UAAU,KAAK;MACf,WAAW,KAAK;MAChB,aAAa,KAAK,eAAe;;MACjC,eAAe,KAAK;MACpB,eAAe,KAAK;MACpB,2BAA2B,KAAK;MAChC,qBAAqB,KAAK;;MAE1B,gBAAgB,KAAK;MACrB,MAAM,KAAK;MACX,SAAS,KAAK;MACd,mBAAmB,KAAK;MACxB,YAAY,KAAK;IACnB;EACF;AACF;AAMO,IAAM,qBAAN,cAAiC,oBAAoB;EACjD,WAAuB;EACvB,cAAc;EAEf;EAER,YAAY,QAA2B;AACrC,UAAM;AACN,SAAK,SAAS;EAChB;EAEA,MAAM,cAAgC;AACpC,WAAO,KAAK,OAAO,YAAY;EACjC;EAEA,MAAM,QAAQ,SAA2C;AACvD,UAAM,KAAK,MAAM,KAAK,OAAO,aAAa,OAAO;AACjD,QAAI,CAAC,GAAI,QAAO;AAChB,WAAO,kBAAkB,EAAE;EAC7B;EAEA,MAAM,UAAU,SAIW;AACzB,UAAM,aAAa,MAAM,KAAK,OAAO,cAAc;AAEnD,QAAI,QAAQ,WAAW,IAAI,CAAC,QAAQ;MAClC,IAAI,GAAG;MACP,MAAM,kBAAkB,EAAE;MAC1B,YAAW,oBAAI,KAAK,GAAE,YAAY;;IACpC,EAAE;AAGF,UAAM,SAAS,SAAS,UAAU;AAClC,UAAM,QAAQ,SAAS,SAAS,MAAM;AACtC,YAAQ,MAAM,MAAM,QAAQ,SAAS,KAAK;AAE1C,WAAO;EACT;EAEA,MAAM,SAAS,MAAgB,SAAmC;AAChE,UAAM,SAAS,kBAAkB,MAAM,OAAO;AAC9C,WAAO,KAAK,OAAO,cAAc,MAAM;EACzC;EAEA,MAAM,WAAW,SAAmC;AAClD,WAAO,KAAK,OAAO,gBAAgB,OAAO;EAC5C;EAEA,MAAM,UAAU,SAA0C;AACxD,UAAM,SAAS,MAAM,KAAK,OAAO,UAAU,OAAO;AAClD,QAAI,CAAC,OAAQ,QAAO,CAAC;AAErB,WAAO,CAAC;MACN,MAAM;MACN,MAAM;MACN,MAAM;MACN,UAAU;IACZ,CAAC;EACH;EAEA,MAAM,gBAAgB,SAAyC;AAE7D,UAAM,OAAO,MAAM,KAAK,OAAO,aAAa,OAAO;AACnD,WAAO,QAAO,oBAAI,KAAK,GAAE,YAAY,IAAI;EAC3C;;;;EAKA,MAAM,SAAS,SAAmD;AAChE,QAAI,CAAC,KAAK,OAAO,mBAAmB;AAClC,aAAO;IACT;AACA,WAAO,KAAK,OAAO,kBAAkB,OAAO;EAC9C;;;;EAKA,MAAM,cAA6D;AACjE,QAAI,CAAC,KAAK,OAAO,aAAa;AAC5B,aAAO;IACT;AACA,WAAO,KAAK,OAAO,YAAY;EACjC;;;;EAKA,MAAM,cAAc,aAAqB,UAAoC;AAC3E,QAAI,CAAC,KAAK,OAAO,eAAe;AAC9B,aAAO;IACT;AACA,UAAM,KAAK,OAAO,cAAc,aAAa,QAAQ;AACrD,WAAO;EACT;AACF;AAKO,SAAS,qBAKd;AACA,QAAM,aAAa,oBAAI,IAAyB;AAChD,QAAM,UAAU,oBAAI,IAAwB;AAC5C,QAAM,QAAQ,oBAAI,IAA8B;AAChD,QAAM,uBAAyE,CAAC;AAEhF,SAAO;IACL;IACA;IACA;IACA;IAEA,MAAM,gBAAgB;AACpB,aAAO,MAAM,KAAK,WAAW,OAAO,CAAC;IACvC;IAEA,MAAM,aAAa,MAAM;AACvB,aAAO,WAAW,IAAI,IAAI,KAAK;IACjC;IAEA,MAAM,cAAc,WAAW;AAC7B,iBAAW,IAAI,UAAU,MAAM,SAAS;AAExC,UAAI,CAAC,MAAM,IAAI,UAAU,IAAI,GAAG;AAC9B,cAAM,IAAI,UAAU,MAAM;UACxB,cAAa,oBAAI,KAAK,GAAE,YAAY;UACpC,WAAW;UACX,cAAc;QAChB,CAAC;MACH;AACA,aAAO,UAAU;IACnB;IAEA,MAAM,gBAAgB,MAAM;AAC1B,YAAM,UAAU,WAAW,IAAI,IAAI;AACnC,iBAAW,OAAO,IAAI;AACtB,cAAQ,OAAO,IAAI;AACnB,YAAM,OAAO,IAAI;AACjB,aAAO;IACT;IAEA,MAAM,UAAU,MAAM;AACpB,aAAO,QAAQ,IAAI,IAAI,KAAK;IAC9B;IAEA,MAAM,cAAc;AAClB,aAAO;IACT;IAEA,MAAM,kBAAkB,MAAM;AAC5B,aAAO,MAAM,IAAI,IAAI,KAAK;IAC5B;IAEA,MAAM,cAAc;AAClB,aAAO,IAAI,IAAI,KAAK;IACtB;IAEA,MAAM,cAAc,aAAa,UAAU;AACzC,2BAAqB,KAAK,EAAE,aAAa,SAAS,CAAC;IACrD;EACF;AACF;ACzTO,SAAS,gBACd,UACA,QAC0B;AAC1B,0BAAwB,iBAAiB;AACzC,MAAI,CAAC,SAAU,QAAO;AAEtB,QAAM,EAAE,MAAM,IAAI;AAGlB,MAAI;AACJ,MAAI;AACF,aAAS,IAAI,IAAI,MAAM,EAAE,EAAE;EAC7B,QAAQ;AACN,WAAO;EACT;AAEA,QAAM,iBAAiB;IACrB,QAAQ,MAAM,iBAAiB,IAAI,MAAM;IACzC,MAAM;EACR;AAEA,MAAI,CAAC,eAAe,SAAS,QAAQ,GAAG;AACtC,WAAO;EACT;AAEA,SAAO;IACL,SAAS,QAAQ,MAAM,iBAAiB,IAAI,MAAM;IAClD,SAAS,CAAC,MAAM,EAAE;IAClB,OAAO;MACL;QACE,KAAK;QACL,MAAM;QACN,MAAM,MAAM;MACd;MACA;QACE,KAAK;QACL,MAAM;QACN,MAAM,MAAM;;MACd;IACF;EACF;AACF;ACtBO,SAAS,wBAAwB,SAA4C;AAClF,0BAAwB,yBAAyB;AAEjD,QAAM,SAAS,QAAQ,SAAS,GAAG,IAAI,QAAQ,MAAM,GAAG,EAAE,IAAI;AAE9D,SAAO;IACL,OAAO;MACL;QACE,KAAK;QACL,MAAM,GAAG,MAAM;MACjB;MACA;QACE,KAAK;QACL,MAAM,GAAG,MAAM;MACjB;IACF;EACF;AACF;AASO,SAAS,eACd,QACA,UAAyB,OACP;AAClB,0BAAwB,gBAAgB;AACxC,SAAO;IACL;IACA,UAAU;MACR,MAAM;MACN,SAAS;IACX;IACA,WAAW;MACT;IACF;IACA,UAAU;MACR,SAAS,CAAC;MACV,UAAU,CAAC;IACb;IACA,mBAAmB;IACnB,OAAO;MACL,OAAO;QACL,OAAO;;MACT;IACF;IACA,UAAU;MACR,UAAU,OAAO,MAAM;MACvB,iBAAiB,OAAO,MAAM;IAChC;EACF;AACF;AC1FO,SAAS,YAAY,QAA0C;AACpE,0BAAwB,aAAa;AAErC,SAAO;IACL,GAAG,OAAO;IACV,YAAY,OAAO,MAAM,UAAU,KAAK;MACtC;MACA;IACF;EACF;AACF;ACPO,IAAM,8BAA8B;EACzC;EACA;IACE,YAAY;IACZ,UAAU,EAAE,OAAO,sBAAsB;EAC3C;AACF;AAsBO,SAAS,mBACd,iBACA,WACA,SACA,SAQc;AACd,QAAM,UAAU,MAAM,QAAQ,SAAS,IAAI,YAAY,CAAC,SAAS;AAEjE,SAAO;IACL,YAAY,CAAC,GAAG,2BAA2B;IAC3C,IAAI,mBAAmB,OAAO;IAC9B,MAAM;IACN,OAAO;IACP,QAAQ,QAAQ,WAAW,IAAI,QAAQ,CAAC,IAAK;IAC7C,SAAS,SAAS;IAClB,UAAU,SAAS;IACnB,YAAW,oBAAI,KAAK,GAAE,YAAY;IAClC,IAAI,SAAS;EACf;AACF;AAOO,SAAS,kBAAkB,UAMzB;AACP,MAAI,CAAC,YAAY,OAAO,aAAa,SAAU,QAAO;AAEtD,QAAM,MAAM;AAGZ,MAAI,IAAI,SAAS,OAAQ,QAAO;AAGhC,MAAI,OAAO,IAAI,UAAU,SAAU,QAAO;AAC1C,MAAI,OAAO,IAAI,OAAO,SAAU,QAAO;AACvC,MAAI,CAAC,IAAI,OAAQ,QAAO;AAGxB,QAAM,UAAU,MAAM,QAAQ,IAAI,MAAM,IACpC,IAAI,OAAO,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ,IAC3D,OAAO,IAAI,WAAW,WACpB,CAAC,IAAI,MAAM,IACX,CAAC;AAEP,MAAI,QAAQ,WAAW,EAAG,QAAO;AAEjC,SAAO;IACL,SAAS,IAAI;IACb,WAAW;IACX,SAAS,OAAO,IAAI,YAAY,WAAW,IAAI,UAAU;IACzD,UAAU,OAAO,IAAI,aAAa,WAAW,IAAI,WAAW;IAC5D,YAAY,IAAI;EAClB;AACF;AAKO,SAAS,qBAAqB,UAGnC;AACA,QAAM,SAAS,kBAAkB,QAAQ;AACzC,MAAI,CAAC,QAAQ;AACX,WAAO;MACL,OAAO;MACP,OAAO;IACT;EACF;AAGA,aAAW,UAAU,OAAO,WAAW;AACrC,QAAI;AACF,UAAI,IAAI,MAAM;IAChB,QAAQ;AACN,aAAO;QACL,OAAO;QACP,OAAO,kCAAkC,MAAM;MACjD;IACF;EACF;AAGA,MAAI,OAAO,WAAW,OAAO,QAAQ,SAAS,KAAM;AAClD,WAAO;MACL,OAAO;MACP,OAAO;IACT;EACF;AAEA,SAAO,EAAE,OAAO,KAAK;AACvB;AAmBO,SAAS,oBACd,cACA,eACA,SACA,SAMe;AACf,SAAO;IACL,YAAY,CAAC,GAAG,2BAA2B;IAC3C,IAAI,mBAAmB,OAAO;IAC9B,MAAM;IACN,OAAO;IACP,QAAQ;IACR,SAAS,SAAS;IAClB,YAAW,oBAAI,KAAK,GAAE,YAAY;EACpC;AACF;AAOO,SAAS,mBAAmB,UAK1B;AACP,MAAI,CAAC,YAAY,OAAO,aAAa,SAAU,QAAO;AAEtD,QAAM,MAAM;AAGZ,MAAI,IAAI,SAAS,QAAS,QAAO;AAGjC,MAAI,OAAO,IAAI,UAAU,SAAU,QAAO;AAC1C,MAAI,OAAO,IAAI,WAAW,SAAU,QAAO;AAC3C,MAAI,OAAO,IAAI,OAAO,SAAU,QAAO;AAEvC,SAAO;IACL,SAAS,IAAI;IACb,UAAU,IAAI;IACd,SAAS,OAAO,IAAI,YAAY,WAAW,IAAI,UAAU;IACzD,YAAY,IAAI;EAClB;AACF;AAKO,SAAS,sBAAsB,UAGpC;AACA,QAAM,SAAS,mBAAmB,QAAQ;AAC1C,MAAI,CAAC,QAAQ;AACX,WAAO;MACL,OAAO;MACP,OAAO;IACT;EACF;AAIA,QAAM,SAAS,OAAO;AACtB,QAAM,WAAW,0DAA0D,KAAK,MAAM;AACtF,MAAI,aAAa;AACjB,MAAI;AACF,QAAI,IAAI,MAAM;AACd,iBAAa;EACf,QAAQ;EAER;AAEA,MAAI,CAAC,YAAY,CAAC,YAAY;AAC5B,WAAO;MACL,OAAO;MACP,OAAO;IACT;EACF;AAEA,SAAO,EAAE,OAAO,KAAK;AACvB;ACnOO,IAAM,0BAA0B,CAAC,oBAAoB,QAAQ,MAAM;AAiEnE,SAAS,qBAAqB,QAAwC;AAC3E,QAAM,SAAiC,CAAC;AAGxC,QAAM,QAAQ;AACd,MAAI;AACJ,UAAQ,QAAQ,MAAM,KAAK,MAAM,OAAO,MAAM;AAC5C,WAAO,MAAM,CAAC,CAAE,IAAI,MAAM,CAAC;EAC7B;AAEA,MAAI,CAAC,OAAO,SAAS,CAAC,OAAO,WAAW;AACtC,WAAO;EACT;AAEA,SAAO;IACL,OAAO,OAAO;IACd,WAAW,OAAO,aAAa;IAC/B,UAAU,OAAO,WAAW,8BAA8B,MAAM,GAAG;IACnE,WAAW,OAAO;EACpB;AACF;AAiCO,SAAS,mBACd,QACA,MACA,SACA,aACQ;AACR,QAAM,SAAS,yBAAyB,QAAQ,MAAM,SAAS,WAAW;AAC1E,MAAI,CAAC,OAAO,SAAS;AAGnB,YAAQ,KAAK,iDAAiD,OAAO,KAAK,EAAE;AAE5E,UAAM,QAAkB,CAAC;AACzB,eAAW,QAAQ,aAAa;AAC9B,UAAI,SAAS,oBAAoB;AAC/B,cAAM,KAAK,qBAAqB,OAAO,YAAY,CAAC,IAAI,IAAI,EAAE;MAChE,WAAW,SAAS,eAAe,SAAS,aAAa;MAEzD,OAAO;AACL,cAAM,QAAQ,QAAQ,IAAI,IAAI;AAC9B,YAAI,UAAU,MAAM;AAClB,gBAAM,KAAK,GAAG,KAAK,YAAY,CAAC,KAAK,KAAK,EAAE;QAC9C;MACF;IACF;AACA,WAAO,MAAM,KAAK,IAAI;EACxB;AACA,SAAO,OAAO;AAChB;AAQO,SAAS,yBACd,QACA,MACA,SACA,aACqB;AACrB,QAAM,QAAkB,CAAC;AACzB,QAAM,iBAA2B,CAAC;AAGlC,QAAM,mBAAmB,oBAAI,IAAI,CAAC,oBAAoB,aAAa,WAAW,CAAC;AAE/E,aAAW,QAAQ,aAAa;AAC9B,QAAI,SAAS,oBAAoB;AAC/B,YAAM,KAAK,qBAAqB,OAAO,YAAY,CAAC,IAAI,IAAI,EAAE;IAChE,WAAW,SAAS,aAAa;IAEjC,WAAW,SAAS,aAAa;IAEjC,OAAO;AACL,YAAM,QAAQ,QAAQ,IAAI,IAAI;AAC9B,UAAI,UAAU,MAAM;AAClB,cAAM,KAAK,GAAG,KAAK,YAAY,CAAC,KAAK,KAAK,EAAE;MAC9C,WAAW,CAAC,iBAAiB,IAAI,IAAI,GAAG;AAEtC,uBAAe,KAAK,IAAI;MAC1B;IACF;EACF;AAEA,MAAI,eAAe,SAAS,GAAG;AAC7B,WAAO;MACL,SAAS;MACT,OAAO,mEAAmE,eAAe,KAAK,IAAI,CAAC;MACnG;IACF;EACF;AAEA,SAAO;IACL,SAAS;IACT,eAAe,MAAM,KAAK,IAAI;EAChC;AACF;AAsBA,eAAsB,oBACpB,QACA,cACA,QACA,MACA,SACA,UAAsC,CAAC,GACrB;AAClB,MAAI;AAEF,QAAI,QAAQ,eAAe;AACzB,YAAM,eAAe,yBAAyB,QAAQ,MAAM,SAAS,OAAO,OAAO;AACnF,UAAI,CAAC,aAAa,SAAS;AACzB,gBAAQ,KAAK,mDAAmD,aAAa,KAAK,EAAE;AACpF,eAAO;MACT;IACF;AAGA,UAAM,gBAAgB,mBAAmB,QAAQ,MAAM,SAAS,OAAO,OAAO;AAG9E,UAAM,YAAY,MAAM,gBAAgB,YAAY;AACpD,QAAI,CAAC,WAAW;AACd,aAAO;IACT;AAGA,UAAM,iBAAiB,oBAAoB,OAAO,SAAS;AAG3D,UAAM,UAAU,IAAI,YAAY;AAChC,UAAM,OAAO,QAAQ,OAAO,aAAa;AAEzC,UAAM,YAAY,OAAO,cAAc,WAAW,sBAAsB;AAExE,WAAO,MAAM,OAAO,OAAO;MACzB,EAAE,MAAM,WAAW,MAAM,UAAU;MACnC;MACA;MACA;IACF;EACF,SAAS,OAAO;AACd,YAAQ,MAAM,kCAAkC,KAAK;AACrD,WAAO;EACT;AACF;AASA,eAAsB,0BACpB,UACA,SACA,SACoC;AAEpC,QAAM,kBAAkB,QAAQ,IAAI,WAAW;AAC/C,MAAI,CAAC,iBAAiB;AACpB,WAAO,EAAE,OAAO,OAAO,OAAO,2BAA2B;EAC3D;AAGA,QAAM,SAAS,qBAAqB,eAAe;AACnD,MAAI,CAAC,QAAQ;AACX,WAAO,EAAE,OAAO,OAAO,OAAO,kCAAkC;EAClE;AAIA,MAAI,QAAQ,YAAY;AACtB,UAAM,iBAAiB,wBAAwB;MAC7C,CAAC,MAAM,CAAC,OAAO,QAAQ,SAAS,CAAC;IACnC;AACA,QAAI,eAAe,SAAS,GAAG;AAC7B,aAAO;QACL,OAAO;QACP,OAAO,oDAAoD,eAAe,KAAK,IAAI,CAAC;MACtF;IACF;AAGA,QAAI,CAAC,QAAQ,IAAI,MAAM,GAAG;AACxB,aAAO,EAAE,OAAO,OAAO,OAAO,oCAAoC;IACpE;AAGA,QAAI,CAAC,QAAQ,IAAI,MAAM,KAAK,CAAC,QAAQ,IAAI,MAAM,GAAG;AAChD,aAAO,EAAE,OAAO,OAAO,OAAO,oCAAoC;IACpE;EACF;AAGA,QAAM,aAAa,QAAQ,IAAI,MAAM;AACrC,MAAI,YAAY;AACd,UAAM,cAAc,IAAI,KAAK,UAAU;AACvC,UAAM,SAAS,QAAQ,UAAU;AACjC,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,cAAc,YAAY,QAAQ;AAExC,QAAI,MAAM,WAAW,GAAG;AACtB,aAAO,EAAE,OAAO,OAAO,OAAO,sBAAsB;IACtD;AAEA,QAAI,KAAK,IAAI,MAAM,WAAW,IAAI,SAAS,KAAM;AAC/C,aAAO,EAAE,OAAO,OAAO,OAAO,+BAA+B;IAC/D;EACF;AAQA,MAAI;AACJ,MAAI;AACJ,MAAI;AACF,UAAM,WAAW,IAAI,IAAI,OAAO,KAAK;AACrC,UAAM,WAAW,IAAI,IAAI,SAAS,KAAK;AAEvC,gBAAY,GAAG,SAAS,MAAM,GAAG,SAAS,QAAQ;AAClD,gBAAY,GAAG,SAAS,MAAM,GAAG,SAAS,QAAQ;EACpD,QAAQ;AACN,WAAO,EAAE,OAAO,OAAO,OAAO,8BAA8B;EAC9D;AAEA,MAAI,cAAc,WAAW;AAC3B,WAAO,EAAE,OAAO,OAAO,OAAO,uCAAuC;EACvE;AAGA,QAAM,UAAU;AAGhB,QAAM,QAAQ,MAAM,QAAQ,WAAW,OAAO;AAC9C,MAAI,CAAC,OAAO;AACV,WAAO,EAAE,OAAO,OAAO,OAAO,wBAAwB;EACxD;AAEA,MAAI,CAAC,MAAM,WAAW,cAAc;AAClC,WAAO,EAAE,OAAO,OAAO,OAAO,0BAA0B;EAC1D;AAGA,MAAI,MAAM,UAAU,OAAO,OAAO,OAAO;AACvC,WAAO,EAAE,OAAO,OAAO,OAAO,kBAAkB;EAClD;AAIA,QAAM,QAAQ,MAAM;IAClB;IACA,MAAM,UAAU;IAChB,QAAQ;IACR,QAAQ;IACR;IACA,EAAE,eAAe,QAAQ,WAAW;EACtC;AAEA,MAAI,CAAC,OAAO;AACV,WAAO,EAAE,OAAO,OAAO,OAAO,oBAAoB;EACpD;AAEA,SAAO,EAAE,OAAO,MAAM,OAAO,OAAO,OAAO,MAAM;AACnD;AAQA,eAAsB,YACpB,SAC4D;AAE5D,MAAI,CAAC,QAAQ,MAAM;AACjB,UAAM,IAAI,MAAM,gFAAgF;EAClG;AAEA,QAAM,MAAM,oBAAI,KAAK;AACrB,QAAM,aAAa,IAAI,YAAY;AAGnC,QAAM,gBAAgB,CAAC,oBAAoB,QAAQ,MAAM;AACzD,QAAM,eAAe,IAAI,QAAQ;AACjC,eAAa,IAAI,QAAQ,UAAU;AACnC,eAAa,IAAI,QAAQ,QAAQ,IAAI;AAErC,MAAI,QAAQ,QAAQ;AAClB,kBAAc,KAAK,QAAQ;AAC3B,iBAAa,IAAI,UAAU,QAAQ,MAAM;EAC3C;AAEA,MAAI,QAAQ,aAAa;AACvB,kBAAc,KAAK,cAAc;AACjC,iBAAa,IAAI,gBAAgB,QAAQ,WAAW;EACtD;AAGA,QAAM,gBAAgB;IACpB,QAAQ;IACR,QAAQ;IACR;IACA;EACF;AAGA,QAAM,aAAa,MAAM,iBAAiB,QAAQ,aAAa;AAC/D,MAAI,CAAC,YAAY;AACf,UAAM,IAAI,MAAM,8BAA8B;EAChD;AAEA,QAAM,UAAU,IAAI,YAAY;AAChC,QAAM,OAAO,QAAQ,OAAO,aAAa;AAEzC,QAAM,kBAAkB,MAAM,OAAO,OAAO;IAC1C,EAAE,MAAM,qBAAqB,MAAM,UAAU;IAC7C;IACA;EACF;AAEA,QAAM,kBAAkB,oBAAoB,eAAe;AAG3D,QAAM,kBACJ,UAAU,QAAQ,KAAK,qCAEX,cAAc,KAAK,GAAG,CAAC,gBACrB,eAAe;AAE/B,SAAO,EAAE,WAAW,iBAAiB,MAAM,YAAY,MAAM,QAAQ,KAAK;AAC5E;AAKA,eAAsB,gBAAgB,MAA6C;AACjF,QAAM,UAAU,IAAI,YAAY;AAChC,QAAM,OAAO,OAAO,SAAS,WAAW,QAAQ,OAAO,IAAI,IAAI;AAE/D,QAAM,aAAa,MAAM,OAAO,OAAO,OAAO,WAAW,IAAI;AAC7D,QAAM,aAAa,oBAAoB,UAAU;AAEjD,SAAO,WAAW,UAAU;AAC9B;AAOA,eAAe,gBAAgB,KAAwC;AACrE,MAAI;AAEF,UAAM,cAAc,IACjB,QAAQ,8BAA8B,EAAE,EACxC,QAAQ,4BAA4B,EAAE,EACtC,QAAQ,kCAAkC,EAAE,EAC5C,QAAQ,gCAAgC,EAAE,EAC1C,QAAQ,OAAO,EAAE;AAEpB,UAAM,YAAY,oBAAoB,WAAW;AAEjD,WAAO,MAAM,OAAO,OAAO;MACzB;MACA;MACA,EAAE,MAAM,qBAAqB,MAAM,UAAU;MAC7C;MACA,CAAC,QAAQ;IACX;EACF,SAAS,OAAO;AACd,YAAQ,MAAM,gCAAgC,KAAK;AACnD,WAAO;EACT;AACF;AAKA,eAAe,iBAAiB,KAAwC;AACtE,MAAI;AAEF,UAAM,cAAc,IACjB,QAAQ,+BAA+B,EAAE,EACzC,QAAQ,6BAA6B,EAAE,EACvC,QAAQ,mCAAmC,EAAE,EAC7C,QAAQ,iCAAiC,EAAE,EAC3C,QAAQ,OAAO,EAAE;AAEpB,UAAM,YAAY,oBAAoB,WAAW;AAEjD,WAAO,MAAM,OAAO,OAAO;MACzB;MACA;MACA,EAAE,MAAM,qBAAqB,MAAM,UAAU;MAC7C;MACA,CAAC,MAAM;IACT;EACF,SAAS,OAAO;AACd,YAAQ,MAAM,iCAAiC,KAAK;AACpD,WAAO;EACT;AACF;AAKA,SAAS,oBAAoB,QAA6B;AAExD,QAAM,aAAa,OAAO,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AAC9D,QAAM,SAAS,KAAK,UAAU;AAC9B,QAAM,QAAQ,IAAI,WAAW,OAAO,MAAM;AAC1C,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,CAAC,IAAI,OAAO,WAAW,CAAC;EAChC;AACA,SAAO,MAAM;AACf;AAKA,SAAS,oBAAoB,QAA6B;AACxD,QAAM,QAAQ,IAAI,WAAW,MAAM;AACnC,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,cAAU,OAAO,aAAa,MAAM,CAAC,CAAE;EACzC;AACA,SAAO,KAAK,MAAM;AACpB;ACviBA,SAAS,uBAAuB,SAAgC;AAC9D,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,OAAO;AAC3B,WAAO,IAAI;EACb,QAAQ;AACN,WAAO;EACT;AACF;AAmCA,eAAsB,YACpB,MACA,SACA,SAqCsB;AACtB,0BAAwB,aAAa;AAErC,MAAI;AAGF,QAAI,QAAQ,iBAAiB;AAC3B,YAAM,UAAU,OAAO,SAAS,YAAY,SAAS,QAAQ,WAAW,OACpE,OAAQ,KAAiC,KAAK,IAC9C;AAEJ,UAAI,SAAS;AACX,cAAM,OAAO,uBAAuB,OAAO;AAC3C,YAAI,QAAQ,MAAM,QAAQ,gBAAgB,kBAAkB,IAAI,GAAG;AACjE,iBAAO;YACL,UAAU;YACV,OAAO,YAAY,IAAI;UACzB;QACF;MACF;IACF;AAGA,QAAI;AACJ,QAAI;AACF,iBAAW,cAAc,IAAI;IAC/B,SAAS,KAAK;AACZ,aAAO;QACL,UAAU;QACV,OAAO,qBAAqB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;MAC9E;IACF;AAGA,QAAI,QAAQ,YAAY;AAEtB,YAAM,kBAAkB,mBAAmB,UACvC,QAAQ,IAAI,WAAW,IACvB,QAAQ,WAAW,KAAK,QAAQ,WAAW;AAE/C,UAAI,CAAC,iBAAiB;AACpB,eAAO;UACL,UAAU;UACV,OAAO;QACT;MACF;AAGA,YAAM,YAAY,qBAAqB,eAAe;AACtD,UAAI,CAAC,WAAW;AACd,eAAO;UACL,UAAU;UACV,OAAO;QACT;MACF;AAIA,YAAM,uBAAuB,wBAAwB;QACnD,CAAC,MAAM,CAAC,UAAU,QAAQ,SAAS,CAAC;MACtC;AACA,UAAI,qBAAqB,SAAS,GAAG;AACnC,eAAO;UACL,UAAU;UACV,OAAO,oDAAoD,qBAAqB,KAAK,IAAI,CAAC;QAC5F;MACF;AAGA,YAAM,aAAa,mBAAmB,UAClC,QAAQ,IAAI,MAAM,IAClB,QAAQ,MAAM,KAAK,QAAQ,MAAM;AACrC,UAAI,CAAC,YAAY;AACf,eAAO;UACL,UAAU;UACV,OAAO;QACT;MACF;AAGA,YAAM,aAAa,mBAAmB,UAClC,QAAQ,IAAI,MAAM,IAClB,QAAQ,MAAM,KAAK,QAAQ,MAAM;AACrC,UAAI,CAAC,YAAY;AACf,eAAO;UACL,UAAU;UACV,OAAO;QACT;MACF;AAGA,YAAM,QAAQ,MAAM,QAAQ,WAAW,SAAS,KAAK;AACrD,UAAI,CAAC,OAAO;AACV,eAAO;UACL,UAAU;UACV,OAAO,kBAAkB,SAAS,KAAK;QACzC;MACF;AAGA,UAAI,CAAC,MAAM,WAAW,cAAc;AAClC,eAAO;UACL,UAAU;UACV,OAAO,SAAS,SAAS,KAAK;QAChC;MACF;AAKA,UAAI;AACF,cAAM,WAAW,IAAI,IAAI,UAAU,KAAK;AACxC,cAAM,WAAW,IAAI,IAAI,SAAS,KAAK;AAGvC,cAAM,YAAY,GAAG,SAAS,MAAM,GAAG,SAAS,QAAQ;AACxD,cAAM,YAAY,GAAG,SAAS,MAAM,GAAG,SAAS,QAAQ;AAKxD,YAAI,cAAc,WAAW;AAC3B,iBAAO;YACL,UAAU;YACV,OAAO,UAAU,UAAU,KAAK,yBAAyB,SAAS,KAAK;UACzE;QACF;AAGA,YAAI,MAAM,UAAU,OAAO,UAAU,OAAO;AAC1C,iBAAO;YACL,UAAU;YACV,OAAO,kBAAkB,MAAM,UAAU,EAAE,oCAAoC,UAAU,KAAK;UAChG;QACF;MACF,QAAQ;AACN,eAAO;UACL,UAAU;UACV,OAAO;QACT;MACF;AAKA,YAAM,eAAe,mBAAmB,UACpC,QAAQ,IAAI,QAAQ,IACpB,QAAQ,QAAQ,KAAK,QAAQ,QAAQ;AAEzC,UAAI,cAAc;AAEhB,YAAI,CAAC,QAAQ,SAAS;AACpB,iBAAO;YACL,UAAU;YACV,OAAO;UACT;QACF;AAGA,cAAM,cAAc,aAAa,MAAM,2BAA2B;AAClE,YAAI,CAAC,aAAa;AAChB,iBAAO;YACL,UAAU;YACV,OAAO;UACT;QACF;AAGA,cAAM,iBAAiB,MAAM,gBAAgB,QAAQ,OAAO;AAC5D,cAAM,eAAe,WAAW,YAAY,CAAC,CAAC;AAG9C,YAAI,mBAAmB,cAAc;AACnC,iBAAO;YACL,UAAU;YACV,OAAO;UACT;QACF;MACF,WAAW,UAAU,QAAQ,SAAS,QAAQ,GAAG;AAE/C,eAAO;UACL,UAAU;UACV,OAAO;QACT;MACF;AAGA,YAAM,SAAS,QAAQ,UAAU;AACjC,YAAM,OAAO,QAAQ,QAAQ;AAG7B,YAAM,oBAAoB,mBAAmB,UACzC,UACA,IAAI,QAAQ,OAAO;AAEvB,YAAM,UAAU,MAAM;QACpB;QACA,MAAM,UAAU;QAChB;QACA;QACA;MACF;AAEA,UAAI,CAAC,SAAS;AACZ,eAAO;UACL,UAAU;UACV,OAAO;QACT;MACF;AAKA,YAAM,kBAAkB,QAAQ,UAAU;AAC1C,YAAM,cAAc,IAAI,KAAK,UAAU;AACvC,YAAM,MAAM,oBAAI,KAAK;AACrB,YAAM,QAAQ,KAAK,IAAI,IAAI,QAAQ,IAAI,YAAY,QAAQ,CAAC;AAE5D,UAAI,MAAM,YAAY,QAAQ,CAAC,GAAG;AAChC,eAAO;UACL,UAAU;UACV,OAAO;QACT;MACF;AAEA,UAAI,QAAQ,kBAAkB,KAAM;AAClC,eAAO;UACL,UAAU;UACV,OAAO,iCAAiC,KAAK,MAAM,QAAQ,GAAI,CAAC,UAAU,eAAe;QAC3F;MACF;IACF;AAGA,UAAM,eAAe,SAAS;AAE9B,YAAQ,cAAc;MACpB,KAAK,QAAQ;AACX,cAAM,SAAS,kBAAkB,QAAQ;AACzC,YAAI,CAAC,QAAQ;AACX,iBAAO;YACL,UAAU;YACV,OAAO;UACT;QACF;AACA,YAAI,QAAQ,QAAQ;AAClB,gBAAM,QAAQ,OAAO,QAAwB;QAC/C;AACA,eAAO,EAAE,UAAU,MAAM,cAAc,OAAO;MAChD;MAEA,KAAK,WAAW;AACd,cAAM,SAAS,qBAAqB,QAAQ;AAC5C,YAAI,CAAC,QAAQ;AACX,iBAAO;YACL,UAAU;YACV,OAAO;UACT;QACF;AACA,YAAI,QAAQ,WAAW;AACrB,gBAAM,QAAQ,UAAU,QAA2B;QACrD;AACA,eAAO,EAAE,UAAU,MAAM,cAAc,UAAU;MACnD;MAEA,KAAK,UAAU;AACb,YAAI,QAAQ,UAAU;AACpB,gBAAM,QAAQ,SAAS,QAAQ;QACjC;AACA,eAAO,EAAE,UAAU,MAAM,cAAc,SAAS;MAClD;MAEA,KAAK,UAAU;AACb,YAAI,QAAQ,UAAU;AACpB,gBAAM,QAAQ,SAAS,QAAQ;QACjC;AACA,eAAO,EAAE,UAAU,MAAM,cAAc,SAAS;MAClD;MAEA,KAAK,UAAU;AACb,YAAI,QAAQ,UAAU;AACpB,gBAAM,QAAQ,SAAS,QAAQ;QACjC;AACA,eAAO,EAAE,UAAU,MAAM,cAAc,SAAS;MAClD;MAEA,KAAK,QAAQ;AACX,YAAI,QAAQ,QAAQ;AAClB,gBAAM,QAAQ,OAAO,QAAQ;QAC/B;AACA,eAAO,EAAE,UAAU,MAAM,cAAc,OAAO;MAChD;MAEA,KAAK,YAAY;AACf,YAAI,QAAQ,YAAY;AACtB,gBAAM,QAAQ,WAAW,QAAQ;QACnC;AACA,eAAO,EAAE,UAAU,MAAM,cAAc,WAAW;MACpD;MAEA,KAAK,QAAQ;AACX,YAAI,QAAQ,QAAQ;AAClB,gBAAM,QAAQ,OAAO,QAAQ;QAC/B;AACA,eAAO,EAAE,UAAU,MAAM,cAAc,OAAO;MAChD;MAEA,KAAK,QAAQ;AACX,cAAM,SAAS,kBAAkB,QAAQ;AACzC,YAAI,QAAQ,UAAU,QAAQ;AAC5B,gBAAM,QAAQ,OAAO,UAAqC,MAAM;QAClE;AACA,eAAO,EAAE,UAAU,MAAM,cAAc,OAAO;MAChD;MAEA,KAAK,SAAS;AACZ,cAAM,SAAS,mBAAmB,QAAQ;AAC1C,YAAI,QAAQ,WAAW,QAAQ;AAC7B,gBAAM,QAAQ,QAAQ,UAAsC,MAAM;QACpE;AACA,eAAO,EAAE,UAAU,MAAM,cAAc,QAAQ;MACjD;MAEA,SAAS;AAEP,eAAO;UACL,UAAU;UACV;QACF;MACF;IACF;EACF,SAAS,KAAK;AACZ,WAAO;MACL,UAAU;MACV,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;IACxD;EACF;AACF;AAKO,SAAS,qBAAqB,UAGnC;AACA,QAAM,SAAS,kBAAkB,QAAQ;AACzC,MAAI,CAAC,QAAQ;AACX,WAAO;MACL,OAAO;MACP,OAAO;IACT;EACF;AAGA,MAAI;AACF,SAAK,MAAM,OAAO,WAAW,OAAO;EACtC,QAAQ;AACN,WAAO;MACL,OAAO;MACP,OAAO;IACT;EACF;AAEA,SAAO,EAAE,OAAO,KAAK;AACvB;AAKO,SAAS,wBAAwB,UAGtC;AACA,QAAM,SAAS,qBAAqB,QAAQ;AAC5C,MAAI,CAAC,QAAQ;AACX,WAAO;MACL,OAAO;MACP,OAAO;IACT;EACF;AAGA,MAAI;AACF,QAAI,IAAI,OAAO,MAAM;EACvB,QAAQ;AACN,WAAO;MACL,OAAO;MACP,OAAO;IACT;EACF;AAEA,SAAO,EAAE,OAAO,KAAK;AACvB;AC5dO,IAAM,wBAAN,MAAuD;EACpD,UAAyC,oBAAI,IAAI;EACjD,UAAyC,oBAAI,IAAI;EACjD,SAAqC,oBAAI,IAAI;EAC7C,WAAuC,oBAAI,IAAI;EAC/C,aAA2C,oBAAI,IAAI;;EAI3D,MAAM,aAAa,QAAiE;AAClF,UAAM,KAAKA,aAAa;AACxB,UAAM,OAAyB,EAAE,GAAG,QAAQ,GAAG;AAC/C,SAAK,QAAQ,IAAI,IAAI,IAAI;AACzB,WAAO;EACT;EAEA,MAAM,UAAU,IAA8C;AAC5D,WAAO,KAAK,QAAQ,IAAI,EAAE,KAAK;EACjC;EAEA,MAAM,aAAa,IAAY,SAAmD;AAChF,UAAM,WAAW,KAAK,QAAQ,IAAI,EAAE;AACpC,QAAI,UAAU;AACZ,WAAK,QAAQ,IAAI,IAAI;QACnB,GAAG;QACH,GAAG;QACH,YAAW,oBAAI,KAAK,GAAE,YAAY;MACpC,CAAC;IACH;EACF;EAEA,MAAM,YAAY,SAQc;AAC9B,QAAI,UAAU,MAAM,KAAK,KAAK,QAAQ,OAAO,CAAC;AAE9C,QAAI,SAAS,QAAQ;AACnB,gBAAU,QAAQ,OAAO,CAAC,MAAM,EAAE,WAAW,QAAQ,MAAM;IAC7D;AACA,QAAI,SAAS,UAAU;AACrB,gBAAU,QAAQ,OAAO,CAAC,MAAM,EAAE,aAAa,QAAQ,QAAQ;IACjE;AACA,QAAI,SAAS,UAAU;AACrB,gBAAU,QAAQ,OAAO,CAAC,MAAM,EAAE,UAAU,SAAS,QAAQ,QAAS,CAAC;IACzE;AACA,QAAI,SAAS,iBAAiB;AAC5B,gBAAU,QAAQ,OAAO,CAAC,MAAM,EAAE,oBAAoB,QAAQ,eAAe;IAC/E;AACA,QAAI,SAAS,OAAO;AAClB,gBAAU,QAAQ,OAAO,CAAC,MAAM,EAAE,aAAa,QAAQ,KAAM;IAC/D;AAGA,YAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,SAAS,CAAC;AAE7D,UAAM,SAAS,SAAS,UAAU;AAClC,UAAM,QAAQ,SAAS,SAAS;AAChC,WAAO,QAAQ,MAAM,QAAQ,SAAS,KAAK;EAC7C;EAEA,MAAM,aAAa,SAAsD;AACvE,QAAI,QAAQ;AACZ,eAAW,UAAU,KAAK,QAAQ,OAAO,GAAG;AAC1C,UAAI,CAAC,SAAS,UAAU,OAAO,WAAW,QAAQ,QAAQ;AACxD;MACF;IACF;AACA,WAAO;EACT;;EAIA,MAAM,aAAa,QAAiE;AAClF,UAAM,KAAKA,aAAa;AACxB,UAAM,OAAyB,EAAE,GAAG,QAAQ,GAAG;AAC/C,SAAK,QAAQ,IAAI,IAAI,IAAI;AACzB,WAAO;EACT;EAEA,MAAM,UAAU,IAA8C;AAC5D,WAAO,KAAK,QAAQ,IAAI,EAAE,KAAK;EACjC;EAEA,MAAM,YAAY,SAOc;AAC9B,QAAI,UAAU,MAAM,KAAK,KAAK,QAAQ,OAAO,CAAC;AAE9C,QAAI,SAAS,UAAU;AACrB,gBAAU,QAAQ,OAAO,CAAC,MAAM,EAAE,aAAa,QAAQ,QAAQ;IACjE;AACA,QAAI,SAAS,kBAAkB;AAC7B,gBAAU,QAAQ,OAAO,CAAC,MAAM,EAAE,qBAAqB,QAAQ,gBAAgB;IACjF;AACA,QAAI,SAAS,YAAY;AACvB,gBAAU,QAAQ,OAAO,CAAC,MAAM,EAAE,eAAe,QAAQ,UAAU;IACrE;AACA,QAAI,SAAS,WAAW,QAAW;AACjC,gBAAU,QAAQ,OAAO,CAAC,MAAM,EAAE,WAAW,QAAQ,MAAM;IAC7D;AACA,QAAI,SAAS,OAAO;AAClB,gBAAU,QAAQ,OAAO,CAAC,MAAM,EAAE,aAAa,QAAQ,KAAM;IAC/D;AAGA,YAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,SAAS,CAAC;AAC7D,WAAO,QAAQ,MAAM,GAAG,SAAS,SAAS,GAAG;EAC/C;EAEA,MAAM,iBAAiB,IAA2B;AAChD,UAAM,SAAS,KAAK,QAAQ,IAAI,EAAE;AAClC,QAAI,QAAQ;AACV,WAAK,QAAQ,IAAI,IAAI,EAAE,GAAG,QAAQ,QAAQ,MAAM,CAAC;IACnD;EACF;;EAIA,MAAM,YAAY,OAA0D;AAC1E,UAAM,KAAKA,aAAa;AACxB,UAAM,OAAsB,EAAE,GAAG,OAAO,GAAG;AAC3C,SAAK,OAAO,IAAI,IAAI,IAAI;AACxB,WAAO;EACT;EAEA,MAAM,SAAS,IAA2C;AACxD,WAAO,KAAK,OAAO,IAAI,EAAE,KAAK;EAChC;EAEA,MAAM,iBAAiB,QAA+C;AACpE,UAAM,aAAa,OAAO,YAAY;AACtC,eAAW,SAAS,KAAK,OAAO,OAAO,GAAG;AACxC,UAAI,MAAM,cAAc,YAAY,MAAM,cAAc,MAAM,QAAQ;AACpE,eAAO;MACT;IACF;AACA,WAAO;EACT;EAEA,MAAM,WAAW,SAA0D;AACzE,QAAI,UAAU,MAAM,KAAK,KAAK,OAAO,OAAO,CAAC;AAC7C,QAAI,SAAS,WAAW,QAAW;AACjC,gBAAU,QAAQ,OAAO,CAAC,MAAM,EAAE,WAAW,QAAQ,MAAM;IAC7D;AAEA,YAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,SAAS,CAAC;AAC7D,WAAO;EACT;EAEA,MAAM,YAAY,IAAY,SAAgD;AAC5E,UAAM,WAAW,KAAK,OAAO,IAAI,EAAE;AACnC,QAAI,UAAU;AACZ,WAAK,OAAO,IAAI,IAAI,EAAE,GAAG,UAAU,GAAG,QAAQ,CAAC;IACjD;EACF;EAEA,MAAM,kBAAkB,QAAkC;AACxD,UAAM,QAAQ,MAAM,KAAK,iBAAiB,MAAM;AAChD,WAAO,UAAU;EACnB;;EAIA,MAAM,aAAa,QAA2D;AAC5E,UAAM,KAAKA,aAAa;AACxB,UAAM,OAAsB,EAAE,GAAG,QAAQ,GAAG;AAC5C,SAAK,SAAS,IAAI,IAAI,IAAI;AAC1B,WAAO;EACT;EAEA,MAAM,UAAU,IAA2C;AACzD,WAAO,KAAK,SAAS,IAAI,EAAE,KAAK;EAClC;EAEA,MAAM,aAAa,SAA2D;AAC5E,QAAI,UAAU,MAAM,KAAK,KAAK,SAAS,OAAO,CAAC;AAC/C,QAAI,SAAS,YAAY,QAAW;AAClC,gBAAU,QAAQ,OAAO,CAAC,MAAM,EAAE,YAAY,QAAQ,OAAO;IAC/D;AACA,WAAO;EACT;EAEA,MAAM,aAAa,IAAY,SAAgD;AAC7E,UAAM,WAAW,KAAK,SAAS,IAAI,EAAE;AACrC,QAAI,UAAU;AACZ,WAAK,SAAS,IAAI,IAAI;QACpB,GAAG;QACH,GAAG;QACH,YAAW,oBAAI,KAAK,GAAE,YAAY;MACpC,CAAC;IACH;EACF;EAEA,MAAM,aAAa,IAA2B;AAC5C,SAAK,SAAS,OAAO,EAAE;EACzB;;EAIA,MAAM,mBAAmB,SAAkD;AACzE,WAAO,KAAK,WAAW,IAAI,OAAO,KAAK;EACzC;EAEA,MAAM,sBAAsB,QAAwC;AAClE,SAAK,WAAW,IAAI,OAAO,SAAS,MAAM;EAC5C;;EAIA,MAAM,YAAY,SAMc;AAC9B,QAAI,UAAU,MAAM,KAAK,YAAY;MACnC,UAAU,SAAS;MACnB,kBAAkB,SAAS;MAC3B,OAAO,SAAS;MAChB,OAAO,SAAS;IAClB,CAAC;AAED,QAAI,SAAS,OAAO;AAClB,gBAAU,QAAQ,OAAO,CAAC,MAAM,EAAE,aAAa,QAAQ,KAAM;IAC/D;AAEA,WAAO;EACT;;;;;EAOA,QAAc;AACZ,SAAK,QAAQ,MAAM;AACnB,SAAK,QAAQ,MAAM;AACnB,SAAK,OAAO,MAAM;AAClB,SAAK,SAAS,MAAM;AACpB,SAAK,WAAW,MAAM;EACxB;;;;EAKA,MAAM,WAKH;AACD,UAAM,iBAAiB,MAAM,KAAK,aAAa,EAAE,QAAQ,UAAU,CAAC;AACpE,UAAM,iBAAiB,MAAM,KAAK,YAAY,EAAE,QAAQ,KAAK,CAAC,GAAG;AACjE,UAAM,gBAAgB,MAAM,KAAK,WAAW,EAAE,QAAQ,KAAK,CAAC,GAAG;AAC/D,UAAM,mBAAmB,MAAM,KAAK,aAAa,EAAE,SAAS,KAAK,CAAC,GAAG;AAErE,WAAO;MACL,SAAS;QACP,OAAO,KAAK,QAAQ;QACpB,SAAS;MACX;MACA,SAAS;QACP,OAAO,KAAK,QAAQ;QACpB,QAAQ;MACV;MACA,QAAQ;QACN,OAAO,KAAK,OAAO;QACnB,QAAQ;MACV;MACA,UAAU;QACR,OAAO,KAAK,SAAS;QACrB,SAAS;MACX;IACF;EACF;AACF;ACpNA,SAAS,oBAAoB,QAAsB;AACjD,QAAM,eAAe;AACrB,MAAI,CAAC,aAAa,KAAK,MAAM,GAAG;AAC9B,UAAM,IAAI;MACR,yBAAyB,MAAM;IACjC;EACF;AACA,MAAI,OAAO,SAAS,IAAI;AACtB,UAAM,IAAI,MAAM,yBAAyB,MAAM,kCAAkC;EACnF;AACF;AA0BO,IAAM,oBAAN,MAAmD;EAChD;EACA;EAER,YAAY,IAAgB,cAAc,cAAc;AACtD,wBAAoB,WAAW;AAC/B,SAAK,KAAK;AACV,SAAK,SAAS;EAChB;;;;EAKA,MAAM,OAAsB;AAE1B,UAAM,KAAK,GAAG,KAAK;mCACY,KAAK,MAAM;;;;;;;;;;;;;;;KAezC;AAED,UAAM,KAAK,GAAG,KAAK;uCACgB,KAAK,MAAM;WACvC,KAAK,MAAM;KACjB;AAED,UAAM,KAAK,GAAG,KAAK;uCACgB,KAAK,MAAM;WACvC,KAAK,MAAM;KACjB;AAGD,UAAM,KAAK,GAAG,KAAK;mCACY,KAAK,MAAM;;;;;;;;;;;;;;KAczC;AAED,UAAM,KAAK,GAAG,KAAK;uCACgB,KAAK,MAAM;WACvC,KAAK,MAAM;KACjB;AAED,UAAM,KAAK,GAAG,KAAK;uCACgB,KAAK,MAAM;WACvC,KAAK,MAAM;KACjB;AAGD,UAAM,KAAK,GAAG,KAAK;mCACY,KAAK,MAAM;;;;;;;;;;;KAWzC;AAED,UAAM,KAAK,GAAG,KAAK;uCACgB,KAAK,MAAM;WACvC,KAAK,MAAM;KACjB;AAGD,UAAM,KAAK,GAAG,KAAK;mCACY,KAAK,MAAM;;;;;;;;;KASzC;AAGD,UAAM,KAAK,GAAG,KAAK;mCACY,KAAK,MAAM;;;;;;;KAOzC;EACH;;EAIA,MAAM,aAAa,QAAiE;AAClF,UAAM,KAAK,OAAO,WAAW;AAC7B,UAAM,OAAyB,EAAE,GAAG,QAAQ,GAAG;AAE/C,UAAM,KAAK,GACR;MACC,eAAe,KAAK,MAAM;;;;IAI5B,EACC;MACC;MACA,OAAO;MACP,OAAO;MACP,KAAK,UAAU,OAAO,SAAS;MAC/B,OAAO;MACP,OAAO;MACP,OAAO;MACP,OAAO;MACP,OAAO;MACP,OAAO;MACP,OAAO;MACP,OAAO,oBAAoB,IAAI;MAC/B,OAAO,WAAW,KAAK,UAAU,OAAO,QAAQ,IAAI;IACtD,EACC,IAAI;AAEP,WAAO;EACT;EAEA,MAAM,UAAU,IAA8C;AAC5D,UAAM,MAAM,MAAM,KAAK,GACpB,QAAQ,iBAAiB,KAAK,MAAM,uBAAuB,EAC3D,KAAK,EAAE,EACP,MAAiB;AAEpB,WAAO,MAAM,KAAK,YAAY,GAAG,IAAI;EACvC;EAEA,MAAM,aAAa,IAAY,SAAmD;AAChF,UAAM,WAAW,MAAM,KAAK,UAAU,EAAE;AACxC,QAAI,CAAC,SAAU;AAEf,UAAM,SAAS,EAAE,GAAG,UAAU,GAAG,SAAS,YAAW,oBAAI,KAAK,GAAE,YAAY,EAAE;AAE9E,UAAM,KAAK,GACR;MACC,UAAU,KAAK,MAAM;;;;;IAKvB,EACC;MACC,OAAO;MACP,OAAO;MACP,KAAK,UAAU,OAAO,SAAS;MAC/B,OAAO;MACP,OAAO;MACP,OAAO;MACP,OAAO;MACP,OAAO;MACP,OAAO;MACP,OAAO,oBAAoB,IAAI;MAC/B,OAAO,WAAW,KAAK,UAAU,OAAO,QAAQ,IAAI;MACpD;IACF,EACC,IAAI;EACT;EAEA,MAAM,YAAY,SAQc;AAC9B,UAAM,aAAuB,CAAC,KAAK;AACnC,UAAM,WAAsB,CAAC;AAE7B,QAAI,SAAS,QAAQ;AACnB,iBAAW,KAAK,YAAY;AAC5B,eAAS,KAAK,QAAQ,MAAM;IAC9B;AACA,QAAI,SAAS,UAAU;AACrB,iBAAW,KAAK,cAAc;AAC9B,eAAS,KAAK,QAAQ,QAAQ;IAChC;AACA,QAAI,SAAS,UAAU;AACrB,iBAAW,KAAK,mBAAmB;AACnC,eAAS,KAAK,IAAI,QAAQ,QAAQ,GAAG;IACvC;AACA,QAAI,SAAS,iBAAiB;AAC5B,iBAAW,KAAK,uBAAuB;AACvC,eAAS,KAAK,QAAQ,eAAe;IACvC;AACA,QAAI,SAAS,OAAO;AAClB,iBAAW,KAAK,iBAAiB;AACjC,eAAS,KAAK,QAAQ,KAAK;IAC7B;AAEA,UAAM,QAAQ,SAAS,SAAS;AAChC,UAAM,SAAS,SAAS,UAAU;AAElC,QAAI,OAAO,KAAK,GAAG;MACjB,iBAAiB,KAAK,MAAM;eACnB,WAAW,KAAK,OAAO,CAAC;;;IAGnC;AAEA,eAAW,WAAW,UAAU;AAC9B,aAAO,KAAK,KAAK,OAAO;IAC1B;AACA,WAAO,KAAK,KAAK,OAAO,MAAM;AAE9B,UAAM,SAAS,MAAM,KAAK,IAAe;AACzC,WAAO,OAAO,QAAQ,IAAI,CAAC,QAAQ,KAAK,YAAY,GAAG,CAAC;EAC1D;EAEA,MAAM,aAAa,SAAsD;AACvE,QAAI,QAAQ,iCAAiC,KAAK,MAAM;AACxD,UAAM,WAAsB,CAAC;AAE7B,QAAI,SAAS,QAAQ;AACnB,eAAS;AACT,eAAS,KAAK,QAAQ,MAAM;IAC9B;AAEA,QAAI,OAAO,KAAK,GAAG,QAAQ,KAAK;AAChC,eAAW,WAAW,UAAU;AAC9B,aAAO,KAAK,KAAK,OAAO;IAC1B;AAEA,UAAM,SAAS,MAAM,KAAK,MAAyB;AACnD,WAAO,QAAQ,SAAS;EAC1B;;EAIA,MAAM,aAAa,QAAiE;AAClF,UAAM,KAAK,OAAO,WAAW;AAC7B,UAAM,OAAyB,EAAE,GAAG,QAAQ,GAAG;AAE/C,UAAM,KAAK,GACR;MACC,eAAe,KAAK,MAAM;;;;IAI5B,EACC;MACC;MACA,OAAO,YAAY;MACnB,OAAO;MACP,OAAO;MACP,OAAO;MACP,OAAO;MACP,OAAO;MACP,OAAO,aAAa;MACpB,OAAO,SAAS,IAAI;MACpB,OAAO,oBAAoB;MAC3B,OAAO,aAAa,KAAK,UAAU,OAAO,UAAU,IAAI;MACxD,OAAO,WAAW,KAAK,UAAU,OAAO,QAAQ,IAAI;IACtD,EACC,IAAI;AAEP,WAAO;EACT;EAEA,MAAM,UAAU,IAA8C;AAC5D,UAAM,MAAM,MAAM,KAAK,GACpB,QAAQ,iBAAiB,KAAK,MAAM,uBAAuB,EAC3D,KAAK,EAAE,EACP,MAAiB;AAEpB,WAAO,MAAM,KAAK,YAAY,GAAG,IAAI;EACvC;EAEA,MAAM,YAAY,SAOc;AAC9B,UAAM,aAAuB,CAAC,KAAK;AACnC,UAAM,WAAsB,CAAC;AAE7B,QAAI,SAAS,UAAU;AACrB,iBAAW,KAAK,eAAe;AAC/B,eAAS,KAAK,QAAQ,QAAQ;IAChC;AACA,QAAI,SAAS,kBAAkB;AAC7B,iBAAW,KAAK,wBAAwB;AACxC,eAAS,KAAK,QAAQ,gBAAgB;IACxC;AACA,QAAI,SAAS,YAAY;AACvB,iBAAW,KAAK,iBAAiB;AACjC,eAAS,KAAK,QAAQ,UAAU;IAClC;AACA,QAAI,SAAS,WAAW,QAAW;AACjC,iBAAW,KAAK,YAAY;AAC5B,eAAS,KAAK,QAAQ,SAAS,IAAI,CAAC;IACtC;AACA,QAAI,SAAS,OAAO;AAClB,iBAAW,KAAK,gBAAgB;AAChC,eAAS,KAAK,QAAQ,KAAK;IAC7B;AAEA,UAAM,QAAQ,SAAS,SAAS;AAEhC,QAAI,OAAO,KAAK,GAAG;MACjB,iBAAiB,KAAK,MAAM;eACnB,WAAW,KAAK,OAAO,CAAC;;;IAGnC;AAEA,eAAW,WAAW,UAAU;AAC9B,aAAO,KAAK,KAAK,OAAO;IAC1B;AACA,WAAO,KAAK,KAAK,KAAK;AAEtB,UAAM,SAAS,MAAM,KAAK,IAAe;AACzC,WAAO,OAAO,QAAQ,IAAI,CAAC,QAAQ,KAAK,YAAY,GAAG,CAAC;EAC1D;EAEA,MAAM,iBAAiB,IAA2B;AAChD,UAAM,KAAK,GACR,QAAQ,UAAU,KAAK,MAAM,sCAAsC,EACnE,KAAK,EAAE,EACP,IAAI;EACT;;EAIA,MAAM,YAAY,OAA0D;AAC1E,UAAM,KAAK,OAAO,WAAW;AAC7B,UAAM,OAAsB,EAAE,GAAG,OAAO,GAAG;AAE3C,UAAM,KAAK,GACR;MACC,eAAe,KAAK,MAAM;;;IAG5B,EACC;MACC;MACA,MAAM,cAAc,YAAY;MAChC,MAAM;MACN,MAAM;MACN,MAAM;MACN,MAAM;MACN,MAAM,SAAS,IAAI;MACnB,MAAM,iBAAiB;MACvB,MAAM,WAAW,IAAI;IACvB,EACC,IAAI;AAEP,WAAO;EACT;EAEA,MAAM,SAAS,IAA2C;AACxD,UAAM,MAAM,MAAM,KAAK,GACpB,QAAQ,iBAAiB,KAAK,MAAM,sBAAsB,EAC1D,KAAK,EAAE,EACP,MAAgB;AAEnB,WAAO,MAAM,KAAK,WAAW,GAAG,IAAI;EACtC;EAEA,MAAM,iBAAiB,QAA+C;AACpE,UAAM,MAAM,MAAM,KAAK,GACpB,QAAQ,iBAAiB,KAAK,MAAM,iDAAiD,EACrF,KAAK,OAAO,YAAY,CAAC,EACzB,MAAgB;AAEnB,WAAO,MAAM,KAAK,WAAW,GAAG,IAAI;EACtC;EAEA,MAAM,WAAW,SAA0D;AACzE,QAAI,QAAQ,iBAAiB,KAAK,MAAM;AACxC,UAAM,WAAsB,CAAC;AAE7B,QAAI,SAAS,WAAW,QAAW;AACjC,eAAS;AACT,eAAS,KAAK,QAAQ,SAAS,IAAI,CAAC;IACtC;AAEA,aAAS;AAET,QAAI,OAAO,KAAK,GAAG,QAAQ,KAAK;AAChC,eAAW,WAAW,UAAU;AAC9B,aAAO,KAAK,KAAK,OAAO;IAC1B;AAEA,UAAM,SAAS,MAAM,KAAK,IAAc;AACxC,WAAO,OAAO,QAAQ,IAAI,CAAC,QAAQ,KAAK,WAAW,GAAG,CAAC;EACzD;EAEA,MAAM,YAAY,IAAY,SAAgD;AAC5E,UAAM,WAAW,MAAM,KAAK,SAAS,EAAE;AACvC,QAAI,CAAC,SAAU;AAEf,UAAM,SAAS,EAAE,GAAG,UAAU,GAAG,QAAQ;AAEzC,UAAM,KAAK,GACR;MACC,UAAU,KAAK,MAAM;;;IAGvB,EACC;MACC,OAAO,cAAc,YAAY;MACjC,OAAO;MACP,OAAO;MACP,OAAO,SAAS,IAAI;MACpB,OAAO,iBAAiB;MACxB,OAAO,WAAW,IAAI;MACtB;IACF,EACC,IAAI;EACT;EAEA,MAAM,kBAAkB,QAAkC;AACxD,UAAM,QAAQ,MAAM,KAAK,iBAAiB,MAAM;AAChD,WAAO,UAAU;EACnB;;EAIA,MAAM,aAAa,QAA2D;AAC5E,UAAM,KAAK,OAAO,WAAW;AAC7B,UAAM,OAAsB,EAAE,GAAG,QAAQ,GAAG;AAE5C,UAAM,KAAK,GACR;MACC,eAAe,KAAK,MAAM;;;IAG5B,EACC;MACC;MACA,OAAO;MACP,OAAO;MACP,KAAK,UAAU,OAAO,KAAK;MAC3B,OAAO,UAAU,IAAI;MACrB,OAAO;MACP,OAAO;IACT,EACC,IAAI;AAEP,WAAO;EACT;EAEA,MAAM,UAAU,IAA2C;AACzD,UAAM,MAAM,MAAM,KAAK,GACpB,QAAQ,iBAAiB,KAAK,MAAM,wBAAwB,EAC5D,KAAK,EAAE,EACP,MAAiB;AAEpB,WAAO,MAAM,KAAK,YAAY,GAAG,IAAI;EACvC;EAEA,MAAM,aAAa,SAA2D;AAC5E,QAAI,QAAQ,iBAAiB,KAAK,MAAM;AACxC,UAAM,WAAsB,CAAC;AAE7B,QAAI,SAAS,YAAY,QAAW;AAClC,eAAS;AACT,eAAS,KAAK,QAAQ,UAAU,IAAI,CAAC;IACvC;AAEA,aAAS;AAET,QAAI,OAAO,KAAK,GAAG,QAAQ,KAAK;AAChC,eAAW,WAAW,UAAU;AAC9B,aAAO,KAAK,KAAK,OAAO;IAC1B;AAEA,UAAM,SAAS,MAAM,KAAK,IAAe;AACzC,WAAO,OAAO,QAAQ,IAAI,CAAC,QAAQ,KAAK,YAAY,GAAG,CAAC;EAC1D;EAEA,MAAM,aAAa,IAAY,SAAgD;AAC7E,UAAM,WAAW,MAAM,KAAK,UAAU,EAAE;AACxC,QAAI,CAAC,SAAU;AAEf,UAAM,SAAS,EAAE,GAAG,UAAU,GAAG,SAAS,YAAW,oBAAI,KAAK,GAAE,YAAY,EAAE;AAE9E,UAAM,KAAK,GACR;MACC,UAAU,KAAK,MAAM;;;IAGvB,EACC;MACC,OAAO;MACP,OAAO;MACP,KAAK,UAAU,OAAO,KAAK;MAC3B,OAAO,UAAU,IAAI;MACrB,OAAO;MACP,OAAO;MACP;IACF,EACC,IAAI;EACT;EAEA,MAAM,aAAa,IAA2B;AAC5C,UAAM,KAAK,GAAG,QAAQ,eAAe,KAAK,MAAM,wBAAwB,EAAE,KAAK,EAAE,EAAE,IAAI;EACzF;;EAIA,MAAM,mBAAmB,SAAkD;AACzE,UAAM,MAAM,MAAM,KAAK,GACpB,QAAQ,iBAAiB,KAAK,MAAM,iCAAiC,EACrE,KAAK,OAAO,EACZ,MAAoB;AAEvB,WAAO,MAAM,KAAK,eAAe,GAAG,IAAI;EAC1C;EAEA,MAAM,sBAAsB,QAAwC;AAClE,UAAM,KAAK,GACR;MACC,eAAe,KAAK,MAAM;;;;;;;IAO5B,EACC,KAAK,OAAO,SAAS,OAAO,QAAQ,OAAO,WAAW,OAAO,YAAY,OAAO,UAAU,EAC1F,IAAI;EACT;;EAIA,MAAM,YAAY,SAMc;AAC9B,UAAM,aAAuB,CAAC,KAAK;AACnC,UAAM,WAAsB,CAAC;AAE7B,QAAI,SAAS,UAAU;AACrB,iBAAW,KAAK,eAAe;AAC/B,eAAS,KAAK,QAAQ,QAAQ;IAChC;AACA,QAAI,SAAS,SAAS;AACpB,iBAAW,KAAK,wBAAwB;AACxC,eAAS,KAAK,QAAQ,OAAO;IAC/B;AACA,QAAI,SAAS,OAAO;AAClB,iBAAW,KAAK,gBAAgB;AAChC,eAAS,KAAK,QAAQ,KAAK;IAC7B;AACA,QAAI,SAAS,OAAO;AAClB,iBAAW,KAAK,gBAAgB;AAChC,eAAS,KAAK,QAAQ,KAAK;IAC7B;AAEA,UAAM,QAAQ,SAAS,SAAS;AAEhC,QAAI,OAAO,KAAK,GAAG;MACjB,iBAAiB,KAAK,MAAM;eACnB,WAAW,KAAK,OAAO,CAAC;;;IAGnC;AAEA,eAAW,WAAW,UAAU;AAC9B,aAAO,KAAK,KAAK,OAAO;IAC1B;AACA,WAAO,KAAK,KAAK,KAAK;AAEtB,UAAM,SAAS,MAAM,KAAK,IAAe;AACzC,WAAO,OAAO,QAAQ,IAAI,CAAC,QAAQ,KAAK,YAAY,GAAG,CAAC;EAC1D;;;;;EAOA,MAAM,QAAuB;AAC3B,UAAM,KAAK,GAAG,QAAQ,eAAe,KAAK,MAAM,UAAU,EAAE,IAAI;AAChE,UAAM,KAAK,GAAG,QAAQ,eAAe,KAAK,MAAM,UAAU,EAAE,IAAI;AAChE,UAAM,KAAK,GAAG,QAAQ,eAAe,KAAK,MAAM,SAAS,EAAE,IAAI;AAC/D,UAAM,KAAK,GAAG,QAAQ,eAAe,KAAK,MAAM,WAAW,EAAE,IAAI;AACjE,UAAM,KAAK,GAAG,QAAQ,eAAe,KAAK,MAAM,cAAc,EAAE,IAAI;EACtE;;;;EAKA,MAAM,WAKH;AACD,UAAM,CAAC,cAAc,gBAAgB,cAAc,eAAe,aAAa,cAAc,eAAe,eAAe,IAAI,MAAM,QAAQ,IAAI;MAC/I,KAAK,GAAG,QAAQ,iCAAiC,KAAK,MAAM,UAAU,EAAE,MAAyB;MACjG,KAAK,GAAG,QAAQ,iCAAiC,KAAK,MAAM,mCAAmC,EAAE,MAAyB;MAC1H,KAAK,GAAG,QAAQ,iCAAiC,KAAK,MAAM,UAAU,EAAE,MAAyB;MACjG,KAAK,GAAG,QAAQ,iCAAiC,KAAK,MAAM,2BAA2B,EAAE,MAAyB;MAClH,KAAK,GAAG,QAAQ,iCAAiC,KAAK,MAAM,SAAS,EAAE,MAAyB;MAChG,KAAK,GAAG,QAAQ,iCAAiC,KAAK,MAAM,0BAA0B,EAAE,MAAyB;MACjH,KAAK,GAAG,QAAQ,iCAAiC,KAAK,MAAM,WAAW,EAAE,MAAyB;MAClG,KAAK,GAAG,QAAQ,iCAAiC,KAAK,MAAM,6BAA6B,EAAE,MAAyB;IACtH,CAAC;AAED,WAAO;MACL,SAAS,EAAE,OAAO,cAAc,SAAS,GAAG,SAAS,gBAAgB,SAAS,EAAE;MAChF,SAAS,EAAE,OAAO,cAAc,SAAS,GAAG,QAAQ,eAAe,SAAS,EAAE;MAC9E,QAAQ,EAAE,OAAO,aAAa,SAAS,GAAG,QAAQ,cAAc,SAAS,EAAE;MAC3E,UAAU,EAAE,OAAO,eAAe,SAAS,GAAG,SAAS,iBAAiB,SAAS,EAAE;IACrF;EACF;;EAIQ,YAAY,KAAkC;AACpD,WAAO;MACL,IAAI,IAAI;MACR,iBAAiB,IAAI;MACrB,kBAAkB,IAAI;MACtB,WAAW,KAAK,MAAM,IAAI,UAAU;MACpC,UAAU,IAAI;MACd,aAAa,IAAI;MACjB,QAAQ,IAAI;MACZ,YAAY,IAAI;MAChB,WAAW,IAAI;MACf,WAAW,IAAI;MACf,mBAAmB,IAAI;MACvB,mBAAmB,IAAI,wBAAwB;MAC/C,UAAU,IAAI,WAAY,KAAK,MAAM,IAAI,QAAQ,IAAgC;IACnF;EACF;EAEQ,YAAY,KAAkC;AACpD,WAAO;MACL,IAAI,IAAI;MACR,UAAU,IAAI,aAAa;MAC3B,kBAAkB,IAAI;MACtB,UAAU,IAAI;MACd,YAAY,IAAI;MAChB,QAAQ,IAAI;MACZ,WAAW,IAAI;MACf,WAAW,IAAI,cAAc;MAC7B,QAAQ,IAAI,WAAW;MACvB,kBAAkB,IAAI,sBAAsB;MAC5C,YAAY,IAAI,cAAe,KAAK,MAAM,IAAI,WAAW,IAAiB;MAC1E,UAAU,IAAI,WAAY,KAAK,MAAM,IAAI,QAAQ,IAAgC;IACnF;EACF;EAEQ,WAAW,KAA8B;AAC/C,WAAO;MACL,IAAI,IAAI;MACR,eAAe,IAAI;MACnB,OAAO,IAAI;MACX,QAAQ,IAAI;MACZ,WAAW,IAAI;MACf,WAAW,IAAI;MACf,QAAQ,IAAI,WAAW;MACvB,eAAe,IAAI,kBAAkB;MACrC,UAAU,IAAI,aAAa;IAC7B;EACF;EAEQ,YAAY,KAA+B;AACjD,WAAO;MACL,IAAI,IAAI;MACR,MAAM,IAAI;MACV,aAAa,IAAI;MACjB,OAAO,KAAK,MAAM,IAAI,KAAK;MAC3B,SAAS,IAAI,YAAY;MACzB,eAAe,IAAI;MACnB,WAAW,IAAI;IACjB;EACF;EAEQ,eAAe,KAAoC;AACzD,WAAO;MACL,SAAS,IAAI;MACb,QAAQ,IAAI;MACZ,WAAW,IAAI;MACf,YAAY,IAAI;MAChB,YAAY,IAAI;IAClB;EACF;AACF;ACv0BA,IAAM,wBAAwB,CAAC,QAAQ,eAAe,eAAe,YAAY,WAAW;AAK5F,IAAM,yBAAyB;AAK/B,IAAM,2BAA2B;AAMjC,IAAM,yBAAyB;EAC7B;;EACA;;EACA;;EACA;;EACA;;AACF;AASO,SAAS,iBAAiB,SAAgC;AAC/D,MAAI,QAAQ,SAAS,0BAA0B;AAC7C,WAAO,qBAAqB,QAAQ,MAAM,eAAe,wBAAwB;EACnF;AAEA,aAAW,oBAAoB,wBAAwB;AACrD,QAAI,iBAAiB,KAAK,OAAO,GAAG;AAClC,aAAO,2EAA2E,gBAAgB;IACpG;EACF;AAEA,SAAO;AACT;AAoBO,IAAM,eAAN,MAAmB;EAChB;EACA,qBAA0C,oBAAI,IAAI;EAE1D,YAAY,OAAwB;AAClC,SAAK,QAAQ;EACf;;;;;;;;EASA,MAAM,aAAa,MAAgB,gBAA0D;AAC3F,UAAM,WAAW,MAAM,KAAK,MAAM,aAAa,EAAE,SAAS,KAAK,CAAC;AAChE,UAAM,aAAqD,CAAC;AAG5D,QAAI,cAA4B;AAChC,QAAI,iBAAiB;AAErB,eAAW,UAAU,UAAU;AAC7B,YAAM,SAAS,KAAK,sBAAsB,MAAM,QAAQ,cAAc;AACtE,iBAAW,KAAK,GAAG,OAAO,YAAY;AAGtC,iBAAW,SAAS,OAAO,cAAc;AACvC,cAAM,OAAO,OAAO,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM,MAAM;AAC3D,YAAI,QAAQ,KAAK,WAAW,gBAAgB;AAC1C,2BAAiB,KAAK;AACtB,wBAAc,KAAK;AAGnB,cAAI,KAAK,WAAW,SAAS;AAC3B,mBAAO;cACL,QAAQ;cACR,cAAc;cACd,UAAU;YACZ;UACF;QACF;MACF;IACF;AAGA,QAAI,WAAW,WAAW,GAAG;AAC3B,YAAM,gBAAgB,SAAS,SAAS,IAAI,SAAS,CAAC,EAAG,gBAAgB;AACzE,aAAO;QACL,QAAQ;QACR,cAAc,CAAC;QACf,UAAU;MACZ;IACF;AAEA,WAAO;MACL,QAAQ;MACR,cAAc;MACd,UAAU;IACZ;EACF;;;;EAKQ,sBACN,MACA,QACA,gBACwB;AACxB,UAAM,UAAkD,CAAC;AAGzD,UAAM,eAAe,OAAO,MAAM,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;AAEjG,eAAW,QAAQ,cAAc;AAC/B,YAAM,QAAQ,KAAK,aAAa,MAAM,MAAM,cAAc;AAC1D,UAAI,OAAO;AACT,gBAAQ,KAAK;UACX,QAAQ,KAAK;UACb,UAAU,KAAK;UACf,cAAc,MAAM;UACpB,cAAc,MAAM;QACtB,CAAC;AAGD,YAAI,KAAK,WAAW,SAAS;AAC3B,iBAAO,EAAE,QAAQ,SAAS,cAAc,SAAS,UAAU,KAAK;QAClE;MACF;IACF;AAEA,WAAO;MACL,QACE,QAAQ,SAAS,IACZ,aAAa,KAAK,CAAC,MAAM,QAAQ,KAAK,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,CAAC,GAAG,UACnE,OAAO,gBACP,OAAO;MACb,cAAc;MACd,UAAU,QAAQ,SAAS;IAC7B;EACF;;;;EAKQ,aACN,MACA,MACA,gBAC2C;AAC3C,YAAQ,KAAK,MAAM;MACjB,KAAK;AACH,eAAO,KAAK,oBAAoB,MAAM,IAAI;MAC5C,KAAK;AACH,eAAO,KAAK,kBAAkB,MAAM,IAAI;MAC1C,KAAK;AACH,eAAO,KAAK,gBAAgB,MAAM,IAAI;MACxC,KAAK;AACH,eAAO,KAAK,oBAAoB,MAAM,IAAI;MAC5C,KAAK;AACH,eAAO,KAAK,qBAAqB,gBAAgB,IAAI;MACvD;AACE,eAAO;IACX;EACF;;;;EAKQ,oBACN,MACA,MAC2C;AAC3C,UAAM,UAAU,KAAK,QAAQ,YAAY;AACzC,UAAM,SAAS,KAAK,gBAAgB;AAEpC,eAAW,SAAS,QAAQ;AAC1B,YAAM,QAAQ,KAAK,aAAa,MAAM,KAAK;AAC3C,UAAI,OAAO,UAAU,YAAY,MAAM,YAAY,EAAE,SAAS,OAAO,GAAG;AACtE,eAAO,EAAE,OAAO,OAAO,QAAQ;MACjC;IACF;AACA,WAAO;EACT;;;;;;;EAQQ,kBACN,MACA,MAC2C;AAC3C,QAAI,QAAQ,KAAK,mBAAmB,IAAI,KAAK,EAAE;AAC/C,QAAI,CAAC,OAAO;AAEV,YAAM,gBAAgB,iBAAiB,KAAK,OAAO;AACnD,UAAI,eAAe;AACjB,gBAAQ,KAAK,sBAAsB,KAAK,IAAI,MAAM,aAAa,EAAE;MACnE;AAEA,UAAI;AACF,gBAAQ,IAAI,OAAO,KAAK,SAAS,GAAG;AACpC,aAAK,mBAAmB,IAAI,KAAK,IAAI,KAAK;MAC5C,QAAQ;AAEN,eAAO;MACT;IACF;AAEA,UAAM,SAAS,KAAK,gBAAgB;AAEpC,eAAW,SAAS,QAAQ;AAC1B,YAAM,QAAQ,KAAK,aAAa,MAAM,KAAK;AAC3C,UAAI,OAAO,UAAU,UAAU;AAE7B,cAAM,iBAAiB,MAAM,SAAS,yBAClC,MAAM,MAAM,GAAG,sBAAsB,IACrC;AAEJ,cAAM,QAAQ,eAAe,MAAM,KAAK;AACxC,YAAI,OAAO;AACT,iBAAO,EAAE,OAAO,OAAO,MAAM,CAAC,EAAE;QAClC;MACF;IACF;AACA,WAAO;EACT;;;;EAKQ,gBACN,MACA,MAC2C;AAC3C,UAAM,OAAO,KAAK,KAAK,QAAQ,CAAC;AAChC,UAAM,YAAY,KAAK,QAAQ,YAAY;AAE3C,UAAM,QAAQ,KAAK,KAAK,CAAC,MAAM,EAAE,YAAY,MAAM,SAAS;AAC5D,QAAI,OAAO;AACT,aAAO,EAAE,OAAO,QAAQ,OAAO,MAAM;IACvC;AACA,WAAO;EACT;;;;EAKQ,oBACN,MACA,MAC2C;AAC3C,UAAM,UAAU,KAAK,KAAK,SAAS,YAAY,KAAK;AACpD,QAAI,YAAY,KAAK,QAAQ,YAAY,GAAG;AAC1C,aAAO,EAAE,OAAO,WAAW,OAAO,QAAQ;IAC5C;AACA,WAAO;EACT;;;;;EAMQ,qBACN,gBACA,MAC2C;AAC3C,QAAI,CAAC,eAAgB,QAAO;AAE5B,UAAM,SAAS,eAAe,YAAY;AAC1C,UAAM,UAAU,KAAK,QAAQ,YAAY;AAGzC,QAAI,QAAQ,WAAW,IAAI,GAAG;AAC5B,YAAM,SAAS,QAAQ,MAAM,CAAC;AAC9B,UAAI,OAAO,SAAS,MAAM,KAAK,WAAW,QAAQ;AAChD,eAAO,EAAE,OAAO,YAAY,OAAO,OAAO;MAC5C;IACF,WAAW,WAAW,SAAS;AAC7B,aAAO,EAAE,OAAO,YAAY,OAAO,OAAO;IAC5C;AACA,WAAO;EACT;;;;EAKQ,aAAa,MAAgB,OAAwB;AAE3D,QAAI,MAAM,SAAS,GAAG,GAAG;AACvB,YAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,UAAI,QAAiB,KAAK;AAC1B,iBAAW,QAAQ,OAAO;AACxB,YAAI,SAAS,OAAO,UAAU,YAAY,QAAQ,OAAO;AACvD,kBAAS,MAAkC,IAAI;QACjD,OAAO;AACL,iBAAO;QACT;MACF;AACA,aAAO;IACT;AAEA,WAAQ,KAAK,KAAiC,KAAK;EACrD;;;;EAKA,aAAmB;AACjB,SAAK,mBAAmB,MAAM;EAChC;;;;;;EAOA,iBAAiB,QAAiE;AAChF,UAAM,SAAmB,CAAC;AAC1B,UAAM,WAAqB,CAAC;AAE5B,eAAW,QAAQ,OAAO,OAAO;AAC/B,UAAI,KAAK,SAAS,WAAW,KAAK,SAAS;AAEzC,cAAM,gBAAgB,iBAAiB,KAAK,OAAO;AACnD,YAAI,eAAe;AACjB,mBAAS,KAAK,SAAS,KAAK,IAAI,MAAM,aAAa,EAAE;QACvD;AAEA,YAAI;AACF,gBAAM,QAAQ,IAAI,OAAO,KAAK,SAAS,GAAG;AAC1C,eAAK,mBAAmB,IAAI,KAAK,IAAI,KAAK;QAC5C,SAAS,KAAK;AACZ,iBAAO,KAAK,SAAS,KAAK,IAAI,wBAAwB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;QAC1G;MACF;IACF;AAEA,WAAO,EAAE,QAAQ,SAAS;EAC5B;AACF;AChXA,IAAM,iBAAoC;EACxC,WAAW;EACX,YAAY;AACd;AAqBO,IAAM,cAAN,MAAkB;EACf;EACA;EAER,YAAY,OAAwB,QAAqC;AACvE,SAAK,QAAQ;AACb,SAAK,SAAS,EAAE,GAAG,gBAAgB,GAAG,OAAO;EAC/C;;;;;;;EAQA,MAAM,gBAAgB,SAA2C;AAC/D,QAAI,SAAS,MAAM,KAAK,MAAM,mBAAmB,OAAO;AAExD,QAAI,CAAC,QAAQ;AAEX,eAAS;QACP;QACA,QAAQ,KAAK,OAAO;QACpB,WAAW,KAAK,OAAO;QACvB,aAAY,oBAAI,KAAK,GAAE,YAAY;QACnC,YAAY,KAAK,OAAO;MAC1B;IACF;AAGA,aAAS,KAAK,aAAa,MAAM;AAEjC,QAAI,OAAO,SAAS,GAAG;AACrB,YAAM,UAAU,KAAK,mBAAmB,MAAM;AAC9C,YAAM,aAAa,KAAK,MAAM,IAAI,KAAK,OAAO,EAAE,QAAQ,IAAI,KAAK,IAAI,KAAK,GAAI;AAG9E,YAAM,KAAK,MAAM,sBAAsB,MAAM;AAE7C,aAAO;QACL,SAAS;QACT,WAAW;QACX;QACA,YAAY,KAAK,IAAI,GAAG,UAAU;MACpC;IACF;AAGA,WAAO,UAAU;AACjB,UAAM,KAAK,MAAM,sBAAsB,MAAM;AAE7C,WAAO;MACL,SAAS;MACT,WAAW,KAAK,MAAM,OAAO,MAAM;MACnC,SAAS,KAAK,mBAAmB,MAAM;IACzC;EACF;;;;;;;EAQA,MAAM,MAAM,SAA2C;AACrD,QAAI,SAAS,MAAM,KAAK,MAAM,mBAAmB,OAAO;AAExD,QAAI,CAAC,QAAQ;AACX,aAAO;QACL,SAAS;QACT,WAAW,KAAK,OAAO;QACvB,UAAS,oBAAI,KAAK,GAAE,YAAY;MAClC;IACF;AAEA,aAAS,KAAK,aAAa,MAAM;AAEjC,WAAO;MACL,SAAS,OAAO,UAAU;MAC1B,WAAW,KAAK,MAAM,OAAO,MAAM;MACnC,SAAS,KAAK,mBAAmB,MAAM;MACvC,YACE,OAAO,SAAS,IACZ,KAAK,MAAM,IAAI,KAAK,KAAK,mBAAmB,MAAM,CAAC,EAAE,QAAQ,IAAI,KAAK,IAAI,KAAK,GAAI,IACnF;IACR;EACF;;;;;;EAOA,MAAM,MAAM,SAAgC;AAC1C,UAAM,SAA0B;MAC9B;MACA,QAAQ,KAAK,OAAO;MACpB,WAAW,KAAK,OAAO;MACvB,aAAY,oBAAI,KAAK,GAAE,YAAY;MACnC,YAAY,KAAK,OAAO;IAC1B;AACA,UAAM,KAAK,MAAM,sBAAsB,MAAM;EAC/C;;;;EAKQ,aAAa,QAA0C;AAC7D,UAAM,MAAM,oBAAI,KAAK;AACrB,UAAM,aAAa,IAAI,KAAK,OAAO,UAAU;AAC7C,UAAM,oBAAoB,IAAI,QAAQ,IAAI,WAAW,QAAQ,MAAM,MAAO,KAAK;AAE/E,UAAM,cAAc,mBAAmB,OAAO;AAE9C,QAAI,eAAe,GAAG;AAEpB,aAAO,SAAS,KAAK,IAAI,OAAO,WAAW,OAAO,SAAS,KAAK,MAAM,WAAW,CAAC;AAClF,aAAO,aAAa,IAAI,YAAY;IACtC;AAEA,WAAO;EACT;;;;EAKQ,mBAAmB,QAAiC;AAC1D,QAAI,OAAO,UAAU,OAAO,WAAW;AACrC,cAAO,oBAAI,KAAK,GAAE,YAAY;IAChC;AAGA,UAAM,eAAe,KAAK,IAAI,GAAG,IAAI,OAAO,MAAM;AAClD,UAAM,kBAAkB,eAAe,OAAO;AAC9C,UAAM,eAAe,kBAAkB,KAAK,KAAK;AAEjD,UAAM,aAAa,IAAI,KAAK,OAAO,UAAU;AAC7C,WAAO,IAAI,KAAK,WAAW,QAAQ,IAAI,YAAY,EAAE,YAAY;EACnE;;;;EAKA,YAA+B;AAC7B,WAAO,EAAE,GAAG,KAAK,OAAO;EAC1B;AACF;AC5KA,IAAI,oBAAoB;AACxB,IAAI,kBAAkB;AAKtB,SAAS,UAAU,MAAkC;AAEnD,MAAI,OAAO,YAAY,eAAe,SAAS,KAAK;AAClD,WAAO,QAAQ,IAAI,IAAI;EACzB;AAEA,SAAO;AACT;AAWO,SAAS,iBAAiB,SAA4C;AAC3E,sBAAoB;AACpB,oBAAkB,SAAS,gBAAgB;AAE3C,QAAM,UAAU,UAAU,UAAU;AACpC,MAAI,YAAY,iBAAiB,YAAY,QAAQ;AACnD,YAAQ;MACN;IAGF;EACF;AACF;AAQO,SAAS,sBAA+B;AAC7C,MAAI,CAAC,mBAAmB;AACtB,WAAO;EACT;AAGA,MAAI,iBAAiB;AACnB,WAAO;EACT;AAGA,QAAM,aAAa,UAAU,oBAAoB,MAAM;AACvD,SAAO;AACT;AAMO,SAAS,wBAAwB,SAAuB;AAC7D,MAAI,CAAC,mBAAmB;AACtB,UAAM,aAAa,OAAO,YAAY,eAAe,SAAS;AAC9D,UAAM,UAAU,aACZ,2EACA;AAEJ,UAAM,IAAI;MACR,8BAA8B,OAAO,kDAClC,OAAO;IAEZ;EACF;AAGA,MAAI,iBAAiB;AACnB;EACF;AAGA,QAAM,aAAa,UAAU,oBAAoB,MAAM;AACvD,MAAI,CAAC,YAAY;AACf,UAAM,IAAI;MACR,oDAAoD,OAAO;IAG7D;EACF;AACF;","names":["newHash","generateUUID"]}
|
|
1
|
+
{"version":3,"sources":["../src/federation.ts","../../core/src/binary.ts","../../core/src/base64.ts","../../core/src/errors.ts","../../core/src/data-url.ts","../../core/src/uri.ts","../../core/src/image.ts","../../core/src/uuid.ts","../../federation/src/logger.ts","../../federation/src/activitypub.ts","../../federation/src/sync-engine.ts","../../federation/src/state-store.ts","../../federation/src/d1-store.ts","../../federation/src/adapters/base.ts","../../federation/src/adapters/http.ts","../../federation/src/adapters/sillytavern.ts","../../federation/src/routes/webfinger.ts","../../federation/src/routes/nodeinfo.ts","../../federation/src/routes/actor.ts","../../federation/src/moderation/activities.ts","../../federation/src/http-signatures.ts","../../federation/src/routes/inbox.ts","../../federation/src/moderation/store.ts","../../federation/src/moderation/d1-store.ts","../../federation/src/moderation/policy-engine.ts","../../federation/src/moderation/rate-limiter.ts","../../federation/src/index.ts"],"sourcesContent":["export * from '@character-foundry/federation';\n","/**\n * Binary Data Utilities\n *\n * Universal binary data operations using Uint8Array.\n * Works in both Node.js and browser environments.\n */\n\n/**\n * Universal binary data type (works in both environments)\n */\nexport type BinaryData = Uint8Array;\n\n/**\n * Read a 32-bit big-endian unsigned integer\n */\nexport function readUInt32BE(data: BinaryData, offset: number): number {\n return (\n (data[offset]! << 24) |\n (data[offset + 1]! << 16) |\n (data[offset + 2]! << 8) |\n data[offset + 3]!\n ) >>> 0;\n}\n\n/**\n * Write a 32-bit big-endian unsigned integer\n */\nexport function writeUInt32BE(data: BinaryData, value: number, offset: number): void {\n data[offset] = (value >>> 24) & 0xff;\n data[offset + 1] = (value >>> 16) & 0xff;\n data[offset + 2] = (value >>> 8) & 0xff;\n data[offset + 3] = value & 0xff;\n}\n\n/**\n * Read a 16-bit big-endian unsigned integer\n */\nexport function readUInt16BE(data: BinaryData, offset: number): number {\n return ((data[offset]! << 8) | data[offset + 1]!) >>> 0;\n}\n\n/**\n * Write a 16-bit big-endian unsigned integer\n */\nexport function writeUInt16BE(data: BinaryData, value: number, offset: number): void {\n data[offset] = (value >>> 8) & 0xff;\n data[offset + 1] = value & 0xff;\n}\n\n/**\n * Find a byte sequence in binary data\n */\nexport function indexOf(data: BinaryData, search: BinaryData, fromIndex = 0): number {\n outer: for (let i = fromIndex; i <= data.length - search.length; i++) {\n for (let j = 0; j < search.length; j++) {\n if (data[i + j] !== search[j]) continue outer;\n }\n return i;\n }\n return -1;\n}\n\n/**\n * Concatenate multiple binary arrays\n */\nexport function concat(...arrays: BinaryData[]): BinaryData {\n const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const arr of arrays) {\n result.set(arr, offset);\n offset += arr.length;\n }\n return result;\n}\n\n/**\n * Slice binary data (returns a view, not a copy)\n */\nexport function slice(data: BinaryData, start: number, end?: number): BinaryData {\n return data.subarray(start, end);\n}\n\n/**\n * Copy a portion of binary data (returns a new array)\n */\nexport function copy(data: BinaryData, start: number, end?: number): BinaryData {\n return data.slice(start, end);\n}\n\n/**\n * Convert string to binary (UTF-8)\n */\nexport function fromString(str: string): BinaryData {\n return new TextEncoder().encode(str);\n}\n\n/**\n * Convert binary to string (UTF-8)\n */\nexport function toString(data: BinaryData): string {\n return new TextDecoder().decode(data);\n}\n\n/**\n * Convert string to binary (Latin1 - for PNG keywords and similar)\n */\nexport function fromLatin1(str: string): BinaryData {\n const result = new Uint8Array(str.length);\n for (let i = 0; i < str.length; i++) {\n result[i] = str.charCodeAt(i) & 0xff;\n }\n return result;\n}\n\n/**\n * Convert binary to string (Latin1)\n */\nexport function toLatin1(data: BinaryData): string {\n let result = '';\n for (let i = 0; i < data.length; i++) {\n result += String.fromCharCode(data[i]!);\n }\n return result;\n}\n\n/**\n * Compare two binary arrays for equality\n */\nexport function equals(a: BinaryData, b: BinaryData): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n\n/**\n * Create a new Uint8Array filled with zeros\n */\nexport function alloc(size: number): BinaryData {\n return new Uint8Array(size);\n}\n\n/**\n * Create a Uint8Array from an array of numbers\n */\nexport function from(data: number[] | ArrayBuffer | BinaryData): BinaryData {\n if (data instanceof Uint8Array) {\n return data;\n }\n if (data instanceof ArrayBuffer) {\n return new Uint8Array(data);\n }\n return new Uint8Array(data);\n}\n\n/**\n * Check if value is a Uint8Array\n */\nexport function isBinaryData(value: unknown): value is BinaryData {\n return value instanceof Uint8Array;\n}\n\n/**\n * Convert Node.js Buffer to Uint8Array (no-op if already Uint8Array)\n * This provides compatibility when interfacing with Node.js code\n */\nexport function toUint8Array(data: BinaryData | Buffer): BinaryData {\n if (data instanceof Uint8Array) {\n // Buffer extends Uint8Array, but we want a plain Uint8Array\n // This ensures we get a proper Uint8Array in all cases\n if (Object.getPrototypeOf(data).constructor.name === 'Buffer') {\n return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);\n }\n return data;\n }\n return new Uint8Array(data);\n}\n\n/**\n * Convert binary data to hex string\n */\nexport function toHex(data: BinaryData): string {\n return Array.from(data)\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\n/**\n * Convert hex string to binary data\n */\nexport function fromHex(hex: string): BinaryData {\n const bytes = new Uint8Array(hex.length / 2);\n for (let i = 0; i < bytes.length; i++) {\n bytes[i] = parseInt(hex.substr(i * 2, 2), 16);\n }\n return bytes;\n}\n","/**\n * Universal Base64 Encoding/Decoding\n *\n * Works in both Node.js and browser environments.\n */\n\nimport type { BinaryData } from './binary.js';\n\n/**\n * Check if we're in a Node.js environment\n */\nconst isNode = typeof process !== 'undefined' &&\n process.versions != null &&\n process.versions.node != null;\n\n/**\n * Threshold for switching to chunked encoding in browsers (1MB)\n * Below this, simple string concatenation is fast enough.\n * Above this, quadratic string growth becomes a problem.\n */\nconst LARGE_BUFFER_THRESHOLD = 1024 * 1024;\n\n/**\n * Encode binary data to base64 string\n *\n * PERFORMANCE: For large buffers (>1MB) in browsers, this automatically\n * uses the chunked implementation to avoid quadratic string concatenation.\n */\nexport function encode(data: BinaryData): string {\n if (isNode) {\n // Node.js: Buffer handles large data efficiently\n return Buffer.from(data).toString('base64');\n }\n\n // Browser: use chunked encoding for large buffers to avoid O(n²) string growth\n if (data.length > LARGE_BUFFER_THRESHOLD) {\n return encodeChunked(data);\n }\n\n // Small buffers: simple approach is fast enough\n let binary = '';\n for (let i = 0; i < data.length; i++) {\n binary += String.fromCharCode(data[i]!);\n }\n return btoa(binary);\n}\n\n/**\n * Decode base64 string to binary data\n */\nexport function decode(base64: string): BinaryData {\n if (isNode) {\n // Node.js: use Buffer\n return new Uint8Array(Buffer.from(base64, 'base64'));\n }\n\n // Browser: use atob\n const binary = atob(base64);\n const result = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n result[i] = binary.charCodeAt(i);\n }\n return result;\n}\n\n/**\n * Check if a string is valid base64\n */\nexport function isBase64(str: string): boolean {\n if (str.length === 0) return false;\n // Base64 regex: only valid base64 characters, length multiple of 4 (with padding)\n const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;\n return base64Regex.test(str) && str.length % 4 === 0;\n}\n\n/**\n * Encode binary data to URL-safe base64 string\n * Replaces + with -, / with _, and removes padding\n */\nexport function encodeUrlSafe(data: BinaryData): string {\n return encode(data)\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n}\n\n/**\n * Decode URL-safe base64 string to binary data\n */\nexport function decodeUrlSafe(base64: string): BinaryData {\n // Add back padding if needed\n let padded = base64\n .replace(/-/g, '+')\n .replace(/_/g, '/');\n\n while (padded.length % 4 !== 0) {\n padded += '=';\n }\n\n return decode(padded);\n}\n\n/**\n * Chunk size for encoding large buffers (64KB)\n * Prevents stack overflow when using String.fromCharCode with spread operator\n */\nconst ENCODE_CHUNK_SIZE = 64 * 1024;\n\n/**\n * Encode binary data to base64 string with chunking for large buffers.\n * Handles buffers >10MB without stack overflow.\n *\n * @param data - Binary data to encode\n * @returns Base64 encoded string\n *\n * @example\n * ```typescript\n * const largeBuffer = new Uint8Array(20 * 1024 * 1024); // 20MB\n * const base64 = encodeChunked(largeBuffer); // No stack overflow\n * ```\n */\nexport function encodeChunked(data: BinaryData): string {\n if (isNode) {\n // Node.js: Buffer handles large data efficiently\n return Buffer.from(data).toString('base64');\n }\n\n // Browser: process in chunks to avoid stack overflow\n const chunks: string[] = [];\n\n for (let i = 0; i < data.length; i += ENCODE_CHUNK_SIZE) {\n const chunk = data.subarray(i, Math.min(i + ENCODE_CHUNK_SIZE, data.length));\n let binary = '';\n for (let j = 0; j < chunk.length; j++) {\n binary += String.fromCharCode(chunk[j]!);\n }\n chunks.push(binary);\n }\n\n return btoa(chunks.join(''));\n}\n","/**\n * Error Classes\n *\n * Specific error types for character card operations.\n * All errors extend FoundryError for consistent handling.\n */\n\n/** Symbol to identify FoundryError instances across ESM/CJS boundaries */\nconst FOUNDRY_ERROR_MARKER = Symbol.for('@character-foundry/core:FoundryError');\n\n/**\n * Base error class for all Character Foundry errors\n */\nexport class FoundryError extends Error {\n /** @internal Marker for cross-module identification */\n readonly [FOUNDRY_ERROR_MARKER] = true;\n\n constructor(message: string, public readonly code: string) {\n super(message);\n this.name = 'FoundryError';\n // Maintains proper stack trace in V8 environments\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, this.constructor);\n }\n }\n}\n\n/**\n * Error during card parsing\n */\nexport class ParseError extends FoundryError {\n constructor(message: string, public readonly format?: string) {\n super(message, 'PARSE_ERROR');\n this.name = 'ParseError';\n }\n}\n\n/**\n * Error during card validation\n */\nexport class ValidationError extends FoundryError {\n constructor(message: string, public readonly field?: string) {\n super(message, 'VALIDATION_ERROR');\n this.name = 'ValidationError';\n }\n}\n\n/**\n * Asset not found in card or archive\n */\nexport class AssetNotFoundError extends FoundryError {\n constructor(public readonly uri: string) {\n super(`Asset not found: ${uri}`, 'ASSET_NOT_FOUND');\n this.name = 'AssetNotFoundError';\n }\n}\n\n/**\n * Format not supported for operation\n */\nexport class FormatNotSupportedError extends FoundryError {\n constructor(public readonly format: string, operation?: string) {\n const msg = operation\n ? `Format '${format}' not supported for ${operation}`\n : `Format not supported: ${format}`;\n super(msg, 'FORMAT_NOT_SUPPORTED');\n this.name = 'FormatNotSupportedError';\n }\n}\n\n/**\n * File size exceeds limits\n */\nexport class SizeLimitError extends FoundryError {\n constructor(\n public readonly actualSize: number,\n public readonly maxSize: number,\n context?: string\n ) {\n const actualMB = (actualSize / 1024 / 1024).toFixed(2);\n const maxMB = (maxSize / 1024 / 1024).toFixed(2);\n const msg = context\n ? `${context}: Size ${actualMB}MB exceeds limit ${maxMB}MB`\n : `Size ${actualMB}MB exceeds limit ${maxMB}MB`;\n super(msg, 'SIZE_LIMIT_EXCEEDED');\n this.name = 'SizeLimitError';\n }\n}\n\n/**\n * Path traversal or unsafe path detected\n */\nexport class PathTraversalError extends FoundryError {\n constructor(public readonly path: string) {\n super(`Unsafe path detected: ${path}`, 'PATH_TRAVERSAL');\n this.name = 'PathTraversalError';\n }\n}\n\n/**\n * Export operation would lose data\n */\nexport class DataLossError extends FoundryError {\n constructor(\n public readonly lostFields: string[],\n public readonly targetFormat: string\n ) {\n const fields = lostFields.slice(0, 3).join(', ');\n const more = lostFields.length > 3 ? ` and ${lostFields.length - 3} more` : '';\n super(\n `Export to ${targetFormat} would lose: ${fields}${more}`,\n 'DATA_LOSS'\n );\n this.name = 'DataLossError';\n }\n}\n\n/**\n * Check if an error is a FoundryError\n *\n * Uses Symbol.for() marker instead of instanceof to handle dual ESM/CJS package loading.\n * In dual-package environments, instanceof can fail if the error comes from a different\n * module instance (e.g., ESM vs CJS version of the same package). Symbol.for() creates\n * a global symbol shared across all module instances.\n */\nexport function isFoundryError(error: unknown): error is FoundryError {\n return (\n error instanceof Error &&\n FOUNDRY_ERROR_MARKER in error &&\n (error as Record<symbol, unknown>)[FOUNDRY_ERROR_MARKER] === true\n );\n}\n\n/**\n * Wrap unknown errors in a FoundryError\n */\nexport function wrapError(error: unknown, context?: string): FoundryError {\n if (isFoundryError(error)) {\n return error;\n }\n\n const message = error instanceof Error\n ? error.message\n : String(error);\n\n return new FoundryError(\n context ? `${context}: ${message}` : message,\n 'UNKNOWN_ERROR'\n );\n}\n","/**\n * Data URL Utilities\n *\n * Convert between Uint8Array buffers and data URLs.\n * Handles large buffers (>10MB) without stack overflow by processing in chunks.\n */\n\nimport type { BinaryData } from './binary.js';\nimport { encodeChunked as base64Encode, decode as base64Decode } from './base64.js';\nimport { ValidationError } from './errors.js';\n\n/**\n * Convert Uint8Array to data URL.\n * Handles large buffers (>10MB) without stack overflow by processing in chunks.\n *\n * @param buffer - Binary data to encode\n * @param mimeType - MIME type for the data URL (e.g., 'image/png', 'application/octet-stream')\n * @returns Data URL string\n *\n * @example\n * ```typescript\n * const png = new Uint8Array([...]);\n * const dataUrl = toDataURL(png, 'image/png');\n * // => \"data:image/png;base64,iVBORw0KGgo...\"\n * ```\n */\nexport function toDataURL(buffer: BinaryData, mimeType: string): string {\n // Use chunked encoding to handle large buffers without stack overflow\n const base64 = base64Encode(buffer);\n return `data:${mimeType};base64,${base64}`;\n}\n\n/**\n * Parse a data URL back to buffer and MIME type.\n * Validates the data URL format before parsing.\n *\n * @param dataUrl - Data URL string to parse\n * @returns Object containing the decoded buffer and MIME type\n * @throws Error if the data URL format is invalid\n *\n * @example\n * ```typescript\n * const { buffer, mimeType } = fromDataURL('data:image/png;base64,iVBORw0KGgo...');\n * // buffer: Uint8Array\n * // mimeType: 'image/png'\n * ```\n */\nexport function fromDataURL(dataUrl: string): { buffer: Uint8Array; mimeType: string } {\n // Validate data URL format\n if (!dataUrl.startsWith('data:')) {\n throw new ValidationError('Invalid data URL: must start with \"data:\"', 'dataUrl');\n }\n\n const commaIndex = dataUrl.indexOf(',');\n if (commaIndex === -1) {\n throw new ValidationError('Invalid data URL: missing comma separator', 'dataUrl');\n }\n\n const header = dataUrl.slice(5, commaIndex); // Skip 'data:'\n const data = dataUrl.slice(commaIndex + 1);\n\n // Parse header: [<mediatype>][;base64]\n let mimeType = 'text/plain';\n let isBase64 = false;\n\n const parts = header.split(';');\n for (const part of parts) {\n if (part === 'base64') {\n isBase64 = true;\n } else if (part && !part.includes('=')) {\n // MIME type (not a parameter like charset=utf-8)\n mimeType = part;\n }\n }\n\n if (!isBase64) {\n // URL-encoded text data\n throw new ValidationError('Non-base64 data URLs are not supported', 'dataUrl');\n }\n\n const buffer = base64Decode(data);\n return { buffer, mimeType };\n}\n\n/**\n * Check if a string is a valid data URL\n *\n * @param str - String to check\n * @returns true if the string is a valid data URL format\n */\nexport function isDataURL(str: string): boolean {\n if (!str.startsWith('data:')) return false;\n const commaIndex = str.indexOf(',');\n if (commaIndex === -1) return false;\n const header = str.slice(5, commaIndex);\n return header.includes('base64');\n}\n","/**\n * URI Utilities\n *\n * Handles different asset URI schemes used in character cards.\n * Supports: embeded://, embedded://, ccdefault:, https://, http://,\n * data:, file://, __asset:, asset:, chara-ext-asset_\n */\n\nexport type URIScheme =\n | 'embeded' // embeded:// (CharX standard, note intentional typo)\n | 'ccdefault' // ccdefault:\n | 'https' // https://\n | 'http' // http://\n | 'data' // data:mime;base64,...\n | 'file' // file://\n | 'internal' // Internal asset ID (UUID/string)\n | 'pngchunk' // PNG chunk reference (__asset:, asset:, chara-ext-asset_)\n | 'unknown';\n\nexport interface ParsedURI {\n scheme: URIScheme;\n originalUri: string;\n normalizedUri: string; // Normalized form of the URI\n path?: string; // For embeded://, file://\n url?: string; // For http://, https://\n data?: string; // For data: URIs\n mimeType?: string; // For data: URIs\n encoding?: string; // For data: URIs (e.g., base64)\n chunkKey?: string; // For pngchunk - the key/index to look up\n chunkCandidates?: string[]; // For pngchunk - all possible chunk keys to search\n}\n\n/**\n * Normalize a URI to its canonical form\n * Handles common typos and variant formats\n */\nexport function normalizeURI(uri: string): string {\n const trimmed = uri.trim();\n\n // Fix embedded:// -> embeded:// (common typo, CharX spec uses single 'd')\n if (trimmed.startsWith('embedded://')) {\n return 'embeded://' + trimmed.substring('embedded://'.length);\n }\n\n // Normalize PNG chunk references to pngchunk: scheme\n if (trimmed.startsWith('__asset:')) {\n const id = trimmed.substring('__asset:'.length);\n return `pngchunk:${id}`;\n }\n if (trimmed.startsWith('asset:')) {\n const id = trimmed.substring('asset:'.length);\n return `pngchunk:${id}`;\n }\n if (trimmed.startsWith('chara-ext-asset_:')) {\n const id = trimmed.substring('chara-ext-asset_:'.length);\n return `pngchunk:${id}`;\n }\n if (trimmed.startsWith('chara-ext-asset_')) {\n const id = trimmed.substring('chara-ext-asset_'.length);\n return `pngchunk:${id}`;\n }\n\n return trimmed;\n}\n\n/**\n * Parse a URI and determine its scheme and components\n */\nexport function parseURI(uri: string): ParsedURI {\n const trimmed = uri.trim();\n const normalized = normalizeURI(trimmed);\n\n // PNG chunk references (__asset:, asset:, chara-ext-asset_, pngchunk:)\n if (\n trimmed.startsWith('__asset:') ||\n trimmed.startsWith('asset:') ||\n trimmed.startsWith('chara-ext-asset_') ||\n trimmed.startsWith('pngchunk:')\n ) {\n let assetId: string;\n if (trimmed.startsWith('__asset:')) {\n assetId = trimmed.substring('__asset:'.length);\n } else if (trimmed.startsWith('asset:')) {\n assetId = trimmed.substring('asset:'.length);\n } else if (trimmed.startsWith('chara-ext-asset_:')) {\n assetId = trimmed.substring('chara-ext-asset_:'.length);\n } else if (trimmed.startsWith('pngchunk:')) {\n assetId = trimmed.substring('pngchunk:'.length);\n } else {\n assetId = trimmed.substring('chara-ext-asset_'.length);\n }\n\n // Generate all possible chunk key variations for lookup\n const candidates = [\n assetId, // \"0\" or \"filename.png\"\n trimmed, // Original URI\n `asset:${assetId}`, // \"asset:0\"\n `__asset:${assetId}`, // \"__asset:0\"\n `__asset_${assetId}`, // \"__asset_0\"\n `chara-ext-asset_${assetId}`, // \"chara-ext-asset_0\"\n `chara-ext-asset_:${assetId}`, // \"chara-ext-asset_:0\"\n `pngchunk:${assetId}`, // \"pngchunk:0\"\n ];\n\n return {\n scheme: 'pngchunk',\n originalUri: uri,\n normalizedUri: normalized,\n chunkKey: assetId,\n chunkCandidates: candidates,\n };\n }\n\n // ccdefault: - use default asset\n if (trimmed === 'ccdefault:' || trimmed.startsWith('ccdefault:')) {\n return {\n scheme: 'ccdefault',\n originalUri: uri,\n normalizedUri: normalized,\n };\n }\n\n // embeded:// or embedded:// (normalize typo)\n if (trimmed.startsWith('embeded://') || trimmed.startsWith('embedded://')) {\n const path = trimmed.startsWith('embeded://')\n ? trimmed.substring('embeded://'.length)\n : trimmed.substring('embedded://'.length);\n return {\n scheme: 'embeded',\n originalUri: uri,\n normalizedUri: normalized,\n path,\n };\n }\n\n // https://\n if (trimmed.startsWith('https://')) {\n return {\n scheme: 'https',\n originalUri: uri,\n normalizedUri: normalized,\n url: trimmed,\n };\n }\n\n // http://\n if (trimmed.startsWith('http://')) {\n return {\n scheme: 'http',\n originalUri: uri,\n normalizedUri: normalized,\n url: trimmed,\n };\n }\n\n // data: URIs\n if (trimmed.startsWith('data:')) {\n const parsed = parseDataURI(trimmed);\n return {\n scheme: 'data',\n originalUri: uri,\n normalizedUri: normalized,\n ...parsed,\n };\n }\n\n // file://\n if (trimmed.startsWith('file://')) {\n const path = trimmed.substring('file://'.length);\n return {\n scheme: 'file',\n originalUri: uri,\n normalizedUri: normalized,\n path,\n };\n }\n\n // Internal asset ID (alphanumeric/UUID format)\n if (/^[a-zA-Z0-9_-]+$/.test(trimmed)) {\n return {\n scheme: 'internal',\n originalUri: uri,\n normalizedUri: normalized,\n path: trimmed,\n };\n }\n\n // Unknown scheme\n return {\n scheme: 'unknown',\n originalUri: uri,\n normalizedUri: normalized,\n };\n}\n\n/**\n * Parse a data URI into its components\n * Format: data:[<mediatype>][;base64],<data>\n */\nfunction parseDataURI(uri: string): { mimeType?: string; encoding?: string; data?: string } {\n const match = uri.match(/^data:([^;,]+)?(;base64)?,(.*)$/);\n\n if (!match) {\n return {};\n }\n\n return {\n mimeType: match[1] || 'text/plain',\n encoding: match[2] ? 'base64' : undefined,\n data: match[3],\n };\n}\n\n/**\n * Check if extension is an image format\n */\nexport function isImageExt(ext: string): boolean {\n const imageExts = ['png', 'jpg', 'jpeg', 'webp', 'gif', 'avif', 'bmp', 'svg'];\n return imageExts.includes(ext.toLowerCase());\n}\n\n/**\n * Check if extension is an audio format\n */\nexport function isAudioExt(ext: string): boolean {\n const audioExts = ['mp3', 'wav', 'ogg', 'flac', 'm4a', 'aac'];\n return audioExts.includes(ext.toLowerCase());\n}\n\n/**\n * Check if extension is a video format\n */\nexport function isVideoExt(ext: string): boolean {\n const videoExts = ['mp4', 'webm', 'avi', 'mov', 'mkv'];\n return videoExts.includes(ext.toLowerCase());\n}\n\n/** Safe MIME types for data: URIs that can be used in href/src */\nconst SAFE_DATA_URI_MIME_TYPES = new Set([\n // Images (safe for img src)\n 'image/png',\n 'image/jpeg',\n 'image/gif',\n 'image/webp',\n 'image/avif',\n 'image/bmp',\n 'image/x-icon',\n // Audio (safe for audio src)\n 'audio/mpeg',\n 'audio/wav',\n 'audio/ogg',\n 'audio/flac',\n 'audio/mp4',\n 'audio/aac',\n // Video (safe for video src)\n 'video/mp4',\n 'video/webm',\n // Text/data (generally safe)\n 'text/plain',\n 'application/json',\n 'application/octet-stream',\n]);\n\n/** Potentially dangerous MIME types that should NOT be used in href/src */\nconst DANGEROUS_DATA_URI_MIME_TYPES = new Set([\n // Executable/script content\n 'text/html',\n 'text/javascript',\n 'application/javascript',\n 'application/x-javascript',\n 'text/css',\n 'image/svg+xml', // SVG can contain scripts\n 'application/xhtml+xml',\n 'application/xml',\n]);\n\n/**\n * Options for URI safety validation\n */\nexport interface URISafetyOptions {\n /** Allow http:// URIs (default: false) */\n allowHttp?: boolean;\n /** Allow file:// URIs (default: false) */\n allowFile?: boolean;\n /**\n * Allowed MIME types for data: URIs (default: all safe types).\n * Set to empty array to reject all data: URIs.\n * Set to undefined to use default safe list.\n */\n allowedDataMimes?: string[];\n}\n\n/**\n * Result of URI safety check with detailed information\n */\nexport interface URISafetyResult {\n /** Whether the URI is safe to use */\n safe: boolean;\n /** Reason if unsafe */\n reason?: string;\n /** Detected scheme */\n scheme: URIScheme;\n /** MIME type for data: URIs */\n mimeType?: string;\n}\n\n/**\n * Validate if a URI is safe to use (detailed version)\n *\n * @param uri - URI to validate\n * @param options - Safety options\n * @returns Detailed safety result\n */\nexport function checkURISafety(uri: string, options: URISafetyOptions = {}): URISafetyResult {\n const parsed = parseURI(uri);\n\n switch (parsed.scheme) {\n case 'embeded':\n case 'ccdefault':\n case 'internal':\n case 'https':\n case 'pngchunk':\n return { safe: true, scheme: parsed.scheme };\n\n case 'data': {\n const mimeType = parsed.mimeType || 'text/plain';\n\n // Check for explicitly dangerous MIME types\n if (DANGEROUS_DATA_URI_MIME_TYPES.has(mimeType)) {\n return {\n safe: false,\n scheme: parsed.scheme,\n mimeType,\n reason: `Data URI with potentially dangerous MIME type: ${mimeType}`,\n };\n }\n\n // If custom allowed list is provided, check against it\n if (options.allowedDataMimes !== undefined) {\n if (options.allowedDataMimes.length === 0) {\n return {\n safe: false,\n scheme: parsed.scheme,\n mimeType,\n reason: 'Data URIs are not allowed',\n };\n }\n if (!options.allowedDataMimes.includes(mimeType)) {\n return {\n safe: false,\n scheme: parsed.scheme,\n mimeType,\n reason: `Data URI MIME type not in allowed list: ${mimeType}`,\n };\n }\n }\n\n // Otherwise use default safe list\n if (!SAFE_DATA_URI_MIME_TYPES.has(mimeType)) {\n return {\n safe: false,\n scheme: parsed.scheme,\n mimeType,\n reason: `Unknown data URI MIME type: ${mimeType}`,\n };\n }\n\n return { safe: true, scheme: parsed.scheme, mimeType };\n }\n\n case 'http':\n if (options.allowHttp === true) {\n return { safe: true, scheme: parsed.scheme };\n }\n return { safe: false, scheme: parsed.scheme, reason: 'HTTP URIs are not allowed' };\n\n case 'file':\n if (options.allowFile === true) {\n return { safe: true, scheme: parsed.scheme };\n }\n return { safe: false, scheme: parsed.scheme, reason: 'File URIs are not allowed' };\n\n case 'unknown':\n default:\n return { safe: false, scheme: parsed.scheme, reason: 'Unknown URI scheme' };\n }\n}\n\n/**\n * Validate if a URI is safe to use (simple boolean version for backwards compatibility)\n *\n * @deprecated Use checkURISafety() for detailed safety information\n */\nexport function isURISafe(uri: string, options: { allowHttp?: boolean; allowFile?: boolean } = {}): boolean {\n return checkURISafety(uri, options).safe;\n}\n\n/**\n * Extract file extension from URI\n */\nexport function getExtensionFromURI(uri: string): string {\n const parsed = parseURI(uri);\n\n if (parsed.path) {\n const parts = parsed.path.split('.');\n if (parts.length > 1) {\n return parts[parts.length - 1]!.toLowerCase();\n }\n }\n\n if (parsed.url) {\n const urlParts = parsed.url.split('?')[0]!.split('.');\n if (urlParts.length > 1) {\n return urlParts[urlParts.length - 1]!.toLowerCase();\n }\n }\n\n if (parsed.mimeType) {\n return getExtFromMimeType(parsed.mimeType);\n }\n\n return 'unknown';\n}\n\n/**\n * Get MIME type from file extension\n */\nexport function getMimeTypeFromExt(ext: string): string {\n const extToMime: Record<string, string> = {\n // Images\n 'png': 'image/png',\n 'jpg': 'image/jpeg',\n 'jpeg': 'image/jpeg',\n 'webp': 'image/webp',\n 'gif': 'image/gif',\n 'avif': 'image/avif',\n 'svg': 'image/svg+xml',\n 'bmp': 'image/bmp',\n 'ico': 'image/x-icon',\n\n // Audio\n 'mp3': 'audio/mpeg',\n 'wav': 'audio/wav',\n 'ogg': 'audio/ogg',\n 'flac': 'audio/flac',\n 'm4a': 'audio/mp4',\n 'aac': 'audio/aac',\n\n // Video\n 'mp4': 'video/mp4',\n 'webm': 'video/webm',\n 'avi': 'video/x-msvideo',\n 'mov': 'video/quicktime',\n 'mkv': 'video/x-matroska',\n\n // Text/Data\n 'json': 'application/json',\n 'txt': 'text/plain',\n 'html': 'text/html',\n 'css': 'text/css',\n 'js': 'application/javascript',\n };\n\n return extToMime[ext.toLowerCase()] || 'application/octet-stream';\n}\n\n/**\n * Get file extension from MIME type\n */\nexport function getExtFromMimeType(mimeType: string): string {\n const mimeToExt: Record<string, string> = {\n 'image/png': 'png',\n 'image/jpeg': 'jpg',\n 'image/webp': 'webp',\n 'image/gif': 'gif',\n 'image/avif': 'avif',\n 'image/svg+xml': 'svg',\n 'image/bmp': 'bmp',\n 'image/x-icon': 'ico',\n 'audio/mpeg': 'mp3',\n 'audio/wav': 'wav',\n 'audio/ogg': 'ogg',\n 'audio/flac': 'flac',\n 'audio/mp4': 'm4a',\n 'audio/aac': 'aac',\n 'video/mp4': 'mp4',\n 'video/webm': 'webm',\n 'video/x-msvideo': 'avi',\n 'video/quicktime': 'mov',\n 'video/x-matroska': 'mkv',\n 'application/json': 'json',\n 'text/plain': 'txt',\n 'text/html': 'html',\n 'text/css': 'css',\n 'application/javascript': 'js',\n };\n\n return mimeToExt[mimeType] || 'bin';\n}\n\n/**\n * Build a data URI from binary data and MIME type\n */\nexport function buildDataURI(data: string, mimeType: string, isBase64 = true): string {\n if (isBase64) {\n return `data:${mimeType};base64,${data}`;\n }\n return `data:${mimeType},${encodeURIComponent(data)}`;\n}\n","/**\n * Image Analysis Utilities\n *\n * Detect properties of image files from binary data.\n */\n\nimport {\n type BinaryData,\n indexOf,\n fromLatin1,\n} from './binary.js';\n\n/**\n * Check if an image buffer contains animation data.\n * Supports: APNG, WebP (Animated), GIF\n */\nexport function isAnimatedImage(data: BinaryData, _mimeType?: string): boolean {\n // 1. WebP Detection\n // RIFF .... WEBP\n if (\n data.length > 12 &&\n data[0] === 0x52 && data[1] === 0x49 && data[2] === 0x46 && data[3] === 0x46 && // RIFF\n data[8] === 0x57 && data[9] === 0x45 && data[10] === 0x42 && data[11] === 0x50 // WEBP\n ) {\n // Check for VP8X chunk\n // VP8X chunk header: 'VP8X' (bytes 12-15)\n if (\n data[12] === 0x56 && data[13] === 0x50 && data[14] === 0x38 && data[15] === 0x58\n ) {\n // Flags byte is at offset 20 (16 + 4 bytes chunk size)\n // Animation bit is bit 1 (0x02)\n const flags = data[20];\n return (flags! & 0x02) !== 0;\n }\n return false;\n }\n\n // 2. PNG/APNG Detection\n // Signature: 89 50 4E 47 0D 0A 1A 0A\n if (\n data.length > 8 &&\n data[0] === 0x89 && data[1] === 0x50 && data[2] === 0x4E && data[3] === 0x47\n ) {\n // Search for 'acTL' chunk (Animation Control)\n // It must appear before IDAT.\n // Simple search: indexOf('acTL')\n // Note: theoretically 'acTL' string could appear in other data, but highly unlikely in valid PNG structure before IDAT\n // We can iterate chunks to be safe, but indexOf is faster for a quick check\n const actlSig = fromLatin1('acTL');\n const idatSig = fromLatin1('IDAT');\n \n const actlIndex = indexOf(data, actlSig);\n if (actlIndex === -1) return false;\n\n const idatIndex = indexOf(data, idatSig);\n // If acTL exists and is before the first IDAT (or IDAT not found yet), it's APNG\n return idatIndex === -1 || actlIndex < idatIndex;\n }\n\n // 3. GIF Detection\n // Signature: GIF87a or GIF89a\n if (\n data.length > 6 &&\n data[0] === 0x47 && data[1] === 0x49 && data[2] === 0x46 // GIF\n ) {\n // Check for NETSCAPE2.0 extension (looping animation)\n // This is a heuristic. Static GIFs are rare in this domain but possible.\n // Full frame counting is expensive. Presence of NETSCAPE block is a strong indicator.\n const netscape = fromLatin1('NETSCAPE2.0');\n return indexOf(data, netscape) !== -1;\n }\n\n return false;\n}\n","/**\n * UUID Generation Utilities\n *\n * Provides crypto-grade UUID v4 generation that works in Node.js,\n * browsers (secure contexts), and falls back gracefully.\n */\n\n/**\n * Format 16 random bytes as a UUID v4 string\n */\nfunction formatUUID(bytes: Uint8Array): string {\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;\n}\n\n/**\n * Fallback UUID generation using Math.random()\n * Only used when crypto APIs are unavailable (rare)\n */\nfunction mathRandomUUID(): string {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\n/**\n * Generate a cryptographically secure UUID v4.\n *\n * Uses crypto.randomUUID() when available (Node.js 19+, modern browsers).\n * Falls back to crypto.getRandomValues() if randomUUID is unavailable.\n * Last resort uses Math.random() (non-secure, emits warning in dev).\n *\n * @returns A valid RFC 4122 UUID v4 string\n *\n * @example\n * ```typescript\n * const id = generateUUID();\n * // => \"550e8400-e29b-41d4-a716-446655440000\"\n * ```\n */\nexport function generateUUID(): string {\n // Node.js 19+ or browser with secure context\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n\n // Fallback using crypto.getRandomValues (older Node/browsers)\n if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n // Set version (4) and variant (RFC 4122)\n bytes[6] = (bytes[6]! & 0x0f) | 0x40; // Version 4\n bytes[8] = (bytes[8]! & 0x3f) | 0x80; // Variant 1\n return formatUUID(bytes);\n }\n\n // Last resort - non-secure fallback\n if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'development') {\n console.warn('[character-foundry/core] generateUUID: Using insecure Math.random() fallback');\n }\n return mathRandomUUID();\n}\n\n/**\n * Validate if a string is a valid UUID v4\n *\n * @param uuid - String to validate\n * @returns true if valid UUID v4 format\n */\nexport function isValidUUID(uuid: string): boolean {\n return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(uuid);\n}\n","/**\n * Federation Logger\n *\n * Lightweight logger with configurable verbosity and a safe default (warn).\n * No external dependencies.\n */\n\nexport type LogLevel = 'silent' | 'error' | 'warn' | 'info' | 'debug';\n\nexport interface Logger {\n debug: (...args: unknown[]) => void;\n info: (...args: unknown[]) => void;\n warn: (...args: unknown[]) => void;\n error: (...args: unknown[]) => void;\n}\n\nconst LEVELS: Record<LogLevel, number> = {\n silent: 0,\n error: 1,\n warn: 2,\n info: 3,\n debug: 4,\n};\n\nconst noop = (): void => {};\n\nexport function createConsoleLogger(level: LogLevel = 'warn'): Logger {\n const severity = LEVELS[level] ?? LEVELS.warn;\n const hasConsole = typeof console !== 'undefined';\n const c = hasConsole ? console : undefined;\n\n const debugImpl = c?.debug ? c.debug.bind(c) : c?.log ? c.log.bind(c) : noop;\n const infoImpl = c?.info ? c.info.bind(c) : c?.log ? c.log.bind(c) : noop;\n const warnImpl = c?.warn ? c.warn.bind(c) : c?.log ? c.log.bind(c) : noop;\n const errorImpl = c?.error ? c.error.bind(c) : c?.log ? c.log.bind(c) : noop;\n\n return {\n debug: severity >= LEVELS.debug ? debugImpl : noop,\n info: severity >= LEVELS.info ? infoImpl : noop,\n warn: severity >= LEVELS.warn ? warnImpl : noop,\n error: severity >= LEVELS.error ? errorImpl : noop,\n };\n}\n\nlet federationLogger: Logger = createConsoleLogger('warn');\n\nexport function getLogger(): Logger {\n return federationLogger;\n}\n\nexport function setLogger(logger: Logger): void {\n federationLogger = logger;\n}\n\nexport function setLogLevel(level: LogLevel): void {\n federationLogger = createConsoleLogger(level);\n}\n\nexport function configureLogger(options?: { logger?: Logger; logLevel?: LogLevel }): void {\n if (!options) return;\n\n if (options.logger) {\n setLogger(options.logger);\n return;\n }\n\n if (options.logLevel) {\n setLogLevel(options.logLevel);\n }\n}\n\n","/**\n * ActivityPub Utilities\n *\n * Convert character cards to/from ActivityPub format.\n */\n\nimport type { CCv3Data } from '@character-foundry/schemas';\nimport { generateUUID } from '@character-foundry/core';\nimport type {\n FederatedCard,\n FederatedActivity,\n FederatedActor,\n PlatformId,\n ForkActivity,\n InstallActivity,\n} from './types.js';\n\n/**\n * ActivityPub context for character cards\n */\nexport const ACTIVITY_CONTEXT = [\n 'https://www.w3.org/ns/activitystreams',\n {\n 'character': 'https://character-foundry.dev/ns#',\n 'character:version': { '@id': 'character:version' },\n 'character:spec': { '@id': 'character:spec' },\n },\n];\n\n/**\n * Extended ActivityPub context for Fork activities\n * Includes custom character-foundry namespace for fork semantics\n */\nexport const FORK_ACTIVITY_CONTEXT = [\n 'https://www.w3.org/ns/activitystreams',\n {\n 'character': 'https://character-foundry.dev/ns#',\n 'character:version': { '@id': 'character:version' },\n 'character:spec': { '@id': 'character:spec' },\n 'Fork': 'character:Fork',\n 'forkedFrom': { '@id': 'character:forkedFrom', '@type': '@id' },\n },\n];\n\n/**\n * Extended ActivityPub context for Install activities\n * Used by consumers (SillyTavern, Voxta) to notify hub about installations\n */\nexport const INSTALL_ACTIVITY_CONTEXT = [\n 'https://www.w3.org/ns/activitystreams',\n {\n 'character': 'https://character-foundry.dev/ns#',\n 'Install': 'character:Install',\n },\n];\n\n/**\n * Generate a unique ID for a federated card\n *\n * @security localId is URI-encoded to prevent path traversal and query injection.\n * Characters like '/', '?', '#', and spaces are safely escaped.\n */\nexport function generateCardId(baseUrl: string, localId: string): string {\n // Encode localId to prevent path confusion and injection attacks\n // e.g., \"foo/bar\" → \"foo%2Fbar\", \"foo?x=1\" → \"foo%3Fx%3D1\"\n const encodedId = encodeURIComponent(localId);\n return `${baseUrl}/cards/${encodedId}`;\n}\n\n/**\n * Generate a unique ID for an activity using crypto-grade randomness\n */\nexport function generateActivityId(baseUrl: string): string {\n const timestamp = Date.now();\n const random = generateUUID().split('-')[0]; // Use first segment of UUID\n return `${baseUrl}/activities/${timestamp}-${random}`;\n}\n\n/**\n * Convert a CCv3 card to ActivityPub FederatedCard format\n */\nexport function cardToActivityPub(\n card: CCv3Data,\n options: {\n id: string;\n actorId: string;\n published?: string;\n updated?: string;\n sourcePlatform?: PlatformId;\n sourceId?: string;\n sourceUrl?: string;\n attachments?: Array<{\n type: 'Image' | 'Audio' | 'Document';\n mediaType: string;\n url: string;\n name?: string;\n }>;\n }\n): FederatedCard {\n const now = new Date().toISOString();\n const cardData = card.data;\n\n // Convert tags to ActivityPub hashtags\n const tags = cardData.tags?.map((tag) => ({\n type: 'Hashtag' as const,\n name: `#${tag.replace(/\\s+/g, '_')}`,\n }));\n\n const federatedCard: FederatedCard = {\n '@context': ACTIVITY_CONTEXT,\n id: options.id,\n type: 'Note',\n name: cardData.name,\n summary: cardData.description?.substring(0, 500),\n content: JSON.stringify(card),\n mediaType: 'application/json',\n attributedTo: options.actorId,\n published: options.published || now,\n updated: options.updated,\n tag: tags,\n attachment: options.attachments,\n 'character:version': cardData.character_version,\n 'character:spec': card.spec_version,\n };\n\n // Add source info if provided\n if (options.sourcePlatform && options.sourceId) {\n federatedCard.source = {\n platform: options.sourcePlatform,\n id: options.sourceId,\n url: options.sourceUrl,\n };\n }\n\n return federatedCard;\n}\n\n/**\n * Extract CCv3 card from ActivityPub FederatedCard\n */\nexport function cardFromActivityPub(federatedCard: FederatedCard): CCv3Data {\n try {\n const card = JSON.parse(federatedCard.content) as CCv3Data;\n\n // Validate it's a proper CCv3 card\n if (card.spec !== 'chara_card_v3') {\n throw new Error('Invalid card spec');\n }\n\n return card;\n } catch (err) {\n throw new Error(\n `Failed to parse card from ActivityPub: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n}\n\n/**\n * Create a Create activity for a new card\n */\nexport function createCreateActivity(\n card: FederatedCard,\n actorId: string,\n baseUrl: string,\n recipients?: { to?: string[]; cc?: string[] }\n): FederatedActivity {\n return {\n '@context': ACTIVITY_CONTEXT,\n id: generateActivityId(baseUrl),\n type: 'Create',\n actor: actorId,\n object: card,\n published: new Date().toISOString(),\n to: recipients?.to || ['https://www.w3.org/ns/activitystreams#Public'],\n cc: recipients?.cc,\n };\n}\n\n/**\n * Create an Update activity for a modified card\n */\nexport function createUpdateActivity(\n card: FederatedCard,\n actorId: string,\n baseUrl: string,\n recipients?: { to?: string[]; cc?: string[] }\n): FederatedActivity {\n return {\n '@context': ACTIVITY_CONTEXT,\n id: generateActivityId(baseUrl),\n type: 'Update',\n actor: actorId,\n object: card,\n published: new Date().toISOString(),\n to: recipients?.to || ['https://www.w3.org/ns/activitystreams#Public'],\n cc: recipients?.cc,\n };\n}\n\n/**\n * Create a Delete activity for a removed card\n */\nexport function createDeleteActivity(\n cardId: string,\n actorId: string,\n baseUrl: string,\n recipients?: { to?: string[]; cc?: string[] }\n): FederatedActivity {\n return {\n '@context': ACTIVITY_CONTEXT,\n id: generateActivityId(baseUrl),\n type: 'Delete',\n actor: actorId,\n object: cardId,\n published: new Date().toISOString(),\n to: recipients?.to || ['https://www.w3.org/ns/activitystreams#Public'],\n cc: recipients?.cc,\n };\n}\n\n/**\n * Create an Announce (reshare) activity\n */\nexport function createAnnounceActivity(\n cardId: string,\n actorId: string,\n baseUrl: string,\n recipients?: { to?: string[]; cc?: string[] }\n): FederatedActivity {\n return {\n '@context': ACTIVITY_CONTEXT,\n id: generateActivityId(baseUrl),\n type: 'Announce',\n actor: actorId,\n object: cardId,\n published: new Date().toISOString(),\n to: recipients?.to || ['https://www.w3.org/ns/activitystreams#Public'],\n cc: recipients?.cc,\n };\n}\n\n/**\n * Create a Like activity\n */\nexport function createLikeActivity(\n cardId: string,\n actorId: string,\n baseUrl: string\n): FederatedActivity {\n return {\n '@context': ACTIVITY_CONTEXT,\n id: generateActivityId(baseUrl),\n type: 'Like',\n actor: actorId,\n object: cardId,\n published: new Date().toISOString(),\n };\n}\n\n/**\n * Create an Undo activity\n */\nexport function createUndoActivity(\n originalActivityId: string,\n actorId: string,\n baseUrl: string\n): FederatedActivity {\n return {\n '@context': ACTIVITY_CONTEXT,\n id: generateActivityId(baseUrl),\n type: 'Undo',\n actor: actorId,\n object: originalActivityId,\n published: new Date().toISOString(),\n };\n}\n\n/**\n * Create a Fork activity for card derivation\n *\n * Fork activities notify the source instance that a card was forked.\n * The forked card contains a reference to the source in its extensions.\n */\nexport function createForkActivity(\n sourceCardId: string,\n forkedCard: FederatedCard,\n actorId: string,\n baseUrl: string,\n recipients?: { to?: string[]; cc?: string[] }\n): ForkActivity {\n return {\n '@context': FORK_ACTIVITY_CONTEXT,\n id: generateActivityId(baseUrl),\n type: 'Fork',\n actor: actorId,\n object: sourceCardId,\n result: forkedCard,\n published: new Date().toISOString(),\n to: recipients?.to || ['https://www.w3.org/ns/activitystreams#Public'],\n cc: recipients?.cc,\n };\n}\n\n/**\n * Parse an incoming Fork activity\n *\n * @returns Parsed fork data or null if invalid\n */\nexport function parseForkActivity(activity: unknown): {\n sourceCardId: string;\n forkedCard: FederatedCard;\n actor: string;\n activityId: string;\n} | null {\n if (!activity || typeof activity !== 'object') {\n return null;\n }\n\n const act = activity as Record<string, unknown>;\n\n // Verify it's a Fork activity\n if (act.type !== 'Fork') {\n return null;\n }\n\n // Validate required fields\n if (\n typeof act.actor !== 'string' ||\n typeof act.object !== 'string' ||\n typeof act.id !== 'string' ||\n !act.result ||\n typeof act.result !== 'object'\n ) {\n return null;\n }\n\n const result = act.result as Record<string, unknown>;\n\n // Validate the forked card has required fields\n if (\n typeof result.id !== 'string' ||\n typeof result.content !== 'string' ||\n typeof result.attributedTo !== 'string'\n ) {\n return null;\n }\n\n return {\n sourceCardId: act.object,\n forkedCard: result as unknown as FederatedCard,\n actor: act.actor,\n activityId: act.id,\n };\n}\n\n/**\n * Create an Install activity for notifying hub about card installation\n *\n * Consumers (SillyTavern, Voxta) send this when a card is installed/saved.\n */\nexport function createInstallActivity(\n cardId: string,\n actorId: string,\n baseUrl: string,\n platform: PlatformId,\n recipients?: { to?: string[]; cc?: string[] }\n): InstallActivity {\n return {\n '@context': INSTALL_ACTIVITY_CONTEXT,\n id: generateActivityId(baseUrl),\n type: 'Install',\n actor: actorId,\n object: cardId,\n target: {\n type: 'Application',\n name: platform,\n },\n published: new Date().toISOString(),\n to: recipients?.to,\n cc: recipients?.cc,\n };\n}\n\n/**\n * Parse an incoming Install activity\n *\n * @returns Parsed install data or null if invalid\n */\nexport function parseInstallActivity(activity: unknown): {\n cardId: string;\n actor: string;\n platform: PlatformId | null;\n activityId: string;\n} | null {\n if (!activity || typeof activity !== 'object') {\n return null;\n }\n\n const act = activity as Record<string, unknown>;\n\n // Verify it's an Install activity\n if (act.type !== 'Install') {\n return null;\n }\n\n // Validate required fields\n if (\n typeof act.actor !== 'string' ||\n typeof act.object !== 'string' ||\n typeof act.id !== 'string'\n ) {\n return null;\n }\n\n // Extract platform from target if present\n let platform: PlatformId | null = null;\n if (act.target && typeof act.target === 'object') {\n const target = act.target as Record<string, unknown>;\n if (target.type === 'Application' && typeof target.name === 'string') {\n platform = target.name as PlatformId;\n }\n }\n\n return {\n cardId: act.object,\n actor: act.actor,\n platform,\n activityId: act.id,\n };\n}\n\n/**\n * Create a minimal actor object\n */\nexport function createActor(options: {\n id: string;\n username: string;\n displayName: string;\n summary?: string;\n icon?: string;\n baseUrl: string;\n publicKeyPem?: string;\n}): FederatedActor {\n const actor: FederatedActor = {\n id: options.id,\n type: 'Person',\n name: options.displayName,\n preferredUsername: options.username,\n summary: options.summary,\n icon: options.icon,\n inbox: `${options.id}/inbox`,\n outbox: `${options.id}/outbox`,\n followers: `${options.id}/followers`,\n following: `${options.id}/following`,\n };\n\n if (options.publicKeyPem) {\n actor.publicKey = {\n id: `${options.id}#main-key`,\n owner: options.id,\n publicKeyPem: options.publicKeyPem,\n };\n }\n\n return actor;\n}\n\n/**\n * Parse an incoming activity\n */\nexport function parseActivity(data: unknown): FederatedActivity {\n if (!data || typeof data !== 'object') {\n throw new Error('Invalid activity: not an object');\n }\n\n const activity = data as Record<string, unknown>;\n\n if (!activity.type || !activity.actor || !activity.object) {\n throw new Error('Invalid activity: missing required fields');\n }\n\n return activity as unknown as FederatedActivity;\n}\n\n/**\n * @deprecated Use validateHttpSignature from http-signatures.ts instead.\n *\n * This stub exists only for backwards compatibility and always throws.\n * For actual HTTP signature verification, use:\n *\n * ```typescript\n * import { validateHttpSignature } from '@character-foundry/federation';\n * ```\n *\n * Or use handleInbox() with strictMode: true for automatic verification.\n */\nexport function validateActivitySignature(\n _activity: FederatedActivity,\n _signature: string,\n _publicKey: string\n): never {\n throw new Error(\n 'validateActivitySignature is deprecated and does not work. ' +\n 'Use validateHttpSignature from http-signatures.ts instead, or use handleInbox() with strictMode: true. ' +\n 'See: https://github.com/character-foundry/character-foundry/blob/master/docs/federation.md#http-signatures'\n );\n}\n","/**\n * Sync Engine\n *\n * Handles synchronization of character cards across platforms.\n */\n\nimport type { CCv3Data } from '@character-foundry/schemas';\nimport { generateUUID } from '@character-foundry/core';\nimport type {\n PlatformId,\n PlatformAdapter,\n CardSyncState,\n SyncOperation,\n SyncResult,\n SyncStateStore,\n FederationEvent,\n FederationEventListener,\n FederationEventType,\n ForkReference,\n ForkResult,\n ForkActivity,\n ForkNotification,\n InstallActivity,\n CardStats,\n} from './types.js';\nimport { generateCardId, parseForkActivity, parseInstallActivity } from './activitypub.js';\nimport { assertFederationEnabled } from './index.js';\nimport { getLogger } from './logger.js';\n\n/**\n * Generate a simple hash of card content for change detection (32-bit djb2-style).\n *\n * Fast but has collision potential (~2^16 birthday bound). Suitable for\n * local change detection where collisions are unlikely and recoverable.\n *\n * @see hashCardSecure for cryptographic alternative\n */\nfunction hashCardFast(card: CCv3Data): string {\n const content = JSON.stringify(card);\n let hash = 0;\n for (let i = 0; i < content.length; i++) {\n const char = content.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n return Math.abs(hash).toString(36);\n}\n\n/**\n * Generate a SHA-256 hash of card content for secure change detection.\n *\n * Cryptographically secure with negligible collision probability.\n * Recommended for federation sync where hash collisions could cause\n * data loss or missed updates across systems.\n *\n * @security Use this for cross-system sync to prevent collision attacks\n */\nasync function hashCardSecure(card: CCv3Data): Promise<string> {\n const content = JSON.stringify(card);\n const data = new TextEncoder().encode(content);\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');\n}\n\n/**\n * Sync Engine Options\n */\nexport interface SyncEngineOptions {\n /** Base URL for this federation instance */\n baseUrl: string;\n /** Actor ID for this instance */\n actorId: string;\n /** State storage */\n stateStore: SyncStateStore;\n /** Auto-sync interval in ms (0 to disable) */\n autoSyncInterval?: number;\n /**\n * Use SHA-256 for change detection instead of fast 32-bit hash.\n *\n * Default: false (fast hash for backwards compatibility)\n * Recommended: true for federation sync across untrusted systems\n *\n * @security Fast hash has collision potential (~2^16 birthday bound).\n * SHA-256 is cryptographically secure but slightly slower.\n */\n secureHashing?: boolean;\n}\n\n/**\n * Sync Engine\n * Coordinates synchronization between registered platforms\n */\nexport class SyncEngine {\n private platforms: Map<PlatformId, PlatformAdapter> = new Map();\n private stateStore: SyncStateStore;\n private baseUrl: string;\n private actorId: string;\n private listeners: Map<FederationEventType, Set<FederationEventListener>> = new Map();\n private autoSyncTimer?: ReturnType<typeof setInterval>;\n private secureHashing: boolean;\n\n /**\n * Mutex flag to prevent concurrent syncAll() executions.\n * When autoSyncInterval triggers while a sync is in progress,\n * the new sync is skipped to prevent race conditions.\n */\n private syncInProgress = false;\n\n constructor(options: SyncEngineOptions) {\n assertFederationEnabled('SyncEngine');\n\n this.baseUrl = options.baseUrl;\n this.actorId = options.actorId;\n this.stateStore = options.stateStore;\n this.secureHashing = options.secureHashing ?? false;\n\n if (options.autoSyncInterval && options.autoSyncInterval > 0) {\n this.autoSyncTimer = setInterval(\n () => void this.syncAll(), // void to handle Promise without blocking\n options.autoSyncInterval\n );\n }\n }\n\n /**\n * Generate a hash for change detection.\n * Uses SHA-256 if secureHashing is enabled, otherwise fast 32-bit hash.\n */\n private async hashCard(card: CCv3Data): Promise<string> {\n if (this.secureHashing) {\n return hashCardSecure(card);\n }\n return hashCardFast(card);\n }\n\n /**\n * Register a platform adapter\n */\n registerPlatform(adapter: PlatformAdapter): void {\n this.platforms.set(adapter.platform, adapter);\n }\n\n /**\n * Unregister a platform adapter\n */\n unregisterPlatform(platform: PlatformId): void {\n this.platforms.delete(platform);\n }\n\n /**\n * Get registered platforms\n */\n getPlatforms(): PlatformId[] {\n return Array.from(this.platforms.keys());\n }\n\n /**\n * Add event listener\n */\n on(event: FederationEventType, listener: FederationEventListener): void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(listener);\n }\n\n /**\n * Remove event listener\n */\n off(event: FederationEventType, listener: FederationEventListener): void {\n this.listeners.get(event)?.delete(listener);\n }\n\n /**\n * Emit an event\n */\n private emit(type: FederationEventType, data: unknown): void {\n const event: FederationEvent = {\n type,\n timestamp: new Date().toISOString(),\n data,\n };\n\n const listeners = this.listeners.get(type);\n if (listeners) {\n for (const listener of listeners) {\n try {\n listener(event);\n } catch (err) {\n getLogger().error('[federation] Event listener error:', err);\n }\n }\n }\n }\n\n /**\n * Push a card from one platform to another\n */\n async pushCard(\n sourcePlatform: PlatformId,\n sourceId: string,\n targetPlatform: PlatformId\n ): Promise<SyncResult> {\n const operation: SyncOperation = {\n type: 'push',\n cardId: `${sourcePlatform}:${sourceId}`,\n sourcePlatform,\n targetPlatform,\n timestamp: new Date().toISOString(),\n };\n\n try {\n // Get source adapter\n const sourceAdapter = this.platforms.get(sourcePlatform);\n if (!sourceAdapter) {\n throw new Error(`Platform not registered: ${sourcePlatform}`);\n }\n\n // Get target adapter\n const targetAdapter = this.platforms.get(targetPlatform);\n if (!targetAdapter) {\n throw new Error(`Platform not registered: ${targetPlatform}`);\n }\n\n // Check availability\n if (!await sourceAdapter.isAvailable()) {\n throw new Error(`Source platform unavailable: ${sourcePlatform}`);\n }\n if (!await targetAdapter.isAvailable()) {\n throw new Error(`Target platform unavailable: ${targetPlatform}`);\n }\n\n // Get the card\n const card = await sourceAdapter.getCard(sourceId);\n if (!card) {\n throw new Error(`Card not found: ${sourceId}`);\n }\n\n // Check for existing sync state\n const federatedId = generateCardId(this.baseUrl, `${sourcePlatform}-${sourceId}`);\n const syncState = await this.stateStore.get(federatedId);\n\n // Check for conflicts\n if (syncState?.platformIds[targetPlatform]) {\n const existingCard = await targetAdapter.getCard(syncState.platformIds[targetPlatform]!);\n if (existingCard) {\n const existingHash = await this.hashCard(existingCard);\n const newHash = await this.hashCard(card);\n\n if (existingHash !== syncState.versionHash && newHash !== syncState.versionHash) {\n // Both sides changed - conflict!\n syncState.status = 'conflict';\n syncState.conflict = {\n localVersion: newHash,\n remoteVersion: existingHash,\n remotePlatform: targetPlatform,\n };\n await this.stateStore.set(syncState);\n\n this.emit('card:conflict', { syncState, sourcePlatform, targetPlatform });\n\n return {\n success: false,\n operation,\n newState: syncState,\n error: 'Sync conflict detected',\n };\n }\n }\n }\n\n // Save to target platform\n const targetId = await targetAdapter.saveCard(\n card,\n syncState?.platformIds[targetPlatform]\n );\n\n // Update sync state\n const newHash = await this.hashCard(card);\n const now = new Date().toISOString();\n\n // Create new state or use existing\n const updatedState: CardSyncState = syncState ?? {\n localId: sourceId,\n federatedId,\n platformIds: {},\n lastSync: {},\n versionHash: newHash,\n status: 'synced',\n };\n\n updatedState.platformIds[sourcePlatform] = sourceId;\n updatedState.platformIds[targetPlatform] = targetId;\n updatedState.lastSync[sourcePlatform] = now;\n updatedState.lastSync[targetPlatform] = now;\n updatedState.versionHash = newHash;\n updatedState.status = 'synced';\n updatedState.conflict = undefined;\n\n await this.stateStore.set(updatedState);\n\n this.emit('card:synced', { syncState: updatedState, sourcePlatform, targetPlatform });\n\n return {\n success: true,\n operation,\n newState: updatedState,\n };\n } catch (err) {\n this.emit('sync:failed', { operation, error: err });\n\n return {\n success: false,\n operation,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n\n /**\n * Pull a card from a remote platform to local\n */\n async pullCard(\n remotePlatform: PlatformId,\n remoteId: string,\n localPlatform: PlatformId\n ): Promise<SyncResult> {\n // Pull is just push in reverse\n return this.pushCard(remotePlatform, remoteId, localPlatform);\n }\n\n /**\n * Sync a card across all registered platforms\n */\n async syncCardToAll(\n sourcePlatform: PlatformId,\n sourceId: string\n ): Promise<SyncResult[]> {\n const results: SyncResult[] = [];\n\n for (const [platform] of this.platforms) {\n if (platform === sourcePlatform) continue;\n\n const result = await this.pushCard(sourcePlatform, sourceId, platform);\n results.push(result);\n }\n\n return results;\n }\n\n /**\n * Sync all cards from one platform to another\n */\n async syncPlatform(\n sourcePlatform: PlatformId,\n targetPlatform: PlatformId\n ): Promise<SyncResult[]> {\n const sourceAdapter = this.platforms.get(sourcePlatform);\n if (!sourceAdapter) {\n throw new Error(`Platform not registered: ${sourcePlatform}`);\n }\n\n this.emit('sync:started', { sourcePlatform, targetPlatform });\n\n const cards = await sourceAdapter.listCards();\n const results: SyncResult[] = [];\n\n for (const { id } of cards) {\n const result = await this.pushCard(sourcePlatform, id, targetPlatform);\n results.push(result);\n }\n\n this.emit('sync:completed', {\n sourcePlatform,\n targetPlatform,\n total: results.length,\n successful: results.filter((r) => r.success).length,\n failed: results.filter((r) => !r.success).length,\n });\n\n return results;\n }\n\n /**\n * Sync all platforms with each other.\n *\n * Includes mutex protection to prevent concurrent executions when\n * triggered by autoSyncInterval. If a sync is already in progress,\n * subsequent calls are skipped and emit a 'sync:skipped' event.\n */\n async syncAll(): Promise<Map<string, SyncResult[]>> {\n // Mutex: prevent concurrent sync operations\n if (this.syncInProgress) {\n this.emit('sync:skipped', { reason: 'already_in_progress', timestamp: new Date().toISOString() });\n return new Map();\n }\n\n this.syncInProgress = true;\n try {\n const results = new Map<string, SyncResult[]>();\n const platforms = Array.from(this.platforms.keys());\n\n for (let i = 0; i < platforms.length; i++) {\n for (let j = i + 1; j < platforms.length; j++) {\n const source = platforms[i]!;\n const target = platforms[j]!;\n const key = `${source}->${target}`;\n\n results.set(key, await this.syncPlatform(source, target));\n }\n }\n\n return results;\n } finally {\n this.syncInProgress = false;\n }\n }\n\n /**\n * Get sync state for a card\n */\n async getSyncState(federatedId: string): Promise<CardSyncState | null> {\n return this.stateStore.get(federatedId);\n }\n\n /**\n * Find sync state by platform ID\n */\n async findSyncState(\n platform: PlatformId,\n platformId: string\n ): Promise<CardSyncState | null> {\n return this.stateStore.findByPlatformId(platform, platformId);\n }\n\n /**\n * Resolve a sync conflict by choosing a version\n */\n async resolveConflict(\n federatedId: string,\n resolution: 'local' | 'remote' | 'merge',\n mergedCard?: CCv3Data\n ): Promise<SyncResult> {\n const syncState = await this.stateStore.get(federatedId);\n if (!syncState || syncState.status !== 'conflict' || !syncState.conflict) {\n throw new Error('No conflict to resolve');\n }\n\n const operation: SyncOperation = {\n type: 'resolve',\n cardId: federatedId,\n sourcePlatform: 'archive', // Will be updated\n targetPlatform: syncState.conflict.remotePlatform,\n timestamp: new Date().toISOString(),\n };\n\n try {\n let sourceCard: CCv3Data | null = null;\n let _sourcePlatform: PlatformId | null = null;\n\n if (resolution === 'merge' && mergedCard) {\n sourceCard = mergedCard;\n } else if (resolution === 'local') {\n // Find local platform\n for (const [platform, id] of Object.entries(syncState.platformIds)) {\n if (platform !== syncState.conflict.remotePlatform) {\n const adapter = this.platforms.get(platform as PlatformId);\n if (adapter) {\n sourceCard = await adapter.getCard(id);\n _sourcePlatform = platform as PlatformId;\n break;\n }\n }\n }\n } else if (resolution === 'remote') {\n const adapter = this.platforms.get(syncState.conflict.remotePlatform);\n if (adapter) {\n const remoteId = syncState.platformIds[syncState.conflict.remotePlatform];\n if (remoteId) {\n sourceCard = await adapter.getCard(remoteId);\n _sourcePlatform = syncState.conflict.remotePlatform;\n }\n }\n }\n\n if (!sourceCard) {\n throw new Error('Could not resolve conflict: source card not found');\n }\n\n // Push resolved card to all platforms\n const newHash = await this.hashCard(sourceCard);\n const now = new Date().toISOString();\n\n for (const [platform, id] of Object.entries(syncState.platformIds)) {\n const adapter = this.platforms.get(platform as PlatformId);\n if (adapter) {\n await adapter.saveCard(sourceCard, id);\n syncState.lastSync[platform as PlatformId] = now;\n }\n }\n\n syncState.versionHash = newHash;\n syncState.status = 'synced';\n syncState.conflict = undefined;\n\n await this.stateStore.set(syncState);\n\n this.emit('card:synced', { syncState, resolution });\n\n return {\n success: true,\n operation,\n newState: syncState,\n };\n } catch (err) {\n return {\n success: false,\n operation,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n\n /**\n * Fork a card from a remote source\n *\n * Creates a local copy of a remote card with fork metadata stored in\n * the card's extensions and sync state. Optionally notifies the source\n * instance about the fork.\n *\n * @param sourceFederatedId - Federated URI of the source card\n * @param sourcePlatform - Platform where the source card resides\n * @param targetPlatform - Platform to save the fork to\n * @param options - Fork options\n */\n async forkCard(\n sourceFederatedId: string,\n sourcePlatform: PlatformId,\n targetPlatform: PlatformId,\n options?: {\n modifications?: Partial<CCv3Data['data']>;\n notifySource?: boolean;\n sourceInbox?: string;\n }\n ): Promise<ForkResult> {\n const operation: SyncOperation = {\n type: 'fork',\n cardId: sourceFederatedId,\n sourcePlatform,\n targetPlatform,\n timestamp: new Date().toISOString(),\n };\n\n try {\n // Get source adapter\n const sourceAdapter = this.platforms.get(sourcePlatform);\n if (!sourceAdapter) {\n throw new Error(`Platform not registered: ${sourcePlatform}`);\n }\n\n // Get target adapter\n const targetAdapter = this.platforms.get(targetPlatform);\n if (!targetAdapter) {\n throw new Error(`Platform not registered: ${targetPlatform}`);\n }\n\n // Find source card by looking up sync state or trying to get directly\n const sourceState = await this.stateStore.get(sourceFederatedId);\n const sourceLocalId = sourceState?.platformIds[sourcePlatform];\n\n if (!sourceLocalId) {\n throw new Error(`Cannot find source card: ${sourceFederatedId}`);\n }\n\n const sourceCard = await sourceAdapter.getCard(sourceLocalId);\n if (!sourceCard) {\n throw new Error(`Source card not found: ${sourceLocalId}`);\n }\n\n // Create forked card with modifications and metadata\n const forkedCard = this.createForkedCard(\n sourceCard,\n sourceFederatedId,\n sourcePlatform,\n sourceState?.versionHash,\n options?.modifications\n );\n\n // Save fork to target platform\n const forkLocalId = await targetAdapter.saveCard(forkedCard);\n\n // Create sync state for the fork\n const forkFederatedId = generateCardId(this.baseUrl, `${targetPlatform}-${forkLocalId}`);\n const forkHash = await this.hashCard(forkedCard);\n const now = new Date().toISOString();\n\n const forkReference: ForkReference = {\n federatedId: sourceFederatedId,\n platform: sourcePlatform,\n forkedAt: now,\n sourceVersionHash: sourceState?.versionHash,\n };\n\n const forkState: CardSyncState = {\n localId: forkLocalId,\n federatedId: forkFederatedId,\n platformIds: { [targetPlatform]: forkLocalId },\n lastSync: { [targetPlatform]: now },\n versionHash: forkHash,\n status: 'synced',\n forkedFrom: forkReference,\n };\n\n await this.stateStore.set(forkState);\n\n this.emit('card:forked', {\n forkState,\n sourceFederatedId,\n sourcePlatform,\n targetPlatform,\n });\n\n return {\n success: true,\n operation,\n forkState,\n sourceFederatedId,\n forkFederatedId,\n };\n } catch (err) {\n this.emit('sync:failed', { operation, error: err });\n\n return {\n success: false,\n operation,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n\n /**\n * Create a forked card with fork metadata in extensions\n */\n private createForkedCard(\n source: CCv3Data,\n sourceFederatedId: string,\n sourcePlatform: PlatformId,\n sourceVersionHash?: string,\n modifications?: Partial<CCv3Data['data']>\n ): CCv3Data {\n const now = new Date().toISOString();\n\n // Deep clone the source card\n const forked: CCv3Data = JSON.parse(JSON.stringify(source));\n\n // Apply modifications if provided\n if (modifications) {\n Object.assign(forked.data, modifications);\n }\n\n // Ensure extensions object exists\n if (!forked.data.extensions) {\n forked.data.extensions = {};\n }\n\n // Add fork metadata to character-foundry extension\n const cfExtension = (forked.data.extensions['character-foundry'] as Record<string, unknown>) || {};\n cfExtension.forkedFrom = {\n federatedId: sourceFederatedId,\n platform: sourcePlatform,\n forkedAt: now,\n sourceVersionHash,\n };\n forked.data.extensions['character-foundry'] = cfExtension;\n\n // Update version info\n forked.data.character_version = generateUUID();\n\n return forked;\n }\n\n /**\n * Handle incoming fork notification\n *\n * Called when another instance notifies us that they forked one of our cards.\n * Increments the fork count and stores the notification.\n */\n async handleForkNotification(activity: ForkActivity): Promise<void> {\n const parsed = parseForkActivity(activity);\n if (!parsed) {\n throw new Error('Invalid fork activity');\n }\n\n const { sourceCardId, actor } = parsed;\n\n // Find our sync state for the source card\n const sourceState = await this.stateStore.get(sourceCardId);\n if (!sourceState) {\n // We don't have this card tracked - might be from before federation\n // or the card was deleted. Just ignore.\n return;\n }\n\n // Determine the forking platform from the actor URL\n // Actor URLs are typically like: https://hub.example.com/users/alice\n let forkPlatform: PlatformId = 'custom';\n if (actor.includes('archive')) forkPlatform = 'archive';\n else if (actor.includes('hub')) forkPlatform = 'hub';\n else if (actor.includes('editor')) forkPlatform = 'editor';\n else if (actor.includes('chub')) forkPlatform = 'chub';\n else if (actor.includes('risu')) forkPlatform = 'risu';\n\n const notification: ForkNotification = {\n forkId: parsed.forkedCard.id,\n actorId: actor,\n platform: forkPlatform,\n timestamp: new Date().toISOString(),\n };\n\n // Update state (increment count, add notification)\n const notifications = sourceState.forkNotifications || [];\n if (notifications.length < 100) {\n notifications.push(notification);\n }\n\n sourceState.forksCount = (sourceState.forksCount || 0) + 1;\n sourceState.forkNotifications = notifications;\n\n await this.stateStore.set(sourceState);\n\n this.emit('card:fork-received', {\n sourceCardId,\n notification,\n newForkCount: sourceState.forksCount,\n });\n }\n\n /**\n * Handle incoming install notification\n *\n * Called when a consumer (SillyTavern, Voxta) notifies us that they installed one of our cards.\n * Increments the install count and stores the notification.\n */\n async handleInstallNotification(activity: InstallActivity): Promise<void> {\n const parsed = parseInstallActivity(activity);\n if (!parsed) {\n throw new Error('Invalid install activity');\n }\n\n const { cardId, actor, platform } = parsed;\n\n // Find our sync state for the card\n const cardState = await this.stateStore.get(cardId);\n if (!cardState) {\n // We don't have this card tracked - might be from before federation\n // or the card was deleted. Just ignore.\n return;\n }\n\n // Determine the installing platform\n const installPlatform: PlatformId = platform || 'custom';\n\n // Initialize stats if needed\n if (!cardState.stats) {\n cardState.stats = {\n installCount: 0,\n installsByPlatform: {},\n forkCount: cardState.forksCount || 0,\n likeCount: 0,\n lastUpdated: new Date().toISOString(),\n };\n }\n\n // Increment counts\n cardState.stats.installCount++;\n cardState.stats.installsByPlatform[installPlatform] =\n (cardState.stats.installsByPlatform[installPlatform] || 0) + 1;\n cardState.stats.lastUpdated = new Date().toISOString();\n\n await this.stateStore.set(cardState);\n\n this.emit('card:install-received', {\n cardId,\n platform: installPlatform,\n actorId: actor,\n newInstallCount: cardState.stats.installCount,\n });\n }\n\n /**\n * Get stats for a card\n */\n async getCardStats(federatedId: string): Promise<CardStats | null> {\n const state = await this.stateStore.get(federatedId);\n return state?.stats || null;\n }\n\n /**\n * Get fork count for a card\n */\n async getForkCount(federatedId: string): Promise<number> {\n const state = await this.stateStore.get(federatedId);\n return state?.forksCount || 0;\n }\n\n /**\n * Get install count for a card\n */\n async getInstallCount(federatedId: string): Promise<number> {\n const state = await this.stateStore.get(federatedId);\n return state?.stats?.installCount || 0;\n }\n\n /**\n * Find all local forks of a source card\n */\n async findForks(sourceFederatedId: string): Promise<CardSyncState[]> {\n const allStates = await this.stateStore.list();\n return allStates.filter(\n (state) => state.forkedFrom?.federatedId === sourceFederatedId\n );\n }\n\n /**\n * Stop the sync engine\n */\n dispose(): void {\n if (this.autoSyncTimer) {\n clearInterval(this.autoSyncTimer);\n }\n this.listeners.clear();\n }\n}\n","/**\n * Sync State Stores\n *\n * Implementations for storing sync state.\n */\n\nimport type {\n SyncStateStore,\n CardSyncState,\n PlatformId,\n ForkNotification,\n} from './types.js';\n\n/**\n * In-memory sync state store\n * Useful for testing and single-session sync\n */\nexport class MemorySyncStateStore implements SyncStateStore {\n private states: Map<string, CardSyncState> = new Map();\n\n async get(federatedId: string): Promise<CardSyncState | null> {\n return this.states.get(federatedId) || null;\n }\n\n async set(state: CardSyncState): Promise<void> {\n this.states.set(state.federatedId, { ...state });\n }\n\n async delete(federatedId: string): Promise<void> {\n this.states.delete(federatedId);\n }\n\n async list(): Promise<CardSyncState[]> {\n return Array.from(this.states.values());\n }\n\n async findByPlatformId(\n platform: PlatformId,\n platformId: string\n ): Promise<CardSyncState | null> {\n for (const state of this.states.values()) {\n if (state.platformIds[platform] === platformId) {\n return state;\n }\n }\n return null;\n }\n\n /**\n * Clear all states (for testing)\n */\n clear(): void {\n this.states.clear();\n }\n\n /**\n * Increment fork count and add notification\n */\n async incrementForkCount(\n federatedId: string,\n notification: ForkNotification\n ): Promise<void> {\n const state = this.states.get(federatedId);\n if (!state) return;\n\n // Cap notifications at 100\n const notifications = state.forkNotifications || [];\n if (notifications.length < 100) {\n notifications.push(notification);\n }\n\n state.forksCount = (state.forksCount || 0) + 1;\n state.forkNotifications = notifications;\n this.states.set(federatedId, state);\n }\n\n /**\n * Get fork count for a card\n */\n async getForkCount(federatedId: string): Promise<number> {\n return this.states.get(federatedId)?.forksCount || 0;\n }\n\n /**\n * Find all cards that are forks of a given source card\n */\n async findForks(sourceFederatedId: string): Promise<CardSyncState[]> {\n const forks: CardSyncState[] = [];\n for (const state of this.states.values()) {\n if (state.forkedFrom?.federatedId === sourceFederatedId) {\n forks.push(state);\n }\n }\n return forks;\n }\n}\n\n/**\n * JSON file-based sync state store\n * Persists state to a JSON file\n */\nexport class FileSyncStateStore implements SyncStateStore {\n private states: Map<string, CardSyncState> = new Map();\n private filePath: string;\n private saveDebounce?: ReturnType<typeof setTimeout>;\n private fs: {\n readFile: (path: string, encoding: string) => Promise<string>;\n writeFile: (path: string, data: string) => Promise<void>;\n mkdir: (path: string, options: { recursive: boolean }) => Promise<void>;\n };\n\n constructor(\n filePath: string,\n fs: {\n readFile: (path: string, encoding: string) => Promise<string>;\n writeFile: (path: string, data: string) => Promise<void>;\n mkdir: (path: string, options: { recursive: boolean }) => Promise<void>;\n }\n ) {\n this.filePath = filePath;\n this.fs = fs;\n }\n\n /**\n * Load state from file\n */\n async load(): Promise<void> {\n try {\n const content = await this.fs.readFile(this.filePath, 'utf-8');\n const data = JSON.parse(content) as CardSyncState[];\n this.states.clear();\n for (const state of data) {\n this.states.set(state.federatedId, state);\n }\n } catch {\n // File doesn't exist or is invalid - start fresh\n this.states.clear();\n }\n }\n\n /**\n * Save state to file (debounced)\n */\n private scheduleSave(): void {\n if (this.saveDebounce) {\n clearTimeout(this.saveDebounce);\n }\n this.saveDebounce = setTimeout(() => this.saveNow(), 1000);\n }\n\n /**\n * Save state to file immediately\n */\n async saveNow(): Promise<void> {\n const data = Array.from(this.states.values());\n const json = JSON.stringify(data, null, 2);\n\n // Ensure directory exists\n const dir = this.filePath.substring(0, this.filePath.lastIndexOf('/'));\n if (dir) {\n await this.fs.mkdir(dir, { recursive: true });\n }\n\n await this.fs.writeFile(this.filePath, json);\n }\n\n async get(federatedId: string): Promise<CardSyncState | null> {\n return this.states.get(federatedId) || null;\n }\n\n async set(state: CardSyncState): Promise<void> {\n this.states.set(state.federatedId, { ...state });\n this.scheduleSave();\n }\n\n async delete(federatedId: string): Promise<void> {\n this.states.delete(federatedId);\n this.scheduleSave();\n }\n\n async list(): Promise<CardSyncState[]> {\n return Array.from(this.states.values());\n }\n\n async findByPlatformId(\n platform: PlatformId,\n platformId: string\n ): Promise<CardSyncState | null> {\n for (const state of this.states.values()) {\n if (state.platformIds[platform] === platformId) {\n return state;\n }\n }\n return null;\n }\n\n /**\n * Increment fork count and add notification\n */\n async incrementForkCount(\n federatedId: string,\n notification: ForkNotification\n ): Promise<void> {\n const state = this.states.get(federatedId);\n if (!state) return;\n\n // Cap notifications at 100\n const notifications = state.forkNotifications || [];\n if (notifications.length < 100) {\n notifications.push(notification);\n }\n\n state.forksCount = (state.forksCount || 0) + 1;\n state.forkNotifications = notifications;\n this.states.set(federatedId, state);\n this.scheduleSave();\n }\n\n /**\n * Get fork count for a card\n */\n async getForkCount(federatedId: string): Promise<number> {\n return this.states.get(federatedId)?.forksCount || 0;\n }\n\n /**\n * Find all cards that are forks of a given source card\n */\n async findForks(sourceFederatedId: string): Promise<CardSyncState[]> {\n const forks: CardSyncState[] = [];\n for (const state of this.states.values()) {\n if (state.forkedFrom?.federatedId === sourceFederatedId) {\n forks.push(state);\n }\n }\n return forks;\n }\n}\n\n/**\n * Storage interface (compatible with Web Storage API)\n */\nexport interface StorageInterface {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\n/**\n * Create a state store backed by a Storage interface (e.g., localStorage in browser)\n *\n * @param key - Prefix key for storage entries\n * @param storage - Storage interface (pass localStorage in browser, or a polyfill in Node.js)\n */\nexport function createLocalStorageStore(\n key: string,\n storage: StorageInterface\n): SyncStateStore {\n return {\n async get(federatedId: string): Promise<CardSyncState | null> {\n const data = storage.getItem(`${key}:${federatedId}`);\n return data ? JSON.parse(data) : null;\n },\n\n async set(state: CardSyncState): Promise<void> {\n storage.setItem(`${key}:${state.federatedId}`, JSON.stringify(state));\n // Also update index\n const indexKey = `${key}:__index__`;\n const index = JSON.parse(storage.getItem(indexKey) || '[]') as string[];\n if (!index.includes(state.federatedId)) {\n index.push(state.federatedId);\n storage.setItem(indexKey, JSON.stringify(index));\n }\n },\n\n async delete(federatedId: string): Promise<void> {\n storage.removeItem(`${key}:${federatedId}`);\n // Update index\n const indexKey = `${key}:__index__`;\n const index = JSON.parse(storage.getItem(indexKey) || '[]') as string[];\n const newIndex = index.filter((id) => id !== federatedId);\n storage.setItem(indexKey, JSON.stringify(newIndex));\n },\n\n async list(): Promise<CardSyncState[]> {\n const indexKey = `${key}:__index__`;\n const index = JSON.parse(storage.getItem(indexKey) || '[]') as string[];\n const states: CardSyncState[] = [];\n for (const id of index) {\n const data = storage.getItem(`${key}:${id}`);\n if (data) {\n states.push(JSON.parse(data));\n }\n }\n return states;\n },\n\n async findByPlatformId(\n platform: PlatformId,\n platformId: string\n ): Promise<CardSyncState | null> {\n const states = await this.list();\n return states.find((s) => s.platformIds[platform] === platformId) || null;\n },\n };\n}\n","/**\n * D1 Sync State Store\n *\n * Cloudflare D1-compatible implementation of SyncStateStore for production\n * federation support on Cloudflare Workers.\n */\n\nimport type { SyncStateStore, CardSyncState, PlatformId, ForkNotification, CardStats } from './types.js';\n\n/**\n * Minimal D1Database interface\n * Compatible with Cloudflare Workers D1 API\n */\nexport interface D1Database {\n prepare(query: string): D1PreparedStatement;\n exec(query: string): Promise<D1ExecResult>;\n batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;\n}\n\nexport interface D1PreparedStatement {\n bind(...values: unknown[]): D1PreparedStatement;\n first<T = unknown>(column?: string): Promise<T | null>;\n run(): Promise<D1Result>;\n all<T = unknown>(): Promise<D1Result<T>>;\n}\n\nexport interface D1Result<T = unknown> {\n results: T[];\n success: boolean;\n meta: {\n duration: number;\n changes: number;\n last_row_id: number;\n served_by?: string;\n };\n}\n\nexport interface D1ExecResult {\n count: number;\n duration: number;\n}\n\n/**\n * Database row representation for CardSyncState\n */\ninterface SyncStateRow {\n federated_id: string;\n local_id: string;\n platform_ids: string;\n last_sync: string;\n version_hash: string;\n status: 'synced' | 'pending' | 'conflict' | 'error';\n conflict: string | null;\n forked_from: string | null;\n forks_count: number;\n fork_notifications: string | null;\n stats: string | null;\n created_at: number;\n updated_at: number;\n}\n\n/**\n * Validate table name to prevent SQL injection\n *\n * Table names must be alphanumeric with underscores only.\n * This is critical since table names cannot be parameterized in SQL.\n */\nfunction validateTableName(name: string): void {\n // Only allow alphanumeric and underscores, must start with letter\n const validPattern = /^[a-zA-Z][a-zA-Z0-9_]*$/;\n if (!validPattern.test(name)) {\n throw new Error(\n `Invalid table name \"${name}\": must start with a letter and contain only alphanumeric characters and underscores`\n );\n }\n // Reasonable length limit\n if (name.length > 64) {\n throw new Error(`Invalid table name \"${name}\": must be 64 characters or less`);\n }\n}\n\n/**\n * D1-compatible implementation of SyncStateStore\n *\n * Stores federation sync state in Cloudflare D1 (SQLite).\n *\n * @example\n * ```typescript\n * const store = new D1SyncStateStore(env.DB);\n * await store.init();\n *\n * // Use with SyncEngine\n * const engine = new SyncEngine({\n * stateStore: store,\n * // ...\n * });\n * ```\n */\nexport class D1SyncStateStore implements SyncStateStore {\n private db: D1Database;\n private tableName: string;\n\n /**\n * Create a new D1SyncStateStore\n *\n * @param db - D1Database instance (from env.DB in Workers)\n * @param tableName - Table name for storing sync state (default: 'federation_sync_state')\n * @throws If tableName contains invalid characters (SQL injection prevention)\n */\n constructor(db: D1Database, tableName = 'federation_sync_state') {\n validateTableName(tableName);\n this.db = db;\n this.tableName = tableName;\n }\n\n /**\n * Initialize the database table\n *\n * Creates the sync state table if it doesn't exist.\n * Safe to call multiple times (idempotent).\n */\n async init(): Promise<void> {\n await this.db.exec(`\n CREATE TABLE IF NOT EXISTS ${this.tableName} (\n federated_id TEXT PRIMARY KEY,\n local_id TEXT NOT NULL,\n platform_ids TEXT NOT NULL,\n last_sync TEXT NOT NULL,\n version_hash TEXT NOT NULL,\n status TEXT NOT NULL CHECK (status IN ('synced', 'pending', 'conflict', 'error')),\n conflict TEXT,\n forked_from TEXT,\n forks_count INTEGER DEFAULT 0,\n fork_notifications TEXT,\n stats TEXT,\n created_at INTEGER DEFAULT (unixepoch()),\n updated_at INTEGER DEFAULT (unixepoch())\n )\n `);\n\n // Create index for platform ID lookups\n await this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_${this.tableName}_local_id ON ${this.tableName}(local_id)\n `);\n\n // Create index for finding forks of a source card\n await this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_${this.tableName}_forked_from\n ON ${this.tableName}(json_extract(forked_from, '$.federatedId'))\n `);\n }\n\n /**\n * Get sync state for a federated card ID\n */\n async get(federatedId: string): Promise<CardSyncState | null> {\n const row = await this.db\n .prepare(`SELECT * FROM ${this.tableName} WHERE federated_id = ?`)\n .bind(federatedId)\n .first<SyncStateRow>();\n\n return row ? this.rowToState(row) : null;\n }\n\n /**\n * Save or update sync state\n */\n async set(state: CardSyncState): Promise<void> {\n const platformIds = JSON.stringify(state.platformIds);\n const lastSync = JSON.stringify(state.lastSync);\n const conflict = state.conflict ? JSON.stringify(state.conflict) : null;\n const forkedFrom = state.forkedFrom ? JSON.stringify(state.forkedFrom) : null;\n const forkNotifications = state.forkNotifications\n ? JSON.stringify(state.forkNotifications)\n : null;\n const stats = state.stats ? JSON.stringify(state.stats) : null;\n\n await this.db\n .prepare(\n `INSERT INTO ${this.tableName} (federated_id, local_id, platform_ids, last_sync, version_hash, status, conflict, forked_from, forks_count, fork_notifications, stats, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, unixepoch())\n ON CONFLICT(federated_id) DO UPDATE SET\n local_id = excluded.local_id,\n platform_ids = excluded.platform_ids,\n last_sync = excluded.last_sync,\n version_hash = excluded.version_hash,\n status = excluded.status,\n conflict = excluded.conflict,\n forked_from = excluded.forked_from,\n forks_count = excluded.forks_count,\n fork_notifications = excluded.fork_notifications,\n stats = excluded.stats,\n updated_at = unixepoch()`\n )\n .bind(\n state.federatedId,\n state.localId,\n platformIds,\n lastSync,\n state.versionHash,\n state.status,\n conflict,\n forkedFrom,\n state.forksCount ?? 0,\n forkNotifications,\n stats\n )\n .run();\n }\n\n /**\n * Delete sync state for a federated card ID\n */\n async delete(federatedId: string): Promise<void> {\n await this.db\n .prepare(`DELETE FROM ${this.tableName} WHERE federated_id = ?`)\n .bind(federatedId)\n .run();\n }\n\n /**\n * List all sync states\n */\n async list(): Promise<CardSyncState[]> {\n const result = await this.db\n .prepare(`SELECT * FROM ${this.tableName} ORDER BY updated_at DESC`)\n .all<SyncStateRow>();\n\n return result.results.map((row) => this.rowToState(row));\n }\n\n /**\n * Find sync state by platform-specific ID\n *\n * @param platform - Platform identifier\n * @param platformId - Platform-specific card ID\n * @returns Sync state if found, null otherwise\n */\n async findByPlatformId(\n platform: PlatformId,\n platformId: string\n ): Promise<CardSyncState | null> {\n // D1 supports JSON extraction with json_extract()\n const result = await this.db\n .prepare(\n `SELECT * FROM ${this.tableName}\n WHERE json_extract(platform_ids, ?) = ?`\n )\n .bind(`$.${platform}`, platformId)\n .first<SyncStateRow>();\n\n return result ? this.rowToState(result) : null;\n }\n\n /**\n * Find sync state by local ID\n *\n * @param localId - Local card ID\n * @returns Sync state if found, null otherwise\n */\n async findByLocalId(localId: string): Promise<CardSyncState | null> {\n const row = await this.db\n .prepare(`SELECT * FROM ${this.tableName} WHERE local_id = ?`)\n .bind(localId)\n .first<SyncStateRow>();\n\n return row ? this.rowToState(row) : null;\n }\n\n /**\n * Get count of all sync states\n */\n async count(): Promise<number> {\n const result = await this.db\n .prepare(`SELECT COUNT(*) as count FROM ${this.tableName}`)\n .first<{ count: number }>();\n\n return result?.count ?? 0;\n }\n\n /**\n * List sync states by status\n *\n * @param status - Status to filter by\n * @returns Array of sync states with matching status\n */\n async listByStatus(\n status: CardSyncState['status']\n ): Promise<CardSyncState[]> {\n const result = await this.db\n .prepare(\n `SELECT * FROM ${this.tableName} WHERE status = ? ORDER BY updated_at DESC`\n )\n .bind(status)\n .all<SyncStateRow>();\n\n return result.results.map((row) => this.rowToState(row));\n }\n\n /**\n * Clear all sync states (for testing)\n *\n * ⚠️ Use with caution - this deletes all data\n */\n async clear(): Promise<void> {\n await this.db.prepare(`DELETE FROM ${this.tableName}`).run();\n }\n\n /**\n * Increment fork count and add notification for a source card\n *\n * Used when receiving a Fork activity to track that someone forked this card.\n * Notifications are capped at 100 to prevent unbounded growth.\n */\n async incrementForkCount(\n federatedId: string,\n notification: ForkNotification\n ): Promise<void> {\n const state = await this.get(federatedId);\n if (!state) {\n return;\n }\n\n // Cap notifications at 100\n const notifications = state.forkNotifications || [];\n if (notifications.length < 100) {\n notifications.push(notification);\n }\n\n state.forksCount = (state.forksCount || 0) + 1;\n state.forkNotifications = notifications;\n\n await this.set(state);\n }\n\n /**\n * Get fork count for a card\n */\n async getForkCount(federatedId: string): Promise<number> {\n const result = await this.db\n .prepare(`SELECT forks_count FROM ${this.tableName} WHERE federated_id = ?`)\n .bind(federatedId)\n .first<{ forks_count: number }>();\n\n return result?.forks_count ?? 0;\n }\n\n /**\n * Find all cards that are forks of a given source card\n */\n async findForks(sourceFederatedId: string): Promise<CardSyncState[]> {\n const result = await this.db\n .prepare(\n `SELECT * FROM ${this.tableName}\n WHERE json_extract(forked_from, '$.federatedId') = ?\n ORDER BY updated_at DESC`\n )\n .bind(sourceFederatedId)\n .all<SyncStateRow>();\n\n return result.results.map((row) => this.rowToState(row));\n }\n\n /**\n * Convert database row to CardSyncState\n */\n private rowToState(row: SyncStateRow): CardSyncState {\n const state: CardSyncState = {\n federatedId: row.federated_id,\n localId: row.local_id,\n platformIds: JSON.parse(row.platform_ids) as Partial<Record<PlatformId, string>>,\n lastSync: JSON.parse(row.last_sync) as Partial<Record<PlatformId, string>>,\n versionHash: row.version_hash,\n status: row.status,\n conflict: row.conflict\n ? (JSON.parse(row.conflict) as CardSyncState['conflict'])\n : undefined,\n };\n\n // Add fork fields if present\n if (row.forked_from) {\n state.forkedFrom = JSON.parse(row.forked_from) as CardSyncState['forkedFrom'];\n }\n if (row.forks_count > 0) {\n state.forksCount = row.forks_count;\n }\n if (row.fork_notifications) {\n state.forkNotifications = JSON.parse(row.fork_notifications) as ForkNotification[];\n }\n // Add stats if present\n if (row.stats) {\n state.stats = JSON.parse(row.stats) as CardStats;\n }\n\n return state;\n }\n}\n","/**\n * Base Platform Adapter\n *\n * Abstract base class for platform adapters.\n */\n\nimport type { CCv3Data } from '@character-foundry/schemas';\nimport { generateUUID } from '@character-foundry/core';\nimport type { PlatformId, PlatformAdapter } from '../types.js';\n\n/**\n * Card with metadata from adapter\n */\nexport interface AdapterCard {\n id: string;\n card: CCv3Data;\n updatedAt: string;\n createdAt?: string;\n}\n\n/**\n * Asset from adapter\n */\nexport interface AdapterAsset {\n name: string;\n type: string;\n data: Uint8Array;\n mimeType?: string;\n}\n\n/**\n * Abstract base adapter with common functionality\n */\nexport abstract class BasePlatformAdapter implements PlatformAdapter {\n abstract readonly platform: PlatformId;\n abstract readonly displayName: string;\n\n /**\n * Check if platform is available\n */\n abstract isAvailable(): Promise<boolean>;\n\n /**\n * Get a card by local ID\n */\n abstract getCard(localId: string): Promise<CCv3Data | null>;\n\n /**\n * List all cards\n */\n abstract listCards(options?: {\n limit?: number;\n offset?: number;\n since?: string;\n }): Promise<AdapterCard[]>;\n\n /**\n * Save/update a card\n */\n abstract saveCard(card: CCv3Data, localId?: string): Promise<string>;\n\n /**\n * Delete a card\n */\n abstract deleteCard(localId: string): Promise<boolean>;\n\n /**\n * Get card assets\n */\n abstract getAssets(localId: string): Promise<AdapterAsset[]>;\n\n /**\n * Get last modified timestamp\n */\n abstract getLastModified(localId: string): Promise<string | null>;\n\n /**\n * Generate a new local ID using crypto-grade randomness\n */\n protected generateId(): string {\n const timestamp = Date.now();\n const random = generateUUID().split('-')[0]; // Use first segment of UUID\n return `${timestamp}-${random}`;\n }\n}\n\n/**\n * In-memory adapter for testing\n */\nexport class MemoryPlatformAdapter extends BasePlatformAdapter {\n readonly platform: PlatformId;\n readonly displayName: string;\n\n private cards: Map<string, AdapterCard> = new Map();\n private assets: Map<string, AdapterAsset[]> = new Map();\n\n constructor(platform: PlatformId = 'custom', displayName = 'Memory Store') {\n super();\n this.platform = platform;\n this.displayName = displayName;\n }\n\n async isAvailable(): Promise<boolean> {\n return true;\n }\n\n async getCard(localId: string): Promise<CCv3Data | null> {\n const entry = this.cards.get(localId);\n return entry?.card || null;\n }\n\n async listCards(options?: {\n limit?: number;\n offset?: number;\n since?: string;\n }): Promise<AdapterCard[]> {\n let cards = Array.from(this.cards.values());\n\n // Filter by since\n if (options?.since) {\n const sinceDate = new Date(options.since);\n cards = cards.filter((c) => new Date(c.updatedAt) > sinceDate);\n }\n\n // Sort by updated time (newest first)\n cards.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());\n\n // Apply pagination\n const offset = options?.offset || 0;\n const limit = options?.limit || cards.length;\n cards = cards.slice(offset, offset + limit);\n\n return cards;\n }\n\n async saveCard(card: CCv3Data, localId?: string): Promise<string> {\n const id = localId || this.generateId();\n const now = new Date().toISOString();\n\n const existing = this.cards.get(id);\n\n this.cards.set(id, {\n id,\n card,\n updatedAt: now,\n createdAt: existing?.createdAt || now,\n });\n\n return id;\n }\n\n async deleteCard(localId: string): Promise<boolean> {\n const existed = this.cards.has(localId);\n this.cards.delete(localId);\n this.assets.delete(localId);\n return existed;\n }\n\n async getAssets(localId: string): Promise<AdapterAsset[]> {\n return this.assets.get(localId) || [];\n }\n\n async getLastModified(localId: string): Promise<string | null> {\n const entry = this.cards.get(localId);\n return entry?.updatedAt || null;\n }\n\n /**\n * Set assets for a card (for testing)\n */\n setAssets(localId: string, assets: AdapterAsset[]): void {\n this.assets.set(localId, assets);\n }\n\n /**\n * Clear all data (for testing)\n */\n clear(): void {\n this.cards.clear();\n this.assets.clear();\n }\n\n /**\n * Get card count\n */\n count(): number {\n return this.cards.size;\n }\n}\n","/**\n * HTTP Platform Adapter\n *\n * Adapter for platforms accessible via HTTP API.\n */\n\nimport type { CCv3Data } from '@character-foundry/schemas';\nimport type { PlatformId } from '../types.js';\nimport { BasePlatformAdapter, type AdapterCard, type AdapterAsset } from './base.js';\nimport { getLogger } from '../logger.js';\n\n/**\n * HTTP fetch function type\n */\nexport type FetchFn = (url: string, init?: RequestInit) => Promise<Response>;\n\n/**\n * HTTP adapter configuration\n */\nexport interface HttpAdapterConfig {\n /** Platform ID */\n platform: PlatformId;\n /** Display name */\n displayName: string;\n /** Base URL of the API */\n baseUrl: string;\n /** API endpoints */\n endpoints: {\n /** List cards: GET */\n list: string;\n /** Get card: GET (append /:id) */\n get: string;\n /** Create card: POST */\n create: string;\n /** Update card: PUT/PATCH (append /:id) */\n update: string;\n /** Delete card: DELETE (append /:id) */\n delete: string;\n /** Get assets: GET (append /:id) */\n assets?: string;\n /** Health check: GET */\n health?: string;\n };\n /** Authentication header */\n auth?: {\n type: 'bearer' | 'api-key' | 'basic';\n token: string;\n header?: string; // Custom header name for api-key\n };\n /** Custom fetch function (for Node.js or testing) */\n fetch?: FetchFn;\n /**\n * Request timeout in milliseconds.\n * Default: 30000 (30 seconds)\n *\n * @security Prevents hanging connections that could cause resource exhaustion.\n */\n timeout?: number;\n /** Response transformers */\n transformers?: {\n /** Transform list response to AdapterCard[] */\n list?: (response: unknown) => AdapterCard[];\n /** Transform get response to CCv3Data */\n get?: (response: unknown) => CCv3Data;\n /** Transform card to create request body */\n create?: (card: CCv3Data) => unknown;\n /** Transform card to update request body */\n update?: (card: CCv3Data) => unknown;\n /** Extract ID from create response */\n extractId?: (response: unknown) => string;\n };\n}\n\n/**\n * Error thrown when resource ID validation fails\n */\nexport class InvalidResourceIdError extends Error {\n constructor(\n public readonly id: string,\n public readonly reason: string\n ) {\n super(`Invalid resource ID \"${id}\": ${reason}`);\n this.name = 'InvalidResourceIdError';\n }\n}\n\n/**\n * Validate and encode a resource ID to prevent SSRF and path traversal attacks.\n *\n * @security This prevents:\n * - Path traversal (../ sequences)\n * - Absolute path injection (/etc/passwd)\n * - Protocol injection (https://evil.com)\n * - URL encoding attacks (%2e%2e)\n *\n * @param id - The resource ID to validate\n * @returns URL-encoded safe ID\n * @throws InvalidResourceIdError if ID contains malicious patterns\n */\nfunction validateAndEncodeId(id: string): string {\n // Decode any URL encoding first to catch %2e%2e (encoded ..)\n let decoded: string;\n try {\n decoded = decodeURIComponent(id);\n } catch {\n // Invalid URL encoding - reject\n throw new InvalidResourceIdError(id, 'invalid URL encoding');\n }\n\n // Check decoded value for path traversal\n if (decoded.includes('..')) {\n throw new InvalidResourceIdError(id, 'path traversal detected');\n }\n\n // Check for absolute paths\n if (decoded.startsWith('/')) {\n throw new InvalidResourceIdError(id, 'absolute path not allowed');\n }\n\n // Check for protocol injection (http://, https://, file://, etc.)\n if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(decoded)) {\n throw new InvalidResourceIdError(id, 'protocol injection detected');\n }\n\n // Check for backslashes (Windows-style paths)\n if (decoded.includes('\\\\')) {\n throw new InvalidResourceIdError(id, 'backslash not allowed');\n }\n\n // URL-encode the ID for safe concatenation\n return encodeURIComponent(id);\n}\n\n/** Default timeout for HTTP requests (30 seconds) */\nconst DEFAULT_TIMEOUT_MS = 30000;\n\n/**\n * HTTP-based platform adapter\n */\nexport class HttpPlatformAdapter extends BasePlatformAdapter {\n readonly platform: PlatformId;\n readonly displayName: string;\n\n private config: HttpAdapterConfig;\n private fetchFn: FetchFn;\n private timeoutMs: number;\n\n constructor(config: HttpAdapterConfig) {\n super();\n this.platform = config.platform;\n this.displayName = config.displayName;\n this.config = config;\n this.fetchFn = config.fetch || globalThis.fetch.bind(globalThis);\n this.timeoutMs = config.timeout ?? DEFAULT_TIMEOUT_MS;\n }\n\n /**\n * Execute fetch with timeout using AbortController\n *\n * @security Prevents hanging connections that could cause resource exhaustion\n */\n private async fetchWithTimeout(url: string, init?: RequestInit): Promise<Response> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);\n\n try {\n const response = await this.fetchFn(url, {\n ...init,\n signal: controller.signal,\n });\n return response;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Build headers for requests\n */\n private buildHeaders(contentType?: string): Record<string, string> {\n const headers: Record<string, string> = {};\n\n if (contentType) {\n headers['Content-Type'] = contentType;\n }\n\n if (this.config.auth) {\n switch (this.config.auth.type) {\n case 'bearer':\n headers['Authorization'] = `Bearer ${this.config.auth.token}`;\n break;\n case 'api-key':\n headers[this.config.auth.header || 'X-API-Key'] = this.config.auth.token;\n break;\n case 'basic':\n headers['Authorization'] = `Basic ${this.config.auth.token}`;\n break;\n }\n }\n\n return headers;\n }\n\n /**\n * Build full URL with optional resource ID.\n *\n * @security ID is validated and encoded to prevent SSRF/path traversal.\n * @throws InvalidResourceIdError if ID contains malicious patterns\n */\n private buildUrl(endpoint: string, id?: string): string {\n let url = `${this.config.baseUrl}${endpoint}`;\n if (id) {\n // Validate and encode ID to prevent SSRF and path traversal\n const safeId = validateAndEncodeId(id);\n url = `${url}/${safeId}`;\n }\n return url;\n }\n\n async isAvailable(): Promise<boolean> {\n try {\n const endpoint = this.config.endpoints.health || this.config.endpoints.list;\n const response = await this.fetchWithTimeout(\n this.buildUrl(endpoint),\n {\n method: 'GET',\n headers: this.buildHeaders(),\n }\n );\n return response.ok;\n } catch {\n return false;\n }\n }\n\n async getCard(localId: string): Promise<CCv3Data | null> {\n try {\n const response = await this.fetchWithTimeout(\n this.buildUrl(this.config.endpoints.get, localId),\n {\n method: 'GET',\n headers: this.buildHeaders(),\n }\n );\n\n if (!response.ok) {\n if (response.status === 404) return null;\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const data = await response.json();\n return this.config.transformers?.get\n ? this.config.transformers.get(data)\n : data as CCv3Data;\n } catch (err) {\n getLogger().error(`[federation] Failed to get card ${localId}:`, err);\n return null;\n }\n }\n\n async listCards(options?: {\n limit?: number;\n offset?: number;\n since?: string;\n }): Promise<AdapterCard[]> {\n const url = new URL(this.buildUrl(this.config.endpoints.list));\n\n if (options?.limit) {\n url.searchParams.set('limit', String(options.limit));\n }\n if (options?.offset) {\n url.searchParams.set('offset', String(options.offset));\n }\n if (options?.since) {\n url.searchParams.set('since', options.since);\n }\n\n const response = await this.fetchWithTimeout(\n url.toString(),\n {\n method: 'GET',\n headers: this.buildHeaders(),\n }\n );\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const data = await response.json();\n return this.config.transformers?.list\n ? this.config.transformers.list(data)\n : data as AdapterCard[];\n }\n\n async saveCard(card: CCv3Data, localId?: string): Promise<string> {\n if (localId) {\n // Update existing\n const body = this.config.transformers?.update\n ? this.config.transformers.update(card)\n : card;\n\n const response = await this.fetchWithTimeout(\n this.buildUrl(this.config.endpoints.update, localId),\n {\n method: 'PUT',\n headers: this.buildHeaders('application/json'),\n body: JSON.stringify(body),\n }\n );\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n return localId;\n } else {\n // Create new\n const body = this.config.transformers?.create\n ? this.config.transformers.create(card)\n : card;\n\n const response = await this.fetchWithTimeout(\n this.buildUrl(this.config.endpoints.create),\n {\n method: 'POST',\n headers: this.buildHeaders('application/json'),\n body: JSON.stringify(body),\n }\n );\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const data = await response.json();\n return this.config.transformers?.extractId\n ? this.config.transformers.extractId(data)\n : (data as { id: string }).id;\n }\n }\n\n async deleteCard(localId: string): Promise<boolean> {\n const response = await this.fetchWithTimeout(\n this.buildUrl(this.config.endpoints.delete, localId),\n {\n method: 'DELETE',\n headers: this.buildHeaders(),\n }\n );\n\n return response.ok;\n }\n\n async getAssets(localId: string): Promise<AdapterAsset[]> {\n if (!this.config.endpoints.assets) {\n return [];\n }\n\n try {\n const response = await this.fetchWithTimeout(\n this.buildUrl(this.config.endpoints.assets, localId),\n {\n method: 'GET',\n headers: this.buildHeaders(),\n }\n );\n\n if (!response.ok) {\n return [];\n }\n\n return (await response.json()) as AdapterAsset[];\n } catch {\n return [];\n }\n }\n\n async getLastModified(localId: string): Promise<string | null> {\n try {\n const response = await this.fetchWithTimeout(\n this.buildUrl(this.config.endpoints.get, localId),\n {\n method: 'HEAD',\n headers: this.buildHeaders(),\n }\n );\n\n if (!response.ok) {\n return null;\n }\n\n return response.headers.get('Last-Modified');\n } catch {\n return null;\n }\n }\n}\n\n/**\n * Create an HTTP adapter for Character Archive API\n */\nexport function createArchiveAdapter(\n baseUrl: string,\n apiKey?: string\n): HttpPlatformAdapter {\n return new HttpPlatformAdapter({\n platform: 'archive',\n displayName: 'Character Archive',\n baseUrl,\n endpoints: {\n list: '/api/characters',\n get: '/api/characters',\n create: '/api/characters',\n update: '/api/characters',\n delete: '/api/characters',\n assets: '/api/characters/assets',\n health: '/api/health',\n },\n auth: apiKey ? { type: 'api-key', token: apiKey } : undefined,\n transformers: {\n list: (data) => {\n const response = data as { characters: Array<{ id: string; data: CCv3Data; updatedAt: string }> };\n return response.characters.map((c) => ({\n id: c.id,\n card: c.data,\n updatedAt: c.updatedAt,\n }));\n },\n get: (data) => (data as { data: CCv3Data }).data,\n extractId: (data) => (data as { id: string }).id,\n },\n });\n}\n\n/**\n * Create an HTTP adapter for CardsHub API\n */\nexport function createHubAdapter(\n baseUrl: string,\n apiKey?: string\n): HttpPlatformAdapter {\n return new HttpPlatformAdapter({\n platform: 'hub',\n displayName: 'CardsHub',\n baseUrl,\n endpoints: {\n list: '/api/cards',\n get: '/api/cards',\n create: '/api/cards',\n update: '/api/cards',\n delete: '/api/cards',\n assets: '/api/cards/assets',\n health: '/api/health',\n },\n auth: apiKey ? { type: 'bearer', token: apiKey } : undefined,\n });\n}\n","/**\n * SillyTavern Platform Adapter\n *\n * Adapter for SillyTavern integration via plugin.\n * This provides the interface that a SillyTavern plugin would implement.\n */\n\nimport type { CCv3Data } from '@character-foundry/schemas';\nimport type { PlatformId } from '../types.js';\nimport { BasePlatformAdapter, type AdapterCard, type AdapterAsset } from './base.js';\n\n/**\n * SillyTavern character format (simplified)\n */\nexport interface STCharacter {\n /** Character filename/ID */\n name: string;\n /** Avatar filename */\n avatar: string;\n /** Character data (TavernCard format) */\n data: {\n name: string;\n description: string;\n personality: string;\n scenario: string;\n first_mes: string;\n mes_example: string;\n creator_notes?: string;\n system_prompt?: string;\n post_history_instructions?: string;\n alternate_greetings?: string[];\n character_book?: {\n entries: Array<{\n keys: string[];\n content: string;\n enabled: boolean;\n [key: string]: unknown;\n }>;\n [key: string]: unknown;\n };\n tags?: string[];\n creator?: string;\n character_version?: string;\n extensions?: Record<string, unknown>;\n };\n}\n\n/**\n * Usage stats for a character in SillyTavern\n */\nexport interface STCharacterStats {\n /** Number of chats with this character */\n chatCount?: number;\n /** Total messages exchanged */\n messageCount?: number;\n /** Last time this character was used */\n lastUsed?: string;\n /** When the character was first added */\n installedAt?: string;\n}\n\n/**\n * Interface that SillyTavern plugin must implement\n */\nexport interface SillyTavernBridge {\n /** Get all characters */\n getCharacters(): Promise<STCharacter[]>;\n\n /** Get a character by name/ID */\n getCharacter(name: string): Promise<STCharacter | null>;\n\n /** Save a character */\n saveCharacter(character: STCharacter): Promise<string>;\n\n /** Delete a character */\n deleteCharacter(name: string): Promise<boolean>;\n\n /** Get character avatar */\n getAvatar(name: string): Promise<Uint8Array | null>;\n\n /** Check if SillyTavern is available */\n isConnected(): Promise<boolean>;\n\n /**\n * Get usage stats for a character (optional)\n * SillyTavern plugins can implement this to report usage data back to hub\n */\n getCharacterStats?(name: string): Promise<STCharacterStats | null>;\n\n /**\n * Get stats for all characters (optional)\n * Returns a map of character name to stats\n */\n getAllStats?(): Promise<Map<string, STCharacterStats>>;\n\n /**\n * Notify hub that a card was installed (optional)\n * Called when a new character is added from federation\n * @param federatedId - The federated URI of the installed card\n * @param hubInbox - The hub's inbox URL to notify\n */\n notifyInstall?(federatedId: string, hubInbox: string): Promise<void>;\n}\n\n/**\n * Convert STCharacter to CCv3Data\n */\nexport function stCharacterToCCv3(st: STCharacter): CCv3Data {\n return {\n spec: 'chara_card_v3',\n spec_version: '3.0',\n data: {\n name: st.data.name,\n description: st.data.description,\n personality: st.data.personality,\n scenario: st.data.scenario,\n first_mes: st.data.first_mes,\n mes_example: st.data.mes_example,\n creator_notes: st.data.creator_notes || '',\n system_prompt: st.data.system_prompt || '',\n post_history_instructions: st.data.post_history_instructions || '',\n alternate_greetings: st.data.alternate_greetings || [],\n group_only_greetings: [],\n tags: st.data.tags || [],\n creator: st.data.creator || '',\n character_version: st.data.character_version || '',\n character_book: st.data.character_book as CCv3Data['data']['character_book'],\n extensions: st.data.extensions || {},\n },\n };\n}\n\n/**\n * Convert CCv3Data to STCharacter\n */\nexport function ccv3ToSTCharacter(card: CCv3Data, filename?: string): STCharacter {\n const data = card.data;\n const name = filename || data.name.replace(/[^a-zA-Z0-9]/g, '_');\n\n return {\n name,\n avatar: `${name}.png`,\n data: {\n name: data.name,\n description: data.description,\n personality: data.personality ?? '', // Coerce null to empty string\n scenario: data.scenario,\n first_mes: data.first_mes,\n mes_example: data.mes_example ?? '', // Coerce null to empty string\n creator_notes: data.creator_notes,\n system_prompt: data.system_prompt,\n post_history_instructions: data.post_history_instructions,\n alternate_greetings: data.alternate_greetings,\n // Cast character_book - ST format is compatible but types differ slightly\n character_book: data.character_book as STCharacter['data']['character_book'],\n tags: data.tags,\n creator: data.creator,\n character_version: data.character_version,\n extensions: data.extensions,\n },\n };\n}\n\n/**\n * SillyTavern adapter\n * Requires a bridge implementation from the SillyTavern plugin\n */\nexport class SillyTavernAdapter extends BasePlatformAdapter {\n readonly platform: PlatformId = 'sillytavern';\n readonly displayName = 'SillyTavern';\n\n private bridge: SillyTavernBridge;\n\n constructor(bridge: SillyTavernBridge) {\n super();\n this.bridge = bridge;\n }\n\n async isAvailable(): Promise<boolean> {\n return this.bridge.isConnected();\n }\n\n async getCard(localId: string): Promise<CCv3Data | null> {\n const st = await this.bridge.getCharacter(localId);\n if (!st) return null;\n return stCharacterToCCv3(st);\n }\n\n async listCards(options?: {\n limit?: number;\n offset?: number;\n since?: string;\n }): Promise<AdapterCard[]> {\n const characters = await this.bridge.getCharacters();\n\n let cards = characters.map((st) => ({\n id: st.name,\n card: stCharacterToCCv3(st),\n updatedAt: new Date().toISOString(), // ST doesn't track this\n }));\n\n // Apply pagination\n const offset = options?.offset || 0;\n const limit = options?.limit || cards.length;\n cards = cards.slice(offset, offset + limit);\n\n return cards;\n }\n\n async saveCard(card: CCv3Data, localId?: string): Promise<string> {\n const stChar = ccv3ToSTCharacter(card, localId);\n return this.bridge.saveCharacter(stChar);\n }\n\n async deleteCard(localId: string): Promise<boolean> {\n return this.bridge.deleteCharacter(localId);\n }\n\n async getAssets(localId: string): Promise<AdapterAsset[]> {\n const avatar = await this.bridge.getAvatar(localId);\n if (!avatar) return [];\n\n return [{\n name: 'avatar',\n type: 'icon',\n data: avatar,\n mimeType: 'image/png',\n }];\n }\n\n async getLastModified(localId: string): Promise<string | null> {\n // SillyTavern doesn't track modification times\n const char = await this.bridge.getCharacter(localId);\n return char ? new Date().toISOString() : null;\n }\n\n /**\n * Get stats for a character (if bridge supports it)\n */\n async getStats(localId: string): Promise<STCharacterStats | null> {\n if (!this.bridge.getCharacterStats) {\n return null;\n }\n return this.bridge.getCharacterStats(localId);\n }\n\n /**\n * Get stats for all characters (if bridge supports it)\n */\n async getAllStats(): Promise<Map<string, STCharacterStats> | null> {\n if (!this.bridge.getAllStats) {\n return null;\n }\n return this.bridge.getAllStats();\n }\n\n /**\n * Notify hub about installation (if bridge supports it)\n */\n async notifyInstall(federatedId: string, hubInbox: string): Promise<boolean> {\n if (!this.bridge.notifyInstall) {\n return false;\n }\n await this.bridge.notifyInstall(federatedId, hubInbox);\n return true;\n }\n}\n\n/**\n * Create a mock SillyTavern bridge for testing\n */\nexport function createMockSTBridge(): SillyTavernBridge & {\n characters: Map<string, STCharacter>;\n avatars: Map<string, Uint8Array>;\n stats: Map<string, STCharacterStats>;\n installNotifications: Array<{ federatedId: string; hubInbox: string }>;\n} {\n const characters = new Map<string, STCharacter>();\n const avatars = new Map<string, Uint8Array>();\n const stats = new Map<string, STCharacterStats>();\n const installNotifications: Array<{ federatedId: string; hubInbox: string }> = [];\n\n return {\n characters,\n avatars,\n stats,\n installNotifications,\n\n async getCharacters() {\n return Array.from(characters.values());\n },\n\n async getCharacter(name) {\n return characters.get(name) || null;\n },\n\n async saveCharacter(character) {\n characters.set(character.name, character);\n // Auto-create initial stats on save\n if (!stats.has(character.name)) {\n stats.set(character.name, {\n installedAt: new Date().toISOString(),\n chatCount: 0,\n messageCount: 0,\n });\n }\n return character.name;\n },\n\n async deleteCharacter(name) {\n const existed = characters.has(name);\n characters.delete(name);\n avatars.delete(name);\n stats.delete(name);\n return existed;\n },\n\n async getAvatar(name) {\n return avatars.get(name) || null;\n },\n\n async isConnected() {\n return true;\n },\n\n async getCharacterStats(name) {\n return stats.get(name) || null;\n },\n\n async getAllStats() {\n return new Map(stats);\n },\n\n async notifyInstall(federatedId, hubInbox) {\n installNotifications.push({ federatedId, hubInbox });\n },\n };\n}\n","import type { FederationConfig } from '../types.js';\nimport { assertFederationEnabled } from '../index.js';\n\nexport interface WebFingerLink {\n rel: string;\n type?: string;\n href: string;\n template?: string;\n}\n\nexport interface WebFingerResponse {\n subject: string;\n aliases?: string[];\n properties?: Record<string, string>;\n links: WebFingerLink[];\n}\n\n/**\n * Handle WebFinger discovery request\n * \n * @param resource - The resource URI being requested (e.g. acct:user@domain)\n * @param config - Federation configuration containing actor details\n * @returns The WebFinger response object or null if not found\n */\nexport function handleWebFinger(\n resource: string,\n config: FederationConfig\n): WebFingerResponse | null {\n assertFederationEnabled('handleWebFinger');\n if (!resource) return null;\n\n const { actor } = config;\n \n // Derive domain from actor ID\n let domain: string;\n try {\n domain = new URL(actor.id).host;\n } catch {\n return null;\n }\n\n const validResources = [\n `acct:${actor.preferredUsername}@${domain}`,\n actor.id\n ];\n\n if (!validResources.includes(resource)) {\n return null;\n }\n\n return {\n subject: `acct:${actor.preferredUsername}@${domain}`,\n aliases: [actor.id],\n links: [\n {\n rel: 'self',\n type: 'application/activity+json',\n href: actor.id,\n },\n {\n rel: 'http://webfinger.net/rel/profile-page',\n type: 'text/html',\n href: actor.id, // Often the actor ID resolves to a profile page in browsers\n }\n ],\n };\n}\n","import type { FederationConfig } from '../types.js';\nimport { assertFederationEnabled } from '../index.js';\n\nexport interface NodeInfoLink {\n rel: string;\n href: string;\n}\n\nexport interface NodeInfoDiscoveryResponse {\n links: NodeInfoLink[];\n}\n\nexport interface NodeInfoUsage {\n users: {\n total: number;\n activeMonth?: number;\n activeHalfyear?: number;\n };\n localPosts?: number;\n localComments?: number;\n}\n\nexport interface NodeInfoResponse {\n version: string;\n software: {\n name: string;\n version: string;\n };\n protocols: string[];\n services: {\n inbound: string[];\n outbound: string[];\n };\n openRegistrations: boolean;\n usage: NodeInfoUsage;\n metadata: Record<string, unknown>;\n}\n\n/**\n * Handle NodeInfo discovery request (/.well-known/nodeinfo)\n * \n * @param baseUrl - The base URL of the server (e.g. https://hub.example.com)\n * @returns The NodeInfo discovery response\n */\nexport function handleNodeInfoDiscovery(baseUrl: string): NodeInfoDiscoveryResponse {\n assertFederationEnabled('handleNodeInfoDiscovery');\n // Ensure no trailing slash\n const origin = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n \n return {\n links: [\n {\n rel: 'http://nodeinfo.diaspora.foundation/ns/schema/2.0',\n href: `${origin}/api/federation/nodeinfo/2.0`,\n },\n {\n rel: 'http://nodeinfo.diaspora.foundation/ns/schema/2.1',\n href: `${origin}/api/federation/nodeinfo/2.1`,\n },\n ],\n };\n}\n\n/**\n * Handle NodeInfo data request\n * \n * @param config - Federation configuration\n * @param version - NodeInfo version ('2.0' or '2.1')\n * @returns The NodeInfo response\n */\nexport function handleNodeInfo(\n config: FederationConfig,\n version: '2.0' | '2.1' = '2.1'\n): NodeInfoResponse {\n assertFederationEnabled('handleNodeInfo');\n return {\n version,\n software: {\n name: 'character-foundry',\n version: '0.1.0', \n },\n protocols: [\n 'activitypub',\n ],\n services: {\n inbound: [],\n outbound: [],\n },\n openRegistrations: false,\n usage: {\n users: {\n total: 1, // TODO: Count actual users if multi-tenant\n },\n },\n metadata: {\n nodeName: config.actor.name,\n nodeDescription: config.actor.summary,\n },\n };\n}\n","import type { FederationConfig, FederatedActor } from '../types.js';\nimport { assertFederationEnabled } from '../index.js';\n\n/**\n * Handle Actor profile request\n * \n * @param config - Federation configuration\n * @returns The Actor JSON-LD object\n */\nexport function handleActor(config: FederationConfig): FederatedActor {\n assertFederationEnabled('handleActor');\n // Ensure @context is present (it should be in the actor object, but good to verify)\n return {\n ...config.actor,\n '@context': config.actor['@context'] || [\n 'https://www.w3.org/ns/activitystreams',\n 'https://w3id.org/security/v1'\n ],\n } as FederatedActor;\n}\n","/**\n * Moderation ActivityPub Activities\n *\n * Create and parse Flag/Block activities per ActivityPub spec.\n */\n\nimport { generateActivityId } from '../activitypub.js';\nimport type { FlagActivity, BlockActivity, ReportCategory } from './types.js';\n\n/**\n * ActivityPub context for moderation activities\n */\nexport const MODERATION_ACTIVITY_CONTEXT = [\n 'https://www.w3.org/ns/activitystreams',\n {\n moderation: 'https://character-foundry.dev/ns/moderation#',\n category: { '@id': 'moderation:category' },\n },\n] as const;\n\n/**\n * Create a Flag activity for reporting content/actors\n *\n * Per ActivityPub spec, Flag is delivered directly to the target's instance inbox,\n * NOT wrapped in a Create activity.\n *\n * @example\n * ```typescript\n * const flag = createFlagActivity(\n * 'https://myinstance.com/users/reporter',\n * 'https://other.com/cards/problematic-card',\n * 'https://myinstance.com',\n * {\n * content: 'This card contains spam',\n * category: 'spam',\n * to: ['https://other.com/inbox'],\n * }\n * );\n * ```\n */\nexport function createFlagActivity(\n reporterActorId: string,\n targetIds: string | string[],\n baseUrl: string,\n options?: {\n /** Report description/reason */\n content?: string;\n /** Report category (flexible string) */\n category?: ReportCategory;\n /** Target inbox(es) */\n to?: string[];\n }\n): FlagActivity {\n const targets = Array.isArray(targetIds) ? targetIds : [targetIds];\n\n return {\n '@context': [...MODERATION_ACTIVITY_CONTEXT],\n id: generateActivityId(baseUrl),\n type: 'Flag',\n actor: reporterActorId,\n object: targets.length === 1 ? targets[0]! : targets,\n content: options?.content,\n category: options?.category,\n published: new Date().toISOString(),\n to: options?.to,\n };\n}\n\n/**\n * Parse incoming Flag activity\n *\n * @returns Parsed flag data or null if invalid\n */\nexport function parseFlagActivity(activity: unknown): {\n actorId: string;\n targetIds: string[];\n content?: string;\n category?: string;\n activityId: string;\n} | null {\n if (!activity || typeof activity !== 'object') return null;\n\n const act = activity as Record<string, unknown>;\n\n // Must be a Flag activity\n if (act.type !== 'Flag') return null;\n\n // Required fields\n if (typeof act.actor !== 'string') return null;\n if (typeof act.id !== 'string') return null;\n if (!act.object) return null;\n\n // Parse targets\n const targets = Array.isArray(act.object)\n ? act.object.filter((t): t is string => typeof t === 'string')\n : typeof act.object === 'string'\n ? [act.object]\n : [];\n\n if (targets.length === 0) return null;\n\n return {\n actorId: act.actor,\n targetIds: targets,\n content: typeof act.content === 'string' ? act.content : undefined,\n category: typeof act.category === 'string' ? act.category : undefined,\n activityId: act.id,\n };\n}\n\n/**\n * Validate a Flag activity has all required fields\n */\nexport function validateFlagActivity(activity: unknown): {\n valid: boolean;\n error?: string;\n} {\n const parsed = parseFlagActivity(activity);\n if (!parsed) {\n return {\n valid: false,\n error: 'Invalid flag activity: missing required fields',\n };\n }\n\n // Validate all target URIs\n for (const target of parsed.targetIds) {\n try {\n new URL(target);\n } catch {\n return {\n valid: false,\n error: `Invalid flag activity: target \"${target}\" is not a valid URI`,\n };\n }\n }\n\n // Validate content length if provided\n if (parsed.content && parsed.content.length > 5000) {\n return {\n valid: false,\n error: 'Flag content too long (max 5000 chars)',\n };\n }\n\n return { valid: true };\n}\n\n/**\n * Create a Block activity for instance-level blocking\n *\n * Block activities are typically:\n * 1. Stored locally for enforcement\n * 2. Optionally announced to trusted federation peers\n *\n * @example\n * ```typescript\n * const block = createBlockActivity(\n * 'https://myinstance.com/actor',\n * 'evil.example.com',\n * 'https://myinstance.com',\n * { summary: 'Spam and harassment' }\n * );\n * ```\n */\nexport function createBlockActivity(\n adminActorId: string,\n blockedTarget: string,\n baseUrl: string,\n options?: {\n /** Block reason */\n summary?: string;\n /** Recipients for federation announcement */\n to?: string[];\n }\n): BlockActivity {\n return {\n '@context': [...MODERATION_ACTIVITY_CONTEXT],\n id: generateActivityId(baseUrl),\n type: 'Block',\n actor: adminActorId,\n object: blockedTarget,\n summary: options?.summary,\n published: new Date().toISOString(),\n };\n}\n\n/**\n * Parse incoming Block activity\n *\n * @returns Parsed block data or null if invalid\n */\nexport function parseBlockActivity(activity: unknown): {\n actorId: string;\n targetId: string;\n summary?: string;\n activityId: string;\n} | null {\n if (!activity || typeof activity !== 'object') return null;\n\n const act = activity as Record<string, unknown>;\n\n // Must be a Block activity\n if (act.type !== 'Block') return null;\n\n // Required fields\n if (typeof act.actor !== 'string') return null;\n if (typeof act.object !== 'string') return null;\n if (typeof act.id !== 'string') return null;\n\n return {\n actorId: act.actor,\n targetId: act.object,\n summary: typeof act.summary === 'string' ? act.summary : undefined,\n activityId: act.id,\n };\n}\n\n/**\n * Validate a Block activity has all required fields\n */\nexport function validateBlockActivity(activity: unknown): {\n valid: boolean;\n error?: string;\n} {\n const parsed = parseBlockActivity(activity);\n if (!parsed) {\n return {\n valid: false,\n error: 'Invalid block activity: missing required fields',\n };\n }\n\n // Block target should be a domain or actor URI\n // Domains won't parse as URLs, actor URIs will\n const target = parsed.targetId;\n const isDomain = /^[a-zA-Z0-9][a-zA-Z0-9-]*(\\.[a-zA-Z0-9][a-zA-Z0-9-]*)+$/.test(target);\n let isValidUri = false;\n try {\n new URL(target);\n isValidUri = true;\n } catch {\n // Not a URI\n }\n\n if (!isDomain && !isValidUri) {\n return {\n valid: false,\n error: 'Invalid block activity: target must be a domain or valid URI',\n };\n }\n\n return { valid: true };\n}\n","/**\n * HTTP Signatures\n *\n * Implements HTTP message signing per draft-cavage-http-signatures\n * for ActivityPub federation.\n *\n * Works in both Node.js and browser/Workers environments using\n * the Web Crypto API.\n */\n\nimport type { FederatedActivity, FederatedActor } from './types.js';\nimport { getLogger } from './logger.js';\n\n/**\n * Parsed HTTP signature header\n */\nexport interface ParsedSignature {\n keyId: string;\n algorithm: string;\n headers: string[];\n signature: string;\n}\n\n/**\n * Required headers for strict signature validation.\n * These headers MUST be included in the signed header list when strictMode is enabled.\n */\nexport const REQUIRED_SIGNED_HEADERS = ['(request-target)', 'host', 'date'] as const;\n\n/**\n * Signature validation options\n */\nexport interface SignatureValidationOptions {\n /** HTTP method (GET, POST, etc.) */\n method: string;\n /** Request path (e.g., /inbox) */\n path: string;\n /** Function to fetch actor by ID */\n fetchActor: (actorId: string) => Promise<FederatedActor | null>;\n /** Maximum age in seconds for Date header. Default: 300 (5 minutes) */\n maxAge?: number;\n /**\n * Enable strict signature validation mode.\n *\n * When enabled, signatures MUST include these headers: (request-target), host, date.\n * This prevents replay attacks and cross-host request reuse.\n *\n * Default: false (for backwards compatibility with existing federation peers)\n * Recommended: true for new deployments\n *\n * @security Without strict mode, attackers can:\n * - Replay captured requests if Date is not signed\n * - Reuse signatures across different hosts if host is not signed\n */\n strictMode?: boolean;\n}\n\n/**\n * Signature validation result\n */\nexport interface SignatureValidationResult {\n valid: boolean;\n error?: string;\n actor?: FederatedActor;\n keyId?: string;\n}\n\n/**\n * Request signing options\n */\nexport interface SigningOptions {\n /** Private key in PEM format */\n privateKeyPem: string;\n /** Key ID (usually actor#main-key) */\n keyId: string;\n /** HTTP method */\n method: string;\n /** Request path */\n path: string;\n /** Optional: target host header */\n host?: string;\n /** Optional: digest header */\n digest?: string;\n /** Optional: content-type header */\n contentType?: string;\n}\n\n/**\n * Parse an HTTP Signature header\n *\n * Format: keyId=\"...\",algorithm=\"...\",headers=\"...\",signature=\"...\"\n */\nexport function parseSignatureHeader(header: string): ParsedSignature | null {\n const params: Record<string, string> = {};\n\n // Parse key=\"value\" pairs\n const regex = /(\\w+)=\"([^\"]+)\"/g;\n let match;\n while ((match = regex.exec(header)) !== null) {\n params[match[1]!] = match[2]!;\n }\n\n if (!params.keyId || !params.signature) {\n return null;\n }\n\n return {\n keyId: params.keyId,\n algorithm: params.algorithm || 'rsa-sha256',\n headers: (params.headers || '(request-target) host date')\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .map((h) => h.toLowerCase()),\n signature: params.signature,\n };\n}\n\n/**\n * Successful signing string result\n */\nexport interface SigningStringSuccess {\n success: true;\n signingString: string;\n}\n\n/**\n * Failed signing string result (missing headers)\n */\nexport interface SigningStringFailure {\n success: false;\n error: string;\n missingHeaders: string[];\n}\n\n/**\n * Result of building a signing string\n */\nexport type SigningStringResult = SigningStringSuccess | SigningStringFailure;\n\n/**\n * Build the signing string from headers\n *\n * @security If a header is listed in headerNames but missing from the request,\n * this function returns an error. This prevents downgrade attacks where an\n * attacker claims to sign headers that aren't actually present.\n *\n * Synthetic headers ((request-target), (created), (expires)) are always allowed.\n */\nexport function buildSigningString(\n method: string,\n path: string,\n headers: Headers,\n headerNames: string[]\n): string {\n const result = buildSigningStringStrict(method, path, headers, headerNames);\n if (!result.success) {\n // Missing claimed headers is always a verification failure.\n throw new Error(result.error);\n }\n return result.signingString;\n}\n\n/**\n * Build signing string with strict header validation\n *\n * Returns an error if any non-synthetic header in headerNames is missing.\n * Use this for new code that wants strict validation.\n */\nexport function buildSigningStringStrict(\n method: string,\n path: string,\n headers: Headers,\n headerNames: string[]\n): SigningStringResult {\n const lines: string[] = [];\n const missingHeaders: string[] = [];\n\n // Synthetic headers that don't need to be present in the request\n const syntheticHeaders = new Set(['(request-target)', '(created)', '(expires)']);\n\n for (const name of headerNames) {\n const normalizedName = name.toLowerCase();\n\n if (normalizedName === '(request-target)') {\n lines.push(`(request-target): ${method.toLowerCase()} ${path}`);\n } else if (normalizedName === '(created)') {\n // Skip for now - optional synthetic header\n } else if (normalizedName === '(expires)') {\n // Skip for now - optional synthetic header\n } else {\n const value = headers.get(normalizedName);\n if (value !== null) {\n lines.push(`${normalizedName}: ${value}`);\n } else if (!syntheticHeaders.has(normalizedName)) {\n // Header is listed as signed but not present - this is an error\n missingHeaders.push(normalizedName);\n }\n }\n }\n\n if (missingHeaders.length > 0) {\n return {\n success: false,\n error: `Signature claims to sign headers that are missing from request: ${missingHeaders.join(', ')}`,\n missingHeaders,\n };\n }\n\n return {\n success: true,\n signingString: lines.join('\\n'),\n };\n}\n\n/**\n * Options for HTTP signature verification\n */\nexport interface VerifyHttpSignatureOptions {\n /**\n * If true, verification fails when any claimed signed header is missing.\n * This prevents downgrade attacks where signatures claim to cover headers\n * that aren't actually present in the request.\n *\n * @security Enable this for production deployments to ensure signatures\n * actually cover all claimed headers.\n */\n strictHeaders?: boolean;\n}\n\n/**\n * Verify an HTTP signature using Web Crypto API\n *\n * @param options.strictHeaders - If true, fails when claimed headers are missing\n */\nexport async function verifyHttpSignature(\n parsed: ParsedSignature,\n publicKeyPem: string,\n method: string,\n path: string,\n headers: Headers,\n options: VerifyHttpSignatureOptions = {}\n): Promise<boolean> {\n try {\n // In strict mode, fail if any claimed header is missing from the request\n if (options.strictHeaders) {\n const strictResult = buildSigningStringStrict(method, path, headers, parsed.headers);\n if (!strictResult.success) {\n getLogger().warn(`[federation] Strict header verification failed: ${strictResult.error}`);\n return false;\n }\n }\n\n // Build the signing string\n const signingString = buildSigningString(method, path, headers, parsed.headers);\n\n // Import the public key\n const publicKey = await importPublicKey(publicKeyPem);\n if (!publicKey) {\n return false;\n }\n\n // Decode the signature from base64\n const signatureBytes = base64ToArrayBuffer(parsed.signature);\n\n // Verify the signature\n const encoder = new TextEncoder();\n const data = encoder.encode(signingString);\n\n const algorithm = parsed.algorithm === 'hs2019' ? 'RSASSA-PKCS1-v1_5' : 'RSASSA-PKCS1-v1_5';\n\n return await crypto.subtle.verify(\n { name: algorithm, hash: 'SHA-256' },\n publicKey,\n signatureBytes,\n data\n );\n } catch (error) {\n getLogger().error('[federation] Signature verification failed:', error);\n return false;\n }\n}\n\n/**\n * Validate an incoming activity's HTTP signature\n *\n * @security Enable `strictMode` for production deployments to prevent:\n * - Replay attacks (requires Date header in signature)\n * - Cross-host request reuse (requires host header in signature)\n */\nexport async function validateActivitySignature(\n activity: FederatedActivity,\n headers: Headers,\n options: SignatureValidationOptions\n): Promise<SignatureValidationResult> {\n // Check for Signature header\n const signatureHeader = headers.get('Signature');\n if (!signatureHeader) {\n return { valid: false, error: 'Missing Signature header' };\n }\n\n // Parse the signature\n const parsed = parseSignatureHeader(signatureHeader);\n if (!parsed) {\n return { valid: false, error: 'Invalid Signature header format' };\n }\n\n // STRICT MODE: Enforce required headers in signature\n // This prevents replay attacks and cross-host request reuse\n if (options.strictMode) {\n const missingHeaders = REQUIRED_SIGNED_HEADERS.filter(\n (h) => !parsed.headers.includes(h)\n );\n if (missingHeaders.length > 0) {\n return {\n valid: false,\n error: `Strict mode: signature missing required headers: ${missingHeaders.join(', ')}`,\n };\n }\n\n // In strict mode, Date header MUST be present (not just signed)\n if (!headers.get('Date')) {\n return { valid: false, error: 'Strict mode: Date header required' };\n }\n\n // In strict mode, host header MUST be present\n if (!headers.get('host') && !headers.get('Host')) {\n return { valid: false, error: 'Strict mode: host header required' };\n }\n }\n\n // Verify Date header freshness (if present)\n const dateHeader = headers.get('Date');\n if (dateHeader) {\n const requestDate = new Date(dateHeader);\n const maxAge = options.maxAge ?? 300; // 5 minutes\n const now = Date.now();\n const requestTime = requestDate.getTime();\n\n if (isNaN(requestTime)) {\n return { valid: false, error: 'Invalid Date header' };\n }\n\n if (Math.abs(now - requestTime) > maxAge * 1000) {\n return { valid: false, error: 'Request too old or in future' };\n }\n }\n\n // Verify the keyId matches the activity actor using strict URL equality\n // Key ID format: \"https://example.com/actors/alice#main-key\"\n // Actor format: \"https://example.com/actors/alice\"\n // @security Strict equality prevents URL confusion attacks where:\n // - actor: https://evil.com/victim\n // - keyId: https://evil.com/victim-fake#main-key (would match with startsWith)\n let keyIdBase: string;\n let actorBase: string;\n try {\n const keyIdUrl = new URL(parsed.keyId);\n const actorUrl = new URL(activity.actor);\n // Extract base (origin + pathname) - key ID has fragment, actor doesn't\n keyIdBase = `${keyIdUrl.origin}${keyIdUrl.pathname}`;\n actorBase = `${actorUrl.origin}${actorUrl.pathname}`;\n } catch {\n return { valid: false, error: 'Invalid key ID or actor URL' };\n }\n\n if (keyIdBase !== actorBase) {\n return { valid: false, error: 'Key ID does not match activity actor' };\n }\n\n // Extract actor ID from keyId for fetching\n const actorId = keyIdBase;\n\n // Fetch the actor to get their public key\n const actor = await options.fetchActor(actorId);\n if (!actor) {\n return { valid: false, error: 'Could not fetch actor' };\n }\n\n if (!actor.publicKey?.publicKeyPem) {\n return { valid: false, error: 'Actor has no public key' };\n }\n\n // Verify the key ID matches\n if (actor.publicKey.id !== parsed.keyId) {\n return { valid: false, error: 'Key ID mismatch' };\n }\n\n // Verify the signature\n // In strict mode, also enforce that claimed headers are actually present\n const valid = await verifyHttpSignature(\n parsed,\n actor.publicKey.publicKeyPem,\n options.method,\n options.path,\n headers,\n { strictHeaders: options.strictMode }\n );\n\n if (!valid) {\n return { valid: false, error: 'Invalid signature' };\n }\n\n return { valid: true, actor, keyId: parsed.keyId };\n}\n\n/**\n * Sign an outgoing HTTP request\n *\n * @param options.host - Required. The target host for the request.\n * @throws If host is not provided (required for valid signatures)\n */\nexport async function signRequest(\n options: SigningOptions\n): Promise<{ signature: string; date: string; host: string }> {\n // Host is required for valid signatures\n if (!options.host) {\n throw new Error('signRequest requires options.host - cannot generate valid signature without it');\n }\n\n const now = new Date();\n const dateString = now.toUTCString();\n\n // Build headers for signing\n const headersToSign = ['(request-target)', 'host', 'date'];\n const headerValues = new Headers();\n headerValues.set('date', dateString);\n headerValues.set('host', options.host);\n\n if (options.digest) {\n headersToSign.push('digest');\n headerValues.set('digest', options.digest);\n }\n\n if (options.contentType) {\n headersToSign.push('content-type');\n headerValues.set('content-type', options.contentType);\n }\n\n // Build signing string\n const signingString = buildSigningString(\n options.method,\n options.path,\n headerValues,\n headersToSign\n );\n\n // Import private key and sign\n const privateKey = await importPrivateKey(options.privateKeyPem);\n if (!privateKey) {\n throw new Error('Failed to import private key');\n }\n\n const encoder = new TextEncoder();\n const data = encoder.encode(signingString);\n\n const signatureBuffer = await crypto.subtle.sign(\n { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },\n privateKey,\n data\n );\n\n const signatureBase64 = arrayBufferToBase64(signatureBuffer);\n\n // Build Signature header\n const signatureHeader =\n `keyId=\"${options.keyId}\",` +\n `algorithm=\"rsa-sha256\",` +\n `headers=\"${headersToSign.join(' ')}\",` +\n `signature=\"${signatureBase64}\"`;\n\n return { signature: signatureHeader, date: dateString, host: options.host };\n}\n\n/**\n * Calculate SHA-256 digest for request body\n */\nexport async function calculateDigest(body: string | ArrayBuffer): Promise<string> {\n const encoder = new TextEncoder();\n const data = typeof body === 'string' ? encoder.encode(body) : body;\n\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n const hashBase64 = arrayBufferToBase64(hashBuffer);\n\n return `SHA-256=${hashBase64}`;\n}\n\n// ============ Crypto Utilities ============\n\n/**\n * Import a PEM-encoded public key\n */\nasync function importPublicKey(pem: string): Promise<CryptoKey | null> {\n try {\n // Remove PEM headers and convert to binary\n const pemContents = pem\n .replace(/-----BEGIN PUBLIC KEY-----/, '')\n .replace(/-----END PUBLIC KEY-----/, '')\n .replace(/-----BEGIN RSA PUBLIC KEY-----/, '')\n .replace(/-----END RSA PUBLIC KEY-----/, '')\n .replace(/\\s/g, '');\n\n const binaryDer = base64ToArrayBuffer(pemContents);\n\n return await crypto.subtle.importKey(\n 'spki',\n binaryDer,\n { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },\n false,\n ['verify']\n );\n } catch (error) {\n getLogger().error('[federation] Failed to import public key:', error);\n return null;\n }\n}\n\n/**\n * Import a PEM-encoded private key\n */\nasync function importPrivateKey(pem: string): Promise<CryptoKey | null> {\n try {\n // Remove PEM headers and convert to binary\n const pemContents = pem\n .replace(/-----BEGIN PRIVATE KEY-----/, '')\n .replace(/-----END PRIVATE KEY-----/, '')\n .replace(/-----BEGIN RSA PRIVATE KEY-----/, '')\n .replace(/-----END RSA PRIVATE KEY-----/, '')\n .replace(/\\s/g, '');\n\n const binaryDer = base64ToArrayBuffer(pemContents);\n\n return await crypto.subtle.importKey(\n 'pkcs8',\n binaryDer,\n { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },\n false,\n ['sign']\n );\n } catch (error) {\n getLogger().error('[federation] Failed to import private key:', error);\n return null;\n }\n}\n\n/**\n * Convert base64 string to ArrayBuffer\n */\nfunction base64ToArrayBuffer(base64: string): ArrayBuffer {\n // Handle URL-safe base64\n const normalized = base64.replace(/-/g, '+').replace(/_/g, '/');\n const binary = atob(normalized);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes.buffer;\n}\n\n/**\n * Convert ArrayBuffer to base64 string\n */\nfunction arrayBufferToBase64(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = '';\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]!);\n }\n return btoa(binary);\n}\n","/**\n * Inbox Handler\n *\n * Process incoming ActivityPub activities.\n */\n\nimport type {\n FederatedActivity,\n InboxResult,\n InboxHandlerOptions,\n ForkActivity,\n InstallActivity,\n ActivityType,\n} from '../types.js';\nimport type {\n ModerationStore,\n FlagActivity,\n BlockActivity,\n} from '../moderation/types.js';\nimport { parseActivity, parseForkActivity, parseInstallActivity } from '../activitypub.js';\nimport { parseFlagActivity, parseBlockActivity } from '../moderation/activities.js';\nimport { assertFederationEnabled } from '../index.js';\nimport { parseSignatureHeader, verifyHttpSignature, calculateDigest, REQUIRED_SIGNED_HEADERS } from '../http-signatures.js';\n\n/**\n * Extract host from an actor ID URL\n */\nfunction extractHostFromActorId(actorId: string): string | null {\n try {\n const url = new URL(actorId);\n return url.host;\n } catch {\n return null;\n }\n}\n\nfunction timingSafeEqualString(a: string, b: string): boolean {\n let mismatch = a.length === b.length ? 0 : 1;\n const maxLen = Math.max(a.length, b.length);\n\n for (let i = 0; i < maxLen; i++) {\n const aCode = a.charCodeAt(i) || 0;\n const bCode = b.charCodeAt(i) || 0;\n mismatch |= aCode ^ bCode;\n }\n\n return mismatch === 0;\n}\n\n/**\n * Handle incoming ActivityPub activities\n *\n * Parses and routes incoming activities to the appropriate handlers.\n * Currently supports Fork activities for fork notification handling.\n *\n * @example\n * ```typescript\n * // In your web framework (e.g., Hono, Express)\n * app.post('/inbox', async (req) => {\n * // IMPORTANT: Capture raw body BEFORE parsing for Digest verification\n * const rawBody = await req.text();\n * const body = JSON.parse(rawBody);\n *\n * const result = await handleInbox(body, req.headers, {\n * rawBody, // Required for body integrity verification\n * method: 'POST',\n * path: '/inbox', // Must match your actual route path\n * strictMode: true,\n * fetchActor: async (id) => fetchActorFromNetwork(id),\n * onFork: async (activity) => {\n * await syncEngine.handleForkNotification(activity);\n * },\n * });\n *\n * if (result.accepted) {\n * return new Response(null, { status: 202 });\n * } else {\n * return new Response(result.error, { status: 400 });\n * }\n * });\n * ```\n */\nexport async function handleInbox(\n body: unknown,\n headers: Headers | Record<string, string>,\n options: InboxHandlerOptions & {\n /** HTTP method (required for signature verification in strictMode) */\n method?: string;\n /** Request path (required for signature verification in strictMode) */\n path?: string;\n /**\n * Raw request body for Digest header verification.\n *\n * @security REQUIRED in strictMode when Digest header is present.\n * Without this, an attacker could modify the JSON body while keeping a valid signature\n * (since signatures only cover headers, not the body).\n *\n * Pass the raw bytes before JSON parsing:\n * ```typescript\n * const rawBody = await req.text();\n * const body = JSON.parse(rawBody);\n * handleInbox(body, headers, { rawBody, ... });\n * ```\n */\n rawBody?: string | ArrayBuffer;\n onFork?: (activity: ForkActivity) => Promise<void>;\n onInstall?: (activity: InstallActivity) => Promise<void>;\n onCreate?: (activity: FederatedActivity) => Promise<void>;\n onUpdate?: (activity: FederatedActivity) => Promise<void>;\n onDelete?: (activity: FederatedActivity) => Promise<void>;\n onLike?: (activity: FederatedActivity) => Promise<void>;\n onAnnounce?: (activity: FederatedActivity) => Promise<void>;\n onUndo?: (activity: FederatedActivity) => Promise<void>;\n // Moderation handlers\n onFlag?: (activity: FlagActivity, parsedReport: ReturnType<typeof parseFlagActivity>) => Promise<void>;\n onBlock?: (activity: BlockActivity, parsedBlock: ReturnType<typeof parseBlockActivity>) => Promise<void>;\n /**\n * Moderation store for instance blocking.\n * If provided, blocked instances are rejected BEFORE signature verification (saves CPU).\n */\n moderationStore?: ModerationStore;\n }\n): Promise<InboxResult> {\n assertFederationEnabled('handleInbox');\n\n try {\n const normalizedHeaders = headers instanceof Headers ? headers : new Headers(headers);\n\n // Optional shared network key gate (internal-only federation)\n const networkKey = typeof options.networkKey === 'string' ? options.networkKey : undefined;\n const networkKeyHeader = options.networkKeyHeader ?? 'X-Foundry-Network-Key';\n\n if (networkKey && networkKey.length > 0) {\n const provided = normalizedHeaders.get(networkKeyHeader);\n if (!provided || !timingSafeEqualString(provided, networkKey)) {\n return {\n accepted: false,\n error: 'Unauthorized: invalid or missing network key',\n };\n }\n }\n\n // Early instance block check - BEFORE signature verification (saves CPU for blocked instances)\n // We extract actor from the raw body to avoid full parsing costs\n if (options.moderationStore) {\n const actorId = typeof body === 'object' && body !== null && 'actor' in body\n ? String((body as Record<string, unknown>).actor)\n : null;\n\n if (actorId) {\n const host = extractHostFromActorId(actorId);\n if (host && await options.moderationStore.isInstanceBlocked(host)) {\n return {\n accepted: false,\n error: `Instance ${host} is blocked`,\n };\n }\n }\n }\n\n // Parse the activity\n let activity: FederatedActivity;\n try {\n activity = parseActivity(body);\n } catch (err) {\n return {\n accepted: false,\n error: `Invalid activity: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n\n // Verify HTTP signature in strict mode\n if (options.strictMode) {\n // Get signature header\n const signatureHeader = normalizedHeaders.get('signature');\n\n if (!signatureHeader) {\n return {\n accepted: false,\n error: 'Missing Signature header (required in strict mode)',\n };\n }\n\n // Parse the signature\n const parsedSig = parseSignatureHeader(signatureHeader);\n if (!parsedSig) {\n return {\n accepted: false,\n error: 'Invalid Signature header format',\n };\n }\n\n // If a network key is configured, require it to be in the signed header list.\n if (networkKey && networkKey.length > 0) {\n const requiredSigned = networkKeyHeader.toLowerCase();\n if (!parsedSig.headers.includes(requiredSigned)) {\n return {\n accepted: false,\n error: `Strict mode: signature missing required header: ${requiredSigned}`,\n };\n }\n }\n\n // STRICT MODE: Enforce required headers in signature\n // This matches validateActivitySignature behavior for consistency\n const missingSignedHeaders = REQUIRED_SIGNED_HEADERS.filter(\n (h) => !parsedSig.headers.includes(h)\n );\n if (missingSignedHeaders.length > 0) {\n return {\n accepted: false,\n error: `Strict mode: signature missing required headers: ${missingSignedHeaders.join(', ')}`,\n };\n }\n\n // STRICT MODE: Require Date header to be present (not just signed)\n const dateHeader = normalizedHeaders.get('date');\n if (!dateHeader) {\n return {\n accepted: false,\n error: 'Strict mode: Date header required',\n };\n }\n\n // STRICT MODE: Require host header to be present\n const hostHeader = normalizedHeaders.get('host');\n if (!hostHeader) {\n return {\n accepted: false,\n error: 'Strict mode: host header required',\n };\n }\n\n // Fetch the actor to get their public key\n const actor = await options.fetchActor(activity.actor);\n if (!actor) {\n return {\n accepted: false,\n error: `Unknown actor: ${activity.actor}`,\n };\n }\n\n // Verify the key belongs to the actor (strict equality on URL origin + path)\n if (!actor.publicKey?.publicKeyPem) {\n return {\n accepted: false,\n error: `Actor ${activity.actor} has no public key`,\n };\n }\n\n // Verify key ID matches actor (strict equality on base URL)\n // Key ID format: \"https://example.com/actors/alice#main-key\"\n // Actor format: \"https://example.com/actors/alice\"\n try {\n const keyIdUrl = new URL(parsedSig.keyId);\n const actorUrl = new URL(activity.actor);\n\n // Extract base (origin + pathname) - key ID has fragment, actor doesn't\n const keyIdBase = `${keyIdUrl.origin}${keyIdUrl.pathname}`;\n const actorBase = `${actorUrl.origin}${actorUrl.pathname}`;\n\n // Strict equality required - no startsWith which could allow:\n // - actor: https://evil.com/victim\n // - keyId: https://evil.com/victim-fake#main-key (would match with startsWith)\n if (keyIdBase !== actorBase) {\n return {\n accepted: false,\n error: `Key ID ${parsedSig.keyId} does not match actor ${activity.actor}`,\n };\n }\n\n // Also verify the fetched actor's key ID matches the signature's key ID\n if (actor.publicKey.id !== parsedSig.keyId) {\n return {\n accepted: false,\n error: `Actor's key ID ${actor.publicKey.id} does not match signature key ID ${parsedSig.keyId}`,\n };\n }\n } catch {\n return {\n accepted: false,\n error: `Invalid key ID or actor URL`,\n };\n }\n\n // Verify Digest header BEFORE signature verification\n // @security This prevents body tampering even if signature is valid (signatures only cover headers)\n // Order: Digest (body integrity, cheap hash) → Signature (header integrity, crypto)\n const digestHeader = normalizedHeaders.get('digest');\n\n if (digestHeader) {\n // Digest header present - MUST verify\n if (!options.rawBody) {\n return {\n accepted: false,\n error: 'Digest header present but rawBody not provided for verification',\n };\n }\n\n // Parse digest format: \"SHA-256=base64hash\" or \"sha-256=base64hash\"\n const digestMatch = digestHeader.match(/^(SHA-256|sha-256)=(.+)$/i);\n if (!digestMatch) {\n return {\n accepted: false,\n error: 'Invalid Digest header format (expected SHA-256=...)',\n };\n }\n\n // Calculate actual digest\n const expectedDigest = await calculateDigest(options.rawBody);\n const actualDigest = `SHA-256=${digestMatch[2]}`;\n\n // Compare (case-insensitive for algorithm, exact for hash)\n if (expectedDigest !== actualDigest) {\n return {\n accepted: false,\n error: 'Digest mismatch - body may have been tampered with',\n };\n }\n } else if (parsedSig.headers.includes('digest')) {\n // Signature claims to have signed a digest, but no Digest header present\n return {\n accepted: false,\n error: 'Signature includes digest but no Digest header present',\n };\n }\n\n // Verify the signature (covers headers including Digest)\n const method = options.method || 'POST';\n const path = options.path || '/inbox';\n\n const isValid = await verifyHttpSignature(\n parsedSig,\n actor.publicKey.publicKeyPem,\n method,\n path,\n normalizedHeaders,\n { strictHeaders: true }\n );\n\n if (!isValid) {\n return {\n accepted: false,\n error: 'Invalid HTTP signature',\n };\n }\n\n // Check date header for replay protection\n // In strict mode, Date header is required (checked above), so always verify freshness\n // Default maxAge: 300 seconds (5 minutes) matches validateActivitySignature behavior\n const effectiveMaxAge = options.maxAge ?? 300;\n const requestDate = new Date(dateHeader);\n const now = new Date();\n const ageMs = Math.abs(now.getTime() - requestDate.getTime());\n\n if (isNaN(requestDate.getTime())) {\n return {\n accepted: false,\n error: 'Invalid Date header format',\n };\n }\n\n if (ageMs > effectiveMaxAge * 1000) {\n return {\n accepted: false,\n error: `Request too old or in future (${Math.round(ageMs / 1000)}s, max ${effectiveMaxAge}s)`,\n };\n }\n }\n\n // Route to appropriate handler\n const activityType = activity.type as ActivityType;\n\n switch (activityType) {\n case 'Fork': {\n const parsed = parseForkActivity(activity);\n if (!parsed) {\n return {\n accepted: false,\n error: 'Invalid Fork activity: missing or malformed required fields',\n };\n }\n if (options.onFork) {\n await options.onFork(activity as ForkActivity);\n }\n return { accepted: true, activityType: 'Fork' };\n }\n\n case 'Install': {\n const parsed = parseInstallActivity(activity);\n if (!parsed) {\n return {\n accepted: false,\n error: 'Invalid Install activity: missing or malformed required fields',\n };\n }\n if (options.onInstall) {\n await options.onInstall(activity as InstallActivity);\n }\n return { accepted: true, activityType: 'Install' };\n }\n\n case 'Create': {\n if (options.onCreate) {\n await options.onCreate(activity);\n }\n return { accepted: true, activityType: 'Create' };\n }\n\n case 'Update': {\n if (options.onUpdate) {\n await options.onUpdate(activity);\n }\n return { accepted: true, activityType: 'Update' };\n }\n\n case 'Delete': {\n if (options.onDelete) {\n await options.onDelete(activity);\n }\n return { accepted: true, activityType: 'Delete' };\n }\n\n case 'Like': {\n if (options.onLike) {\n await options.onLike(activity);\n }\n return { accepted: true, activityType: 'Like' };\n }\n\n case 'Announce': {\n if (options.onAnnounce) {\n await options.onAnnounce(activity);\n }\n return { accepted: true, activityType: 'Announce' };\n }\n\n case 'Undo': {\n if (options.onUndo) {\n await options.onUndo(activity);\n }\n return { accepted: true, activityType: 'Undo' };\n }\n\n case 'Flag': {\n const parsed = parseFlagActivity(activity);\n if (options.onFlag && parsed) {\n await options.onFlag(activity as unknown as FlagActivity, parsed);\n }\n return { accepted: true, activityType: 'Flag' };\n }\n\n case 'Block': {\n const parsed = parseBlockActivity(activity);\n if (options.onBlock && parsed) {\n await options.onBlock(activity as unknown as BlockActivity, parsed);\n }\n return { accepted: true, activityType: 'Block' };\n }\n\n default: {\n // Unknown activity type - accept but ignore\n return {\n accepted: true,\n activityType: activityType,\n };\n }\n }\n } catch (err) {\n return {\n accepted: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\n/**\n * Validate that a Fork activity has all required fields\n */\nexport function validateForkActivity(activity: unknown): {\n valid: boolean;\n error?: string;\n} {\n const parsed = parseForkActivity(activity);\n if (!parsed) {\n return {\n valid: false,\n error: 'Invalid fork activity: missing required fields',\n };\n }\n\n // Verify the forked card has content\n try {\n JSON.parse(parsed.forkedCard.content);\n } catch {\n return {\n valid: false,\n error: 'Invalid fork activity: forked card content is not valid JSON',\n };\n }\n\n return { valid: true };\n}\n\n/**\n * Validate that an Install activity has all required fields\n */\nexport function validateInstallActivity(activity: unknown): {\n valid: boolean;\n error?: string;\n} {\n const parsed = parseInstallActivity(activity);\n if (!parsed) {\n return {\n valid: false,\n error: 'Invalid install activity: missing required fields',\n };\n }\n\n // Verify the card ID is a valid URI\n try {\n new URL(parsed.cardId);\n } catch {\n return {\n valid: false,\n error: 'Invalid install activity: cardId is not a valid URI',\n };\n }\n\n return { valid: true };\n}\n","/**\n * In-Memory Moderation Store\n *\n * Memory implementation for testing and single-session use.\n * Follows the same pattern as MemorySyncStateStore.\n */\n\nimport { generateUUID } from '@character-foundry/core';\nimport type {\n ModerationStore,\n ModerationReport,\n ModerationAction,\n InstanceBlock,\n ContentPolicy,\n RateLimitBucket,\n ReportStatus,\n ReportCategory,\n ActionType,\n} from './types.js';\n\n/**\n * In-memory implementation of ModerationStore\n *\n * Suitable for testing, development, and single-session use cases.\n * Data is lost when the process ends.\n */\nexport class MemoryModerationStore implements ModerationStore {\n private reports: Map<string, ModerationReport> = new Map();\n private actions: Map<string, ModerationAction> = new Map();\n private blocks: Map<string, InstanceBlock> = new Map();\n private policies: Map<string, ContentPolicy> = new Map();\n private rateLimits: Map<string, RateLimitBucket> = new Map();\n\n // ============ Report Operations ============\n\n async createReport(report: Omit<ModerationReport, 'id'>): Promise<ModerationReport> {\n const id = generateUUID();\n const full: ModerationReport = { ...report, id };\n this.reports.set(id, full);\n return full;\n }\n\n async getReport(id: string): Promise<ModerationReport | null> {\n return this.reports.get(id) || null;\n }\n\n async updateReport(id: string, updates: Partial<ModerationReport>): Promise<void> {\n const existing = this.reports.get(id);\n if (existing) {\n this.reports.set(id, {\n ...existing,\n ...updates,\n updatedAt: new Date().toISOString(),\n });\n }\n }\n\n async listReports(filters?: {\n status?: ReportStatus;\n category?: ReportCategory;\n targetId?: string;\n reporterActorId?: string;\n since?: string;\n limit?: number;\n offset?: number;\n }): Promise<ModerationReport[]> {\n let results = Array.from(this.reports.values());\n\n if (filters?.status) {\n results = results.filter((r) => r.status === filters.status);\n }\n if (filters?.category) {\n results = results.filter((r) => r.category === filters.category);\n }\n if (filters?.targetId) {\n results = results.filter((r) => r.targetIds.includes(filters.targetId!));\n }\n if (filters?.reporterActorId) {\n results = results.filter((r) => r.reporterActorId === filters.reporterActorId);\n }\n if (filters?.since) {\n results = results.filter((r) => r.createdAt >= filters.since!);\n }\n\n // Sort by creation date descending (newest first)\n results.sort((a, b) => b.createdAt.localeCompare(a.createdAt));\n\n const offset = filters?.offset || 0;\n const limit = filters?.limit || 100;\n return results.slice(offset, offset + limit);\n }\n\n async countReports(filters?: { status?: ReportStatus }): Promise<number> {\n let count = 0;\n for (const report of this.reports.values()) {\n if (!filters?.status || report.status === filters.status) {\n count++;\n }\n }\n return count;\n }\n\n // ============ Action Operations ============\n\n async createAction(action: Omit<ModerationAction, 'id'>): Promise<ModerationAction> {\n const id = generateUUID();\n const full: ModerationAction = { ...action, id };\n this.actions.set(id, full);\n return full;\n }\n\n async getAction(id: string): Promise<ModerationAction | null> {\n return this.actions.get(id) || null;\n }\n\n async listActions(filters?: {\n targetId?: string;\n moderatorActorId?: string;\n actionType?: ActionType;\n active?: boolean;\n since?: string;\n limit?: number;\n }): Promise<ModerationAction[]> {\n let results = Array.from(this.actions.values());\n\n if (filters?.targetId) {\n results = results.filter((a) => a.targetId === filters.targetId);\n }\n if (filters?.moderatorActorId) {\n results = results.filter((a) => a.moderatorActorId === filters.moderatorActorId);\n }\n if (filters?.actionType) {\n results = results.filter((a) => a.actionType === filters.actionType);\n }\n if (filters?.active !== undefined) {\n results = results.filter((a) => a.active === filters.active);\n }\n if (filters?.since) {\n results = results.filter((a) => a.timestamp >= filters.since!);\n }\n\n // Sort by timestamp descending (newest first)\n results.sort((a, b) => b.timestamp.localeCompare(a.timestamp));\n return results.slice(0, filters?.limit || 100);\n }\n\n async deactivateAction(id: string): Promise<void> {\n const action = this.actions.get(id);\n if (action) {\n this.actions.set(id, { ...action, active: false });\n }\n }\n\n // ============ Instance Block Operations ============\n\n async createBlock(block: Omit<InstanceBlock, 'id'>): Promise<InstanceBlock> {\n const id = generateUUID();\n const full: InstanceBlock = { ...block, id };\n this.blocks.set(id, full);\n return full;\n }\n\n async getBlock(id: string): Promise<InstanceBlock | null> {\n return this.blocks.get(id) || null;\n }\n\n async getBlockByDomain(domain: string): Promise<InstanceBlock | null> {\n const normalized = domain.toLowerCase();\n for (const block of this.blocks.values()) {\n if (block.blockedDomain.toLowerCase() === normalized && block.active) {\n return block;\n }\n }\n return null;\n }\n\n async listBlocks(filters?: { active?: boolean }): Promise<InstanceBlock[]> {\n let results = Array.from(this.blocks.values());\n if (filters?.active !== undefined) {\n results = results.filter((b) => b.active === filters.active);\n }\n // Sort by creation date descending\n results.sort((a, b) => b.createdAt.localeCompare(a.createdAt));\n return results;\n }\n\n async updateBlock(id: string, updates: Partial<InstanceBlock>): Promise<void> {\n const existing = this.blocks.get(id);\n if (existing) {\n this.blocks.set(id, { ...existing, ...updates });\n }\n }\n\n async isInstanceBlocked(domain: string): Promise<boolean> {\n const block = await this.getBlockByDomain(domain);\n return block !== null;\n }\n\n // ============ Content Policy Operations ============\n\n async createPolicy(policy: Omit<ContentPolicy, 'id'>): Promise<ContentPolicy> {\n const id = generateUUID();\n const full: ContentPolicy = { ...policy, id };\n this.policies.set(id, full);\n return full;\n }\n\n async getPolicy(id: string): Promise<ContentPolicy | null> {\n return this.policies.get(id) || null;\n }\n\n async listPolicies(filters?: { enabled?: boolean }): Promise<ContentPolicy[]> {\n let results = Array.from(this.policies.values());\n if (filters?.enabled !== undefined) {\n results = results.filter((p) => p.enabled === filters.enabled);\n }\n return results;\n }\n\n async updatePolicy(id: string, updates: Partial<ContentPolicy>): Promise<void> {\n const existing = this.policies.get(id);\n if (existing) {\n this.policies.set(id, {\n ...existing,\n ...updates,\n updatedAt: new Date().toISOString(),\n });\n }\n }\n\n async deletePolicy(id: string): Promise<void> {\n this.policies.delete(id);\n }\n\n // ============ Rate Limit Operations ============\n\n async getRateLimitBucket(actorId: string): Promise<RateLimitBucket | null> {\n return this.rateLimits.get(actorId) || null;\n }\n\n async updateRateLimitBucket(bucket: RateLimitBucket): Promise<void> {\n this.rateLimits.set(bucket.actorId, bucket);\n }\n\n // ============ Audit ============\n\n async getAuditLog(filters?: {\n targetId?: string;\n actorId?: string;\n since?: string;\n until?: string;\n limit?: number;\n }): Promise<ModerationAction[]> {\n let results = await this.listActions({\n targetId: filters?.targetId,\n moderatorActorId: filters?.actorId,\n since: filters?.since,\n limit: filters?.limit,\n });\n\n if (filters?.until) {\n results = results.filter((a) => a.timestamp <= filters.until!);\n }\n\n return results;\n }\n\n // ============ Utility Methods ============\n\n /**\n * Clear all data (useful for testing)\n */\n clear(): void {\n this.reports.clear();\n this.actions.clear();\n this.blocks.clear();\n this.policies.clear();\n this.rateLimits.clear();\n }\n\n /**\n * Get counts for dashboard/stats\n */\n async getStats(): Promise<{\n reports: { total: number; pending: number };\n actions: { total: number; active: number };\n blocks: { total: number; active: number };\n policies: { total: number; enabled: number };\n }> {\n const pendingReports = await this.countReports({ status: 'pending' });\n const activeActions = (await this.listActions({ active: true })).length;\n const activeBlocks = (await this.listBlocks({ active: true })).length;\n const enabledPolicies = (await this.listPolicies({ enabled: true })).length;\n\n return {\n reports: {\n total: this.reports.size,\n pending: pendingReports,\n },\n actions: {\n total: this.actions.size,\n active: activeActions,\n },\n blocks: {\n total: this.blocks.size,\n active: activeBlocks,\n },\n policies: {\n total: this.policies.size,\n enabled: enabledPolicies,\n },\n };\n }\n}\n","/**\n * D1 Moderation Store\n *\n * Cloudflare D1-compatible implementation of ModerationStore for production\n * moderation support on Cloudflare Workers.\n */\n\nimport type { D1Database } from '../d1-store.js';\nimport type {\n ModerationStore,\n ModerationReport,\n ModerationAction,\n InstanceBlock,\n ContentPolicy,\n RateLimitBucket,\n ReportStatus,\n ReportCategory,\n ActionType,\n InstanceBlockLevel,\n PolicyAction,\n} from './types.js';\n\n/**\n * Database row for ModerationReport\n */\ninterface ReportRow {\n id: string;\n reporter_actor_id: string;\n reporter_instance: string;\n target_ids: string; // JSON array\n category: string;\n description: string;\n status: string;\n activity_id: string;\n created_at: string;\n updated_at: string;\n receiving_instance: string;\n federated_to_target: number; // SQLite boolean\n metadata: string | null;\n}\n\n/**\n * Database row for ModerationAction\n */\ninterface ActionRow {\n id: string;\n report_id: string | null;\n moderator_actor_id: string;\n target_id: string;\n action_type: string;\n reason: string;\n timestamp: string;\n expires_at: string | null;\n active: number; // SQLite boolean\n reverses_action_id: string | null;\n approved_by: string | null; // JSON array\n metadata: string | null;\n}\n\n/**\n * Database row for InstanceBlock\n */\ninterface BlockRow {\n id: string;\n blocked_domain: string;\n level: string;\n reason: string;\n created_by: string;\n created_at: string;\n active: number;\n public_comment: string | null;\n federate: number;\n}\n\n/**\n * Database row for ContentPolicy\n */\ninterface PolicyRow {\n id: string;\n name: string;\n description: string;\n rules: string; // JSON array\n enabled: number;\n default_action: string;\n updated_at: string;\n}\n\n/**\n * Database row for RateLimitBucket\n */\ninterface RateLimitRow {\n actor_id: string;\n tokens: number;\n max_tokens: number;\n last_refill: string;\n refill_rate: number;\n}\n\n/**\n * Validate table name prefix to prevent SQL injection\n */\nfunction validateTablePrefix(prefix: string): void {\n const validPattern = /^[a-zA-Z][a-zA-Z0-9_]*$/;\n if (!validPattern.test(prefix)) {\n throw new Error(\n `Invalid table prefix \"${prefix}\": must start with a letter and contain only alphanumeric characters and underscores`\n );\n }\n if (prefix.length > 32) {\n throw new Error(`Invalid table prefix \"${prefix}\": must be 32 characters or less`);\n }\n}\n\n/**\n * D1-compatible implementation of ModerationStore\n *\n * @example\n * ```typescript\n * const store = new D1ModerationStore(env.DB);\n * await store.init();\n *\n * // Create a report\n * const report = await store.createReport({\n * reporterActorId: 'https://example.com/users/alice',\n * reporterInstance: 'example.com',\n * targetIds: ['https://bad.com/cards/spam'],\n * category: 'spam',\n * description: 'This card is spam',\n * status: 'pending',\n * activityId: 'https://example.com/activities/123',\n * createdAt: new Date().toISOString(),\n * updatedAt: new Date().toISOString(),\n * receivingInstance: 'bad.com',\n * federatedToTarget: true,\n * });\n * ```\n */\nexport class D1ModerationStore implements ModerationStore {\n private db: D1Database;\n private prefix: string;\n\n constructor(db: D1Database, tablePrefix = 'moderation') {\n validateTablePrefix(tablePrefix);\n this.db = db;\n this.prefix = tablePrefix;\n }\n\n /**\n * Initialize all moderation tables\n */\n async init(): Promise<void> {\n // Reports table\n await this.db.exec(`\n CREATE TABLE IF NOT EXISTS ${this.prefix}_reports (\n id TEXT PRIMARY KEY,\n reporter_actor_id TEXT NOT NULL,\n reporter_instance TEXT NOT NULL,\n target_ids TEXT NOT NULL,\n category TEXT NOT NULL,\n description TEXT NOT NULL,\n status TEXT NOT NULL,\n activity_id TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n receiving_instance TEXT NOT NULL,\n federated_to_target INTEGER NOT NULL DEFAULT 0,\n metadata TEXT\n )\n `);\n\n await this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_${this.prefix}_reports_status\n ON ${this.prefix}_reports(status)\n `);\n\n await this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_${this.prefix}_reports_reporter\n ON ${this.prefix}_reports(reporter_actor_id)\n `);\n\n // Actions table\n await this.db.exec(`\n CREATE TABLE IF NOT EXISTS ${this.prefix}_actions (\n id TEXT PRIMARY KEY,\n report_id TEXT,\n moderator_actor_id TEXT NOT NULL,\n target_id TEXT NOT NULL,\n action_type TEXT NOT NULL,\n reason TEXT NOT NULL,\n timestamp TEXT NOT NULL,\n expires_at TEXT,\n active INTEGER NOT NULL DEFAULT 1,\n reverses_action_id TEXT,\n approved_by TEXT,\n metadata TEXT\n )\n `);\n\n await this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_${this.prefix}_actions_target\n ON ${this.prefix}_actions(target_id)\n `);\n\n await this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_${this.prefix}_actions_active\n ON ${this.prefix}_actions(active)\n `);\n\n // Instance blocks table\n await this.db.exec(`\n CREATE TABLE IF NOT EXISTS ${this.prefix}_blocks (\n id TEXT PRIMARY KEY,\n blocked_domain TEXT NOT NULL UNIQUE,\n level TEXT NOT NULL CHECK (level IN ('suspend', 'silence', 'reject_media')),\n reason TEXT NOT NULL,\n created_by TEXT NOT NULL,\n created_at TEXT NOT NULL,\n active INTEGER NOT NULL DEFAULT 1,\n public_comment TEXT,\n federate INTEGER NOT NULL DEFAULT 0\n )\n `);\n\n await this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_${this.prefix}_blocks_domain\n ON ${this.prefix}_blocks(blocked_domain)\n `);\n\n // Content policies table\n await this.db.exec(`\n CREATE TABLE IF NOT EXISTS ${this.prefix}_policies (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE,\n description TEXT NOT NULL,\n rules TEXT NOT NULL,\n enabled INTEGER NOT NULL DEFAULT 1,\n default_action TEXT NOT NULL,\n updated_at TEXT NOT NULL\n )\n `);\n\n // Rate limits table\n await this.db.exec(`\n CREATE TABLE IF NOT EXISTS ${this.prefix}_rate_limits (\n actor_id TEXT PRIMARY KEY,\n tokens REAL NOT NULL,\n max_tokens INTEGER NOT NULL,\n last_refill TEXT NOT NULL,\n refill_rate REAL NOT NULL\n )\n `);\n }\n\n // ============ Report Operations ============\n\n async createReport(report: Omit<ModerationReport, 'id'>): Promise<ModerationReport> {\n const id = crypto.randomUUID();\n const full: ModerationReport = { ...report, id };\n\n await this.db\n .prepare(\n `INSERT INTO ${this.prefix}_reports\n (id, reporter_actor_id, reporter_instance, target_ids, category, description, status,\n activity_id, created_at, updated_at, receiving_instance, federated_to_target, metadata)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`\n )\n .bind(\n id,\n report.reporterActorId,\n report.reporterInstance,\n JSON.stringify(report.targetIds),\n report.category,\n report.description,\n report.status,\n report.activityId,\n report.createdAt,\n report.updatedAt,\n report.receivingInstance,\n report.federatedToTarget ? 1 : 0,\n report.metadata ? JSON.stringify(report.metadata) : null\n )\n .run();\n\n return full;\n }\n\n async getReport(id: string): Promise<ModerationReport | null> {\n const row = await this.db\n .prepare(`SELECT * FROM ${this.prefix}_reports WHERE id = ?`)\n .bind(id)\n .first<ReportRow>();\n\n return row ? this.rowToReport(row) : null;\n }\n\n async updateReport(id: string, updates: Partial<ModerationReport>): Promise<void> {\n const existing = await this.getReport(id);\n if (!existing) return;\n\n const merged = { ...existing, ...updates, updatedAt: new Date().toISOString() };\n\n await this.db\n .prepare(\n `UPDATE ${this.prefix}_reports SET\n reporter_actor_id = ?, reporter_instance = ?, target_ids = ?, category = ?,\n description = ?, status = ?, activity_id = ?, updated_at = ?,\n receiving_instance = ?, federated_to_target = ?, metadata = ?\n WHERE id = ?`\n )\n .bind(\n merged.reporterActorId,\n merged.reporterInstance,\n JSON.stringify(merged.targetIds),\n merged.category,\n merged.description,\n merged.status,\n merged.activityId,\n merged.updatedAt,\n merged.receivingInstance,\n merged.federatedToTarget ? 1 : 0,\n merged.metadata ? JSON.stringify(merged.metadata) : null,\n id\n )\n .run();\n }\n\n async listReports(filters?: {\n status?: ReportStatus;\n category?: ReportCategory;\n targetId?: string;\n reporterActorId?: string;\n since?: string;\n limit?: number;\n offset?: number;\n }): Promise<ModerationReport[]> {\n const conditions: string[] = ['1=1'];\n const bindings: unknown[] = [];\n\n if (filters?.status) {\n conditions.push('status = ?');\n bindings.push(filters.status);\n }\n if (filters?.category) {\n conditions.push('category = ?');\n bindings.push(filters.category);\n }\n if (filters?.targetId) {\n conditions.push('target_ids LIKE ?');\n bindings.push(`%${filters.targetId}%`);\n }\n if (filters?.reporterActorId) {\n conditions.push('reporter_actor_id = ?');\n bindings.push(filters.reporterActorId);\n }\n if (filters?.since) {\n conditions.push('created_at >= ?');\n bindings.push(filters.since);\n }\n\n const limit = filters?.limit ?? 100;\n const offset = filters?.offset ?? 0;\n\n let stmt = this.db.prepare(\n `SELECT * FROM ${this.prefix}_reports\n WHERE ${conditions.join(' AND ')}\n ORDER BY created_at DESC\n LIMIT ? OFFSET ?`\n );\n\n for (const binding of bindings) {\n stmt = stmt.bind(binding);\n }\n stmt = stmt.bind(limit, offset);\n\n const result = await stmt.all<ReportRow>();\n return result.results.map((row) => this.rowToReport(row));\n }\n\n async countReports(filters?: { status?: ReportStatus }): Promise<number> {\n let query = `SELECT COUNT(*) as count FROM ${this.prefix}_reports`;\n const bindings: unknown[] = [];\n\n if (filters?.status) {\n query += ' WHERE status = ?';\n bindings.push(filters.status);\n }\n\n let stmt = this.db.prepare(query);\n for (const binding of bindings) {\n stmt = stmt.bind(binding);\n }\n\n const result = await stmt.first<{ count: number }>();\n return result?.count ?? 0;\n }\n\n // ============ Action Operations ============\n\n async createAction(action: Omit<ModerationAction, 'id'>): Promise<ModerationAction> {\n const id = crypto.randomUUID();\n const full: ModerationAction = { ...action, id };\n\n await this.db\n .prepare(\n `INSERT INTO ${this.prefix}_actions\n (id, report_id, moderator_actor_id, target_id, action_type, reason, timestamp,\n expires_at, active, reverses_action_id, approved_by, metadata)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`\n )\n .bind(\n id,\n action.reportId ?? null,\n action.moderatorActorId,\n action.targetId,\n action.actionType,\n action.reason,\n action.timestamp,\n action.expiresAt ?? null,\n action.active ? 1 : 0,\n action.reversesActionId ?? null,\n action.approvedBy ? JSON.stringify(action.approvedBy) : null,\n action.metadata ? JSON.stringify(action.metadata) : null\n )\n .run();\n\n return full;\n }\n\n async getAction(id: string): Promise<ModerationAction | null> {\n const row = await this.db\n .prepare(`SELECT * FROM ${this.prefix}_actions WHERE id = ?`)\n .bind(id)\n .first<ActionRow>();\n\n return row ? this.rowToAction(row) : null;\n }\n\n async listActions(filters?: {\n targetId?: string;\n moderatorActorId?: string;\n actionType?: ActionType;\n active?: boolean;\n since?: string;\n limit?: number;\n }): Promise<ModerationAction[]> {\n const conditions: string[] = ['1=1'];\n const bindings: unknown[] = [];\n\n if (filters?.targetId) {\n conditions.push('target_id = ?');\n bindings.push(filters.targetId);\n }\n if (filters?.moderatorActorId) {\n conditions.push('moderator_actor_id = ?');\n bindings.push(filters.moderatorActorId);\n }\n if (filters?.actionType) {\n conditions.push('action_type = ?');\n bindings.push(filters.actionType);\n }\n if (filters?.active !== undefined) {\n conditions.push('active = ?');\n bindings.push(filters.active ? 1 : 0);\n }\n if (filters?.since) {\n conditions.push('timestamp >= ?');\n bindings.push(filters.since);\n }\n\n const limit = filters?.limit ?? 100;\n\n let stmt = this.db.prepare(\n `SELECT * FROM ${this.prefix}_actions\n WHERE ${conditions.join(' AND ')}\n ORDER BY timestamp DESC\n LIMIT ?`\n );\n\n for (const binding of bindings) {\n stmt = stmt.bind(binding);\n }\n stmt = stmt.bind(limit);\n\n const result = await stmt.all<ActionRow>();\n return result.results.map((row) => this.rowToAction(row));\n }\n\n async deactivateAction(id: string): Promise<void> {\n await this.db\n .prepare(`UPDATE ${this.prefix}_actions SET active = 0 WHERE id = ?`)\n .bind(id)\n .run();\n }\n\n // ============ Instance Block Operations ============\n\n async createBlock(block: Omit<InstanceBlock, 'id'>): Promise<InstanceBlock> {\n const id = crypto.randomUUID();\n const full: InstanceBlock = { ...block, id };\n\n await this.db\n .prepare(\n `INSERT INTO ${this.prefix}_blocks\n (id, blocked_domain, level, reason, created_by, created_at, active, public_comment, federate)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`\n )\n .bind(\n id,\n block.blockedDomain.toLowerCase(),\n block.level,\n block.reason,\n block.createdBy,\n block.createdAt,\n block.active ? 1 : 0,\n block.publicComment ?? null,\n block.federate ? 1 : 0\n )\n .run();\n\n return full;\n }\n\n async getBlock(id: string): Promise<InstanceBlock | null> {\n const row = await this.db\n .prepare(`SELECT * FROM ${this.prefix}_blocks WHERE id = ?`)\n .bind(id)\n .first<BlockRow>();\n\n return row ? this.rowToBlock(row) : null;\n }\n\n async getBlockByDomain(domain: string): Promise<InstanceBlock | null> {\n const row = await this.db\n .prepare(`SELECT * FROM ${this.prefix}_blocks WHERE blocked_domain = ? AND active = 1`)\n .bind(domain.toLowerCase())\n .first<BlockRow>();\n\n return row ? this.rowToBlock(row) : null;\n }\n\n async listBlocks(filters?: { active?: boolean }): Promise<InstanceBlock[]> {\n let query = `SELECT * FROM ${this.prefix}_blocks`;\n const bindings: unknown[] = [];\n\n if (filters?.active !== undefined) {\n query += ' WHERE active = ?';\n bindings.push(filters.active ? 1 : 0);\n }\n\n query += ' ORDER BY created_at DESC';\n\n let stmt = this.db.prepare(query);\n for (const binding of bindings) {\n stmt = stmt.bind(binding);\n }\n\n const result = await stmt.all<BlockRow>();\n return result.results.map((row) => this.rowToBlock(row));\n }\n\n async updateBlock(id: string, updates: Partial<InstanceBlock>): Promise<void> {\n const existing = await this.getBlock(id);\n if (!existing) return;\n\n const merged = { ...existing, ...updates };\n\n await this.db\n .prepare(\n `UPDATE ${this.prefix}_blocks SET\n blocked_domain = ?, level = ?, reason = ?, active = ?, public_comment = ?, federate = ?\n WHERE id = ?`\n )\n .bind(\n merged.blockedDomain.toLowerCase(),\n merged.level,\n merged.reason,\n merged.active ? 1 : 0,\n merged.publicComment ?? null,\n merged.federate ? 1 : 0,\n id\n )\n .run();\n }\n\n async isInstanceBlocked(domain: string): Promise<boolean> {\n const block = await this.getBlockByDomain(domain);\n return block !== null;\n }\n\n // ============ Content Policy Operations ============\n\n async createPolicy(policy: Omit<ContentPolicy, 'id'>): Promise<ContentPolicy> {\n const id = crypto.randomUUID();\n const full: ContentPolicy = { ...policy, id };\n\n await this.db\n .prepare(\n `INSERT INTO ${this.prefix}_policies\n (id, name, description, rules, enabled, default_action, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)`\n )\n .bind(\n id,\n policy.name,\n policy.description,\n JSON.stringify(policy.rules),\n policy.enabled ? 1 : 0,\n policy.defaultAction,\n policy.updatedAt\n )\n .run();\n\n return full;\n }\n\n async getPolicy(id: string): Promise<ContentPolicy | null> {\n const row = await this.db\n .prepare(`SELECT * FROM ${this.prefix}_policies WHERE id = ?`)\n .bind(id)\n .first<PolicyRow>();\n\n return row ? this.rowToPolicy(row) : null;\n }\n\n async listPolicies(filters?: { enabled?: boolean }): Promise<ContentPolicy[]> {\n let query = `SELECT * FROM ${this.prefix}_policies`;\n const bindings: unknown[] = [];\n\n if (filters?.enabled !== undefined) {\n query += ' WHERE enabled = ?';\n bindings.push(filters.enabled ? 1 : 0);\n }\n\n query += ' ORDER BY name ASC';\n\n let stmt = this.db.prepare(query);\n for (const binding of bindings) {\n stmt = stmt.bind(binding);\n }\n\n const result = await stmt.all<PolicyRow>();\n return result.results.map((row) => this.rowToPolicy(row));\n }\n\n async updatePolicy(id: string, updates: Partial<ContentPolicy>): Promise<void> {\n const existing = await this.getPolicy(id);\n if (!existing) return;\n\n const merged = { ...existing, ...updates, updatedAt: new Date().toISOString() };\n\n await this.db\n .prepare(\n `UPDATE ${this.prefix}_policies SET\n name = ?, description = ?, rules = ?, enabled = ?, default_action = ?, updated_at = ?\n WHERE id = ?`\n )\n .bind(\n merged.name,\n merged.description,\n JSON.stringify(merged.rules),\n merged.enabled ? 1 : 0,\n merged.defaultAction,\n merged.updatedAt,\n id\n )\n .run();\n }\n\n async deletePolicy(id: string): Promise<void> {\n await this.db.prepare(`DELETE FROM ${this.prefix}_policies WHERE id = ?`).bind(id).run();\n }\n\n // ============ Rate Limit Operations ============\n\n async getRateLimitBucket(actorId: string): Promise<RateLimitBucket | null> {\n const row = await this.db\n .prepare(`SELECT * FROM ${this.prefix}_rate_limits WHERE actor_id = ?`)\n .bind(actorId)\n .first<RateLimitRow>();\n\n return row ? this.rowToRateLimit(row) : null;\n }\n\n async updateRateLimitBucket(bucket: RateLimitBucket): Promise<void> {\n await this.db\n .prepare(\n `INSERT INTO ${this.prefix}_rate_limits (actor_id, tokens, max_tokens, last_refill, refill_rate)\n VALUES (?, ?, ?, ?, ?)\n ON CONFLICT(actor_id) DO UPDATE SET\n tokens = excluded.tokens,\n max_tokens = excluded.max_tokens,\n last_refill = excluded.last_refill,\n refill_rate = excluded.refill_rate`\n )\n .bind(bucket.actorId, bucket.tokens, bucket.maxTokens, bucket.lastRefill, bucket.refillRate)\n .run();\n }\n\n // ============ Audit ============\n\n async getAuditLog(filters?: {\n targetId?: string;\n actorId?: string;\n since?: string;\n until?: string;\n limit?: number;\n }): Promise<ModerationAction[]> {\n const conditions: string[] = ['1=1'];\n const bindings: unknown[] = [];\n\n if (filters?.targetId) {\n conditions.push('target_id = ?');\n bindings.push(filters.targetId);\n }\n if (filters?.actorId) {\n conditions.push('moderator_actor_id = ?');\n bindings.push(filters.actorId);\n }\n if (filters?.since) {\n conditions.push('timestamp >= ?');\n bindings.push(filters.since);\n }\n if (filters?.until) {\n conditions.push('timestamp <= ?');\n bindings.push(filters.until);\n }\n\n const limit = filters?.limit ?? 100;\n\n let stmt = this.db.prepare(\n `SELECT * FROM ${this.prefix}_actions\n WHERE ${conditions.join(' AND ')}\n ORDER BY timestamp DESC\n LIMIT ?`\n );\n\n for (const binding of bindings) {\n stmt = stmt.bind(binding);\n }\n stmt = stmt.bind(limit);\n\n const result = await stmt.all<ActionRow>();\n return result.results.map((row) => this.rowToAction(row));\n }\n\n // ============ Utility Methods ============\n\n /**\n * Clear all moderation data (for testing)\n */\n async clear(): Promise<void> {\n await this.db.prepare(`DELETE FROM ${this.prefix}_reports`).run();\n await this.db.prepare(`DELETE FROM ${this.prefix}_actions`).run();\n await this.db.prepare(`DELETE FROM ${this.prefix}_blocks`).run();\n await this.db.prepare(`DELETE FROM ${this.prefix}_policies`).run();\n await this.db.prepare(`DELETE FROM ${this.prefix}_rate_limits`).run();\n }\n\n /**\n * Get stats for dashboard\n */\n async getStats(): Promise<{\n reports: { total: number; pending: number };\n actions: { total: number; active: number };\n blocks: { total: number; active: number };\n policies: { total: number; enabled: number };\n }> {\n const [reportsTotal, reportsPending, actionsTotal, actionsActive, blocksTotal, blocksActive, policiesTotal, policiesEnabled] = await Promise.all([\n this.db.prepare(`SELECT COUNT(*) as count FROM ${this.prefix}_reports`).first<{ count: number }>(),\n this.db.prepare(`SELECT COUNT(*) as count FROM ${this.prefix}_reports WHERE status = 'pending'`).first<{ count: number }>(),\n this.db.prepare(`SELECT COUNT(*) as count FROM ${this.prefix}_actions`).first<{ count: number }>(),\n this.db.prepare(`SELECT COUNT(*) as count FROM ${this.prefix}_actions WHERE active = 1`).first<{ count: number }>(),\n this.db.prepare(`SELECT COUNT(*) as count FROM ${this.prefix}_blocks`).first<{ count: number }>(),\n this.db.prepare(`SELECT COUNT(*) as count FROM ${this.prefix}_blocks WHERE active = 1`).first<{ count: number }>(),\n this.db.prepare(`SELECT COUNT(*) as count FROM ${this.prefix}_policies`).first<{ count: number }>(),\n this.db.prepare(`SELECT COUNT(*) as count FROM ${this.prefix}_policies WHERE enabled = 1`).first<{ count: number }>(),\n ]);\n\n return {\n reports: { total: reportsTotal?.count ?? 0, pending: reportsPending?.count ?? 0 },\n actions: { total: actionsTotal?.count ?? 0, active: actionsActive?.count ?? 0 },\n blocks: { total: blocksTotal?.count ?? 0, active: blocksActive?.count ?? 0 },\n policies: { total: policiesTotal?.count ?? 0, enabled: policiesEnabled?.count ?? 0 },\n };\n }\n\n // ============ Row Converters ============\n\n private rowToReport(row: ReportRow): ModerationReport {\n return {\n id: row.id,\n reporterActorId: row.reporter_actor_id,\n reporterInstance: row.reporter_instance,\n targetIds: JSON.parse(row.target_ids) as string[],\n category: row.category as ReportCategory,\n description: row.description,\n status: row.status as ReportStatus,\n activityId: row.activity_id,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n receivingInstance: row.receiving_instance,\n federatedToTarget: row.federated_to_target === 1,\n metadata: row.metadata ? (JSON.parse(row.metadata) as Record<string, unknown>) : undefined,\n };\n }\n\n private rowToAction(row: ActionRow): ModerationAction {\n return {\n id: row.id,\n reportId: row.report_id ?? undefined,\n moderatorActorId: row.moderator_actor_id,\n targetId: row.target_id,\n actionType: row.action_type as ActionType,\n reason: row.reason,\n timestamp: row.timestamp,\n expiresAt: row.expires_at ?? undefined,\n active: row.active === 1,\n reversesActionId: row.reverses_action_id ?? undefined,\n approvedBy: row.approved_by ? (JSON.parse(row.approved_by) as string[]) : undefined,\n metadata: row.metadata ? (JSON.parse(row.metadata) as Record<string, unknown>) : undefined,\n };\n }\n\n private rowToBlock(row: BlockRow): InstanceBlock {\n return {\n id: row.id,\n blockedDomain: row.blocked_domain,\n level: row.level as InstanceBlockLevel,\n reason: row.reason,\n createdBy: row.created_by,\n createdAt: row.created_at,\n active: row.active === 1,\n publicComment: row.public_comment ?? undefined,\n federate: row.federate === 1,\n };\n }\n\n private rowToPolicy(row: PolicyRow): ContentPolicy {\n return {\n id: row.id,\n name: row.name,\n description: row.description,\n rules: JSON.parse(row.rules) as ContentPolicy['rules'],\n enabled: row.enabled === 1,\n defaultAction: row.default_action as PolicyAction,\n updatedAt: row.updated_at,\n };\n }\n\n private rowToRateLimit(row: RateLimitRow): RateLimitBucket {\n return {\n actorId: row.actor_id,\n tokens: row.tokens,\n maxTokens: row.max_tokens,\n lastRefill: row.last_refill,\n refillRate: row.refill_rate,\n };\n }\n}\n","/**\n * Content Policy Engine\n *\n * Evaluates incoming cards against content policies.\n * Provides tools for community-defined moderation rules.\n */\n\nimport type { CCv3Data } from '@character-foundry/schemas';\nimport type {\n ContentPolicy,\n ContentPolicyRule,\n PolicyAction,\n PolicyEvaluationResult,\n ModerationStore,\n} from './types.js';\nimport { getLogger } from '../logger.js';\n\n/**\n * Default fields to check for keyword/regex rules\n */\nconst DEFAULT_TARGET_FIELDS = ['name', 'description', 'personality', 'scenario', 'first_mes'];\n\n/**\n * Maximum input length for regex matching (prevents ReDoS on large inputs)\n */\nconst MAX_REGEX_INPUT_LENGTH = 100_000;\n\n/**\n * Maximum regex pattern length\n */\nconst MAX_REGEX_PATTERN_LENGTH = 1000;\n\n/**\n * Common ReDoS-prone patterns to detect and warn about\n * These patterns can cause catastrophic backtracking\n */\nconst REDOS_WARNING_PATTERNS = [\n /\\(\\.\\*\\)\\+/, // (.*)+\n /\\(\\.\\+\\)\\+/, // (.+)+\n /\\([^)]*\\+[^)]*\\)\\+/, // (a+)+ style nested quantifiers\n /\\([^)]*\\*[^)]*\\)\\*/, // (a*)* style nested quantifiers\n /\\(\\[.*?\\]\\+\\)\\+/, // ([a-z]+)+ character class with nested quantifiers\n];\n\n/**\n * Check if a regex pattern might be vulnerable to ReDoS\n * Returns a warning message if the pattern looks dangerous, null if safe\n *\n * @security This is a heuristic check, not a guarantee.\n * Admin-only patterns should still be audited manually.\n */\nexport function checkRegexSafety(pattern: string): string | null {\n if (pattern.length > MAX_REGEX_PATTERN_LENGTH) {\n return `Pattern too long (${pattern.length} chars, max ${MAX_REGEX_PATTERN_LENGTH})`;\n }\n\n for (const dangerousPattern of REDOS_WARNING_PATTERNS) {\n if (dangerousPattern.test(pattern)) {\n return `Pattern may be vulnerable to ReDoS (catastrophic backtracking): matches ${dangerousPattern}`;\n }\n }\n\n return null;\n}\n\n/**\n * Content Policy Engine\n *\n * Evaluates cards against configured policies and returns the appropriate action.\n *\n * @example\n * ```typescript\n * const engine = new PolicyEngine(moderationStore);\n *\n * const result = await engine.evaluateCard(card, 'source.example.com');\n * if (result.action === 'reject') {\n * return new Response('Content policy violation', { status: 403 });\n * }\n * if (result.action === 'review') {\n * await queueForReview(card, result.matchedRules);\n * }\n * ```\n */\nexport class PolicyEngine {\n private store: ModerationStore;\n private compiledRegexCache: Map<string, RegExp> = new Map();\n\n constructor(store: ModerationStore) {\n this.store = store;\n }\n\n /**\n * Evaluate a card against all active policies\n *\n * @param card - The CCv3 card to evaluate\n * @param sourceInstance - Optional source instance domain for instance rules\n * @returns Evaluation result with action and matched rules\n */\n async evaluateCard(card: CCv3Data, sourceInstance?: string): Promise<PolicyEvaluationResult> {\n const policies = await this.store.listPolicies({ enabled: true });\n const allMatches: PolicyEvaluationResult['matchedRules'] = [];\n\n // Track the highest priority (lowest number) action found\n let finalAction: PolicyAction = 'allow';\n let lowestPriority = Infinity;\n\n for (const policy of policies) {\n const result = this.evaluateAgainstPolicy(card, policy, sourceInstance);\n allMatches.push(...result.matchedRules);\n\n // Check each matched rule's priority\n for (const match of result.matchedRules) {\n const rule = policy.rules.find((r) => r.id === match.ruleId);\n if (rule && rule.priority < lowestPriority) {\n lowestPriority = rule.priority;\n finalAction = rule.action;\n\n // 'allow' rules act as whitelist - immediate return\n if (rule.action === 'allow') {\n return {\n action: 'allow',\n matchedRules: allMatches,\n hasMatch: true,\n };\n }\n }\n }\n }\n\n // If no matches, use default action from first policy or allow\n if (allMatches.length === 0) {\n const defaultAction = policies.length > 0 ? policies[0]!.defaultAction : 'allow';\n return {\n action: defaultAction,\n matchedRules: [],\n hasMatch: false,\n };\n }\n\n return {\n action: finalAction,\n matchedRules: allMatches,\n hasMatch: true,\n };\n }\n\n /**\n * Evaluate card against a single policy\n */\n private evaluateAgainstPolicy(\n card: CCv3Data,\n policy: ContentPolicy,\n sourceInstance?: string\n ): PolicyEvaluationResult {\n const matches: PolicyEvaluationResult['matchedRules'] = [];\n\n // Sort rules by priority (lower = checked first)\n const enabledRules = policy.rules.filter((r) => r.enabled).sort((a, b) => a.priority - b.priority);\n\n for (const rule of enabledRules) {\n const match = this.evaluateRule(card, rule, sourceInstance);\n if (match) {\n matches.push({\n ruleId: rule.id,\n ruleName: rule.name,\n matchedField: match.field,\n matchedValue: match.value,\n });\n\n // If rule is 'allow', this is a whitelist - skip remaining rules\n if (rule.action === 'allow') {\n return { action: 'allow', matchedRules: matches, hasMatch: true };\n }\n }\n }\n\n return {\n action:\n matches.length > 0\n ? (enabledRules.find((r) => matches.some((m) => m.ruleId === r.id))?.action ??\n policy.defaultAction)\n : policy.defaultAction,\n matchedRules: matches,\n hasMatch: matches.length > 0,\n };\n }\n\n /**\n * Evaluate a single rule against a card\n */\n private evaluateRule(\n card: CCv3Data,\n rule: ContentPolicyRule,\n sourceInstance?: string\n ): { field?: string; value?: string } | null {\n switch (rule.type) {\n case 'keyword':\n return this.evaluateKeywordRule(card, rule);\n case 'regex':\n return this.evaluateRegexRule(card, rule);\n case 'tag':\n return this.evaluateTagRule(card, rule);\n case 'creator':\n return this.evaluateCreatorRule(card, rule);\n case 'instance':\n return this.evaluateInstanceRule(sourceInstance, rule);\n default:\n return null;\n }\n }\n\n /**\n * Keyword rule - case-insensitive text search\n */\n private evaluateKeywordRule(\n card: CCv3Data,\n rule: ContentPolicyRule\n ): { field?: string; value?: string } | null {\n const keyword = rule.pattern.toLowerCase();\n const fields = rule.targetFields || DEFAULT_TARGET_FIELDS;\n\n for (const field of fields) {\n const value = this.getCardField(card, field);\n if (typeof value === 'string' && value.toLowerCase().includes(keyword)) {\n return { field, value: keyword };\n }\n }\n return null;\n }\n\n /**\n * Regex rule - pattern matching with compiled cache\n *\n * @security Input is truncated to MAX_REGEX_INPUT_LENGTH to prevent ReDoS.\n * Patterns should be admin-audited. Use checkRegexSafety() when creating rules.\n */\n private evaluateRegexRule(\n card: CCv3Data,\n rule: ContentPolicyRule\n ): { field?: string; value?: string } | null {\n let regex = this.compiledRegexCache.get(rule.id);\n if (!regex) {\n // Check pattern safety\n const safetyWarning = checkRegexSafety(rule.pattern);\n if (safetyWarning) {\n getLogger().warn(`[moderation] Rule \"${rule.name}\": ${safetyWarning}`);\n }\n\n try {\n regex = new RegExp(rule.pattern, 'i');\n this.compiledRegexCache.set(rule.id, regex);\n } catch {\n // Invalid regex pattern\n return null;\n }\n }\n\n const fields = rule.targetFields || DEFAULT_TARGET_FIELDS;\n\n for (const field of fields) {\n const value = this.getCardField(card, field);\n if (typeof value === 'string') {\n // Truncate input to prevent ReDoS on very large fields\n const truncatedValue = value.length > MAX_REGEX_INPUT_LENGTH\n ? value.slice(0, MAX_REGEX_INPUT_LENGTH)\n : value;\n\n const match = truncatedValue.match(regex);\n if (match) {\n return { field, value: match[0] };\n }\n }\n }\n return null;\n }\n\n /**\n * Tag rule - check card tags array\n */\n private evaluateTagRule(\n card: CCv3Data,\n rule: ContentPolicyRule\n ): { field?: string; value?: string } | null {\n const tags = card.data.tags || [];\n const targetTag = rule.pattern.toLowerCase();\n\n const found = tags.find((t) => t.toLowerCase() === targetTag);\n if (found) {\n return { field: 'tags', value: found };\n }\n return null;\n }\n\n /**\n * Creator rule - match card creator\n */\n private evaluateCreatorRule(\n card: CCv3Data,\n rule: ContentPolicyRule\n ): { field?: string; value?: string } | null {\n const creator = card.data.creator?.toLowerCase() || '';\n if (creator === rule.pattern.toLowerCase()) {\n return { field: 'creator', value: creator };\n }\n return null;\n }\n\n /**\n * Instance rule - match source domain\n * Supports wildcard patterns like \"*.evil.com\"\n */\n private evaluateInstanceRule(\n sourceInstance: string | undefined,\n rule: ContentPolicyRule\n ): { field?: string; value?: string } | null {\n if (!sourceInstance) return null;\n\n const domain = sourceInstance.toLowerCase();\n const pattern = rule.pattern.toLowerCase();\n\n // Support wildcard: \"*.evil.com\" matches \"sub.evil.com\"\n if (pattern.startsWith('*.')) {\n const suffix = pattern.slice(2); // Remove \"*.\"\n if (domain.endsWith(suffix) || domain === suffix) {\n return { field: 'instance', value: domain };\n }\n } else if (domain === pattern) {\n return { field: 'instance', value: domain };\n }\n return null;\n }\n\n /**\n * Get a field value from card data\n */\n private getCardField(card: CCv3Data, field: string): unknown {\n // Handle nested fields like 'extensions.something'\n if (field.includes('.')) {\n const parts = field.split('.');\n let value: unknown = card.data;\n for (const part of parts) {\n if (value && typeof value === 'object' && part in value) {\n value = (value as Record<string, unknown>)[part];\n } else {\n return undefined;\n }\n }\n return value;\n }\n\n return (card.data as Record<string, unknown>)[field];\n }\n\n /**\n * Clear regex cache (call when rules change)\n */\n clearCache(): void {\n this.compiledRegexCache.clear();\n }\n\n /**\n * Pre-compile regexes for a policy (optimization)\n *\n * @returns errors for invalid patterns, warnings for potentially unsafe patterns\n */\n precompilePolicy(policy: ContentPolicy): { errors: string[]; warnings: string[] } {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n for (const rule of policy.rules) {\n if (rule.type === 'regex' && rule.enabled) {\n // Check for ReDoS-prone patterns\n const safetyWarning = checkRegexSafety(rule.pattern);\n if (safetyWarning) {\n warnings.push(`Rule \"${rule.name}\": ${safetyWarning}`);\n }\n\n try {\n const regex = new RegExp(rule.pattern, 'i');\n this.compiledRegexCache.set(rule.id, regex);\n } catch (err) {\n errors.push(`Rule \"${rule.name}\" has invalid regex: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n }\n\n return { errors, warnings };\n }\n}\n","/**\n * Rate Limiter\n *\n * Token bucket rate limiting for moderation actions.\n * Prevents abuse of report/flag functionality.\n */\n\nimport type { ModerationStore, RateLimitBucket, RateLimitResult } from './types.js';\n\n/**\n * Rate limiter configuration\n */\nexport interface RateLimiterConfig {\n /** Max tokens per bucket (default: 5) */\n maxTokens: number;\n /** Tokens per hour refill rate (default: 1) */\n refillRate: number;\n}\n\nconst DEFAULT_CONFIG: RateLimiterConfig = {\n maxTokens: 5,\n refillRate: 1,\n};\n\n/**\n * Token bucket rate limiter\n *\n * @example\n * ```typescript\n * const limiter = new RateLimiter(moderationStore, {\n * maxTokens: 10,\n * refillRate: 2, // 2 tokens per hour\n * });\n *\n * const result = await limiter.checkAndConsume('https://example.com/users/alice');\n * if (!result.allowed) {\n * return new Response('Too many reports', {\n * status: 429,\n * headers: { 'Retry-After': String(result.retryAfter) },\n * });\n * }\n * ```\n */\nexport class RateLimiter {\n private store: ModerationStore;\n private config: RateLimiterConfig;\n\n constructor(store: ModerationStore, config?: Partial<RateLimiterConfig>) {\n this.store = store;\n this.config = { ...DEFAULT_CONFIG, ...config };\n }\n\n /**\n * Check if action is allowed and consume a token if so\n *\n * @param actorId - Actor URI to check\n * @returns Rate limit result with remaining tokens and reset time\n */\n async checkAndConsume(actorId: string): Promise<RateLimitResult> {\n let bucket = await this.store.getRateLimitBucket(actorId);\n\n if (!bucket) {\n // Create new bucket for actor\n bucket = {\n actorId,\n tokens: this.config.maxTokens,\n maxTokens: this.config.maxTokens,\n lastRefill: new Date().toISOString(),\n refillRate: this.config.refillRate,\n };\n }\n\n // Refill tokens based on time elapsed\n bucket = this.refillBucket(bucket);\n\n if (bucket.tokens < 1) {\n const resetAt = this.calculateResetTime(bucket);\n const retryAfter = Math.ceil((new Date(resetAt).getTime() - Date.now()) / 1000);\n\n // Update bucket even if denied (to persist refill time)\n await this.store.updateRateLimitBucket(bucket);\n\n return {\n allowed: false,\n remaining: 0,\n resetAt,\n retryAfter: Math.max(0, retryAfter),\n };\n }\n\n // Consume token\n bucket.tokens -= 1;\n await this.store.updateRateLimitBucket(bucket);\n\n return {\n allowed: true,\n remaining: Math.floor(bucket.tokens),\n resetAt: this.calculateResetTime(bucket),\n };\n }\n\n /**\n * Check remaining tokens without consuming\n *\n * @param actorId - Actor URI to check\n * @returns Current rate limit status\n */\n async check(actorId: string): Promise<RateLimitResult> {\n let bucket = await this.store.getRateLimitBucket(actorId);\n\n if (!bucket) {\n return {\n allowed: true,\n remaining: this.config.maxTokens,\n resetAt: new Date().toISOString(),\n };\n }\n\n bucket = this.refillBucket(bucket);\n\n return {\n allowed: bucket.tokens >= 1,\n remaining: Math.floor(bucket.tokens),\n resetAt: this.calculateResetTime(bucket),\n retryAfter:\n bucket.tokens < 1\n ? Math.ceil((new Date(this.calculateResetTime(bucket)).getTime() - Date.now()) / 1000)\n : undefined,\n };\n }\n\n /**\n * Reset rate limit for an actor (admin function)\n *\n * @param actorId - Actor URI to reset\n */\n async reset(actorId: string): Promise<void> {\n const bucket: RateLimitBucket = {\n actorId,\n tokens: this.config.maxTokens,\n maxTokens: this.config.maxTokens,\n lastRefill: new Date().toISOString(),\n refillRate: this.config.refillRate,\n };\n await this.store.updateRateLimitBucket(bucket);\n }\n\n /**\n * Refill tokens based on time elapsed since last refill\n */\n private refillBucket(bucket: RateLimitBucket): RateLimitBucket {\n const now = new Date();\n const lastRefill = new Date(bucket.lastRefill);\n const hoursSinceRefill = (now.getTime() - lastRefill.getTime()) / (1000 * 60 * 60);\n\n const tokensToAdd = hoursSinceRefill * bucket.refillRate;\n\n if (tokensToAdd >= 1) {\n // Only update if we're adding at least 1 token (avoid floating point issues)\n bucket.tokens = Math.min(bucket.maxTokens, bucket.tokens + Math.floor(tokensToAdd));\n bucket.lastRefill = now.toISOString();\n }\n\n return bucket;\n }\n\n /**\n * Calculate when bucket will have at least 1 token\n */\n private calculateResetTime(bucket: RateLimitBucket): string {\n if (bucket.tokens >= bucket.maxTokens) {\n return new Date().toISOString();\n }\n\n // Calculate time until 1 token is available\n const tokensNeeded = Math.max(0, 1 - bucket.tokens);\n const hoursUntilToken = tokensNeeded / bucket.refillRate;\n const msUntilToken = hoursUntilToken * 60 * 60 * 1000;\n\n const lastRefill = new Date(bucket.lastRefill);\n return new Date(lastRefill.getTime() + msUntilToken).toISOString();\n }\n\n /**\n * Get current configuration\n */\n getConfig(): RateLimiterConfig {\n return { ...this.config };\n }\n}\n","/**\n * @character-foundry/federation\n *\n * Federation layer for syncing character cards across platforms via ActivityPub.\n *\n * ⚠️ WARNING: This package is experimental and incomplete.\n *\n * Security-critical features (signature validation, inbox handling) are stubbed.\n * Do NOT use in production without explicit opt-in.\n *\n * To enable federation features, you MUST:\n * 1. Set FEDERATION_ENABLED=true in your environment (Node.js) or call enableFederation({ skipEnvCheck: true }) in browser/Workers\n * 2. Call enableFederation() before using SyncEngine or route handlers\n *\n * Both steps are required as a dual opt-in safety mechanism (except in environments without process.env).\n */\n\nimport type { Logger, LogLevel } from './logger.js';\nimport { configureLogger, getLogger } from './logger.js';\n\nlet explicitlyEnabled = false;\nlet envCheckSkipped = false;\n\n/**\n * Safe check for environment variable - works in Node.js, browser, and Workers\n */\nfunction getEnvVar(name: string): string | undefined {\n // Check if process exists and has env (Node.js)\n if (typeof process !== 'undefined' && process?.env) {\n return process.env[name];\n }\n // In browser/Workers, return undefined (env var check will be skipped)\n return undefined;\n}\n\n/**\n * Enable federation features. Must be called before using SyncEngine or route handlers.\n * This is a safety gate - federation is experimental and has incomplete security.\n *\n * @param options.skipEnvCheck - Skip FEDERATION_ENABLED env var check (required for browser/Workers)\n *\n * Note: In Node.js, both this call AND FEDERATION_ENABLED=true are required (dual opt-in).\n * In browser/Workers, use skipEnvCheck: true since env vars aren't available.\n */\nexport function enableFederation(options?: {\n skipEnvCheck?: boolean;\n /** Optional logger override */\n logger?: Logger;\n /** Optional log level (used when logger is not provided). Default: warn */\n logLevel?: LogLevel;\n}): void {\n configureLogger(options);\n\n explicitlyEnabled = true;\n envCheckSkipped = options?.skipEnvCheck ?? false;\n\n const nodeEnv = getEnvVar('NODE_ENV');\n if (nodeEnv === 'development' || nodeEnv === 'test') {\n getLogger().warn(\n '[character-foundry/federation] Federation enabled. ' +\n 'WARNING: Verify HTTP signatures in production. ' +\n 'Do NOT use in production with untrusted inputs without signature validation.'\n );\n }\n}\n\n/**\n * Check if federation is enabled\n *\n * In Node.js: requires BOTH env var AND enableFederation() call\n * In browser/Workers with skipEnvCheck: requires only enableFederation() call\n */\nexport function isFederationEnabled(): boolean {\n if (!explicitlyEnabled) {\n return false;\n }\n\n // If env check was skipped (browser/Workers), only check explicit enable\n if (envCheckSkipped) {\n return true;\n }\n\n // In Node.js, also require env var\n const envEnabled = getEnvVar('FEDERATION_ENABLED') === 'true';\n return envEnabled;\n}\n\n/**\n * Assert federation is enabled, throw if not\n * @internal\n */\nexport function assertFederationEnabled(feature: string): void {\n if (!explicitlyEnabled) {\n const hasProcess = typeof process !== 'undefined' && process?.env;\n const envHint = hasProcess\n ? 'You must BOTH call enableFederation() AND set FEDERATION_ENABLED=true.'\n : 'You must call enableFederation({ skipEnvCheck: true }) in browser/Workers environments.';\n\n throw new Error(\n `Federation is not enabled. ${feature} requires federation to be explicitly enabled. ` +\n `${envHint} ` +\n `WARNING: Federation security features are incomplete.`\n );\n }\n\n // If env check was skipped, we're done\n if (envCheckSkipped) {\n return;\n }\n\n // In Node.js, also check env var\n const envEnabled = getEnvVar('FEDERATION_ENABLED') === 'true';\n if (!envEnabled) {\n throw new Error(\n `FEDERATION_ENABLED environment variable not set. ${feature} requires FEDERATION_ENABLED=true. ` +\n `Code opt-in alone is not sufficient (dual opt-in required). ` +\n `In browser/Workers, use enableFederation({ skipEnvCheck: true }) instead.`\n );\n }\n}\n\n// Logging\nexport type { Logger, LogLevel } from './logger.js';\nexport {\n createConsoleLogger,\n getLogger as getFederationLogger,\n setLogger as setFederationLogger,\n setLogLevel as setFederationLogLevel,\n} from './logger.js';\n\n// Core types\nexport type {\n FederatedCardId,\n PlatformId,\n PlatformRole,\n FederatedActor,\n FederatedCard,\n ActivityType,\n FederatedActivity,\n CardSyncState,\n SyncOperation,\n SyncResult,\n PlatformAdapter,\n FederationConfig,\n SyncStateStore,\n FederationEventType,\n FederationEvent,\n FederationEventListener,\n // Fork types\n ForkReference,\n ForkNotification,\n ForkActivity,\n ForkResult,\n InboxResult,\n InboxHandlerOptions,\n // Install/stats types\n InstallNotification,\n InstallActivity,\n CardStats,\n} from './types.js';\n\n// ActivityPub utilities\nexport {\n ACTIVITY_CONTEXT,\n FORK_ACTIVITY_CONTEXT,\n INSTALL_ACTIVITY_CONTEXT,\n generateCardId,\n generateActivityId,\n cardToActivityPub,\n cardFromActivityPub,\n createCreateActivity,\n createUpdateActivity,\n createDeleteActivity,\n createAnnounceActivity,\n createLikeActivity,\n createUndoActivity,\n createForkActivity,\n parseForkActivity,\n createInstallActivity,\n parseInstallActivity,\n createActor,\n parseActivity,\n // validateActivitySignature is deprecated - use validateHttpSignature instead\n} from './activitypub.js';\n\n// Sync engine\nexport {\n SyncEngine,\n type SyncEngineOptions,\n} from './sync-engine.js';\n\n// State stores\nexport {\n MemorySyncStateStore,\n FileSyncStateStore,\n createLocalStorageStore,\n} from './state-store.js';\n\nexport {\n D1SyncStateStore,\n type D1Database,\n type D1PreparedStatement,\n type D1Result,\n type D1ExecResult,\n} from './d1-store.js';\n\n// Platform adapters\nexport {\n BasePlatformAdapter,\n MemoryPlatformAdapter,\n HttpPlatformAdapter,\n InvalidResourceIdError,\n SillyTavernAdapter,\n createArchiveAdapter,\n createHubAdapter,\n stCharacterToCCv3,\n ccv3ToSTCharacter,\n createMockSTBridge,\n type AdapterCard,\n type AdapterAsset,\n type HttpAdapterConfig,\n type FetchFn,\n type SillyTavernBridge,\n type STCharacter,\n type STCharacterStats,\n} from './adapters/index.js';\n\n// Routes\nexport {\n handleWebFinger,\n type WebFingerResponse,\n type WebFingerLink,\n handleNodeInfoDiscovery,\n handleNodeInfo,\n type NodeInfoDiscoveryResponse,\n type NodeInfoResponse,\n handleActor,\n handleInbox,\n validateForkActivity,\n validateInstallActivity,\n} from './routes/index.js';\n\n// HTTP Signatures\nexport {\n REQUIRED_SIGNED_HEADERS,\n type ParsedSignature,\n type SignatureValidationOptions,\n type SignatureValidationResult,\n type SigningOptions,\n type SigningStringResult,\n type VerifyHttpSignatureOptions,\n parseSignatureHeader,\n buildSigningString,\n buildSigningStringStrict,\n verifyHttpSignature,\n validateActivitySignature as validateHttpSignature,\n signRequest,\n calculateDigest,\n} from './http-signatures.js';\n\n// Moderation - community-based content moderation tools\nexport {\n // Activity types\n type FlagActivity,\n type BlockActivity,\n // Report types\n type ModerationReport,\n type ReportStatus,\n type ReportCategory,\n // Action types\n type ModerationAction,\n type ActionType,\n // Block types\n type InstanceBlock,\n type InstanceBlockLevel,\n // Policy types\n type ContentPolicy,\n type ContentPolicyRule,\n type PolicyRuleType,\n type PolicyAction,\n type PolicyEvaluationResult,\n // Rate limit types\n type RateLimitBucket,\n type RateLimitResult,\n // Store interface\n type ModerationStore,\n // Event types\n type ModerationEvent,\n type ModerationEventType,\n type ModerationEventListener,\n // Activities\n MODERATION_ACTIVITY_CONTEXT,\n createFlagActivity,\n parseFlagActivity,\n validateFlagActivity as validateFlagActivityFields,\n createBlockActivity,\n parseBlockActivity,\n validateBlockActivity as validateBlockActivityFields,\n // Stores\n MemoryModerationStore,\n D1ModerationStore,\n // Engines\n PolicyEngine,\n checkRegexSafety,\n RateLimiter,\n type RateLimiterConfig,\n} from './moderation/index.js';\n"],"mappings":"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","names":["newHash","generateUUID"]}
|