@emeryld/rrroutes-openapi 2.2.21 → 2.2.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/LeafDocsPage.d.ts +25 -1
- package/dist/docs/docs.d.ts +2 -1
- package/dist/docs/presets.d.ts +14 -0
- package/dist/index.cjs +90 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +31 -3
- package/dist/index.mjs +90 -6
- package/dist/index.mjs.map +1 -1
- package/dist/public/assets/docs.css +1 -1
- package/dist/public/assets/docs.js +14 -12
- package/dist/web/app.d.ts +4 -1
- package/dist/web/components/EndpointCard.d.ts +2 -1
- package/dist/web/components/HistoryView.d.ts +6 -0
- package/dist/web/components/PlaygroundOverlay.d.ts +29 -1
- package/dist/web/components/PresetsView.d.ts +9 -0
- package/dist/web/historyStore.d.ts +63 -0
- package/package.json +1 -1
|
@@ -1,17 +1,41 @@
|
|
|
1
1
|
import type { AnyLeaf } from '@emeryld/rrroutes-contract';
|
|
2
2
|
import type { ReactElement } from 'react';
|
|
3
|
+
import type { SerializablePreset } from './presets.js';
|
|
3
4
|
export interface RenderOptions {
|
|
4
5
|
/** CSP nonce applied to data + script tags. */
|
|
5
6
|
cspNonce?: string;
|
|
6
7
|
/** Base URL where static assets are served (e.g. `/__rrroutes/docs/assets`). */
|
|
7
8
|
assetBasePath?: string;
|
|
9
|
+
/** Root path where the docs are mounted (e.g. `/__rrroutes/docs`). Used for client routing. */
|
|
10
|
+
docsBasePath?: string;
|
|
11
|
+
/** Preset collections rendered into the docs UI. */
|
|
12
|
+
presets?: SerializablePreset[];
|
|
13
|
+
/** Optional seed history entries to pre-populate the UI (useful in dev). */
|
|
14
|
+
historySeeds?: SerializableHistoryEntry[];
|
|
8
15
|
}
|
|
9
16
|
type DocsDocumentProps = {
|
|
10
17
|
leavesJson: string;
|
|
18
|
+
presetsJson: string;
|
|
11
19
|
assetBase: string;
|
|
20
|
+
docsBase: string;
|
|
21
|
+
historyJson: string;
|
|
12
22
|
cspNonce?: string;
|
|
13
23
|
};
|
|
14
|
-
export declare const DocsDocument: ({ leavesJson, assetBase, cspNonce }: DocsDocumentProps) => import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
export declare const DocsDocument: ({ leavesJson, presetsJson, assetBase, docsBase, historyJson, cspNonce, }: DocsDocumentProps) => import("react/jsx-runtime").JSX.Element;
|
|
15
25
|
export declare function createLeafDocsDocument(leaves: AnyLeaf[], options?: RenderOptions): ReactElement;
|
|
16
26
|
export declare function renderLeafDocsHTML(leaves: AnyLeaf[], options?: RenderOptions): string;
|
|
27
|
+
export type SerializableHistoryEntry = {
|
|
28
|
+
id?: string;
|
|
29
|
+
timestamp: number;
|
|
30
|
+
method: string;
|
|
31
|
+
path: string;
|
|
32
|
+
fullUrl: string;
|
|
33
|
+
params?: Record<string, string>;
|
|
34
|
+
query?: Record<string, string>;
|
|
35
|
+
body?: string;
|
|
36
|
+
output?: string;
|
|
37
|
+
status?: number;
|
|
38
|
+
durationMs: number;
|
|
39
|
+
error?: string;
|
|
40
|
+
};
|
|
17
41
|
export {};
|
package/dist/docs/docs.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AnyLeaf } from "@emeryld/rrroutes-contract";
|
|
2
2
|
import { RenderOptions } from "./LeafDocsPage.js";
|
|
3
3
|
export declare function renderLeafDocsHTML(leaves: AnyLeaf[], options?: RenderOptions): string;
|
|
4
|
-
export type { RenderOptions } from "./LeafDocsPage.js";
|
|
4
|
+
export type { RenderOptions, SerializableHistoryEntry } from "./LeafDocsPage.js";
|
|
5
5
|
export { createLeafDocsDocument } from "./LeafDocsPage.js";
|
|
6
|
+
export type { SerializablePreset, SerializablePresetOperation } from "./presets.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type SerializablePresetOperation = {
|
|
2
|
+
method: string;
|
|
3
|
+
path: string;
|
|
4
|
+
body?: unknown;
|
|
5
|
+
query?: unknown;
|
|
6
|
+
params?: unknown;
|
|
7
|
+
};
|
|
8
|
+
export type SerializablePreset = {
|
|
9
|
+
name: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
tags: string[];
|
|
12
|
+
docsGroup?: string;
|
|
13
|
+
ops: SerializablePresetOperation[];
|
|
14
|
+
};
|
package/dist/index.cjs
CHANGED
|
@@ -205,9 +205,22 @@ function normalizeBase(base) {
|
|
|
205
205
|
if (!base) return DEFAULT_ASSET_BASE;
|
|
206
206
|
return base.endsWith("/") ? base.slice(0, -1) : base;
|
|
207
207
|
}
|
|
208
|
-
|
|
208
|
+
function normalizeDocsBase(base) {
|
|
209
|
+
if (!base) return "";
|
|
210
|
+
if (base === "/") return "/";
|
|
211
|
+
return base.endsWith("/") && base.length > 1 ? base.slice(0, -1) : base;
|
|
212
|
+
}
|
|
213
|
+
var DocsDocument = ({
|
|
214
|
+
leavesJson,
|
|
215
|
+
presetsJson,
|
|
216
|
+
assetBase,
|
|
217
|
+
docsBase,
|
|
218
|
+
historyJson,
|
|
219
|
+
cspNonce
|
|
220
|
+
}) => {
|
|
209
221
|
const cssHref = `${assetBase}/docs.css`;
|
|
210
222
|
const jsSrc = `${assetBase}/docs.js`;
|
|
223
|
+
const configJson = serializeConfig(docsBase);
|
|
211
224
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("html", { lang: "en", children: [
|
|
212
225
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("head", { children: [
|
|
213
226
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("meta", { charSet: "UTF-8" }),
|
|
@@ -226,6 +239,33 @@ var DocsDocument = ({ leavesJson, assetBase, cspNonce }) => {
|
|
|
226
239
|
dangerouslySetInnerHTML: { __html: leavesJson }
|
|
227
240
|
}
|
|
228
241
|
),
|
|
242
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
243
|
+
"script",
|
|
244
|
+
{
|
|
245
|
+
id: "preset-data",
|
|
246
|
+
type: "application/json",
|
|
247
|
+
nonce: cspNonce,
|
|
248
|
+
dangerouslySetInnerHTML: { __html: presetsJson }
|
|
249
|
+
}
|
|
250
|
+
),
|
|
251
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
252
|
+
"script",
|
|
253
|
+
{
|
|
254
|
+
id: "history-data",
|
|
255
|
+
type: "application/json",
|
|
256
|
+
nonce: cspNonce,
|
|
257
|
+
dangerouslySetInnerHTML: { __html: historyJson }
|
|
258
|
+
}
|
|
259
|
+
),
|
|
260
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
261
|
+
"script",
|
|
262
|
+
{
|
|
263
|
+
id: "docs-config",
|
|
264
|
+
type: "application/json",
|
|
265
|
+
nonce: cspNonce,
|
|
266
|
+
dangerouslySetInnerHTML: { __html: configJson }
|
|
267
|
+
}
|
|
268
|
+
),
|
|
229
269
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("script", { type: "module", src: jsSrc, nonce: cspNonce })
|
|
230
270
|
] })
|
|
231
271
|
] });
|
|
@@ -233,10 +273,32 @@ var DocsDocument = ({ leavesJson, assetBase, cspNonce }) => {
|
|
|
233
273
|
function serializeLeaves(leaves) {
|
|
234
274
|
return JSON.stringify(leaves.map(serializeLeaf)).replace(/<\//g, "<\\/");
|
|
235
275
|
}
|
|
276
|
+
function serializePresets(presets) {
|
|
277
|
+
return JSON.stringify(Array.isArray(presets) ? presets : []).replace(/<\//g, "<\\/");
|
|
278
|
+
}
|
|
279
|
+
function serializeHistorySeeds(historySeeds) {
|
|
280
|
+
return JSON.stringify(Array.isArray(historySeeds) ? historySeeds : []).replace(/<\//g, "<\\/");
|
|
281
|
+
}
|
|
282
|
+
function serializeConfig(docsBase) {
|
|
283
|
+
return JSON.stringify({ docsBasePath: docsBase }).replace(/<\//g, "<\\/");
|
|
284
|
+
}
|
|
236
285
|
function createLeafDocsDocument(leaves, options = {}) {
|
|
237
286
|
const assetBase = normalizeBase(options.assetBasePath ?? DEFAULT_ASSET_BASE);
|
|
238
287
|
const leavesJson = serializeLeaves(leaves);
|
|
239
|
-
|
|
288
|
+
const presetsJson = serializePresets(options.presets);
|
|
289
|
+
const docsBase = normalizeDocsBase(options.docsBasePath);
|
|
290
|
+
const historyJson = serializeHistorySeeds(options.historySeeds);
|
|
291
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
292
|
+
DocsDocument,
|
|
293
|
+
{
|
|
294
|
+
leavesJson,
|
|
295
|
+
presetsJson,
|
|
296
|
+
assetBase,
|
|
297
|
+
docsBase,
|
|
298
|
+
historyJson,
|
|
299
|
+
cspNonce: options.cspNonce
|
|
300
|
+
}
|
|
301
|
+
);
|
|
240
302
|
}
|
|
241
303
|
function renderLeafDocsHTML(leaves, options = {}) {
|
|
242
304
|
const doc = createLeafDocsDocument(leaves, options);
|
|
@@ -254,6 +316,7 @@ var trimTrailingSlash = (value) => value.endsWith("/") && value.length > 1 ? val
|
|
|
254
316
|
function mountRRRoutesDocs({
|
|
255
317
|
router,
|
|
256
318
|
leaves,
|
|
319
|
+
presets = [],
|
|
257
320
|
options = {}
|
|
258
321
|
}) {
|
|
259
322
|
const prefix = options.prefix ? trimTrailingSlash(options.prefix) : "";
|
|
@@ -265,12 +328,14 @@ function mountRRRoutesDocs({
|
|
|
265
328
|
const publicDir = resolvePublicDir();
|
|
266
329
|
const assetsDir = import_node_path.default.join(publicDir, "assets");
|
|
267
330
|
const cspEnabled = options.csp !== false;
|
|
268
|
-
console.log(`Mounting RRRoutes docs at ${normalizedDocsPath} and assets at ${assetsMountPath}`);
|
|
269
331
|
router.use(assetsMountPath, (0, import_express.static)(assetsDir, { immutable: true, maxAge: "365d" }));
|
|
270
|
-
|
|
332
|
+
const docsRoutePaths = [normalizedDocsPath, `${normalizedDocsPath}/`, `${normalizedDocsPath}/*`];
|
|
333
|
+
router.get(docsRoutePaths, (req, res) => {
|
|
271
334
|
const preparedLeaves = Array.isArray(leaves) ? leaves.filter((leaf) => leaf.cfg.docsHidden !== true) : [];
|
|
272
|
-
const
|
|
335
|
+
const preparedPresets = Array.isArray(presets) ? presets : [];
|
|
336
|
+
const onRequestResult = options.onRequest?.({ req, res, leaves: preparedLeaves, presets: preparedPresets }) ?? {};
|
|
273
337
|
const finalLeaves = onRequestResult.leaves ?? preparedLeaves;
|
|
338
|
+
const finalPresets = onRequestResult.presets ?? preparedPresets;
|
|
274
339
|
const hasCustomHtml = typeof onRequestResult.html === "string";
|
|
275
340
|
let nonce = onRequestResult.nonce;
|
|
276
341
|
if (!nonce && cspEnabled && !hasCustomHtml) {
|
|
@@ -278,7 +343,10 @@ function mountRRRoutesDocs({
|
|
|
278
343
|
}
|
|
279
344
|
const html = hasCustomHtml ? onRequestResult.html : renderLeafDocsHTML2(finalLeaves, {
|
|
280
345
|
cspNonce: nonce,
|
|
281
|
-
assetBasePath: `${prefix}assetsMountPath
|
|
346
|
+
assetBasePath: `${prefix}${assetsMountPath}`,
|
|
347
|
+
docsBasePath: `${prefix}${normalizedDocsPath}`,
|
|
348
|
+
historySeeds: options.historySeeds,
|
|
349
|
+
presets: normalizePresets(finalPresets)
|
|
282
350
|
});
|
|
283
351
|
if (cspEnabled && nonce) {
|
|
284
352
|
res.setHeader(
|
|
@@ -306,6 +374,22 @@ function resolvePublicDir() {
|
|
|
306
374
|
if (import_node_fs.default.existsSync(fallback)) return fallback;
|
|
307
375
|
return fromModule;
|
|
308
376
|
}
|
|
377
|
+
function normalizePresets(presets) {
|
|
378
|
+
if (!Array.isArray(presets)) return [];
|
|
379
|
+
return presets.map((preset) => ({
|
|
380
|
+
name: preset.name,
|
|
381
|
+
description: preset.description,
|
|
382
|
+
tags: Array.isArray(preset.tags) ? preset.tags.slice() : [],
|
|
383
|
+
docsGroup: preset.docsGroup,
|
|
384
|
+
ops: Array.isArray(preset.ops) ? preset.ops.map((op) => ({
|
|
385
|
+
method: typeof op.method === "string" ? op.method.toUpperCase() : "",
|
|
386
|
+
path: typeof op.path === "string" ? op.path : "",
|
|
387
|
+
body: op.body,
|
|
388
|
+
query: op.query,
|
|
389
|
+
params: op.params
|
|
390
|
+
})) : []
|
|
391
|
+
}));
|
|
392
|
+
}
|
|
309
393
|
// Annotate the CommonJS export names for ESM import in node:
|
|
310
394
|
0 && (module.exports = {
|
|
311
395
|
mountRRRoutesDocs,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/docs/LeafDocsPage.tsx","../src/docs/schemaIntrospection.ts","../src/docs/serializer.ts","../src/docs/docs.ts"],"sourcesContent":["import type { AnyLeaf } from '@emeryld/rrroutes-contract';\nimport { randomBytes } from 'crypto';\nimport type { Request, Response, Router } from 'express';\nimport { static as expressStatic } from 'express';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { renderLeafDocsHTML } from './docs/docs.js';\n\nexport type DocsRequestContext = {\n req: Request;\n res: Response;\n leaves: AnyLeaf[];\n};\n\nexport type DocsOnRequestResult = {\n leaves?: AnyLeaf[];\n nonce?: string;\n html?: string;\n};\n\nexport type OpenApiDocsOptions = {\n /** Path where docs are mounted. Defaults to `/__rrroutes/docs`. */\n path?: string;\n prefix?: string;\n /** Whether to emit a CSP header + nonce. Defaults to true. */\n csp?: boolean;\n /** Override where static assets are served from. Defaults to `${path}/assets`. */\n assetBasePath?: string;\n /**\n * Hook that runs on every request. Use it to adjust leaves, override nonce, or\n * provide a fully custom HTML response.\n */\n onRequest?: (ctx: DocsRequestContext) => DocsOnRequestResult | void;\n};\n\nexport type MountDocsArgs = {\n router: Router;\n leaves: AnyLeaf[];\n options?: OpenApiDocsOptions;\n};\n\nconst trimTrailingSlash = (value: string) =>\n value.endsWith('/') && value.length > 1 ? value.slice(0, -1) : value;\n\nexport function mountRRRoutesDocs({\n router,\n leaves,\n options = {},\n}: MountDocsArgs) {\n const prefix = options.prefix ? trimTrailingSlash(options.prefix) : '';\n const docsPath = options.path ?? '/__rrroutes/docs';\n const normalizedDocsPath = trimTrailingSlash(docsPath);\n const assetsMountPath = trimTrailingSlash(\n options.assetBasePath ?? `${normalizedDocsPath}/assets`,\n );\n const publicDir = resolvePublicDir();\n const assetsDir = path.join(publicDir, 'assets');\n const cspEnabled = options.csp !== false;\n\n console.log(`Mounting RRRoutes docs at ${normalizedDocsPath} and assets at ${assetsMountPath}`);\n\n router.use(assetsMountPath, expressStatic(assetsDir, { immutable: true, maxAge: '365d' }));\n\n router.get(normalizedDocsPath, (req, res) => {\n const preparedLeaves = Array.isArray(leaves)\n ? leaves.filter((leaf) => leaf.cfg.docsHidden !== true)\n : [];\n const onRequestResult = options.onRequest?.({ req, res, leaves: preparedLeaves }) ?? {};\n const finalLeaves = onRequestResult.leaves ?? preparedLeaves;\n\n const hasCustomHtml = typeof onRequestResult.html === 'string';\n\n let nonce = onRequestResult.nonce;\n if (!nonce && cspEnabled && !hasCustomHtml) {\n nonce = randomBytes(16).toString('base64');\n }\n\n const html = hasCustomHtml\n ? (onRequestResult.html as string)\n : renderLeafDocsHTML(finalLeaves, {\n cspNonce: nonce,\n assetBasePath: `${prefix}assetsMountPath`,\n });\n\n if (cspEnabled && nonce) {\n res.setHeader(\n 'Content-Security-Policy',\n [\n \"default-src 'self'\",\n `script-src 'self' 'nonce-${nonce}'`,\n `style-src 'self' 'nonce-${nonce}'`,\n \"img-src 'self' data:\",\n \"connect-src 'self'\",\n \"font-src 'self'\",\n \"frame-ancestors 'self'\",\n ].join('; '),\n );\n }\n\n res.send(html);\n });\n\n return { path: docsPath };\n}\n\nfunction resolvePublicDir() {\n const moduleDir =\n typeof __dirname !== 'undefined'\n ? __dirname\n : path.dirname(fileURLToPath(import.meta.url));\n const fromModule = path.resolve(moduleDir, '../public');\n if (fs.existsSync(fromModule)) return fromModule;\n\n // When running from source (ts-node), fall back to the built output path.\n const fallback = path.resolve(moduleDir, '../dist/public');\n if (fs.existsSync(fallback)) return fallback;\n\n return fromModule; // fallback; express static will 404 if missing\n}\n\nexport { renderLeafDocsHTML } from './docs/docs.js';\nexport { serializeLeaf } from './docs/serializer.js';\nexport type { SerializableLeaf } from './docs/serializer.js';\n","import type { AnyLeaf } from '@emeryld/rrroutes-contract';\nimport type { ReactElement } from 'react';\nimport { renderToStaticMarkup } from 'react-dom/server';\nimport { serializeLeaf } from './serializer.js';\n\nexport interface RenderOptions {\n /** CSP nonce applied to data + script tags. */\n cspNonce?: string;\n /** Base URL where static assets are served (e.g. `/__rrroutes/docs/assets`). */\n assetBasePath?: string;\n}\n\nconst DEFAULT_ASSET_BASE = '/__rrroutes/docs/assets';\n\nfunction normalizeBase(base: string) {\n if (!base) return DEFAULT_ASSET_BASE;\n return base.endsWith('/') ? base.slice(0, -1) : base;\n}\n\ntype DocsDocumentProps = {\n leavesJson: string;\n assetBase: string;\n cspNonce?: string;\n};\n\nexport const DocsDocument = ({ leavesJson, assetBase, cspNonce }: DocsDocumentProps) => {\n const cssHref = `${assetBase}/docs.css`;\n const jsSrc = `${assetBase}/docs.js`;\n\n return (\n <html lang=\"en\">\n <head>\n <meta charSet=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>API Reference</title>\n <link rel=\"stylesheet\" href={cssHref} />\n </head>\n <body>\n <div id=\"docs-root\"></div>\n <script\n id=\"leaf-data\"\n type=\"application/json\"\n nonce={cspNonce}\n dangerouslySetInnerHTML={{ __html: leavesJson }}\n />\n <script type=\"module\" src={jsSrc} nonce={cspNonce} />\n </body>\n </html>\n );\n};\n\nfunction serializeLeaves(leaves: AnyLeaf[]) {\n return JSON.stringify(leaves.map(serializeLeaf)).replace(/<\\//g, '<\\\\/');\n}\n\nexport function createLeafDocsDocument(\n leaves: AnyLeaf[],\n options: RenderOptions = {},\n): ReactElement {\n const assetBase = normalizeBase(options.assetBasePath ?? DEFAULT_ASSET_BASE);\n const leavesJson = serializeLeaves(leaves);\n\n return <DocsDocument leavesJson={leavesJson} assetBase={assetBase} cspNonce={options.cspNonce} />;\n}\n\nexport function renderLeafDocsHTML(leaves: AnyLeaf[], options: RenderOptions = {}): string {\n const doc = createLeafDocsDocument(leaves, options);\n const html = renderToStaticMarkup(doc);\n return `<!DOCTYPE html>${html}`;\n}\n","// schemaIntrospection.ts\nimport * as z from \"zod\";\n\nexport type SerializableSchemaNode = {\n kind: string; // \"object\" | \"string\" | \"number\" | ...\n optional?: boolean;\n nullable?: boolean;\n description?: string;\n\n // object\n properties?: Record<string, SerializableSchemaNode>;\n // array\n element?: SerializableSchemaNode;\n // union\n union?: SerializableSchemaNode[];\n // literal\n literal?: unknown;\n // enum\n enumValues?: string[];\n};\n\ntype ZodAny = z.ZodTypeAny;\n\n/**\n * Zod 3 uses `schema._def`, Zod 4 uses `schema._zod.def`.\n */\nfunction getDef(schema: unknown): any | undefined {\n if (!schema || typeof schema !== \"object\") return undefined;\n const anySchema = schema as any;\n return anySchema._zod?.def ?? anySchema._def;\n}\n\n/**\n * Try to get a human-readable description.\n * Zod 4: use metadata/registry; fallback to internal def.description.\n * Zod 3: only internal def.description exists.\n */\nfunction getDescription(schema: ZodAny): string | undefined {\n const anyZ: any = z as any;\n\n // Zod 4 global registry metadata, if present\n const registry = anyZ.globalRegistry?.get\n ? anyZ.globalRegistry.get(schema)\n : undefined;\n if (registry && typeof registry.description === \"string\") {\n return registry.description;\n }\n\n // Legacy / internal description\n const def = getDef(schema);\n if (def && typeof def.description === \"string\") {\n return def.description;\n }\n\n return undefined;\n}\n\n/**\n * Peel off wrappers (effects, optional, nullable, default) and\n * return the inner schema + flags.\n *\n * Supports:\n * - Zod 3: ZodEffects, ZodOptional, ZodNullable, ZodDefault\n * - Zod 4: ZodOptional, ZodNullable, ZodDefault\n */\nfunction unwrap(schema: ZodAny): {\n base: ZodAny;\n optional: boolean;\n nullable: boolean;\n} {\n let s: ZodAny = schema;\n let optional = false;\n let nullable = false;\n\n // Zod 3 only (undefined in Zod 4)\n const ZodEffectsCtor: any = (z as any).ZodEffects;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n // Zod 3: ZodEffects wrapper\n if (ZodEffectsCtor && s instanceof ZodEffectsCtor) {\n const def = getDef(s) || {};\n const sourceType =\n typeof (s as any).sourceType === \"function\"\n ? (s as any).sourceType()\n : def.schema;\n if (!sourceType) break;\n s = sourceType;\n continue;\n }\n\n // Zod 3 + 4: optional/nullable/default wrappers\n if (s instanceof z.ZodOptional) {\n optional = true;\n const def = getDef(s);\n s = (def && def.innerType) || s; // innerType exists in both 3 & 4\n continue;\n }\n\n if (s instanceof z.ZodNullable) {\n nullable = true;\n const def = getDef(s);\n s = (def && def.innerType) || s;\n continue;\n }\n\n if (s instanceof z.ZodDefault) {\n const def = getDef(s);\n s = (def && def.innerType) || s;\n continue;\n }\n\n break;\n }\n\n return { base: s, optional, nullable };\n}\n\nexport function introspectSchema(\n schema: ZodAny | undefined\n): SerializableSchemaNode | undefined {\n if (!schema) return undefined;\n\n const { base, optional, nullable } = unwrap(schema);\n const def = getDef(base);\n\n const node: SerializableSchemaNode = {\n kind: inferKind(base),\n optional: optional || undefined,\n nullable: nullable || undefined,\n description: getDescription(base),\n };\n\n // OBJECT\n if (base instanceof z.ZodObject) {\n // Zod 3: _def.shape() (function)\n // Zod 4: .shape getter returns an object\n const rawShape: any =\n (base as any).shape ?? (def && typeof def.shape === \"function\"\n ? def.shape()\n : def?.shape);\n\n const shape =\n typeof rawShape === \"function\" ? rawShape() : rawShape ?? {};\n\n const props: Record<string, SerializableSchemaNode> = {};\n for (const key of Object.keys(shape)) {\n const child = shape[key] as ZodAny;\n const childNode = introspectSchema(child);\n if (childNode) props[key] = childNode;\n }\n node.properties = props;\n }\n\n // ARRAY\n if (base instanceof z.ZodArray) {\n // Zod 3: def.type is inner schema\n // Zod 4: def.element is inner schema\n const inner =\n (def && (def.element as ZodAny)) ||\n (def && (def.type as ZodAny)) ||\n undefined;\n if (inner) {\n node.element = introspectSchema(inner);\n }\n }\n\n // UNION\n if (base instanceof z.ZodUnion) {\n const options: ZodAny[] = (def && def.options) || [];\n node.union = options\n .map((opt) => introspectSchema(opt))\n .filter(Boolean) as SerializableSchemaNode[];\n }\n\n // LITERAL\n if (base instanceof z.ZodLiteral) {\n if (def) {\n // Zod 4: def.values (multi-literal)\n if (Array.isArray(def.values)) {\n node.literal =\n def.values.length === 1 ? def.values[0] : def.values.slice();\n } else {\n // Zod 3: def.value\n node.literal = def.value;\n }\n }\n }\n\n // ENUM\n if (base instanceof z.ZodEnum) {\n if (def) {\n if (Array.isArray(def.values)) {\n // Zod 3\n node.enumValues = def.values.slice();\n } else if (def.entries && typeof def.entries === \"object\") {\n // Zod 4: entries is a { key: value } map\n node.enumValues = Object.values(def.entries).map((v: unknown) =>\n String(v)\n );\n }\n }\n }\n\n return node;\n}\n\nfunction inferKind(schema: ZodAny): string {\n // This path still uses instanceof; it works with Zod 4 Classic\n // (importing from \"zod\"). Anything unknown falls back to \"unknown\".\n if (schema instanceof z.ZodString) return \"string\";\n if (schema instanceof z.ZodNumber) return \"number\";\n if (schema instanceof z.ZodBoolean) return \"boolean\";\n if (schema instanceof z.ZodBigInt) return \"bigint\";\n if (schema instanceof z.ZodDate) return \"date\";\n if (schema instanceof z.ZodArray) return \"array\";\n if (schema instanceof z.ZodObject) return \"object\";\n if (schema instanceof z.ZodUnion) return \"union\";\n if (schema instanceof z.ZodLiteral) return \"literal\";\n if (schema instanceof z.ZodEnum) return \"enum\";\n if (schema instanceof z.ZodRecord) return \"record\";\n if (schema instanceof z.ZodTuple) return \"tuple\";\n if (schema instanceof z.ZodUnknown) return \"unknown\";\n if (schema instanceof z.ZodAny) return \"any\";\n\n return \"unknown\";\n}\n","// serializer.ts\nimport type { AnyLeaf, MethodCfg } from \"@emeryld/rrroutes-contract\";\nimport { introspectSchema, SerializableSchemaNode } from \"./schemaIntrospection.js\";\n\ntype SerializableMethodCfg = Pick<\n MethodCfg,\n | \"description\"\n | \"summary\"\n | \"docsGroup\"\n | \"tags\"\n | \"deprecated\"\n | \"stability\"\n | \"feed\"\n | \"docsMeta\"\n> & {\n hasBody: boolean;\n hasQuery: boolean;\n hasParams: boolean;\n hasOutput: boolean;\n\n // NEW: full Zod ASTs\n bodySchema?: SerializableSchemaNode;\n querySchema?: SerializableSchemaNode;\n paramsSchema?: SerializableSchemaNode;\n outputSchema?: SerializableSchemaNode;\n};\n\nexport type SerializableLeaf = {\n method: string;\n path: string;\n cfg: SerializableMethodCfg;\n};\n\nexport type { SerializableSchemaNode } from \"./schemaIntrospection.js\";\n\nexport function serializeLeaf(leaf: AnyLeaf): SerializableLeaf {\n const cfg = leaf.cfg;\n\n const tags = Array.isArray(cfg.tags) ? cfg.tags.slice() : [];\n\n return {\n method: leaf.method, // 'get' | 'post' | ...\n path: leaf.path,\n cfg: {\n description: cfg.description,\n summary: cfg.summary,\n docsGroup: cfg.docsGroup,\n tags,\n deprecated: cfg.deprecated,\n stability: cfg.stability,\n feed: !!cfg.feed,\n docsMeta: cfg.docsMeta,\n hasBody: !!cfg.bodySchema || !!cfg.bodyFiles?.length,\n hasQuery: !!cfg.querySchema,\n hasParams: !!cfg.paramsSchema,\n hasOutput: !!cfg.outputSchema,\n\n bodySchema: introspectSchema(cfg.bodySchema),\n querySchema: introspectSchema(cfg.querySchema),\n paramsSchema: introspectSchema(cfg.paramsSchema),\n outputSchema: introspectSchema(cfg.outputSchema),\n },\n };\n}\n","// renderLeafDocsHTML.ts\nimport type { AnyLeaf } from \"@emeryld/rrroutes-contract\";\nimport {\n createLeafDocsDocument,\n renderLeafDocsHTML as LeafDocsPage,\n RenderOptions,\n} from \"./LeafDocsPage.js\";\n\nexport function renderLeafDocsHTML(leaves: AnyLeaf[], options: RenderOptions = {}): string {\n return LeafDocsPage(leaves, options);\n}\n\nexport type { RenderOptions } from \"./LeafDocsPage.js\";\nexport { createLeafDocsDocument } from \"./LeafDocsPage.js\";\n"],"mappings":"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","names":["renderLeafDocsHTML","renderLeafDocsHTML","path","expressStatic","renderLeafDocsHTML","fs"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/docs/LeafDocsPage.tsx","../src/docs/schemaIntrospection.ts","../src/docs/serializer.ts","../src/docs/docs.ts"],"sourcesContent":["/**\n * dry styles\nfake history logs data for testing\ngraphs for history data\nlogs webhook\nsecurity -> gated access + give environment and if environment='production' -> extra \"Are you sure you want to do this\" pop-up for each non-get action\n */\n\nimport type { AnyLeaf } from '@emeryld/rrroutes-contract';\nimport { randomBytes } from 'crypto';\nimport type { Request, Response, Router } from 'express';\nimport { static as expressStatic } from 'express';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport z from 'zod';\nimport { renderLeafDocsHTML } from './docs/docs.js';\nimport type { SerializableHistoryEntry } from './docs/docs.js';\nimport type { SerializablePreset } from './docs/presets.js';\n\nexport type DocsRequestContext = {\n req: Request;\n res: Response;\n leaves: AnyLeaf[];\n presets: PresetGroup<AnyLeaf>[];\n};\n\nexport type DocsOnRequestResult = {\n leaves?: AnyLeaf[];\n presets?: PresetGroup<AnyLeaf>[];\n nonce?: string;\n html?: string;\n};\n\nexport type OpenApiDocsOptions = {\n /** Path where docs are mounted. Defaults to `/__rrroutes/docs`. */\n path?: string;\n prefix?: string;\n /** Whether to emit a CSP header + nonce. Defaults to true. */\n csp?: boolean;\n /** Override where static assets are served from. Defaults to `${path}/assets`. */\n assetBasePath?: string;\n /** Optional seed history entries that will pre-populate the docs UI history. */\n historySeeds?: SerializableHistoryEntry[];\n /**\n * Hook that runs on every request. Use it to adjust leaves, override nonce, or\n * provide a fully custom HTML response.\n */\n onRequest?: (ctx: DocsRequestContext) => DocsOnRequestResult | void;\n};\n\nexport type Preset<L extends AnyLeaf> = L extends infer A extends AnyLeaf? {\n method: A['method'];\n path: A['path'];\n // pre-set values for schemas. Does not have to be complete.\n body: z.input<A['cfg']['bodySchema']>;\n query: z.input<A['cfg']['querySchema']>;\n params: z.input<A['cfg']['paramsSchema']>;\n}:never;\n\nexport type PresetGroup<L extends AnyLeaf> = {\n name: string;\n description?: string;\n tags: string[];\n docsGroup?: string;\n\n ops: Preset<L>[];\n};\n\nexport type MountDocsArgs<L extends AnyLeaf> = {\n router: Router;\n leaves: L[];\n presets?: PresetGroup<L>[];\n options?: OpenApiDocsOptions;\n};\n\nconst trimTrailingSlash = (value: string) =>\n value.endsWith('/') && value.length > 1 ? value.slice(0, -1) : value;\n\nexport function mountRRRoutesDocs<L extends AnyLeaf>({\n router,\n leaves,\n presets = [],\n options = {},\n}: MountDocsArgs<L>) {\n const prefix = options.prefix ? trimTrailingSlash(options.prefix) : '';\n const docsPath = options.path ?? '/__rrroutes/docs';\n const normalizedDocsPath = trimTrailingSlash(docsPath);\n const assetsMountPath = trimTrailingSlash(\n options.assetBasePath ?? `${normalizedDocsPath}/assets`,\n );\n const publicDir = resolvePublicDir();\n const assetsDir = path.join(publicDir, 'assets');\n const cspEnabled = options.csp !== false;\n\n router.use(assetsMountPath, expressStatic(assetsDir, { immutable: true, maxAge: '365d' }));\n\n const docsRoutePaths = [normalizedDocsPath, `${normalizedDocsPath}/`, `${normalizedDocsPath}/*`];\n\n router.get(docsRoutePaths, (req, res) => {\n const preparedLeaves = Array.isArray(leaves)\n ? leaves.filter((leaf) => leaf.cfg.docsHidden !== true)\n : [];\n const preparedPresets = Array.isArray(presets) ? presets : [];\n const onRequestResult =\n options.onRequest?.({ req, res, leaves: preparedLeaves, presets: preparedPresets }) ?? {};\n const finalLeaves = onRequestResult.leaves ?? preparedLeaves;\n const finalPresets = onRequestResult.presets ?? preparedPresets;\n\n const hasCustomHtml = typeof onRequestResult.html === 'string';\n\n let nonce = onRequestResult.nonce;\n if (!nonce && cspEnabled && !hasCustomHtml) {\n nonce = randomBytes(16).toString('base64');\n }\n\n const html = hasCustomHtml\n ? (onRequestResult.html as string)\n : renderLeafDocsHTML(finalLeaves, {\n cspNonce: nonce,\n assetBasePath: `${prefix}${assetsMountPath}`,\n docsBasePath: `${prefix}${normalizedDocsPath}`,\n historySeeds: options.historySeeds,\n presets: normalizePresets(finalPresets),\n });\n\n if (cspEnabled && nonce) {\n res.setHeader(\n 'Content-Security-Policy',\n [\n \"default-src 'self'\",\n `script-src 'self' 'nonce-${nonce}'`,\n `style-src 'self' 'nonce-${nonce}'`,\n \"img-src 'self' data:\",\n \"connect-src 'self'\",\n \"font-src 'self'\",\n \"frame-ancestors 'self'\",\n ].join('; '),\n );\n }\n\n res.send(html);\n });\n\n return { path: docsPath };\n}\n\nfunction resolvePublicDir() {\n const moduleDir =\n typeof __dirname !== 'undefined'\n ? __dirname\n : path.dirname(fileURLToPath(import.meta.url));\n const fromModule = path.resolve(moduleDir, '../public');\n if (fs.existsSync(fromModule)) return fromModule;\n\n // When running from source (ts-node), fall back to the built output path.\n const fallback = path.resolve(moduleDir, '../dist/public');\n if (fs.existsSync(fallback)) return fallback;\n\n return fromModule; // fallback; express static will 404 if missing\n}\n\nfunction normalizePresets(presets: PresetGroup<AnyLeaf>[]): SerializablePreset[] {\n if (!Array.isArray(presets)) return [];\n return presets.map((preset) => ({\n name: preset.name,\n description: preset.description,\n tags: Array.isArray(preset.tags) ? preset.tags.slice() : [],\n docsGroup: preset.docsGroup,\n ops: Array.isArray(preset.ops)\n ? preset.ops.map((op) => ({\n method: typeof op.method === 'string' ? op.method.toUpperCase() : '',\n path: typeof op.path === 'string' ? op.path : '',\n body: op.body,\n query: op.query,\n params: op.params,\n }))\n : [],\n }));\n}\n\nexport { renderLeafDocsHTML } from './docs/docs.js';\nexport { serializeLeaf } from './docs/serializer.js';\nexport type { SerializableLeaf } from './docs/serializer.js';\n","import type { AnyLeaf } from '@emeryld/rrroutes-contract';\nimport type { ReactElement } from 'react';\nimport { renderToStaticMarkup } from 'react-dom/server';\nimport type { SerializablePreset } from './presets.js';\nimport { serializeLeaf } from './serializer.js';\n\nexport interface RenderOptions {\n /** CSP nonce applied to data + script tags. */\n cspNonce?: string;\n /** Base URL where static assets are served (e.g. `/__rrroutes/docs/assets`). */\n assetBasePath?: string;\n /** Root path where the docs are mounted (e.g. `/__rrroutes/docs`). Used for client routing. */\n docsBasePath?: string;\n /** Preset collections rendered into the docs UI. */\n presets?: SerializablePreset[];\n /** Optional seed history entries to pre-populate the UI (useful in dev). */\n historySeeds?: SerializableHistoryEntry[];\n}\n\nconst DEFAULT_ASSET_BASE = '/__rrroutes/docs/assets';\n\nfunction normalizeBase(base: string) {\n if (!base) return DEFAULT_ASSET_BASE;\n return base.endsWith('/') ? base.slice(0, -1) : base;\n}\n\nfunction normalizeDocsBase(base: string | undefined) {\n if (!base) return '';\n if (base === '/') return '/';\n return base.endsWith('/') && base.length > 1 ? base.slice(0, -1) : base;\n}\n\ntype DocsDocumentProps = {\n leavesJson: string;\n presetsJson: string;\n assetBase: string;\n docsBase: string;\n historyJson: string;\n cspNonce?: string;\n};\n\nexport const DocsDocument = ({\n leavesJson,\n presetsJson,\n assetBase,\n docsBase,\n historyJson,\n cspNonce,\n}: DocsDocumentProps) => {\n const cssHref = `${assetBase}/docs.css`;\n const jsSrc = `${assetBase}/docs.js`;\n const configJson = serializeConfig(docsBase);\n\n return (\n <html lang=\"en\">\n <head>\n <meta charSet=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>API Reference</title>\n <link rel=\"stylesheet\" href={cssHref} />\n </head>\n <body>\n <div id=\"docs-root\"></div>\n <script\n id=\"leaf-data\"\n type=\"application/json\"\n nonce={cspNonce}\n dangerouslySetInnerHTML={{ __html: leavesJson }}\n />\n <script\n id=\"preset-data\"\n type=\"application/json\"\n nonce={cspNonce}\n dangerouslySetInnerHTML={{ __html: presetsJson }}\n />\n <script\n id=\"history-data\"\n type=\"application/json\"\n nonce={cspNonce}\n dangerouslySetInnerHTML={{ __html: historyJson }}\n />\n <script\n id=\"docs-config\"\n type=\"application/json\"\n nonce={cspNonce}\n dangerouslySetInnerHTML={{ __html: configJson }}\n />\n <script type=\"module\" src={jsSrc} nonce={cspNonce} />\n </body>\n </html>\n );\n};\n\nfunction serializeLeaves(leaves: AnyLeaf[]) {\n return JSON.stringify(leaves.map(serializeLeaf)).replace(/<\\//g, '<\\\\/');\n}\n\nfunction serializePresets(presets: SerializablePreset[] | undefined) {\n return JSON.stringify(Array.isArray(presets) ? presets : []).replace(/<\\//g, '<\\\\/');\n}\n\nfunction serializeHistorySeeds(historySeeds: SerializableHistoryEntry[] | undefined) {\n return JSON.stringify(Array.isArray(historySeeds) ? historySeeds : []).replace(/<\\//g, '<\\\\/');\n}\n\nfunction serializeConfig(docsBase: string) {\n return JSON.stringify({ docsBasePath: docsBase }).replace(/<\\//g, '<\\\\/');\n}\n\nexport function createLeafDocsDocument(\n leaves: AnyLeaf[],\n options: RenderOptions = {},\n): ReactElement {\n const assetBase = normalizeBase(options.assetBasePath ?? DEFAULT_ASSET_BASE);\n const leavesJson = serializeLeaves(leaves);\n const presetsJson = serializePresets(options.presets);\n const docsBase = normalizeDocsBase(options.docsBasePath);\n const historyJson = serializeHistorySeeds(options.historySeeds);\n\n return (\n <DocsDocument\n leavesJson={leavesJson}\n presetsJson={presetsJson}\n assetBase={assetBase}\n docsBase={docsBase}\n historyJson={historyJson}\n cspNonce={options.cspNonce}\n />\n );\n}\n\nexport function renderLeafDocsHTML(leaves: AnyLeaf[], options: RenderOptions = {}): string {\n const doc = createLeafDocsDocument(leaves, options);\n const html = renderToStaticMarkup(doc);\n return `<!DOCTYPE html>${html}`;\n}\n\nexport type SerializableHistoryEntry = {\n id?: string;\n timestamp: number;\n method: string;\n path: string;\n fullUrl: string;\n params?: Record<string, string>;\n query?: Record<string, string>;\n body?: string;\n output?: string;\n status?: number;\n durationMs: number;\n error?: string;\n};\n","// schemaIntrospection.ts\nimport * as z from \"zod\";\n\nexport type SerializableSchemaNode = {\n kind: string; // \"object\" | \"string\" | \"number\" | ...\n optional?: boolean;\n nullable?: boolean;\n description?: string;\n\n // object\n properties?: Record<string, SerializableSchemaNode>;\n // array\n element?: SerializableSchemaNode;\n // union\n union?: SerializableSchemaNode[];\n // literal\n literal?: unknown;\n // enum\n enumValues?: string[];\n};\n\ntype ZodAny = z.ZodTypeAny;\n\n/**\n * Zod 3 uses `schema._def`, Zod 4 uses `schema._zod.def`.\n */\nfunction getDef(schema: unknown): any | undefined {\n if (!schema || typeof schema !== \"object\") return undefined;\n const anySchema = schema as any;\n return anySchema._zod?.def ?? anySchema._def;\n}\n\n/**\n * Try to get a human-readable description.\n * Zod 4: use metadata/registry; fallback to internal def.description.\n * Zod 3: only internal def.description exists.\n */\nfunction getDescription(schema: ZodAny): string | undefined {\n const anyZ: any = z as any;\n\n // Zod 4 global registry metadata, if present\n const registry = anyZ.globalRegistry?.get\n ? anyZ.globalRegistry.get(schema)\n : undefined;\n if (registry && typeof registry.description === \"string\") {\n return registry.description;\n }\n\n // Legacy / internal description\n const def = getDef(schema);\n if (def && typeof def.description === \"string\") {\n return def.description;\n }\n\n return undefined;\n}\n\n/**\n * Peel off wrappers (effects, optional, nullable, default) and\n * return the inner schema + flags.\n *\n * Supports:\n * - Zod 3: ZodEffects, ZodOptional, ZodNullable, ZodDefault\n * - Zod 4: ZodOptional, ZodNullable, ZodDefault\n */\nfunction unwrap(schema: ZodAny): {\n base: ZodAny;\n optional: boolean;\n nullable: boolean;\n} {\n let s: ZodAny = schema;\n let optional = false;\n let nullable = false;\n\n // Zod 3 only (undefined in Zod 4)\n const ZodEffectsCtor: any = (z as any).ZodEffects;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n // Zod 3: ZodEffects wrapper\n if (ZodEffectsCtor && s instanceof ZodEffectsCtor) {\n const def = getDef(s) || {};\n const sourceType =\n typeof (s as any).sourceType === \"function\"\n ? (s as any).sourceType()\n : def.schema;\n if (!sourceType) break;\n s = sourceType;\n continue;\n }\n\n // Zod 3 + 4: optional/nullable/default wrappers\n if (s instanceof z.ZodOptional) {\n optional = true;\n const def = getDef(s);\n s = (def && def.innerType) || s; // innerType exists in both 3 & 4\n continue;\n }\n\n if (s instanceof z.ZodNullable) {\n nullable = true;\n const def = getDef(s);\n s = (def && def.innerType) || s;\n continue;\n }\n\n if (s instanceof z.ZodDefault) {\n const def = getDef(s);\n s = (def && def.innerType) || s;\n continue;\n }\n\n break;\n }\n\n return { base: s, optional, nullable };\n}\n\nexport function introspectSchema(\n schema: ZodAny | undefined\n): SerializableSchemaNode | undefined {\n if (!schema) return undefined;\n\n const { base, optional, nullable } = unwrap(schema);\n const def = getDef(base);\n\n const node: SerializableSchemaNode = {\n kind: inferKind(base),\n optional: optional || undefined,\n nullable: nullable || undefined,\n description: getDescription(base),\n };\n\n // OBJECT\n if (base instanceof z.ZodObject) {\n // Zod 3: _def.shape() (function)\n // Zod 4: .shape getter returns an object\n const rawShape: any =\n (base as any).shape ?? (def && typeof def.shape === \"function\"\n ? def.shape()\n : def?.shape);\n\n const shape =\n typeof rawShape === \"function\" ? rawShape() : rawShape ?? {};\n\n const props: Record<string, SerializableSchemaNode> = {};\n for (const key of Object.keys(shape)) {\n const child = shape[key] as ZodAny;\n const childNode = introspectSchema(child);\n if (childNode) props[key] = childNode;\n }\n node.properties = props;\n }\n\n // ARRAY\n if (base instanceof z.ZodArray) {\n // Zod 3: def.type is inner schema\n // Zod 4: def.element is inner schema\n const inner =\n (def && (def.element as ZodAny)) ||\n (def && (def.type as ZodAny)) ||\n undefined;\n if (inner) {\n node.element = introspectSchema(inner);\n }\n }\n\n // UNION\n if (base instanceof z.ZodUnion) {\n const options: ZodAny[] = (def && def.options) || [];\n node.union = options\n .map((opt) => introspectSchema(opt))\n .filter(Boolean) as SerializableSchemaNode[];\n }\n\n // LITERAL\n if (base instanceof z.ZodLiteral) {\n if (def) {\n // Zod 4: def.values (multi-literal)\n if (Array.isArray(def.values)) {\n node.literal =\n def.values.length === 1 ? def.values[0] : def.values.slice();\n } else {\n // Zod 3: def.value\n node.literal = def.value;\n }\n }\n }\n\n // ENUM\n if (base instanceof z.ZodEnum) {\n if (def) {\n if (Array.isArray(def.values)) {\n // Zod 3\n node.enumValues = def.values.slice();\n } else if (def.entries && typeof def.entries === \"object\") {\n // Zod 4: entries is a { key: value } map\n node.enumValues = Object.values(def.entries).map((v: unknown) =>\n String(v)\n );\n }\n }\n }\n\n return node;\n}\n\nfunction inferKind(schema: ZodAny): string {\n // This path still uses instanceof; it works with Zod 4 Classic\n // (importing from \"zod\"). Anything unknown falls back to \"unknown\".\n if (schema instanceof z.ZodString) return \"string\";\n if (schema instanceof z.ZodNumber) return \"number\";\n if (schema instanceof z.ZodBoolean) return \"boolean\";\n if (schema instanceof z.ZodBigInt) return \"bigint\";\n if (schema instanceof z.ZodDate) return \"date\";\n if (schema instanceof z.ZodArray) return \"array\";\n if (schema instanceof z.ZodObject) return \"object\";\n if (schema instanceof z.ZodUnion) return \"union\";\n if (schema instanceof z.ZodLiteral) return \"literal\";\n if (schema instanceof z.ZodEnum) return \"enum\";\n if (schema instanceof z.ZodRecord) return \"record\";\n if (schema instanceof z.ZodTuple) return \"tuple\";\n if (schema instanceof z.ZodUnknown) return \"unknown\";\n if (schema instanceof z.ZodAny) return \"any\";\n\n return \"unknown\";\n}\n","// serializer.ts\nimport type { AnyLeaf, MethodCfg } from \"@emeryld/rrroutes-contract\";\nimport { introspectSchema, SerializableSchemaNode } from \"./schemaIntrospection.js\";\n\ntype SerializableMethodCfg = Pick<\n MethodCfg,\n | \"description\"\n | \"summary\"\n | \"docsGroup\"\n | \"tags\"\n | \"deprecated\"\n | \"stability\"\n | \"feed\"\n | \"docsMeta\"\n> & {\n hasBody: boolean;\n hasQuery: boolean;\n hasParams: boolean;\n hasOutput: boolean;\n\n // NEW: full Zod ASTs\n bodySchema?: SerializableSchemaNode;\n querySchema?: SerializableSchemaNode;\n paramsSchema?: SerializableSchemaNode;\n outputSchema?: SerializableSchemaNode;\n};\n\nexport type SerializableLeaf = {\n method: string;\n path: string;\n cfg: SerializableMethodCfg;\n};\n\nexport type { SerializableSchemaNode } from \"./schemaIntrospection.js\";\n\nexport function serializeLeaf(leaf: AnyLeaf): SerializableLeaf {\n const cfg = leaf.cfg;\n\n const tags = Array.isArray(cfg.tags) ? cfg.tags.slice() : [];\n\n return {\n method: leaf.method, // 'get' | 'post' | ...\n path: leaf.path,\n cfg: {\n description: cfg.description,\n summary: cfg.summary,\n docsGroup: cfg.docsGroup,\n tags,\n deprecated: cfg.deprecated,\n stability: cfg.stability,\n feed: !!cfg.feed,\n docsMeta: cfg.docsMeta,\n hasBody: !!cfg.bodySchema || !!cfg.bodyFiles?.length,\n hasQuery: !!cfg.querySchema,\n hasParams: !!cfg.paramsSchema,\n hasOutput: !!cfg.outputSchema,\n\n bodySchema: introspectSchema(cfg.bodySchema),\n querySchema: introspectSchema(cfg.querySchema),\n paramsSchema: introspectSchema(cfg.paramsSchema),\n outputSchema: introspectSchema(cfg.outputSchema),\n },\n };\n}\n","// renderLeafDocsHTML.ts\nimport type { AnyLeaf } from \"@emeryld/rrroutes-contract\";\nimport {\n createLeafDocsDocument,\n renderLeafDocsHTML as LeafDocsPage,\n RenderOptions,\n} from \"./LeafDocsPage.js\";\n\nexport function renderLeafDocsHTML(leaves: AnyLeaf[], options: RenderOptions = {}): string {\n return LeafDocsPage(leaves, options);\n}\n\nexport type { RenderOptions, SerializableHistoryEntry } from \"./LeafDocsPage.js\";\nexport { createLeafDocsDocument } from \"./LeafDocsPage.js\";\nexport type { SerializablePreset, SerializablePresetOperation } from \"./presets.js\";\n"],"mappings":"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","names":["renderLeafDocsHTML","renderLeafDocsHTML","path","expressStatic","renderLeafDocsHTML","fs"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dry styles
|
|
3
|
+
fake history logs data for testing
|
|
4
|
+
graphs for history data
|
|
5
|
+
logs webhook
|
|
6
|
+
security -> gated access + give environment and if environment='production' -> extra "Are you sure you want to do this" pop-up for each non-get action
|
|
7
|
+
*/
|
|
1
8
|
import type { AnyLeaf } from '@emeryld/rrroutes-contract';
|
|
2
9
|
import type { Request, Response, Router } from 'express';
|
|
10
|
+
import z from 'zod';
|
|
11
|
+
import type { SerializableHistoryEntry } from './docs/docs.js';
|
|
3
12
|
export type DocsRequestContext = {
|
|
4
13
|
req: Request;
|
|
5
14
|
res: Response;
|
|
6
15
|
leaves: AnyLeaf[];
|
|
16
|
+
presets: PresetGroup<AnyLeaf>[];
|
|
7
17
|
};
|
|
8
18
|
export type DocsOnRequestResult = {
|
|
9
19
|
leaves?: AnyLeaf[];
|
|
20
|
+
presets?: PresetGroup<AnyLeaf>[];
|
|
10
21
|
nonce?: string;
|
|
11
22
|
html?: string;
|
|
12
23
|
};
|
|
@@ -18,18 +29,35 @@ export type OpenApiDocsOptions = {
|
|
|
18
29
|
csp?: boolean;
|
|
19
30
|
/** Override where static assets are served from. Defaults to `${path}/assets`. */
|
|
20
31
|
assetBasePath?: string;
|
|
32
|
+
/** Optional seed history entries that will pre-populate the docs UI history. */
|
|
33
|
+
historySeeds?: SerializableHistoryEntry[];
|
|
21
34
|
/**
|
|
22
35
|
* Hook that runs on every request. Use it to adjust leaves, override nonce, or
|
|
23
36
|
* provide a fully custom HTML response.
|
|
24
37
|
*/
|
|
25
38
|
onRequest?: (ctx: DocsRequestContext) => DocsOnRequestResult | void;
|
|
26
39
|
};
|
|
27
|
-
export type
|
|
40
|
+
export type Preset<L extends AnyLeaf> = L extends infer A extends AnyLeaf ? {
|
|
41
|
+
method: A['method'];
|
|
42
|
+
path: A['path'];
|
|
43
|
+
body: z.input<A['cfg']['bodySchema']>;
|
|
44
|
+
query: z.input<A['cfg']['querySchema']>;
|
|
45
|
+
params: z.input<A['cfg']['paramsSchema']>;
|
|
46
|
+
} : never;
|
|
47
|
+
export type PresetGroup<L extends AnyLeaf> = {
|
|
48
|
+
name: string;
|
|
49
|
+
description?: string;
|
|
50
|
+
tags: string[];
|
|
51
|
+
docsGroup?: string;
|
|
52
|
+
ops: Preset<L>[];
|
|
53
|
+
};
|
|
54
|
+
export type MountDocsArgs<L extends AnyLeaf> = {
|
|
28
55
|
router: Router;
|
|
29
|
-
leaves:
|
|
56
|
+
leaves: L[];
|
|
57
|
+
presets?: PresetGroup<L>[];
|
|
30
58
|
options?: OpenApiDocsOptions;
|
|
31
59
|
};
|
|
32
|
-
export declare function mountRRRoutesDocs({ router, leaves, options, }: MountDocsArgs): {
|
|
60
|
+
export declare function mountRRRoutesDocs<L extends AnyLeaf>({ router, leaves, presets, options, }: MountDocsArgs<L>): {
|
|
33
61
|
path: string;
|
|
34
62
|
};
|
|
35
63
|
export { renderLeafDocsHTML } from './docs/docs.js';
|
package/dist/index.mjs
CHANGED
|
@@ -168,9 +168,22 @@ function normalizeBase(base) {
|
|
|
168
168
|
if (!base) return DEFAULT_ASSET_BASE;
|
|
169
169
|
return base.endsWith("/") ? base.slice(0, -1) : base;
|
|
170
170
|
}
|
|
171
|
-
|
|
171
|
+
function normalizeDocsBase(base) {
|
|
172
|
+
if (!base) return "";
|
|
173
|
+
if (base === "/") return "/";
|
|
174
|
+
return base.endsWith("/") && base.length > 1 ? base.slice(0, -1) : base;
|
|
175
|
+
}
|
|
176
|
+
var DocsDocument = ({
|
|
177
|
+
leavesJson,
|
|
178
|
+
presetsJson,
|
|
179
|
+
assetBase,
|
|
180
|
+
docsBase,
|
|
181
|
+
historyJson,
|
|
182
|
+
cspNonce
|
|
183
|
+
}) => {
|
|
172
184
|
const cssHref = `${assetBase}/docs.css`;
|
|
173
185
|
const jsSrc = `${assetBase}/docs.js`;
|
|
186
|
+
const configJson = serializeConfig(docsBase);
|
|
174
187
|
return /* @__PURE__ */ jsxs("html", { lang: "en", children: [
|
|
175
188
|
/* @__PURE__ */ jsxs("head", { children: [
|
|
176
189
|
/* @__PURE__ */ jsx("meta", { charSet: "UTF-8" }),
|
|
@@ -189,6 +202,33 @@ var DocsDocument = ({ leavesJson, assetBase, cspNonce }) => {
|
|
|
189
202
|
dangerouslySetInnerHTML: { __html: leavesJson }
|
|
190
203
|
}
|
|
191
204
|
),
|
|
205
|
+
/* @__PURE__ */ jsx(
|
|
206
|
+
"script",
|
|
207
|
+
{
|
|
208
|
+
id: "preset-data",
|
|
209
|
+
type: "application/json",
|
|
210
|
+
nonce: cspNonce,
|
|
211
|
+
dangerouslySetInnerHTML: { __html: presetsJson }
|
|
212
|
+
}
|
|
213
|
+
),
|
|
214
|
+
/* @__PURE__ */ jsx(
|
|
215
|
+
"script",
|
|
216
|
+
{
|
|
217
|
+
id: "history-data",
|
|
218
|
+
type: "application/json",
|
|
219
|
+
nonce: cspNonce,
|
|
220
|
+
dangerouslySetInnerHTML: { __html: historyJson }
|
|
221
|
+
}
|
|
222
|
+
),
|
|
223
|
+
/* @__PURE__ */ jsx(
|
|
224
|
+
"script",
|
|
225
|
+
{
|
|
226
|
+
id: "docs-config",
|
|
227
|
+
type: "application/json",
|
|
228
|
+
nonce: cspNonce,
|
|
229
|
+
dangerouslySetInnerHTML: { __html: configJson }
|
|
230
|
+
}
|
|
231
|
+
),
|
|
192
232
|
/* @__PURE__ */ jsx("script", { type: "module", src: jsSrc, nonce: cspNonce })
|
|
193
233
|
] })
|
|
194
234
|
] });
|
|
@@ -196,10 +236,32 @@ var DocsDocument = ({ leavesJson, assetBase, cspNonce }) => {
|
|
|
196
236
|
function serializeLeaves(leaves) {
|
|
197
237
|
return JSON.stringify(leaves.map(serializeLeaf)).replace(/<\//g, "<\\/");
|
|
198
238
|
}
|
|
239
|
+
function serializePresets(presets) {
|
|
240
|
+
return JSON.stringify(Array.isArray(presets) ? presets : []).replace(/<\//g, "<\\/");
|
|
241
|
+
}
|
|
242
|
+
function serializeHistorySeeds(historySeeds) {
|
|
243
|
+
return JSON.stringify(Array.isArray(historySeeds) ? historySeeds : []).replace(/<\//g, "<\\/");
|
|
244
|
+
}
|
|
245
|
+
function serializeConfig(docsBase) {
|
|
246
|
+
return JSON.stringify({ docsBasePath: docsBase }).replace(/<\//g, "<\\/");
|
|
247
|
+
}
|
|
199
248
|
function createLeafDocsDocument(leaves, options = {}) {
|
|
200
249
|
const assetBase = normalizeBase(options.assetBasePath ?? DEFAULT_ASSET_BASE);
|
|
201
250
|
const leavesJson = serializeLeaves(leaves);
|
|
202
|
-
|
|
251
|
+
const presetsJson = serializePresets(options.presets);
|
|
252
|
+
const docsBase = normalizeDocsBase(options.docsBasePath);
|
|
253
|
+
const historyJson = serializeHistorySeeds(options.historySeeds);
|
|
254
|
+
return /* @__PURE__ */ jsx(
|
|
255
|
+
DocsDocument,
|
|
256
|
+
{
|
|
257
|
+
leavesJson,
|
|
258
|
+
presetsJson,
|
|
259
|
+
assetBase,
|
|
260
|
+
docsBase,
|
|
261
|
+
historyJson,
|
|
262
|
+
cspNonce: options.cspNonce
|
|
263
|
+
}
|
|
264
|
+
);
|
|
203
265
|
}
|
|
204
266
|
function renderLeafDocsHTML(leaves, options = {}) {
|
|
205
267
|
const doc = createLeafDocsDocument(leaves, options);
|
|
@@ -217,6 +279,7 @@ var trimTrailingSlash = (value) => value.endsWith("/") && value.length > 1 ? val
|
|
|
217
279
|
function mountRRRoutesDocs({
|
|
218
280
|
router,
|
|
219
281
|
leaves,
|
|
282
|
+
presets = [],
|
|
220
283
|
options = {}
|
|
221
284
|
}) {
|
|
222
285
|
const prefix = options.prefix ? trimTrailingSlash(options.prefix) : "";
|
|
@@ -228,12 +291,14 @@ function mountRRRoutesDocs({
|
|
|
228
291
|
const publicDir = resolvePublicDir();
|
|
229
292
|
const assetsDir = path.join(publicDir, "assets");
|
|
230
293
|
const cspEnabled = options.csp !== false;
|
|
231
|
-
console.log(`Mounting RRRoutes docs at ${normalizedDocsPath} and assets at ${assetsMountPath}`);
|
|
232
294
|
router.use(assetsMountPath, expressStatic(assetsDir, { immutable: true, maxAge: "365d" }));
|
|
233
|
-
|
|
295
|
+
const docsRoutePaths = [normalizedDocsPath, `${normalizedDocsPath}/`, `${normalizedDocsPath}/*`];
|
|
296
|
+
router.get(docsRoutePaths, (req, res) => {
|
|
234
297
|
const preparedLeaves = Array.isArray(leaves) ? leaves.filter((leaf) => leaf.cfg.docsHidden !== true) : [];
|
|
235
|
-
const
|
|
298
|
+
const preparedPresets = Array.isArray(presets) ? presets : [];
|
|
299
|
+
const onRequestResult = options.onRequest?.({ req, res, leaves: preparedLeaves, presets: preparedPresets }) ?? {};
|
|
236
300
|
const finalLeaves = onRequestResult.leaves ?? preparedLeaves;
|
|
301
|
+
const finalPresets = onRequestResult.presets ?? preparedPresets;
|
|
237
302
|
const hasCustomHtml = typeof onRequestResult.html === "string";
|
|
238
303
|
let nonce = onRequestResult.nonce;
|
|
239
304
|
if (!nonce && cspEnabled && !hasCustomHtml) {
|
|
@@ -241,7 +306,10 @@ function mountRRRoutesDocs({
|
|
|
241
306
|
}
|
|
242
307
|
const html = hasCustomHtml ? onRequestResult.html : renderLeafDocsHTML2(finalLeaves, {
|
|
243
308
|
cspNonce: nonce,
|
|
244
|
-
assetBasePath: `${prefix}assetsMountPath
|
|
309
|
+
assetBasePath: `${prefix}${assetsMountPath}`,
|
|
310
|
+
docsBasePath: `${prefix}${normalizedDocsPath}`,
|
|
311
|
+
historySeeds: options.historySeeds,
|
|
312
|
+
presets: normalizePresets(finalPresets)
|
|
245
313
|
});
|
|
246
314
|
if (cspEnabled && nonce) {
|
|
247
315
|
res.setHeader(
|
|
@@ -269,6 +337,22 @@ function resolvePublicDir() {
|
|
|
269
337
|
if (fs.existsSync(fallback)) return fallback;
|
|
270
338
|
return fromModule;
|
|
271
339
|
}
|
|
340
|
+
function normalizePresets(presets) {
|
|
341
|
+
if (!Array.isArray(presets)) return [];
|
|
342
|
+
return presets.map((preset) => ({
|
|
343
|
+
name: preset.name,
|
|
344
|
+
description: preset.description,
|
|
345
|
+
tags: Array.isArray(preset.tags) ? preset.tags.slice() : [],
|
|
346
|
+
docsGroup: preset.docsGroup,
|
|
347
|
+
ops: Array.isArray(preset.ops) ? preset.ops.map((op) => ({
|
|
348
|
+
method: typeof op.method === "string" ? op.method.toUpperCase() : "",
|
|
349
|
+
path: typeof op.path === "string" ? op.path : "",
|
|
350
|
+
body: op.body,
|
|
351
|
+
query: op.query,
|
|
352
|
+
params: op.params
|
|
353
|
+
})) : []
|
|
354
|
+
}));
|
|
355
|
+
}
|
|
272
356
|
export {
|
|
273
357
|
mountRRRoutesDocs,
|
|
274
358
|
renderLeafDocsHTML2 as renderLeafDocsHTML,
|