{"version":3,"file":"plugin-utils.mjs","names":[],"sources":["../src/plugin-utils.ts"],"sourcesContent":["/**\n * Shared utilities for plugin admin UIs.\n *\n * Plugin admin components (`admin.tsx`) run inside the EmDash admin dashboard.\n * This module provides the common helpers they all need: API fetching with CSRF\n * protection, response envelope unwrapping, and type narrowing.\n *\n * Import as: `import { apiFetch, parseApiResponse, isRecord } from \"@premium-cms/emdash/plugin-utils\";`\n */\n\nimport type { EmDashHandlers } from \"./astro/types.js\";\n\nexport type PublicPluginApiRouteHandler = EmDashHandlers[\"handlePublicPluginApiRoute\"];\n\nexport interface PublicPluginRuntimeLocals {\n\temdash?: {\n\t\thandlePublicPluginApiRoute?: PublicPluginApiRouteHandler;\n\t};\n}\n\n/**\n * Fetch wrapper that adds the `X-EmDash-Request` CSRF protection header.\n *\n * All plugin admin API calls should use this instead of raw `fetch()`.\n * State-changing endpoints reject requests without this header.\n */\nexport function apiFetch(input: string | URL | Request, init?: RequestInit): Promise<Response> {\n\tconst headers = new Headers(init?.headers);\n\theaders.set(\"X-EmDash-Request\", \"1\");\n\treturn fetch(input, { ...init, headers });\n}\n\n/**\n * Get the public-only plugin route dispatcher exposed to SSR page components.\n *\n * This intentionally reads `handlePublicPluginApiRoute`, not the raw\n * `handlePluginApiRoute` used by core's authenticated plugin API route.\n */\nexport function getPublicPluginApiRouteHandler(\n\tlocals: PublicPluginRuntimeLocals | null | undefined,\n): PublicPluginApiRouteHandler | undefined {\n\tconst handler = locals?.emdash?.handlePublicPluginApiRoute;\n\treturn typeof handler === \"function\" ? handler : undefined;\n}\n\n/**\n * Parse an API response, unwrapping the `{ success, data: T }` envelope.\n *\n * All plugin API routes return success responses wrapped in\n * `{ success: true, data: ... }` by `apiSuccess()`. This helper unwraps that\n * envelope and handles errors.\n *\n * On error responses (non-2xx), throws an Error with the server's message\n * (from `{ success: false, error: { message } }`) or the fallback message.\n *\n * @example\n * ```ts\n * const res = await apiFetch(\"/_emdash/api/plugins/my-plugin/items\");\n * const { items } = await parseApiResponse<{ items: Item[] }>(res, \"Failed to load items\");\n * ```\n */\nexport async function parseApiResponse<T>(\n\tresponse: Response,\n\tfallbackMessage = \"Request failed\",\n): Promise<T> {\n\tif (!response.ok) {\n\t\tthrow new Error(await getErrorMessage(response, `${fallbackMessage}: ${response.statusText}`));\n\t}\n\tconst body: { data: T } = await response.json();\n\treturn body.data;\n}\n\n/**\n * Extract the error message from a failed API response.\n *\n * Error responses use the shape `{ success: false, error: { code, message } }`.\n * This helper parses that body and returns the message, falling back to the\n * provided default.\n * Swallows JSON parse failures gracefully.\n *\n * @example\n * ```ts\n * if (!res.ok) {\n *   setError(await getErrorMessage(res, \"Failed to save\"));\n *   return;\n * }\n * ```\n */\nexport async function getErrorMessage(response: Response, fallback: string): Promise<string> {\n\tconst body: unknown = await response.json().catch(() => ({}));\n\tif (isRecord(body) && isRecord(body.error)) {\n\t\tconst msg = body.error.message;\n\t\tif (typeof msg === \"string\") return msg;\n\t}\n\treturn fallback;\n}\n\n/**\n * Narrow `unknown` to a plain object record.\n *\n * Useful for safely inspecting untyped API responses before accessing properties.\n */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"],"mappings":";;;;;;;AA0BA,SAAgB,SAAS,OAA+B,MAAuC;CAC9F,MAAM,UAAU,IAAI,QAAQ,MAAM,QAAQ;AAC1C,SAAQ,IAAI,oBAAoB,IAAI;AACpC,QAAO,MAAM,OAAO;EAAE,GAAG;EAAM;EAAS,CAAC;;;;;;;;AAS1C,SAAgB,+BACf,QAC0C;CAC1C,MAAM,UAAU,QAAQ,QAAQ;AAChC,QAAO,OAAO,YAAY,aAAa,UAAU;;;;;;;;;;;;;;;;;;AAmBlD,eAAsB,iBACrB,UACA,kBAAkB,kBACL;AACb,KAAI,CAAC,SAAS,GACb,OAAM,IAAI,MAAM,MAAM,gBAAgB,UAAU,GAAG,gBAAgB,IAAI,SAAS,aAAa,CAAC;AAG/F,SAD0B,MAAM,SAAS,MAAM,EACnC;;;;;;;;;;;;;;;;;;AAmBb,eAAsB,gBAAgB,UAAoB,UAAmC;CAC5F,MAAM,OAAgB,MAAM,SAAS,MAAM,CAAC,aAAa,EAAE,EAAE;AAC7D,KAAI,SAAS,KAAK,IAAI,SAAS,KAAK,MAAM,EAAE;EAC3C,MAAM,MAAM,KAAK,MAAM;AACvB,MAAI,OAAO,QAAQ,SAAU,QAAO;;AAErC,QAAO;;;;;;;AAQR,SAAgB,SAAS,OAAkD;AAC1E,QAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM"}