@emeryld/rrroutes-openapi 2.2.22 → 2.2.23

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,"sources":["../src/index.ts","../src/docs/LeafDocsPage.tsx","../src/docs/schemaIntrospection.ts","../src/docs/serializer.ts","../src/docs/docs.ts"],"sourcesContent":["import type { AnyLeaf } from '@emeryld/rrroutes-contract';\nimport { randomBytes } from 'crypto';\nimport type { Request, Response, Router } from 'express';\nimport { static as expressStatic } from 'express';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { renderLeafDocsHTML } from './docs/docs.js';\n\nexport type DocsRequestContext = {\n req: Request;\n res: Response;\n leaves: AnyLeaf[];\n};\n\nexport type DocsOnRequestResult = {\n leaves?: AnyLeaf[];\n nonce?: string;\n html?: string;\n};\n\nexport type OpenApiDocsOptions = {\n /** Path where docs are mounted. Defaults to `/__rrroutes/docs`. */\n path?: string;\n prefix?: string;\n /** Whether to emit a CSP header + nonce. Defaults to true. */\n csp?: boolean;\n /** Override where static assets are served from. Defaults to `${path}/assets`. */\n assetBasePath?: string;\n /**\n * Hook that runs on every request. Use it to adjust leaves, override nonce, or\n * provide a fully custom HTML response.\n */\n onRequest?: (ctx: DocsRequestContext) => DocsOnRequestResult | void;\n};\n\nexport type MountDocsArgs = {\n router: Router;\n leaves: AnyLeaf[];\n options?: OpenApiDocsOptions;\n};\n\nconst trimTrailingSlash = (value: string) =>\n value.endsWith('/') && value.length > 1 ? value.slice(0, -1) : value;\n\nexport function mountRRRoutesDocs({\n router,\n leaves,\n options = {},\n}: MountDocsArgs) {\n const prefix = options.prefix ? trimTrailingSlash(options.prefix) : '';\n const docsPath = options.path ?? '/__rrroutes/docs';\n const normalizedDocsPath = trimTrailingSlash(docsPath);\n const assetsMountPath = trimTrailingSlash(\n options.assetBasePath ?? `${normalizedDocsPath}/assets`,\n );\n const publicDir = resolvePublicDir();\n const assetsDir = path.join(publicDir, 'assets');\n const cspEnabled = options.csp !== false;\n\n console.log(`Mounting RRRoutes docs at ${normalizedDocsPath} and assets at ${assetsMountPath}`);\n\n router.use(assetsMountPath, expressStatic(assetsDir, { immutable: true, maxAge: '365d' }));\n\n router.get(normalizedDocsPath, (req, res) => {\n const preparedLeaves = Array.isArray(leaves)\n ? leaves.filter((leaf) => leaf.cfg.docsHidden !== true)\n : [];\n const onRequestResult = options.onRequest?.({ req, res, leaves: preparedLeaves }) ?? {};\n const finalLeaves = onRequestResult.leaves ?? preparedLeaves;\n\n const hasCustomHtml = typeof onRequestResult.html === 'string';\n\n let nonce = onRequestResult.nonce;\n if (!nonce && cspEnabled && !hasCustomHtml) {\n nonce = randomBytes(16).toString('base64');\n }\n\n const html = hasCustomHtml\n ? (onRequestResult.html as string)\n : renderLeafDocsHTML(finalLeaves, {\n cspNonce: nonce,\n assetBasePath: `${prefix}${assetsMountPath}`,\n });\n\n if (cspEnabled && nonce) {\n res.setHeader(\n 'Content-Security-Policy',\n [\n \"default-src 'self'\",\n `script-src 'self' 'nonce-${nonce}'`,\n `style-src 'self' 'nonce-${nonce}'`,\n \"img-src 'self' data:\",\n \"connect-src 'self'\",\n \"font-src 'self'\",\n \"frame-ancestors 'self'\",\n ].join('; '),\n );\n }\n\n res.send(html);\n });\n\n return { path: docsPath };\n}\n\nfunction resolvePublicDir() {\n const moduleDir =\n typeof __dirname !== 'undefined'\n ? __dirname\n : path.dirname(fileURLToPath(import.meta.url));\n const fromModule = path.resolve(moduleDir, '../public');\n if (fs.existsSync(fromModule)) return fromModule;\n\n // When running from source (ts-node), fall back to the built output path.\n const fallback = path.resolve(moduleDir, '../dist/public');\n if (fs.existsSync(fallback)) return fallback;\n\n return fromModule; // fallback; express static will 404 if missing\n}\n\nexport { renderLeafDocsHTML } from './docs/docs.js';\nexport { serializeLeaf } from './docs/serializer.js';\nexport type { SerializableLeaf } from './docs/serializer.js';\n","import type { AnyLeaf } from '@emeryld/rrroutes-contract';\nimport type { ReactElement } from 'react';\nimport { renderToStaticMarkup } from 'react-dom/server';\nimport { serializeLeaf } from './serializer.js';\n\nexport interface RenderOptions {\n /** CSP nonce applied to data + script tags. */\n cspNonce?: string;\n /** Base URL where static assets are served (e.g. `/__rrroutes/docs/assets`). */\n assetBasePath?: string;\n}\n\nconst DEFAULT_ASSET_BASE = '/__rrroutes/docs/assets';\n\nfunction normalizeBase(base: string) {\n if (!base) return DEFAULT_ASSET_BASE;\n return base.endsWith('/') ? base.slice(0, -1) : base;\n}\n\ntype DocsDocumentProps = {\n leavesJson: string;\n assetBase: string;\n cspNonce?: string;\n};\n\nexport const DocsDocument = ({ leavesJson, assetBase, cspNonce }: DocsDocumentProps) => {\n const cssHref = `${assetBase}/docs.css`;\n const jsSrc = `${assetBase}/docs.js`;\n\n return (\n <html lang=\"en\">\n <head>\n <meta charSet=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>API Reference</title>\n <link rel=\"stylesheet\" href={cssHref} />\n </head>\n <body>\n <div id=\"docs-root\"></div>\n <script\n id=\"leaf-data\"\n type=\"application/json\"\n nonce={cspNonce}\n dangerouslySetInnerHTML={{ __html: leavesJson }}\n />\n <script type=\"module\" src={jsSrc} nonce={cspNonce} />\n </body>\n </html>\n );\n};\n\nfunction serializeLeaves(leaves: AnyLeaf[]) {\n return JSON.stringify(leaves.map(serializeLeaf)).replace(/<\\//g, '<\\\\/');\n}\n\nexport function createLeafDocsDocument(\n leaves: AnyLeaf[],\n options: RenderOptions = {},\n): ReactElement {\n const assetBase = normalizeBase(options.assetBasePath ?? DEFAULT_ASSET_BASE);\n const leavesJson = serializeLeaves(leaves);\n\n return <DocsDocument leavesJson={leavesJson} assetBase={assetBase} cspNonce={options.cspNonce} />;\n}\n\nexport function renderLeafDocsHTML(leaves: AnyLeaf[], options: RenderOptions = {}): string {\n const doc = createLeafDocsDocument(leaves, options);\n const html = renderToStaticMarkup(doc);\n return `<!DOCTYPE html>${html}`;\n}\n","// schemaIntrospection.ts\nimport * as z from \"zod\";\n\nexport type SerializableSchemaNode = {\n kind: string; // \"object\" | \"string\" | \"number\" | ...\n optional?: boolean;\n nullable?: boolean;\n description?: string;\n\n // object\n properties?: Record<string, SerializableSchemaNode>;\n // array\n element?: SerializableSchemaNode;\n // union\n union?: SerializableSchemaNode[];\n // literal\n literal?: unknown;\n // enum\n enumValues?: string[];\n};\n\ntype ZodAny = z.ZodTypeAny;\n\n/**\n * Zod 3 uses `schema._def`, Zod 4 uses `schema._zod.def`.\n */\nfunction getDef(schema: unknown): any | undefined {\n if (!schema || typeof schema !== \"object\") return undefined;\n const anySchema = schema as any;\n return anySchema._zod?.def ?? anySchema._def;\n}\n\n/**\n * Try to get a human-readable description.\n * Zod 4: use metadata/registry; fallback to internal def.description.\n * Zod 3: only internal def.description exists.\n */\nfunction getDescription(schema: ZodAny): string | undefined {\n const anyZ: any = z as any;\n\n // Zod 4 global registry metadata, if present\n const registry = anyZ.globalRegistry?.get\n ? anyZ.globalRegistry.get(schema)\n : undefined;\n if (registry && typeof registry.description === \"string\") {\n return registry.description;\n }\n\n // Legacy / internal description\n const def = getDef(schema);\n if (def && typeof def.description === \"string\") {\n return def.description;\n }\n\n return undefined;\n}\n\n/**\n * Peel off wrappers (effects, optional, nullable, default) and\n * return the inner schema + flags.\n *\n * Supports:\n * - Zod 3: ZodEffects, ZodOptional, ZodNullable, ZodDefault\n * - Zod 4: ZodOptional, ZodNullable, ZodDefault\n */\nfunction unwrap(schema: ZodAny): {\n base: ZodAny;\n optional: boolean;\n nullable: boolean;\n} {\n let s: ZodAny = schema;\n let optional = false;\n let nullable = false;\n\n // Zod 3 only (undefined in Zod 4)\n const ZodEffectsCtor: any = (z as any).ZodEffects;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n // Zod 3: ZodEffects wrapper\n if (ZodEffectsCtor && s instanceof ZodEffectsCtor) {\n const def = getDef(s) || {};\n const sourceType =\n typeof (s as any).sourceType === \"function\"\n ? (s as any).sourceType()\n : def.schema;\n if (!sourceType) break;\n s = sourceType;\n continue;\n }\n\n // Zod 3 + 4: optional/nullable/default wrappers\n if (s instanceof z.ZodOptional) {\n optional = true;\n const def = getDef(s);\n s = (def && def.innerType) || s; // innerType exists in both 3 & 4\n continue;\n }\n\n if (s instanceof z.ZodNullable) {\n nullable = true;\n const def = getDef(s);\n s = (def && def.innerType) || s;\n continue;\n }\n\n if (s instanceof z.ZodDefault) {\n const def = getDef(s);\n s = (def && def.innerType) || s;\n continue;\n }\n\n break;\n }\n\n return { base: s, optional, nullable };\n}\n\nexport function introspectSchema(\n schema: ZodAny | undefined\n): SerializableSchemaNode | undefined {\n if (!schema) return undefined;\n\n const { base, optional, nullable } = unwrap(schema);\n const def = getDef(base);\n\n const node: SerializableSchemaNode = {\n kind: inferKind(base),\n optional: optional || undefined,\n nullable: nullable || undefined,\n description: getDescription(base),\n };\n\n // OBJECT\n if (base instanceof z.ZodObject) {\n // Zod 3: _def.shape() (function)\n // Zod 4: .shape getter returns an object\n const rawShape: any =\n (base as any).shape ?? (def && typeof def.shape === \"function\"\n ? def.shape()\n : def?.shape);\n\n const shape =\n typeof rawShape === \"function\" ? rawShape() : rawShape ?? {};\n\n const props: Record<string, SerializableSchemaNode> = {};\n for (const key of Object.keys(shape)) {\n const child = shape[key] as ZodAny;\n const childNode = introspectSchema(child);\n if (childNode) props[key] = childNode;\n }\n node.properties = props;\n }\n\n // ARRAY\n if (base instanceof z.ZodArray) {\n // Zod 3: def.type is inner schema\n // Zod 4: def.element is inner schema\n const inner =\n (def && (def.element as ZodAny)) ||\n (def && (def.type as ZodAny)) ||\n undefined;\n if (inner) {\n node.element = introspectSchema(inner);\n }\n }\n\n // UNION\n if (base instanceof z.ZodUnion) {\n const options: ZodAny[] = (def && def.options) || [];\n node.union = options\n .map((opt) => introspectSchema(opt))\n .filter(Boolean) as SerializableSchemaNode[];\n }\n\n // LITERAL\n if (base instanceof z.ZodLiteral) {\n if (def) {\n // Zod 4: def.values (multi-literal)\n if (Array.isArray(def.values)) {\n node.literal =\n def.values.length === 1 ? def.values[0] : def.values.slice();\n } else {\n // Zod 3: def.value\n node.literal = def.value;\n }\n }\n }\n\n // ENUM\n if (base instanceof z.ZodEnum) {\n if (def) {\n if (Array.isArray(def.values)) {\n // Zod 3\n node.enumValues = def.values.slice();\n } else if (def.entries && typeof def.entries === \"object\") {\n // Zod 4: entries is a { key: value } map\n node.enumValues = Object.values(def.entries).map((v: unknown) =>\n String(v)\n );\n }\n }\n }\n\n return node;\n}\n\nfunction inferKind(schema: ZodAny): string {\n // This path still uses instanceof; it works with Zod 4 Classic\n // (importing from \"zod\"). Anything unknown falls back to \"unknown\".\n if (schema instanceof z.ZodString) return \"string\";\n if (schema instanceof z.ZodNumber) return \"number\";\n if (schema instanceof z.ZodBoolean) return \"boolean\";\n if (schema instanceof z.ZodBigInt) return \"bigint\";\n if (schema instanceof z.ZodDate) return \"date\";\n if (schema instanceof z.ZodArray) return \"array\";\n if (schema instanceof z.ZodObject) return \"object\";\n if (schema instanceof z.ZodUnion) return \"union\";\n if (schema instanceof z.ZodLiteral) return \"literal\";\n if (schema instanceof z.ZodEnum) return \"enum\";\n if (schema instanceof z.ZodRecord) return \"record\";\n if (schema instanceof z.ZodTuple) return \"tuple\";\n if (schema instanceof z.ZodUnknown) return \"unknown\";\n if (schema instanceof z.ZodAny) return \"any\";\n\n return \"unknown\";\n}\n","// serializer.ts\nimport type { AnyLeaf, MethodCfg } from \"@emeryld/rrroutes-contract\";\nimport { introspectSchema, SerializableSchemaNode } from \"./schemaIntrospection.js\";\n\ntype SerializableMethodCfg = Pick<\n MethodCfg,\n | \"description\"\n | \"summary\"\n | \"docsGroup\"\n | \"tags\"\n | \"deprecated\"\n | \"stability\"\n | \"feed\"\n | \"docsMeta\"\n> & {\n hasBody: boolean;\n hasQuery: boolean;\n hasParams: boolean;\n hasOutput: boolean;\n\n // NEW: full Zod ASTs\n bodySchema?: SerializableSchemaNode;\n querySchema?: SerializableSchemaNode;\n paramsSchema?: SerializableSchemaNode;\n outputSchema?: SerializableSchemaNode;\n};\n\nexport type SerializableLeaf = {\n method: string;\n path: string;\n cfg: SerializableMethodCfg;\n};\n\nexport type { SerializableSchemaNode } from \"./schemaIntrospection.js\";\n\nexport function serializeLeaf(leaf: AnyLeaf): SerializableLeaf {\n const cfg = leaf.cfg;\n\n const tags = Array.isArray(cfg.tags) ? cfg.tags.slice() : [];\n\n return {\n method: leaf.method, // 'get' | 'post' | ...\n path: leaf.path,\n cfg: {\n description: cfg.description,\n summary: cfg.summary,\n docsGroup: cfg.docsGroup,\n tags,\n deprecated: cfg.deprecated,\n stability: cfg.stability,\n feed: !!cfg.feed,\n docsMeta: cfg.docsMeta,\n hasBody: !!cfg.bodySchema || !!cfg.bodyFiles?.length,\n hasQuery: !!cfg.querySchema,\n hasParams: !!cfg.paramsSchema,\n hasOutput: !!cfg.outputSchema,\n\n bodySchema: introspectSchema(cfg.bodySchema),\n querySchema: introspectSchema(cfg.querySchema),\n paramsSchema: introspectSchema(cfg.paramsSchema),\n outputSchema: introspectSchema(cfg.outputSchema),\n },\n };\n}\n","// renderLeafDocsHTML.ts\nimport type { AnyLeaf } from \"@emeryld/rrroutes-contract\";\nimport {\n createLeafDocsDocument,\n renderLeafDocsHTML as LeafDocsPage,\n RenderOptions,\n} from \"./LeafDocsPage.js\";\n\nexport function renderLeafDocsHTML(leaves: AnyLeaf[], options: RenderOptions = {}): string {\n return LeafDocsPage(leaves, options);\n}\n\nexport type { RenderOptions } from \"./LeafDocsPage.js\";\nexport { createLeafDocsDocument } from \"./LeafDocsPage.js\";\n"],"mappings":";AACA,SAAS,mBAAmB;AAE5B,SAAS,UAAU,qBAAqB;AACxC,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,qBAAqB;;;ACJ9B,SAAS,4BAA4B;;;ACDrC,YAAY,OAAO;AAyBnB,SAAS,OAAO,QAAkC;AAChD,MAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAClD,QAAM,YAAY;AAClB,SAAO,UAAU,MAAM,OAAO,UAAU;AAC1C;AAOA,SAAS,eAAe,QAAoC;AAC1D,QAAM,OAAY;AAGlB,QAAM,WAAW,KAAK,gBAAgB,MAClC,KAAK,eAAe,IAAI,MAAM,IAC9B;AACJ,MAAI,YAAY,OAAO,SAAS,gBAAgB,UAAU;AACxD,WAAO,SAAS;AAAA,EAClB;AAGA,QAAM,MAAM,OAAO,MAAM;AACzB,MAAI,OAAO,OAAO,IAAI,gBAAgB,UAAU;AAC9C,WAAO,IAAI;AAAA,EACb;AAEA,SAAO;AACT;AAUA,SAAS,OAAO,QAId;AACA,MAAI,IAAY;AAChB,MAAI,WAAW;AACf,MAAI,WAAW;AAGf,QAAM,iBAAiC;AAGvC,SAAO,MAAM;AAEX,QAAI,kBAAkB,aAAa,gBAAgB;AACjD,YAAM,MAAM,OAAO,CAAC,KAAK,CAAC;AAC1B,YAAM,aACJ,OAAQ,EAAU,eAAe,aAC5B,EAAU,WAAW,IACtB,IAAI;AACV,UAAI,CAAC,WAAY;AACjB,UAAI;AACJ;AAAA,IACF;AAGA,QAAI,aAAe,eAAa;AAC9B,iBAAW;AACX,YAAM,MAAM,OAAO,CAAC;AACpB,UAAK,OAAO,IAAI,aAAc;AAC9B;AAAA,IACF;AAEA,QAAI,aAAe,eAAa;AAC9B,iBAAW;AACX,YAAM,MAAM,OAAO,CAAC;AACpB,UAAK,OAAO,IAAI,aAAc;AAC9B;AAAA,IACF;AAEA,QAAI,aAAe,cAAY;AAC7B,YAAM,MAAM,OAAO,CAAC;AACpB,UAAK,OAAO,IAAI,aAAc;AAC9B;AAAA,IACF;AAEA;AAAA,EACF;AAEA,SAAO,EAAE,MAAM,GAAG,UAAU,SAAS;AACvC;AAEO,SAAS,iBACd,QACoC;AACpC,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,EAAE,MAAM,UAAU,SAAS,IAAI,OAAO,MAAM;AAClD,QAAM,MAAM,OAAO,IAAI;AAEvB,QAAM,OAA+B;AAAA,IACnC,MAAM,UAAU,IAAI;AAAA,IACpB,UAAU,YAAY;AAAA,IACtB,UAAU,YAAY;AAAA,IACtB,aAAa,eAAe,IAAI;AAAA,EAClC;AAGA,MAAI,gBAAkB,aAAW;AAG/B,UAAM,WACH,KAAa,UAAU,OAAO,OAAO,IAAI,UAAU,aAChD,IAAI,MAAM,IACV,KAAK;AAEX,UAAM,QACJ,OAAO,aAAa,aAAa,SAAS,IAAI,YAAY,CAAC;AAE7D,UAAM,QAAgD,CAAC;AACvD,eAAW,OAAO,OAAO,KAAK,KAAK,GAAG;AACpC,YAAM,QAAQ,MAAM,GAAG;AACvB,YAAM,YAAY,iBAAiB,KAAK;AACxC,UAAI,UAAW,OAAM,GAAG,IAAI;AAAA,IAC9B;AACA,SAAK,aAAa;AAAA,EACpB;AAGA,MAAI,gBAAkB,YAAU;AAG9B,UAAM,QACH,OAAQ,IAAI,WACZ,OAAQ,IAAI,QACb;AACF,QAAI,OAAO;AACT,WAAK,UAAU,iBAAiB,KAAK;AAAA,IACvC;AAAA,EACF;AAGA,MAAI,gBAAkB,YAAU;AAC9B,UAAM,UAAqB,OAAO,IAAI,WAAY,CAAC;AACnD,SAAK,QAAQ,QACV,IAAI,CAAC,QAAQ,iBAAiB,GAAG,CAAC,EAClC,OAAO,OAAO;AAAA,EACnB;AAGA,MAAI,gBAAkB,cAAY;AAChC,QAAI,KAAK;AAEP,UAAI,MAAM,QAAQ,IAAI,MAAM,GAAG;AAC7B,aAAK,UACH,IAAI,OAAO,WAAW,IAAI,IAAI,OAAO,CAAC,IAAI,IAAI,OAAO,MAAM;AAAA,MAC/D,OAAO;AAEL,aAAK,UAAU,IAAI;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AAGA,MAAI,gBAAkB,WAAS;AAC7B,QAAI,KAAK;AACP,UAAI,MAAM,QAAQ,IAAI,MAAM,GAAG;AAE7B,aAAK,aAAa,IAAI,OAAO,MAAM;AAAA,MACrC,WAAW,IAAI,WAAW,OAAO,IAAI,YAAY,UAAU;AAEzD,aAAK,aAAa,OAAO,OAAO,IAAI,OAAO,EAAE;AAAA,UAAI,CAAC,MAChD,OAAO,CAAC;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,UAAU,QAAwB;AAGzC,MAAI,kBAAoB,YAAW,QAAO;AAC1C,MAAI,kBAAoB,YAAW,QAAO;AAC1C,MAAI,kBAAoB,aAAY,QAAO;AAC3C,MAAI,kBAAoB,YAAW,QAAO;AAC1C,MAAI,kBAAoB,UAAS,QAAO;AACxC,MAAI,kBAAoB,WAAU,QAAO;AACzC,MAAI,kBAAoB,YAAW,QAAO;AAC1C,MAAI,kBAAoB,WAAU,QAAO;AACzC,MAAI,kBAAoB,aAAY,QAAO;AAC3C,MAAI,kBAAoB,UAAS,QAAO;AACxC,MAAI,kBAAoB,YAAW,QAAO;AAC1C,MAAI,kBAAoB,WAAU,QAAO;AACzC,MAAI,kBAAoB,aAAY,QAAO;AAC3C,MAAI,kBAAoB,SAAQ,QAAO;AAEvC,SAAO;AACT;;;AC/LO,SAAS,cAAc,MAAiC;AAC7D,QAAM,MAAM,KAAK;AAEjB,QAAM,OAAO,MAAM,QAAQ,IAAI,IAAI,IAAI,IAAI,KAAK,MAAM,IAAI,CAAC;AAE3D,SAAO;AAAA,IACL,QAAQ,KAAK;AAAA;AAAA,IACb,MAAM,KAAK;AAAA,IACX,KAAK;AAAA,MACH,aAAa,IAAI;AAAA,MACjB,SAAS,IAAI;AAAA,MACb,WAAW,IAAI;AAAA,MACf;AAAA,MACA,YAAY,IAAI;AAAA,MAChB,WAAW,IAAI;AAAA,MACf,MAAM,CAAC,CAAC,IAAI;AAAA,MACZ,UAAU,IAAI;AAAA,MACd,SAAS,CAAC,CAAC,IAAI,cAAc,CAAC,CAAC,IAAI,WAAW;AAAA,MAC9C,UAAU,CAAC,CAAC,IAAI;AAAA,MAChB,WAAW,CAAC,CAAC,IAAI;AAAA,MACjB,WAAW,CAAC,CAAC,IAAI;AAAA,MAEjB,YAAY,iBAAiB,IAAI,UAAU;AAAA,MAC3C,aAAa,iBAAiB,IAAI,WAAW;AAAA,MAC7C,cAAc,iBAAiB,IAAI,YAAY;AAAA,MAC/C,cAAc,iBAAiB,IAAI,YAAY;AAAA,IACjD;AAAA,EACF;AACF;;;AFhCM,SACE,KADF;AAnBN,IAAM,qBAAqB;AAE3B,SAAS,cAAc,MAAc;AACnC,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,KAAK,SAAS,GAAG,IAAI,KAAK,MAAM,GAAG,EAAE,IAAI;AAClD;AAQO,IAAM,eAAe,CAAC,EAAE,YAAY,WAAW,SAAS,MAAyB;AACtF,QAAM,UAAU,GAAG,SAAS;AAC5B,QAAM,QAAQ,GAAG,SAAS;AAE1B,SACE,qBAAC,UAAK,MAAK,MACT;AAAA,yBAAC,UACC;AAAA,0BAAC,UAAK,SAAQ,SAAQ;AAAA,MACtB,oBAAC,UAAK,MAAK,YAAW,SAAQ,yCAAwC;AAAA,MACtE,oBAAC,WAAM,2BAAa;AAAA,MACpB,oBAAC,UAAK,KAAI,cAAa,MAAM,SAAS;AAAA,OACxC;AAAA,IACA,qBAAC,UACC;AAAA,0BAAC,SAAI,IAAG,aAAY;AAAA,MACpB;AAAA,QAAC;AAAA;AAAA,UACC,IAAG;AAAA,UACH,MAAK;AAAA,UACL,OAAO;AAAA,UACP,yBAAyB,EAAE,QAAQ,WAAW;AAAA;AAAA,MAChD;AAAA,MACA,oBAAC,YAAO,MAAK,UAAS,KAAK,OAAO,OAAO,UAAU;AAAA,OACrD;AAAA,KACF;AAEJ;AAEA,SAAS,gBAAgB,QAAmB;AAC1C,SAAO,KAAK,UAAU,OAAO,IAAI,aAAa,CAAC,EAAE,QAAQ,QAAQ,MAAM;AACzE;AAEO,SAAS,uBACd,QACA,UAAyB,CAAC,GACZ;AACd,QAAM,YAAY,cAAc,QAAQ,iBAAiB,kBAAkB;AAC3E,QAAM,aAAa,gBAAgB,MAAM;AAEzC,SAAO,oBAAC,gBAAa,YAAwB,WAAsB,UAAU,QAAQ,UAAU;AACjG;AAEO,SAAS,mBAAmB,QAAmB,UAAyB,CAAC,GAAW;AACzF,QAAM,MAAM,uBAAuB,QAAQ,OAAO;AAClD,QAAM,OAAO,qBAAqB,GAAG;AACrC,SAAO,kBAAkB,IAAI;AAC/B;;;AG7DO,SAASA,oBAAmB,QAAmB,UAAyB,CAAC,GAAW;AACzF,SAAO,mBAAa,QAAQ,OAAO;AACrC;;;AJgCA,IAAM,oBAAoB,CAAC,UACzB,MAAM,SAAS,GAAG,KAAK,MAAM,SAAS,IAAI,MAAM,MAAM,GAAG,EAAE,IAAI;AAE1D,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA,UAAU,CAAC;AACb,GAAkB;AAChB,QAAM,SAAS,QAAQ,SAAS,kBAAkB,QAAQ,MAAM,IAAI;AACpE,QAAM,WAAW,QAAQ,QAAQ;AACjC,QAAM,qBAAqB,kBAAkB,QAAQ;AACrD,QAAM,kBAAkB;AAAA,IACtB,QAAQ,iBAAiB,GAAG,kBAAkB;AAAA,EAChD;AACA,QAAM,YAAY,iBAAiB;AACnC,QAAM,YAAY,KAAK,KAAK,WAAW,QAAQ;AAC/C,QAAM,aAAa,QAAQ,QAAQ;AAEnC,UAAQ,IAAI,6BAA6B,kBAAkB,kBAAkB,eAAe,EAAE;AAE9F,SAAO,IAAI,iBAAiB,cAAc,WAAW,EAAE,WAAW,MAAM,QAAQ,OAAO,CAAC,CAAC;AAEzF,SAAO,IAAI,oBAAoB,CAAC,KAAK,QAAQ;AAC3C,UAAM,iBAAiB,MAAM,QAAQ,MAAM,IACvC,OAAO,OAAO,CAAC,SAAS,KAAK,IAAI,eAAe,IAAI,IACpD,CAAC;AACL,UAAM,kBAAkB,QAAQ,YAAY,EAAE,KAAK,KAAK,QAAQ,eAAe,CAAC,KAAK,CAAC;AACtF,UAAM,cAAc,gBAAgB,UAAU;AAE9C,UAAM,gBAAgB,OAAO,gBAAgB,SAAS;AAEtD,QAAI,QAAQ,gBAAgB;AAC5B,QAAI,CAAC,SAAS,cAAc,CAAC,eAAe;AAC1C,cAAQ,YAAY,EAAE,EAAE,SAAS,QAAQ;AAAA,IAC3C;AAEA,UAAM,OAAO,gBACR,gBAAgB,OACjBC,oBAAmB,aAAa;AAAA,MAC9B,UAAU;AAAA,MACV,eAAe,GAAG,MAAM,GAAG,eAAe;AAAA,IAC5C,CAAC;AAEL,QAAI,cAAc,OAAO;AACvB,UAAI;AAAA,QACF;AAAA,QACA;AAAA,UACE;AAAA,UACA,4BAA4B,KAAK;AAAA,UACjC,2BAA2B,KAAK;AAAA,UAChC;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF,EAAE,KAAK,IAAI;AAAA,MACb;AAAA,IACF;AAEA,QAAI,KAAK,IAAI;AAAA,EACf,CAAC;AAED,SAAO,EAAE,MAAM,SAAS;AAC1B;AAEA,SAAS,mBAAmB;AAC1B,QAAM,YACJ,OAAO,cAAc,cACjB,YACA,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AACjD,QAAM,aAAa,KAAK,QAAQ,WAAW,WAAW;AACtD,MAAI,GAAG,WAAW,UAAU,EAAG,QAAO;AAGtC,QAAM,WAAW,KAAK,QAAQ,WAAW,gBAAgB;AACzD,MAAI,GAAG,WAAW,QAAQ,EAAG,QAAO;AAEpC,SAAO;AACT;","names":["renderLeafDocsHTML","renderLeafDocsHTML"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/docs/LeafDocsPage.tsx","../src/docs/schemaIntrospection.ts","../src/docs/serializer.ts","../src/docs/docs.ts"],"sourcesContent":["/**\n * dry styles\nfake history logs data for testing\ngraphs for history data\nlogs webhook\nsecurity -> gated access + give environment and if environment='production' -> extra \"Are you sure you want to do this\" pop-up for each non-get action\n */\n\nimport type { AnyLeaf } from '@emeryld/rrroutes-contract';\nimport { randomBytes } from 'crypto';\nimport type { Request, Response, Router } from 'express';\nimport { static as expressStatic } from 'express';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport z from 'zod';\nimport { renderLeafDocsHTML } from './docs/docs.js';\nimport type { SerializableHistoryEntry } from './docs/docs.js';\nimport type { SerializablePreset } from './docs/presets.js';\n\nexport type DocsRequestContext = {\n req: Request;\n res: Response;\n leaves: AnyLeaf[];\n presets: PresetGroup<AnyLeaf>[];\n};\n\nexport type DocsOnRequestResult = {\n leaves?: AnyLeaf[];\n presets?: PresetGroup<AnyLeaf>[];\n nonce?: string;\n html?: string;\n};\n\nexport type OpenApiDocsOptions = {\n /** Path where docs are mounted. Defaults to `/__rrroutes/docs`. */\n path?: string;\n prefix?: string;\n /** Whether to emit a CSP header + nonce. Defaults to true. */\n csp?: boolean;\n /** Override where static assets are served from. Defaults to `${path}/assets`. */\n assetBasePath?: string;\n /** Optional seed history entries that will pre-populate the docs UI history. */\n historySeeds?: SerializableHistoryEntry[];\n /**\n * Hook that runs on every request. Use it to adjust leaves, override nonce, or\n * provide a fully custom HTML response.\n */\n onRequest?: (ctx: DocsRequestContext) => DocsOnRequestResult | void;\n};\n\nexport type Preset<L extends AnyLeaf> = L extends infer A extends AnyLeaf? {\n method: A['method'];\n path: A['path'];\n // pre-set values for schemas. Does not have to be complete.\n body: z.input<A['cfg']['bodySchema']>;\n query: z.input<A['cfg']['querySchema']>;\n params: z.input<A['cfg']['paramsSchema']>;\n}:never;\n\nexport type PresetGroup<L extends AnyLeaf> = {\n name: string;\n description?: string;\n tags: string[];\n docsGroup?: string;\n\n ops: Preset<L>[];\n};\n\nexport type MountDocsArgs<L extends AnyLeaf> = {\n router: Router;\n leaves: L[];\n presets?: PresetGroup<L>[];\n options?: OpenApiDocsOptions;\n};\n\nconst trimTrailingSlash = (value: string) =>\n value.endsWith('/') && value.length > 1 ? value.slice(0, -1) : value;\n\nexport function mountRRRoutesDocs<L extends AnyLeaf>({\n router,\n leaves,\n presets = [],\n options = {},\n}: MountDocsArgs<L>) {\n const prefix = options.prefix ? trimTrailingSlash(options.prefix) : '';\n const docsPath = options.path ?? '/__rrroutes/docs';\n const normalizedDocsPath = trimTrailingSlash(docsPath);\n const assetsMountPath = trimTrailingSlash(\n options.assetBasePath ?? `${normalizedDocsPath}/assets`,\n );\n const publicDir = resolvePublicDir();\n const assetsDir = path.join(publicDir, 'assets');\n const cspEnabled = options.csp !== false;\n\n router.use(assetsMountPath, expressStatic(assetsDir, { immutable: true, maxAge: '365d' }));\n\n const docsRoutePaths = [normalizedDocsPath, `${normalizedDocsPath}/`, `${normalizedDocsPath}/*`];\n\n router.get(docsRoutePaths, (req, res) => {\n const preparedLeaves = Array.isArray(leaves)\n ? leaves.filter((leaf) => leaf.cfg.docsHidden !== true)\n : [];\n const preparedPresets = Array.isArray(presets) ? presets : [];\n const onRequestResult =\n options.onRequest?.({ req, res, leaves: preparedLeaves, presets: preparedPresets }) ?? {};\n const finalLeaves = onRequestResult.leaves ?? preparedLeaves;\n const finalPresets = onRequestResult.presets ?? preparedPresets;\n\n const hasCustomHtml = typeof onRequestResult.html === 'string';\n\n let nonce = onRequestResult.nonce;\n if (!nonce && cspEnabled && !hasCustomHtml) {\n nonce = randomBytes(16).toString('base64');\n }\n\n const html = hasCustomHtml\n ? (onRequestResult.html as string)\n : renderLeafDocsHTML(finalLeaves, {\n cspNonce: nonce,\n assetBasePath: `${prefix}${assetsMountPath}`,\n docsBasePath: `${prefix}${normalizedDocsPath}`,\n historySeeds: options.historySeeds,\n presets: normalizePresets(finalPresets),\n });\n\n if (cspEnabled && nonce) {\n res.setHeader(\n 'Content-Security-Policy',\n [\n \"default-src 'self'\",\n `script-src 'self' 'nonce-${nonce}'`,\n `style-src 'self' 'nonce-${nonce}'`,\n \"img-src 'self' data:\",\n \"connect-src 'self'\",\n \"font-src 'self'\",\n \"frame-ancestors 'self'\",\n ].join('; '),\n );\n }\n\n res.send(html);\n });\n\n return { path: docsPath };\n}\n\nfunction resolvePublicDir() {\n const moduleDir =\n typeof __dirname !== 'undefined'\n ? __dirname\n : path.dirname(fileURLToPath(import.meta.url));\n const fromModule = path.resolve(moduleDir, '../public');\n if (fs.existsSync(fromModule)) return fromModule;\n\n // When running from source (ts-node), fall back to the built output path.\n const fallback = path.resolve(moduleDir, '../dist/public');\n if (fs.existsSync(fallback)) return fallback;\n\n return fromModule; // fallback; express static will 404 if missing\n}\n\nfunction normalizePresets(presets: PresetGroup<AnyLeaf>[]): SerializablePreset[] {\n if (!Array.isArray(presets)) return [];\n return presets.map((preset) => ({\n name: preset.name,\n description: preset.description,\n tags: Array.isArray(preset.tags) ? preset.tags.slice() : [],\n docsGroup: preset.docsGroup,\n ops: Array.isArray(preset.ops)\n ? preset.ops.map((op) => ({\n method: typeof op.method === 'string' ? op.method.toUpperCase() : '',\n path: typeof op.path === 'string' ? op.path : '',\n body: op.body,\n query: op.query,\n params: op.params,\n }))\n : [],\n }));\n}\n\nexport { renderLeafDocsHTML } from './docs/docs.js';\nexport { serializeLeaf } from './docs/serializer.js';\nexport type { SerializableLeaf } from './docs/serializer.js';\n","import type { AnyLeaf } from '@emeryld/rrroutes-contract';\nimport type { ReactElement } from 'react';\nimport { renderToStaticMarkup } from 'react-dom/server';\nimport type { SerializablePreset } from './presets.js';\nimport { serializeLeaf } from './serializer.js';\n\nexport interface RenderOptions {\n /** CSP nonce applied to data + script tags. */\n cspNonce?: string;\n /** Base URL where static assets are served (e.g. `/__rrroutes/docs/assets`). */\n assetBasePath?: string;\n /** Root path where the docs are mounted (e.g. `/__rrroutes/docs`). Used for client routing. */\n docsBasePath?: string;\n /** Preset collections rendered into the docs UI. */\n presets?: SerializablePreset[];\n /** Optional seed history entries to pre-populate the UI (useful in dev). */\n historySeeds?: SerializableHistoryEntry[];\n}\n\nconst DEFAULT_ASSET_BASE = '/__rrroutes/docs/assets';\n\nfunction normalizeBase(base: string) {\n if (!base) return DEFAULT_ASSET_BASE;\n return base.endsWith('/') ? base.slice(0, -1) : base;\n}\n\nfunction normalizeDocsBase(base: string | undefined) {\n if (!base) return '';\n if (base === '/') return '/';\n return base.endsWith('/') && base.length > 1 ? base.slice(0, -1) : base;\n}\n\ntype DocsDocumentProps = {\n leavesJson: string;\n presetsJson: string;\n assetBase: string;\n docsBase: string;\n historyJson: string;\n cspNonce?: string;\n};\n\nexport const DocsDocument = ({\n leavesJson,\n presetsJson,\n assetBase,\n docsBase,\n historyJson,\n cspNonce,\n}: DocsDocumentProps) => {\n const cssHref = `${assetBase}/docs.css`;\n const jsSrc = `${assetBase}/docs.js`;\n const configJson = serializeConfig(docsBase);\n\n return (\n <html lang=\"en\">\n <head>\n <meta charSet=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>API Reference</title>\n <link rel=\"stylesheet\" href={cssHref} />\n </head>\n <body>\n <div id=\"docs-root\"></div>\n <script\n id=\"leaf-data\"\n type=\"application/json\"\n nonce={cspNonce}\n dangerouslySetInnerHTML={{ __html: leavesJson }}\n />\n <script\n id=\"preset-data\"\n type=\"application/json\"\n nonce={cspNonce}\n dangerouslySetInnerHTML={{ __html: presetsJson }}\n />\n <script\n id=\"history-data\"\n type=\"application/json\"\n nonce={cspNonce}\n dangerouslySetInnerHTML={{ __html: historyJson }}\n />\n <script\n id=\"docs-config\"\n type=\"application/json\"\n nonce={cspNonce}\n dangerouslySetInnerHTML={{ __html: configJson }}\n />\n <script type=\"module\" src={jsSrc} nonce={cspNonce} />\n </body>\n </html>\n );\n};\n\nfunction serializeLeaves(leaves: AnyLeaf[]) {\n return JSON.stringify(leaves.map(serializeLeaf)).replace(/<\\//g, '<\\\\/');\n}\n\nfunction serializePresets(presets: SerializablePreset[] | undefined) {\n return JSON.stringify(Array.isArray(presets) ? presets : []).replace(/<\\//g, '<\\\\/');\n}\n\nfunction serializeHistorySeeds(historySeeds: SerializableHistoryEntry[] | undefined) {\n return JSON.stringify(Array.isArray(historySeeds) ? historySeeds : []).replace(/<\\//g, '<\\\\/');\n}\n\nfunction serializeConfig(docsBase: string) {\n return JSON.stringify({ docsBasePath: docsBase }).replace(/<\\//g, '<\\\\/');\n}\n\nexport function createLeafDocsDocument(\n leaves: AnyLeaf[],\n options: RenderOptions = {},\n): ReactElement {\n const assetBase = normalizeBase(options.assetBasePath ?? DEFAULT_ASSET_BASE);\n const leavesJson = serializeLeaves(leaves);\n const presetsJson = serializePresets(options.presets);\n const docsBase = normalizeDocsBase(options.docsBasePath);\n const historyJson = serializeHistorySeeds(options.historySeeds);\n\n return (\n <DocsDocument\n leavesJson={leavesJson}\n presetsJson={presetsJson}\n assetBase={assetBase}\n docsBase={docsBase}\n historyJson={historyJson}\n cspNonce={options.cspNonce}\n />\n );\n}\n\nexport function renderLeafDocsHTML(leaves: AnyLeaf[], options: RenderOptions = {}): string {\n const doc = createLeafDocsDocument(leaves, options);\n const html = renderToStaticMarkup(doc);\n return `<!DOCTYPE html>${html}`;\n}\n\nexport type SerializableHistoryEntry = {\n id?: string;\n timestamp: number;\n method: string;\n path: string;\n fullUrl: string;\n params?: Record<string, string>;\n query?: Record<string, string>;\n body?: string;\n output?: string;\n status?: number;\n durationMs: number;\n error?: string;\n};\n","// schemaIntrospection.ts\nimport * as z from \"zod\";\n\nexport type SerializableSchemaNode = {\n kind: string; // \"object\" | \"string\" | \"number\" | ...\n optional?: boolean;\n nullable?: boolean;\n description?: string;\n\n // object\n properties?: Record<string, SerializableSchemaNode>;\n // array\n element?: SerializableSchemaNode;\n // union\n union?: SerializableSchemaNode[];\n // literal\n literal?: unknown;\n // enum\n enumValues?: string[];\n};\n\ntype ZodAny = z.ZodTypeAny;\n\n/**\n * Zod 3 uses `schema._def`, Zod 4 uses `schema._zod.def`.\n */\nfunction getDef(schema: unknown): any | undefined {\n if (!schema || typeof schema !== \"object\") return undefined;\n const anySchema = schema as any;\n return anySchema._zod?.def ?? anySchema._def;\n}\n\n/**\n * Try to get a human-readable description.\n * Zod 4: use metadata/registry; fallback to internal def.description.\n * Zod 3: only internal def.description exists.\n */\nfunction getDescription(schema: ZodAny): string | undefined {\n const anyZ: any = z as any;\n\n // Zod 4 global registry metadata, if present\n const registry = anyZ.globalRegistry?.get\n ? anyZ.globalRegistry.get(schema)\n : undefined;\n if (registry && typeof registry.description === \"string\") {\n return registry.description;\n }\n\n // Legacy / internal description\n const def = getDef(schema);\n if (def && typeof def.description === \"string\") {\n return def.description;\n }\n\n return undefined;\n}\n\n/**\n * Peel off wrappers (effects, optional, nullable, default) and\n * return the inner schema + flags.\n *\n * Supports:\n * - Zod 3: ZodEffects, ZodOptional, ZodNullable, ZodDefault\n * - Zod 4: ZodOptional, ZodNullable, ZodDefault\n */\nfunction unwrap(schema: ZodAny): {\n base: ZodAny;\n optional: boolean;\n nullable: boolean;\n} {\n let s: ZodAny = schema;\n let optional = false;\n let nullable = false;\n\n // Zod 3 only (undefined in Zod 4)\n const ZodEffectsCtor: any = (z as any).ZodEffects;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n // Zod 3: ZodEffects wrapper\n if (ZodEffectsCtor && s instanceof ZodEffectsCtor) {\n const def = getDef(s) || {};\n const sourceType =\n typeof (s as any).sourceType === \"function\"\n ? (s as any).sourceType()\n : def.schema;\n if (!sourceType) break;\n s = sourceType;\n continue;\n }\n\n // Zod 3 + 4: optional/nullable/default wrappers\n if (s instanceof z.ZodOptional) {\n optional = true;\n const def = getDef(s);\n s = (def && def.innerType) || s; // innerType exists in both 3 & 4\n continue;\n }\n\n if (s instanceof z.ZodNullable) {\n nullable = true;\n const def = getDef(s);\n s = (def && def.innerType) || s;\n continue;\n }\n\n if (s instanceof z.ZodDefault) {\n const def = getDef(s);\n s = (def && def.innerType) || s;\n continue;\n }\n\n break;\n }\n\n return { base: s, optional, nullable };\n}\n\nexport function introspectSchema(\n schema: ZodAny | undefined\n): SerializableSchemaNode | undefined {\n if (!schema) return undefined;\n\n const { base, optional, nullable } = unwrap(schema);\n const def = getDef(base);\n\n const node: SerializableSchemaNode = {\n kind: inferKind(base),\n optional: optional || undefined,\n nullable: nullable || undefined,\n description: getDescription(base),\n };\n\n // OBJECT\n if (base instanceof z.ZodObject) {\n // Zod 3: _def.shape() (function)\n // Zod 4: .shape getter returns an object\n const rawShape: any =\n (base as any).shape ?? (def && typeof def.shape === \"function\"\n ? def.shape()\n : def?.shape);\n\n const shape =\n typeof rawShape === \"function\" ? rawShape() : rawShape ?? {};\n\n const props: Record<string, SerializableSchemaNode> = {};\n for (const key of Object.keys(shape)) {\n const child = shape[key] as ZodAny;\n const childNode = introspectSchema(child);\n if (childNode) props[key] = childNode;\n }\n node.properties = props;\n }\n\n // ARRAY\n if (base instanceof z.ZodArray) {\n // Zod 3: def.type is inner schema\n // Zod 4: def.element is inner schema\n const inner =\n (def && (def.element as ZodAny)) ||\n (def && (def.type as ZodAny)) ||\n undefined;\n if (inner) {\n node.element = introspectSchema(inner);\n }\n }\n\n // UNION\n if (base instanceof z.ZodUnion) {\n const options: ZodAny[] = (def && def.options) || [];\n node.union = options\n .map((opt) => introspectSchema(opt))\n .filter(Boolean) as SerializableSchemaNode[];\n }\n\n // LITERAL\n if (base instanceof z.ZodLiteral) {\n if (def) {\n // Zod 4: def.values (multi-literal)\n if (Array.isArray(def.values)) {\n node.literal =\n def.values.length === 1 ? def.values[0] : def.values.slice();\n } else {\n // Zod 3: def.value\n node.literal = def.value;\n }\n }\n }\n\n // ENUM\n if (base instanceof z.ZodEnum) {\n if (def) {\n if (Array.isArray(def.values)) {\n // Zod 3\n node.enumValues = def.values.slice();\n } else if (def.entries && typeof def.entries === \"object\") {\n // Zod 4: entries is a { key: value } map\n node.enumValues = Object.values(def.entries).map((v: unknown) =>\n String(v)\n );\n }\n }\n }\n\n return node;\n}\n\nfunction inferKind(schema: ZodAny): string {\n // This path still uses instanceof; it works with Zod 4 Classic\n // (importing from \"zod\"). Anything unknown falls back to \"unknown\".\n if (schema instanceof z.ZodString) return \"string\";\n if (schema instanceof z.ZodNumber) return \"number\";\n if (schema instanceof z.ZodBoolean) return \"boolean\";\n if (schema instanceof z.ZodBigInt) return \"bigint\";\n if (schema instanceof z.ZodDate) return \"date\";\n if (schema instanceof z.ZodArray) return \"array\";\n if (schema instanceof z.ZodObject) return \"object\";\n if (schema instanceof z.ZodUnion) return \"union\";\n if (schema instanceof z.ZodLiteral) return \"literal\";\n if (schema instanceof z.ZodEnum) return \"enum\";\n if (schema instanceof z.ZodRecord) return \"record\";\n if (schema instanceof z.ZodTuple) return \"tuple\";\n if (schema instanceof z.ZodUnknown) return \"unknown\";\n if (schema instanceof z.ZodAny) return \"any\";\n\n return \"unknown\";\n}\n","// serializer.ts\nimport type { AnyLeaf, MethodCfg } from \"@emeryld/rrroutes-contract\";\nimport { introspectSchema, SerializableSchemaNode } from \"./schemaIntrospection.js\";\n\ntype SerializableMethodCfg = Pick<\n MethodCfg,\n | \"description\"\n | \"summary\"\n | \"docsGroup\"\n | \"tags\"\n | \"deprecated\"\n | \"stability\"\n | \"feed\"\n | \"docsMeta\"\n> & {\n hasBody: boolean;\n hasQuery: boolean;\n hasParams: boolean;\n hasOutput: boolean;\n\n // NEW: full Zod ASTs\n bodySchema?: SerializableSchemaNode;\n querySchema?: SerializableSchemaNode;\n paramsSchema?: SerializableSchemaNode;\n outputSchema?: SerializableSchemaNode;\n};\n\nexport type SerializableLeaf = {\n method: string;\n path: string;\n cfg: SerializableMethodCfg;\n};\n\nexport type { SerializableSchemaNode } from \"./schemaIntrospection.js\";\n\nexport function serializeLeaf(leaf: AnyLeaf): SerializableLeaf {\n const cfg = leaf.cfg;\n\n const tags = Array.isArray(cfg.tags) ? cfg.tags.slice() : [];\n\n return {\n method: leaf.method, // 'get' | 'post' | ...\n path: leaf.path,\n cfg: {\n description: cfg.description,\n summary: cfg.summary,\n docsGroup: cfg.docsGroup,\n tags,\n deprecated: cfg.deprecated,\n stability: cfg.stability,\n feed: !!cfg.feed,\n docsMeta: cfg.docsMeta,\n hasBody: !!cfg.bodySchema || !!cfg.bodyFiles?.length,\n hasQuery: !!cfg.querySchema,\n hasParams: !!cfg.paramsSchema,\n hasOutput: !!cfg.outputSchema,\n\n bodySchema: introspectSchema(cfg.bodySchema),\n querySchema: introspectSchema(cfg.querySchema),\n paramsSchema: introspectSchema(cfg.paramsSchema),\n outputSchema: introspectSchema(cfg.outputSchema),\n },\n };\n}\n","// renderLeafDocsHTML.ts\nimport type { AnyLeaf } from \"@emeryld/rrroutes-contract\";\nimport {\n createLeafDocsDocument,\n renderLeafDocsHTML as LeafDocsPage,\n RenderOptions,\n} from \"./LeafDocsPage.js\";\n\nexport function renderLeafDocsHTML(leaves: AnyLeaf[], options: RenderOptions = {}): string {\n return LeafDocsPage(leaves, options);\n}\n\nexport type { RenderOptions, SerializableHistoryEntry } from \"./LeafDocsPage.js\";\nexport { createLeafDocsDocument } from \"./LeafDocsPage.js\";\nexport type { SerializablePreset, SerializablePresetOperation } from \"./presets.js\";\n"],"mappings":"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","names":["renderLeafDocsHTML","renderLeafDocsHTML"]}
@@ -1 +1 @@
1
- :root{--bg-root: #020617;--bg-card: rgba(15, 23, 42, .6);--bg-card-hover: rgba(30, 41, 59, .7);--bg-glass: rgba(15, 23, 42, .9);--bg-panel: #0f172a;--border-subtle: rgba(148, 163, 184, .2);--text-main: #f8fafc;--text-muted: #94a3b8;--text-accent: #c4b5fd;--accent-primary: #a855f7;--accent-glow: rgba(168, 85, 247, .25);--accent-success: #4ade80;--accent-error: #f87171;--method-get: #4ade80;--method-get-bg: rgba(34, 197, 94, .15);--method-post: #60a5fa;--method-post-bg: rgba(59, 130, 246, .15);--method-put: #facc15;--method-put-bg: rgba(234, 179, 8, .15);--method-patch: #2dd4bf;--method-patch-bg: rgba(45, 212, 191, .15);--method-delete: #f87171;--method-delete-bg: rgba(248, 113, 113, .15);--radius-card: 12px;--shadow-card: 0 4px 20px rgba(0, 0, 0, .4);--shadow-panel: -5px 0 30px rgba(0,0,0,.5);--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;--tag-0-bg: rgba(254, 202, 202, .2);--tag-0-fg: #fca5a5;--tag-1-bg: rgba(253, 230, 138, .2);--tag-1-fg: #fcd34d;--tag-2-bg: rgba(187, 247, 208, .2);--tag-2-fg: #86efac;--tag-3-bg: rgba(165, 243, 252, .2);--tag-3-fg: #67e8f9;--tag-4-bg: rgba(191, 219, 254, .2);--tag-4-fg: #93c5fd;--tag-5-bg: rgba(233, 213, 255, .2);--tag-5-fg: #d8b4fe;--tag-6-bg: rgba(251, 207, 232, .2);--tag-6-fg: #f9a8d4}*{box-sizing:border-box}html,body{height:100%;margin:0;padding:0;background-color:var(--bg-root);color:var(--text-main);font-family:system-ui,-apple-system,sans-serif}body{background-image:radial-gradient(circle at 15% 10%,rgba(88,28,135,.15),transparent 40%),radial-gradient(circle at 85% 80%,rgba(15,23,42,1),transparent 40%);background-attachment:fixed}.page{min-height:100vh;padding:20px 40px 60px;max-width:1600px;margin:0 auto;display:flex;flex-direction:column;gap:24px}.header{display:flex;justify-content:space-between;padding-bottom:10px;border-bottom:1px solid var(--border-subtle)}.header-title h1{font-size:28px;margin:0;font-weight:700;letter-spacing:-.02em;background:linear-gradient(to right,#e2e8f0,#a855f7);-webkit-background-clip:text;-webkit-text-fill-color:transparent}.controls-container{position:sticky;top:0;z-index:50;background:var(--bg-glass);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);border:1px solid var(--border-subtle);border-radius:var(--radius-card);padding:12px 16px;display:flex;flex-direction:column;gap:12px;box-shadow:0 10px 30px -10px #00000080}.filters-row{display:flex;align-items:flex-start;gap:16px;width:100%}.left-column{display:flex;flex-direction:column;gap:16px;flex-basis:300px;min-width:250px}.search-box{width:100%;position:relative}.search-input{width:100%;background:#02061799;border:1px solid var(--border-subtle);color:var(--text-main);padding:8px 12px 8px 32px;border-radius:6px;font-size:13px}.search-input:focus{outline:none;border-color:var(--accent-primary)}.search-icon{position:absolute;left:10px;top:50%;transform:translateY(-50%);color:var(--text-muted);font-size:12px}.filter-group{display:flex;flex-direction:column;gap:6px}.filter-label{font-size:10px;text-transform:uppercase;color:var(--text-muted);font-weight:700}.checkbox-group{display:flex;flex-wrap:wrap;gap:6px}.pill-checkbox input{display:none}.pill-checkbox span{display:inline-block;padding:3px 8px;border-radius:4px;font-size:11px;font-weight:600;background:#1e293b80;color:var(--text-muted);border:1px solid transparent;cursor:pointer}.pill-checkbox input:checked+span{background:#a855f726;color:var(--accent-primary);border-color:var(--accent-primary)}.pill-checkbox.colored-tag input:checked+.tag-filter-pill{border-color:var(--accent-primary);box-shadow:0 0 0 1px var(--accent-glow)}.overview-row{display:flex;flex-wrap:wrap;gap:8px;padding-top:8px;border-top:1px solid var(--border-subtle);align-items:center}.overview-label{font-size:10px;text-transform:uppercase;color:var(--text-muted);font-weight:700}.group-chip{font-size:11px;text-decoration:none;color:var(--text-accent);background:#a855f714;padding:3px 8px;border-radius:4px;border:1px solid transparent}.group-chip:hover{background:#a855f733;border-color:var(--accent-primary);color:#fff}.api-group{margin-bottom:40px;scroll-margin-top:180px}.group-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;border-bottom:1px solid var(--border-subtle)}.group-title{font-size:20px;font-weight:600;margin:0;padding-bottom:8px}.group-actions button{background:transparent;border:1px solid var(--border-subtle);color:var(--text-muted);font-size:11px;padding:4px 10px;border-radius:4px;cursor:pointer;margin-left:8px}.endpoint-card{background:var(--bg-card);border:1px solid var(--border-subtle);border-radius:var(--radius-card);overflow:hidden;transition:all .2s ease;margin-bottom:12px}.endpoint-card:hover{border-color:#94a3b866;box-shadow:var(--shadow-card)}.card-header{padding:12px 16px;display:flex;align-items:center;gap:12px;flex-wrap:wrap;cursor:pointer}.header-spacer{flex:1}.card-body{display:none;padding:0 16px 16px;border-top:1px solid var(--border-subtle);margin-top:4px}.endpoint-card[data-expanded=true] .card-body{display:block;animation:slideDown .2s ease-out}.endpoint-card[data-expanded=true] .expand-icon{transform:rotate(180deg);color:var(--text-main)}@keyframes slideDown{0%{opacity:0;transform:translateY(-5px)}to{opacity:1;transform:translateY(0)}}.method-badge{font-size:11px;font-weight:800;text-transform:uppercase;padding:4px 8px;border-radius:4px;min-width:55px;text-align:center}.m-GET{background:var(--method-get-bg);color:var(--method-get);border:1px solid rgba(74,222,128,.3)}.m-POST{background:var(--method-post-bg);color:var(--method-post);border:1px solid rgba(96,165,250,.3)}.m-PUT{background:var(--method-put-bg);color:var(--method-put);border:1px solid rgba(250,204,21,.3)}.m-PATCH{background:var(--method-patch-bg);color:var(--method-patch);border:1px solid rgba(45,212,191,.3)}.m-DELETE{background:var(--method-delete-bg);color:var(--method-delete);border:1px solid rgba(248,113,113,.3)}.path-container{font-family:var(--font-mono);font-size:13px;display:inline-flex;align-items:center;gap:8px;padding:2px 6px;border-radius:4px}.path-container:hover{background:#ffffff0d}.copy-icon{opacity:0;font-size:10px}.path-container:hover .copy-icon{opacity:1}.tags-container{display:flex;gap:6px}.status-badge{font-size:9px;text-transform:uppercase;padding:2px 6px;border-radius:3px;font-weight:600}.tag-0{background:var(--tag-0-bg);color:var(--tag-0-fg);border-color:var(--tag-0-fg)}.tag-1{background:var(--tag-1-bg);color:var(--tag-1-fg);border-color:var(--tag-1-fg)}.tag-2{background:var(--tag-2-bg);color:var(--tag-2-fg);border-color:var(--tag-2-fg)}.tag-3{background:var(--tag-3-bg);color:var(--tag-3-fg);border-color:var(--tag-3-fg)}.tag-4{background:var(--tag-4-bg);color:var(--tag-4-fg);border-color:var(--tag-4-fg)}.tag-5{background:var(--tag-5-bg);color:var(--tag-5-fg);border-color:var(--tag-5-fg)}.tag-6{background:var(--tag-6-bg);color:var(--tag-6-fg);border-color:var(--tag-6-fg)}.not-implemented{border-color:var(--method-delete);color:var(--method-delete);background:var(--method-delete-bg)}.btn-try-it{background:#a855f71a;border:1px solid rgba(168,85,247,.4);color:var(--text-accent);font-size:11px;font-weight:600;padding:4px 12px;border-radius:12px;cursor:pointer;transition:all .2s;margin-right:12px;display:flex;align-items:center;gap:6px}.btn-try-it:hover{background:#a855f740;color:#fff;box-shadow:0 0 10px var(--accent-glow)}.btn-try-it .play-icon{font-size:10px}.section-block{margin-top:18px}.section-title{font-size:11px;text-transform:uppercase;letter-spacing:.1em;color:var(--text-muted);margin-bottom:8px;border-bottom:1px solid var(--border-subtle);padding-bottom:4px}.schema-table{width:100%;border-collapse:collapse;font-size:12px}.schema-table th{text-align:left;color:var(--text-muted);font-weight:600;padding:6px 8px;border-bottom:1px solid var(--border-subtle);background:#0003}.schema-table td{padding:6px 8px;border-bottom:1px solid var(--border-subtle);vertical-align:top;color:var(--text-muted)}.col-name{font-family:var(--font-mono);color:var(--text-accent)!important;width:25%}.col-type{font-family:var(--font-mono);color:#93c5fd!important;width:15%}.req-badge{font-size:9px;text-transform:uppercase;padding:2px 4px;border-radius:2px}.req-true{color:#4ade80;background:#4ade801a}.req-false{color:#94a3b8;background:#94a3b81a}.schema-toggle{background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:10px;padding:0 4px;margin-right:4px;width:16px;text-align:center}.schema-toggle:hover{color:var(--text-main)}tr[data-hidden=true]{display:none}.schema-name-cell{display:flex;align-items:center}.schema-name-text{opacity:.8}.schema-toggle-placeholder{width:20px;display:inline-block}.schema-depth-0{padding-left:0}.schema-depth-1{padding-left:16px}.schema-depth-2{padding-left:32px}.schema-depth-3{padding-left:48px}.schema-depth-4{padding-left:64px}.schema-depth-5{padding-left:80px}.schema-depth-6{padding-left:96px}.schema-depth-7{padding-left:112px}.schema-depth-8{padding-left:128px}.playground-overlay{position:fixed;top:0;right:0;bottom:0;width:600px;max-width:90vw;background:var(--bg-panel);border-left:1px solid var(--border-subtle);box-shadow:var(--shadow-panel);z-index:100;transform:translate(100%);transition:transform .3s cubic-bezier(.16,1,.3,1);display:flex;flex-direction:column}.playground-overlay.open{transform:translate(0)}.loading-placeholder{text-align:center;padding:40px;color:#64748b}.no-results{text-align:center;padding:40px;color:var(--text-muted)}.section-header-row{display:flex;justify-content:space-between;align-items:flex-end;border-bottom:1px solid var(--border-subtle);margin-bottom:8px}.section-title-no-border{border:none;margin:0}.schema-actions-inline{margin-bottom:4px}.schema-actions-inline button.expand-all{background:none;border:none;color:var(--text-accent);cursor:pointer;font-size:10px}.schema-actions-inline button.collapse-all{background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:10px}.pg-header{padding:16px;border-bottom:1px solid var(--border-subtle);display:flex;justify-content:space-between;align-items:center;background:var(--bg-glass)}.pg-title{font-size:16px;font-weight:700;color:var(--text-main)}.pg-close{background:transparent;border:none;color:var(--text-muted);font-size:20px;cursor:pointer}.pg-close:hover{color:var(--text-main)}.pg-content{flex:1;overflow-y:auto;padding:20px;display:flex;flex-direction:column;gap:24px}.pg-section{display:flex;flex-direction:column;gap:8px}.pg-label{font-size:11px;text-transform:uppercase;color:var(--text-muted);font-weight:700;letter-spacing:.05em}.pg-input,.pg-textarea{background:#02061766;border:1px solid var(--border-subtle);color:var(--text-main);padding:8px;border-radius:6px;font-family:var(--font-mono);font-size:12px}.pg-input:focus,.pg-textarea:focus{outline:none;border-color:var(--accent-primary)}.pg-textarea{min-height:150px;resize:vertical}.pg-input-disabled-label{opacity:.7}.param-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}.param-row{display:contents}.btn-add-query{font-size:11px;background:none;border:1px dashed var(--border-subtle);color:var(--text-muted);padding:4px;cursor:pointer}.pg-footer{padding:16px;border-top:1px solid var(--border-subtle);background:var(--bg-glass);display:flex;justify-content:flex-end;gap:12px}.btn-send{background:var(--accent-primary);color:#fff;border:none;padding:8px 24px;border-radius:6px;font-weight:600;cursor:pointer;transition:opacity .2s}.btn-send:hover{opacity:.9}.btn-send:disabled{opacity:.5;cursor:not-allowed}.response-box{background:#0000004d;border-radius:8px;border:1px solid var(--border-subtle);overflow:hidden}.response-meta{background:#ffffff0d;padding:8px 12px;display:flex;gap:12px;font-size:11px;font-family:var(--font-mono);border-bottom:1px solid var(--border-subtle)}.meta-item{display:flex;align-items:center;gap:4px}.status-ok{color:var(--accent-success)}.status-err{color:var(--accent-error)}.response-body{padding:12px;overflow-x:auto;font-family:var(--font-mono);font-size:12px}.pg-response-hidden{display:none}.response-pre{margin:0}.json-tree{font-family:var(--font-mono);line-height:1.6}.jt-obj,.jt-arr{margin-left:14px}.jt-key{color:#93c5fd;margin-right:4px}.jt-str{color:#86efac}.jt-num{color:#fca5a5}.jt-bool{color:#fcd34d}.jt-null{color:#94a3b8}.jt-toggle{display:inline-block;width:12px;text-align:center;cursor:pointer;color:var(--text-muted);-webkit-user-select:none;user-select:none;margin-left:-14px}.jt-toggle:hover{color:var(--text-main)}.jt-collapsed .jt-content{display:none}.jt-collapsed:after{content:" ... ";color:var(--text-muted);font-size:10px}.json-tree-line{padding-left:16px}.hidden-template{display:none}
1
+ :root{--bg-root: #020617;--bg-card: rgba(15, 23, 42, .6);--bg-card-hover: rgba(30, 41, 59, .7);--bg-glass: rgba(15, 23, 42, .9);--bg-panel: #0f172a;--border-subtle: rgba(148, 163, 184, .2);--text-main: #f8fafc;--text-muted: #94a3b8;--text-accent: #c4b5fd;--accent-primary: #a855f7;--accent-glow: rgba(168, 85, 247, .25);--accent-success: #4ade80;--accent-error: #f87171;--method-get: #4ade80;--method-get-bg: rgba(34, 197, 94, .15);--method-post: #60a5fa;--method-post-bg: rgba(59, 130, 246, .15);--method-put: #facc15;--method-put-bg: rgba(234, 179, 8, .15);--method-patch: #2dd4bf;--method-patch-bg: rgba(45, 212, 191, .15);--method-delete: #f87171;--method-delete-bg: rgba(248, 113, 113, .15);--radius-card: 12px;--shadow-card: 0 4px 20px rgba(0, 0, 0, .4);--shadow-panel: -5px 0 30px rgba(0,0,0,.5);--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;--tag-0-bg: rgba(254, 202, 202, .2);--tag-0-fg: #fca5a5;--tag-1-bg: rgba(253, 230, 138, .2);--tag-1-fg: #fcd34d;--tag-2-bg: rgba(187, 247, 208, .2);--tag-2-fg: #86efac;--tag-3-bg: rgba(165, 243, 252, .2);--tag-3-fg: #67e8f9;--tag-4-bg: rgba(191, 219, 254, .2);--tag-4-fg: #93c5fd;--tag-5-bg: rgba(233, 213, 255, .2);--tag-5-fg: #d8b4fe;--tag-6-bg: rgba(251, 207, 232, .2);--tag-6-fg: #f9a8d4}*{box-sizing:border-box}html,body{height:100%;margin:0;padding:0;background-color:var(--bg-root);color:var(--text-main);font-family:system-ui,-apple-system,sans-serif}body{background-image:radial-gradient(circle at 15% 10%,rgba(88,28,135,.15),transparent 40%),radial-gradient(circle at 85% 80%,rgba(15,23,42,1),transparent 40%);background-attachment:fixed}.page{min-height:100vh;padding:20px 40px 60px;max-width:1600px;margin:0 auto;display:flex;flex-direction:column;gap:24px}.header{display:flex;justify-content:space-between;padding-bottom:10px;border-bottom:1px solid var(--border-subtle)}.header-title h1{font-size:28px;margin:0;font-weight:700;letter-spacing:-.02em;background:linear-gradient(to right,#e2e8f0,#a855f7);-webkit-background-clip:text;-webkit-text-fill-color:transparent}.header-actions{display:flex;align-items:center;gap:8px}.preset-nav-btn{background:#a855f71f;border:1px solid rgba(168,85,247,.5);color:var(--text-accent);padding:6px 12px;border-radius:6px;font-weight:700;cursor:pointer;font-size:12px;transition:all .2s ease}.preset-nav-btn:hover{box-shadow:0 0 0 1px var(--accent-glow);color:#fff}.preset-nav-btn:disabled{opacity:.4;cursor:not-allowed}.preset-nav-btn.ghost-btn{background:transparent;border-color:var(--border-subtle);color:var(--text-muted)}.preset-nav-btn.nav-pill{background:#ffffff0a;border-color:var(--border-subtle);color:var(--text-main)}.preset-nav-btn.nav-pill[data-active=true]{background:var(--accent-primary);color:#fff;border-color:var(--accent-primary);box-shadow:0 0 0 1px var(--accent-glow)}.link-button{background:none;border:none;color:var(--text-accent);cursor:pointer;padding:0;font-weight:700}.link-button:hover{color:#fff}.breadcrumb-sep{color:var(--text-muted)}.controls-container{position:sticky;top:0;z-index:50;background:var(--bg-glass);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);border:1px solid var(--border-subtle);border-radius:var(--radius-card);padding:12px 16px;display:flex;flex-direction:column;gap:12px;box-shadow:0 10px 30px -10px #00000080}.filters-row{display:flex;align-items:flex-start;gap:16px;width:100%}.left-column{display:flex;flex-direction:column;gap:16px;flex-basis:300px;min-width:250px}.search-box{width:100%;position:relative}.search-input{width:100%;background:#02061799;border:1px solid var(--border-subtle);color:var(--text-main);padding:8px 12px 8px 32px;border-radius:6px;font-size:13px}.search-input:focus{outline:none;border-color:var(--accent-primary)}.search-icon{position:absolute;left:10px;top:50%;transform:translateY(-50%);color:var(--text-muted);font-size:12px}.filter-group{display:flex;flex-direction:column;gap:6px}.filter-label{font-size:10px;text-transform:uppercase;color:var(--text-muted);font-weight:700}.checkbox-group{display:flex;flex-wrap:wrap;gap:6px}.pill-checkbox input{display:none}.pill-checkbox span{display:inline-block;padding:3px 8px;border-radius:4px;font-size:11px;font-weight:600;background:#1e293b80;color:var(--text-muted);border:1px solid transparent;cursor:pointer}.pill-checkbox input:checked+span{background:#a855f726;color:var(--accent-primary);border-color:var(--accent-primary)}.pill-checkbox.colored-tag input:checked+.tag-filter-pill{border-color:var(--accent-primary);box-shadow:0 0 0 1px var(--accent-glow)}.overview-row{display:flex;flex-wrap:wrap;gap:8px;padding-top:8px;border-top:1px solid var(--border-subtle);align-items:center}.overview-label{font-size:10px;text-transform:uppercase;color:var(--text-muted);font-weight:700}.group-chip{font-size:11px;text-decoration:none;color:var(--text-accent);background:#a855f714;padding:3px 8px;border-radius:4px;border:1px solid transparent}.group-chip:hover{background:#a855f733;border-color:var(--accent-primary);color:#fff}.preset-menu{display:flex;flex-direction:column;gap:16px}.preset-menu-header{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap}.preset-menu-title{margin:0}.preset-menu-subtitle{margin:4px 0 0;color:var(--text-muted)}.preset-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px}.preset-card{background:var(--bg-card);border:1px solid var(--border-subtle);border-radius:var(--radius-card);padding:14px;display:flex;flex-direction:column;gap:8px;cursor:pointer;transition:border-color .2s ease,transform .2s ease}.preset-card:hover{border-color:#94a3b866;box-shadow:var(--shadow-card);transform:translateY(-2px)}.preset-card-title{font-weight:700;font-size:15px}.preset-card-desc{margin:0;color:var(--text-muted);line-height:1.4}.preset-card-meta{display:flex;gap:8px;align-items:center;font-size:11px;color:var(--text-muted)}.preset-tag-row{display:flex;gap:6px;flex-wrap:wrap}.preset-tag{background:#ffffff0f;border:1px solid var(--border-subtle);color:var(--text-muted);padding:3px 8px;border-radius:999px;font-size:11px}.preset-empty{padding:20px;border:1px dashed var(--border-subtle);border-radius:var(--radius-card);color:var(--text-muted);display:flex;gap:12px;align-items:center}.api-group{margin-bottom:40px;scroll-margin-top:180px}.group-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;border-bottom:1px solid var(--border-subtle)}.group-title{font-size:20px;font-weight:600;margin:0;padding-bottom:8px}.group-actions button{background:transparent;border:1px solid var(--border-subtle);color:var(--text-muted);font-size:11px;padding:4px 10px;border-radius:4px;cursor:pointer;margin-left:8px}.endpoint-card{background:var(--bg-card);border:1px solid var(--border-subtle);border-radius:var(--radius-card);overflow:hidden;transition:all .2s ease;margin-bottom:12px}.endpoint-card:hover{border-color:#94a3b866;box-shadow:var(--shadow-card);transform:translateY(-2px)}.card-header{padding:12px 16px;display:flex;align-items:center;gap:12px;flex-wrap:wrap;cursor:pointer}.history-link-btn{background:transparent;border:1px solid var(--border-subtle);color:var(--text-muted);padding:4px 8px;border-radius:6px;cursor:pointer;font-size:11px}.history-link-btn:hover{color:#fff;border-color:var(--accent-primary)}.header-spacer{flex:1}.card-body{display:none;padding:0 16px 16px;border-top:1px solid var(--border-subtle);margin-top:4px}.endpoint-card[data-expanded=true] .card-body{display:block;animation:slideDown .2s ease-out}.endpoint-card[data-expanded=true] .expand-icon{transform:rotate(180deg);color:var(--text-main)}@keyframes slideDown{0%{opacity:0;transform:translateY(-5px)}to{opacity:1;transform:translateY(0)}}.method-badge{font-size:11px;font-weight:800;text-transform:uppercase;padding:4px 8px;border-radius:4px;min-width:55px;text-align:center;transition:all .3s ease}.m-GET{background:var(--method-get-bg);color:var(--method-get);border:1px solid rgba(74,222,128,.3)}.m-GET:hover{background:var(--method-get-hover-bg);color:var(--method-get-hover);border:1px solid rgba(74,222,128,.5)}.m-GET:active{background:var(--method-get-click-bg);color:var(--method-get-click);border:1px solid rgba(74,222,128,.7)}.m-POST{background:var(--method-post-bg);color:var(--method-post);border:1px solid rgba(96,165,250,.3)}.m-POST:hover{background:var(--method-post-hover-bg);color:var(--method-post-hover);border:1px solid rgba(96,165,250,.5)}.m-POST:active{background:var(--method-post-click-bg);color:var(--method-post-click);border:1px solid rgba(96,165,250,.7)}.m-PUT{background:var(--method-put-bg);color:var(--method-put);border:1px solid rgba(250,204,21,.3)}.m-PUT:hover{background:var(--method-put-hover-bg);color:var(--method-put-hover);border:1px solid rgba(250,204,21,.5)}.m-PUT:active{background:var(--method-put-click-bg);color:var(--method-put-click);border:1px solid rgba(250,204,21,.7)}.m-PATCH{background:var(--method-patch-bg);color:var(--method-patch);border:1px solid rgba(45,212,191,.3)}.m-PATCH:hover{background:var(--method-patch-hover-bg);color:var(--method-patch-hover);border:1px solid rgba(45,212,191,.5)}.m-PATCH:active{background:var(--method-patch-click-bg);color:var(--method-patch-click);border:1px solid rgba(45,212,191,.7)}.m-DELETE{background:var(--method-delete-bg);color:var(--method-delete);border:1px solid rgba(248,113,113,.3)}.m-DELETE:hover{background:var(--method-delete-hover-bg);color:var(--method-delete-hover);border:1px solid rgba(248,113,113,.5)}.m-DELETE:active{background:var(--method-delete-click-bg);color:var(--method-delete-click);border:1px solid rgba(248,113,113,.7)}.path-container{font-family:var(--font-mono);font-size:13px;display:inline-flex;align-items:center;gap:8px;padding:2px 6px;border-radius:4px}.path-container:hover{background:#ffffff0d}.copy-icon{opacity:0;font-size:10px}.path-container:hover .copy-icon{opacity:1}.tags-container{display:flex;gap:6px}.status-badge{font-size:9px;text-transform:uppercase;padding:2px 6px;border-radius:3px;font-weight:600}.tag-0{background:var(--tag-0-bg);color:var(--tag-0-fg);border-color:var(--tag-0-fg)}.tag-1{background:var(--tag-1-bg);color:var(--tag-1-fg);border-color:var(--tag-1-fg)}.tag-2{background:var(--tag-2-bg);color:var(--tag-2-fg);border-color:var(--tag-2-fg)}.tag-3{background:var(--tag-3-bg);color:var(--tag-3-fg);border-color:var(--tag-3-fg)}.tag-4{background:var(--tag-4-bg);color:var(--tag-4-fg);border-color:var(--tag-4-fg)}.tag-5{background:var(--tag-5-bg);color:var(--tag-5-fg);border-color:var(--tag-5-fg)}.tag-6{background:var(--tag-6-bg);color:var(--tag-6-fg);border-color:var(--tag-6-fg)}.not-implemented{border-color:var(--method-delete);color:var(--method-delete);background:var(--method-delete-bg)}.btn-try-it{background:#a855f71a;border:1px solid rgba(168,85,247,.4);color:var(--text-accent);font-size:11px;font-weight:600;padding:4px 12px;border-radius:12px;cursor:pointer;transition:all .2s;margin-right:12px;display:flex;align-items:center;gap:6px}.btn-try-it:hover{background:#a855f740;color:#fff;box-shadow:0 0 10px var(--accent-glow)}.btn-try-it .play-icon{font-size:10px}.section-block{margin-top:18px}.section-title{font-size:11px;text-transform:uppercase;letter-spacing:.1em;color:var(--text-muted);margin-bottom:8px;border-bottom:1px solid var(--border-subtle);padding-bottom:4px}.schema-table{width:100%;border-collapse:collapse;font-size:12px}.schema-table th{text-align:left;color:var(--text-muted);font-weight:600;padding:6px 8px;border-bottom:1px solid var(--border-subtle);background:#0003}.schema-table td{padding:6px 8px;border-bottom:1px solid var(--border-subtle);vertical-align:top;color:var(--text-muted)}.col-name{font-family:var(--font-mono);color:var(--text-accent)!important;width:25%}.col-type{font-family:var(--font-mono);color:#93c5fd!important;width:15%}.req-badge{font-size:9px;text-transform:uppercase;padding:2px 4px;border-radius:2px}.req-true{color:#4ade80;background:#4ade801a}.req-false{color:#94a3b8;background:#94a3b81a}.schema-toggle{background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:10px;padding:0 4px;margin-right:4px;width:16px;text-align:center}.schema-toggle:hover{color:var(--text-main)}tr[data-hidden=true]{display:none}.schema-name-cell{display:flex;align-items:center}.schema-name-text{opacity:.8}.schema-toggle-placeholder{width:20px;display:inline-block}.schema-depth-0{padding-left:0}.schema-depth-1{padding-left:16px}.schema-depth-2{padding-left:32px}.schema-depth-3{padding-left:48px}.schema-depth-4{padding-left:64px}.schema-depth-5{padding-left:80px}.schema-depth-6{padding-left:96px}.schema-depth-7{padding-left:112px}.schema-depth-8{padding-left:128px}.playground-overlay{position:fixed;top:0;right:0;bottom:0;width:600px;max-width:90vw;background:var(--bg-panel);border-left:1px solid var(--border-subtle);box-shadow:var(--shadow-panel);z-index:100;transform:translate(100%);transition:transform .3s cubic-bezier(.16,1,.3,1);display:flex;flex-direction:column}.playground-overlay.open{transform:translate(0)}.playground-inline{background:var(--bg-card);border:1px solid var(--border-subtle);border-radius:var(--radius-card);overflow:hidden;margin-top:8px}.playground-inline .pg-content{padding:16px}.playground-inline .pg-footer{padding:12px 16px}.pg-note{margin-left:8px;color:var(--text-muted);font-size:11px}.loading-placeholder{text-align:center;padding:40px;color:#64748b}.no-results{text-align:center;padding:40px;color:var(--text-muted)}.preset-detail{display:flex;flex-direction:column;gap:16px}.preset-detail-header{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;border-bottom:1px solid var(--border-subtle);padding-bottom:8px}.preset-breadcrumb{margin:0 0 4px;color:var(--text-muted);display:flex;align-items:center;gap:6px;font-size:12px}.preset-detail-title{margin:0}.preset-detail-desc{margin:4px 0 0;color:var(--text-muted)}.preset-detail-actions{display:flex;gap:8px;align-items:center}.preset-meta-row{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap}.preset-tags{display:flex;gap:8px;flex-wrap:wrap}.preset-tag.ghost-tag{border-style:dashed}.preset-meta-count{color:var(--text-muted);font-size:12px}.preset-ops-list{display:flex;flex-direction:column;gap:14px}.preset-op-card{border:1px solid var(--border-subtle);border-radius:var(--radius-card);background:var(--bg-card);padding:12px;box-shadow:0 6px 20px #0000001f}.preset-op-card.preset-op-missing{border-style:dashed;opacity:.85}.preset-op-header{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;margin-bottom:8px}.preset-op-label{font-size:11px;color:var(--text-muted);letter-spacing:.08em;text-transform:uppercase}.preset-op-title{display:flex;align-items:center;gap:8px;font-weight:700}.preset-op-path{font-family:var(--font-mono);color:var(--text-main)}.preset-op-summary{margin-top:4px;font-weight:600}.preset-op-desc{margin:4px 0 0;color:var(--text-muted)}.preset-run-all{display:flex;justify-content:flex-end;align-items:center;gap:10px}.preset-run-status{color:var(--text-muted);font-size:12px}.section-header-row{display:flex;justify-content:space-between;align-items:flex-end;border-bottom:1px solid var(--border-subtle);margin-bottom:8px}.section-title-no-border{border:none;margin:0}.schema-actions-inline{margin-bottom:4px}.schema-actions-inline button.expand-all{background:none;border:none;color:var(--text-accent);cursor:pointer;font-size:10px}.schema-actions-inline button.collapse-all{background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:10px}.endpoint-history-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:8px}.endpoint-history-stat{border:1px solid var(--border-subtle);border-radius:8px;padding:8px 10px;background:#ffffff0a}.endpoint-history-label{font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--text-muted)}.endpoint-history-value{font-size:16px;font-weight:700;color:var(--text-main)}.history-mini-empty{color:var(--text-muted);font-size:12px}.pg-header{padding:16px;border-bottom:1px solid var(--border-subtle);display:flex;justify-content:space-between;align-items:center;background:var(--bg-glass)}.pg-title{font-size:16px;font-weight:700;color:var(--text-main)}.pg-close{background:transparent;border:none;color:var(--text-muted);font-size:20px;cursor:pointer}.pg-close:hover{color:var(--text-main)}.pg-content{flex:1;overflow-y:auto;padding:20px;display:flex;flex-direction:column;gap:24px}.pg-section{display:flex;flex-direction:column;gap:8px}.pg-label{font-size:11px;text-transform:uppercase;color:var(--text-muted);font-weight:700;letter-spacing:.05em}.pg-input,.pg-textarea{background:#02061766;border:1px solid var(--border-subtle);color:var(--text-main);padding:8px;border-radius:6px;font-family:var(--font-mono);font-size:12px}.pg-input:focus,.pg-textarea:focus{outline:none;border-color:var(--accent-primary)}.pg-textarea{min-height:150px;resize:vertical}.pg-input-disabled-label{opacity:.7}.param-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}.param-row{display:contents}.param-grid-queries{grid-template-columns:1fr 1fr 40px;align-items:center}.btn-remove-param{background:transparent;border:1px solid var(--border-subtle);color:var(--text-muted);border-radius:6px;width:36px;height:36px;cursor:pointer}.btn-remove-param:hover{color:var(--accent-error);border-color:var(--accent-error)}.btn-remove-param:disabled{opacity:.5;cursor:not-allowed}.btn-add-query{font-size:11px;background:none;border:1px dashed var(--border-subtle);color:var(--text-muted);padding:4px;cursor:pointer}.pg-footer{padding:16px;border-top:1px solid var(--border-subtle);background:var(--bg-glass);display:flex;justify-content:flex-end;gap:12px}.btn-clear{background:transparent;border:1px solid var(--border-subtle);color:var(--text-muted);padding:8px 14px;border-radius:6px;font-weight:600;cursor:pointer;transition:color .2s,border-color .2s}.btn-clear:hover{color:var(--text-main);border-color:var(--text-muted)}.btn-clear:disabled{opacity:.6;cursor:not-allowed}.btn-send{background:var(--accent-primary);color:#fff;border:none;padding:8px 24px;border-radius:6px;font-weight:600;cursor:pointer;transition:opacity .2s}.btn-send:hover{opacity:.9}.btn-send:disabled{opacity:.5;cursor:not-allowed}.response-box{background:#0000004d;border-radius:8px;border:1px solid var(--border-subtle);overflow:hidden}.response-meta{background:#ffffff0d;padding:8px 70px 8px 12px;display:flex;gap:12px;font-size:11px;font-family:var(--font-mono);border-bottom:1px solid var(--border-subtle);position:relative}.response-box{position:relative}.meta-item{display:flex;align-items:center;gap:4px}.status-ok{color:var(--accent-success)}.status-err{color:var(--accent-error)}.response-body{padding:12px;overflow-x:auto;font-family:var(--font-mono);font-size:12px}.pg-response-hidden{display:none}.response-pre{margin:0}.json-tree{font-family:var(--font-mono);line-height:1.6}.jt-obj,.jt-arr{margin-left:14px}.jt-key{color:#93c5fd;margin-right:4px}.jt-str{color:#86efac}.jt-num{color:#fca5a5}.jt-bool{color:#fcd34d}.jt-null{color:#94a3b8}.jt-toggle{display:inline-block;width:12px;text-align:center;cursor:pointer;color:var(--text-muted);-webkit-user-select:none;user-select:none;margin-left:-14px}.jt-toggle:hover{color:var(--text-main)}.jt-collapsed .jt-content{display:none}.jt-collapsed:after{content:" ... ";color:var(--text-muted);font-size:10px}.json-tree-line{padding-left:16px}.history-view{display:flex;flex-direction:column;gap:16px}.history-toolbar{display:flex;flex-direction:column;gap:10px;padding:14px;background:var(--bg-card);border:1px solid var(--border-subtle);border-radius:var(--radius-card);box-shadow:var(--shadow-card)}.history-toolbar-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px}.history-path-filter{grid-column:span 2;min-width:320px}@media(max-width:900px){.history-path-filter{grid-column:span 1;min-width:0}}.history-methods{min-height:36px}.history-duration .duration-inputs{display:flex;gap:8px}.history-sort-row{display:flex;gap:8px;align-items:center}.sort-select{min-width:140px}.sort-direction-btn{height:32px;width:40px;border-radius:6px;border:1px solid var(--border-subtle);background:#ffffff08;color:var(--text-main);cursor:pointer}.sort-direction-btn:hover{border-color:var(--accent-primary);color:#fff}.history-toolbar-actions{display:flex;gap:8px;justify-content:flex-end;flex-wrap:wrap;width:100%;padding-top:6px;border-top:1px dashed var(--border-subtle)}.history-list{display:flex;flex-direction:column;gap:12px}.history-card{border:1px solid var(--border-subtle);border-radius:var(--radius-card);background:var(--bg-card);overflow:hidden}.highlightOnHover:hover{background:#ffffff0d;cursor:pointer;border-radius:6px;padding:6px;margin:12px -6px -6px}.history-card:hover{border-color:#94a3b866;box-shadow:var(--shadow-card);transform:translateY(-2px)}.history-card[data-expanded=true]{box-shadow:var(--shadow-card)}.history-card-header{display:flex;align-items:center;gap:12px;padding:12px 14px;cursor:pointer}.history-card .expand-icon{color:var(--text-muted)}.history-card[data-expanded=true] .expand-icon{color:var(--text-main);transform:rotate(180deg)}.history-title{display:flex;align-items:center;gap:8px;flex:1;min-width:0}.history-path{font-family:var(--font-mono);color:var(--text-main);overflow-wrap:anywhere}.history-meta-row{display:flex;align-items:center;gap:8px}.history-status{border:1px solid var(--border-subtle);padding:2px 8px;border-radius:6px;font-size:11px;color:var(--text-muted)}.history-status-error{border-color:var(--accent-error);color:var(--accent-error)}.history-chip{background:#ffffff0f;border:1px solid var(--border-subtle);color:var(--text-muted);padding:3px 8px;border-radius:6px;font-size:11px}.history-card-body{border-top:1px solid var(--border-subtle);padding:12px 14px;display:flex;flex-direction:column;gap:10px}.history-meta-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px}.history-detail{display:flex;flex-direction:column;gap:4px}.history-detail-label{font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--text-muted)}.history-detail-value{font-size:13px;color:var(--text-main);overflow-wrap:anywhere}.history-kv{display:flex;flex-direction:column;gap:4px}.history-kv-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.history-kv-key{font-family:var(--font-mono);color:var(--text-main);min-width:100px}.history-kv-value{font-family:var(--font-mono);color:var(--text-muted)}.history-empty{color:var(--text-muted);font-style:italic}.history-pre{margin:0;background:#00000040;border:1px solid var(--border-subtle);border-radius:8px;padding:8px;overflow-x:auto;font-family:var(--font-mono);font-size:12px;white-space:pre-wrap}.history-error{color:var(--accent-error);font-weight:700}.history-stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px;margin-top:12px}.history-stat-card{border:1px solid var(--border-subtle);border-radius:var(--radius-card);background:#ffffff08;padding:10px 12px}.history-stat-card[data-tone=good]{border-color:#4ade8066}.history-stat-card[data-tone=warn]{border-color:#eab30866}.history-stat-card[data-tone=bad]{border-color:#f8717166}.history-stat-label{font-size:11px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.08em}.history-stat-value{font-size:22px;font-weight:800;margin-top:4px}.history-stat-hint{font-size:12px;color:var(--text-muted)}.history-graph-card{border:1px solid var(--border-subtle);border-radius:var(--radius-card);background:var(--bg-card);padding:10px 12px;display:flex;flex-direction:column;gap:10px;position:relative}.history-graph-header{display:flex;justify-content:space-between;align-items:center}.history-graph-title{font-weight:700}.history-graph-subtitle{color:var(--text-muted);font-size:12px}.history-graph-body{min-height:120px;display:flex;align-items:stretch}.history-graph-body>*{width:100%}.history-graph-empty{color:var(--text-muted);font-size:12px}.history-bar-chart{display:flex;flex-direction:column;gap:8px}.history-bar-row{display:grid;grid-template-columns:1fr 2fr auto;gap:8px;align-items:center}.history-bar-label{font-size:12px;color:var(--text-main)}.history-bar-track{height:10px;background:#ffffff0f;border-radius:999px;overflow:hidden}.history-bar-fill{height:100%;background:linear-gradient(90deg,var(--accent-primary),rgba(168,85,247,.3));border-radius:999px}.history-bar-fill.tone-good{background:linear-gradient(90deg,#4ade80,#4ade804d)}.history-bar-fill.tone-warn{background:linear-gradient(90deg,#facc15,#facc1540)}.history-bar-fill.tone-bad{background:linear-gradient(90deg,#f87171,#f8717140)}.history-bar-value{font-size:12px;color:var(--text-main);display:flex;flex-direction:column;align-items:flex-end}.history-bar-hint{color:var(--text-muted);font-size:11px}.history-line-chart{width:100%;height:100%;min-height:90px}.history-line-wrapper{position:relative;width:100%;height:100%}.history-line-path{fill:none;stroke:var(--accent-primary);stroke-width:.5}.history-line-dot{fill:#c4b5fd}.history-line-dot.dot-error{fill:var(--accent-error)}.history-line-grid{stroke:#94a3b84d;stroke-width:.6}.history-line-grid.vertical{opacity:.4}.history-line-axis{stroke:var(--border-subtle);stroke-width:1}.history-line-axis-label{fill:var(--text-muted);dominant-baseline:middle;font-size:4px}.history-line-axis-label.x-axis{text-anchor:middle}.history-graph-card.graph-timeline .history-graph-body{min-height:104px}.history-graphs-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px;margin-top:12px}.history-graph-card.graph-boxplot .history-graph-body{min-height:320px;flex:1}@media(min-width:1100px){.history-graphs-grid{grid-template-columns:repeat(4,minmax(0,1fr));grid-template-areas:"timeline timeline boxplot top" "status volume boxplot top";grid-auto-rows:minmax(140px,auto)}.history-graph-card.graph-timeline{grid-area:timeline}.history-graph-card.graph-status{grid-area:status}.history-graph-card.graph-volume{grid-area:volume}.history-graph-card.graph-boxplot{grid-area:boxplot}.history-graph-card.graph-top-paths{grid-area:top}}.history-boxplot-vertical{position:relative;padding-left:64px;padding-bottom:52px;min-height:240px;height:100%}.boxplot-scale{position:absolute;left:0;right:0;top:6px;bottom:52px;pointer-events:none}.scale-row{position:absolute;left:0;right:0;display:flex;align-items:center;gap:8px;font-size:11px;color:var(--text-muted)}.scale-label{width:60px;text-align:right}.scale-line{flex:1;height:1px;background:var(--border-subtle);opacity:.6}.boxplot-columns{display:flex;align-items:stretch;gap:14px;min-height:100%;height:100%;overflow-x:auto;padding-left:4px;padding-bottom:8px}.boxplot-column{display:flex;flex-direction:column;align-items:center;gap:6px;min-width:56px;flex:1 1 56px}.boxplot-vert-track{position:relative;width:32px;flex:1 1 auto;min-height:140px;background:#ffffff0a;border:1px solid var(--border-subtle);border-radius:10px;overflow:hidden}.boxplot-vert-whisker{position:absolute;left:13px;width:6px;background:#94a3b899;border-radius:6px}.boxplot-vert-box{position:absolute;left:7px;width:18px;background:#c4b5fd4d;border:1px solid var(--accent-primary);border-radius:8px}.boxplot-vert-median{position:absolute;left:4px;right:4px;height:2px;background:var(--accent-primary)}.boxplot-col-label{font-size:12px;color:var(--text-main);font-weight:700}.boxplot-col-meta{font-size:11px;color:var(--text-muted);margin-bottom:-4px}.copy-pre-wrapper{position:relative}.copy-pre-wrapper .history-pre{padding-right:48px}.copy-btn{position:absolute;top:6px;right:6px;background:#ffffff14;border:1px solid var(--border-subtle);color:var(--text-main);border-radius:6px;padding:4px 8px;font-size:10px;cursor:pointer;z-index:2}.copy-btn:hover{border-color:var(--accent-primary);color:#fff}.hidden-template{display:none}