@emeryld/rrroutes-contract 2.7.5 → 2.7.7

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@emeryld/rrroutes-contract",
3
3
  "description": "TypeScript contract definitions for RRRoutes",
4
- "version": "2.7.5",
4
+ "version": "2.7.7",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "main": "dist/index.cjs",
@@ -14,11 +14,16 @@
14
14
  "require": "./dist/index.cjs"
15
15
  }
16
16
  },
17
+ "bin": {
18
+ "rrroutes-export-finalized-leaves": "./bin/rrroutes-export-finalized-leaves.mjs"
19
+ },
17
20
  "files": [
18
21
  "dist",
19
- "tools/finalized-leaves-viewer.html"
22
+ "tools/finalized-leaves-viewer.html",
23
+ "bin/rrroutes-export-finalized-leaves.mjs"
20
24
  ],
21
25
  "dependencies": {
26
+ "typescript": "^5.9.3",
22
27
  "zod": "^4.3.6"
23
28
  },
24
29
  "devDependencies": {
@@ -156,6 +156,12 @@
156
156
  color: var(--muted);
157
157
  }
158
158
 
159
+ .kv .k a {
160
+ color: inherit;
161
+ text-decoration: underline;
162
+ text-underline-offset: 2px;
163
+ }
164
+
159
165
  .kv .v {
160
166
  margin-top: 2px;
161
167
  word-break: break-word;
@@ -312,6 +318,10 @@
312
318
  <option value="exact">exact</option>
313
319
  </select>
314
320
  </label>
321
+ <label class="field-item">
322
+ <input id="schemaRowsMatchOnly" type="checkbox" />
323
+ <span>schema rows match filter</span>
324
+ </label>
315
325
  </div>
316
326
 
317
327
  <div id="fieldCheckboxes" class="field-row"></div>
@@ -346,6 +356,31 @@
346
356
  { id: 'tags', label: 'tags', get: (leaf) => leaf.cfg?.tags || [] },
347
357
  { id: 'stability', label: 'stability', get: (leaf) => [leaf.cfg?.stability] },
348
358
  { id: 'docsMeta', label: 'docsMeta', get: (leaf) => [leaf.cfg?.docsMeta] },
359
+ {
360
+ id: 'sourceDefinition',
361
+ label: 'source definition',
362
+ get: (leaf, schemaFlatByLeaf, payload) => {
363
+ const source = payload?.sourceByLeaf?.[leaf.key]?.definition
364
+ if (!source) return []
365
+ return [`${source.file}:${source.line}:${source.column}`]
366
+ },
367
+ },
368
+ {
369
+ id: 'sourceSchemas',
370
+ label: 'source schemas',
371
+ get: (leaf, schemaFlatByLeaf, payload) => {
372
+ const schemas = payload?.sourceByLeaf?.[leaf.key]?.schemas
373
+ if (!schemas || typeof schemas !== 'object') return []
374
+ return Object.values(schemas).flatMap((schema) => {
375
+ if (!schema || typeof schema !== 'object') return []
376
+ const tokens = []
377
+ if (schema.sourceName) tokens.push(schema.sourceName)
378
+ if (schema.tag) tokens.push(schema.tag)
379
+ tokens.push(`${schema.file}:${schema.line}:${schema.column}`)
380
+ return tokens
381
+ })
382
+ },
383
+ },
349
384
  {
350
385
  id: 'types',
351
386
  label: 'types',
@@ -390,6 +425,7 @@
390
425
  const caseSensitiveInput = document.getElementById('caseSensitive')
391
426
  const regexSearchInput = document.getElementById('regexSearch')
392
427
  const typeMatchModeInput = document.getElementById('typeMatchMode')
428
+ const schemaRowsMatchOnlyInput = document.getElementById('schemaRowsMatchOnly')
393
429
  const fieldCheckboxes = document.getElementById('fieldCheckboxes')
394
430
  const activeFilterChips = document.getElementById('activeFilterChips')
395
431
  const selectAllFieldsBtn = document.getElementById('selectAllFields')
@@ -557,6 +593,63 @@
557
593
  return Array.from(tokens)
558
594
  }
559
595
 
596
+ function schemaEntryTokens(path, info) {
597
+ const tokens = new Set()
598
+
599
+ if (path) {
600
+ tokens.add(path)
601
+ path.split('.').forEach((segment) => {
602
+ if (segment) tokens.add(segment)
603
+ })
604
+ }
605
+
606
+ if (info && typeof info === 'object') {
607
+ tokens.add(JSON.stringify(info))
608
+ if (info.type) tokens.add(String(info.type))
609
+ if (info.kind) tokens.add(String(info.kind))
610
+ if (info.description) tokens.add(String(info.description))
611
+ if (Array.isArray(info.enumValues)) {
612
+ info.enumValues.forEach((value) => tokens.add(String(value)))
613
+ }
614
+ } else if (info !== null && info !== undefined) {
615
+ tokens.add(String(info))
616
+ }
617
+
618
+ return Array.from(tokens)
619
+ }
620
+
621
+ function schemaEntryMatchesActiveFilter(sectionName, fullPath, info, engine, selectedIds) {
622
+ if (!engine.active) return true
623
+
624
+ let matched = false
625
+ if (selectedIds.includes(sectionName)) {
626
+ matched = schemaEntryTokens(fullPath, info).some((token) => engine.test(token))
627
+ }
628
+
629
+ if (!matched && selectedIds.includes('types')) {
630
+ const typeTokens = []
631
+ if (info && typeof info === 'object') {
632
+ if (info.type) typeTokens.push(String(info.type))
633
+ if (info.kind) typeTokens.push(String(info.kind))
634
+ if (Array.isArray(info.enumValues)) {
635
+ info.enumValues.forEach((value) => typeTokens.push(String(value)))
636
+ }
637
+ }
638
+
639
+ if (typeMatchModeInput.value === 'exact' && !engine.regex) {
640
+ const needle = engine.caseSensitive ? engine.query : engine.query.toLowerCase()
641
+ const normalized = engine.caseSensitive
642
+ ? typeTokens
643
+ : typeTokens.map((token) => token.toLowerCase())
644
+ matched = normalized.includes(needle)
645
+ } else {
646
+ matched = typeTokens.some((token) => engine.test(token))
647
+ }
648
+ }
649
+
650
+ return matched
651
+ }
652
+
560
653
  function selectedFieldIds() {
561
654
  return SEARCH_FIELDS.filter((field) => {
562
655
  const input = document.getElementById(`field-${field.id}`)
@@ -568,10 +661,11 @@
568
661
  if (!engine.active) return true
569
662
  if (selectedIds.length === 0) return false
570
663
  const schemaFlatByLeaf = state.payload?.schemaFlatByLeaf || {}
664
+ const payload = state.payload || {}
571
665
 
572
666
  return SEARCH_FIELDS.some((field) => {
573
667
  if (!selectedIds.includes(field.id)) return false
574
- const tokens = toTokens(field.get(leaf, schemaFlatByLeaf))
668
+ const tokens = toTokens(field.get(leaf, schemaFlatByLeaf, payload))
575
669
  if (field.id === 'types' && typeMatchModeInput.value === 'exact' && !engine.regex) {
576
670
  const needle = engine.caseSensitive ? engine.query : engine.query.toLowerCase()
577
671
  const normalized = engine.caseSensitive
@@ -629,6 +723,40 @@
629
723
  return row
630
724
  }
631
725
 
726
+ function sourceHref(source) {
727
+ if (!source || !source.file) return undefined
728
+ const normalizedPath = String(source.file).replace(/\\/g, '/')
729
+ const prefix = normalizedPath.startsWith('/') ? 'file://' : 'file:///'
730
+ return `${prefix}${encodeURI(normalizedPath)}`
731
+ }
732
+
733
+ function createSourceRow(key, source, engine) {
734
+ const box = el('div', 'kv')
735
+ const keyNode = el('div', 'k')
736
+ const valueNode = el('div', 'v mono')
737
+
738
+ if (!source || !source.file) {
739
+ setHighlighted(keyNode, key, engine)
740
+ box.appendChild(keyNode)
741
+ setHighlighted(valueNode, '—', engine)
742
+ box.appendChild(valueNode)
743
+ return box
744
+ }
745
+
746
+ const href = sourceHref(source)
747
+ const keyLink = document.createElement('a')
748
+ keyLink.href = href
749
+ keyLink.target = '_blank'
750
+ keyLink.rel = 'noopener noreferrer'
751
+ keyLink.textContent = key
752
+ keyNode.appendChild(keyLink)
753
+
754
+ setHighlighted(valueNode, `${source.file}:${source.line}:${source.column}`, engine)
755
+ box.appendChild(keyNode)
756
+ box.appendChild(valueNode)
757
+ return box
758
+ }
759
+
632
760
  function splitFlatSchemaBySection(flatSchema) {
633
761
  const result = {
634
762
  params: {},
@@ -737,17 +865,27 @@
737
865
  return row
738
866
  }
739
867
 
740
- function renderSeparatedSchemas(flatSchema, engine) {
868
+ function renderSeparatedSchemas(flatSchema, engine, selectedIds) {
741
869
  if (!flatSchema || typeof flatSchema !== 'object') return null
742
870
  const section = el('div', 'section')
743
871
  section.appendChild(el('h3', '', 'Schemas (separated by section)'))
744
872
 
745
873
  const grouped = splitFlatSchemaBySection(flatSchema)
746
874
  let hasAnySchemaEntries = false
875
+ const limitToMatchedRows = schemaRowsMatchOnlyInput.checked && engine.active
747
876
 
748
877
  SCHEMA_SECTIONS.forEach((sectionName) => {
749
- const entries = grouped[sectionName]
750
- if (!entries || Object.keys(entries).length === 0) return
878
+ const rawEntries = grouped[sectionName]
879
+ if (!rawEntries || Object.keys(rawEntries).length === 0) return
880
+
881
+ const entries = limitToMatchedRows
882
+ ? Object.fromEntries(
883
+ Object.entries(rawEntries).filter(([fullPath, info]) =>
884
+ schemaEntryMatchesActiveFilter(sectionName, fullPath, info, engine, selectedIds),
885
+ ),
886
+ )
887
+ : rawEntries
888
+ if (Object.keys(entries).length === 0) return
751
889
 
752
890
  hasAnySchemaEntries = true
753
891
  const block = el('div', 'schema-block')
@@ -764,7 +902,7 @@
764
902
  return hasAnySchemaEntries ? section : null
765
903
  }
766
904
 
767
- function renderLeaf(leaf, engine) {
905
+ function renderLeaf(leaf, engine, selectedIds) {
768
906
  const details = el('details', 'leaf')
769
907
  const summary = el('summary')
770
908
  setHighlighted(summary, `${String(leaf.method || '').toUpperCase()} ${leaf.path || ''}`, engine)
@@ -823,7 +961,32 @@
823
961
  content.appendChild(files)
824
962
  }
825
963
 
826
- const separatedSchemas = renderSeparatedSchemas(flatSchema, engine)
964
+ const sourceByLeaf = state.payload?.sourceByLeaf || {}
965
+ const source = sourceByLeaf[leaf.key]
966
+ if (source) {
967
+ const sourceSection = el('div', 'section')
968
+ sourceSection.appendChild(el('h3', '', 'Source'))
969
+ sourceSection.appendChild(createSourceRow('definition', source.definition, engine))
970
+
971
+ const schemaSources = source.schemas || {}
972
+ const schemaEntries = Object.entries(schemaSources)
973
+ if (schemaEntries.length > 0) {
974
+ const schemaGrid = el('div', 'grid-2')
975
+ schemaEntries.forEach(([schemaName, schemaSource]) => {
976
+ const display = schemaSource?.sourceName
977
+ ? `${schemaName}: ${schemaSource.sourceName}`
978
+ : schemaSource?.tag
979
+ ? `${schemaName}: ${schemaSource.tag}`
980
+ : schemaName
981
+ schemaGrid.appendChild(createSourceRow(display, schemaSource, engine))
982
+ })
983
+ sourceSection.appendChild(schemaGrid)
984
+ }
985
+
986
+ content.appendChild(sourceSection)
987
+ }
988
+
989
+ const separatedSchemas = renderSeparatedSchemas(flatSchema, engine, selectedIds)
827
990
  if (separatedSchemas) {
828
991
  content.appendChild(separatedSchemas)
829
992
  }
@@ -863,7 +1026,7 @@
863
1026
  }
864
1027
 
865
1028
  filtered.forEach((leaf) => {
866
- resultsEl.appendChild(renderLeaf(leaf, engine))
1029
+ resultsEl.appendChild(renderLeaf(leaf, engine, selectedIds))
867
1030
  })
868
1031
  syncFilterStateToUrl()
869
1032
  }
@@ -897,6 +1060,7 @@
897
1060
  caseSensitiveInput.checked = false
898
1061
  regexSearchInput.checked = false
899
1062
  typeMatchModeInput.value = 'contains'
1063
+ schemaRowsMatchOnlyInput.checked = false
900
1064
  setFieldSelection(new Set(SEARCH_FIELDS.map((field) => field.id)))
901
1065
  }
902
1066
 
@@ -908,6 +1072,7 @@
908
1072
 
909
1073
  if (searchValue) chips.push(`search: ${searchValue}`)
910
1074
  if (typeMatchModeInput.value === 'exact') chips.push('type mode: exact')
1075
+ if (schemaRowsMatchOnlyInput.checked) chips.push('schema rows: matched only')
911
1076
  if (caseSensitiveInput.checked) chips.push('case sensitive')
912
1077
  if (regexSearchInput.checked) chips.push('regex')
913
1078
  if (hasRegexError) chips.push('regex error')
@@ -935,6 +1100,7 @@
935
1100
  caseSensitive: caseSensitiveInput.checked,
936
1101
  regex: regexSearchInput.checked,
937
1102
  typeMatchMode: typeMatchModeInput.value === 'exact' ? 'exact' : 'contains',
1103
+ schemaRowsMatchOnly: schemaRowsMatchOnlyInput.checked,
938
1104
  selectedFields: selectedFieldIds(),
939
1105
  }
940
1106
  }
@@ -966,6 +1132,7 @@
966
1132
  if (parsed.typeMatchMode === 'exact' || parsed.typeMatchMode === 'contains') {
967
1133
  typeMatchModeInput.value = parsed.typeMatchMode
968
1134
  }
1135
+ schemaRowsMatchOnlyInput.checked = Boolean(parsed.schemaRowsMatchOnly)
969
1136
 
970
1137
  if (Array.isArray(parsed.selectedFields)) {
971
1138
  const allowed = new Set(parsed.selectedFields)
@@ -1020,6 +1187,7 @@
1020
1187
  caseSensitiveInput.addEventListener('change', renderResults)
1021
1188
  regexSearchInput.addEventListener('change', renderResults)
1022
1189
  typeMatchModeInput.addEventListener('change', renderResults)
1190
+ schemaRowsMatchOnlyInput.addEventListener('change', renderResults)
1023
1191
 
1024
1192
  selectAllFieldsBtn.addEventListener('click', () =>
1025
1193
  setFieldSelection(new Set(SEARCH_FIELDS.map((field) => field.id))),