@karixi/payload-ai 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"base-CWybHD_y.mjs","names":["detectMediaType"],"sources":["../src/core/providers/anthropic.ts","../src/core/providers/gemini.ts","../src/core/providers/ollama.ts","../src/core/providers/openai.ts","../src/core/providers/base.ts"],"sourcesContent":["import type { AIProvider } from '../../types.js'\n\ntype AnthropicMessage = {\n role: 'user' | 'assistant'\n content: string | AnthropicContentBlock[]\n}\n\ntype AnthropicContentBlock =\n | { type: 'text'; text: string }\n | { type: 'image'; source: { type: 'base64'; media_type: string; data: string } }\n\ntype AnthropicResponse = {\n content: Array<{ type: string; text?: string }>\n usage?: { input_tokens: number; output_tokens: number }\n}\n\nfunction isAnthropicResponse(value: unknown): value is AnthropicResponse {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'content' in value &&\n Array.isArray((value as AnthropicResponse).content)\n )\n}\n\nexport type AnthropicProviderConfig = {\n apiKey: string\n model?: string\n baseUrl?: string\n}\n\nexport function createAnthropicProvider(configOrKey: AnthropicProviderConfig | string): AIProvider {\n const config: AnthropicProviderConfig =\n typeof configOrKey === 'string' ? { apiKey: configOrKey } : configOrKey\n const apiKey = config.apiKey\n const model = config.model ?? 'claude-sonnet-4-20250514'\n const baseUrl = (config.baseUrl ?? 'https://api.anthropic.com').replace(/\\/+$/, '')\n\n async function callAPI(messages: AnthropicMessage[]): Promise<AnthropicResponse> {\n const response = await fetch(`${baseUrl}/v1/messages`, {\n method: 'POST',\n headers: {\n 'x-api-key': apiKey,\n 'anthropic-version': '2023-06-01',\n 'content-type': 'application/json',\n },\n body: JSON.stringify({\n model,\n max_tokens: 8192,\n messages,\n }),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(`Anthropic API error ${response.status}: ${errorText}`)\n }\n\n const data: unknown = await response.json()\n if (!isAnthropicResponse(data)) {\n throw new Error('Unexpected Anthropic API response shape')\n }\n return data\n }\n\n return {\n async generate(prompt: string, _outputSchema: Record<string, unknown>): Promise<unknown[]> {\n const messages: AnthropicMessage[] = [\n {\n role: 'user',\n content: `${prompt}\\n\\nRespond with ONLY a valid JSON array. No markdown, no explanation, just the JSON array.`,\n },\n ]\n\n const data = await callAPI(messages)\n const textBlock = data.content.find((block) => block.type === 'text')\n if (!textBlock || !('text' in textBlock) || typeof textBlock.text !== 'string') {\n throw new Error('No text content in Anthropic response')\n }\n\n const text = textBlock.text.trim()\n // Strip markdown code fences if present\n const jsonText = text\n .replace(/^```(?:json)?\\s*/i, '')\n .replace(/\\s*```\\s*$/, '')\n .trim()\n\n const parsed: unknown = JSON.parse(jsonText)\n if (!Array.isArray(parsed)) {\n throw new Error('Anthropic response is not a JSON array')\n }\n return parsed\n },\n\n async analyzeImage(imageBuffer: Buffer): Promise<string> {\n const base64 = imageBuffer.toString('base64')\n // Detect image type from buffer magic bytes\n let mediaType = 'image/jpeg'\n if (imageBuffer[0] === 0x89 && imageBuffer[1] === 0x50) mediaType = 'image/png'\n else if (imageBuffer[0] === 0x47 && imageBuffer[1] === 0x49) mediaType = 'image/gif'\n else if (imageBuffer[0] === 0x52 && imageBuffer[1] === 0x49) mediaType = 'image/webp'\n\n const messages: AnthropicMessage[] = [\n {\n role: 'user',\n content: [\n {\n type: 'image',\n source: { type: 'base64', media_type: mediaType, data: base64 },\n },\n {\n type: 'text',\n text: 'Describe this image concisely for use as alt text. Focus on the main subject and important visual details. Respond with only the alt text description, no extra explanation.',\n },\n ],\n },\n ]\n\n const data = await callAPI(messages)\n const textBlock = data.content.find((block) => block.type === 'text')\n if (!textBlock || !('text' in textBlock) || typeof textBlock.text !== 'string') {\n throw new Error('No text content in Anthropic image analysis response')\n }\n return textBlock.text.trim()\n },\n }\n}\n","import type { AIProvider } from '../../types.js'\n\ntype GeminiPart = { text: string } | { inline_data: { mime_type: string; data: string } }\n\ntype GeminiContent = {\n role: 'user' | 'model'\n parts: GeminiPart[]\n}\n\ntype GeminiResponse = {\n candidates?: Array<{\n content: { parts: Array<{ text?: string }>; role: string }\n finishReason: string\n }>\n usageMetadata?: {\n promptTokenCount: number\n candidatesTokenCount: number\n totalTokenCount: number\n }\n promptFeedback?: { blockReason?: string }\n}\n\nfunction isGeminiResponse(value: unknown): value is GeminiResponse {\n return typeof value === 'object' && value !== null && 'candidates' in value\n}\n\nfunction detectMediaType(buffer: Buffer): string {\n if (buffer[0] === 0x89 && buffer[1] === 0x50) return 'image/png'\n if (buffer[0] === 0x47 && buffer[1] === 0x49) return 'image/gif'\n if (buffer[0] === 0x52 && buffer[1] === 0x49) return 'image/webp'\n return 'image/jpeg'\n}\n\nfunction extractText(response: GeminiResponse): string {\n if (!response.candidates || response.candidates.length === 0) {\n const reason = response.promptFeedback?.blockReason ?? 'unknown'\n throw new Error(`Gemini request blocked: ${reason}`)\n }\n\n const candidate = response.candidates[0]\n const text = candidate.content.parts.find((p) => 'text' in p)?.text\n if (!text) {\n throw new Error('No text content in Gemini response')\n }\n\n if (candidate.finishReason === 'MAX_TOKENS') {\n throw new Error('Gemini response truncated (MAX_TOKENS) — output may be incomplete')\n }\n\n return text.trim()\n}\n\nexport type GeminiProviderConfig = {\n apiKey: string\n model?: string\n baseUrl?: string\n}\n\nexport function createGeminiProvider(config: GeminiProviderConfig): AIProvider {\n const model = config.model ?? 'gemini-2.5-flash'\n const baseUrl = config.baseUrl ?? 'https://generativelanguage.googleapis.com/v1beta'\n\n async function callAPI(\n contents: GeminiContent[],\n systemInstruction?: string,\n jsonMode?: boolean,\n ): Promise<GeminiResponse> {\n const body: Record<string, unknown> = { contents }\n\n if (systemInstruction) {\n body.system_instruction = { parts: [{ text: systemInstruction }] }\n }\n\n if (jsonMode) {\n body.generationConfig = {\n responseMimeType: 'application/json',\n }\n }\n\n const url = `${baseUrl}/models/${model}:generateContent`\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'x-goog-api-key': config.apiKey,\n 'content-type': 'application/json',\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(`Gemini API error ${response.status}: ${errorText}`)\n }\n\n const data: unknown = await response.json()\n if (!isGeminiResponse(data)) {\n throw new Error('Unexpected Gemini API response shape')\n }\n return data\n }\n\n return {\n async generate(prompt: string, _outputSchema: Record<string, unknown>): Promise<unknown[]> {\n const contents: GeminiContent[] = [\n {\n role: 'user',\n parts: [\n {\n text: `${prompt}\\n\\nRespond with ONLY a valid JSON array. No markdown, no explanation, just the JSON array.`,\n },\n ],\n },\n ]\n\n const data = await callAPI(\n contents,\n 'You are a data generation assistant. Always respond with valid JSON arrays only.',\n true,\n )\n\n const text = extractText(data)\n const jsonText = text\n .replace(/^```(?:json)?\\s*/i, '')\n .replace(/\\s*```\\s*$/, '')\n .trim()\n\n const parsed: unknown = JSON.parse(jsonText)\n if (Array.isArray(parsed)) {\n return parsed\n }\n if (typeof parsed === 'object' && parsed !== null && 'items' in parsed) {\n const items = (parsed as { items: unknown }).items\n if (Array.isArray(items)) return items\n }\n throw new Error('Gemini response is not a JSON array')\n },\n\n async analyzeImage(imageBuffer: Buffer): Promise<string> {\n const base64 = imageBuffer.toString('base64')\n const mimeType = detectMediaType(imageBuffer)\n\n const contents: GeminiContent[] = [\n {\n role: 'user',\n parts: [\n { inline_data: { mime_type: mimeType, data: base64 } },\n {\n text: 'Describe this image concisely for use as alt text. Focus on the main subject and important visual details. Respond with only the alt text description, no extra explanation.',\n },\n ],\n },\n ]\n\n const data = await callAPI(contents)\n return extractText(data)\n },\n }\n}\n","import type { AIProvider } from '../../types.js'\n\n/**\n * OpenAI-compatible provider for local LLM servers.\n * Works with: Ollama, LocalAI, vLLM, LM Studio, and any\n * server exposing an OpenAI-compatible /v1/chat/completions endpoint.\n */\n\ntype ChatMessage = {\n role: 'user' | 'assistant' | 'system'\n content: string | ContentBlock[]\n}\n\ntype ContentBlock =\n | { type: 'text'; text: string }\n | { type: 'image_url'; image_url: { url: string } }\n\ntype ChatResponse = {\n choices: Array<{\n message: { role: string; content: string | null }\n }>\n}\n\nfunction isChatResponse(value: unknown): value is ChatResponse {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'choices' in value &&\n Array.isArray((value as ChatResponse).choices)\n )\n}\n\nfunction detectMediaType(buffer: Buffer): string {\n if (buffer[0] === 0x89 && buffer[1] === 0x50) return 'image/png'\n if (buffer[0] === 0x47 && buffer[1] === 0x49) return 'image/gif'\n if (buffer[0] === 0x52 && buffer[1] === 0x49) return 'image/webp'\n return 'image/jpeg'\n}\n\nexport type OllamaProviderConfig = {\n baseUrl?: string\n model?: string\n apiKey?: string\n}\n\nexport function createOllamaProvider(config: OllamaProviderConfig): AIProvider {\n const baseUrl = (config.baseUrl ?? 'http://localhost:11434').replace(/\\/+$/, '')\n const model = config.model ?? 'llama3.3:8b'\n const apiKey = config.apiKey ?? 'ollama'\n\n async function callAPI(messages: ChatMessage[], jsonMode: boolean): Promise<ChatResponse> {\n const body: Record<string, unknown> = { model, messages }\n if (jsonMode) {\n body.response_format = { type: 'json_object' }\n }\n\n const response = await fetch(`${baseUrl}/v1/chat/completions`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${apiKey}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(`Ollama API error ${response.status}: ${errorText}`)\n }\n\n const data: unknown = await response.json()\n if (!isChatResponse(data)) {\n throw new Error('Unexpected API response shape from local LLM server')\n }\n return data\n }\n\n return {\n async generate(prompt: string, _outputSchema: Record<string, unknown>): Promise<unknown[]> {\n const messages: ChatMessage[] = [\n {\n role: 'system',\n content:\n 'You are a data generation assistant. Always respond with valid JSON only. When asked for an array, wrap it in {\"items\": [...]} so json_object mode is satisfied.',\n },\n {\n role: 'user',\n content: `${prompt}\\n\\nRespond with JSON object {\"items\": [...]} where items is the array of generated documents.`,\n },\n ]\n\n const data = await callAPI(messages, true)\n const choice = data.choices[0]\n if (!choice || choice.message.content === null) {\n throw new Error('No content in response from local LLM server')\n }\n\n const parsed: unknown = JSON.parse(choice.message.content)\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n 'items' in parsed &&\n Array.isArray((parsed as { items: unknown }).items)\n ) {\n return (parsed as { items: unknown[] }).items\n }\n if (Array.isArray(parsed)) {\n return parsed\n }\n throw new Error('Local LLM response is not a JSON array')\n },\n\n async analyzeImage(imageBuffer: Buffer): Promise<string> {\n const base64 = imageBuffer.toString('base64')\n const mediaType = detectMediaType(imageBuffer)\n const dataUrl = `data:${mediaType};base64,${base64}`\n\n const messages: ChatMessage[] = [\n {\n role: 'user',\n content: [\n { type: 'image_url', image_url: { url: dataUrl } },\n {\n type: 'text',\n text: 'Describe this image concisely for use as alt text. Focus on the main subject and important visual details. Respond with only the alt text description, no extra explanation.',\n },\n ],\n },\n ]\n\n const data = await callAPI(messages, false)\n const choice = data.choices[0]\n if (!choice || choice.message.content === null) {\n throw new Error('No content in image analysis response from local LLM server')\n }\n return (choice.message.content as string).trim()\n },\n }\n}\n","import type { AIProvider } from '../../types.js'\n\ntype OpenAIMessage = {\n role: 'user' | 'assistant' | 'system'\n content: string | OpenAIContentBlock[]\n}\n\ntype OpenAIContentBlock =\n | { type: 'text'; text: string }\n | { type: 'image_url'; image_url: { url: string } }\n\ntype OpenAIResponse = {\n choices: Array<{\n message: { role: string; content: string | null }\n }>\n usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number }\n}\n\nfunction isOpenAIResponse(value: unknown): value is OpenAIResponse {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'choices' in value &&\n Array.isArray((value as OpenAIResponse).choices)\n )\n}\n\nexport type OpenAIProviderConfig = {\n apiKey: string\n model?: string\n baseUrl?: string\n}\n\nexport function createOpenAIProvider(configOrKey: OpenAIProviderConfig | string): AIProvider {\n const config: OpenAIProviderConfig =\n typeof configOrKey === 'string' ? { apiKey: configOrKey } : configOrKey\n const apiKey = config.apiKey\n const model = config.model ?? 'gpt-4o'\n const baseUrl = (config.baseUrl ?? 'https://api.openai.com').replace(/\\/+$/, '')\n\n async function callAPI(messages: OpenAIMessage[], jsonMode: boolean): Promise<OpenAIResponse> {\n const body: Record<string, unknown> = {\n model,\n messages,\n }\n if (jsonMode) {\n body.response_format = { type: 'json_object' }\n }\n\n const response = await fetch(`${baseUrl}/v1/chat/completions`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${apiKey}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(`OpenAI API error ${response.status}: ${errorText}`)\n }\n\n const data: unknown = await response.json()\n if (!isOpenAIResponse(data)) {\n throw new Error('Unexpected OpenAI API response shape')\n }\n return data\n }\n\n return {\n async generate(prompt: string, _outputSchema: Record<string, unknown>): Promise<unknown[]> {\n const messages: OpenAIMessage[] = [\n {\n role: 'system',\n content:\n 'You are a data generation assistant. Always respond with valid JSON only. When asked for an array, wrap it in {\"items\": [...]} so json_object mode is satisfied.',\n },\n {\n role: 'user',\n content: `${prompt}\\n\\nRespond with JSON object {\"items\": [...]} where items is the array of generated documents.`,\n },\n ]\n\n const data = await callAPI(messages, true)\n const choice = data.choices[0]\n if (!choice || choice.message.content === null) {\n throw new Error('No content in OpenAI response')\n }\n\n const parsed: unknown = JSON.parse(choice.message.content)\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n 'items' in parsed &&\n Array.isArray((parsed as { items: unknown }).items)\n ) {\n return (parsed as { items: unknown[] }).items\n }\n if (Array.isArray(parsed)) {\n return parsed\n }\n throw new Error('OpenAI response is not a JSON array')\n },\n\n async analyzeImage(imageBuffer: Buffer): Promise<string> {\n const base64 = imageBuffer.toString('base64')\n // Detect image type from buffer magic bytes\n let mediaType = 'image/jpeg'\n if (imageBuffer[0] === 0x89 && imageBuffer[1] === 0x50) mediaType = 'image/png'\n else if (imageBuffer[0] === 0x47 && imageBuffer[1] === 0x49) mediaType = 'image/gif'\n else if (imageBuffer[0] === 0x52 && imageBuffer[1] === 0x49) mediaType = 'image/webp'\n\n const dataUrl = `data:${mediaType};base64,${base64}`\n\n const messages: OpenAIMessage[] = [\n {\n role: 'user',\n content: [\n {\n type: 'image_url',\n image_url: { url: dataUrl },\n },\n {\n type: 'text',\n text: 'Describe this image concisely for use as alt text. Focus on the main subject and important visual details. Respond with only the alt text description, no extra explanation.',\n },\n ],\n },\n ]\n\n const data = await callAPI(messages, false)\n const choice = data.choices[0]\n if (!choice || choice.message.content === null) {\n throw new Error('No content in OpenAI image analysis response')\n }\n return choice.message.content.trim()\n },\n }\n}\n","import type { AIProvider } from '../../types.js'\nimport { createAnthropicProvider } from './anthropic.js'\nimport { createGeminiProvider } from './gemini.js'\nimport { createOllamaProvider } from './ollama.js'\nimport { createOpenAIProvider } from './openai.js'\n\nexport type { AIProvider }\n\nexport type ProviderConfig = {\n provider: 'anthropic' | 'openai' | 'gemini' | 'ollama'\n apiKey: string\n baseUrl?: string\n model?: string\n}\n\nexport function createProvider(config: ProviderConfig): AIProvider {\n switch (config.provider) {\n case 'anthropic':\n return createAnthropicProvider({\n apiKey: config.apiKey,\n model: config.model,\n baseUrl: config.baseUrl,\n })\n case 'openai':\n return createOpenAIProvider({\n apiKey: config.apiKey,\n model: config.model,\n baseUrl: config.baseUrl,\n })\n case 'gemini':\n return createGeminiProvider({\n apiKey: config.apiKey,\n model: config.model,\n baseUrl: config.baseUrl,\n })\n case 'ollama':\n return createOllamaProvider({\n baseUrl: config.baseUrl,\n model: config.model,\n apiKey: config.apiKey,\n })\n default: {\n const _exhaustive: never = config.provider\n throw new Error(`Unknown provider: ${String(_exhaustive)}`)\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"base-CWybHD_y.mjs","names":["detectMediaType"],"sources":["../src/core/providers/anthropic.ts","../src/core/providers/gemini.ts","../src/core/providers/ollama.ts","../src/core/providers/openai.ts","../src/core/providers/base.ts"],"sourcesContent":["import type { AIProvider } from '../../types.js'\n\ntype AnthropicMessage = {\n role: 'user' | 'assistant'\n content: string | AnthropicContentBlock[]\n}\n\ntype AnthropicContentBlock =\n | { type: 'text'; text: string }\n | { type: 'image'; source: { type: 'base64'; media_type: string; data: string } }\n\ntype AnthropicResponse = {\n content: Array<{ type: string; text?: string }>\n usage?: { input_tokens: number; output_tokens: number }\n}\n\nfunction isAnthropicResponse(value: unknown): value is AnthropicResponse {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'content' in value &&\n Array.isArray((value as AnthropicResponse).content)\n )\n}\n\nexport type AnthropicProviderConfig = {\n apiKey: string\n model?: string\n baseUrl?: string\n}\n\nexport function createAnthropicProvider(configOrKey: AnthropicProviderConfig | string): AIProvider {\n const config: AnthropicProviderConfig =\n typeof configOrKey === 'string' ? { apiKey: configOrKey } : configOrKey\n const apiKey = config.apiKey\n const model = config.model ?? 'claude-sonnet-4-20250514'\n const baseUrl = (config.baseUrl ?? 'https://api.anthropic.com').replace(/\\/+$/, '')\n\n async function callAPI(messages: AnthropicMessage[]): Promise<AnthropicResponse> {\n const response = await fetch(`${baseUrl}/v1/messages`, {\n method: 'POST',\n headers: {\n 'x-api-key': apiKey,\n 'anthropic-version': '2023-06-01',\n 'content-type': 'application/json',\n },\n body: JSON.stringify({\n model,\n max_tokens: 8192,\n messages,\n }),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(`Anthropic API error ${response.status}: ${errorText}`)\n }\n\n const data: unknown = await response.json()\n if (!isAnthropicResponse(data)) {\n throw new Error('Unexpected Anthropic API response shape')\n }\n return data\n }\n\n return {\n async generate(prompt: string, _outputSchema: Record<string, unknown>): Promise<unknown[]> {\n const messages: AnthropicMessage[] = [\n {\n role: 'user',\n content: `${prompt}\\n\\nRespond with ONLY a valid JSON array. No markdown, no explanation, just the JSON array.`,\n },\n ]\n\n const data = await callAPI(messages)\n const textBlock = data.content.find((block) => block.type === 'text')\n if (!textBlock || !('text' in textBlock) || typeof textBlock.text !== 'string') {\n throw new Error('No text content in Anthropic response')\n }\n\n const text = textBlock.text.trim()\n // Strip markdown code fences if present\n const jsonText = text\n .replace(/^```(?:json)?\\s*/i, '')\n .replace(/\\s*```\\s*$/, '')\n .trim()\n\n const parsed: unknown = JSON.parse(jsonText)\n if (!Array.isArray(parsed)) {\n throw new Error('Anthropic response is not a JSON array')\n }\n return parsed\n },\n\n async analyzeImage(imageBuffer: Buffer): Promise<string> {\n const base64 = imageBuffer.toString('base64')\n // Detect image type from buffer magic bytes\n let mediaType = 'image/jpeg'\n if (imageBuffer[0] === 0x89 && imageBuffer[1] === 0x50) mediaType = 'image/png'\n else if (imageBuffer[0] === 0x47 && imageBuffer[1] === 0x49) mediaType = 'image/gif'\n else if (imageBuffer[0] === 0x52 && imageBuffer[1] === 0x49) mediaType = 'image/webp'\n\n const messages: AnthropicMessage[] = [\n {\n role: 'user',\n content: [\n {\n type: 'image',\n source: { type: 'base64', media_type: mediaType, data: base64 },\n },\n {\n type: 'text',\n text: 'Describe this image concisely for use as alt text. Focus on the main subject and important visual details. Respond with only the alt text description, no extra explanation.',\n },\n ],\n },\n ]\n\n const data = await callAPI(messages)\n const textBlock = data.content.find((block) => block.type === 'text')\n if (!textBlock || !('text' in textBlock) || typeof textBlock.text !== 'string') {\n throw new Error('No text content in Anthropic image analysis response')\n }\n return textBlock.text.trim()\n },\n }\n}\n","import type { AIProvider } from '../../types.js'\n\ntype GeminiPart = { text: string } | { inline_data: { mime_type: string; data: string } }\n\ntype GeminiContent = {\n role: 'user' | 'model'\n parts: GeminiPart[]\n}\n\ntype GeminiResponse = {\n candidates?: Array<{\n content: { parts: Array<{ text?: string }>; role: string }\n finishReason: string\n }>\n usageMetadata?: {\n promptTokenCount: number\n candidatesTokenCount: number\n totalTokenCount: number\n }\n promptFeedback?: { blockReason?: string }\n}\n\nfunction isGeminiResponse(value: unknown): value is GeminiResponse {\n return typeof value === 'object' && value !== null && 'candidates' in value\n}\n\nfunction detectMediaType(buffer: Buffer): string {\n if (buffer[0] === 0x89 && buffer[1] === 0x50) return 'image/png'\n if (buffer[0] === 0x47 && buffer[1] === 0x49) return 'image/gif'\n if (buffer[0] === 0x52 && buffer[1] === 0x49) return 'image/webp'\n return 'image/jpeg'\n}\n\nfunction extractText(response: GeminiResponse): string {\n if (!response.candidates || response.candidates.length === 0) {\n const reason = response.promptFeedback?.blockReason ?? 'unknown'\n throw new Error(`Gemini request blocked: ${reason}`)\n }\n\n const candidate = response.candidates[0]\n const text = candidate.content.parts.find((p) => 'text' in p)?.text\n if (!text) {\n throw new Error('No text content in Gemini response')\n }\n\n if (candidate.finishReason === 'MAX_TOKENS') {\n throw new Error('Gemini response truncated (MAX_TOKENS) — output may be incomplete')\n }\n\n return text.trim()\n}\n\nexport type GeminiProviderConfig = {\n apiKey: string\n model?: string\n baseUrl?: string\n}\n\nexport function createGeminiProvider(config: GeminiProviderConfig): AIProvider {\n const model = config.model ?? 'gemini-2.5-flash'\n const baseUrl = config.baseUrl ?? 'https://generativelanguage.googleapis.com/v1beta'\n\n async function callAPI(\n contents: GeminiContent[],\n systemInstruction?: string,\n jsonMode?: boolean,\n ): Promise<GeminiResponse> {\n const body: Record<string, unknown> = { contents }\n\n if (systemInstruction) {\n body.system_instruction = { parts: [{ text: systemInstruction }] }\n }\n\n if (jsonMode) {\n body.generationConfig = {\n responseMimeType: 'application/json',\n }\n }\n\n const url = `${baseUrl}/models/${model}:generateContent`\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'x-goog-api-key': config.apiKey,\n 'content-type': 'application/json',\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(`Gemini API error ${response.status}: ${errorText}`)\n }\n\n const data: unknown = await response.json()\n if (!isGeminiResponse(data)) {\n throw new Error('Unexpected Gemini API response shape')\n }\n return data\n }\n\n return {\n async generate(prompt: string, _outputSchema: Record<string, unknown>): Promise<unknown[]> {\n const contents: GeminiContent[] = [\n {\n role: 'user',\n parts: [\n {\n text: `${prompt}\\n\\nRespond with ONLY a valid JSON array. No markdown, no explanation, just the JSON array.`,\n },\n ],\n },\n ]\n\n const data = await callAPI(\n contents,\n 'You are a data generation assistant. Always respond with valid JSON arrays only.',\n true,\n )\n\n const text = extractText(data)\n const jsonText = text\n .replace(/^```(?:json)?\\s*/i, '')\n .replace(/\\s*```\\s*$/, '')\n .trim()\n\n const parsed: unknown = JSON.parse(jsonText)\n if (Array.isArray(parsed)) {\n return parsed\n }\n if (typeof parsed === 'object' && parsed !== null && 'items' in parsed) {\n const items = (parsed as { items: unknown }).items\n if (Array.isArray(items)) return items\n }\n throw new Error('Gemini response is not a JSON array')\n },\n\n async analyzeImage(imageBuffer: Buffer): Promise<string> {\n const base64 = imageBuffer.toString('base64')\n const mimeType = detectMediaType(imageBuffer)\n\n const contents: GeminiContent[] = [\n {\n role: 'user',\n parts: [\n { inline_data: { mime_type: mimeType, data: base64 } },\n {\n text: 'Describe this image concisely for use as alt text. Focus on the main subject and important visual details. Respond with only the alt text description, no extra explanation.',\n },\n ],\n },\n ]\n\n const data = await callAPI(contents)\n return extractText(data)\n },\n }\n}\n","import type { AIProvider } from '../../types.js'\n\n/**\n * OpenAI-compatible provider for local LLM servers.\n * Works with: Ollama, LocalAI, vLLM, LM Studio, and any\n * server exposing an OpenAI-compatible /v1/chat/completions endpoint.\n */\n\ntype ChatMessage = {\n role: 'user' | 'assistant' | 'system'\n content: string | ContentBlock[]\n}\n\ntype ContentBlock =\n | { type: 'text'; text: string }\n | { type: 'image_url'; image_url: { url: string } }\n\ntype ChatResponse = {\n choices: Array<{\n message: { role: string; content: string | null }\n }>\n}\n\nfunction isChatResponse(value: unknown): value is ChatResponse {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'choices' in value &&\n Array.isArray((value as ChatResponse).choices)\n )\n}\n\nfunction detectMediaType(buffer: Buffer): string {\n if (buffer[0] === 0x89 && buffer[1] === 0x50) return 'image/png'\n if (buffer[0] === 0x47 && buffer[1] === 0x49) return 'image/gif'\n if (buffer[0] === 0x52 && buffer[1] === 0x49) return 'image/webp'\n return 'image/jpeg'\n}\n\nexport type OllamaProviderConfig = {\n baseUrl?: string\n model?: string\n apiKey?: string\n}\n\nexport function createOllamaProvider(config: OllamaProviderConfig): AIProvider {\n const baseUrl = (config.baseUrl ?? 'http://localhost:11434').replace(/\\/+$/, '')\n const model = config.model ?? 'llama3.3:8b'\n const apiKey = config.apiKey ?? 'ollama'\n\n async function callAPI(messages: ChatMessage[], jsonMode: boolean): Promise<ChatResponse> {\n const body: Record<string, unknown> = { model, messages }\n if (jsonMode) {\n body.response_format = { type: 'json_object' }\n }\n\n const response = await fetch(`${baseUrl}/v1/chat/completions`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${apiKey}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(`Ollama API error ${response.status}: ${errorText}`)\n }\n\n const data: unknown = await response.json()\n if (!isChatResponse(data)) {\n throw new Error('Unexpected API response shape from local LLM server')\n }\n return data\n }\n\n return {\n async generate(prompt: string, _outputSchema: Record<string, unknown>): Promise<unknown[]> {\n const messages: ChatMessage[] = [\n {\n role: 'system',\n content:\n 'You are a data generation assistant. Always respond with valid JSON only. When asked for an array, wrap it in {\"items\": [...]} so json_object mode is satisfied.',\n },\n {\n role: 'user',\n content: `${prompt}\\n\\nRespond with JSON object {\"items\": [...]} where items is the array of generated documents.`,\n },\n ]\n\n const data = await callAPI(messages, true)\n const choice = data.choices[0]\n if (!choice || choice.message.content === null) {\n throw new Error('No content in response from local LLM server')\n }\n\n const parsed: unknown = JSON.parse(choice.message.content)\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n 'items' in parsed &&\n Array.isArray((parsed as { items: unknown }).items)\n ) {\n return (parsed as { items: unknown[] }).items\n }\n if (Array.isArray(parsed)) {\n return parsed\n }\n throw new Error('Local LLM response is not a JSON array')\n },\n\n async analyzeImage(imageBuffer: Buffer): Promise<string> {\n const base64 = imageBuffer.toString('base64')\n const mediaType = detectMediaType(imageBuffer)\n const dataUrl = `data:${mediaType};base64,${base64}`\n\n const messages: ChatMessage[] = [\n {\n role: 'user',\n content: [\n { type: 'image_url', image_url: { url: dataUrl } },\n {\n type: 'text',\n text: 'Describe this image concisely for use as alt text. Focus on the main subject and important visual details. Respond with only the alt text description, no extra explanation.',\n },\n ],\n },\n ]\n\n const data = await callAPI(messages, false)\n const choice = data.choices[0]\n if (!choice || choice.message.content === null) {\n throw new Error('No content in image analysis response from local LLM server')\n }\n return (choice.message.content as string).trim()\n },\n }\n}\n","import type { AIProvider } from '../../types.js'\n\ntype OpenAIMessage = {\n role: 'user' | 'assistant' | 'system'\n content: string | OpenAIContentBlock[]\n}\n\ntype OpenAIContentBlock =\n | { type: 'text'; text: string }\n | { type: 'image_url'; image_url: { url: string } }\n\ntype OpenAIResponse = {\n choices: Array<{\n message: { role: string; content: string | null }\n }>\n usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number }\n}\n\nfunction isOpenAIResponse(value: unknown): value is OpenAIResponse {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'choices' in value &&\n Array.isArray((value as OpenAIResponse).choices)\n )\n}\n\nexport type OpenAIProviderConfig = {\n apiKey: string\n model?: string\n baseUrl?: string\n}\n\nexport function createOpenAIProvider(configOrKey: OpenAIProviderConfig | string): AIProvider {\n const config: OpenAIProviderConfig =\n typeof configOrKey === 'string' ? { apiKey: configOrKey } : configOrKey\n const apiKey = config.apiKey\n const model = config.model ?? 'gpt-4o'\n const baseUrl = (config.baseUrl ?? 'https://api.openai.com').replace(/\\/+$/, '')\n\n async function callAPI(messages: OpenAIMessage[], jsonMode: boolean): Promise<OpenAIResponse> {\n const body: Record<string, unknown> = {\n model,\n messages,\n }\n if (jsonMode) {\n body.response_format = { type: 'json_object' }\n }\n\n const response = await fetch(`${baseUrl}/v1/chat/completions`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${apiKey}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(`OpenAI API error ${response.status}: ${errorText}`)\n }\n\n const data: unknown = await response.json()\n if (!isOpenAIResponse(data)) {\n throw new Error('Unexpected OpenAI API response shape')\n }\n return data\n }\n\n return {\n async generate(prompt: string, _outputSchema: Record<string, unknown>): Promise<unknown[]> {\n const messages: OpenAIMessage[] = [\n {\n role: 'system',\n content:\n 'You are a data generation assistant. Always respond with valid JSON only. When asked for an array, wrap it in {\"items\": [...]} so json_object mode is satisfied.',\n },\n {\n role: 'user',\n content: `${prompt}\\n\\nRespond with JSON object {\"items\": [...]} where items is the array of generated documents.`,\n },\n ]\n\n const data = await callAPI(messages, true)\n const choice = data.choices[0]\n if (!choice || choice.message.content === null) {\n throw new Error('No content in OpenAI response')\n }\n\n const parsed: unknown = JSON.parse(choice.message.content)\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n 'items' in parsed &&\n Array.isArray((parsed as { items: unknown }).items)\n ) {\n return (parsed as { items: unknown[] }).items\n }\n if (Array.isArray(parsed)) {\n return parsed\n }\n throw new Error('OpenAI response is not a JSON array')\n },\n\n async analyzeImage(imageBuffer: Buffer): Promise<string> {\n const base64 = imageBuffer.toString('base64')\n // Detect image type from buffer magic bytes\n let mediaType = 'image/jpeg'\n if (imageBuffer[0] === 0x89 && imageBuffer[1] === 0x50) mediaType = 'image/png'\n else if (imageBuffer[0] === 0x47 && imageBuffer[1] === 0x49) mediaType = 'image/gif'\n else if (imageBuffer[0] === 0x52 && imageBuffer[1] === 0x49) mediaType = 'image/webp'\n\n const dataUrl = `data:${mediaType};base64,${base64}`\n\n const messages: OpenAIMessage[] = [\n {\n role: 'user',\n content: [\n {\n type: 'image_url',\n image_url: { url: dataUrl },\n },\n {\n type: 'text',\n text: 'Describe this image concisely for use as alt text. Focus on the main subject and important visual details. Respond with only the alt text description, no extra explanation.',\n },\n ],\n },\n ]\n\n const data = await callAPI(messages, false)\n const choice = data.choices[0]\n if (!choice || choice.message.content === null) {\n throw new Error('No content in OpenAI image analysis response')\n }\n return choice.message.content.trim()\n },\n }\n}\n","import type { AIProvider } from '../../types.js'\nimport { createAnthropicProvider } from './anthropic.js'\nimport { createGeminiProvider } from './gemini.js'\nimport { createOllamaProvider } from './ollama.js'\nimport { createOpenAIProvider } from './openai.js'\n\nexport type { AIProvider }\n\nexport type ProviderConfig = {\n provider: 'anthropic' | 'openai' | 'gemini' | 'ollama'\n apiKey: string\n baseUrl?: string\n model?: string\n}\n\nexport function createProvider(config: ProviderConfig): AIProvider {\n switch (config.provider) {\n case 'anthropic':\n return createAnthropicProvider({\n apiKey: config.apiKey,\n model: config.model,\n baseUrl: config.baseUrl,\n })\n case 'openai':\n return createOpenAIProvider({\n apiKey: config.apiKey,\n model: config.model,\n baseUrl: config.baseUrl,\n })\n case 'gemini':\n return createGeminiProvider({\n apiKey: config.apiKey,\n model: config.model,\n baseUrl: config.baseUrl,\n })\n case 'ollama':\n return createOllamaProvider({\n baseUrl: config.baseUrl,\n model: config.model,\n apiKey: config.apiKey,\n })\n default: {\n const _exhaustive: never = config.provider\n throw new Error(`Unknown provider: ${String(_exhaustive)}`)\n }\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"content-generator-BcUxGqga.mjs","names":[],"sources":["../src/generate/block-generator.ts","../src/generate/richtext-generator.ts","../src/core/prompt-builder.ts","../src/core/content-generator.ts"],"sourcesContent":["import type { BlockSchema, FieldSchema } from '../types.js'\n\n/**\n * Block generation: turn a Blocks field definition into a JSON-Schema\n * discriminated union + prompt fragment, and validate generated values\n * back into shape.\n *\n * Blocks were previously marked SKIP across the pipeline. This module\n * lets prompt-builder + content-generator include them without forcing\n * any refactor of the existing switch-case: the top-level modules simply\n * call into this file when they see `field.type === 'blocks'`.\n */\n\n/** JSON-Schema fragment describing the allowed shape of one blocks field. */\nexport function blocksOutputSchema(field: FieldSchema): Record<string, unknown> {\n const blocks = field.blocks ?? []\n if (blocks.length === 0) return { type: 'array', items: {} }\n\n const oneOf = blocks.map((block) => ({\n type: 'object',\n required: ['blockType'],\n properties: {\n blockType: { type: 'string', const: block.slug },\n ...blockFieldProperties(block),\n },\n }))\n\n return {\n type: 'array',\n items: {\n oneOf,\n discriminator: { propertyName: 'blockType' },\n },\n }\n}\n\nfunction blockFieldProperties(block: BlockSchema): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const f of block.fields) {\n // Nested blocks/relationship/upload/richText: describe as string|object\n // for the LLM — full recursive lowering is out-of-scope for the first\n // cut (see roadmap).\n if (f.type === 'relationship' || f.type === 'upload') continue\n if (f.type === 'richText') {\n out[f.name] = { type: 'string' }\n continue\n }\n if (f.type === 'blocks') {\n out[f.name] = blocksOutputSchema(f)\n continue\n }\n if (f.type === 'array') {\n out[f.name] = { type: 'array', items: { type: 'object' } }\n continue\n }\n if (f.type === 'group') {\n out[f.name] = { type: 'object' }\n continue\n }\n if (f.type === 'select' || f.type === 'radio') {\n const values = (f.options ?? []).map((o) => o.value)\n out[f.name] = { type: 'string', enum: values }\n continue\n }\n if (f.type === 'number') {\n out[f.name] = { type: 'number' }\n continue\n }\n if (f.type === 'checkbox') {\n out[f.name] = { type: 'boolean' }\n continue\n }\n if (f.type === 'date') {\n out[f.name] = { type: 'string', format: 'date-time' }\n continue\n }\n out[f.name] = { type: 'string' }\n }\n return out\n}\n\n/** Prompt fragment describing a blocks field and its available block catalog. */\nexport function describeBlocksField(field: FieldSchema): string {\n const blocks = field.blocks ?? []\n if (blocks.length === 0) {\n return `- \"${field.name}\" (blocks${\n field.required ? ' (required)' : ' (optional)'\n }): empty block catalog — omit`\n }\n\n const lines: string[] = [\n `- \"${field.name}\" (blocks${\n field.required ? ' (required)' : ' (optional)'\n }): array of block objects. Each object MUST include a \"blockType\" discriminator string.`,\n ' Available block types:',\n ]\n\n for (const block of blocks) {\n const label = block.label ? ` (${block.label})` : ''\n const desc = block.description ? ` — ${block.description}` : ''\n lines.push(` • blockType: \"${block.slug}\"${label}${desc}`)\n for (const f of block.fields) {\n const required = f.required ? ' (required)' : ''\n const suffix = describeBlockFieldInline(f)\n lines.push(` - ${f.name} (${f.type}${required})${suffix}`)\n }\n if (block.requiredFields && block.requiredFields.length > 0) {\n lines.push(` required: ${block.requiredFields.join(', ')}`)\n }\n }\n\n lines.push(\n ` Generate 2–5 blocks forming a coherent layout; mix block types if the theme permits.`,\n )\n return lines.join('\\n')\n}\n\nfunction describeBlockFieldInline(f: FieldSchema): string {\n if (f.type === 'select' || f.type === 'radio') {\n const values = (f.options ?? []).map((o) => `\"${o.value}\"`).join(', ')\n return values ? ` — one of [${values}]` : ''\n }\n if (f.type === 'relationship') return ' — use an existing ID from related collections'\n if (f.type === 'upload') return ' — omit; media is attached separately'\n if (f.type === 'richText') return ' — plain text paragraph'\n if (f.type === 'number') return ' — numeric value'\n if (f.type === 'checkbox') return ' — boolean'\n if (f.type === 'date') return ' — ISO 8601 timestamp'\n if (f.type === 'blocks') return ' — nested block array'\n if (f.type === 'array') return ' — array of sub-objects'\n if (f.type === 'group') return ' — object of sub-fields'\n return ''\n}\n\nexport type BlockValidationIssue = { path: string; message: string }\n\n/**\n * Validate a generated blocks array against the schema. Drops blocks with\n * unknown blockType; reports missing required fields per block.\n * Returns { valid, issues } — `valid` is a cleaned array safe to persist.\n */\nexport function validateBlocks(\n value: unknown,\n field: FieldSchema,\n): { valid: Array<Record<string, unknown>>; issues: BlockValidationIssue[] } {\n const issues: BlockValidationIssue[] = []\n if (!Array.isArray(value)) {\n if (value === undefined || value === null) return { valid: [], issues }\n return {\n valid: [],\n issues: [{ path: field.name, message: 'expected array of blocks' }],\n }\n }\n\n const catalog = new Map((field.blocks ?? []).map((b) => [b.slug, b]))\n const valid: Array<Record<string, unknown>> = []\n\n value.forEach((item, idx) => {\n if (typeof item !== 'object' || item === null || Array.isArray(item)) {\n issues.push({ path: `${field.name}[${idx}]`, message: 'block must be an object' })\n return\n }\n const obj = item as Record<string, unknown>\n const blockType = obj.blockType\n if (typeof blockType !== 'string') {\n issues.push({\n path: `${field.name}[${idx}]`,\n message: 'missing blockType discriminator',\n })\n return\n }\n const block = catalog.get(blockType)\n if (!block) {\n issues.push({\n path: `${field.name}[${idx}]`,\n message: `unknown blockType \"${blockType}\" — valid: ${[...catalog.keys()].join(', ')}`,\n })\n return\n }\n const missing = (block.requiredFields ?? []).filter((name) => {\n const v = obj[name]\n return v === undefined || v === null || v === ''\n })\n if (missing.length > 0) {\n issues.push({\n path: `${field.name}[${idx}]`,\n message: `${blockType} missing required: ${missing.join(', ')}`,\n })\n return\n }\n valid.push(obj)\n })\n\n return { valid, issues }\n}\n","/** Lexical rich text node types */\n\nexport type LexicalTextNode = {\n detail: number\n format: number\n mode: 'normal'\n style: string\n text: string\n type: 'text'\n version: 1\n}\n\nexport type LexicalParagraph = {\n children: LexicalTextNode[]\n direction: 'ltr'\n format: ''\n indent: 0\n type: 'paragraph'\n version: 1\n}\n\nexport type LexicalHeading = {\n children: LexicalTextNode[]\n direction: 'ltr'\n format: ''\n indent: 0\n type: 'heading'\n tag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'\n version: 1\n}\n\nexport type LexicalListItem = {\n children: LexicalTextNode[]\n direction: 'ltr'\n format: ''\n indent: 0\n type: 'listitem'\n value: number\n version: 1\n}\n\nexport type LexicalList = {\n children: LexicalListItem[]\n direction: 'ltr'\n format: ''\n indent: 0\n type: 'list'\n listType: 'bullet' | 'number'\n tag: 'ul' | 'ol'\n version: 1\n}\n\nexport type LexicalNode =\n | LexicalParagraph\n | LexicalHeading\n | LexicalList\n | LexicalListItem\n | LexicalTextNode\n\nexport type LexicalRoot = {\n root: {\n children: Array<LexicalParagraph | LexicalHeading | LexicalList>\n direction: 'ltr'\n format: ''\n indent: 0\n type: 'root'\n version: 1\n }\n}\n\nfunction makeTextNode(text: string, format = 0): LexicalTextNode {\n return {\n detail: 0,\n format,\n mode: 'normal',\n style: '',\n text,\n type: 'text',\n version: 1,\n }\n}\n\nfunction makeParagraph(text: string): LexicalParagraph {\n return {\n children: text.length > 0 ? [makeTextNode(text)] : [],\n direction: 'ltr',\n format: '',\n indent: 0,\n type: 'paragraph',\n version: 1,\n }\n}\n\nfunction makeHeading(text: string, tag: LexicalHeading['tag'] = 'h2'): LexicalHeading {\n return {\n children: [makeTextNode(text)],\n direction: 'ltr',\n format: '',\n indent: 0,\n type: 'heading',\n tag,\n version: 1,\n }\n}\n\nfunction makeList(items: string[], listType: 'bullet' | 'number' = 'bullet'): LexicalList {\n return {\n children: items.map(\n (item, index): LexicalListItem => ({\n children: [makeTextNode(item)],\n direction: 'ltr',\n format: '',\n indent: 0,\n type: 'listitem',\n value: index + 1,\n version: 1,\n }),\n ),\n direction: 'ltr',\n format: '',\n indent: 0,\n type: 'list',\n listType,\n tag: listType === 'bullet' ? 'ul' : 'ol',\n version: 1,\n }\n}\n\n/**\n * Convert plain text to a Lexical root node.\n * Each non-empty line becomes a paragraph.\n */\nexport function textToLexical(text: string): LexicalRoot {\n const lines = text.split('\\n')\n const children = lines.map((line) => makeParagraph(line))\n\n return {\n root: {\n children,\n direction: 'ltr',\n format: '',\n indent: 0,\n type: 'root',\n version: 1,\n },\n }\n}\n\n/**\n * Convert structured content to a Lexical root node.\n * Sections may have an optional heading, paragraphs, and bullet points.\n */\nexport function contentToLexical(content: {\n sections: Array<{\n heading?: string\n paragraphs: string[]\n bulletPoints?: string[]\n }>\n}): LexicalRoot {\n const children: Array<LexicalParagraph | LexicalHeading | LexicalList> = []\n\n for (const section of content.sections) {\n if (section.heading) {\n children.push(makeHeading(section.heading, 'h2'))\n }\n\n for (const paragraph of section.paragraphs) {\n children.push(makeParagraph(paragraph))\n }\n\n if (section.bulletPoints && section.bulletPoints.length > 0) {\n children.push(makeList(section.bulletPoints, 'bullet'))\n }\n }\n\n return {\n root: {\n children,\n direction: 'ltr',\n format: '',\n indent: 0,\n type: 'root',\n version: 1,\n },\n }\n}\n","import { blocksOutputSchema, describeBlocksField } from '../generate/block-generator.js'\nimport type { CollectionSchema, FieldSchema } from '../types.js'\nimport type { FieldAdapterRegistry } from './field-adapters.js'\n\nexport type GenerationContext = {\n count: number\n theme?: string\n locale?: string\n existingIds?: Record<string, string[]>\n /** Optional domain framing (e.g. \"blog\", \"news site\", \"SaaS app\"). If\n * omitted, the generic \"ecommerce platform\" phrasing is preserved for\n * backwards compatibility. Set to empty string to drop domain framing. */\n domain?: string\n /** Opt-in: generate Blocks fields using the block catalog instead of\n * skipping them. Defaults to false to preserve existing behavior. */\n includeBlocks?: boolean\n /** Optional registry of FieldTypeAdapters for custom field types. When\n * a field.type isn't recognized by the built-in switch, the registry\n * is consulted. If it too has no adapter, a generic fallback is used. */\n adapters?: FieldAdapterRegistry\n}\n\nfunction detectHeadingLevels(features: string[]): number[] {\n const levels = new Set<number>()\n for (const f of features) {\n const m = f.match(/h([1-6])/i)\n if (m) levels.add(Number.parseInt(m[1], 10))\n }\n // Also match a generic \"heading\" feature → allow h2–h4 as a sensible default\n if (features.some((f) => /heading/i.test(f) && !/h[1-6]/i.test(f))) {\n levels.add(2)\n levels.add(3)\n levels.add(4)\n }\n return [...levels].sort((a, b) => a - b)\n}\n\nfunction describeField(\n field: FieldSchema,\n existingIds?: Record<string, string[]>,\n includeBlocks = false,\n adapters?: FieldAdapterRegistry,\n domain?: string,\n): string {\n const lines: string[] = []\n const required = field.required ? ' (required)' : ' (optional)'\n\n switch (field.type) {\n case 'text':\n case 'textarea':\n case 'email':\n lines.push(\n `- \"${field.name}\" (${field.type}${required}): Generate realistic ${field.type} content`,\n )\n break\n\n case 'number':\n lines.push(\n `- \"${field.name}\" (number${required}): Generate a realistic numeric value (e.g. price 1–999, quantity 1–100, rating 1–5)`,\n )\n break\n\n case 'checkbox':\n lines.push(`- \"${field.name}\" (boolean${required}): true or false`)\n break\n\n case 'date':\n lines.push(\n `- \"${field.name}\" (date${required}): ISO 8601 date string (e.g. \"2024-06-15T10:00:00.000Z\")`,\n )\n break\n\n case 'select': {\n const values = (field.options ?? []).map((o) => `\"${o.value}\"`).join(', ')\n lines.push(`- \"${field.name}\" (select${required}): Must be one of [${values}]`)\n break\n }\n\n case 'relationship': {\n const collections = Array.isArray(field.relationTo)\n ? field.relationTo\n : [field.relationTo ?? '']\n const idLists = collections.map((col) => {\n const ids = existingIds?.[col] ?? []\n return ids.length > 0\n ? `${col}: [${ids.map((id) => `\"${id}\"`).join(', ')}]`\n : `${col}: (no existing IDs available — omit this field)`\n })\n const hasIds = collections.some((col) => (existingIds?.[col] ?? []).length > 0)\n if (hasIds) {\n lines.push(\n `- \"${field.name}\" (relationship${required}): Pick from existing IDs — ${idLists.join('; ')}${field.hasMany ? ' (can be an array of IDs)' : ' (single ID string)'}`,\n )\n } else {\n lines.push(`- \"${field.name}\" (relationship${required}): SKIP — no existing IDs available`)\n }\n break\n }\n\n case 'richText': {\n const features = field.lexicalFeatures ?? []\n if (features.length > 0) {\n const headingLevels = detectHeadingLevels(features)\n const allowLists = features.some((f) => /list|bullet|number|ordered/i.test(f))\n const capLines: string[] = []\n if (headingLevels.length > 0) {\n capLines.push(`headings (${headingLevels.map((l) => `h${l}`).join(', ')})`)\n }\n if (allowLists) capLines.push('bullet/numbered lists')\n capLines.push('paragraphs')\n lines.push(\n `- \"${field.name}\" (richtext${required}): Return a structured object ` +\n `{\"sections\":[{\"heading\":\"optional\",\"paragraphs\":[\"...\"],\"bulletPoints\":[\"...\"]}]}. ` +\n `The editor supports: ${capLines.join(', ')}. Keep each section short (1–3 paragraphs).`,\n )\n } else {\n lines.push(\n `- \"${field.name}\" (richtext${required}): Return PLAIN TEXT only (do not wrap in Lexical/JSON — the system will convert it). Write 1–3 sentences of realistic content.`,\n )\n }\n break\n }\n\n case 'upload':\n lines.push(`- \"${field.name}\" (upload${required}): SKIP — handled separately`)\n break\n\n case 'array':\n case 'group':\n if (field.fields && field.fields.length > 0) {\n lines.push(`- \"${field.name}\" (${field.type}${required}):`)\n for (const subField of field.fields) {\n lines.push(` ${describeField(subField, existingIds, includeBlocks, adapters, domain)}`)\n }\n }\n break\n\n case 'blocks':\n if (includeBlocks) {\n lines.push(describeBlocksField(field))\n } else {\n lines.push(`- \"${field.name}\" (blocks${required}): SKIP — complex layout field, omit`)\n }\n break\n\n default: {\n // Consult the FieldTypeAdapter registry for custom types before\n // falling back to the generic description.\n const adapter = adapters?.get(field.type)\n if (adapter?.describe) {\n const fragment = adapter.describe(field, { existingIds, domain })\n if (fragment !== null) lines.push(fragment)\n break\n }\n lines.push(`- \"${field.name}\" (${field.type}${required}): Generate appropriate content`)\n }\n }\n\n return lines.join('\\n')\n}\n\nexport function buildGenerationPrompt(\n schema: CollectionSchema,\n context: GenerationContext,\n): string {\n const fieldDescriptions = schema.fields\n .map((f) =>\n describeField(\n f,\n context.existingIds,\n context.includeBlocks ?? false,\n context.adapters,\n context.domain,\n ),\n )\n .join('\\n')\n\n const requiredNote =\n schema.requiredFields.length > 0\n ? `\\nRequired fields (must be present): ${schema.requiredFields.map((f) => `\"${f}\"`).join(', ')}`\n : ''\n\n const themeNote = context.theme ? `\\nContent theme/style: ${context.theme}` : ''\n\n const localeNote =\n context.locale && context.locale !== 'en'\n ? `\\nGenerate content in locale: ${context.locale}`\n : ''\n\n // Domain framing: default is the historical \"ecommerce platform\" string.\n // Passing context.domain=\"\" drops the phrase; passing any other string\n // substitutes it. This preserves current behavior when domain is undefined.\n const domain =\n context.domain === undefined\n ? 'an ecommerce platform'\n : context.domain.trim().length === 0\n ? ''\n : context.domain\n const domainNote = domain ? ` appropriate for ${domain}` : ''\n\n return `Generate ${context.count} realistic document(s) for the \"${schema.slug}\" collection.\n${themeNote}${localeNote}${requiredNote}\n\nFields to generate:\n${fieldDescriptions}\n\nRules:\n- Return a JSON array with exactly ${context.count} item(s)\n- Each item must be a flat JSON object with field names as keys\n- Skip fields marked as SKIP\n- For richtext fields: return plain text strings only\n- For relationship fields: use the provided existing IDs exactly as shown\n- Do not include extra fields not listed above\n- Generate varied, realistic content${domainNote}`\n}\n\nexport function buildOutputSchema(\n schema: CollectionSchema,\n options?: { includeBlocks?: boolean; adapters?: FieldAdapterRegistry },\n): Record<string, unknown> {\n const properties: Record<string, unknown> = {}\n const required: string[] = []\n const includeBlocks = options?.includeBlocks === true\n const adapters = options?.adapters\n\n for (const field of schema.fields) {\n if (field.type === 'blocks') {\n if (!includeBlocks) continue\n properties[field.name] = blocksOutputSchema(field)\n if (field.required) required.push(field.name)\n continue\n }\n\n // Skip fields handled separately\n if (['relationship', 'richText', 'upload'].includes(field.type)) {\n continue\n }\n\n let fieldSchema: Record<string, unknown>\n\n switch (field.type) {\n case 'text':\n case 'textarea':\n case 'email':\n case 'date':\n fieldSchema = { type: 'string' }\n break\n\n case 'number':\n fieldSchema = { type: 'number' }\n break\n\n case 'checkbox':\n fieldSchema = { type: 'boolean' }\n break\n\n case 'select': {\n const enumValues = (field.options ?? []).map((o) => o.value)\n fieldSchema = { type: 'string', enum: enumValues }\n break\n }\n\n case 'array':\n fieldSchema = { type: 'array', items: { type: 'object' } }\n break\n\n case 'group':\n fieldSchema = { type: 'object' }\n break\n\n default: {\n // Route unknown types through the adapter registry first.\n const adapter = adapters?.get(field.type)\n const custom = adapter?.outputSchema?.(field)\n if (custom !== undefined) {\n if (custom === null) continue // adapter chose to exclude\n fieldSchema = custom\n break\n }\n fieldSchema = { type: 'string' }\n }\n }\n\n properties[field.name] = fieldSchema\n if (field.required) {\n required.push(field.name)\n }\n }\n\n return {\n type: 'object',\n properties: {\n items: {\n type: 'array',\n items: {\n type: 'object',\n properties,\n required,\n },\n },\n },\n }\n}\n","import { validateBlocks } from '../generate/block-generator.js'\nimport { contentToLexical, textToLexical } from '../generate/richtext-generator.js'\nimport type { AIProvider, CollectionSchema, FieldSchema } from '../types.js'\nimport type { FieldAdapterRegistry, ValidationIssue } from './field-adapters.js'\nimport type { GenerationContext } from './prompt-builder.js'\nimport { buildGenerationPrompt, buildOutputSchema } from './prompt-builder.js'\n\nexport type { GenerationContext }\n\nexport type GenerationResult = {\n documents: Record<string, unknown>[]\n tokensUsed?: number\n}\n\ntype ValidationError = {\n field: string\n message: string\n}\n\nfunction getSelectValues(field: FieldSchema): string[] {\n return (field.options ?? []).map((o) => o.value)\n}\n\n/**\n * Convert richText values (strings or {sections:[...]} objects) into Lexical\n * JSON. Other values pass through unchanged. This is defensive — if the AI\n * returns something already shaped like Lexical (has `root`), we keep it.\n */\nfunction convertRichTextValue(value: unknown): unknown {\n if (value === null || value === undefined) return value\n if (typeof value === 'string') return textToLexical(value)\n if (typeof value === 'object') {\n const v = value as Record<string, unknown>\n if (v.root !== undefined) return v\n if (Array.isArray((v as { sections?: unknown }).sections)) {\n return contentToLexical(v as Parameters<typeof contentToLexical>[0])\n }\n }\n return value\n}\n\n/** Walk the generated doc and convert every richText field to Lexical. */\nfunction applyRichTextPostprocess(\n doc: Record<string, unknown>,\n fields: FieldSchema[],\n): Record<string, unknown> {\n for (const field of fields) {\n if (!(field.name in doc)) continue\n if (field.type === 'richText') {\n doc[field.name] = convertRichTextValue(doc[field.name])\n continue\n }\n if (field.type === 'group' && field.fields && typeof doc[field.name] === 'object') {\n const sub = doc[field.name] as Record<string, unknown> | null\n if (sub && !Array.isArray(sub)) applyRichTextPostprocess(sub, field.fields)\n continue\n }\n if (field.type === 'array' && field.fields && Array.isArray(doc[field.name])) {\n for (const item of doc[field.name] as unknown[]) {\n if (item && typeof item === 'object' && !Array.isArray(item)) {\n applyRichTextPostprocess(item as Record<string, unknown>, field.fields)\n }\n }\n }\n }\n return doc\n}\n\nfunction validateDocument(\n doc: unknown,\n schema: CollectionSchema,\n options: { includeBlocks?: boolean; adapters?: FieldAdapterRegistry } = {},\n): ValidationError[] {\n const errors: ValidationError[] = []\n\n if (typeof doc !== 'object' || doc === null || Array.isArray(doc)) {\n return [{ field: '_root', message: 'Document must be a plain object' }]\n }\n\n const record = doc as Record<string, unknown>\n\n // Check required fields\n for (const fieldName of schema.requiredFields) {\n const field = schema.fields.find((f) => f.name === fieldName)\n if (!field) continue\n // Skip fields that are handled separately\n if (['relationship', 'richText', 'upload'].includes(field.type)) continue\n if (field.type === 'blocks' && !options.includeBlocks) continue\n if (record[fieldName] === undefined || record[fieldName] === null || record[fieldName] === '') {\n errors.push({\n field: fieldName,\n message: `Required field \"${fieldName}\" is missing or empty`,\n })\n }\n }\n\n // Validate select field values\n for (const field of schema.fields) {\n if (field.type !== 'select') continue\n const value = record[field.name]\n if (value === undefined || value === null) continue\n const validValues = getSelectValues(field)\n if (validValues.length > 0 && !validValues.includes(String(value))) {\n errors.push({\n field: field.name,\n message: `Field \"${field.name}\" has invalid select value \"${String(value)}\". Must be one of: ${validValues.join(', ')}`,\n })\n }\n }\n\n // Validate blocks fields when opted-in\n if (options.includeBlocks) {\n for (const field of schema.fields) {\n if (field.type !== 'blocks') continue\n const value = record[field.name]\n if (value === undefined) continue\n const { valid, issues } = validateBlocks(value, field)\n // Replace the raw value with the cleaned, validated array so downstream\n // Payload .create() receives only valid blocks.\n record[field.name] = valid\n for (const issue of issues) {\n errors.push({ field: issue.path, message: issue.message })\n }\n }\n }\n\n // Custom FieldTypeAdapter validators\n if (options.adapters) {\n for (const field of schema.fields) {\n const adapter = options.adapters.get(field.type)\n if (!adapter?.validate) continue\n const value = record[field.name]\n if (value === undefined) continue\n const adapterIssues: ValidationIssue[] = adapter.validate(value, field)\n for (const issue of adapterIssues) {\n errors.push({ field: issue.field, message: issue.message })\n }\n }\n }\n\n return errors\n}\n\nfunction buildRetryPrompt(originalPrompt: string, errors: ValidationError[]): string {\n const errorList = errors.map((e) => `- ${e.field}: ${e.message}`).join('\\n')\n return `${originalPrompt}\n\nIMPORTANT: Your previous response had validation errors. Fix these issues:\n${errorList}\n\nReturn a corrected JSON array addressing all the errors above.`\n}\n\nexport async function generateDocuments(\n provider: AIProvider,\n schema: CollectionSchema,\n context: GenerationContext,\n options?: { maxRetries?: number },\n): Promise<GenerationResult> {\n const maxRetries = options?.maxRetries ?? 3\n const outputSchema = buildOutputSchema(schema, {\n includeBlocks: context.includeBlocks === true,\n adapters: context.adapters,\n })\n\n let prompt = buildGenerationPrompt(schema, context)\n let lastError: Error | null = null\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n let rawItems: unknown[]\n\n try {\n rawItems = await provider.generate(prompt, outputSchema)\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err))\n if (attempt < maxRetries) {\n prompt = buildRetryPrompt(buildGenerationPrompt(schema, context), [\n { field: '_generation', message: `API error: ${lastError.message}` },\n ])\n continue\n }\n throw lastError\n }\n\n // Validate all documents\n const allErrors: ValidationError[] = []\n const validDocuments: Record<string, unknown>[] = []\n\n for (const item of rawItems) {\n const errors = validateDocument(item, schema, {\n includeBlocks: context.includeBlocks === true,\n adapters: context.adapters,\n })\n if (errors.length > 0) {\n allErrors.push(...errors)\n } else {\n validDocuments.push(item as Record<string, unknown>)\n }\n }\n\n if (allErrors.length === 0) {\n return {\n documents: validDocuments.map((d) => applyRichTextPostprocess(d, schema.fields)),\n }\n }\n\n // Validation failed — retry if attempts remain\n if (attempt < maxRetries) {\n prompt = buildRetryPrompt(buildGenerationPrompt(schema, context), allErrors)\n lastError = new Error(`Validation failed: ${allErrors.map((e) => e.message).join('; ')}`)\n continue\n }\n\n // Out of retries — return what we have if any valid docs, otherwise throw\n if (validDocuments.length > 0) {\n return {\n documents: validDocuments.map((d) => applyRichTextPostprocess(d, schema.fields)),\n }\n }\n\n throw new Error(\n `Content generation failed after ${maxRetries} retries. Last errors: ${allErrors.map((e) => e.message).join('; ')}`,\n )\n }\n\n throw lastError ?? new Error('Content generation failed')\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"content-generator-BcUxGqga.mjs","names":[],"sources":["../src/generate/block-generator.ts","../src/generate/richtext-generator.ts","../src/core/prompt-builder.ts","../src/core/content-generator.ts"],"sourcesContent":["import type { BlockSchema, FieldSchema } from '../types.js'\n\n/**\n * Block generation: turn a Blocks field definition into a JSON-Schema\n * discriminated union + prompt fragment, and validate generated values\n * back into shape.\n *\n * Blocks were previously marked SKIP across the pipeline. This module\n * lets prompt-builder + content-generator include them without forcing\n * any refactor of the existing switch-case: the top-level modules simply\n * call into this file when they see `field.type === 'blocks'`.\n */\n\n/** JSON-Schema fragment describing the allowed shape of one blocks field. */\nexport function blocksOutputSchema(field: FieldSchema): Record<string, unknown> {\n const blocks = field.blocks ?? []\n if (blocks.length === 0) return { type: 'array', items: {} }\n\n const oneOf = blocks.map((block) => ({\n type: 'object',\n required: ['blockType'],\n properties: {\n blockType: { type: 'string', const: block.slug },\n ...blockFieldProperties(block),\n },\n }))\n\n return {\n type: 'array',\n items: {\n oneOf,\n discriminator: { propertyName: 'blockType' },\n },\n }\n}\n\nfunction blockFieldProperties(block: BlockSchema): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const f of block.fields) {\n // Nested blocks/relationship/upload/richText: describe as string|object\n // for the LLM — full recursive lowering is out-of-scope for the first\n // cut (see roadmap).\n if (f.type === 'relationship' || f.type === 'upload') continue\n if (f.type === 'richText') {\n out[f.name] = { type: 'string' }\n continue\n }\n if (f.type === 'blocks') {\n out[f.name] = blocksOutputSchema(f)\n continue\n }\n if (f.type === 'array') {\n out[f.name] = { type: 'array', items: { type: 'object' } }\n continue\n }\n if (f.type === 'group') {\n out[f.name] = { type: 'object' }\n continue\n }\n if (f.type === 'select' || f.type === 'radio') {\n const values = (f.options ?? []).map((o) => o.value)\n out[f.name] = { type: 'string', enum: values }\n continue\n }\n if (f.type === 'number') {\n out[f.name] = { type: 'number' }\n continue\n }\n if (f.type === 'checkbox') {\n out[f.name] = { type: 'boolean' }\n continue\n }\n if (f.type === 'date') {\n out[f.name] = { type: 'string', format: 'date-time' }\n continue\n }\n out[f.name] = { type: 'string' }\n }\n return out\n}\n\n/** Prompt fragment describing a blocks field and its available block catalog. */\nexport function describeBlocksField(field: FieldSchema): string {\n const blocks = field.blocks ?? []\n if (blocks.length === 0) {\n return `- \"${field.name}\" (blocks${\n field.required ? ' (required)' : ' (optional)'\n }): empty block catalog — omit`\n }\n\n const lines: string[] = [\n `- \"${field.name}\" (blocks${\n field.required ? ' (required)' : ' (optional)'\n }): array of block objects. Each object MUST include a \"blockType\" discriminator string.`,\n ' Available block types:',\n ]\n\n for (const block of blocks) {\n const label = block.label ? ` (${block.label})` : ''\n const desc = block.description ? ` — ${block.description}` : ''\n lines.push(` • blockType: \"${block.slug}\"${label}${desc}`)\n for (const f of block.fields) {\n const required = f.required ? ' (required)' : ''\n const suffix = describeBlockFieldInline(f)\n lines.push(` - ${f.name} (${f.type}${required})${suffix}`)\n }\n if (block.requiredFields && block.requiredFields.length > 0) {\n lines.push(` required: ${block.requiredFields.join(', ')}`)\n }\n }\n\n lines.push(\n ` Generate 2–5 blocks forming a coherent layout; mix block types if the theme permits.`,\n )\n return lines.join('\\n')\n}\n\nfunction describeBlockFieldInline(f: FieldSchema): string {\n if (f.type === 'select' || f.type === 'radio') {\n const values = (f.options ?? []).map((o) => `\"${o.value}\"`).join(', ')\n return values ? ` — one of [${values}]` : ''\n }\n if (f.type === 'relationship') return ' — use an existing ID from related collections'\n if (f.type === 'upload') return ' — omit; media is attached separately'\n if (f.type === 'richText') return ' — plain text paragraph'\n if (f.type === 'number') return ' — numeric value'\n if (f.type === 'checkbox') return ' — boolean'\n if (f.type === 'date') return ' — ISO 8601 timestamp'\n if (f.type === 'blocks') return ' — nested block array'\n if (f.type === 'array') return ' — array of sub-objects'\n if (f.type === 'group') return ' — object of sub-fields'\n return ''\n}\n\nexport type BlockValidationIssue = { path: string; message: string }\n\n/**\n * Validate a generated blocks array against the schema. Drops blocks with\n * unknown blockType; reports missing required fields per block.\n * Returns { valid, issues } — `valid` is a cleaned array safe to persist.\n */\nexport function validateBlocks(\n value: unknown,\n field: FieldSchema,\n): { valid: Array<Record<string, unknown>>; issues: BlockValidationIssue[] } {\n const issues: BlockValidationIssue[] = []\n if (!Array.isArray(value)) {\n if (value === undefined || value === null) return { valid: [], issues }\n return {\n valid: [],\n issues: [{ path: field.name, message: 'expected array of blocks' }],\n }\n }\n\n const catalog = new Map((field.blocks ?? []).map((b) => [b.slug, b]))\n const valid: Array<Record<string, unknown>> = []\n\n value.forEach((item, idx) => {\n if (typeof item !== 'object' || item === null || Array.isArray(item)) {\n issues.push({ path: `${field.name}[${idx}]`, message: 'block must be an object' })\n return\n }\n const obj = item as Record<string, unknown>\n const blockType = obj.blockType\n if (typeof blockType !== 'string') {\n issues.push({\n path: `${field.name}[${idx}]`,\n message: 'missing blockType discriminator',\n })\n return\n }\n const block = catalog.get(blockType)\n if (!block) {\n issues.push({\n path: `${field.name}[${idx}]`,\n message: `unknown blockType \"${blockType}\" — valid: ${[...catalog.keys()].join(', ')}`,\n })\n return\n }\n const missing = (block.requiredFields ?? []).filter((name) => {\n const v = obj[name]\n return v === undefined || v === null || v === ''\n })\n if (missing.length > 0) {\n issues.push({\n path: `${field.name}[${idx}]`,\n message: `${blockType} missing required: ${missing.join(', ')}`,\n })\n return\n }\n valid.push(obj)\n })\n\n return { valid, issues }\n}\n","/** Lexical rich text node types */\n\nexport type LexicalTextNode = {\n detail: number\n format: number\n mode: 'normal'\n style: string\n text: string\n type: 'text'\n version: 1\n}\n\nexport type LexicalParagraph = {\n children: LexicalTextNode[]\n direction: 'ltr'\n format: ''\n indent: 0\n type: 'paragraph'\n version: 1\n}\n\nexport type LexicalHeading = {\n children: LexicalTextNode[]\n direction: 'ltr'\n format: ''\n indent: 0\n type: 'heading'\n tag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'\n version: 1\n}\n\nexport type LexicalListItem = {\n children: LexicalTextNode[]\n direction: 'ltr'\n format: ''\n indent: 0\n type: 'listitem'\n value: number\n version: 1\n}\n\nexport type LexicalList = {\n children: LexicalListItem[]\n direction: 'ltr'\n format: ''\n indent: 0\n type: 'list'\n listType: 'bullet' | 'number'\n tag: 'ul' | 'ol'\n version: 1\n}\n\nexport type LexicalNode =\n | LexicalParagraph\n | LexicalHeading\n | LexicalList\n | LexicalListItem\n | LexicalTextNode\n\nexport type LexicalRoot = {\n root: {\n children: Array<LexicalParagraph | LexicalHeading | LexicalList>\n direction: 'ltr'\n format: ''\n indent: 0\n type: 'root'\n version: 1\n }\n}\n\nfunction makeTextNode(text: string, format = 0): LexicalTextNode {\n return {\n detail: 0,\n format,\n mode: 'normal',\n style: '',\n text,\n type: 'text',\n version: 1,\n }\n}\n\nfunction makeParagraph(text: string): LexicalParagraph {\n return {\n children: text.length > 0 ? [makeTextNode(text)] : [],\n direction: 'ltr',\n format: '',\n indent: 0,\n type: 'paragraph',\n version: 1,\n }\n}\n\nfunction makeHeading(text: string, tag: LexicalHeading['tag'] = 'h2'): LexicalHeading {\n return {\n children: [makeTextNode(text)],\n direction: 'ltr',\n format: '',\n indent: 0,\n type: 'heading',\n tag,\n version: 1,\n }\n}\n\nfunction makeList(items: string[], listType: 'bullet' | 'number' = 'bullet'): LexicalList {\n return {\n children: items.map(\n (item, index): LexicalListItem => ({\n children: [makeTextNode(item)],\n direction: 'ltr',\n format: '',\n indent: 0,\n type: 'listitem',\n value: index + 1,\n version: 1,\n }),\n ),\n direction: 'ltr',\n format: '',\n indent: 0,\n type: 'list',\n listType,\n tag: listType === 'bullet' ? 'ul' : 'ol',\n version: 1,\n }\n}\n\n/**\n * Convert plain text to a Lexical root node.\n * Each non-empty line becomes a paragraph.\n */\nexport function textToLexical(text: string): LexicalRoot {\n const lines = text.split('\\n')\n const children = lines.map((line) => makeParagraph(line))\n\n return {\n root: {\n children,\n direction: 'ltr',\n format: '',\n indent: 0,\n type: 'root',\n version: 1,\n },\n }\n}\n\n/**\n * Convert structured content to a Lexical root node.\n * Sections may have an optional heading, paragraphs, and bullet points.\n */\nexport function contentToLexical(content: {\n sections: Array<{\n heading?: string\n paragraphs: string[]\n bulletPoints?: string[]\n }>\n}): LexicalRoot {\n const children: Array<LexicalParagraph | LexicalHeading | LexicalList> = []\n\n for (const section of content.sections) {\n if (section.heading) {\n children.push(makeHeading(section.heading, 'h2'))\n }\n\n for (const paragraph of section.paragraphs) {\n children.push(makeParagraph(paragraph))\n }\n\n if (section.bulletPoints && section.bulletPoints.length > 0) {\n children.push(makeList(section.bulletPoints, 'bullet'))\n }\n }\n\n return {\n root: {\n children,\n direction: 'ltr',\n format: '',\n indent: 0,\n type: 'root',\n version: 1,\n },\n }\n}\n","import { blocksOutputSchema, describeBlocksField } from '../generate/block-generator.js'\nimport type { CollectionSchema, FieldSchema } from '../types.js'\nimport type { FieldAdapterRegistry } from './field-adapters.js'\n\nexport type GenerationContext = {\n count: number\n theme?: string\n locale?: string\n existingIds?: Record<string, string[]>\n /** Optional domain framing (e.g. \"blog\", \"news site\", \"SaaS app\"). If\n * omitted, the generic \"ecommerce platform\" phrasing is preserved for\n * backwards compatibility. Set to empty string to drop domain framing. */\n domain?: string\n /** Opt-in: generate Blocks fields using the block catalog instead of\n * skipping them. Defaults to false to preserve existing behavior. */\n includeBlocks?: boolean\n /** Optional registry of FieldTypeAdapters for custom field types. When\n * a field.type isn't recognized by the built-in switch, the registry\n * is consulted. If it too has no adapter, a generic fallback is used. */\n adapters?: FieldAdapterRegistry\n}\n\nfunction detectHeadingLevels(features: string[]): number[] {\n const levels = new Set<number>()\n for (const f of features) {\n const m = f.match(/h([1-6])/i)\n if (m) levels.add(Number.parseInt(m[1], 10))\n }\n // Also match a generic \"heading\" feature → allow h2–h4 as a sensible default\n if (features.some((f) => /heading/i.test(f) && !/h[1-6]/i.test(f))) {\n levels.add(2)\n levels.add(3)\n levels.add(4)\n }\n return [...levels].sort((a, b) => a - b)\n}\n\nfunction describeField(\n field: FieldSchema,\n existingIds?: Record<string, string[]>,\n includeBlocks = false,\n adapters?: FieldAdapterRegistry,\n domain?: string,\n): string {\n const lines: string[] = []\n const required = field.required ? ' (required)' : ' (optional)'\n\n switch (field.type) {\n case 'text':\n case 'textarea':\n case 'email':\n lines.push(\n `- \"${field.name}\" (${field.type}${required}): Generate realistic ${field.type} content`,\n )\n break\n\n case 'number':\n lines.push(\n `- \"${field.name}\" (number${required}): Generate a realistic numeric value (e.g. price 1–999, quantity 1–100, rating 1–5)`,\n )\n break\n\n case 'checkbox':\n lines.push(`- \"${field.name}\" (boolean${required}): true or false`)\n break\n\n case 'date':\n lines.push(\n `- \"${field.name}\" (date${required}): ISO 8601 date string (e.g. \"2024-06-15T10:00:00.000Z\")`,\n )\n break\n\n case 'select': {\n const values = (field.options ?? []).map((o) => `\"${o.value}\"`).join(', ')\n lines.push(`- \"${field.name}\" (select${required}): Must be one of [${values}]`)\n break\n }\n\n case 'relationship': {\n const collections = Array.isArray(field.relationTo)\n ? field.relationTo\n : [field.relationTo ?? '']\n const idLists = collections.map((col) => {\n const ids = existingIds?.[col] ?? []\n return ids.length > 0\n ? `${col}: [${ids.map((id) => `\"${id}\"`).join(', ')}]`\n : `${col}: (no existing IDs available — omit this field)`\n })\n const hasIds = collections.some((col) => (existingIds?.[col] ?? []).length > 0)\n if (hasIds) {\n lines.push(\n `- \"${field.name}\" (relationship${required}): Pick from existing IDs — ${idLists.join('; ')}${field.hasMany ? ' (can be an array of IDs)' : ' (single ID string)'}`,\n )\n } else {\n lines.push(`- \"${field.name}\" (relationship${required}): SKIP — no existing IDs available`)\n }\n break\n }\n\n case 'richText': {\n const features = field.lexicalFeatures ?? []\n if (features.length > 0) {\n const headingLevels = detectHeadingLevels(features)\n const allowLists = features.some((f) => /list|bullet|number|ordered/i.test(f))\n const capLines: string[] = []\n if (headingLevels.length > 0) {\n capLines.push(`headings (${headingLevels.map((l) => `h${l}`).join(', ')})`)\n }\n if (allowLists) capLines.push('bullet/numbered lists')\n capLines.push('paragraphs')\n lines.push(\n `- \"${field.name}\" (richtext${required}): Return a structured object ` +\n `{\"sections\":[{\"heading\":\"optional\",\"paragraphs\":[\"...\"],\"bulletPoints\":[\"...\"]}]}. ` +\n `The editor supports: ${capLines.join(', ')}. Keep each section short (1–3 paragraphs).`,\n )\n } else {\n lines.push(\n `- \"${field.name}\" (richtext${required}): Return PLAIN TEXT only (do not wrap in Lexical/JSON — the system will convert it). Write 1–3 sentences of realistic content.`,\n )\n }\n break\n }\n\n case 'upload':\n lines.push(`- \"${field.name}\" (upload${required}): SKIP — handled separately`)\n break\n\n case 'array':\n case 'group':\n if (field.fields && field.fields.length > 0) {\n lines.push(`- \"${field.name}\" (${field.type}${required}):`)\n for (const subField of field.fields) {\n lines.push(` ${describeField(subField, existingIds, includeBlocks, adapters, domain)}`)\n }\n }\n break\n\n case 'blocks':\n if (includeBlocks) {\n lines.push(describeBlocksField(field))\n } else {\n lines.push(`- \"${field.name}\" (blocks${required}): SKIP — complex layout field, omit`)\n }\n break\n\n default: {\n // Consult the FieldTypeAdapter registry for custom types before\n // falling back to the generic description.\n const adapter = adapters?.get(field.type)\n if (adapter?.describe) {\n const fragment = adapter.describe(field, { existingIds, domain })\n if (fragment !== null) lines.push(fragment)\n break\n }\n lines.push(`- \"${field.name}\" (${field.type}${required}): Generate appropriate content`)\n }\n }\n\n return lines.join('\\n')\n}\n\nexport function buildGenerationPrompt(\n schema: CollectionSchema,\n context: GenerationContext,\n): string {\n const fieldDescriptions = schema.fields\n .map((f) =>\n describeField(\n f,\n context.existingIds,\n context.includeBlocks ?? false,\n context.adapters,\n context.domain,\n ),\n )\n .join('\\n')\n\n const requiredNote =\n schema.requiredFields.length > 0\n ? `\\nRequired fields (must be present): ${schema.requiredFields.map((f) => `\"${f}\"`).join(', ')}`\n : ''\n\n const themeNote = context.theme ? `\\nContent theme/style: ${context.theme}` : ''\n\n const localeNote =\n context.locale && context.locale !== 'en'\n ? `\\nGenerate content in locale: ${context.locale}`\n : ''\n\n // Domain framing: default is the historical \"ecommerce platform\" string.\n // Passing context.domain=\"\" drops the phrase; passing any other string\n // substitutes it. This preserves current behavior when domain is undefined.\n const domain =\n context.domain === undefined\n ? 'an ecommerce platform'\n : context.domain.trim().length === 0\n ? ''\n : context.domain\n const domainNote = domain ? ` appropriate for ${domain}` : ''\n\n return `Generate ${context.count} realistic document(s) for the \"${schema.slug}\" collection.\n${themeNote}${localeNote}${requiredNote}\n\nFields to generate:\n${fieldDescriptions}\n\nRules:\n- Return a JSON array with exactly ${context.count} item(s)\n- Each item must be a flat JSON object with field names as keys\n- Skip fields marked as SKIP\n- For richtext fields: return plain text strings only\n- For relationship fields: use the provided existing IDs exactly as shown\n- Do not include extra fields not listed above\n- Generate varied, realistic content${domainNote}`\n}\n\nexport function buildOutputSchema(\n schema: CollectionSchema,\n options?: { includeBlocks?: boolean; adapters?: FieldAdapterRegistry },\n): Record<string, unknown> {\n const properties: Record<string, unknown> = {}\n const required: string[] = []\n const includeBlocks = options?.includeBlocks === true\n const adapters = options?.adapters\n\n for (const field of schema.fields) {\n if (field.type === 'blocks') {\n if (!includeBlocks) continue\n properties[field.name] = blocksOutputSchema(field)\n if (field.required) required.push(field.name)\n continue\n }\n\n // Skip fields handled separately\n if (['relationship', 'richText', 'upload'].includes(field.type)) {\n continue\n }\n\n let fieldSchema: Record<string, unknown>\n\n switch (field.type) {\n case 'text':\n case 'textarea':\n case 'email':\n case 'date':\n fieldSchema = { type: 'string' }\n break\n\n case 'number':\n fieldSchema = { type: 'number' }\n break\n\n case 'checkbox':\n fieldSchema = { type: 'boolean' }\n break\n\n case 'select': {\n const enumValues = (field.options ?? []).map((o) => o.value)\n fieldSchema = { type: 'string', enum: enumValues }\n break\n }\n\n case 'array':\n fieldSchema = { type: 'array', items: { type: 'object' } }\n break\n\n case 'group':\n fieldSchema = { type: 'object' }\n break\n\n default: {\n // Route unknown types through the adapter registry first.\n const adapter = adapters?.get(field.type)\n const custom = adapter?.outputSchema?.(field)\n if (custom !== undefined) {\n if (custom === null) continue // adapter chose to exclude\n fieldSchema = custom\n break\n }\n fieldSchema = { type: 'string' }\n }\n }\n\n properties[field.name] = fieldSchema\n if (field.required) {\n required.push(field.name)\n }\n }\n\n return {\n type: 'object',\n properties: {\n items: {\n type: 'array',\n items: {\n type: 'object',\n properties,\n required,\n },\n },\n },\n }\n}\n","import { validateBlocks } from '../generate/block-generator.js'\nimport { contentToLexical, textToLexical } from '../generate/richtext-generator.js'\nimport type { AIProvider, CollectionSchema, FieldSchema } from '../types.js'\nimport type { FieldAdapterRegistry, ValidationIssue } from './field-adapters.js'\nimport type { GenerationContext } from './prompt-builder.js'\nimport { buildGenerationPrompt, buildOutputSchema } from './prompt-builder.js'\n\nexport type { GenerationContext }\n\nexport type GenerationResult = {\n documents: Record<string, unknown>[]\n tokensUsed?: number\n}\n\ntype ValidationError = {\n field: string\n message: string\n}\n\nfunction getSelectValues(field: FieldSchema): string[] {\n return (field.options ?? []).map((o) => o.value)\n}\n\n/**\n * Convert richText values (strings or {sections:[...]} objects) into Lexical\n * JSON. Other values pass through unchanged. This is defensive — if the AI\n * returns something already shaped like Lexical (has `root`), we keep it.\n */\nfunction convertRichTextValue(value: unknown): unknown {\n if (value === null || value === undefined) return value\n if (typeof value === 'string') return textToLexical(value)\n if (typeof value === 'object') {\n const v = value as Record<string, unknown>\n if (v.root !== undefined) return v\n if (Array.isArray((v as { sections?: unknown }).sections)) {\n return contentToLexical(v as Parameters<typeof contentToLexical>[0])\n }\n }\n return value\n}\n\n/** Walk the generated doc and convert every richText field to Lexical. */\nfunction applyRichTextPostprocess(\n doc: Record<string, unknown>,\n fields: FieldSchema[],\n): Record<string, unknown> {\n for (const field of fields) {\n if (!(field.name in doc)) continue\n if (field.type === 'richText') {\n doc[field.name] = convertRichTextValue(doc[field.name])\n continue\n }\n if (field.type === 'group' && field.fields && typeof doc[field.name] === 'object') {\n const sub = doc[field.name] as Record<string, unknown> | null\n if (sub && !Array.isArray(sub)) applyRichTextPostprocess(sub, field.fields)\n continue\n }\n if (field.type === 'array' && field.fields && Array.isArray(doc[field.name])) {\n for (const item of doc[field.name] as unknown[]) {\n if (item && typeof item === 'object' && !Array.isArray(item)) {\n applyRichTextPostprocess(item as Record<string, unknown>, field.fields)\n }\n }\n }\n }\n return doc\n}\n\nfunction validateDocument(\n doc: unknown,\n schema: CollectionSchema,\n options: { includeBlocks?: boolean; adapters?: FieldAdapterRegistry } = {},\n): ValidationError[] {\n const errors: ValidationError[] = []\n\n if (typeof doc !== 'object' || doc === null || Array.isArray(doc)) {\n return [{ field: '_root', message: 'Document must be a plain object' }]\n }\n\n const record = doc as Record<string, unknown>\n\n // Check required fields\n for (const fieldName of schema.requiredFields) {\n const field = schema.fields.find((f) => f.name === fieldName)\n if (!field) continue\n // Skip fields that are handled separately\n if (['relationship', 'richText', 'upload'].includes(field.type)) continue\n if (field.type === 'blocks' && !options.includeBlocks) continue\n if (record[fieldName] === undefined || record[fieldName] === null || record[fieldName] === '') {\n errors.push({\n field: fieldName,\n message: `Required field \"${fieldName}\" is missing or empty`,\n })\n }\n }\n\n // Validate select field values\n for (const field of schema.fields) {\n if (field.type !== 'select') continue\n const value = record[field.name]\n if (value === undefined || value === null) continue\n const validValues = getSelectValues(field)\n if (validValues.length > 0 && !validValues.includes(String(value))) {\n errors.push({\n field: field.name,\n message: `Field \"${field.name}\" has invalid select value \"${String(value)}\". Must be one of: ${validValues.join(', ')}`,\n })\n }\n }\n\n // Validate blocks fields when opted-in\n if (options.includeBlocks) {\n for (const field of schema.fields) {\n if (field.type !== 'blocks') continue\n const value = record[field.name]\n if (value === undefined) continue\n const { valid, issues } = validateBlocks(value, field)\n // Replace the raw value with the cleaned, validated array so downstream\n // Payload .create() receives only valid blocks.\n record[field.name] = valid\n for (const issue of issues) {\n errors.push({ field: issue.path, message: issue.message })\n }\n }\n }\n\n // Custom FieldTypeAdapter validators\n if (options.adapters) {\n for (const field of schema.fields) {\n const adapter = options.adapters.get(field.type)\n if (!adapter?.validate) continue\n const value = record[field.name]\n if (value === undefined) continue\n const adapterIssues: ValidationIssue[] = adapter.validate(value, field)\n for (const issue of adapterIssues) {\n errors.push({ field: issue.field, message: issue.message })\n }\n }\n }\n\n return errors\n}\n\nfunction buildRetryPrompt(originalPrompt: string, errors: ValidationError[]): string {\n const errorList = errors.map((e) => `- ${e.field}: ${e.message}`).join('\\n')\n return `${originalPrompt}\n\nIMPORTANT: Your previous response had validation errors. Fix these issues:\n${errorList}\n\nReturn a corrected JSON array addressing all the errors above.`\n}\n\nexport async function generateDocuments(\n provider: AIProvider,\n schema: CollectionSchema,\n context: GenerationContext,\n options?: { maxRetries?: number },\n): Promise<GenerationResult> {\n const maxRetries = options?.maxRetries ?? 3\n const outputSchema = buildOutputSchema(schema, {\n includeBlocks: context.includeBlocks === true,\n adapters: context.adapters,\n })\n\n let prompt = buildGenerationPrompt(schema, context)\n let lastError: Error | null = null\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n let rawItems: unknown[]\n\n try {\n rawItems = await provider.generate(prompt, outputSchema)\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err))\n if (attempt < maxRetries) {\n prompt = buildRetryPrompt(buildGenerationPrompt(schema, context), [\n { field: '_generation', message: `API error: ${lastError.message}` },\n ])\n continue\n }\n throw lastError\n }\n\n // Validate all documents\n const allErrors: ValidationError[] = []\n const validDocuments: Record<string, unknown>[] = []\n\n for (const item of rawItems) {\n const errors = validateDocument(item, schema, {\n includeBlocks: context.includeBlocks === true,\n adapters: context.adapters,\n })\n if (errors.length > 0) {\n allErrors.push(...errors)\n } else {\n validDocuments.push(item as Record<string, unknown>)\n }\n }\n\n if (allErrors.length === 0) {\n return {\n documents: validDocuments.map((d) => applyRichTextPostprocess(d, schema.fields)),\n }\n }\n\n // Validation failed — retry if attempts remain\n if (attempt < maxRetries) {\n prompt = buildRetryPrompt(buildGenerationPrompt(schema, context), allErrors)\n lastError = new Error(`Validation failed: ${allErrors.map((e) => e.message).join('; ')}`)\n continue\n }\n\n // Out of retries — return what we have if any valid docs, otherwise throw\n if (validDocuments.length > 0) {\n return {\n documents: validDocuments.map((d) => applyRichTextPostprocess(d, schema.fields)),\n }\n }\n\n throw new Error(\n `Content generation failed after ${maxRetries} retries. Last errors: ${allErrors.map((e) => e.message).join('; ')}`,\n )\n }\n\n throw lastError ?? new Error('Content generation failed')\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/core/field-adapters.ts","../src/core/schema-manifest.ts","../src/mcp/prompts.ts","../src/mcp/resources.ts","../src/generate/deletion-log.ts","../src/generate/relationship-linker.ts","../src/orchestrate/concurrency.ts","../src/orchestrate/progress-tracker.ts","../src/orchestrate/bulk-runner.ts","../src/mcp/tools.ts","../src/admin/hooks/afterUpload.ts","../src/admin/hooks/beforeChange.ts","../src/plugin.ts"],"sourcesContent":["import type { FieldSchema } from '../types.js'\n\n/**\n * A FieldTypeAdapter is the extension seam for custom Payload field types.\n *\n * Every built-in type (text, number, select, relationship, blocks, ...) is\n * represented by an adapter. Downstream plugin users can register additional\n * adapters for custom field types (e.g. slug from @payloadcms/plugin-nested-docs,\n * code fields, geolocation) without modifying core.\n *\n * Adapters are looked up by field.type. If none matches, the core default\n * adapter is used.\n */\nexport type FieldTypeAdapter = {\n /** The Payload field.type string this adapter handles (e.g. 'text', 'slug') */\n type: string\n\n /**\n * Return a one-line prompt fragment describing this field to the AI.\n * Returning null signals \"skip this field — do not include it in the\n * generation prompt\" (used for blocks, upload, relationship which are\n * handled separately).\n */\n describe?: (field: FieldSchema, ctx: PromptContext) => string | null\n\n /**\n * Return a JSON-Schema fragment for this field, to be composed into the\n * top-level output schema. Returning null excludes the field from\n * structured output entirely.\n */\n outputSchema?: (field: FieldSchema) => Record<string, unknown> | null\n\n /**\n * Validate a raw generated value against the field. Return errors for\n * any problem found; empty array means valid.\n */\n validate?: (value: unknown, field: FieldSchema) => ValidationIssue[]\n\n /**\n * Optional: transform the raw generated value before persistence\n * (e.g. plain-text → Lexical JSON for richText fields).\n */\n postprocess?: (value: unknown, field: FieldSchema) => unknown\n}\n\nexport type PromptContext = {\n existingIds?: Record<string, string[]>\n /** Optional domain hint (\"ecommerce\", \"blog\", \"news site\") */\n domain?: string\n /** Optional theme */\n theme?: string\n}\n\nexport type ValidationIssue = {\n field: string\n message: string\n}\n\nexport type FieldAdapterRegistry = {\n get(type: string): FieldTypeAdapter | undefined\n register(adapter: FieldTypeAdapter): void\n list(): FieldTypeAdapter[]\n}\n\nexport function createRegistry(initial: FieldTypeAdapter[] = []): FieldAdapterRegistry {\n const table = new Map<string, FieldTypeAdapter>()\n for (const a of initial) table.set(a.type, a)\n\n return {\n get: (type) => table.get(type),\n register: (adapter) => {\n table.set(adapter.type, adapter)\n },\n list: () => [...table.values()],\n }\n}\n\n/** Helper: format the required/optional suffix used in describe() */\nexport function requiredSuffix(field: FieldSchema): string {\n return field.required ? ' (required)' : ' (optional)'\n}\n\n/** Helper: format field-level metadata hints as a trailing annotation */\nexport function formatMetadataHints(field: FieldSchema): string {\n const m = field.metadata\n if (!m) return ''\n const hints: string[] = []\n if (m.description) hints.push(m.description)\n if (m.aiHint) hints.push(`hint: ${m.aiHint}`)\n if (m.placeholder) hints.push(`example: ${m.placeholder}`)\n if (typeof m.minLength === 'number' || typeof m.maxLength === 'number') {\n hints.push(`length ${m.minLength ?? 0}–${m.maxLength ?? '∞'}`.replace('∞', 'unbounded'))\n }\n if (typeof m.min === 'number' || typeof m.max === 'number') {\n hints.push(`range ${m.min ?? '-∞'}–${m.max ?? '∞'}`)\n }\n if (m.unique) hints.push('must be unique')\n if (m.defaultValue !== undefined) {\n try {\n hints.push(`default: ${JSON.stringify(m.defaultValue)}`)\n } catch {\n /* unserializable default — skip */\n }\n }\n return hints.length > 0 ? ` [${hints.join('; ')}]` : ''\n}\n","import type { Payload } from 'payload'\nimport type { BlockSchema, CollectionSchema, FieldSchema } from '../types.js'\nimport { readAllCollectionSchemas, type SchemaReaderOptions } from './schema-reader.js'\n\n/**\n * The universal handshake artifact for this plugin.\n *\n * A SchemaManifest is a single JSON object that fully describes a Payload\n * project's shape in a form any MCP client can consume: collections,\n * deduplicated block catalog, lexical editor capabilities, custom field\n * types that the core adapters don't recognize, locales, upload\n * collections, and a config fingerprint for caching.\n *\n * Produced by buildSchemaManifest(payload) — exposed via MCP as the\n * `describePayloadProject` tool and the `schema://manifest` resource.\n */\nexport type SchemaManifest = {\n collections: CollectionSchema[]\n /** Dedup'd block catalog keyed by block slug (blocks can be reused across\n * many fields; the catalog lists each slug once with its sub-fields). */\n blocks: Record<string, BlockSchema>\n /** Rich-text editor capability per fully-qualified field path */\n lexicalEditors: Record<\n string,\n {\n features: string[]\n headingLevels: number[]\n supportsLists: boolean\n supportsUpload: boolean\n supportsRelationship: boolean\n supportsLinks: boolean\n }\n >\n /** Collection slugs whose Payload config declares `upload: true` */\n uploadCollections: string[]\n /** Collection slugs whose Payload config declares `auth: true` */\n authCollections: string[]\n /** Field types the adapter registry does not know about (custom plugins) */\n customFieldTypes: string[]\n /** Configured locales (if any). Empty when project is single-locale. */\n locales: string[]\n /** Collections considered non-populatable by the schema reader */\n nonPopulatable: string[]\n /** Stable hash of the manifest contents, for caching/invalidation */\n fingerprint: string\n /** Manifest version so future MCP clients can handshake */\n manifestVersion: '1'\n /** ISO timestamp when the manifest was produced */\n generatedAt: string\n}\n\nconst NATIVE_FIELD_TYPES = new Set([\n 'text',\n 'textarea',\n 'email',\n 'number',\n 'checkbox',\n 'date',\n 'select',\n 'radio',\n 'relationship',\n 'upload',\n 'richText',\n 'blocks',\n 'array',\n 'group',\n 'row',\n 'tabs',\n 'collapsible',\n 'point',\n 'json',\n 'code',\n 'ui',\n])\n\nfunction collectBlocks(\n fields: FieldSchema[],\n out: Record<string, BlockSchema>,\n customTypes: Set<string>,\n): void {\n for (const f of fields) {\n if (!NATIVE_FIELD_TYPES.has(f.type)) customTypes.add(f.type)\n if (f.blocks) {\n for (const b of f.blocks) {\n // First occurrence wins; later identical slugs are ignored.\n if (!(b.slug in out)) out[b.slug] = b\n collectBlocks(b.fields, out, customTypes)\n }\n }\n if (f.fields) collectBlocks(f.fields, out, customTypes)\n }\n}\n\nfunction collectLexicalEditors(\n collectionSlug: string,\n fields: FieldSchema[],\n out: SchemaManifest['lexicalEditors'],\n): void {\n for (const f of fields) {\n if (f.type === 'richText') {\n const features = f.lexicalFeatures ?? []\n const headingLevels: number[] = []\n for (const feat of features) {\n const m = feat.match(/h([1-6])/i)\n if (m) headingLevels.push(Number.parseInt(m[1], 10))\n }\n if (\n headingLevels.length === 0 &&\n features.some((x) => /heading/i.test(x) && !/h[1-6]/i.test(x))\n ) {\n headingLevels.push(2, 3, 4)\n }\n const supportsLists = features.some((x) => /list|bullet|number|ordered/i.test(x))\n const supportsUpload = features.some((x) => /upload|image/i.test(x))\n const supportsRelationship = features.some((x) => /relation/i.test(x))\n const supportsLinks = features.some((x) => /link/i.test(x))\n out[`${collectionSlug}.${f.path}`] = {\n features,\n headingLevels: [...new Set(headingLevels)].sort((a, b) => a - b),\n supportsLists,\n supportsUpload,\n supportsRelationship,\n supportsLinks,\n }\n }\n if (f.fields) collectLexicalEditors(collectionSlug, f.fields, out)\n if (f.blocks) for (const b of f.blocks) collectLexicalEditors(collectionSlug, b.fields, out)\n }\n}\n\n/** Fast non-crypto hash (djb2) for fingerprinting. */\nfunction fingerprint(input: string): string {\n let h = 5381\n for (let i = 0; i < input.length; i++) {\n h = ((h << 5) + h + input.charCodeAt(i)) | 0\n }\n return (h >>> 0).toString(36)\n}\n\nexport type BuildManifestOptions = SchemaReaderOptions & {\n /** Override locales if not available from payload.config.localization */\n locales?: string[]\n}\n\nexport function buildSchemaManifest(\n payload: Payload,\n options?: BuildManifestOptions,\n): SchemaManifest {\n const collections = readAllCollectionSchemas(payload, options)\n\n const blocks: Record<string, BlockSchema> = {}\n const customTypes = new Set<string>()\n const lexicalEditors: SchemaManifest['lexicalEditors'] = {}\n const uploadCollections: string[] = []\n const authCollections: string[] = []\n\n for (const c of collections) {\n collectBlocks(c.fields, blocks, customTypes)\n collectLexicalEditors(c.slug, c.fields, lexicalEditors)\n\n const entry = (payload.collections as unknown as Record<string, { config?: unknown }>)[c.slug]\n const cfg = (entry?.config ?? {}) as Record<string, unknown>\n if (cfg.upload) uploadCollections.push(c.slug)\n if (cfg.auth) authCollections.push(c.slug)\n }\n\n const nonPopulatable = collections.filter((c) => !c.populatable).map((c) => c.slug)\n\n // Try to extract locales from payload.config\n const payloadCfg = (payload as unknown as { config?: Record<string, unknown> }).config\n const localization = payloadCfg?.localization as\n | { locales?: Array<string | { code: string }> }\n | undefined\n const locales =\n options?.locales ??\n (localization?.locales\n ? localization.locales.map((l) => (typeof l === 'string' ? l : l.code))\n : [])\n\n const manifest: Omit<SchemaManifest, 'fingerprint'> = {\n collections,\n blocks,\n lexicalEditors,\n uploadCollections,\n authCollections,\n customFieldTypes: [...customTypes].sort(),\n locales,\n nonPopulatable,\n manifestVersion: '1',\n generatedAt: new Date().toISOString(),\n }\n\n return {\n ...manifest,\n fingerprint: fingerprint(\n JSON.stringify({\n collections: collections.map((c) => ({\n slug: c.slug,\n fields: c.fields.map((f) => ({ name: f.name, type: f.type, path: f.path })),\n })),\n }),\n ),\n }\n}\n","import type { PayloadRequest } from 'payload'\nimport { z } from 'zod'\n\nexport type MCPPrompt = {\n name: string\n title: string\n description: string\n argsSchema: Record<string, z.ZodTypeAny>\n handler: (\n args: Record<string, unknown>,\n req: PayloadRequest,\n extra: unknown,\n ) => { messages: Array<{ content: { text: string; type: 'text' }; role: 'user' | 'assistant' }> }\n}\n\nexport function getAIPrompts(): MCPPrompt[] {\n return [\n {\n name: 'generateContentBrief',\n title: 'Generate Content Brief',\n description:\n 'Produce a structured brief that guides AI content generation for a given collection and theme.',\n argsSchema: {\n collection: z.string().describe('The collection slug to generate content for'),\n theme: z.string().describe('The overarching theme or topic for the content'),\n count: z.number().describe('Number of documents to generate'),\n },\n handler(args) {\n const collection = args.collection as string\n const theme = args.theme as string\n const count = args.count as number\n\n const text = [\n `You are preparing a content generation brief for the Payload CMS collection: **${collection}**.`,\n '',\n `**Theme**: ${theme}`,\n `**Documents to generate**: ${count}`,\n '',\n 'Please produce a structured brief that includes:',\n '1. A short description of the content style and tone that fits the theme.',\n '2. Key topics or sub-themes each document should cover.',\n '3. Any field-level guidance (titles, descriptions, categories, tags) relevant to this collection.',\n '4. Example values for the most important fields.',\n '',\n `Output the brief as a JSON object with keys: style, topics, fieldGuidance, examples.`,\n ].join('\\n')\n\n return {\n messages: [\n {\n role: 'user',\n content: { type: 'text', text },\n },\n ],\n }\n },\n },\n\n {\n name: 'reviewGeneratedContent',\n title: 'Review Generated Content',\n description:\n 'Return a review prompt asking an AI to evaluate and suggest improvements for a generated document.',\n argsSchema: {\n collection: z.string().describe('The collection slug the document belongs to'),\n documentId: z.string().describe('The ID of the document to review'),\n },\n handler(args) {\n const collection = args.collection as string\n const documentId = args.documentId as string\n\n const text = [\n `Please review the following generated document.`,\n '',\n `**Collection**: ${collection}`,\n `**Document ID**: ${documentId}`,\n '',\n 'Evaluate the document on:',\n '1. **Accuracy** — Is the content factually plausible and internally consistent?',\n '2. **Completeness** — Are all important fields populated with meaningful values?',\n '3. **Quality** — Is the writing style appropriate and free of obvious AI artifacts?',\n '4. **Schema compliance** — Do field values match the expected types and constraints?',\n '',\n 'Provide a structured review with a score (1–10) for each criterion and specific suggestions for improvement.',\n ].join('\\n')\n\n return {\n messages: [\n {\n role: 'user',\n content: { type: 'text', text },\n },\n ],\n }\n },\n },\n ]\n}\n","export type MCPResource = {\n name: string\n title: string\n description: string\n uri: string\n mimeType: string\n handler: (...args: unknown[]) => { contents: Array<{ text: string; uri: string }> }\n}\n\nexport function getAIResources(): MCPResource[] {\n return [\n {\n name: 'schemaOverview',\n title: 'Collection Schema Overview',\n description:\n 'A high-level overview of all Payload CMS collections including their slugs, field counts, and populatable status.',\n uri: 'schema://collections',\n mimeType: 'text/plain',\n handler() {\n const text = [\n 'This resource provides a schema overview of all registered Payload CMS collections.',\n '',\n 'To retrieve the full schema for a specific collection, use the `getCollectionSchema` tool.',\n 'To list all collections with field counts and populatable status, use the `listCollections` tool.',\n 'To discover the entire project (collections + blocks + lexical editors + locales + custom field types) in one call, use the `describePayloadProject` tool.',\n 'To generate documents into a single collection, use the `populateCollection` tool.',\n 'To populate multiple collections at once, use the `bulkPopulate` tool.',\n '',\n 'All schema information is derived from the live Payload configuration at runtime.',\n ].join('\\n')\n\n return {\n contents: [\n {\n uri: 'schema://collections',\n text,\n },\n ],\n }\n },\n },\n {\n name: 'schemaManifestHowTo',\n title: 'Schema Manifest Usage',\n description:\n 'Explains how to consume the full Schema Manifest emitted by the describePayloadProject tool.',\n uri: 'schema://manifest',\n mimeType: 'text/plain',\n handler() {\n const text = [\n 'The Schema Manifest is the single source of truth for this Payload project.',\n '',\n 'Call the `describePayloadProject` MCP tool to receive it. Shape:',\n ' {',\n ' collections: CollectionSchema[],',\n \" blocks: { [slug]: BlockSchema } // dedup'd global block catalog\",\n ' lexicalEditors: { [collection.field]: { features, headingLevels, supportsLists, supportsUpload, supportsRelationship, supportsLinks } }',\n ' uploadCollections: string[]',\n ' authCollections: string[]',\n ' customFieldTypes: string[] // field types not in the built-in adapter set',\n ' locales: string[]',\n ' nonPopulatable: string[]',\n ' fingerprint: string // cache key',\n ' manifestVersion: \"1\"',\n ' generatedAt: string // ISO timestamp',\n ' }',\n '',\n 'Recommended flow: call describePayloadProject once per session, cache by fingerprint, then use populateCollection / bulkPopulate with the knowledge of block catalogs and lexical capabilities.',\n 'To get a lightweight summary without every field tree, pass { includeFields: false } to describePayloadProject.',\n ].join('\\n')\n return { contents: [{ uri: 'schema://manifest', text }] }\n },\n },\n ]\n}\n","import type { Payload, PayloadRequest } from 'payload'\nimport type { DeletionLogEntry } from '../types.js'\n\n/**\n * Tracks created documents for rollback on failure.\n * Uses in-memory journal — no MongoDB transaction dependency.\n * Works on standalone MongoDB, replica sets, and Atlas.\n */\nexport class DeletionLog {\n private journal: DeletionLogEntry[] = []\n\n /** Record a created document for potential rollback */\n record(collection: string, id: string): void {\n this.journal.push({ collection, id, createdAt: new Date() })\n }\n\n /** Get all recorded entries */\n getEntries(): readonly DeletionLogEntry[] {\n return [...this.journal]\n }\n\n /** Get count of recorded entries */\n get size(): number {\n return this.journal.length\n }\n\n /**\n * Rollback all created documents by deleting them in reverse order.\n * Best-effort: if process crashes, journal is lost.\n */\n async rollback(\n payload: Payload,\n req: PayloadRequest,\n ): Promise<{\n deleted: number\n failed: Array<{ collection: string; id: string; error: string }>\n }> {\n const failed: Array<{ collection: string; id: string; error: string }> = []\n let deleted = 0\n\n // Delete in reverse order (most recent first)\n for (const entry of [...this.journal].reverse()) {\n try {\n await payload.delete({\n collection: entry.collection as Parameters<Payload['delete']>[0]['collection'],\n id: entry.id,\n req,\n })\n deleted++\n } catch (err) {\n failed.push({\n collection: entry.collection,\n id: entry.id,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n this.journal = []\n return { deleted, failed }\n }\n\n /** Clear the journal without deleting documents */\n clear(): void {\n this.journal = []\n }\n}\n","import type { Payload, PayloadRequest } from 'payload'\nimport type { CollectionSchema } from '../types.js'\n\nexport type LinkRelationshipsOptions = {\n /**\n * Optional: a deterministic seeded RNG. Pass a Math.random-compatible\n * function to make relationship picks reproducible (tests, dry-runs).\n */\n rng?: () => number\n /**\n * Per-document semantic similarity scorer. Given a source document and\n * a candidate target document, return a score in [0,1]. Higher wins.\n * Used to prefer semantically-matched relationships over random picks.\n */\n scoreMatch?: (source: Record<string, unknown>, candidate: Record<string, unknown>) => number\n /**\n * Optional: map of collection slug -> full document payloads (indexed by\n * doc id). When supplied, the linker uses scoreMatch to prefer\n * semantically-matched candidates instead of picking at random.\n */\n documents?: Record<string, Record<string, Record<string, unknown>>>\n}\n\n/**\n * Deterministic Fisher-Yates shuffle using the supplied rng.\n */\nfunction shuffle<T>(items: readonly T[], rng: () => number): T[] {\n const arr = [...items]\n for (let i = arr.length - 1; i > 0; i--) {\n const j = Math.floor(rng() * (i + 1))\n const tmp = arr[i]\n arr[i] = arr[j]\n arr[j] = tmp\n }\n return arr\n}\n\n/**\n * Default scorer: lightweight token overlap on string fields — prefers\n * targets whose string values share tokens with the source document.\n * Returns 0 for totally unrelated docs so we don't over-weight arbitrary pairs.\n */\nfunction defaultScoreMatch(\n source: Record<string, unknown>,\n candidate: Record<string, unknown>,\n): number {\n const tokens = (v: unknown): Set<string> => {\n if (typeof v !== 'string') return new Set()\n return new Set(\n v\n .toLowerCase()\n .replace(/[^a-z0-9\\s]/g, ' ')\n .split(/\\s+/)\n .filter((t) => t.length > 2),\n )\n }\n\n const src = new Set<string>()\n for (const val of Object.values(source)) for (const t of tokens(val)) src.add(t)\n if (src.size === 0) return 0\n\n const cand = new Set<string>()\n for (const val of Object.values(candidate)) for (const t of tokens(val)) cand.add(t)\n if (cand.size === 0) return 0\n\n let shared = 0\n for (const t of src) if (cand.has(t)) shared++\n // Dice coefficient\n return (2 * shared) / (src.size + cand.size)\n}\n\n/**\n * Link relationship fields between generated documents.\n *\n * For each relationship defined in the schema, looks up available IDs\n * from documentIds and updates the documents in that collection.\n *\n * When `options.scoreMatch` is provided (defaults to token-overlap), the\n * linker prefers semantically-matched candidates over random picks. Falls\n * back to random if no targets share tokens (score = 0 for all).\n */\nexport async function linkRelationships(\n payload: Payload,\n req: PayloadRequest,\n schema: CollectionSchema,\n documentIds: Record<string, string[]>,\n options: LinkRelationshipsOptions = {},\n): Promise<{ updated: number; failed: number }> {\n const rng = options.rng ?? Math.random\n const scoreMatch = options.scoreMatch ?? defaultScoreMatch\n let updated = 0\n let failed = 0\n\n const ownIds = documentIds[schema.slug] ?? []\n if (ownIds.length === 0) return { updated, failed }\n\n /** Rank candidate target IDs by semantic score relative to source. */\n const rankCandidates = (\n sourceCollection: string,\n sourceId: string,\n targetCollection: string,\n targetIds: string[],\n ) => {\n const srcDoc = options.documents?.[sourceCollection]?.[sourceId]\n if (!srcDoc) return shuffle(targetIds, rng)\n const scored = targetIds.map((id) => {\n const targetDoc = options.documents?.[targetCollection]?.[id]\n const score = targetDoc ? scoreMatch(srcDoc, targetDoc) : 0\n return { id, score }\n })\n // Sort by score desc, break ties with shuffle for variance\n scored.sort((a, b) => b.score - a.score)\n const bestScore = scored[0]?.score ?? 0\n if (bestScore === 0) return shuffle(targetIds, rng)\n return scored.map((s) => s.id)\n }\n\n for (const docId of ownIds) {\n const updateData: Record<string, unknown> = {}\n let hasUpdates = false\n\n for (const rel of schema.relationships) {\n const targetCollection = Array.isArray(rel.collection) ? rel.collection[0] : rel.collection\n\n if (!targetCollection) continue\n\n // Self-referential: assign 2–4 sibling IDs (excluding self)\n if (rel.isSelfReferential) {\n const siblings = ownIds.filter((id) => id !== docId)\n if (siblings.length === 0) continue\n\n const count = Math.min(siblings.length, 2 + Math.floor(rng() * 3))\n const ranked = rankCandidates(schema.slug, docId, schema.slug, siblings)\n const picked = ranked.slice(0, count)\n\n updateData[rel.field] = rel.hasMany ? picked : picked[0]\n hasUpdates = true\n continue\n }\n\n const targetIds = documentIds[targetCollection] ?? []\n if (targetIds.length === 0) continue\n\n const ranked = rankCandidates(schema.slug, docId, targetCollection, targetIds)\n\n if (rel.hasMany) {\n // Pick up to 3, preferring top-scored matches\n const count = Math.min(targetIds.length, 1 + Math.floor(rng() * 3))\n updateData[rel.field] = ranked.slice(0, count)\n } else {\n // Pick the top-scored match\n updateData[rel.field] = ranked[0]\n }\n\n hasUpdates = true\n }\n\n if (!hasUpdates) continue\n\n try {\n await payload.update({\n collection: schema.slug as Parameters<Payload['update']>[0]['collection'],\n id: docId,\n data: updateData,\n overrideAccess: true,\n req,\n })\n updated++\n } catch (err) {\n console.error(\n `[relationship-linker] Failed to update ${schema.slug}/${docId}:`,\n err instanceof Error ? err.message : String(err),\n )\n failed++\n }\n }\n\n return { updated, failed }\n}\n","/**\n * Minimal zero-dependency concurrency limiter.\n *\n * Problem: the existing bulk-runner calls payload.create() sequentially\n * per document. For large bulk jobs this serializes all I/O and also\n * makes rateLimit.maxConcurrentRequests / delayBetweenRequests (declared\n * in AIPluginConfig but never consumed) a no-op.\n *\n * This helper provides a p-limit-style primitive that honors the\n * configured limits without pulling a dependency.\n */\n\nexport type Limiter = {\n /** Schedule a task and wait for its result. Blocks when capacity is full. */\n <T>(task: () => Promise<T>): Promise<T>\n /** Number of tasks currently running */\n activeCount: () => number\n /** Number of tasks waiting for a slot */\n pendingCount: () => number\n}\n\nexport function createLimiter(\n options: { concurrency?: number; delayBetweenMs?: number } = {},\n): Limiter {\n const maxConcurrency = Math.max(1, options.concurrency ?? 1)\n const delayBetweenMs = Math.max(0, options.delayBetweenMs ?? 0)\n\n let active = 0\n const queue: Array<() => void> = []\n let lastDispatchAt = 0\n\n async function waitForDelay(): Promise<void> {\n if (delayBetweenMs <= 0) return\n // Loop because another task may dispatch during our sleep, resetting\n // lastDispatchAt; we must re-check rather than wake up early.\n // The first call (lastDispatchAt === 0) is allowed to dispatch immediately.\n while (lastDispatchAt !== 0) {\n const since = Date.now() - lastDispatchAt\n if (since >= delayBetweenMs) return\n await new Promise((r) => setTimeout(r, delayBetweenMs - since))\n }\n }\n\n async function acquire(): Promise<void> {\n await waitForDelay()\n\n if (active < maxConcurrency) {\n active++\n lastDispatchAt = Date.now()\n return\n }\n\n await new Promise<void>((resolve) => queue.push(resolve))\n // Re-check the delay gate after being woken up: the previous dispatch\n // may have been very recent, and we want every dispatch to respect\n // the minimum spacing regardless of whether the task queued or not.\n await waitForDelay()\n active++\n lastDispatchAt = Date.now()\n }\n\n function release(): void {\n active--\n const next = queue.shift()\n if (next) next()\n }\n\n const limiter = async <T>(task: () => Promise<T>): Promise<T> => {\n await acquire()\n try {\n return await task()\n } finally {\n release()\n }\n }\n\n return Object.assign(limiter, {\n activeCount: () => active,\n pendingCount: () => queue.length,\n }) as Limiter\n}\n","import { EventEmitter } from 'node:events'\nimport type { ProgressEvent } from '../types.js'\n\n/** Create a typed EventEmitter for progress events */\nexport function createProgressEmitter(): EventEmitter {\n return new EventEmitter()\n}\n\n/** Emit a progress event on the emitter */\nexport function emitProgress(emitter: EventEmitter, event: ProgressEvent): void {\n emitter.emit('progress', event)\n}\n","import type { EventEmitter } from 'node:events'\nimport type { Payload, PayloadRequest } from 'payload'\nimport type { FieldAdapterRegistry } from '../core/field-adapters.js'\nimport { DeletionLog } from '../generate/deletion-log.js'\nimport { linkRelationships } from '../generate/relationship-linker.js'\nimport type { AIProvider, CollectionSchema, ProgressEvent } from '../types.js'\nimport { createLimiter } from './concurrency.js'\nimport { emitProgress } from './progress-tracker.js'\n\nexport type BulkRunConfig = {\n theme: string\n counts: Record<string, number>\n provider: AIProvider\n rollbackOnError?: boolean\n mediaSource?: 'unsplash' | 'placeholder'\n /** Forwarded to GenerationContext.domain — replaces default ecommerce framing */\n domain?: string\n /** Forwarded to GenerationContext.includeBlocks — enables first-class Blocks support */\n includeBlocks?: boolean\n /** Max concurrent payload.create() calls per collection. Default: 1 (serial). */\n maxConcurrentCreates?: number\n /** Minimum ms between dispatches within a collection. Default: 0. */\n delayBetweenCreatesMs?: number\n /** Optional FieldTypeAdapter registry for custom field types */\n adapters?: FieldAdapterRegistry\n}\n\nexport type BulkRunResult = {\n created: Record<string, number>\n failed: Record<string, number>\n documentIds: Record<string, string[]>\n rolledBack: boolean\n elapsed: number\n}\n\n/**\n * Run bulk population of Payload collections in dependency order.\n *\n * Flow:\n * 1. Resolve creation order from schemas\n * 2. Filter to collections requested in config.counts\n * 3. For each collection: generate documents, create them, record in deletion log\n * 4. After all: link deferred relationships\n * 5. On error + rollbackOnError: rollback all created documents\n */\nexport async function runBulkPopulation(\n payload: Payload,\n req: PayloadRequest,\n schemas: CollectionSchema[],\n config: BulkRunConfig,\n emitter?: EventEmitter,\n): Promise<BulkRunResult> {\n const startTime = Date.now()\n\n // Lazy imports to avoid circular dependency issues at module load time\n const { resolveCreationOrder } = await import('../core/dependency-resolver.js')\n const { generateDocuments } = await import('../core/content-generator.js')\n\n const created: Record<string, number> = {}\n const failed: Record<string, number> = {}\n const documentIds: Record<string, string[]> = {}\n const documentBodies: Record<string, Record<string, Record<string, unknown>>> = {}\n const deletionLog = new DeletionLog()\n let rolledBack = false\n\n // Determine ordered list of slugs to process\n const orderedSlugs = resolveCreationOrder(schemas)\n const slugsToProcess = orderedSlugs.filter(\n (slug) => slug in config.counts && config.counts[slug] > 0,\n )\n\n const schemaMap = new Map(schemas.map((s) => [s.slug, s]))\n\n try {\n for (const slug of slugsToProcess) {\n const schema = schemaMap.get(slug)\n if (!schema) continue\n\n const count = config.counts[slug] ?? 0\n created[slug] = 0\n failed[slug] = 0\n documentIds[slug] = []\n\n let generatedDocs: Record<string, unknown>[]\n try {\n const result = await generateDocuments(config.provider, schema, {\n count,\n theme: config.theme,\n existingIds: documentIds,\n ...(config.domain !== undefined ? { domain: config.domain } : {}),\n ...(config.includeBlocks !== undefined ? { includeBlocks: config.includeBlocks } : {}),\n ...(config.adapters ? { adapters: config.adapters } : {}),\n })\n generatedDocs = result.documents\n } catch (err) {\n console.error(\n `[bulk-runner] Failed to generate documents for \"${slug}\":`,\n err instanceof Error ? err.message : String(err),\n )\n failed[slug] = count\n continue\n }\n\n const limiter = createLimiter({\n concurrency: config.maxConcurrentCreates ?? 1,\n delayBetweenMs: config.delayBetweenCreatesMs ?? 0,\n })\n\n await Promise.all(\n generatedDocs.map((doc) =>\n limiter(async () => {\n try {\n const record = await payload.create({\n collection: slug as Parameters<Payload['create']>[0]['collection'],\n data: doc as Record<string, unknown>,\n overrideAccess: true,\n req,\n })\n\n const id = String(record.id)\n deletionLog.record(slug, id)\n documentIds[slug].push(id)\n if (!documentBodies[slug]) documentBodies[slug] = {}\n documentBodies[slug][id] = doc as Record<string, unknown>\n created[slug]++\n } catch (err) {\n console.error(\n `[bulk-runner] Failed to create document in \"${slug}\":`,\n err instanceof Error ? err.message : String(err),\n )\n failed[slug]++\n }\n\n if (emitter) {\n const event: ProgressEvent = {\n phase: 'create',\n collection: slug,\n created: created[slug],\n failed: failed[slug],\n total: count,\n elapsed: Date.now() - startTime,\n }\n emitProgress(emitter, event)\n }\n }),\n ),\n )\n }\n\n // Deferred: link relationships across all processed collections\n for (const slug of slugsToProcess) {\n const schema = schemaMap.get(slug)\n if (!schema || schema.relationships.length === 0) continue\n\n try {\n await linkRelationships(payload, req, schema, documentIds, {\n documents: documentBodies,\n })\n } catch (err) {\n console.error(\n `[bulk-runner] Failed to link relationships for \"${slug}\":`,\n err instanceof Error ? err.message : String(err),\n )\n }\n }\n } catch (err) {\n console.error(\n '[bulk-runner] Unexpected error during bulk population:',\n err instanceof Error ? err.message : String(err),\n )\n\n if (config.rollbackOnError) {\n console.log('[bulk-runner] Rolling back created documents...')\n await deletionLog.rollback(payload, req)\n rolledBack = true\n }\n }\n\n return {\n created,\n failed,\n documentIds,\n rolledBack,\n elapsed: Date.now() - startTime,\n }\n}\n","import type { PayloadRequest } from 'payload'\nimport { z } from 'zod'\nimport { generateDocuments } from '../core/content-generator.js'\nimport { createRegistry } from '../core/field-adapters.js'\nimport { createProvider } from '../core/providers/base.js'\nimport { buildSchemaManifest } from '../core/schema-manifest.js'\nimport { readAllCollectionSchemas, readCollectionSchema } from '../core/schema-reader.js'\nimport { runBulkPopulation } from '../orchestrate/bulk-runner.js'\nimport type { AIPluginConfig } from '../types.js'\n\nexport type MCPTool = {\n name: string\n description: string\n parameters: Record<string, z.ZodTypeAny>\n handler: (\n args: Record<string, unknown>,\n req: PayloadRequest,\n extra: unknown,\n ) => Promise<{ content: Array<{ text: string; type: 'text' }> }>\n}\n\nexport function getAITools(pluginConfig: AIPluginConfig): MCPTool[] {\n // Shared adapter registry: built from whatever custom adapters the user\n // supplied in aiPlugin({ fieldAdapters: [...] }). Built-in native types\n // go through the legacy switch in prompt-builder; the registry is only\n // consulted for types the switch doesn't know about.\n const adapters = pluginConfig.fieldAdapters\n ? createRegistry(pluginConfig.fieldAdapters)\n : undefined\n\n return [\n {\n name: 'populateCollection',\n description: 'Generate and insert AI-created documents into a Payload CMS collection.',\n parameters: {\n collection: z.string().describe('The collection slug to populate'),\n count: z.number().min(1).max(100).describe('Number of documents to generate'),\n theme: z\n .string()\n .optional()\n .describe('Optional theme or topic to guide content generation'),\n includeBlocks: z\n .boolean()\n .optional()\n .describe(\n 'Opt-in to generate Blocks (layout) fields with block-catalog awareness. Defaults to false for backwards compatibility.',\n ),\n domain: z\n .string()\n .optional()\n .describe(\n 'Optional domain framing (e.g. \"blog\", \"news site\"). Replaces the default \"ecommerce platform\" phrasing. Empty string drops framing entirely.',\n ),\n },\n async handler(args, req) {\n const collection = args.collection as string\n const count = args.count as number\n const theme = (args.theme as string | undefined) ?? 'general'\n const includeBlocks = args.includeBlocks as boolean | undefined\n const domainArg = args.domain as string | undefined\n const effectiveDomain = domainArg !== undefined ? domainArg : pluginConfig.domain\n\n try {\n const provider = createProvider({\n provider: pluginConfig.provider,\n apiKey: process.env[pluginConfig.apiKeyEnvVar] ?? '',\n baseUrl: pluginConfig.baseUrl,\n model: pluginConfig.model,\n })\n\n const schema = readCollectionSchema(req.payload, collection, {\n nonPopulatableSlugs: pluginConfig.nonPopulatableSlugs,\n replaceDefaults: pluginConfig.replaceNonPopulatableDefaults,\n })\n const result = await generateDocuments(provider, schema, {\n count,\n theme,\n ...(effectiveDomain !== undefined ? { domain: effectiveDomain } : {}),\n ...(includeBlocks !== undefined ? { includeBlocks } : {}),\n ...(adapters ? { adapters } : {}),\n })\n\n let created = 0\n let failed = 0\n\n for (const doc of result.documents) {\n try {\n await req.payload.create({\n collection: collection as Parameters<typeof req.payload.create>[0]['collection'],\n data: doc as Record<string, unknown>,\n overrideAccess: true,\n req,\n })\n created++\n } catch {\n failed++\n }\n }\n\n const text = `Populated \"${collection}\": ${created} created, ${failed} failed (requested ${count}).`\n return { content: [{ type: 'text', text }] }\n } catch (err) {\n const text = `Error populating \"${collection}\": ${err instanceof Error ? err.message : String(err)}`\n return { content: [{ type: 'text', text }] }\n }\n },\n },\n\n {\n name: 'bulkPopulate',\n description:\n 'Generate and insert AI-created documents across multiple Payload CMS collections at once.',\n parameters: {\n theme: z\n .string()\n .describe('Theme or topic to guide content generation across all collections'),\n counts: z\n .string()\n .describe('JSON map of collection slug to count, e.g. {\"posts\":5,\"categories\":3}'),\n includeBlocks: z\n .boolean()\n .optional()\n .describe('Opt-in to first-class Blocks (layout) generation across all collections.'),\n domain: z\n .string()\n .optional()\n .describe(\n 'Optional domain framing. Replaces default \"ecommerce platform\". Empty string drops framing.',\n ),\n },\n async handler(args, req) {\n const theme = args.theme as string\n const countsRaw = args.counts as string\n const includeBlocks = args.includeBlocks as boolean | undefined\n const domainArg = args.domain as string | undefined\n const effectiveDomain = domainArg !== undefined ? domainArg : pluginConfig.domain\n\n let counts: Record<string, number>\n try {\n counts = JSON.parse(countsRaw) as Record<string, number>\n } catch {\n return {\n content: [\n {\n type: 'text',\n text: 'Error: \"counts\" must be valid JSON, e.g. {\"posts\":5,\"categories\":3}',\n },\n ],\n }\n }\n\n try {\n const provider = createProvider({\n provider: pluginConfig.provider,\n apiKey: process.env[pluginConfig.apiKeyEnvVar] ?? '',\n baseUrl: pluginConfig.baseUrl,\n model: pluginConfig.model,\n })\n\n const schemas = readAllCollectionSchemas(req.payload, {\n nonPopulatableSlugs: pluginConfig.nonPopulatableSlugs,\n replaceDefaults: pluginConfig.replaceNonPopulatableDefaults,\n })\n const result = await runBulkPopulation(req.payload, req, schemas, {\n theme,\n counts,\n provider,\n rollbackOnError: pluginConfig.rollbackOnError,\n ...(effectiveDomain !== undefined ? { domain: effectiveDomain } : {}),\n ...(includeBlocks !== undefined ? { includeBlocks } : {}),\n ...(pluginConfig.rateLimit?.maxConcurrentRequests !== undefined\n ? { maxConcurrentCreates: pluginConfig.rateLimit.maxConcurrentRequests }\n : {}),\n ...(pluginConfig.rateLimit?.delayBetweenRequests !== undefined\n ? { delayBetweenCreatesMs: pluginConfig.rateLimit.delayBetweenRequests }\n : {}),\n ...(adapters ? { adapters } : {}),\n })\n\n const summary = Object.entries(result.created)\n .map(([slug, n]) => `${slug}: ${n} created, ${result.failed[slug] ?? 0} failed`)\n .join('\\n')\n\n const text = `Bulk population complete in ${result.elapsed}ms${result.rolledBack ? ' (rolled back)' : ''}:\\n${summary}`\n return { content: [{ type: 'text', text }] }\n } catch (err) {\n const text = `Error during bulk population: ${err instanceof Error ? err.message : String(err)}`\n return { content: [{ type: 'text', text }] }\n }\n },\n },\n\n {\n name: 'getCollectionSchema',\n description: 'Return the analyzed schema for a single Payload CMS collection as JSON.',\n parameters: {\n collection: z.string().describe('The collection slug to inspect'),\n },\n async handler(args, req) {\n const collection = args.collection as string\n\n try {\n const schema = readCollectionSchema(req.payload, collection)\n const text = JSON.stringify(schema, null, 2)\n return { content: [{ type: 'text', text }] }\n } catch (err) {\n const text = `Error reading schema for \"${collection}\": ${err instanceof Error ? err.message : String(err)}`\n return { content: [{ type: 'text', text }] }\n }\n },\n },\n\n {\n name: 'listCollections',\n description:\n 'List all Payload CMS collections with their field counts and whether they can be auto-populated.',\n parameters: {},\n async handler(_args, req) {\n try {\n const schemas = readAllCollectionSchemas(req.payload)\n const lines = schemas.map(\n (s) => `- ${s.slug}: ${s.fields.length} fields, populatable=${s.populatable}`,\n )\n const text = `Collections (${schemas.length}):\\n${lines.join('\\n')}`\n return { content: [{ type: 'text', text }] }\n } catch (err) {\n const text = `Error listing collections: ${err instanceof Error ? err.message : String(err)}`\n return { content: [{ type: 'text', text }] }\n }\n },\n },\n\n {\n name: 'describePayloadProject',\n description:\n 'Return a complete Schema Manifest for this Payload project: every collection, deduplicated block catalog, lexical editor capabilities, custom field types, upload + auth collections, locales, and a config fingerprint. Call this once at the start of a session to discover the project shape without asking per-collection.',\n parameters: {\n includeFields: z\n .boolean()\n .optional()\n .describe(\n 'When true (default), each collection includes its full field tree. When false, only slugs and summary counts are returned — useful for reducing token usage on large projects.',\n ),\n },\n async handler(args, req) {\n try {\n const includeFields = args.includeFields !== false\n const manifest = buildSchemaManifest(req.payload)\n\n if (!includeFields) {\n const slim = {\n ...manifest,\n collections: manifest.collections.map((c) => ({\n slug: c.slug,\n fieldCount: c.fields.length,\n relationshipCount: c.relationships.length,\n populatable: c.populatable,\n requiredFields: c.requiredFields,\n })),\n }\n return { content: [{ type: 'text', text: JSON.stringify(slim, null, 2) }] }\n }\n\n return { content: [{ type: 'text', text: JSON.stringify(manifest, null, 2) }] }\n } catch (err) {\n const text = `Error building schema manifest: ${err instanceof Error ? err.message : String(err)}`\n return { content: [{ type: 'text', text }] }\n }\n },\n },\n ]\n}\n","import type { CollectionAfterChangeHook } from 'payload'\nimport type { AIPluginConfig } from '../../types.js'\n\n/**\n * Fallback alt text derived from the filename — keeps the hook useful\n * when AI vision is unavailable (no API key, no URL, vision call fails).\n */\nfunction altFromFilename(filename: string): string {\n const cleanName = filename.replace(/[-_]/g, ' ').replace(/\\.[^.]+$/, '')\n return `${cleanName} - uploaded media`\n}\n\n/**\n * Fetch the uploaded image into a Buffer so we can hand it to\n * provider.analyzeImage. Supports absolute URLs (doc.url) and, if the\n * payload is configured to serve uploads, falls back to filename + serverURL.\n */\nasync function fetchImageBuffer(url: string): Promise<Buffer | null> {\n try {\n const res = await fetch(url)\n if (!res.ok) return null\n const ct = res.headers.get('content-type') ?? ''\n if (!ct.startsWith('image/')) return null\n const ab = await res.arrayBuffer()\n return Buffer.from(ab)\n } catch {\n return null\n }\n}\n\n/**\n * Creates an afterChange hook for the media collection that auto-generates alt text.\n * Only runs on 'create' operations where alt text is empty.\n *\n * Uses provider.analyzeImage() when a URL + API key are available, and\n * falls back to a filename-derived placeholder otherwise.\n */\nexport function createAltTextHook(pluginConfig: AIPluginConfig): CollectionAfterChangeHook {\n return async ({ doc, operation, req }) => {\n if (operation !== 'create') return doc\n\n // Only process if alt is empty\n if (doc.alt && String(doc.alt).trim() !== '') return doc\n\n const apiKey = process.env[pluginConfig.apiKeyEnvVar]\n if (!apiKey) return doc\n\n const filename = doc.filename ? String(doc.filename) : 'image'\n const rawUrl = doc.url ? String(doc.url) : ''\n\n try {\n let altText = altFromFilename(filename)\n\n // Try the vision path: fetch the image, hand it to the provider.\n if (rawUrl) {\n try {\n const { createProvider } = await import('../../core/providers/base.js')\n const provider = createProvider({\n provider: pluginConfig.provider,\n apiKey,\n baseUrl: pluginConfig.baseUrl,\n model: pluginConfig.model,\n })\n const buffer = await fetchImageBuffer(rawUrl)\n if (buffer && buffer.length > 0) {\n const vision = await provider.analyzeImage(buffer)\n const trimmed = vision.trim()\n if (trimmed) altText = trimmed\n }\n } catch (visionErr) {\n console.warn(\n '[@karixi/payload-ai] Vision alt text failed, falling back to filename:',\n visionErr instanceof Error ? visionErr.message : String(visionErr),\n )\n }\n }\n\n await req.payload.update({\n collection: 'media',\n id: doc.id as string,\n data: { alt: altText },\n req,\n })\n\n return { ...doc, alt: altText }\n } catch (err) {\n console.warn('[@karixi/payload-ai] Alt text generation failed:', err)\n return doc\n }\n }\n}\n","import type { CollectionBeforeChangeHook } from 'payload'\nimport type { AIPluginConfig } from '../../types.js'\n\n/**\n * Creates a beforeChange hook that auto-fills empty fields with AI content.\n * Only runs on 'create' operations. Only fills fields configured in AIPluginConfig.\n */\nexport function createSmartDefaultsHook(\n pluginConfig: AIPluginConfig,\n collectionSlug: string,\n): CollectionBeforeChangeHook {\n return async ({ data, operation, req }) => {\n if (operation !== 'create') return data\n\n const collectionConfig = pluginConfig.collections?.[collectionSlug]\n if (!collectionConfig?.fields) return data\n\n const apiKey = process.env[pluginConfig.apiKeyEnvVar]\n if (!apiKey) return data\n\n // Import dynamically to avoid circular deps\n const { createProvider } = await import('../../core/providers/base.js')\n const { readCollectionSchema } = await import('../../core/schema-reader.js')\n const { generateDocuments } = await import('../../core/content-generator.js')\n\n const provider = createProvider({\n provider: pluginConfig.provider,\n apiKey,\n baseUrl: pluginConfig.baseUrl,\n model: pluginConfig.model,\n })\n\n // Collect every empty enabled field into a single sub-schema. Inject\n // each field's per-field prompt as metadata.aiHint so the batched\n // generation prompt preserves the guidance. Then issue ONE generate\n // call for all fields instead of N.\n const schema = readCollectionSchema(req.payload, collectionSlug, {\n nonPopulatableSlugs: pluginConfig.nonPopulatableSlugs,\n replaceDefaults: pluginConfig.replaceNonPopulatableDefaults,\n })\n\n const fieldsToFill: typeof schema.fields = []\n const fieldPrompts: string[] = []\n for (const [fieldName, fieldConfig] of Object.entries(collectionConfig.fields)) {\n if (!fieldConfig.enabled) continue\n const currentValue = (data as Record<string, unknown>)[fieldName]\n if (currentValue !== undefined && currentValue !== null && currentValue !== '') continue\n const fieldSchema = schema.fields.find((f) => f.name === fieldName)\n if (!fieldSchema) continue\n const hinted = fieldConfig.prompt\n ? {\n ...fieldSchema,\n metadata: { ...fieldSchema.metadata, aiHint: fieldConfig.prompt },\n }\n : fieldSchema\n fieldsToFill.push(hinted)\n if (fieldConfig.prompt) fieldPrompts.push(fieldConfig.prompt)\n }\n\n if (fieldsToFill.length === 0) return data\n\n try {\n const result = await generateDocuments(\n provider,\n {\n ...schema,\n fields: fieldsToFill,\n requiredFields: fieldsToFill.filter((f) => f.required).map((f) => f.name),\n },\n {\n count: 1,\n // Combined theme from per-field prompts gives the model a single\n // coherent brief across all fields being filled at once.\n theme: fieldPrompts.length > 0 ? fieldPrompts.join('; ') : undefined,\n ...(pluginConfig.domain !== undefined ? { domain: pluginConfig.domain } : {}),\n },\n )\n\n const generated = result.documents[0]\n if (generated) {\n for (const field of fieldsToFill) {\n const value = generated[field.name]\n if (value !== undefined) {\n ;(data as Record<string, unknown>)[field.name] = value\n }\n }\n }\n } catch (err) {\n console.warn(\n `[@karixi/payload-ai] Smart defaults batch failed for ${fieldsToFill\n .map((f) => f.name)\n .join(', ')}:`,\n err,\n )\n }\n\n return data\n }\n}\n","import type { CollectionConfig, Config, Payload } from 'payload'\nimport { createAltTextHook } from './admin/hooks/afterUpload.js'\nimport { createSmartDefaultsHook } from './admin/hooks/beforeChange.js'\nimport type { AIPluginConfig } from './types.js'\n\n/**\n * Payload AI Plugin — adds AI-powered data generation and admin features.\n * Auto-injects MCP custom tools if @payloadcms/plugin-mcp is present.\n */\nexport function aiPlugin(config: AIPluginConfig) {\n return (incomingConfig: Config): Config => {\n // Validate API key env var exists at init time\n const apiKey = process.env[config.apiKeyEnvVar]\n if (!apiKey) {\n console.warn(\n `[@karixi/payload-ai] Warning: ${config.apiKeyEnvVar} environment variable is not set. AI features will not work.`,\n )\n }\n\n const existingOnInit = incomingConfig.onInit\n\n // TODO Phase 2: Inject schema introspection + AI generation\n // TODO Phase 3: Register MCP custom tools (auto-detect mcpPlugin)\n\n let collections = incomingConfig.collections\n\n if (config.features?.adminUI && collections) {\n collections = collections.map((collection: CollectionConfig) => {\n const slug = collection.slug\n const updatedHooks = { ...collection.hooks }\n\n // Add beforeChange smart defaults hook for configured collections\n if (config.collections?.[slug]) {\n updatedHooks.beforeChange = [\n ...(updatedHooks.beforeChange ?? []),\n createSmartDefaultsHook(config, slug),\n ]\n }\n\n // Add afterChange alt text hook for media collection\n if (slug === 'media') {\n updatedHooks.afterChange = [\n ...(updatedHooks.afterChange ?? []),\n createAltTextHook(config),\n ]\n }\n\n if (\n updatedHooks.beforeChange !== collection.hooks?.beforeChange ||\n updatedHooks.afterChange !== collection.hooks?.afterChange\n ) {\n return { ...collection, hooks: updatedHooks }\n }\n\n return collection\n })\n }\n\n return {\n ...incomingConfig,\n collections,\n onInit: async (payload: Payload) => {\n if (existingOnInit) await existingOnInit(payload)\n console.log(`[@karixi/payload-ai] Plugin initialized. Provider: ${config.provider}`)\n // TODO Phase 4: Initialize admin UI components\n // TODO Phase 5: Initialize dev tools\n },\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/core/field-adapters.ts","../src/core/schema-manifest.ts","../src/mcp/prompts.ts","../src/mcp/resources.ts","../src/generate/deletion-log.ts","../src/generate/relationship-linker.ts","../src/orchestrate/concurrency.ts","../src/orchestrate/progress-tracker.ts","../src/orchestrate/bulk-runner.ts","../src/mcp/tools.ts","../src/admin/hooks/afterUpload.ts","../src/admin/hooks/beforeChange.ts","../src/plugin.ts"],"sourcesContent":["import type { FieldSchema } from '../types.js'\n\n/**\n * A FieldTypeAdapter is the extension seam for custom Payload field types.\n *\n * Every built-in type (text, number, select, relationship, blocks, ...) is\n * represented by an adapter. Downstream plugin users can register additional\n * adapters for custom field types (e.g. slug from @payloadcms/plugin-nested-docs,\n * code fields, geolocation) without modifying core.\n *\n * Adapters are looked up by field.type. If none matches, the core default\n * adapter is used.\n */\nexport type FieldTypeAdapter = {\n /** The Payload field.type string this adapter handles (e.g. 'text', 'slug') */\n type: string\n\n /**\n * Return a one-line prompt fragment describing this field to the AI.\n * Returning null signals \"skip this field — do not include it in the\n * generation prompt\" (used for blocks, upload, relationship which are\n * handled separately).\n */\n describe?: (field: FieldSchema, ctx: PromptContext) => string | null\n\n /**\n * Return a JSON-Schema fragment for this field, to be composed into the\n * top-level output schema. Returning null excludes the field from\n * structured output entirely.\n */\n outputSchema?: (field: FieldSchema) => Record<string, unknown> | null\n\n /**\n * Validate a raw generated value against the field. Return errors for\n * any problem found; empty array means valid.\n */\n validate?: (value: unknown, field: FieldSchema) => ValidationIssue[]\n\n /**\n * Optional: transform the raw generated value before persistence\n * (e.g. plain-text → Lexical JSON for richText fields).\n */\n postprocess?: (value: unknown, field: FieldSchema) => unknown\n}\n\nexport type PromptContext = {\n existingIds?: Record<string, string[]>\n /** Optional domain hint (\"ecommerce\", \"blog\", \"news site\") */\n domain?: string\n /** Optional theme */\n theme?: string\n}\n\nexport type ValidationIssue = {\n field: string\n message: string\n}\n\nexport type FieldAdapterRegistry = {\n get(type: string): FieldTypeAdapter | undefined\n register(adapter: FieldTypeAdapter): void\n list(): FieldTypeAdapter[]\n}\n\nexport function createRegistry(initial: FieldTypeAdapter[] = []): FieldAdapterRegistry {\n const table = new Map<string, FieldTypeAdapter>()\n for (const a of initial) table.set(a.type, a)\n\n return {\n get: (type) => table.get(type),\n register: (adapter) => {\n table.set(adapter.type, adapter)\n },\n list: () => [...table.values()],\n }\n}\n\n/** Helper: format the required/optional suffix used in describe() */\nexport function requiredSuffix(field: FieldSchema): string {\n return field.required ? ' (required)' : ' (optional)'\n}\n\n/** Helper: format field-level metadata hints as a trailing annotation */\nexport function formatMetadataHints(field: FieldSchema): string {\n const m = field.metadata\n if (!m) return ''\n const hints: string[] = []\n if (m.description) hints.push(m.description)\n if (m.aiHint) hints.push(`hint: ${m.aiHint}`)\n if (m.placeholder) hints.push(`example: ${m.placeholder}`)\n if (typeof m.minLength === 'number' || typeof m.maxLength === 'number') {\n hints.push(`length ${m.minLength ?? 0}–${m.maxLength ?? '∞'}`.replace('∞', 'unbounded'))\n }\n if (typeof m.min === 'number' || typeof m.max === 'number') {\n hints.push(`range ${m.min ?? '-∞'}–${m.max ?? '∞'}`)\n }\n if (m.unique) hints.push('must be unique')\n if (m.defaultValue !== undefined) {\n try {\n hints.push(`default: ${JSON.stringify(m.defaultValue)}`)\n } catch {\n /* unserializable default — skip */\n }\n }\n return hints.length > 0 ? ` [${hints.join('; ')}]` : ''\n}\n","import type { Payload } from 'payload'\nimport type { BlockSchema, CollectionSchema, FieldSchema } from '../types.js'\nimport { readAllCollectionSchemas, type SchemaReaderOptions } from './schema-reader.js'\n\n/**\n * The universal handshake artifact for this plugin.\n *\n * A SchemaManifest is a single JSON object that fully describes a Payload\n * project's shape in a form any MCP client can consume: collections,\n * deduplicated block catalog, lexical editor capabilities, custom field\n * types that the core adapters don't recognize, locales, upload\n * collections, and a config fingerprint for caching.\n *\n * Produced by buildSchemaManifest(payload) — exposed via MCP as the\n * `describePayloadProject` tool and the `schema://manifest` resource.\n */\nexport type SchemaManifest = {\n collections: CollectionSchema[]\n /** Dedup'd block catalog keyed by block slug (blocks can be reused across\n * many fields; the catalog lists each slug once with its sub-fields). */\n blocks: Record<string, BlockSchema>\n /** Rich-text editor capability per fully-qualified field path */\n lexicalEditors: Record<\n string,\n {\n features: string[]\n headingLevels: number[]\n supportsLists: boolean\n supportsUpload: boolean\n supportsRelationship: boolean\n supportsLinks: boolean\n }\n >\n /** Collection slugs whose Payload config declares `upload: true` */\n uploadCollections: string[]\n /** Collection slugs whose Payload config declares `auth: true` */\n authCollections: string[]\n /** Field types the adapter registry does not know about (custom plugins) */\n customFieldTypes: string[]\n /** Configured locales (if any). Empty when project is single-locale. */\n locales: string[]\n /** Collections considered non-populatable by the schema reader */\n nonPopulatable: string[]\n /** Stable hash of the manifest contents, for caching/invalidation */\n fingerprint: string\n /** Manifest version so future MCP clients can handshake */\n manifestVersion: '1'\n /** ISO timestamp when the manifest was produced */\n generatedAt: string\n}\n\nconst NATIVE_FIELD_TYPES = new Set([\n 'text',\n 'textarea',\n 'email',\n 'number',\n 'checkbox',\n 'date',\n 'select',\n 'radio',\n 'relationship',\n 'upload',\n 'richText',\n 'blocks',\n 'array',\n 'group',\n 'row',\n 'tabs',\n 'collapsible',\n 'point',\n 'json',\n 'code',\n 'ui',\n])\n\nfunction collectBlocks(\n fields: FieldSchema[],\n out: Record<string, BlockSchema>,\n customTypes: Set<string>,\n): void {\n for (const f of fields) {\n if (!NATIVE_FIELD_TYPES.has(f.type)) customTypes.add(f.type)\n if (f.blocks) {\n for (const b of f.blocks) {\n // First occurrence wins; later identical slugs are ignored.\n if (!(b.slug in out)) out[b.slug] = b\n collectBlocks(b.fields, out, customTypes)\n }\n }\n if (f.fields) collectBlocks(f.fields, out, customTypes)\n }\n}\n\nfunction collectLexicalEditors(\n collectionSlug: string,\n fields: FieldSchema[],\n out: SchemaManifest['lexicalEditors'],\n): void {\n for (const f of fields) {\n if (f.type === 'richText') {\n const features = f.lexicalFeatures ?? []\n const headingLevels: number[] = []\n for (const feat of features) {\n const m = feat.match(/h([1-6])/i)\n if (m) headingLevels.push(Number.parseInt(m[1], 10))\n }\n if (\n headingLevels.length === 0 &&\n features.some((x) => /heading/i.test(x) && !/h[1-6]/i.test(x))\n ) {\n headingLevels.push(2, 3, 4)\n }\n const supportsLists = features.some((x) => /list|bullet|number|ordered/i.test(x))\n const supportsUpload = features.some((x) => /upload|image/i.test(x))\n const supportsRelationship = features.some((x) => /relation/i.test(x))\n const supportsLinks = features.some((x) => /link/i.test(x))\n out[`${collectionSlug}.${f.path}`] = {\n features,\n headingLevels: [...new Set(headingLevels)].sort((a, b) => a - b),\n supportsLists,\n supportsUpload,\n supportsRelationship,\n supportsLinks,\n }\n }\n if (f.fields) collectLexicalEditors(collectionSlug, f.fields, out)\n if (f.blocks) for (const b of f.blocks) collectLexicalEditors(collectionSlug, b.fields, out)\n }\n}\n\n/** Fast non-crypto hash (djb2) for fingerprinting. */\nfunction fingerprint(input: string): string {\n let h = 5381\n for (let i = 0; i < input.length; i++) {\n h = ((h << 5) + h + input.charCodeAt(i)) | 0\n }\n return (h >>> 0).toString(36)\n}\n\nexport type BuildManifestOptions = SchemaReaderOptions & {\n /** Override locales if not available from payload.config.localization */\n locales?: string[]\n}\n\nexport function buildSchemaManifest(\n payload: Payload,\n options?: BuildManifestOptions,\n): SchemaManifest {\n const collections = readAllCollectionSchemas(payload, options)\n\n const blocks: Record<string, BlockSchema> = {}\n const customTypes = new Set<string>()\n const lexicalEditors: SchemaManifest['lexicalEditors'] = {}\n const uploadCollections: string[] = []\n const authCollections: string[] = []\n\n for (const c of collections) {\n collectBlocks(c.fields, blocks, customTypes)\n collectLexicalEditors(c.slug, c.fields, lexicalEditors)\n\n const entry = (payload.collections as unknown as Record<string, { config?: unknown }>)[c.slug]\n const cfg = (entry?.config ?? {}) as Record<string, unknown>\n if (cfg.upload) uploadCollections.push(c.slug)\n if (cfg.auth) authCollections.push(c.slug)\n }\n\n const nonPopulatable = collections.filter((c) => !c.populatable).map((c) => c.slug)\n\n // Try to extract locales from payload.config\n const payloadCfg = (payload as unknown as { config?: Record<string, unknown> }).config\n const localization = payloadCfg?.localization as\n | { locales?: Array<string | { code: string }> }\n | undefined\n const locales =\n options?.locales ??\n (localization?.locales\n ? localization.locales.map((l) => (typeof l === 'string' ? l : l.code))\n : [])\n\n const manifest: Omit<SchemaManifest, 'fingerprint'> = {\n collections,\n blocks,\n lexicalEditors,\n uploadCollections,\n authCollections,\n customFieldTypes: [...customTypes].sort(),\n locales,\n nonPopulatable,\n manifestVersion: '1',\n generatedAt: new Date().toISOString(),\n }\n\n return {\n ...manifest,\n fingerprint: fingerprint(\n JSON.stringify({\n collections: collections.map((c) => ({\n slug: c.slug,\n fields: c.fields.map((f) => ({ name: f.name, type: f.type, path: f.path })),\n })),\n }),\n ),\n }\n}\n","import type { PayloadRequest } from 'payload'\nimport { z } from 'zod'\n\nexport type MCPPrompt = {\n name: string\n title: string\n description: string\n argsSchema: Record<string, z.ZodTypeAny>\n handler: (\n args: Record<string, unknown>,\n req: PayloadRequest,\n extra: unknown,\n ) => { messages: Array<{ content: { text: string; type: 'text' }; role: 'user' | 'assistant' }> }\n}\n\nexport function getAIPrompts(): MCPPrompt[] {\n return [\n {\n name: 'generateContentBrief',\n title: 'Generate Content Brief',\n description:\n 'Produce a structured brief that guides AI content generation for a given collection and theme.',\n argsSchema: {\n collection: z.string().describe('The collection slug to generate content for'),\n theme: z.string().describe('The overarching theme or topic for the content'),\n count: z.number().describe('Number of documents to generate'),\n },\n handler(args) {\n const collection = args.collection as string\n const theme = args.theme as string\n const count = args.count as number\n\n const text = [\n `You are preparing a content generation brief for the Payload CMS collection: **${collection}**.`,\n '',\n `**Theme**: ${theme}`,\n `**Documents to generate**: ${count}`,\n '',\n 'Please produce a structured brief that includes:',\n '1. A short description of the content style and tone that fits the theme.',\n '2. Key topics or sub-themes each document should cover.',\n '3. Any field-level guidance (titles, descriptions, categories, tags) relevant to this collection.',\n '4. Example values for the most important fields.',\n '',\n `Output the brief as a JSON object with keys: style, topics, fieldGuidance, examples.`,\n ].join('\\n')\n\n return {\n messages: [\n {\n role: 'user',\n content: { type: 'text', text },\n },\n ],\n }\n },\n },\n\n {\n name: 'reviewGeneratedContent',\n title: 'Review Generated Content',\n description:\n 'Return a review prompt asking an AI to evaluate and suggest improvements for a generated document.',\n argsSchema: {\n collection: z.string().describe('The collection slug the document belongs to'),\n documentId: z.string().describe('The ID of the document to review'),\n },\n handler(args) {\n const collection = args.collection as string\n const documentId = args.documentId as string\n\n const text = [\n `Please review the following generated document.`,\n '',\n `**Collection**: ${collection}`,\n `**Document ID**: ${documentId}`,\n '',\n 'Evaluate the document on:',\n '1. **Accuracy** — Is the content factually plausible and internally consistent?',\n '2. **Completeness** — Are all important fields populated with meaningful values?',\n '3. **Quality** — Is the writing style appropriate and free of obvious AI artifacts?',\n '4. **Schema compliance** — Do field values match the expected types and constraints?',\n '',\n 'Provide a structured review with a score (1–10) for each criterion and specific suggestions for improvement.',\n ].join('\\n')\n\n return {\n messages: [\n {\n role: 'user',\n content: { type: 'text', text },\n },\n ],\n }\n },\n },\n ]\n}\n","export type MCPResource = {\n name: string\n title: string\n description: string\n uri: string\n mimeType: string\n handler: (...args: unknown[]) => { contents: Array<{ text: string; uri: string }> }\n}\n\nexport function getAIResources(): MCPResource[] {\n return [\n {\n name: 'schemaOverview',\n title: 'Collection Schema Overview',\n description:\n 'A high-level overview of all Payload CMS collections including their slugs, field counts, and populatable status.',\n uri: 'schema://collections',\n mimeType: 'text/plain',\n handler() {\n const text = [\n 'This resource provides a schema overview of all registered Payload CMS collections.',\n '',\n 'To retrieve the full schema for a specific collection, use the `getCollectionSchema` tool.',\n 'To list all collections with field counts and populatable status, use the `listCollections` tool.',\n 'To discover the entire project (collections + blocks + lexical editors + locales + custom field types) in one call, use the `describePayloadProject` tool.',\n 'To generate documents into a single collection, use the `populateCollection` tool.',\n 'To populate multiple collections at once, use the `bulkPopulate` tool.',\n '',\n 'All schema information is derived from the live Payload configuration at runtime.',\n ].join('\\n')\n\n return {\n contents: [\n {\n uri: 'schema://collections',\n text,\n },\n ],\n }\n },\n },\n {\n name: 'schemaManifestHowTo',\n title: 'Schema Manifest Usage',\n description:\n 'Explains how to consume the full Schema Manifest emitted by the describePayloadProject tool.',\n uri: 'schema://manifest',\n mimeType: 'text/plain',\n handler() {\n const text = [\n 'The Schema Manifest is the single source of truth for this Payload project.',\n '',\n 'Call the `describePayloadProject` MCP tool to receive it. Shape:',\n ' {',\n ' collections: CollectionSchema[],',\n \" blocks: { [slug]: BlockSchema } // dedup'd global block catalog\",\n ' lexicalEditors: { [collection.field]: { features, headingLevels, supportsLists, supportsUpload, supportsRelationship, supportsLinks } }',\n ' uploadCollections: string[]',\n ' authCollections: string[]',\n ' customFieldTypes: string[] // field types not in the built-in adapter set',\n ' locales: string[]',\n ' nonPopulatable: string[]',\n ' fingerprint: string // cache key',\n ' manifestVersion: \"1\"',\n ' generatedAt: string // ISO timestamp',\n ' }',\n '',\n 'Recommended flow: call describePayloadProject once per session, cache by fingerprint, then use populateCollection / bulkPopulate with the knowledge of block catalogs and lexical capabilities.',\n 'To get a lightweight summary without every field tree, pass { includeFields: false } to describePayloadProject.',\n ].join('\\n')\n return { contents: [{ uri: 'schema://manifest', text }] }\n },\n },\n ]\n}\n","import type { Payload, PayloadRequest } from 'payload'\nimport type { DeletionLogEntry } from '../types.js'\n\n/**\n * Tracks created documents for rollback on failure.\n * Uses in-memory journal — no MongoDB transaction dependency.\n * Works on standalone MongoDB, replica sets, and Atlas.\n */\nexport class DeletionLog {\n private journal: DeletionLogEntry[] = []\n\n /** Record a created document for potential rollback */\n record(collection: string, id: string): void {\n this.journal.push({ collection, id, createdAt: new Date() })\n }\n\n /** Get all recorded entries */\n getEntries(): readonly DeletionLogEntry[] {\n return [...this.journal]\n }\n\n /** Get count of recorded entries */\n get size(): number {\n return this.journal.length\n }\n\n /**\n * Rollback all created documents by deleting them in reverse order.\n * Best-effort: if process crashes, journal is lost.\n */\n async rollback(\n payload: Payload,\n req: PayloadRequest,\n ): Promise<{\n deleted: number\n failed: Array<{ collection: string; id: string; error: string }>\n }> {\n const failed: Array<{ collection: string; id: string; error: string }> = []\n let deleted = 0\n\n // Delete in reverse order (most recent first)\n for (const entry of [...this.journal].reverse()) {\n try {\n await payload.delete({\n collection: entry.collection as Parameters<Payload['delete']>[0]['collection'],\n id: entry.id,\n req,\n })\n deleted++\n } catch (err) {\n failed.push({\n collection: entry.collection,\n id: entry.id,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n this.journal = []\n return { deleted, failed }\n }\n\n /** Clear the journal without deleting documents */\n clear(): void {\n this.journal = []\n }\n}\n","import type { Payload, PayloadRequest } from 'payload'\nimport type { CollectionSchema } from '../types.js'\n\nexport type LinkRelationshipsOptions = {\n /**\n * Optional: a deterministic seeded RNG. Pass a Math.random-compatible\n * function to make relationship picks reproducible (tests, dry-runs).\n */\n rng?: () => number\n /**\n * Per-document semantic similarity scorer. Given a source document and\n * a candidate target document, return a score in [0,1]. Higher wins.\n * Used to prefer semantically-matched relationships over random picks.\n */\n scoreMatch?: (source: Record<string, unknown>, candidate: Record<string, unknown>) => number\n /**\n * Optional: map of collection slug -> full document payloads (indexed by\n * doc id). When supplied, the linker uses scoreMatch to prefer\n * semantically-matched candidates instead of picking at random.\n */\n documents?: Record<string, Record<string, Record<string, unknown>>>\n}\n\n/**\n * Deterministic Fisher-Yates shuffle using the supplied rng.\n */\nfunction shuffle<T>(items: readonly T[], rng: () => number): T[] {\n const arr = [...items]\n for (let i = arr.length - 1; i > 0; i--) {\n const j = Math.floor(rng() * (i + 1))\n const tmp = arr[i]\n arr[i] = arr[j]\n arr[j] = tmp\n }\n return arr\n}\n\n/**\n * Default scorer: lightweight token overlap on string fields — prefers\n * targets whose string values share tokens with the source document.\n * Returns 0 for totally unrelated docs so we don't over-weight arbitrary pairs.\n */\nfunction defaultScoreMatch(\n source: Record<string, unknown>,\n candidate: Record<string, unknown>,\n): number {\n const tokens = (v: unknown): Set<string> => {\n if (typeof v !== 'string') return new Set()\n return new Set(\n v\n .toLowerCase()\n .replace(/[^a-z0-9\\s]/g, ' ')\n .split(/\\s+/)\n .filter((t) => t.length > 2),\n )\n }\n\n const src = new Set<string>()\n for (const val of Object.values(source)) for (const t of tokens(val)) src.add(t)\n if (src.size === 0) return 0\n\n const cand = new Set<string>()\n for (const val of Object.values(candidate)) for (const t of tokens(val)) cand.add(t)\n if (cand.size === 0) return 0\n\n let shared = 0\n for (const t of src) if (cand.has(t)) shared++\n // Dice coefficient\n return (2 * shared) / (src.size + cand.size)\n}\n\n/**\n * Link relationship fields between generated documents.\n *\n * For each relationship defined in the schema, looks up available IDs\n * from documentIds and updates the documents in that collection.\n *\n * When `options.scoreMatch` is provided (defaults to token-overlap), the\n * linker prefers semantically-matched candidates over random picks. Falls\n * back to random if no targets share tokens (score = 0 for all).\n */\nexport async function linkRelationships(\n payload: Payload,\n req: PayloadRequest,\n schema: CollectionSchema,\n documentIds: Record<string, string[]>,\n options: LinkRelationshipsOptions = {},\n): Promise<{ updated: number; failed: number }> {\n const rng = options.rng ?? Math.random\n const scoreMatch = options.scoreMatch ?? defaultScoreMatch\n let updated = 0\n let failed = 0\n\n const ownIds = documentIds[schema.slug] ?? []\n if (ownIds.length === 0) return { updated, failed }\n\n /** Rank candidate target IDs by semantic score relative to source. */\n const rankCandidates = (\n sourceCollection: string,\n sourceId: string,\n targetCollection: string,\n targetIds: string[],\n ) => {\n const srcDoc = options.documents?.[sourceCollection]?.[sourceId]\n if (!srcDoc) return shuffle(targetIds, rng)\n const scored = targetIds.map((id) => {\n const targetDoc = options.documents?.[targetCollection]?.[id]\n const score = targetDoc ? scoreMatch(srcDoc, targetDoc) : 0\n return { id, score }\n })\n // Sort by score desc, break ties with shuffle for variance\n scored.sort((a, b) => b.score - a.score)\n const bestScore = scored[0]?.score ?? 0\n if (bestScore === 0) return shuffle(targetIds, rng)\n return scored.map((s) => s.id)\n }\n\n for (const docId of ownIds) {\n const updateData: Record<string, unknown> = {}\n let hasUpdates = false\n\n for (const rel of schema.relationships) {\n const targetCollection = Array.isArray(rel.collection) ? rel.collection[0] : rel.collection\n\n if (!targetCollection) continue\n\n // Self-referential: assign 2–4 sibling IDs (excluding self)\n if (rel.isSelfReferential) {\n const siblings = ownIds.filter((id) => id !== docId)\n if (siblings.length === 0) continue\n\n const count = Math.min(siblings.length, 2 + Math.floor(rng() * 3))\n const ranked = rankCandidates(schema.slug, docId, schema.slug, siblings)\n const picked = ranked.slice(0, count)\n\n updateData[rel.field] = rel.hasMany ? picked : picked[0]\n hasUpdates = true\n continue\n }\n\n const targetIds = documentIds[targetCollection] ?? []\n if (targetIds.length === 0) continue\n\n const ranked = rankCandidates(schema.slug, docId, targetCollection, targetIds)\n\n if (rel.hasMany) {\n // Pick up to 3, preferring top-scored matches\n const count = Math.min(targetIds.length, 1 + Math.floor(rng() * 3))\n updateData[rel.field] = ranked.slice(0, count)\n } else {\n // Pick the top-scored match\n updateData[rel.field] = ranked[0]\n }\n\n hasUpdates = true\n }\n\n if (!hasUpdates) continue\n\n try {\n await payload.update({\n collection: schema.slug as Parameters<Payload['update']>[0]['collection'],\n id: docId,\n data: updateData,\n overrideAccess: true,\n req,\n })\n updated++\n } catch (err) {\n console.error(\n `[relationship-linker] Failed to update ${schema.slug}/${docId}:`,\n err instanceof Error ? err.message : String(err),\n )\n failed++\n }\n }\n\n return { updated, failed }\n}\n","/**\n * Minimal zero-dependency concurrency limiter.\n *\n * Problem: the existing bulk-runner calls payload.create() sequentially\n * per document. For large bulk jobs this serializes all I/O and also\n * makes rateLimit.maxConcurrentRequests / delayBetweenRequests (declared\n * in AIPluginConfig but never consumed) a no-op.\n *\n * This helper provides a p-limit-style primitive that honors the\n * configured limits without pulling a dependency.\n */\n\nexport type Limiter = {\n /** Schedule a task and wait for its result. Blocks when capacity is full. */\n <T>(task: () => Promise<T>): Promise<T>\n /** Number of tasks currently running */\n activeCount: () => number\n /** Number of tasks waiting for a slot */\n pendingCount: () => number\n}\n\nexport function createLimiter(\n options: { concurrency?: number; delayBetweenMs?: number } = {},\n): Limiter {\n const maxConcurrency = Math.max(1, options.concurrency ?? 1)\n const delayBetweenMs = Math.max(0, options.delayBetweenMs ?? 0)\n\n let active = 0\n const queue: Array<() => void> = []\n let lastDispatchAt = 0\n\n async function waitForDelay(): Promise<void> {\n if (delayBetweenMs <= 0) return\n // Loop because another task may dispatch during our sleep, resetting\n // lastDispatchAt; we must re-check rather than wake up early.\n // The first call (lastDispatchAt === 0) is allowed to dispatch immediately.\n while (lastDispatchAt !== 0) {\n const since = Date.now() - lastDispatchAt\n if (since >= delayBetweenMs) return\n await new Promise((r) => setTimeout(r, delayBetweenMs - since))\n }\n }\n\n async function acquire(): Promise<void> {\n await waitForDelay()\n\n if (active < maxConcurrency) {\n active++\n lastDispatchAt = Date.now()\n return\n }\n\n await new Promise<void>((resolve) => queue.push(resolve))\n // Re-check the delay gate after being woken up: the previous dispatch\n // may have been very recent, and we want every dispatch to respect\n // the minimum spacing regardless of whether the task queued or not.\n await waitForDelay()\n active++\n lastDispatchAt = Date.now()\n }\n\n function release(): void {\n active--\n const next = queue.shift()\n if (next) next()\n }\n\n const limiter = async <T>(task: () => Promise<T>): Promise<T> => {\n await acquire()\n try {\n return await task()\n } finally {\n release()\n }\n }\n\n return Object.assign(limiter, {\n activeCount: () => active,\n pendingCount: () => queue.length,\n }) as Limiter\n}\n","import { EventEmitter } from 'node:events'\nimport type { ProgressEvent } from '../types.js'\n\n/** Create a typed EventEmitter for progress events */\nexport function createProgressEmitter(): EventEmitter {\n return new EventEmitter()\n}\n\n/** Emit a progress event on the emitter */\nexport function emitProgress(emitter: EventEmitter, event: ProgressEvent): void {\n emitter.emit('progress', event)\n}\n","import type { EventEmitter } from 'node:events'\nimport type { Payload, PayloadRequest } from 'payload'\nimport type { FieldAdapterRegistry } from '../core/field-adapters.js'\nimport { DeletionLog } from '../generate/deletion-log.js'\nimport { linkRelationships } from '../generate/relationship-linker.js'\nimport type { AIProvider, CollectionSchema, ProgressEvent } from '../types.js'\nimport { createLimiter } from './concurrency.js'\nimport { emitProgress } from './progress-tracker.js'\n\nexport type BulkRunConfig = {\n theme: string\n counts: Record<string, number>\n provider: AIProvider\n rollbackOnError?: boolean\n mediaSource?: 'unsplash' | 'placeholder'\n /** Forwarded to GenerationContext.domain — replaces default ecommerce framing */\n domain?: string\n /** Forwarded to GenerationContext.includeBlocks — enables first-class Blocks support */\n includeBlocks?: boolean\n /** Max concurrent payload.create() calls per collection. Default: 1 (serial). */\n maxConcurrentCreates?: number\n /** Minimum ms between dispatches within a collection. Default: 0. */\n delayBetweenCreatesMs?: number\n /** Optional FieldTypeAdapter registry for custom field types */\n adapters?: FieldAdapterRegistry\n}\n\nexport type BulkRunResult = {\n created: Record<string, number>\n failed: Record<string, number>\n documentIds: Record<string, string[]>\n rolledBack: boolean\n elapsed: number\n}\n\n/**\n * Run bulk population of Payload collections in dependency order.\n *\n * Flow:\n * 1. Resolve creation order from schemas\n * 2. Filter to collections requested in config.counts\n * 3. For each collection: generate documents, create them, record in deletion log\n * 4. After all: link deferred relationships\n * 5. On error + rollbackOnError: rollback all created documents\n */\nexport async function runBulkPopulation(\n payload: Payload,\n req: PayloadRequest,\n schemas: CollectionSchema[],\n config: BulkRunConfig,\n emitter?: EventEmitter,\n): Promise<BulkRunResult> {\n const startTime = Date.now()\n\n // Lazy imports to avoid circular dependency issues at module load time\n const { resolveCreationOrder } = await import('../core/dependency-resolver.js')\n const { generateDocuments } = await import('../core/content-generator.js')\n\n const created: Record<string, number> = {}\n const failed: Record<string, number> = {}\n const documentIds: Record<string, string[]> = {}\n const documentBodies: Record<string, Record<string, Record<string, unknown>>> = {}\n const deletionLog = new DeletionLog()\n let rolledBack = false\n\n // Determine ordered list of slugs to process\n const orderedSlugs = resolveCreationOrder(schemas)\n const slugsToProcess = orderedSlugs.filter(\n (slug) => slug in config.counts && config.counts[slug] > 0,\n )\n\n const schemaMap = new Map(schemas.map((s) => [s.slug, s]))\n\n try {\n for (const slug of slugsToProcess) {\n const schema = schemaMap.get(slug)\n if (!schema) continue\n\n const count = config.counts[slug] ?? 0\n created[slug] = 0\n failed[slug] = 0\n documentIds[slug] = []\n\n let generatedDocs: Record<string, unknown>[]\n try {\n const result = await generateDocuments(config.provider, schema, {\n count,\n theme: config.theme,\n existingIds: documentIds,\n ...(config.domain !== undefined ? { domain: config.domain } : {}),\n ...(config.includeBlocks !== undefined ? { includeBlocks: config.includeBlocks } : {}),\n ...(config.adapters ? { adapters: config.adapters } : {}),\n })\n generatedDocs = result.documents\n } catch (err) {\n console.error(\n `[bulk-runner] Failed to generate documents for \"${slug}\":`,\n err instanceof Error ? err.message : String(err),\n )\n failed[slug] = count\n continue\n }\n\n const limiter = createLimiter({\n concurrency: config.maxConcurrentCreates ?? 1,\n delayBetweenMs: config.delayBetweenCreatesMs ?? 0,\n })\n\n await Promise.all(\n generatedDocs.map((doc) =>\n limiter(async () => {\n try {\n const record = await payload.create({\n collection: slug as Parameters<Payload['create']>[0]['collection'],\n data: doc as Record<string, unknown>,\n overrideAccess: true,\n req,\n })\n\n const id = String(record.id)\n deletionLog.record(slug, id)\n documentIds[slug].push(id)\n if (!documentBodies[slug]) documentBodies[slug] = {}\n documentBodies[slug][id] = doc as Record<string, unknown>\n created[slug]++\n } catch (err) {\n console.error(\n `[bulk-runner] Failed to create document in \"${slug}\":`,\n err instanceof Error ? err.message : String(err),\n )\n failed[slug]++\n }\n\n if (emitter) {\n const event: ProgressEvent = {\n phase: 'create',\n collection: slug,\n created: created[slug],\n failed: failed[slug],\n total: count,\n elapsed: Date.now() - startTime,\n }\n emitProgress(emitter, event)\n }\n }),\n ),\n )\n }\n\n // Deferred: link relationships across all processed collections\n for (const slug of slugsToProcess) {\n const schema = schemaMap.get(slug)\n if (!schema || schema.relationships.length === 0) continue\n\n try {\n await linkRelationships(payload, req, schema, documentIds, {\n documents: documentBodies,\n })\n } catch (err) {\n console.error(\n `[bulk-runner] Failed to link relationships for \"${slug}\":`,\n err instanceof Error ? err.message : String(err),\n )\n }\n }\n } catch (err) {\n console.error(\n '[bulk-runner] Unexpected error during bulk population:',\n err instanceof Error ? err.message : String(err),\n )\n\n if (config.rollbackOnError) {\n console.log('[bulk-runner] Rolling back created documents...')\n await deletionLog.rollback(payload, req)\n rolledBack = true\n }\n }\n\n return {\n created,\n failed,\n documentIds,\n rolledBack,\n elapsed: Date.now() - startTime,\n }\n}\n","import type { PayloadRequest } from 'payload'\nimport { z } from 'zod'\nimport { generateDocuments } from '../core/content-generator.js'\nimport { createRegistry } from '../core/field-adapters.js'\nimport { createProvider } from '../core/providers/base.js'\nimport { buildSchemaManifest } from '../core/schema-manifest.js'\nimport { readAllCollectionSchemas, readCollectionSchema } from '../core/schema-reader.js'\nimport { runBulkPopulation } from '../orchestrate/bulk-runner.js'\nimport type { AIPluginConfig } from '../types.js'\n\nexport type MCPTool = {\n name: string\n description: string\n parameters: Record<string, z.ZodTypeAny>\n handler: (\n args: Record<string, unknown>,\n req: PayloadRequest,\n extra: unknown,\n ) => Promise<{ content: Array<{ text: string; type: 'text' }> }>\n}\n\nexport function getAITools(pluginConfig: AIPluginConfig): MCPTool[] {\n // Shared adapter registry: built from whatever custom adapters the user\n // supplied in aiPlugin({ fieldAdapters: [...] }). Built-in native types\n // go through the legacy switch in prompt-builder; the registry is only\n // consulted for types the switch doesn't know about.\n const adapters = pluginConfig.fieldAdapters\n ? createRegistry(pluginConfig.fieldAdapters)\n : undefined\n\n return [\n {\n name: 'populateCollection',\n description: 'Generate and insert AI-created documents into a Payload CMS collection.',\n parameters: {\n collection: z.string().describe('The collection slug to populate'),\n count: z.number().min(1).max(100).describe('Number of documents to generate'),\n theme: z\n .string()\n .optional()\n .describe('Optional theme or topic to guide content generation'),\n includeBlocks: z\n .boolean()\n .optional()\n .describe(\n 'Opt-in to generate Blocks (layout) fields with block-catalog awareness. Defaults to false for backwards compatibility.',\n ),\n domain: z\n .string()\n .optional()\n .describe(\n 'Optional domain framing (e.g. \"blog\", \"news site\"). Replaces the default \"ecommerce platform\" phrasing. Empty string drops framing entirely.',\n ),\n },\n async handler(args, req) {\n const collection = args.collection as string\n const count = args.count as number\n const theme = (args.theme as string | undefined) ?? 'general'\n const includeBlocks = args.includeBlocks as boolean | undefined\n const domainArg = args.domain as string | undefined\n const effectiveDomain = domainArg !== undefined ? domainArg : pluginConfig.domain\n\n try {\n const provider = createProvider({\n provider: pluginConfig.provider,\n apiKey: process.env[pluginConfig.apiKeyEnvVar] ?? '',\n baseUrl: pluginConfig.baseUrl,\n model: pluginConfig.model,\n })\n\n const schema = readCollectionSchema(req.payload, collection, {\n nonPopulatableSlugs: pluginConfig.nonPopulatableSlugs,\n replaceDefaults: pluginConfig.replaceNonPopulatableDefaults,\n })\n const result = await generateDocuments(provider, schema, {\n count,\n theme,\n ...(effectiveDomain !== undefined ? { domain: effectiveDomain } : {}),\n ...(includeBlocks !== undefined ? { includeBlocks } : {}),\n ...(adapters ? { adapters } : {}),\n })\n\n let created = 0\n let failed = 0\n\n for (const doc of result.documents) {\n try {\n await req.payload.create({\n collection: collection as Parameters<typeof req.payload.create>[0]['collection'],\n data: doc as Record<string, unknown>,\n overrideAccess: true,\n req,\n })\n created++\n } catch {\n failed++\n }\n }\n\n const text = `Populated \"${collection}\": ${created} created, ${failed} failed (requested ${count}).`\n return { content: [{ type: 'text', text }] }\n } catch (err) {\n const text = `Error populating \"${collection}\": ${err instanceof Error ? err.message : String(err)}`\n return { content: [{ type: 'text', text }] }\n }\n },\n },\n\n {\n name: 'bulkPopulate',\n description:\n 'Generate and insert AI-created documents across multiple Payload CMS collections at once.',\n parameters: {\n theme: z\n .string()\n .describe('Theme or topic to guide content generation across all collections'),\n counts: z\n .string()\n .describe('JSON map of collection slug to count, e.g. {\"posts\":5,\"categories\":3}'),\n includeBlocks: z\n .boolean()\n .optional()\n .describe('Opt-in to first-class Blocks (layout) generation across all collections.'),\n domain: z\n .string()\n .optional()\n .describe(\n 'Optional domain framing. Replaces default \"ecommerce platform\". Empty string drops framing.',\n ),\n },\n async handler(args, req) {\n const theme = args.theme as string\n const countsRaw = args.counts as string\n const includeBlocks = args.includeBlocks as boolean | undefined\n const domainArg = args.domain as string | undefined\n const effectiveDomain = domainArg !== undefined ? domainArg : pluginConfig.domain\n\n let counts: Record<string, number>\n try {\n counts = JSON.parse(countsRaw) as Record<string, number>\n } catch {\n return {\n content: [\n {\n type: 'text',\n text: 'Error: \"counts\" must be valid JSON, e.g. {\"posts\":5,\"categories\":3}',\n },\n ],\n }\n }\n\n try {\n const provider = createProvider({\n provider: pluginConfig.provider,\n apiKey: process.env[pluginConfig.apiKeyEnvVar] ?? '',\n baseUrl: pluginConfig.baseUrl,\n model: pluginConfig.model,\n })\n\n const schemas = readAllCollectionSchemas(req.payload, {\n nonPopulatableSlugs: pluginConfig.nonPopulatableSlugs,\n replaceDefaults: pluginConfig.replaceNonPopulatableDefaults,\n })\n const result = await runBulkPopulation(req.payload, req, schemas, {\n theme,\n counts,\n provider,\n rollbackOnError: pluginConfig.rollbackOnError,\n ...(effectiveDomain !== undefined ? { domain: effectiveDomain } : {}),\n ...(includeBlocks !== undefined ? { includeBlocks } : {}),\n ...(pluginConfig.rateLimit?.maxConcurrentRequests !== undefined\n ? { maxConcurrentCreates: pluginConfig.rateLimit.maxConcurrentRequests }\n : {}),\n ...(pluginConfig.rateLimit?.delayBetweenRequests !== undefined\n ? { delayBetweenCreatesMs: pluginConfig.rateLimit.delayBetweenRequests }\n : {}),\n ...(adapters ? { adapters } : {}),\n })\n\n const summary = Object.entries(result.created)\n .map(([slug, n]) => `${slug}: ${n} created, ${result.failed[slug] ?? 0} failed`)\n .join('\\n')\n\n const text = `Bulk population complete in ${result.elapsed}ms${result.rolledBack ? ' (rolled back)' : ''}:\\n${summary}`\n return { content: [{ type: 'text', text }] }\n } catch (err) {\n const text = `Error during bulk population: ${err instanceof Error ? err.message : String(err)}`\n return { content: [{ type: 'text', text }] }\n }\n },\n },\n\n {\n name: 'getCollectionSchema',\n description: 'Return the analyzed schema for a single Payload CMS collection as JSON.',\n parameters: {\n collection: z.string().describe('The collection slug to inspect'),\n },\n async handler(args, req) {\n const collection = args.collection as string\n\n try {\n const schema = readCollectionSchema(req.payload, collection)\n const text = JSON.stringify(schema, null, 2)\n return { content: [{ type: 'text', text }] }\n } catch (err) {\n const text = `Error reading schema for \"${collection}\": ${err instanceof Error ? err.message : String(err)}`\n return { content: [{ type: 'text', text }] }\n }\n },\n },\n\n {\n name: 'listCollections',\n description:\n 'List all Payload CMS collections with their field counts and whether they can be auto-populated.',\n parameters: {},\n async handler(_args, req) {\n try {\n const schemas = readAllCollectionSchemas(req.payload)\n const lines = schemas.map(\n (s) => `- ${s.slug}: ${s.fields.length} fields, populatable=${s.populatable}`,\n )\n const text = `Collections (${schemas.length}):\\n${lines.join('\\n')}`\n return { content: [{ type: 'text', text }] }\n } catch (err) {\n const text = `Error listing collections: ${err instanceof Error ? err.message : String(err)}`\n return { content: [{ type: 'text', text }] }\n }\n },\n },\n\n {\n name: 'describePayloadProject',\n description:\n 'Return a complete Schema Manifest for this Payload project: every collection, deduplicated block catalog, lexical editor capabilities, custom field types, upload + auth collections, locales, and a config fingerprint. Call this once at the start of a session to discover the project shape without asking per-collection.',\n parameters: {\n includeFields: z\n .boolean()\n .optional()\n .describe(\n 'When true (default), each collection includes its full field tree. When false, only slugs and summary counts are returned — useful for reducing token usage on large projects.',\n ),\n },\n async handler(args, req) {\n try {\n const includeFields = args.includeFields !== false\n const manifest = buildSchemaManifest(req.payload)\n\n if (!includeFields) {\n const slim = {\n ...manifest,\n collections: manifest.collections.map((c) => ({\n slug: c.slug,\n fieldCount: c.fields.length,\n relationshipCount: c.relationships.length,\n populatable: c.populatable,\n requiredFields: c.requiredFields,\n })),\n }\n return { content: [{ type: 'text', text: JSON.stringify(slim, null, 2) }] }\n }\n\n return { content: [{ type: 'text', text: JSON.stringify(manifest, null, 2) }] }\n } catch (err) {\n const text = `Error building schema manifest: ${err instanceof Error ? err.message : String(err)}`\n return { content: [{ type: 'text', text }] }\n }\n },\n },\n ]\n}\n","import type { CollectionAfterChangeHook } from 'payload'\nimport type { AIPluginConfig } from '../../types.js'\n\n/**\n * Fallback alt text derived from the filename — keeps the hook useful\n * when AI vision is unavailable (no API key, no URL, vision call fails).\n */\nfunction altFromFilename(filename: string): string {\n const cleanName = filename.replace(/[-_]/g, ' ').replace(/\\.[^.]+$/, '')\n return `${cleanName} - uploaded media`\n}\n\n/**\n * Fetch the uploaded image into a Buffer so we can hand it to\n * provider.analyzeImage. Supports absolute URLs (doc.url) and, if the\n * payload is configured to serve uploads, falls back to filename + serverURL.\n */\nasync function fetchImageBuffer(url: string): Promise<Buffer | null> {\n try {\n const res = await fetch(url)\n if (!res.ok) return null\n const ct = res.headers.get('content-type') ?? ''\n if (!ct.startsWith('image/')) return null\n const ab = await res.arrayBuffer()\n return Buffer.from(ab)\n } catch {\n return null\n }\n}\n\n/**\n * Creates an afterChange hook for the media collection that auto-generates alt text.\n * Only runs on 'create' operations where alt text is empty.\n *\n * Uses provider.analyzeImage() when a URL + API key are available, and\n * falls back to a filename-derived placeholder otherwise.\n */\nexport function createAltTextHook(pluginConfig: AIPluginConfig): CollectionAfterChangeHook {\n return async ({ doc, operation, req }) => {\n if (operation !== 'create') return doc\n\n // Only process if alt is empty\n if (doc.alt && String(doc.alt).trim() !== '') return doc\n\n const apiKey = process.env[pluginConfig.apiKeyEnvVar]\n if (!apiKey) return doc\n\n const filename = doc.filename ? String(doc.filename) : 'image'\n const rawUrl = doc.url ? String(doc.url) : ''\n\n try {\n let altText = altFromFilename(filename)\n\n // Try the vision path: fetch the image, hand it to the provider.\n if (rawUrl) {\n try {\n const { createProvider } = await import('../../core/providers/base.js')\n const provider = createProvider({\n provider: pluginConfig.provider,\n apiKey,\n baseUrl: pluginConfig.baseUrl,\n model: pluginConfig.model,\n })\n const buffer = await fetchImageBuffer(rawUrl)\n if (buffer && buffer.length > 0) {\n const vision = await provider.analyzeImage(buffer)\n const trimmed = vision.trim()\n if (trimmed) altText = trimmed\n }\n } catch (visionErr) {\n console.warn(\n '[@karixi/payload-ai] Vision alt text failed, falling back to filename:',\n visionErr instanceof Error ? visionErr.message : String(visionErr),\n )\n }\n }\n\n await req.payload.update({\n collection: 'media',\n id: doc.id as string,\n data: { alt: altText },\n req,\n })\n\n return { ...doc, alt: altText }\n } catch (err) {\n console.warn('[@karixi/payload-ai] Alt text generation failed:', err)\n return doc\n }\n }\n}\n","import type { CollectionBeforeChangeHook } from 'payload'\nimport type { AIPluginConfig } from '../../types.js'\n\n/**\n * Creates a beforeChange hook that auto-fills empty fields with AI content.\n * Only runs on 'create' operations. Only fills fields configured in AIPluginConfig.\n */\nexport function createSmartDefaultsHook(\n pluginConfig: AIPluginConfig,\n collectionSlug: string,\n): CollectionBeforeChangeHook {\n return async ({ data, operation, req }) => {\n if (operation !== 'create') return data\n\n const collectionConfig = pluginConfig.collections?.[collectionSlug]\n if (!collectionConfig?.fields) return data\n\n const apiKey = process.env[pluginConfig.apiKeyEnvVar]\n if (!apiKey) return data\n\n // Import dynamically to avoid circular deps\n const { createProvider } = await import('../../core/providers/base.js')\n const { readCollectionSchema } = await import('../../core/schema-reader.js')\n const { generateDocuments } = await import('../../core/content-generator.js')\n\n const provider = createProvider({\n provider: pluginConfig.provider,\n apiKey,\n baseUrl: pluginConfig.baseUrl,\n model: pluginConfig.model,\n })\n\n // Collect every empty enabled field into a single sub-schema. Inject\n // each field's per-field prompt as metadata.aiHint so the batched\n // generation prompt preserves the guidance. Then issue ONE generate\n // call for all fields instead of N.\n const schema = readCollectionSchema(req.payload, collectionSlug, {\n nonPopulatableSlugs: pluginConfig.nonPopulatableSlugs,\n replaceDefaults: pluginConfig.replaceNonPopulatableDefaults,\n })\n\n const fieldsToFill: typeof schema.fields = []\n const fieldPrompts: string[] = []\n for (const [fieldName, fieldConfig] of Object.entries(collectionConfig.fields)) {\n if (!fieldConfig.enabled) continue\n const currentValue = (data as Record<string, unknown>)[fieldName]\n if (currentValue !== undefined && currentValue !== null && currentValue !== '') continue\n const fieldSchema = schema.fields.find((f) => f.name === fieldName)\n if (!fieldSchema) continue\n const hinted = fieldConfig.prompt\n ? {\n ...fieldSchema,\n metadata: { ...fieldSchema.metadata, aiHint: fieldConfig.prompt },\n }\n : fieldSchema\n fieldsToFill.push(hinted)\n if (fieldConfig.prompt) fieldPrompts.push(fieldConfig.prompt)\n }\n\n if (fieldsToFill.length === 0) return data\n\n try {\n const result = await generateDocuments(\n provider,\n {\n ...schema,\n fields: fieldsToFill,\n requiredFields: fieldsToFill.filter((f) => f.required).map((f) => f.name),\n },\n {\n count: 1,\n // Combined theme from per-field prompts gives the model a single\n // coherent brief across all fields being filled at once.\n theme: fieldPrompts.length > 0 ? fieldPrompts.join('; ') : undefined,\n ...(pluginConfig.domain !== undefined ? { domain: pluginConfig.domain } : {}),\n },\n )\n\n const generated = result.documents[0]\n if (generated) {\n for (const field of fieldsToFill) {\n const value = generated[field.name]\n if (value !== undefined) {\n ;(data as Record<string, unknown>)[field.name] = value\n }\n }\n }\n } catch (err) {\n console.warn(\n `[@karixi/payload-ai] Smart defaults batch failed for ${fieldsToFill\n .map((f) => f.name)\n .join(', ')}:`,\n err,\n )\n }\n\n return data\n }\n}\n","import type { CollectionConfig, Config, Payload } from 'payload'\nimport { createAltTextHook } from './admin/hooks/afterUpload.js'\nimport { createSmartDefaultsHook } from './admin/hooks/beforeChange.js'\nimport type { AIPluginConfig } from './types.js'\n\n/**\n * Payload AI Plugin — adds AI-powered data generation and admin features.\n * Auto-injects MCP custom tools if @payloadcms/plugin-mcp is present.\n */\nexport function aiPlugin(config: AIPluginConfig) {\n return (incomingConfig: Config): Config => {\n // Validate API key env var exists at init time\n const apiKey = process.env[config.apiKeyEnvVar]\n if (!apiKey) {\n console.warn(\n `[@karixi/payload-ai] Warning: ${config.apiKeyEnvVar} environment variable is not set. AI features will not work.`,\n )\n }\n\n const existingOnInit = incomingConfig.onInit\n\n // TODO Phase 2: Inject schema introspection + AI generation\n // TODO Phase 3: Register MCP custom tools (auto-detect mcpPlugin)\n\n let collections = incomingConfig.collections\n\n if (config.features?.adminUI && collections) {\n collections = collections.map((collection: CollectionConfig) => {\n const slug = collection.slug\n const updatedHooks = { ...collection.hooks }\n\n // Add beforeChange smart defaults hook for configured collections\n if (config.collections?.[slug]) {\n updatedHooks.beforeChange = [\n ...(updatedHooks.beforeChange ?? []),\n createSmartDefaultsHook(config, slug),\n ]\n }\n\n // Add afterChange alt text hook for media collection\n if (slug === 'media') {\n updatedHooks.afterChange = [\n ...(updatedHooks.afterChange ?? []),\n createAltTextHook(config),\n ]\n }\n\n if (\n updatedHooks.beforeChange !== collection.hooks?.beforeChange ||\n updatedHooks.afterChange !== collection.hooks?.afterChange\n ) {\n return { ...collection, hooks: updatedHooks }\n }\n\n return collection\n })\n }\n\n return {\n ...incomingConfig,\n collections,\n onInit: async (payload: Payload) => {\n if (existingOnInit) await existingOnInit(payload)\n console.log(`[@karixi/payload-ai] Plugin initialized. Provider: ${config.provider}`)\n // TODO Phase 4: Initialize admin UI components\n // TODO Phase 5: Initialize dev tools\n },\n }\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"schema-reader-N3-mDRkR.mjs","names":[],"sources":["../src/core/field-analyzer.ts","../src/core/schema-reader.ts"],"sourcesContent":["import type {\n ArrayField,\n BlocksField,\n CollapsibleField,\n Field,\n GroupField,\n RadioField,\n RelationshipField,\n RichTextField,\n RowField,\n SelectField,\n Tab,\n TabsField,\n UIField,\n UploadField,\n} from 'payload'\nimport type { BlockSchema, FieldMetadata, FieldSchema } from '../types.js'\n\nfunction normalizeOptions(options: readonly unknown[]): Array<{ label: string; value: string }> {\n return options.map((opt) => {\n if (typeof opt === 'string') {\n return { label: opt, value: opt }\n }\n if (typeof opt === 'object' && opt !== null && 'value' in opt) {\n const o = opt as { label?: unknown; value: unknown }\n const label =\n typeof o.label === 'string'\n ? o.label\n : typeof o.label === 'object' && o.label !== null\n ? (Object.values(o.label)[0] ?? String(o.value))\n : String(o.value)\n return { label, value: String(o.value) }\n }\n return { label: String(opt), value: String(opt) }\n })\n}\n\nfunction buildPath(parentPath: string | undefined, name: string): string {\n return parentPath ? `${parentPath}.${name}` : name\n}\n\nfunction analyzeTabs(tabs: Tab[], parentPath?: string): FieldSchema[] {\n const result: FieldSchema[] = []\n for (const tab of tabs) {\n // Named tabs create a named path segment; unnamed tabs are transparent\n const tabName = 'name' in tab && typeof tab.name === 'string' ? tab.name : undefined\n const tabPath = tabName ? buildPath(parentPath, tabName) : parentPath\n result.push(...analyzeFields(tab.fields, tabPath))\n }\n return result\n}\n\nfunction isSelectField(field: Field): field is SelectField {\n return field.type === 'select'\n}\n\nfunction isRadioField(field: Field): field is RadioField {\n return field.type === 'radio'\n}\n\nfunction isRelationshipField(field: Field): field is RelationshipField {\n return field.type === 'relationship'\n}\n\nfunction isUploadField(field: Field): field is UploadField {\n return field.type === 'upload'\n}\n\nfunction isArrayField(field: Field): field is ArrayField {\n return field.type === 'array'\n}\n\nfunction isGroupField(field: Field): field is GroupField {\n return field.type === 'group'\n}\n\nfunction isCollapsibleField(field: Field): field is CollapsibleField {\n return field.type === 'collapsible'\n}\n\nfunction isRowField(field: Field): field is RowField {\n return field.type === 'row'\n}\n\nfunction isBlocksField(field: Field): field is BlocksField {\n return field.type === 'blocks'\n}\n\nfunction isTabsField(field: Field): field is TabsField {\n return field.type === 'tabs'\n}\n\nfunction isUIField(field: Field): field is UIField {\n return field.type === 'ui'\n}\n\nfunction isRichTextField(field: Field): field is RichTextField {\n return field.type === 'richText'\n}\n\nfunction pickStringLabel(value: unknown): string | undefined {\n if (typeof value === 'string') return value\n if (typeof value === 'object' && value !== null) {\n const first = Object.values(value as Record<string, unknown>).find(\n (v) => typeof v === 'string' && v.length > 0,\n )\n if (typeof first === 'string') return first\n }\n return undefined\n}\n\nfunction extractFieldMetadata(field: Field): FieldMetadata | undefined {\n const meta: FieldMetadata = {}\n const admin = (field as { admin?: Record<string, unknown> }).admin\n if (admin) {\n const description = pickStringLabel(admin.description)\n if (description) meta.description = description\n const placeholder = pickStringLabel(admin.placeholder)\n if (placeholder) meta.placeholder = placeholder\n if (admin.readOnly === true) meta.readOnly = true\n const custom = admin.custom as Record<string, unknown> | undefined\n if (custom && typeof custom.aiHint === 'string') meta.aiHint = custom.aiHint\n }\n\n const custom = (field as { custom?: Record<string, unknown> }).custom\n if (custom && typeof custom.aiHint === 'string' && meta.aiHint === undefined) {\n meta.aiHint = custom.aiHint\n }\n\n const anyField = field as Record<string, unknown>\n if ('defaultValue' in anyField) {\n const dv = anyField.defaultValue\n if (typeof dv !== 'function') meta.defaultValue = dv\n }\n if (typeof anyField.minLength === 'number') meta.minLength = anyField.minLength as number\n if (typeof anyField.maxLength === 'number') meta.maxLength = anyField.maxLength as number\n if (typeof anyField.min === 'number') meta.min = anyField.min as number\n if (typeof anyField.max === 'number') meta.max = anyField.max as number\n if (anyField.unique === true) meta.unique = true\n if (anyField.localized === true) meta.localized = true\n\n return Object.keys(meta).length > 0 ? meta : undefined\n}\n\nfunction analyzeBlock(block: unknown, parentPath: string): BlockSchema {\n const b = block as {\n slug: string\n fields: Field[]\n labels?: { singular?: unknown; plural?: unknown }\n imageAltText?: string\n interfaceName?: string\n admin?: { custom?: Record<string, unknown> }\n }\n const subFields = analyzeFields(b.fields, parentPath)\n const required = subFields.filter((f) => f.required).map((f) => f.name)\n const label = pickStringLabel(b.labels?.singular)\n const description = pickStringLabel(b.admin?.custom?.description)\n const out: BlockSchema = {\n slug: b.slug,\n fields: subFields,\n ...(label ? { label } : {}),\n ...(b.imageAltText ? { imageAltText: b.imageAltText } : {}),\n ...(description ? { description } : {}),\n ...(required.length > 0 ? { requiredFields: required } : {}),\n }\n return out\n}\n\nfunction detectLexicalFeatures(field: RichTextField): string[] {\n const features: string[] = []\n const editor = field.editor\n if (!editor || typeof editor !== 'object') return features\n // Lexical editor config is often stored in editor.editorConfig or similar\n // We do a best-effort detection via known property names\n const editorObj = editor as Record<string, unknown>\n const editorConfig = editorObj.editorConfig ?? editorObj.lexicalConfig ?? editorObj\n if (typeof editorConfig === 'object' && editorConfig !== null) {\n const cfg = editorConfig as Record<string, unknown>\n const featureList = cfg.features ?? cfg.resolvedFeatures\n if (Array.isArray(featureList)) {\n for (const f of featureList) {\n if (f && typeof f === 'object') {\n const fObj = f as Record<string, unknown>\n const key = fObj.key ?? fObj.feature ?? fObj.name\n if (typeof key === 'string') features.push(key)\n }\n }\n }\n }\n return features\n}\n\nexport function analyzeFields(fields: Field[], parentPath?: string): FieldSchema[] {\n const result: FieldSchema[] = []\n\n for (const field of fields) {\n // Skip UI fields — they carry no data\n if (isUIField(field)) continue\n\n if (isTabsField(field)) {\n result.push(...analyzeTabs(field.tabs, parentPath))\n continue\n }\n\n if (isRowField(field) || isCollapsibleField(field)) {\n result.push(...analyzeFields(field.fields, parentPath))\n continue\n }\n\n // Fields from here need a name\n const namedField = field as Field & { name?: string }\n if (!('name' in namedField) || typeof namedField.name !== 'string') {\n // Unnamed group/collapsible already handled above; skip anything else unnamed\n continue\n }\n\n const name = namedField.name\n const path = buildPath(parentPath, name)\n const required = 'required' in field && field.required === true\n\n const metadata = extractFieldMetadata(field)\n const metaPart = metadata ? { metadata } : {}\n\n if (isRelationshipField(field)) {\n const _isSelfRef = false // computed in schema-reader at collection level\n const schema: FieldSchema = {\n name,\n type: 'relationship',\n required,\n path,\n relationTo: field.relationTo,\n hasMany: field.hasMany === true,\n ...metaPart,\n }\n result.push(schema)\n continue\n }\n\n if (isUploadField(field)) {\n result.push({\n name,\n type: 'upload',\n required,\n path,\n relationTo: field.relationTo,\n hasMany: false,\n ...metaPart,\n })\n continue\n }\n\n if (isSelectField(field)) {\n result.push({\n name,\n type: 'select',\n required,\n path,\n options: normalizeOptions(field.options),\n hasMany: field.hasMany === true,\n ...metaPart,\n })\n continue\n }\n\n if (isRadioField(field)) {\n result.push({\n name,\n type: 'radio',\n required,\n path,\n options: normalizeOptions(field.options),\n ...metaPart,\n })\n continue\n }\n\n if (isArrayField(field)) {\n const subFields = analyzeFields(field.fields, path)\n result.push({\n name,\n type: 'array',\n required,\n path,\n fields: subFields,\n ...metaPart,\n })\n continue\n }\n\n if (isGroupField(field)) {\n // GroupField can be named or unnamed; we already checked name above\n const subFields = analyzeFields(field.fields as Field[], path)\n result.push({\n name,\n type: 'group',\n required,\n path,\n fields: subFields,\n ...metaPart,\n })\n continue\n }\n\n if (isBlocksField(field)) {\n const blocks = field.blocks.map((block) => analyzeBlock(block, path))\n result.push({\n name,\n type: 'blocks',\n required,\n path,\n blocks,\n ...metaPart,\n })\n continue\n }\n\n if (isRichTextField(field)) {\n const lexicalFeatures = detectLexicalFeatures(field)\n result.push({\n name,\n type: 'richText',\n required,\n path,\n ...(lexicalFeatures.length > 0 ? { lexicalFeatures } : {}),\n ...metaPart,\n })\n continue\n }\n\n // All remaining scalar field types\n result.push({\n name,\n type: field.type,\n required,\n path,\n ...metaPart,\n })\n }\n\n return result\n}\n","import type { Payload } from 'payload'\nimport type { CollectionSchema, FieldSchema, RelationshipInfo } from '../types.js'\nimport { analyzeFields } from './field-analyzer.js'\n\n/** Collections that should not be auto-populated by default */\nconst NON_POPULATABLE_SLUGS = new Set(['orders', 'carts', 'transactions'])\n\nexport type SchemaReaderOptions = {\n /** Extra slugs to mark as non-populatable (merged with defaults) */\n nonPopulatableSlugs?: string[]\n /**\n * When true, `nonPopulatableSlugs` REPLACES the built-in defaults rather\n * than merging with them. Use for non-ecommerce projects.\n */\n replaceDefaults?: boolean\n}\n\nfunction extractRelationships(fields: FieldSchema[], collectionSlug: string): RelationshipInfo[] {\n const result: RelationshipInfo[] = []\n\n for (const field of fields) {\n if (field.type === 'relationship' || field.type === 'upload') {\n if (field.relationTo !== undefined) {\n const collections = Array.isArray(field.relationTo) ? field.relationTo : [field.relationTo]\n\n for (const col of collections) {\n result.push({\n field: field.path,\n collection: field.relationTo,\n hasMany: field.hasMany === true,\n isSelfReferential: col === collectionSlug,\n })\n }\n }\n }\n\n // Recurse into nested fields\n if (field.fields && field.fields.length > 0) {\n result.push(...extractRelationships(field.fields, collectionSlug))\n }\n\n if (field.blocks) {\n for (const block of field.blocks) {\n result.push(...extractRelationships(block.fields, collectionSlug))\n }\n }\n }\n\n return result\n}\n\nfunction collectRequiredPaths(fields: FieldSchema[]): string[] {\n const required: string[] = []\n for (const field of fields) {\n if (field.required) required.push(field.path)\n if (field.fields) required.push(...collectRequiredPaths(field.fields))\n if (field.blocks) {\n for (const block of field.blocks) {\n required.push(...collectRequiredPaths(block.fields))\n }\n }\n }\n return required\n}\n\nfunction collectLexicalFeatures(fields: FieldSchema[]): string[] {\n const features: string[] = []\n for (const field of fields) {\n if (field.type === 'richText' && field.lexicalFeatures) {\n features.push(...field.lexicalFeatures)\n }\n if (field.fields) features.push(...collectLexicalFeatures(field.fields))\n if (field.blocks) {\n for (const block of field.blocks) {\n features.push(...collectLexicalFeatures(block.fields))\n }\n }\n }\n return [...new Set(features)]\n}\n\nexport function readCollectionSchema(\n payload: Payload,\n slug: string,\n options?: SchemaReaderOptions,\n): CollectionSchema {\n const collection = payload.collections[slug]\n if (!collection) {\n throw new Error(`Collection \"${slug}\" not found in payload.collections`)\n }\n\n const config = collection.config\n const fields = analyzeFields(config.fields)\n\n const nonPopulatable = options?.replaceDefaults\n ? new Set(options?.nonPopulatableSlugs ?? [])\n : new Set([...NON_POPULATABLE_SLUGS, ...(options?.nonPopulatableSlugs ?? [])])\n\n const relationships = extractRelationships(fields, slug)\n const requiredFields = collectRequiredPaths(fields)\n const lexicalFeatures = collectLexicalFeatures(fields)\n\n return {\n slug,\n fields,\n relationships,\n requiredFields,\n populatable: !nonPopulatable.has(slug),\n ...(lexicalFeatures.length > 0 ? { lexicalFeatures } : {}),\n }\n}\n\nexport function readAllCollectionSchemas(\n payload: Payload,\n options?: SchemaReaderOptions,\n): CollectionSchema[] {\n const slugs = Object.keys(payload.collections)\n return slugs.map((slug) => readCollectionSchema(payload, slug, options))\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"schema-reader-N3-mDRkR.mjs","names":[],"sources":["../src/core/field-analyzer.ts","../src/core/schema-reader.ts"],"sourcesContent":["import type {\n ArrayField,\n BlocksField,\n CollapsibleField,\n Field,\n GroupField,\n RadioField,\n RelationshipField,\n RichTextField,\n RowField,\n SelectField,\n Tab,\n TabsField,\n UIField,\n UploadField,\n} from 'payload'\nimport type { BlockSchema, FieldMetadata, FieldSchema } from '../types.js'\n\nfunction normalizeOptions(options: readonly unknown[]): Array<{ label: string; value: string }> {\n return options.map((opt) => {\n if (typeof opt === 'string') {\n return { label: opt, value: opt }\n }\n if (typeof opt === 'object' && opt !== null && 'value' in opt) {\n const o = opt as { label?: unknown; value: unknown }\n const label =\n typeof o.label === 'string'\n ? o.label\n : typeof o.label === 'object' && o.label !== null\n ? (Object.values(o.label)[0] ?? String(o.value))\n : String(o.value)\n return { label, value: String(o.value) }\n }\n return { label: String(opt), value: String(opt) }\n })\n}\n\nfunction buildPath(parentPath: string | undefined, name: string): string {\n return parentPath ? `${parentPath}.${name}` : name\n}\n\nfunction analyzeTabs(tabs: Tab[], parentPath?: string): FieldSchema[] {\n const result: FieldSchema[] = []\n for (const tab of tabs) {\n // Named tabs create a named path segment; unnamed tabs are transparent\n const tabName = 'name' in tab && typeof tab.name === 'string' ? tab.name : undefined\n const tabPath = tabName ? buildPath(parentPath, tabName) : parentPath\n result.push(...analyzeFields(tab.fields, tabPath))\n }\n return result\n}\n\nfunction isSelectField(field: Field): field is SelectField {\n return field.type === 'select'\n}\n\nfunction isRadioField(field: Field): field is RadioField {\n return field.type === 'radio'\n}\n\nfunction isRelationshipField(field: Field): field is RelationshipField {\n return field.type === 'relationship'\n}\n\nfunction isUploadField(field: Field): field is UploadField {\n return field.type === 'upload'\n}\n\nfunction isArrayField(field: Field): field is ArrayField {\n return field.type === 'array'\n}\n\nfunction isGroupField(field: Field): field is GroupField {\n return field.type === 'group'\n}\n\nfunction isCollapsibleField(field: Field): field is CollapsibleField {\n return field.type === 'collapsible'\n}\n\nfunction isRowField(field: Field): field is RowField {\n return field.type === 'row'\n}\n\nfunction isBlocksField(field: Field): field is BlocksField {\n return field.type === 'blocks'\n}\n\nfunction isTabsField(field: Field): field is TabsField {\n return field.type === 'tabs'\n}\n\nfunction isUIField(field: Field): field is UIField {\n return field.type === 'ui'\n}\n\nfunction isRichTextField(field: Field): field is RichTextField {\n return field.type === 'richText'\n}\n\nfunction pickStringLabel(value: unknown): string | undefined {\n if (typeof value === 'string') return value\n if (typeof value === 'object' && value !== null) {\n const first = Object.values(value as Record<string, unknown>).find(\n (v) => typeof v === 'string' && v.length > 0,\n )\n if (typeof first === 'string') return first\n }\n return undefined\n}\n\nfunction extractFieldMetadata(field: Field): FieldMetadata | undefined {\n const meta: FieldMetadata = {}\n const admin = (field as { admin?: Record<string, unknown> }).admin\n if (admin) {\n const description = pickStringLabel(admin.description)\n if (description) meta.description = description\n const placeholder = pickStringLabel(admin.placeholder)\n if (placeholder) meta.placeholder = placeholder\n if (admin.readOnly === true) meta.readOnly = true\n const custom = admin.custom as Record<string, unknown> | undefined\n if (custom && typeof custom.aiHint === 'string') meta.aiHint = custom.aiHint\n }\n\n const custom = (field as { custom?: Record<string, unknown> }).custom\n if (custom && typeof custom.aiHint === 'string' && meta.aiHint === undefined) {\n meta.aiHint = custom.aiHint\n }\n\n const anyField = field as Record<string, unknown>\n if ('defaultValue' in anyField) {\n const dv = anyField.defaultValue\n if (typeof dv !== 'function') meta.defaultValue = dv\n }\n if (typeof anyField.minLength === 'number') meta.minLength = anyField.minLength as number\n if (typeof anyField.maxLength === 'number') meta.maxLength = anyField.maxLength as number\n if (typeof anyField.min === 'number') meta.min = anyField.min as number\n if (typeof anyField.max === 'number') meta.max = anyField.max as number\n if (anyField.unique === true) meta.unique = true\n if (anyField.localized === true) meta.localized = true\n\n return Object.keys(meta).length > 0 ? meta : undefined\n}\n\nfunction analyzeBlock(block: unknown, parentPath: string): BlockSchema {\n const b = block as {\n slug: string\n fields: Field[]\n labels?: { singular?: unknown; plural?: unknown }\n imageAltText?: string\n interfaceName?: string\n admin?: { custom?: Record<string, unknown> }\n }\n const subFields = analyzeFields(b.fields, parentPath)\n const required = subFields.filter((f) => f.required).map((f) => f.name)\n const label = pickStringLabel(b.labels?.singular)\n const description = pickStringLabel(b.admin?.custom?.description)\n const out: BlockSchema = {\n slug: b.slug,\n fields: subFields,\n ...(label ? { label } : {}),\n ...(b.imageAltText ? { imageAltText: b.imageAltText } : {}),\n ...(description ? { description } : {}),\n ...(required.length > 0 ? { requiredFields: required } : {}),\n }\n return out\n}\n\nfunction detectLexicalFeatures(field: RichTextField): string[] {\n const features: string[] = []\n const editor = field.editor\n if (!editor || typeof editor !== 'object') return features\n // Lexical editor config is often stored in editor.editorConfig or similar\n // We do a best-effort detection via known property names\n const editorObj = editor as Record<string, unknown>\n const editorConfig = editorObj.editorConfig ?? editorObj.lexicalConfig ?? editorObj\n if (typeof editorConfig === 'object' && editorConfig !== null) {\n const cfg = editorConfig as Record<string, unknown>\n const featureList = cfg.features ?? cfg.resolvedFeatures\n if (Array.isArray(featureList)) {\n for (const f of featureList) {\n if (f && typeof f === 'object') {\n const fObj = f as Record<string, unknown>\n const key = fObj.key ?? fObj.feature ?? fObj.name\n if (typeof key === 'string') features.push(key)\n }\n }\n }\n }\n return features\n}\n\nexport function analyzeFields(fields: Field[], parentPath?: string): FieldSchema[] {\n const result: FieldSchema[] = []\n\n for (const field of fields) {\n // Skip UI fields — they carry no data\n if (isUIField(field)) continue\n\n if (isTabsField(field)) {\n result.push(...analyzeTabs(field.tabs, parentPath))\n continue\n }\n\n if (isRowField(field) || isCollapsibleField(field)) {\n result.push(...analyzeFields(field.fields, parentPath))\n continue\n }\n\n // Fields from here need a name\n const namedField = field as Field & { name?: string }\n if (!('name' in namedField) || typeof namedField.name !== 'string') {\n // Unnamed group/collapsible already handled above; skip anything else unnamed\n continue\n }\n\n const name = namedField.name\n const path = buildPath(parentPath, name)\n const required = 'required' in field && field.required === true\n\n const metadata = extractFieldMetadata(field)\n const metaPart = metadata ? { metadata } : {}\n\n if (isRelationshipField(field)) {\n const _isSelfRef = false // computed in schema-reader at collection level\n const schema: FieldSchema = {\n name,\n type: 'relationship',\n required,\n path,\n relationTo: field.relationTo,\n hasMany: field.hasMany === true,\n ...metaPart,\n }\n result.push(schema)\n continue\n }\n\n if (isUploadField(field)) {\n result.push({\n name,\n type: 'upload',\n required,\n path,\n relationTo: field.relationTo,\n hasMany: false,\n ...metaPart,\n })\n continue\n }\n\n if (isSelectField(field)) {\n result.push({\n name,\n type: 'select',\n required,\n path,\n options: normalizeOptions(field.options),\n hasMany: field.hasMany === true,\n ...metaPart,\n })\n continue\n }\n\n if (isRadioField(field)) {\n result.push({\n name,\n type: 'radio',\n required,\n path,\n options: normalizeOptions(field.options),\n ...metaPart,\n })\n continue\n }\n\n if (isArrayField(field)) {\n const subFields = analyzeFields(field.fields, path)\n result.push({\n name,\n type: 'array',\n required,\n path,\n fields: subFields,\n ...metaPart,\n })\n continue\n }\n\n if (isGroupField(field)) {\n // GroupField can be named or unnamed; we already checked name above\n const subFields = analyzeFields(field.fields as Field[], path)\n result.push({\n name,\n type: 'group',\n required,\n path,\n fields: subFields,\n ...metaPart,\n })\n continue\n }\n\n if (isBlocksField(field)) {\n const blocks = field.blocks.map((block) => analyzeBlock(block, path))\n result.push({\n name,\n type: 'blocks',\n required,\n path,\n blocks,\n ...metaPart,\n })\n continue\n }\n\n if (isRichTextField(field)) {\n const lexicalFeatures = detectLexicalFeatures(field)\n result.push({\n name,\n type: 'richText',\n required,\n path,\n ...(lexicalFeatures.length > 0 ? { lexicalFeatures } : {}),\n ...metaPart,\n })\n continue\n }\n\n // All remaining scalar field types\n result.push({\n name,\n type: field.type,\n required,\n path,\n ...metaPart,\n })\n }\n\n return result\n}\n","import type { Payload } from 'payload'\nimport type { CollectionSchema, FieldSchema, RelationshipInfo } from '../types.js'\nimport { analyzeFields } from './field-analyzer.js'\n\n/** Collections that should not be auto-populated by default */\nconst NON_POPULATABLE_SLUGS = new Set(['orders', 'carts', 'transactions'])\n\nexport type SchemaReaderOptions = {\n /** Extra slugs to mark as non-populatable (merged with defaults) */\n nonPopulatableSlugs?: string[]\n /**\n * When true, `nonPopulatableSlugs` REPLACES the built-in defaults rather\n * than merging with them. Use for non-ecommerce projects.\n */\n replaceDefaults?: boolean\n}\n\nfunction extractRelationships(fields: FieldSchema[], collectionSlug: string): RelationshipInfo[] {\n const result: RelationshipInfo[] = []\n\n for (const field of fields) {\n if (field.type === 'relationship' || field.type === 'upload') {\n if (field.relationTo !== undefined) {\n const collections = Array.isArray(field.relationTo) ? field.relationTo : [field.relationTo]\n\n for (const col of collections) {\n result.push({\n field: field.path,\n collection: field.relationTo,\n hasMany: field.hasMany === true,\n isSelfReferential: col === collectionSlug,\n })\n }\n }\n }\n\n // Recurse into nested fields\n if (field.fields && field.fields.length > 0) {\n result.push(...extractRelationships(field.fields, collectionSlug))\n }\n\n if (field.blocks) {\n for (const block of field.blocks) {\n result.push(...extractRelationships(block.fields, collectionSlug))\n }\n }\n }\n\n return result\n}\n\nfunction collectRequiredPaths(fields: FieldSchema[]): string[] {\n const required: string[] = []\n for (const field of fields) {\n if (field.required) required.push(field.path)\n if (field.fields) required.push(...collectRequiredPaths(field.fields))\n if (field.blocks) {\n for (const block of field.blocks) {\n required.push(...collectRequiredPaths(block.fields))\n }\n }\n }\n return required\n}\n\nfunction collectLexicalFeatures(fields: FieldSchema[]): string[] {\n const features: string[] = []\n for (const field of fields) {\n if (field.type === 'richText' && field.lexicalFeatures) {\n features.push(...field.lexicalFeatures)\n }\n if (field.fields) features.push(...collectLexicalFeatures(field.fields))\n if (field.blocks) {\n for (const block of field.blocks) {\n features.push(...collectLexicalFeatures(block.fields))\n }\n }\n }\n return [...new Set(features)]\n}\n\nexport function readCollectionSchema(\n payload: Payload,\n slug: string,\n options?: SchemaReaderOptions,\n): CollectionSchema {\n const collection = payload.collections[slug]\n if (!collection) {\n throw new Error(`Collection \"${slug}\" not found in payload.collections`)\n }\n\n const config = collection.config\n const fields = analyzeFields(config.fields)\n\n const nonPopulatable = options?.replaceDefaults\n ? new Set(options?.nonPopulatableSlugs ?? [])\n : new Set([...NON_POPULATABLE_SLUGS, ...(options?.nonPopulatableSlugs ?? [])])\n\n const relationships = extractRelationships(fields, slug)\n const requiredFields = collectRequiredPaths(fields)\n const lexicalFeatures = collectLexicalFeatures(fields)\n\n return {\n slug,\n fields,\n relationships,\n requiredFields,\n populatable: !nonPopulatable.has(slug),\n ...(lexicalFeatures.length > 0 ? { lexicalFeatures } : {}),\n }\n}\n\nexport function readAllCollectionSchemas(\n payload: Payload,\n options?: SchemaReaderOptions,\n): CollectionSchema[] {\n const slugs = Object.keys(payload.collections)\n return slugs.map((slug) => readCollectionSchema(payload, slug, options))\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karixi/payload-ai",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.mjs",
6
6
  "types": "dist/index.d.mts",
@@ -36,15 +36,15 @@
36
36
  "zod": "^4.3.6"
37
37
  },
38
38
  "devDependencies": {
39
- "@biomejs/biome": "^2.4.10",
40
- "@payloadcms/plugin-mcp": "^3.81.0",
39
+ "@biomejs/biome": "^2.4.13",
40
+ "@payloadcms/plugin-mcp": "^3.84.1",
41
41
  "@types/react": "^19.2.14",
42
42
  "bumpp": "^11.0.1",
43
- "payload": "^3.81.0",
44
- "react": "^19.2.4",
45
- "tsdown": "^0.21.7",
46
- "typescript": "^6.0.2",
47
- "vitest": "^4.1.2"
43
+ "payload": "^3.84.1",
44
+ "react": "^19.2.5",
45
+ "tsdown": "^0.21.10",
46
+ "typescript": "^6.0.3",
47
+ "vitest": "^4.1.5"
48
48
  },
49
49
  "scripts": {
50
50
  "build": "tsdown",