@emeryld/rrroutes-openapi 2.2.15 → 2.2.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -36,9 +36,10 @@ __export(index_exports, {
36
36
  });
37
37
  module.exports = __toCommonJS(index_exports);
38
38
  var import_crypto = require("crypto");
39
+ var import_express = require("express");
40
+ var import_node_fs = __toESM(require("fs"), 1);
39
41
  var import_node_path = __toESM(require("path"), 1);
40
42
  var import_node_url = require("url");
41
- var import_express = require("express");
42
43
 
43
44
  // src/docs/LeafDocsPage.tsx
44
45
  var import_server = require("react-dom/server");
@@ -260,7 +261,7 @@ function mountRRRoutesDocs({
260
261
  const assetsMountPath = trimTrailingSlash(
261
262
  options.assetBasePath ?? `${normalizedDocsPath}/assets`
262
263
  );
263
- const publicDir = (0, import_node_url.fileURLToPath)(new URL("../public", import_meta.url));
264
+ const publicDir = resolvePublicDir();
264
265
  const assetsDir = import_node_path.default.join(publicDir, "assets");
265
266
  const cspEnabled = options.csp !== false;
266
267
  router.use(assetsMountPath, (0, import_express.static)(assetsDir, { immutable: true, maxAge: "365d" }));
@@ -295,6 +296,14 @@ function mountRRRoutesDocs({
295
296
  });
296
297
  return { path: docsPath };
297
298
  }
299
+ function resolvePublicDir() {
300
+ const moduleDir = import_node_path.default.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
301
+ const fromModule = (0, import_node_url.fileURLToPath)(new URL("../public", import_meta.url));
302
+ if (import_node_fs.default.existsSync(fromModule)) return fromModule;
303
+ const fallback = import_node_path.default.resolve(moduleDir, "../dist/public");
304
+ if (import_node_fs.default.existsSync(fallback)) return fallback;
305
+ return fromModule;
306
+ }
298
307
  // Annotate the CommonJS export names for ESM import in node:
299
308
  0 && (module.exports = {
300
309
  mountRRRoutesDocs,
@@ -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 { randomBytes } from 'crypto';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { static as expressStatic } from 'express';\nimport type { Request, Response, Router } from 'express';\nimport type { AnyLeaf } from '@emeryld/rrroutes-contract';\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 /** 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 docsPath = options.path ?? '/__rrroutes/docs';\n const normalizedDocsPath = trimTrailingSlash(docsPath);\n const assetsMountPath = trimTrailingSlash(\n options.assetBasePath ?? `${normalizedDocsPath}/assets`,\n );\n const publicDir = fileURLToPath(new URL('../public', import.meta.url));\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 router.get(docsPath, (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: 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\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":"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","names":["renderLeafDocsHTML","renderLeafDocsHTML","path","expressStatic","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":["import { randomBytes } from 'crypto';\nimport { static as expressStatic } from 'express';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { Request, Response, Router } from 'express';\nimport type { AnyLeaf } from '@emeryld/rrroutes-contract';\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 /** 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 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 router.get(docsPath, (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: 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 = path.dirname(fileURLToPath(import.meta.url));\n const fromModule = fileURLToPath(new URL('../public', import.meta.url));\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":"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","names":["renderLeafDocsHTML","renderLeafDocsHTML","path","expressStatic","renderLeafDocsHTML","fs"]}
package/dist/index.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  // src/index.ts
2
2
  import { randomBytes } from "crypto";
3
+ import { static as expressStatic } from "express";
4
+ import fs from "fs";
3
5
  import path from "path";
4
6
  import { fileURLToPath } from "url";
5
- import { static as expressStatic } from "express";
6
7
 
7
8
  // src/docs/LeafDocsPage.tsx
8
9
  import { renderToStaticMarkup } from "react-dom/server";
@@ -223,7 +224,7 @@ function mountRRRoutesDocs({
223
224
  const assetsMountPath = trimTrailingSlash(
224
225
  options.assetBasePath ?? `${normalizedDocsPath}/assets`
225
226
  );
226
- const publicDir = fileURLToPath(new URL("../public", import.meta.url));
227
+ const publicDir = resolvePublicDir();
227
228
  const assetsDir = path.join(publicDir, "assets");
228
229
  const cspEnabled = options.csp !== false;
229
230
  router.use(assetsMountPath, expressStatic(assetsDir, { immutable: true, maxAge: "365d" }));
@@ -258,6 +259,14 @@ function mountRRRoutesDocs({
258
259
  });
259
260
  return { path: docsPath };
260
261
  }
262
+ function resolvePublicDir() {
263
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
264
+ const fromModule = fileURLToPath(new URL("../public", import.meta.url));
265
+ if (fs.existsSync(fromModule)) return fromModule;
266
+ const fallback = path.resolve(moduleDir, "../dist/public");
267
+ if (fs.existsSync(fallback)) return fallback;
268
+ return fromModule;
269
+ }
261
270
  export {
262
271
  mountRRRoutesDocs,
263
272
  renderLeafDocsHTML2 as renderLeafDocsHTML,
@@ -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 { randomBytes } from 'crypto';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { static as expressStatic } from 'express';\nimport type { Request, Response, Router } from 'express';\nimport type { AnyLeaf } from '@emeryld/rrroutes-contract';\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 /** 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 docsPath = options.path ?? '/__rrroutes/docs';\n const normalizedDocsPath = trimTrailingSlash(docsPath);\n const assetsMountPath = trimTrailingSlash(\n options.assetBasePath ?? `${normalizedDocsPath}/assets`,\n );\n const publicDir = fileURLToPath(new URL('../public', import.meta.url));\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 router.get(docsPath, (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: 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\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":"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","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":["import { randomBytes } from 'crypto';\nimport { static as expressStatic } from 'express';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { Request, Response, Router } from 'express';\nimport type { AnyLeaf } from '@emeryld/rrroutes-contract';\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 /** 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 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 router.get(docsPath, (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: 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 = path.dirname(fileURLToPath(import.meta.url));\n const fromModule = fileURLToPath(new URL('../public', import.meta.url));\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":"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","names":["renderLeafDocsHTML","renderLeafDocsHTML"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emeryld/rrroutes-openapi",
3
- "version": "2.2.15",
3
+ "version": "2.2.16",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",