@emeryld/rrroutes-openapi 2.2.15

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/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # @emeryld/rrroutes-openapi
2
+
3
+ RRRoutes docs + “try it” playground, shipped as static assets plus a thin Express helper. The UI is React-based and reads the serialized registry you provide (no OpenAPI YAML needed).
4
+
5
+ ## What you get
6
+ - React docs UI (filters, grouping, schema tables) with a built-in playground for hitting your endpoints.
7
+ - Express helper that mounts the docs page and serves the built assets.
8
+ - Escape hatches: supply your own React tree or prerendered HTML if you want full control.
9
+
10
+ ## Quick start (Express)
11
+
12
+ ```ts
13
+ import express from 'express';
14
+ import { finalize } from '@emeryld/rrroutes-contract';
15
+ import { mountRRRoutesDocs } from '@emeryld/rrroutes-openapi';
16
+ import { leaves } from './contract';
17
+
18
+ const registry = finalize(leaves);
19
+ const router = express.Router();
20
+
21
+ // Mounts page at /__rrroutes/docs and serves JS/CSS from /__rrroutes/docs/assets
22
+ mountRRRoutesDocs({
23
+ router,
24
+ leaves: registry.all,
25
+ options: { path: '/__rrroutes/docs' },
26
+ });
27
+ ```
28
+
29
+ ### Options (server)
30
+ - `path` (default `/__rrroutes/docs`): where the page is mounted.
31
+ - `assetBasePath` (default `${path}/assets`): where the static JS/CSS are served from.
32
+ - `csp` (default `true`): emit a CSP header + nonce.
33
+ - `onRequest`: `(ctx) => ({ leaves?, nonce?, html? })` hook to tweak leaves, override the nonce, or return a fully custom HTML response.
34
+
35
+ ## Render it yourself
36
+
37
+ If you want to control rendering:
38
+
39
+ ```ts
40
+ import { createLeafDocsDocument, renderLeafDocsHTML } from '@emeryld/rrroutes-openapi';
41
+
42
+ // React element – mount into your own tree
43
+ const element = createLeafDocsDocument(registry.all, { assetBasePath: '/__rrroutes/docs/assets' });
44
+
45
+ // Or prerender to a string
46
+ const html = renderLeafDocsHTML(registry.all, { assetBasePath: '/__rrroutes/docs/assets' });
47
+ ```
48
+
49
+ ## Data shape
50
+
51
+ The docs UI expects serialized leaves from `@emeryld/rrroutes-contract`. Each leaf includes:
52
+ - `method`, `path`, and `cfg` (summary, description, tags, docsGroup, stability, deprecated, docsMeta, feed)
53
+ - Optional Zod schemas for params/query/body/output; these are introspected into a serializable AST for schema tables.
54
+
55
+ ## Build / develop
56
+
57
+ ```sh
58
+ pnpm install
59
+ pnpm run dev # Vite dev server for the UI (serves index.html)
60
+ pnpm run build # Builds node bundle + types + web assets to dist/public
61
+ ```
62
+
63
+ Published package contents:
64
+ - `dist/index.{mjs,cjs,d.ts}`: server helper + exports
65
+ - `dist/public/assets/docs.{js,css}`: the docs UI bundle served by `mountRRRoutesDocs`
@@ -0,0 +1,17 @@
1
+ import type { AnyLeaf } from '@emeryld/rrroutes-contract';
2
+ import type { ReactElement } from 'react';
3
+ export interface RenderOptions {
4
+ /** CSP nonce applied to data + script tags. */
5
+ cspNonce?: string;
6
+ /** Base URL where static assets are served (e.g. `/__rrroutes/docs/assets`). */
7
+ assetBasePath?: string;
8
+ }
9
+ type DocsDocumentProps = {
10
+ leavesJson: string;
11
+ assetBase: string;
12
+ cspNonce?: string;
13
+ };
14
+ export declare const DocsDocument: ({ leavesJson, assetBase, cspNonce }: DocsDocumentProps) => import("react/jsx-runtime").JSX.Element;
15
+ export declare function createLeafDocsDocument(leaves: AnyLeaf[], options?: RenderOptions): ReactElement;
16
+ export declare function renderLeafDocsHTML(leaves: AnyLeaf[], options?: RenderOptions): string;
17
+ export {};
@@ -0,0 +1,5 @@
1
+ import type { AnyLeaf } from "@emeryld/rrroutes-contract";
2
+ import { RenderOptions } from "./LeafDocsPage.js";
3
+ export declare function renderLeafDocsHTML(leaves: AnyLeaf[], options?: RenderOptions): string;
4
+ export type { RenderOptions } from "./LeafDocsPage.js";
5
+ export { createLeafDocsDocument } from "./LeafDocsPage.js";
@@ -0,0 +1,15 @@
1
+ import * as z from "zod";
2
+ export type SerializableSchemaNode = {
3
+ kind: string;
4
+ optional?: boolean;
5
+ nullable?: boolean;
6
+ description?: string;
7
+ properties?: Record<string, SerializableSchemaNode>;
8
+ element?: SerializableSchemaNode;
9
+ union?: SerializableSchemaNode[];
10
+ literal?: unknown;
11
+ enumValues?: string[];
12
+ };
13
+ type ZodAny = z.ZodTypeAny;
14
+ export declare function introspectSchema(schema: ZodAny | undefined): SerializableSchemaNode | undefined;
15
+ export {};
@@ -0,0 +1,19 @@
1
+ import type { AnyLeaf, MethodCfg } from "@emeryld/rrroutes-contract";
2
+ import { SerializableSchemaNode } from "./schemaIntrospection.js";
3
+ type SerializableMethodCfg = Pick<MethodCfg, "description" | "summary" | "docsGroup" | "tags" | "deprecated" | "stability" | "feed" | "docsMeta"> & {
4
+ hasBody: boolean;
5
+ hasQuery: boolean;
6
+ hasParams: boolean;
7
+ hasOutput: boolean;
8
+ bodySchema?: SerializableSchemaNode;
9
+ querySchema?: SerializableSchemaNode;
10
+ paramsSchema?: SerializableSchemaNode;
11
+ outputSchema?: SerializableSchemaNode;
12
+ };
13
+ export type SerializableLeaf = {
14
+ method: string;
15
+ path: string;
16
+ cfg: SerializableMethodCfg;
17
+ };
18
+ export type { SerializableSchemaNode } from "./schemaIntrospection.js";
19
+ export declare function serializeLeaf(leaf: AnyLeaf): SerializableLeaf;
package/dist/index.cjs ADDED
@@ -0,0 +1,304 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ mountRRRoutesDocs: () => mountRRRoutesDocs,
34
+ renderLeafDocsHTML: () => renderLeafDocsHTML2,
35
+ serializeLeaf: () => serializeLeaf
36
+ });
37
+ module.exports = __toCommonJS(index_exports);
38
+ var import_crypto = require("crypto");
39
+ var import_node_path = __toESM(require("path"), 1);
40
+ var import_node_url = require("url");
41
+ var import_express = require("express");
42
+
43
+ // src/docs/LeafDocsPage.tsx
44
+ var import_server = require("react-dom/server");
45
+
46
+ // src/docs/schemaIntrospection.ts
47
+ var z = __toESM(require("zod"), 1);
48
+ function getDef(schema) {
49
+ if (!schema || typeof schema !== "object") return void 0;
50
+ const anySchema = schema;
51
+ return anySchema._zod?.def ?? anySchema._def;
52
+ }
53
+ function getDescription(schema) {
54
+ const anyZ = z;
55
+ const registry = anyZ.globalRegistry?.get ? anyZ.globalRegistry.get(schema) : void 0;
56
+ if (registry && typeof registry.description === "string") {
57
+ return registry.description;
58
+ }
59
+ const def = getDef(schema);
60
+ if (def && typeof def.description === "string") {
61
+ return def.description;
62
+ }
63
+ return void 0;
64
+ }
65
+ function unwrap(schema) {
66
+ let s = schema;
67
+ let optional = false;
68
+ let nullable = false;
69
+ const ZodEffectsCtor = z.ZodEffects;
70
+ while (true) {
71
+ if (ZodEffectsCtor && s instanceof ZodEffectsCtor) {
72
+ const def = getDef(s) || {};
73
+ const sourceType = typeof s.sourceType === "function" ? s.sourceType() : def.schema;
74
+ if (!sourceType) break;
75
+ s = sourceType;
76
+ continue;
77
+ }
78
+ if (s instanceof z.ZodOptional) {
79
+ optional = true;
80
+ const def = getDef(s);
81
+ s = def && def.innerType || s;
82
+ continue;
83
+ }
84
+ if (s instanceof z.ZodNullable) {
85
+ nullable = true;
86
+ const def = getDef(s);
87
+ s = def && def.innerType || s;
88
+ continue;
89
+ }
90
+ if (s instanceof z.ZodDefault) {
91
+ const def = getDef(s);
92
+ s = def && def.innerType || s;
93
+ continue;
94
+ }
95
+ break;
96
+ }
97
+ return { base: s, optional, nullable };
98
+ }
99
+ function introspectSchema(schema) {
100
+ if (!schema) return void 0;
101
+ const { base, optional, nullable } = unwrap(schema);
102
+ const def = getDef(base);
103
+ const node = {
104
+ kind: inferKind(base),
105
+ optional: optional || void 0,
106
+ nullable: nullable || void 0,
107
+ description: getDescription(base)
108
+ };
109
+ if (base instanceof z.ZodObject) {
110
+ const rawShape = base.shape ?? (def && typeof def.shape === "function" ? def.shape() : def?.shape);
111
+ const shape = typeof rawShape === "function" ? rawShape() : rawShape ?? {};
112
+ const props = {};
113
+ for (const key of Object.keys(shape)) {
114
+ const child = shape[key];
115
+ const childNode = introspectSchema(child);
116
+ if (childNode) props[key] = childNode;
117
+ }
118
+ node.properties = props;
119
+ }
120
+ if (base instanceof z.ZodArray) {
121
+ const inner = def && def.element || def && def.type || void 0;
122
+ if (inner) {
123
+ node.element = introspectSchema(inner);
124
+ }
125
+ }
126
+ if (base instanceof z.ZodUnion) {
127
+ const options = def && def.options || [];
128
+ node.union = options.map((opt) => introspectSchema(opt)).filter(Boolean);
129
+ }
130
+ if (base instanceof z.ZodLiteral) {
131
+ if (def) {
132
+ if (Array.isArray(def.values)) {
133
+ node.literal = def.values.length === 1 ? def.values[0] : def.values.slice();
134
+ } else {
135
+ node.literal = def.value;
136
+ }
137
+ }
138
+ }
139
+ if (base instanceof z.ZodEnum) {
140
+ if (def) {
141
+ if (Array.isArray(def.values)) {
142
+ node.enumValues = def.values.slice();
143
+ } else if (def.entries && typeof def.entries === "object") {
144
+ node.enumValues = Object.values(def.entries).map(
145
+ (v) => String(v)
146
+ );
147
+ }
148
+ }
149
+ }
150
+ return node;
151
+ }
152
+ function inferKind(schema) {
153
+ if (schema instanceof z.ZodString) return "string";
154
+ if (schema instanceof z.ZodNumber) return "number";
155
+ if (schema instanceof z.ZodBoolean) return "boolean";
156
+ if (schema instanceof z.ZodBigInt) return "bigint";
157
+ if (schema instanceof z.ZodDate) return "date";
158
+ if (schema instanceof z.ZodArray) return "array";
159
+ if (schema instanceof z.ZodObject) return "object";
160
+ if (schema instanceof z.ZodUnion) return "union";
161
+ if (schema instanceof z.ZodLiteral) return "literal";
162
+ if (schema instanceof z.ZodEnum) return "enum";
163
+ if (schema instanceof z.ZodRecord) return "record";
164
+ if (schema instanceof z.ZodTuple) return "tuple";
165
+ if (schema instanceof z.ZodUnknown) return "unknown";
166
+ if (schema instanceof z.ZodAny) return "any";
167
+ return "unknown";
168
+ }
169
+
170
+ // src/docs/serializer.ts
171
+ function serializeLeaf(leaf) {
172
+ const cfg = leaf.cfg;
173
+ const tags = Array.isArray(cfg.tags) ? cfg.tags.slice() : [];
174
+ return {
175
+ method: leaf.method,
176
+ // 'get' | 'post' | ...
177
+ path: leaf.path,
178
+ cfg: {
179
+ description: cfg.description,
180
+ summary: cfg.summary,
181
+ docsGroup: cfg.docsGroup,
182
+ tags,
183
+ deprecated: cfg.deprecated,
184
+ stability: cfg.stability,
185
+ feed: !!cfg.feed,
186
+ docsMeta: cfg.docsMeta,
187
+ hasBody: !!cfg.bodySchema || !!cfg.bodyFiles?.length,
188
+ hasQuery: !!cfg.querySchema,
189
+ hasParams: !!cfg.paramsSchema,
190
+ hasOutput: !!cfg.outputSchema,
191
+ bodySchema: introspectSchema(cfg.bodySchema),
192
+ querySchema: introspectSchema(cfg.querySchema),
193
+ paramsSchema: introspectSchema(cfg.paramsSchema),
194
+ outputSchema: introspectSchema(cfg.outputSchema)
195
+ }
196
+ };
197
+ }
198
+
199
+ // src/docs/LeafDocsPage.tsx
200
+ var import_jsx_runtime = require("react/jsx-runtime");
201
+ var DEFAULT_ASSET_BASE = "/__rrroutes/docs/assets";
202
+ function normalizeBase(base) {
203
+ if (!base) return DEFAULT_ASSET_BASE;
204
+ return base.endsWith("/") ? base.slice(0, -1) : base;
205
+ }
206
+ var DocsDocument = ({ leavesJson, assetBase, cspNonce }) => {
207
+ const cssHref = `${assetBase}/docs.css`;
208
+ const jsSrc = `${assetBase}/docs.js`;
209
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("html", { lang: "en", children: [
210
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("head", { children: [
211
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("meta", { charSet: "UTF-8" }),
212
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("meta", { name: "viewport", content: "width=device-width, initial-scale=1.0" }),
213
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("title", { children: "API Reference" }),
214
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("link", { rel: "stylesheet", href: cssHref })
215
+ ] }),
216
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("body", { children: [
217
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { id: "docs-root" }),
218
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
219
+ "script",
220
+ {
221
+ id: "leaf-data",
222
+ type: "application/json",
223
+ nonce: cspNonce,
224
+ dangerouslySetInnerHTML: { __html: leavesJson }
225
+ }
226
+ ),
227
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("script", { type: "module", src: jsSrc, nonce: cspNonce })
228
+ ] })
229
+ ] });
230
+ };
231
+ function serializeLeaves(leaves) {
232
+ return JSON.stringify(leaves.map(serializeLeaf)).replace(/<\//g, "<\\/");
233
+ }
234
+ function createLeafDocsDocument(leaves, options = {}) {
235
+ const assetBase = normalizeBase(options.assetBasePath ?? DEFAULT_ASSET_BASE);
236
+ const leavesJson = serializeLeaves(leaves);
237
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DocsDocument, { leavesJson, assetBase, cspNonce: options.cspNonce });
238
+ }
239
+ function renderLeafDocsHTML(leaves, options = {}) {
240
+ const doc = createLeafDocsDocument(leaves, options);
241
+ const html = (0, import_server.renderToStaticMarkup)(doc);
242
+ return `<!DOCTYPE html>${html}`;
243
+ }
244
+
245
+ // src/docs/docs.ts
246
+ function renderLeafDocsHTML2(leaves, options = {}) {
247
+ return renderLeafDocsHTML(leaves, options);
248
+ }
249
+
250
+ // src/index.ts
251
+ var import_meta = {};
252
+ var trimTrailingSlash = (value) => value.endsWith("/") && value.length > 1 ? value.slice(0, -1) : value;
253
+ function mountRRRoutesDocs({
254
+ router,
255
+ leaves,
256
+ options = {}
257
+ }) {
258
+ const docsPath = options.path ?? "/__rrroutes/docs";
259
+ const normalizedDocsPath = trimTrailingSlash(docsPath);
260
+ const assetsMountPath = trimTrailingSlash(
261
+ options.assetBasePath ?? `${normalizedDocsPath}/assets`
262
+ );
263
+ const publicDir = (0, import_node_url.fileURLToPath)(new URL("../public", import_meta.url));
264
+ const assetsDir = import_node_path.default.join(publicDir, "assets");
265
+ const cspEnabled = options.csp !== false;
266
+ router.use(assetsMountPath, (0, import_express.static)(assetsDir, { immutable: true, maxAge: "365d" }));
267
+ router.get(docsPath, (req, res) => {
268
+ const preparedLeaves = Array.isArray(leaves) ? leaves.filter((leaf) => leaf.cfg.docsHidden !== true) : [];
269
+ const onRequestResult = options.onRequest?.({ req, res, leaves: preparedLeaves }) ?? {};
270
+ const finalLeaves = onRequestResult.leaves ?? preparedLeaves;
271
+ const hasCustomHtml = typeof onRequestResult.html === "string";
272
+ let nonce = onRequestResult.nonce;
273
+ if (!nonce && cspEnabled && !hasCustomHtml) {
274
+ nonce = (0, import_crypto.randomBytes)(16).toString("base64");
275
+ }
276
+ const html = hasCustomHtml ? onRequestResult.html : renderLeafDocsHTML2(finalLeaves, {
277
+ cspNonce: nonce,
278
+ assetBasePath: assetsMountPath
279
+ });
280
+ if (cspEnabled && nonce) {
281
+ res.setHeader(
282
+ "Content-Security-Policy",
283
+ [
284
+ "default-src 'self'",
285
+ `script-src 'self' 'nonce-${nonce}'`,
286
+ `style-src 'self' 'nonce-${nonce}'`,
287
+ "img-src 'self' data:",
288
+ "connect-src 'self'",
289
+ "font-src 'self'",
290
+ "frame-ancestors 'self'"
291
+ ].join("; ")
292
+ );
293
+ }
294
+ res.send(html);
295
+ });
296
+ return { path: docsPath };
297
+ }
298
+ // Annotate the CommonJS export names for ESM import in node:
299
+ 0 && (module.exports = {
300
+ mountRRRoutesDocs,
301
+ renderLeafDocsHTML,
302
+ serializeLeaf
303
+ });
304
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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"]}
@@ -0,0 +1,36 @@
1
+ import type { Request, Response, Router } from 'express';
2
+ import type { AnyLeaf } from '@emeryld/rrroutes-contract';
3
+ export type DocsRequestContext = {
4
+ req: Request;
5
+ res: Response;
6
+ leaves: AnyLeaf[];
7
+ };
8
+ export type DocsOnRequestResult = {
9
+ leaves?: AnyLeaf[];
10
+ nonce?: string;
11
+ html?: string;
12
+ };
13
+ export type OpenApiDocsOptions = {
14
+ /** Path where docs are mounted. Defaults to `/__rrroutes/docs`. */
15
+ path?: string;
16
+ /** Whether to emit a CSP header + nonce. Defaults to true. */
17
+ csp?: boolean;
18
+ /** Override where static assets are served from. Defaults to `${path}/assets`. */
19
+ assetBasePath?: string;
20
+ /**
21
+ * Hook that runs on every request. Use it to adjust leaves, override nonce, or
22
+ * provide a fully custom HTML response.
23
+ */
24
+ onRequest?: (ctx: DocsRequestContext) => DocsOnRequestResult | void;
25
+ };
26
+ export type MountDocsArgs = {
27
+ router: Router;
28
+ leaves: AnyLeaf[];
29
+ options?: OpenApiDocsOptions;
30
+ };
31
+ export declare function mountRRRoutesDocs({ router, leaves, options, }: MountDocsArgs): {
32
+ path: string;
33
+ };
34
+ export { renderLeafDocsHTML } from './docs/docs.js';
35
+ export { serializeLeaf } from './docs/serializer.js';
36
+ export type { SerializableLeaf } from './docs/serializer.js';