@openuji/speculator 0.2.0 → 0.3.0

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 CHANGED
@@ -6,7 +6,7 @@ A TypeScript library for processing ReSpec-like markup and open for intgration i
6
6
  ## Installation
7
7
 
8
8
  ```bash
9
- pnpm install @openui/speculator
9
+ pnpm install -D @openui/speculator linkdom
10
10
  ```
11
11
 
12
12
 
package/dist/browser.cjs CHANGED
@@ -33,6 +33,7 @@ __export(browser_exports, {
33
33
  DOMHtmlRenderer: () => DOMHtmlRenderer,
34
34
  FormatProcessor: () => FormatProcessor,
35
35
  IncludeProcessor: () => IncludeProcessor,
36
+ RespecXrefResolver: () => RespecXrefResolver,
36
37
  Speculator: () => Speculator,
37
38
  SpeculatorError: () => SpeculatorError,
38
39
  browserFileLoader: () => browserFileLoader,
@@ -101,16 +102,35 @@ var SpeculatorError = class extends Error {
101
102
  };
102
103
 
103
104
  // src/pipeline/postprocess.ts
104
- async function postprocess(root, passes, options = {}) {
105
- const warnings = [];
106
- for (const pass of passes) {
107
- const result = await pass.run(root, options);
108
- if (result && result.length) {
109
- warnings.push(...result);
105
+ var Postprocessor = class {
106
+ constructor(passes) {
107
+ this.passes = passes;
108
+ }
109
+ /**
110
+ * Run the configured passes on the given root element.
111
+ *
112
+ * @param root The document root to process.
113
+ * @param areas Optional list of output areas to run. If omitted, all passes
114
+ * are executed.
115
+ * @param options Configuration options for the passes.
116
+ */
117
+ async run(root, areas, options = {}) {
118
+ const warnings = [];
119
+ const outputs = {};
120
+ const active = areas ? this.passes.filter((p) => areas.includes(p.area)) : this.passes;
121
+ for (const pass of active) {
122
+ const current = outputs[pass.area];
123
+ const result = await pass.run(root, current, options);
124
+ if (result.data !== void 0) {
125
+ outputs[pass.area] = result.data;
126
+ }
127
+ if (result.warnings && result.warnings.length) {
128
+ warnings.push(...result.warnings);
129
+ }
110
130
  }
131
+ return { outputs, warnings };
111
132
  }
112
- return { warnings };
113
- }
133
+ };
114
134
 
115
135
  // src/processors/include-processor.ts
116
136
  var IncludeProcessor = class {
@@ -478,12 +498,13 @@ function resolveIdlLinks(root, index, warnings, suppressClass) {
478
498
  });
479
499
  }
480
500
  var idlPass = {
481
- async run(root, options) {
501
+ area: "idl",
502
+ async run(root, _data, options) {
482
503
  const warnings = [];
483
504
  const suppressClass = options.diagnostics?.suppressClass ?? "no-link-warnings";
484
505
  const index = buildIdlIndex(root, warnings);
485
506
  resolveIdlLinks(root, index, warnings, suppressClass);
486
- return warnings;
507
+ return { warnings };
487
508
  }
488
509
  };
489
510
 
@@ -503,6 +524,17 @@ function slugify2(text) {
503
524
  function isSuppressed(node, suppressClass) {
504
525
  return !!node.closest(`.${suppressClass}`);
505
526
  }
527
+ function getCiteSpecs(node) {
528
+ let el = node;
529
+ while (el) {
530
+ const cite = el.getAttribute("data-cite");
531
+ if (cite) {
532
+ return cite.split(/[\s,]+/).map((s) => s.trim()).filter(Boolean);
533
+ }
534
+ el = el.parentElement;
535
+ }
536
+ return void 0;
537
+ }
506
538
  function buildLocalMap(root) {
507
539
  const map = /* @__PURE__ */ new Map();
508
540
  const doc = root.ownerDocument;
@@ -548,11 +580,13 @@ function buildLocalMap(root) {
548
580
  return map;
549
581
  }
550
582
  var xrefPass = {
551
- async run(root, options) {
583
+ area: "xref",
584
+ async run(root, _data, options) {
552
585
  const suppressClass = options.diagnostics?.suppressClass ?? "no-link-warnings";
553
586
  const warnings = [];
554
587
  const localMap = buildLocalMap(root);
555
588
  const xrefAnchors = Array.from(root.querySelectorAll("a[data-xref]"));
589
+ const resolverConfigs = Array.isArray(options.xref) ? options.xref : options.xref ? [options.xref] : [];
556
590
  const unresolved = /* @__PURE__ */ new Map();
557
591
  for (const a of xrefAnchors) {
558
592
  if (isSuppressed(a, suppressClass)) continue;
@@ -561,34 +595,96 @@ var xrefPass = {
561
595
  const hit = localMap.get(key);
562
596
  if (hit) {
563
597
  a.setAttribute("href", hit.href);
564
- } else {
565
- const bucket = unresolved.get(key) || [];
566
- bucket.push(a);
567
- unresolved.set(key, bucket);
598
+ continue;
599
+ }
600
+ const specsOverride = getCiteSpecs(a);
601
+ const mapKey = `${key}|${(specsOverride || []).join(",")}`;
602
+ let entry = unresolved.get(mapKey);
603
+ if (!entry) {
604
+ entry = { term, anchors: [], results: [] };
605
+ if (specsOverride) entry.specsOverride = specsOverride;
606
+ unresolved.set(mapKey, entry);
568
607
  }
608
+ entry.anchors.push(a);
569
609
  }
570
- const resolver = options.xref?.resolver;
571
- if (resolver && unresolved.size) {
572
- const queries = Array.from(unresolved.keys()).map((term) => ({ term }));
573
- resolver.resolveBatch(queries, options.xref?.specs).then((results) => {
574
- for (const [key, anchors] of unresolved.entries()) {
575
- const res = results.get(key);
576
- if (!res) continue;
577
- for (const a of anchors) {
578
- a.setAttribute("href", res.href);
579
- if (res.cite) a.setAttribute("data-cite", res.cite);
580
- }
581
- unresolved.delete(key);
610
+ for (const cfg of resolverConfigs) {
611
+ const queries = [];
612
+ const idMap = /* @__PURE__ */ new Map();
613
+ for (const [key, entry] of unresolved.entries()) {
614
+ let specs;
615
+ if (entry.specsOverride) {
616
+ const allowed = cfg.specs ? entry.specsOverride.filter((s) => cfg.specs.includes(s)) : entry.specsOverride;
617
+ specs = allowed;
618
+ } else {
619
+ specs = cfg.specs;
582
620
  }
583
- }).catch((err) => {
584
- warnings.push(`Xref resolver failed: ${err instanceof Error ? err.message : String(err)}`);
585
- });
621
+ if (specs && specs.length === 0) continue;
622
+ const id = `${key}|${queries.length}`;
623
+ const q = { id, term: entry.term };
624
+ if (specs && specs.length) q.specs = specs;
625
+ queries.push(q);
626
+ idMap.set(id, entry);
627
+ }
628
+ if (!queries.length) continue;
629
+ try {
630
+ const resMap = await cfg.resolver.resolveBatch(queries);
631
+ for (const [id, hits] of resMap.entries()) {
632
+ const entry = idMap.get(id);
633
+ if (entry) entry.results.push(...hits);
634
+ }
635
+ } catch (err) {
636
+ warnings.push(
637
+ `Xref resolver failed: ${err instanceof Error ? err.message : String(err)}`
638
+ );
639
+ }
586
640
  }
587
- for (const [key, anchors] of unresolved.entries()) {
588
- const original = anchors[0].getAttribute("data-xref") || key;
589
- warnings.push(`Unresolved xref: "${original}"`);
641
+ const defaultPriority = resolverConfigs.flatMap((cfg) => cfg.specs || []);
642
+ for (const entry of unresolved.values()) {
643
+ const hits = entry.results;
644
+ let chosen;
645
+ let ambiguous = false;
646
+ const preferred = entry.specsOverride && entry.specsOverride.length ? entry.specsOverride : defaultPriority;
647
+ if (hits.length > 0) {
648
+ if (preferred && preferred.length) {
649
+ const remaining = new Set(hits);
650
+ for (const spec of preferred) {
651
+ const matches = hits.filter((h) => h.cite === spec);
652
+ matches.forEach((m) => remaining.delete(m));
653
+ if (matches.length === 1) {
654
+ chosen = matches[0];
655
+ break;
656
+ }
657
+ if (matches.length > 1) {
658
+ ambiguous = true;
659
+ break;
660
+ }
661
+ }
662
+ if (!chosen && !ambiguous) {
663
+ const leftovers = Array.from(remaining);
664
+ if (leftovers.length === 1) {
665
+ chosen = leftovers[0];
666
+ } else if (leftovers.length > 1) {
667
+ ambiguous = true;
668
+ }
669
+ }
670
+ } else if (hits.length === 1) {
671
+ chosen = hits[0];
672
+ } else {
673
+ ambiguous = true;
674
+ }
675
+ }
676
+ if (chosen) {
677
+ for (const a of entry.anchors) {
678
+ a.setAttribute("href", chosen.href);
679
+ if (chosen.cite) a.setAttribute("data-cite", chosen.cite);
680
+ }
681
+ } else if (ambiguous) {
682
+ warnings.push(`Ambiguous xref: "${entry.term}"`);
683
+ } else {
684
+ warnings.push(`No matching xref: "${entry.term}"`);
685
+ }
590
686
  }
591
- return warnings;
687
+ return { warnings };
592
688
  }
593
689
  };
594
690
 
@@ -629,11 +725,12 @@ function idForRef(id) {
629
725
  return `bib-${id.toLowerCase()}`;
630
726
  }
631
727
  var referencesPass = {
632
- async run(root, options) {
728
+ area: "references",
729
+ async run(root, _data, options) {
633
730
  const warnings = [];
634
731
  const biblio = options.biblio?.entries ?? {};
635
732
  const cites = Array.from(root.querySelectorAll("a[data-spec]"));
636
- if (!cites.length) return warnings;
733
+ if (!cites.length) return { warnings };
637
734
  const normative = /* @__PURE__ */ new Set();
638
735
  const informative = /* @__PURE__ */ new Set();
639
736
  for (const a of cites) {
@@ -670,7 +767,7 @@ var referencesPass = {
670
767
  a.setAttribute("href", `#${targetId}`);
671
768
  a.setAttribute("data-cite-ref", targetId);
672
769
  }
673
- return warnings;
770
+ return { warnings };
674
771
  }
675
772
  };
676
773
 
@@ -689,9 +786,10 @@ function createSection(doc, id, title, content) {
689
786
  return sec;
690
787
  }
691
788
  var boilerplatePass = {
692
- async run(root, options) {
789
+ area: "boilerplate",
790
+ async run(root, _data, options) {
693
791
  const bp = options.boilerplate;
694
- if (!bp) return [];
792
+ if (!bp) return { warnings: [] };
695
793
  const doc = root.ownerDocument;
696
794
  const mountMode = bp.mount || "end";
697
795
  let ref = null;
@@ -724,20 +822,21 @@ var boilerplatePass = {
724
822
  const sec = createSection(doc, id, title, content);
725
823
  insert(sec);
726
824
  }
727
- return [];
825
+ return { warnings: [] };
728
826
  }
729
827
  };
730
828
 
731
829
  // src/pipeline/passes/toc.ts
732
830
  var tocPass = {
733
- async run(root, options) {
831
+ area: "toc",
832
+ async run(root, _data, options) {
734
833
  const { toc } = options;
735
- if (toc?.enabled === false) return [];
834
+ if (toc?.enabled === false) return { warnings: [] };
736
835
  const selector = toc?.selector ?? "#toc";
737
836
  const mount = root.querySelector(selector);
738
- if (!mount) return [];
837
+ if (!mount) return { warnings: [] };
739
838
  const headings = Array.from(root.querySelectorAll("h2, h3"));
740
- if (!headings.length) return [];
839
+ if (!headings.length) return { warnings: [] };
741
840
  const doc = root.ownerDocument;
742
841
  const ol = doc.createElement("ol");
743
842
  ol.setAttribute("role", "list");
@@ -751,13 +850,14 @@ var tocPass = {
751
850
  }
752
851
  mount.innerHTML = "";
753
852
  mount.appendChild(ol);
754
- return [];
853
+ return { warnings: [] };
755
854
  }
756
855
  };
757
856
 
758
857
  // src/pipeline/passes/diagnostics.ts
759
858
  var diagnosticsPass = {
760
- async run(root, options) {
859
+ area: "diagnostics",
860
+ async run(root, _data, options) {
761
861
  const warnings = [];
762
862
  const suppressClass = options.diagnostics?.suppressClass ?? "no-link-warnings";
763
863
  const idsAndLinks = options.diagnostics?.idsAndLinks ?? true;
@@ -785,7 +885,7 @@ var diagnosticsPass = {
785
885
  }
786
886
  });
787
887
  }
788
- return warnings;
888
+ return { warnings };
789
889
  }
790
890
  };
791
891
 
@@ -799,6 +899,14 @@ var Speculator = class {
799
899
  this.formatProcessor = options.formatProcessor || new FormatProcessor(markdownOptions);
800
900
  this.includeProcessor = options.includeProcessor || new IncludeProcessor(baseUrl, fileLoader, this.formatProcessor);
801
901
  this.htmlRenderer = options.htmlRenderer || new DOMHtmlRenderer();
902
+ this.postprocessor = new Postprocessor([
903
+ idlPass,
904
+ xrefPass,
905
+ referencesPass,
906
+ boilerplatePass,
907
+ tocPass,
908
+ diagnosticsPass
909
+ ]);
802
910
  }
803
911
  /**
804
912
  * Process a single DOM element
@@ -866,16 +974,20 @@ var Speculator = class {
866
974
  section.parentNode.replaceChild(processedElements[index], section);
867
975
  }
868
976
  });
869
- const passes = [
870
- idlPass,
871
- xrefPass,
872
- referencesPass,
873
- boilerplatePass,
874
- tocPass,
875
- diagnosticsPass
876
- ];
877
977
  try {
878
- const { warnings } = await postprocess(container, passes, this.postprocessOptions || {});
978
+ const areas = [
979
+ "idl",
980
+ "xref",
981
+ "references",
982
+ "boilerplate",
983
+ "toc",
984
+ "diagnostics"
985
+ ];
986
+ const { warnings } = await this.postprocessor.run(
987
+ container,
988
+ areas,
989
+ this.postprocessOptions || {}
990
+ );
879
991
  allWarnings.push(...warnings);
880
992
  } catch (e) {
881
993
  allWarnings.push(`Postprocess failed: ${e instanceof Error ? e.message : "Unknown error"}`);
@@ -886,10 +998,63 @@ var Speculator = class {
886
998
  /**
887
999
  * Process HTML string and return processed HTML
888
1000
  */
889
- async renderHTML(html) {
890
- const container = this.htmlRenderer.parse(html);
891
- await this.renderDocument(container);
892
- return this.htmlRenderer.serialize(container);
1001
+ async renderHTML(inputHtml) {
1002
+ const container = this.htmlRenderer.parse(inputHtml);
1003
+ const result = await this.renderDocument(container);
1004
+ const html = this.htmlRenderer.serialize(result.element);
1005
+ return { html, warnings: result.warnings, stats: result.stats };
1006
+ }
1007
+ };
1008
+
1009
+ // src/utils/respec-xref-resolver.ts
1010
+ function groupQueriesBySpec(queries) {
1011
+ const bySpec = /* @__PURE__ */ new Map();
1012
+ for (const q of queries) {
1013
+ const key = (q.specs || []).slice().sort().join(",");
1014
+ const arr = bySpec.get(key) || [];
1015
+ arr.push(q);
1016
+ bySpec.set(key, arr);
1017
+ }
1018
+ return bySpec;
1019
+ }
1020
+ function buildXrefURL(endpoint, specKey, qs) {
1021
+ const params = new URLSearchParams();
1022
+ for (const q of qs) {
1023
+ params.append("terms", q.term);
1024
+ }
1025
+ if (specKey) params.set("cite", specKey);
1026
+ return `${endpoint}?${params.toString()}`;
1027
+ }
1028
+ function mapXrefResults(data, q) {
1029
+ const list = data[q.term.toLowerCase()] || [];
1030
+ return list.map((item) => ({
1031
+ href: item.uri || item.url || item.href,
1032
+ text: item.title || item.term || item.text,
1033
+ cite: item.spec || item.shortname
1034
+ })).filter((r) => !!r.href);
1035
+ }
1036
+ var RespecXrefResolver = class {
1037
+ constructor(fetchFn = fetch, endpoint = "https://respec.org/xref") {
1038
+ this.fetchFn = fetchFn;
1039
+ this.endpoint = endpoint;
1040
+ }
1041
+ async resolveBatch(queries) {
1042
+ const results = /* @__PURE__ */ new Map();
1043
+ for (const [specKey, qs] of groupQueriesBySpec(queries)) {
1044
+ const url = buildXrefURL(this.endpoint, specKey, qs);
1045
+ try {
1046
+ const resp = await this.fetchFn(url);
1047
+ const data = await resp.json();
1048
+ for (const q of qs) {
1049
+ results.set(q.id || q.term, mapXrefResults(data, q));
1050
+ }
1051
+ } catch {
1052
+ for (const q of qs) {
1053
+ results.set(q.id || q.term, []);
1054
+ }
1055
+ }
1056
+ }
1057
+ return results;
893
1058
  }
894
1059
  };
895
1060
  // Annotate the CommonJS export names for ESM import in node:
@@ -897,6 +1062,7 @@ var Speculator = class {
897
1062
  DOMHtmlRenderer,
898
1063
  FormatProcessor,
899
1064
  IncludeProcessor,
1065
+ RespecXrefResolver,
900
1066
  Speculator,
901
1067
  SpeculatorError,
902
1068
  browserFileLoader,
@@ -88,6 +88,14 @@ interface ProcessingResult {
88
88
  /** Processing statistics */
89
89
  stats: ProcessingStats;
90
90
  }
91
+ interface HtmlProcessingResult {
92
+ /** The processed HTML element */
93
+ html: string;
94
+ /** Any warnings encountered during processing */
95
+ warnings: string[];
96
+ /** Processing statistics */
97
+ stats: ProcessingStats;
98
+ }
91
99
  /**
92
100
  * Processing statistics
93
101
  */
@@ -114,7 +122,13 @@ declare class SpeculatorError extends Error {
114
122
  constructor(message: string, element?: Element | undefined, path?: string | undefined);
115
123
  }
116
124
  type XrefQuery = {
125
+ /** Unique identifier returned with results (internal use). */
126
+ id?: string;
127
+ /** The term to resolve. */
117
128
  term: string;
129
+ /** Optional list of spec shortnames to constrain the search. */
130
+ specs?: string[];
131
+ /** Additional context for the lookup (unused for now). */
118
132
  context?: string;
119
133
  };
120
134
  type XrefResult = {
@@ -123,11 +137,15 @@ type XrefResult = {
123
137
  cite?: string;
124
138
  };
125
139
  interface XrefResolver {
126
- resolveBatch(queries: XrefQuery[], specs?: string[]): Promise<Map<string, XrefResult>>;
140
+ /**
141
+ * Resolve a batch of xref queries. The returned map is keyed by the query's
142
+ * `id` if provided, otherwise by the query term.
143
+ */
144
+ resolveBatch(queries: XrefQuery[]): Promise<Map<string, XrefResult[]>>;
127
145
  }
128
146
  interface XrefOptions {
129
147
  specs?: string[];
130
- resolver?: XrefResolver;
148
+ resolver: XrefResolver;
131
149
  }
132
150
  interface BiblioEntry {
133
151
  id: string;
@@ -149,6 +167,12 @@ interface TocOptions {
149
167
  selector?: string;
150
168
  enabled?: boolean;
151
169
  }
170
+ interface DiagnosticsOptions {
171
+ /** Suppress link warnings within elements having this class. */
172
+ suppressClass?: string;
173
+ /** Enable duplicate-id and missing-href checks (default true). */
174
+ idsAndLinks?: boolean;
175
+ }
152
176
  interface BoilerplateOptions {
153
177
  conformance?: boolean | {
154
178
  title?: string;
@@ -167,24 +191,8 @@ interface BoilerplateOptions {
167
191
  };
168
192
  mount?: 'end' | 'before-references' | 'after-toc';
169
193
  }
170
- interface DiagnosticsOptions {
171
- /** Suppress link warnings within elements having this class. */
172
- suppressClass?: string;
173
- }
174
- interface DiagnosticsOptions {
175
- suppressClass?: string;
176
- /** Enable duplicate-id and missing-href checks (default true). */
177
- idsAndLinks?: boolean;
178
- }
179
194
  interface PostprocessOptions {
180
- xref?: XrefOptions;
181
- biblio?: BiblioOptions;
182
- idl?: IdlOptions;
183
- toc?: TocOptions;
184
- diagnostics?: DiagnosticsOptions;
185
- }
186
- interface PostprocessOptions {
187
- xref?: XrefOptions;
195
+ xref?: XrefOptions | XrefOptions[];
188
196
  biblio?: BiblioOptions;
189
197
  idl?: IdlOptions;
190
198
  toc?: TocOptions;
@@ -200,6 +208,7 @@ declare class Speculator {
200
208
  private readonly formatProcessor;
201
209
  private readonly htmlRenderer;
202
210
  private readonly postprocessOptions;
211
+ private readonly postprocessor;
203
212
  constructor(options?: SpeculatorOptions);
204
213
  /**
205
214
  * Process a single DOM element
@@ -212,7 +221,7 @@ declare class Speculator {
212
221
  /**
213
222
  * Process HTML string and return processed HTML
214
223
  */
215
- renderHTML(html: string): Promise<string>;
224
+ renderHTML(inputHtml: string): Promise<HtmlProcessingResult>;
216
225
  }
217
226
 
218
227
  declare function createMarkdownRenderer(options?: MarkdownOptions): MarkdownIt;
@@ -235,4 +244,16 @@ declare function createFallbackFileLoader(loaders: FileLoader[]): FileLoader;
235
244
  */
236
245
  declare function getDefaultFileLoader(): FileLoader;
237
246
 
238
- export { DOMHtmlRenderer, type DataFormat, type FileLoader, FormatProcessor, type HtmlRenderer, IncludeProcessor, type MarkdownOptions, type ProcessingResult, type ProcessingStats, Speculator, SpeculatorError, type SpeculatorOptions, browserFileLoader, createFallbackFileLoader, createMarkdownRenderer, getDefaultFileLoader, nodeFileLoader, parseMarkdown };
247
+ /**
248
+ * Xref resolver that queries the public ReSpec xref database.
249
+ *
250
+ * The fetch implementation is injectable to allow easy testing.
251
+ */
252
+ declare class RespecXrefResolver implements XrefResolver {
253
+ private readonly fetchFn;
254
+ private readonly endpoint;
255
+ constructor(fetchFn?: typeof fetch, endpoint?: string);
256
+ resolveBatch(queries: XrefQuery[]): Promise<Map<string, XrefResult[]>>;
257
+ }
258
+
259
+ export { DOMHtmlRenderer, type DataFormat, type FileLoader, FormatProcessor, type HtmlRenderer, IncludeProcessor, type MarkdownOptions, type ProcessingResult, type ProcessingStats, RespecXrefResolver, Speculator, SpeculatorError, type SpeculatorOptions, browserFileLoader, createFallbackFileLoader, createMarkdownRenderer, getDefaultFileLoader, nodeFileLoader, parseMarkdown };
package/dist/browser.d.ts CHANGED
@@ -88,6 +88,14 @@ interface ProcessingResult {
88
88
  /** Processing statistics */
89
89
  stats: ProcessingStats;
90
90
  }
91
+ interface HtmlProcessingResult {
92
+ /** The processed HTML element */
93
+ html: string;
94
+ /** Any warnings encountered during processing */
95
+ warnings: string[];
96
+ /** Processing statistics */
97
+ stats: ProcessingStats;
98
+ }
91
99
  /**
92
100
  * Processing statistics
93
101
  */
@@ -114,7 +122,13 @@ declare class SpeculatorError extends Error {
114
122
  constructor(message: string, element?: Element | undefined, path?: string | undefined);
115
123
  }
116
124
  type XrefQuery = {
125
+ /** Unique identifier returned with results (internal use). */
126
+ id?: string;
127
+ /** The term to resolve. */
117
128
  term: string;
129
+ /** Optional list of spec shortnames to constrain the search. */
130
+ specs?: string[];
131
+ /** Additional context for the lookup (unused for now). */
118
132
  context?: string;
119
133
  };
120
134
  type XrefResult = {
@@ -123,11 +137,15 @@ type XrefResult = {
123
137
  cite?: string;
124
138
  };
125
139
  interface XrefResolver {
126
- resolveBatch(queries: XrefQuery[], specs?: string[]): Promise<Map<string, XrefResult>>;
140
+ /**
141
+ * Resolve a batch of xref queries. The returned map is keyed by the query's
142
+ * `id` if provided, otherwise by the query term.
143
+ */
144
+ resolveBatch(queries: XrefQuery[]): Promise<Map<string, XrefResult[]>>;
127
145
  }
128
146
  interface XrefOptions {
129
147
  specs?: string[];
130
- resolver?: XrefResolver;
148
+ resolver: XrefResolver;
131
149
  }
132
150
  interface BiblioEntry {
133
151
  id: string;
@@ -149,6 +167,12 @@ interface TocOptions {
149
167
  selector?: string;
150
168
  enabled?: boolean;
151
169
  }
170
+ interface DiagnosticsOptions {
171
+ /** Suppress link warnings within elements having this class. */
172
+ suppressClass?: string;
173
+ /** Enable duplicate-id and missing-href checks (default true). */
174
+ idsAndLinks?: boolean;
175
+ }
152
176
  interface BoilerplateOptions {
153
177
  conformance?: boolean | {
154
178
  title?: string;
@@ -167,24 +191,8 @@ interface BoilerplateOptions {
167
191
  };
168
192
  mount?: 'end' | 'before-references' | 'after-toc';
169
193
  }
170
- interface DiagnosticsOptions {
171
- /** Suppress link warnings within elements having this class. */
172
- suppressClass?: string;
173
- }
174
- interface DiagnosticsOptions {
175
- suppressClass?: string;
176
- /** Enable duplicate-id and missing-href checks (default true). */
177
- idsAndLinks?: boolean;
178
- }
179
194
  interface PostprocessOptions {
180
- xref?: XrefOptions;
181
- biblio?: BiblioOptions;
182
- idl?: IdlOptions;
183
- toc?: TocOptions;
184
- diagnostics?: DiagnosticsOptions;
185
- }
186
- interface PostprocessOptions {
187
- xref?: XrefOptions;
195
+ xref?: XrefOptions | XrefOptions[];
188
196
  biblio?: BiblioOptions;
189
197
  idl?: IdlOptions;
190
198
  toc?: TocOptions;
@@ -200,6 +208,7 @@ declare class Speculator {
200
208
  private readonly formatProcessor;
201
209
  private readonly htmlRenderer;
202
210
  private readonly postprocessOptions;
211
+ private readonly postprocessor;
203
212
  constructor(options?: SpeculatorOptions);
204
213
  /**
205
214
  * Process a single DOM element
@@ -212,7 +221,7 @@ declare class Speculator {
212
221
  /**
213
222
  * Process HTML string and return processed HTML
214
223
  */
215
- renderHTML(html: string): Promise<string>;
224
+ renderHTML(inputHtml: string): Promise<HtmlProcessingResult>;
216
225
  }
217
226
 
218
227
  declare function createMarkdownRenderer(options?: MarkdownOptions): MarkdownIt;
@@ -235,4 +244,16 @@ declare function createFallbackFileLoader(loaders: FileLoader[]): FileLoader;
235
244
  */
236
245
  declare function getDefaultFileLoader(): FileLoader;
237
246
 
238
- export { DOMHtmlRenderer, type DataFormat, type FileLoader, FormatProcessor, type HtmlRenderer, IncludeProcessor, type MarkdownOptions, type ProcessingResult, type ProcessingStats, Speculator, SpeculatorError, type SpeculatorOptions, browserFileLoader, createFallbackFileLoader, createMarkdownRenderer, getDefaultFileLoader, nodeFileLoader, parseMarkdown };
247
+ /**
248
+ * Xref resolver that queries the public ReSpec xref database.
249
+ *
250
+ * The fetch implementation is injectable to allow easy testing.
251
+ */
252
+ declare class RespecXrefResolver implements XrefResolver {
253
+ private readonly fetchFn;
254
+ private readonly endpoint;
255
+ constructor(fetchFn?: typeof fetch, endpoint?: string);
256
+ resolveBatch(queries: XrefQuery[]): Promise<Map<string, XrefResult[]>>;
257
+ }
258
+
259
+ export { DOMHtmlRenderer, type DataFormat, type FileLoader, FormatProcessor, type HtmlRenderer, IncludeProcessor, type MarkdownOptions, type ProcessingResult, type ProcessingStats, RespecXrefResolver, Speculator, SpeculatorError, type SpeculatorOptions, browserFileLoader, createFallbackFileLoader, createMarkdownRenderer, getDefaultFileLoader, nodeFileLoader, parseMarkdown };
package/dist/browser.js CHANGED
@@ -2,6 +2,7 @@ import {
2
2
  DOMHtmlRenderer,
3
3
  FormatProcessor,
4
4
  IncludeProcessor,
5
+ RespecXrefResolver,
5
6
  Speculator,
6
7
  SpeculatorError,
7
8
  browserFileLoader,
@@ -10,11 +11,12 @@ import {
10
11
  getDefaultFileLoader,
11
12
  nodeFileLoader,
12
13
  parseMarkdown
13
- } from "./chunk-JAR5PGCK.js";
14
+ } from "./chunk-U4GQ6EOV.js";
14
15
  export {
15
16
  DOMHtmlRenderer,
16
17
  FormatProcessor,
17
18
  IncludeProcessor,
19
+ RespecXrefResolver,
18
20
  Speculator,
19
21
  SpeculatorError,
20
22
  browserFileLoader,