@emeryld/rrroutes-contract 2.1.12 → 2.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { z, ZodTypeAny } from 'zod';
1
+ import { z, ZodType } from 'zod';
2
2
  /** Supported HTTP verbs for the routes DSL. */
3
3
  export type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';
4
4
  /** Declarative description of a multipart upload field. */
@@ -16,13 +16,13 @@ export type NodeCfg = {
16
16
  /** Per-method configuration merged with inherited node config. */
17
17
  export type MethodCfg = {
18
18
  /** Zod schema describing the request body. */
19
- bodySchema?: ZodTypeAny;
19
+ bodySchema?: ZodType;
20
20
  /** Zod schema describing the query string. */
21
- querySchema?: ZodTypeAny;
21
+ querySchema?: ZodType;
22
22
  /** Zod schema describing path params (overrides inferred params). */
23
- paramsSchema?: ZodTypeAny;
23
+ paramsSchema?: ZodType;
24
24
  /** Zod schema describing the response payload. */
25
- outputSchema?: ZodTypeAny;
25
+ outputSchema?: ZodType;
26
26
  /** Multipart upload definitions for the route. */
27
27
  bodyFiles?: FileField[];
28
28
  /** Marks the route as an infinite feed (enables cursor helpers). */
@@ -102,13 +102,13 @@ export declare function buildCacheKey<L extends AnyLeaf>(args: {
102
102
  query?: InferQuery<L>;
103
103
  }): readonly [HttpMethod, string, {}];
104
104
  /** Infer params either from the explicit params schema or from the path literal. */
105
- export type InferParams<L extends AnyLeaf> = L['cfg']['paramsSchema'] extends ZodTypeAny ? z.infer<L['cfg']['paramsSchema']> : ExtractParamsFromPath<L['path']>;
105
+ export type InferParams<L extends AnyLeaf> = L['cfg']['paramsSchema'] extends ZodType ? z.infer<L['cfg']['paramsSchema']> : ExtractParamsFromPath<L['path']>;
106
106
  /** Infer query shape from a Zod schema when present. */
107
- export type InferQuery<L extends AnyLeaf> = L['cfg']['querySchema'] extends ZodTypeAny ? z.infer<L['cfg']['querySchema']> : never;
107
+ export type InferQuery<L extends AnyLeaf> = L['cfg']['querySchema'] extends ZodType ? z.infer<L['cfg']['querySchema']> : never;
108
108
  /** Infer request body shape from a Zod schema when present. */
109
- export type InferBody<L extends AnyLeaf> = L['cfg']['bodySchema'] extends ZodTypeAny ? z.infer<L['cfg']['bodySchema']> : never;
109
+ export type InferBody<L extends AnyLeaf> = L['cfg']['bodySchema'] extends ZodType ? z.infer<L['cfg']['bodySchema']> : never;
110
110
  /** Infer handler output shape from a Zod schema. Defaults to unknown. */
111
- export type InferOutput<L extends AnyLeaf> = L['cfg']['outputSchema'] extends ZodTypeAny ? z.infer<L['cfg']['outputSchema']> : unknown;
111
+ export type InferOutput<L extends AnyLeaf> = L['cfg']['outputSchema'] extends ZodType ? z.infer<L['cfg']['outputSchema']> : unknown;
112
112
  /** Render a type as if it were a simple object literal. */
113
113
  export type Prettify<T> = {
114
114
  [K in keyof T]: T[K];
@@ -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 {};
@@ -1,9 +1,14 @@
1
1
  import { AnyLeaf, MethodCfg } from "../core/routesV3.core";
2
- type SerializableMethodCfg = Pick<MethodCfg, 'description' | 'summary' | 'docsGroup' | 'tags' | 'deprecated' | 'stability' | 'feed' | 'docsMeta'> & {
2
+ import { SerializableSchemaNode } from "./schemaIntrospection";
3
+ type SerializableMethodCfg = Pick<MethodCfg, "description" | "summary" | "docsGroup" | "tags" | "deprecated" | "stability" | "feed" | "docsMeta"> & {
3
4
  hasBody: boolean;
4
5
  hasQuery: boolean;
5
6
  hasParams: boolean;
6
7
  hasOutput: boolean;
8
+ bodySchema?: SerializableSchemaNode;
9
+ querySchema?: SerializableSchemaNode;
10
+ paramsSchema?: SerializableSchemaNode;
11
+ outputSchema?: SerializableSchemaNode;
7
12
  };
8
13
  export type SerializableLeaf = {
9
14
  method: string;
package/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
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
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -299,6 +309,130 @@ function defineSocketEvents(config, events) {
299
309
  return { config, events };
300
310
  }
301
311
 
312
+ // src/docs/schemaIntrospection.ts
313
+ var z3 = __toESM(require("zod"), 1);
314
+ function getDef(schema) {
315
+ if (!schema || typeof schema !== "object") return void 0;
316
+ const anySchema = schema;
317
+ return anySchema._zod?.def ?? anySchema._def;
318
+ }
319
+ function getDescription(schema) {
320
+ const anyZ = z3;
321
+ const registry = anyZ.globalRegistry?.get ? anyZ.globalRegistry.get(schema) : void 0;
322
+ if (registry && typeof registry.description === "string") {
323
+ return registry.description;
324
+ }
325
+ const def = getDef(schema);
326
+ if (def && typeof def.description === "string") {
327
+ return def.description;
328
+ }
329
+ return void 0;
330
+ }
331
+ function unwrap(schema) {
332
+ let s = schema;
333
+ let optional = false;
334
+ let nullable = false;
335
+ const ZodEffectsCtor = z3.ZodEffects;
336
+ while (true) {
337
+ if (ZodEffectsCtor && s instanceof ZodEffectsCtor) {
338
+ const def = getDef(s) || {};
339
+ const sourceType = typeof s.sourceType === "function" ? s.sourceType() : def.schema;
340
+ if (!sourceType) break;
341
+ s = sourceType;
342
+ continue;
343
+ }
344
+ if (s instanceof z3.ZodOptional) {
345
+ optional = true;
346
+ const def = getDef(s);
347
+ s = def && def.innerType || s;
348
+ continue;
349
+ }
350
+ if (s instanceof z3.ZodNullable) {
351
+ nullable = true;
352
+ const def = getDef(s);
353
+ s = def && def.innerType || s;
354
+ continue;
355
+ }
356
+ if (s instanceof z3.ZodDefault) {
357
+ const def = getDef(s);
358
+ s = def && def.innerType || s;
359
+ continue;
360
+ }
361
+ break;
362
+ }
363
+ return { base: s, optional, nullable };
364
+ }
365
+ function introspectSchema(schema) {
366
+ if (!schema) return void 0;
367
+ const { base, optional, nullable } = unwrap(schema);
368
+ const def = getDef(base);
369
+ const node = {
370
+ kind: inferKind(base),
371
+ optional: optional || void 0,
372
+ nullable: nullable || void 0,
373
+ description: getDescription(base)
374
+ };
375
+ if (base instanceof z3.ZodObject) {
376
+ const rawShape = base.shape ?? (def && typeof def.shape === "function" ? def.shape() : def?.shape);
377
+ const shape = typeof rawShape === "function" ? rawShape() : rawShape ?? {};
378
+ const props = {};
379
+ for (const key of Object.keys(shape)) {
380
+ const child = shape[key];
381
+ const childNode = introspectSchema(child);
382
+ if (childNode) props[key] = childNode;
383
+ }
384
+ node.properties = props;
385
+ }
386
+ if (base instanceof z3.ZodArray) {
387
+ const inner = def && def.element || def && def.type || void 0;
388
+ if (inner) {
389
+ node.element = introspectSchema(inner);
390
+ }
391
+ }
392
+ if (base instanceof z3.ZodUnion) {
393
+ const options = def && def.options || [];
394
+ node.union = options.map((opt) => introspectSchema(opt)).filter(Boolean);
395
+ }
396
+ if (base instanceof z3.ZodLiteral) {
397
+ if (def) {
398
+ if (Array.isArray(def.values)) {
399
+ node.literal = def.values.length === 1 ? def.values[0] : def.values.slice();
400
+ } else {
401
+ node.literal = def.value;
402
+ }
403
+ }
404
+ }
405
+ if (base instanceof z3.ZodEnum) {
406
+ if (def) {
407
+ if (Array.isArray(def.values)) {
408
+ node.enumValues = def.values.slice();
409
+ } else if (def.entries && typeof def.entries === "object") {
410
+ node.enumValues = Object.values(def.entries).map(
411
+ (v) => String(v)
412
+ );
413
+ }
414
+ }
415
+ }
416
+ return node;
417
+ }
418
+ function inferKind(schema) {
419
+ if (schema instanceof z3.ZodString) return "string";
420
+ if (schema instanceof z3.ZodNumber) return "number";
421
+ if (schema instanceof z3.ZodBoolean) return "boolean";
422
+ if (schema instanceof z3.ZodBigInt) return "bigint";
423
+ if (schema instanceof z3.ZodDate) return "date";
424
+ if (schema instanceof z3.ZodArray) return "array";
425
+ if (schema instanceof z3.ZodObject) return "object";
426
+ if (schema instanceof z3.ZodUnion) return "union";
427
+ if (schema instanceof z3.ZodLiteral) return "literal";
428
+ if (schema instanceof z3.ZodEnum) return "enum";
429
+ if (schema instanceof z3.ZodRecord) return "record";
430
+ if (schema instanceof z3.ZodTuple) return "tuple";
431
+ if (schema instanceof z3.ZodUnknown) return "unknown";
432
+ if (schema instanceof z3.ZodAny) return "any";
433
+ return "unknown";
434
+ }
435
+
302
436
  // src/docs/serializer.ts
303
437
  function serializeLeaf(leaf) {
304
438
  const cfg = leaf.cfg;
@@ -319,7 +453,11 @@ function serializeLeaf(leaf) {
319
453
  hasBody: !!cfg.bodySchema || !!cfg.bodyFiles?.length,
320
454
  hasQuery: !!cfg.querySchema,
321
455
  hasParams: !!cfg.paramsSchema,
322
- hasOutput: !!cfg.outputSchema
456
+ hasOutput: !!cfg.outputSchema,
457
+ bodySchema: introspectSchema(cfg.bodySchema),
458
+ querySchema: introspectSchema(cfg.querySchema),
459
+ paramsSchema: introspectSchema(cfg.paramsSchema),
460
+ outputSchema: introspectSchema(cfg.outputSchema)
323
461
  }
324
462
  };
325
463
  }
@@ -718,7 +856,104 @@ var styles = `
718
856
  border-radius: 10px;
719
857
  border: 1px dashed rgba(148, 163, 184, 0.5);
720
858
  background: rgba(15, 23, 42, 0.9);
721
- }`;
859
+ }
860
+ .schema-section {
861
+ margin-top: 8px;
862
+ font-size: 11px;
863
+ border-top: 1px dashed rgba(148, 163, 184, 0.5);
864
+ padding-top: 6px;
865
+ }
866
+
867
+ .schema-details {
868
+ margin-top: 6px;
869
+ border-radius: 10px;
870
+ border: 1px solid rgba(148, 163, 184, 0.4);
871
+ background: rgba(15, 23, 42, 0.95);
872
+ padding: 8px 10px;
873
+ }
874
+
875
+ .schema-details summary {
876
+ cursor: pointer;
877
+ list-style: none;
878
+ font-size: 11px;
879
+ color: var(--text-muted);
880
+ }
881
+
882
+ .schema-details summary::-webkit-details-marker {
883
+ display: none;
884
+ }
885
+
886
+ .schema-details summary::before {
887
+ content: "\u25B8";
888
+ display: inline-block;
889
+ margin-right: 6px;
890
+ font-size: 9px;
891
+ }
892
+
893
+ .schema-details[open] summary::before {
894
+ content: "\u25BE";
895
+ }
896
+
897
+ .schema-table {
898
+ width: 100%;
899
+ border-collapse: collapse;
900
+ margin-top: 6px;
901
+ font-size: 11px;
902
+ }
903
+
904
+ .schema-table th,
905
+ .schema-table td {
906
+ padding: 3px 4px;
907
+ border-bottom: 1px solid rgba(30, 64, 175, 0.35);
908
+ text-align: left;
909
+ }
910
+
911
+ .schema-table th {
912
+ font-weight: 500;
913
+ color: var(--text-muted);
914
+ }
915
+
916
+ .schema-field-name {
917
+ font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco,
918
+ Consolas, "Liberation Mono", "Courier New", monospace;
919
+ }
920
+
921
+ .schema-type {
922
+ color: var(--accent);
923
+ font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco,
924
+ Consolas, "Liberation Mono", "Courier New", monospace;
925
+ }
926
+
927
+ .schema-required {
928
+ font-size: 10px;
929
+ text-transform: uppercase;
930
+ letter-spacing: 0.12em;
931
+ padding: 1px 5px;
932
+ border-radius: 999px;
933
+ border: 1px solid rgba(52, 211, 153, 0.7);
934
+ color: #bbf7d0;
935
+ background: rgba(22, 163, 74, 0.1);
936
+ }
937
+
938
+ .schema-optional {
939
+ font-size: 10px;
940
+ text-transform: uppercase;
941
+ letter-spacing: 0.12em;
942
+ padding: 1px 5px;
943
+ border-radius: 999px;
944
+ border: 1px solid rgba(148, 163, 184, 0.6);
945
+ color: var(--text-muted);
946
+ background: rgba(15, 23, 42, 0.95);
947
+ }
948
+
949
+ .schema-meta {
950
+ margin-top: 6px;
951
+ font-size: 10px;
952
+ color: var(--text-muted);
953
+ white-space: pre-wrap;
954
+ word-break: break-word;
955
+ }
956
+ `;
722
957
  function renderLeafDocsHTML(leaves, options = {}) {
723
958
  const docsLeaves = leaves.map(serializeLeaf);
724
959
  const leafJson = JSON.stringify(docsLeaves).replace(/</g, "\\u003c");
@@ -790,6 +1025,99 @@ ${styles}
790
1025
  <script id="leaf-data" type="application/json"${nonceAttr}>${leafJson}</script>
791
1026
  <script${nonceAttr}>
792
1027
  (function () {
1028
+
1029
+ function typeLabel(node) {
1030
+ if (!node) return "unknown";
1031
+ var base = node.kind || "unknown";
1032
+ if (node.literal !== undefined) {
1033
+ return JSON.stringify(node.literal);
1034
+ }
1035
+ if (node.enumValues && node.enumValues.length) {
1036
+ return "enum[" + node.enumValues.join(" | ") + "]";
1037
+ }
1038
+ if (node.kind === "array" && node.element) {
1039
+ return "Array<" + typeLabel(node.element) + ">";
1040
+ }
1041
+ if (node.kind === "union" && node.union && node.union.length) {
1042
+ return node.union.map(typeLabel).join(" | ");
1043
+ }
1044
+ return base;
1045
+ }
1046
+
1047
+ function renderObjectRows(node, prefix) {
1048
+ if (!node || node.kind !== "object" || !node.properties) return "";
1049
+ var rows = "";
1050
+ Object.keys(node.properties).forEach(function (key) {
1051
+ var child = node.properties[key];
1052
+ var fullName = prefix ? prefix + "." + key : key;
1053
+ var required = !child.optional;
1054
+ rows +=
1055
+ "<tr>" +
1056
+ '<td class="schema-field-name">' +
1057
+ escapeHtml(fullName) +
1058
+ "</td>" +
1059
+ '<td class="schema-type">' +
1060
+ escapeHtml(typeLabel(child)) +
1061
+ "</td>" +
1062
+ "<td>" +
1063
+ (required
1064
+ ? '<span class="schema-required">required</span>'
1065
+ : '<span class="schema-optional">optional</span>') +
1066
+ "</td>" +
1067
+ "<td>" +
1068
+ (child.description
1069
+ ? escapeHtml(String(child.description))
1070
+ : "") +
1071
+ "</td>" +
1072
+ "</tr>";
1073
+
1074
+ // Recursively show nested objects, arrays-of-objects, etc.
1075
+ if (child.kind === "object") {
1076
+ rows += renderObjectRows(child, fullName);
1077
+ } else if (child.kind === "array" && child.element && child.element.kind === "object") {
1078
+ rows += renderObjectRows(
1079
+ child.element,
1080
+ fullName + "[]."
1081
+ );
1082
+ }
1083
+ });
1084
+ return rows;
1085
+ }
1086
+
1087
+ function renderSchemaSection(label, node) {
1088
+ if (!node) return "";
1089
+ // For non-object schemas, just show a one-line type.
1090
+ var isObject = node.kind === "object" && node.properties;
1091
+ var content = "";
1092
+
1093
+ if (isObject) {
1094
+ var rows = renderObjectRows(node, "");
1095
+ content =
1096
+ '<table class="schema-table">' +
1097
+ "<thead><tr>" +
1098
+ "<th>Field</th><th>Type</th><th></th><th>Description</th>" +
1099
+ "</tr></thead>" +
1100
+ "<tbody>" +
1101
+ rows +
1102
+ "</tbody></table>";
1103
+ } else {
1104
+ content =
1105
+ '<div class="schema-meta">' +
1106
+ "<strong>Type:</strong> " +
1107
+ escapeHtml(typeLabel(node)) +
1108
+ "</div>";
1109
+ }
1110
+
1111
+ return (
1112
+ '<div class="schema-section">' +
1113
+ "<div><strong>" +
1114
+ escapeHtml(label) +
1115
+ "</strong></div>" +
1116
+ content +
1117
+ "</div>"
1118
+ );
1119
+ }
1120
+
793
1121
  function escapeHtml(str) {
794
1122
  return String(str)
795
1123
  .replace(/&/g, '&amp;')
@@ -885,40 +1213,40 @@ ${styles}
885
1213
  }
886
1214
 
887
1215
  function routeToHTML(route) {
888
- var method = String(route.method || '').toUpperCase();
889
- var methodClass = 'method-' + String(route.method || '').toLowerCase();
1216
+ var method = String(route.method || "").toUpperCase();
1217
+ var methodClass = "method-" + String(route.method || "").toLowerCase();
890
1218
 
891
1219
  var cfg = route.cfg || {};
892
- var group = cfg.docsGroup || 'Ungrouped';
1220
+ var group = cfg.docsGroup || "Ungrouped";
893
1221
 
894
- var summary = cfg.summary || cfg.description || '';
1222
+ var summary = cfg.summary || cfg.description || "";
895
1223
  var description =
896
1224
  cfg.description && cfg.summary !== cfg.description
897
1225
  ? cfg.description
898
- : '';
1226
+ : "";
899
1227
 
900
1228
  var tags = Array.isArray(cfg.tags) ? cfg.tags : [];
901
1229
  var tagHtml = tags
902
1230
  .map(function (tag) {
903
1231
  var t = String(tag);
904
- var extraClass = t === 'not-implemented' ? ' tag-not-impl' : '';
905
- return '<span class="tag' + extraClass + '">' + escapeHtml(t) + '</span>';
1232
+ var extraClass = t === "not-implemented" ? " tag-not-impl" : "";
1233
+ return '<span class="tag' + extraClass + '">' + escapeHtml(t) + "</span>";
906
1234
  })
907
- .join('');
1235
+ .join("");
908
1236
 
909
- var metaBadges = '';
1237
+ var metaBadges = "";
910
1238
  if (cfg.feed) {
911
1239
  metaBadges += '<span class="badge badge-feed">Feed</span>';
912
1240
  }
913
- if (cfg.deprecated || cfg.stability === 'deprecated') {
1241
+ if (cfg.deprecated || cfg.stability === "deprecated") {
914
1242
  metaBadges += '<span class="badge badge-deprecated">Deprecated</span>';
915
- } else if (cfg.stability && cfg.stability !== 'stable') {
1243
+ } else if (cfg.stability && cfg.stability !== "stable") {
916
1244
  metaBadges +=
917
1245
  '<span class="badge badge-' +
918
1246
  escapeHtml(cfg.stability) +
919
1247
  '">' +
920
1248
  escapeHtml(String(cfg.stability)) +
921
- '</span>';
1249
+ "</span>";
922
1250
  }
923
1251
 
924
1252
  var schemaPills = [];
@@ -929,8 +1257,48 @@ ${styles}
929
1257
 
930
1258
  var schemaHtml =
931
1259
  schemaPills.length > 0
932
- ? '<div class="schema-row">' + schemaPills.join('') + '</div>'
933
- : '';
1260
+ ? '<div class="schema-row">' + schemaPills.join("") + "</div>"
1261
+ : "";
1262
+
1263
+ // NEW: full expandable details including field-level schemas + docsMeta
1264
+ var hasDetailedSchemas =
1265
+ cfg.bodySchema || cfg.querySchema || cfg.paramsSchema || cfg.outputSchema;
1266
+ var detailsInner = "";
1267
+
1268
+ if (cfg.bodySchema) {
1269
+ detailsInner += renderSchemaSection("Request body", cfg.bodySchema);
1270
+ }
1271
+ if (cfg.querySchema) {
1272
+ detailsInner += renderSchemaSection("Query params", cfg.querySchema);
1273
+ }
1274
+ if (cfg.paramsSchema) {
1275
+ detailsInner += renderSchemaSection("Path params", cfg.paramsSchema);
1276
+ }
1277
+ if (cfg.outputSchema) {
1278
+ detailsInner += renderSchemaSection("Response", cfg.outputSchema);
1279
+ }
1280
+
1281
+ if (cfg.docsMeta) {
1282
+ try {
1283
+ var metaJson = JSON.stringify(cfg.docsMeta, null, 2);
1284
+ detailsInner +=
1285
+ '<div class="schema-section">' +
1286
+ "<div><strong>Metadata</strong></div>" +
1287
+ '<div class="schema-meta">' +
1288
+ escapeHtml(metaJson) +
1289
+ "</div>" +
1290
+ "</div>";
1291
+ } catch (e) {}
1292
+ }
1293
+
1294
+ var detailsHtml = "";
1295
+ if (hasDetailedSchemas || cfg.docsMeta) {
1296
+ detailsHtml =
1297
+ '<details class="schema-details">' +
1298
+ "<summary>Inspect schemas & metadata</summary>" +
1299
+ detailsInner +
1300
+ "</details>";
1301
+ }
934
1302
 
935
1303
  return (
936
1304
  '<article class="route-card">' +
@@ -939,31 +1307,33 @@ ${styles}
939
1307
  methodClass +
940
1308
  '">' +
941
1309
  method +
942
- '</span>' +
1310
+ "</span>" +
943
1311
  '<code class="route-path">' +
944
- escapeHtml(route.path || '') +
945
- '</code>' +
1312
+ escapeHtml(route.path || "") +
1313
+ "</code>" +
946
1314
  (group
947
- ? '<span class="group-label">' + escapeHtml(String(group)) + '</span>'
948
- : '') +
949
- '</header>' +
1315
+ ? '<span class="group-label">' + escapeHtml(String(group)) + "</span>"
1316
+ : "") +
1317
+ "</header>" +
950
1318
  '<div class="route-tags">' +
951
1319
  tagHtml +
952
1320
  metaBadges +
953
- '</div>' +
1321
+ "</div>" +
954
1322
  (summary
955
- ? '<p class="route-summary">' + escapeHtml(String(summary)) + '</p>'
956
- : '') +
1323
+ ? '<p class="route-summary">' + escapeHtml(String(summary)) + "</p>"
1324
+ : "") +
957
1325
  (description
958
1326
  ? '<p class="route-description">' +
959
1327
  escapeHtml(String(description)) +
960
- '</p>'
961
- : '') +
1328
+ "</p>"
1329
+ : "") +
962
1330
  schemaHtml +
963
- '</article>'
1331
+ (detailsHtml ? detailsHtml : "") +
1332
+ "</article>"
964
1333
  );
965
1334
  }
966
1335
 
1336
+
967
1337
  function applyFilters() {
968
1338
  var query = searchInput.value.toLowerCase().trim();
969
1339