@contractkit/explorer-ui 0.1.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/dist/index.js ADDED
@@ -0,0 +1,1359 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/html.ts
5
+ var RAW = /* @__PURE__ */ Symbol("raw");
6
+ function raw(value) {
7
+ return {
8
+ [RAW]: true,
9
+ value
10
+ };
11
+ }
12
+ __name(raw, "raw");
13
+ function isRaw(value) {
14
+ return typeof value === "object" && value !== null && value[RAW] === true;
15
+ }
16
+ __name(isRaw, "isRaw");
17
+ function escapeHtml(value) {
18
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
19
+ }
20
+ __name(escapeHtml, "escapeHtml");
21
+ function renderValue(value) {
22
+ if (value === null || value === void 0 || value === false) return "";
23
+ if (isRaw(value)) return value.value;
24
+ if (Array.isArray(value)) return value.map(renderValue).join("");
25
+ return escapeHtml(String(value));
26
+ }
27
+ __name(renderValue, "renderValue");
28
+ function html(strings, ...values) {
29
+ let out = strings[0] ?? "";
30
+ for (let i = 0; i < values.length; i++) {
31
+ out += renderValue(values[i]) + (strings[i + 1] ?? "");
32
+ }
33
+ return out;
34
+ }
35
+ __name(html, "html");
36
+ function slug(value) {
37
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
38
+ }
39
+ __name(slug, "slug");
40
+
41
+ // src/markdown.ts
42
+ function renderMarkdown(input) {
43
+ if (!input) return "";
44
+ const lines = input.replace(/\r\n/g, "\n").split("\n");
45
+ const out = [];
46
+ let i = 0;
47
+ while (i < lines.length) {
48
+ const line = lines[i] ?? "";
49
+ const fence = /^```(\w*)\s*$/.exec(line);
50
+ if (fence) {
51
+ const lang = fence[1] ?? "";
52
+ const buf2 = [];
53
+ i++;
54
+ while (i < lines.length && !/^```\s*$/.test(lines[i] ?? "")) {
55
+ buf2.push(lines[i] ?? "");
56
+ i++;
57
+ }
58
+ i++;
59
+ const langAttr = lang ? ` data-lang="${escapeHtml(lang)}"` : "";
60
+ out.push(`<pre class="ce-code"${langAttr}><code>${escapeHtml(buf2.join("\n"))}</code></pre>`);
61
+ continue;
62
+ }
63
+ const heading = /^(#{1,6})\s+(.+)$/.exec(line);
64
+ if (heading) {
65
+ const level = heading[1].length;
66
+ out.push(`<h${level}>${renderInline(heading[2])}</h${level}>`);
67
+ i++;
68
+ continue;
69
+ }
70
+ if (/^\s*[-*]\s+/.test(line)) {
71
+ const items = [];
72
+ while (i < lines.length && /^\s*[-*]\s+/.test(lines[i] ?? "")) {
73
+ items.push(renderInline((lines[i] ?? "").replace(/^\s*[-*]\s+/, "")));
74
+ i++;
75
+ }
76
+ out.push(`<ul>${items.map((t) => `<li>${t}</li>`).join("")}</ul>`);
77
+ continue;
78
+ }
79
+ if (/^\s*\d+\.\s+/.test(line)) {
80
+ const items = [];
81
+ while (i < lines.length && /^\s*\d+\.\s+/.test(lines[i] ?? "")) {
82
+ items.push(renderInline((lines[i] ?? "").replace(/^\s*\d+\.\s+/, "")));
83
+ i++;
84
+ }
85
+ out.push(`<ol>${items.map((t) => `<li>${t}</li>`).join("")}</ol>`);
86
+ continue;
87
+ }
88
+ if (line.trim() === "") {
89
+ i++;
90
+ continue;
91
+ }
92
+ const buf = [
93
+ line
94
+ ];
95
+ i++;
96
+ while (i < lines.length) {
97
+ const next = lines[i] ?? "";
98
+ if (next.trim() === "" || /^```/.test(next) || /^#{1,6}\s/.test(next) || /^\s*[-*]\s/.test(next) || /^\s*\d+\.\s/.test(next)) break;
99
+ buf.push(next);
100
+ i++;
101
+ }
102
+ out.push(`<p>${renderInline(buf.join(" "))}</p>`);
103
+ }
104
+ return out.join("\n");
105
+ }
106
+ __name(renderMarkdown, "renderMarkdown");
107
+ function renderInline(text) {
108
+ let s = escapeHtml(text);
109
+ s = s.replace(/`([^`]+)`/g, (_m, code) => `<code>${code}</code>`);
110
+ s = s.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
111
+ s = s.replace(/(^|[^*])\*([^*\s][^*]*?)\*(?!\*)/g, (_m, before, body) => `${before}<em>${body}</em>`);
112
+ s = s.replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g, (_m, label, url) => {
113
+ return `<a href="${url}" rel="noopener noreferrer">${label}</a>`;
114
+ });
115
+ return s;
116
+ }
117
+ __name(renderInline, "renderInline");
118
+
119
+ // src/render-code-samples.ts
120
+ import { resolveEffectiveFields } from "@contractkit/core/type-utils";
121
+ import { faker } from "@faker-js/faker";
122
+ function renderCodeSamples(op, baseUrl, ctx = {}) {
123
+ const curl = renderCurlSample(op, baseUrl, ctx);
124
+ const responseExample = renderResponseExample(op, ctx);
125
+ return `<section class="ce-rail-section">
126
+ <h4 class="ce-rail-heading">Request Sample</h4>
127
+ <pre class="ce-code ce-code-dark">${curl}</pre>
128
+ </section>
129
+ ${responseExample}`;
130
+ }
131
+ __name(renderCodeSamples, "renderCodeSamples");
132
+ function renderCurlSample(op, baseUrl, ctx) {
133
+ const cleanBase = baseUrl ? baseUrl.replace(/\/$/, "") : "https://api.example.com";
134
+ const url = `${cleanBase}${op.routePath}`;
135
+ const lines = [];
136
+ lines.push(`curl --request ${op.method.toUpperCase()} \\`);
137
+ lines.push(` --url ${url}`);
138
+ const hasJsonResponse = op.op.responses.some((r) => r.contentType?.includes("json"));
139
+ const jsonBody = op.op.request?.bodies.find((b) => b.contentType === "application/json" || b.contentType.endsWith("+json"));
140
+ if (hasJsonResponse) {
141
+ lines[lines.length - 1] += " \\";
142
+ lines.push(` --header 'Accept: application/json'`);
143
+ }
144
+ if (jsonBody) {
145
+ lines[lines.length - 1] += " \\";
146
+ lines.push(` --header 'Content-Type: application/json'`);
147
+ const sample = buildSampleJson(jsonBody.bodyType, ctx, `${op.method}:${op.routePath}:request-body`, "readonly");
148
+ const bodyJson = JSON.stringify(sample, null, 2).replace(/'/g, `'\\''`);
149
+ lines[lines.length - 1] += " \\";
150
+ lines.push(` --data '${bodyJson}'`);
151
+ }
152
+ return escapeHtml(lines.join("\n"));
153
+ }
154
+ __name(renderCurlSample, "renderCurlSample");
155
+ function renderResponseExample(op, ctx) {
156
+ const primary = op.op.responses.find((r) => r.statusCode >= 200 && r.statusCode < 300 && r.bodyType !== void 0 && r.contentType !== void 0 && r.contentType.includes("json"));
157
+ if (!primary || !primary.bodyType) return "";
158
+ faker.seed(hashString(`${op.method}:${op.routePath}:${primary.statusCode}`));
159
+ const sample = jsonSample(primary.bodyType, ctx, /* @__PURE__ */ new Set(), void 0, "writeonly");
160
+ const formatted = JSON.stringify(sample, null, 2);
161
+ return `<section class="ce-rail-section">
162
+ <h4 class="ce-rail-heading">Response Example</h4>
163
+ <pre class="ce-code ce-code-dark">${escapeHtml(formatted)}</pre>
164
+ </section>`;
165
+ }
166
+ __name(renderResponseExample, "renderResponseExample");
167
+ function jsonSample(type, ctx, visited, fieldName, exclude) {
168
+ switch (type.kind) {
169
+ case "scalar":
170
+ return scalarSample(type, fieldName);
171
+ case "literal":
172
+ return type.value;
173
+ case "enum":
174
+ return type.values.length > 0 ? faker.helpers.arrayElement(type.values) : null;
175
+ case "array": {
176
+ const len = faker.number.int({
177
+ min: 1,
178
+ max: 3
179
+ });
180
+ return Array.from({
181
+ length: len
182
+ }, () => jsonSample(type.item, ctx, visited, fieldName, exclude));
183
+ }
184
+ case "tuple":
185
+ return type.items.map((t) => jsonSample(t, ctx, visited, fieldName, exclude));
186
+ case "record":
187
+ return {
188
+ key: jsonSample(type.value, ctx, visited, void 0, exclude)
189
+ };
190
+ case "union":
191
+ case "discriminatedUnion": {
192
+ const first = type.members[0];
193
+ return first ? jsonSample(first, ctx, visited, fieldName, exclude) : null;
194
+ }
195
+ case "intersection": {
196
+ const resolved = resolveEffectiveFields(type, modelIndexFromCtx(ctx));
197
+ if (resolved.fields.length > 0) {
198
+ const out = {};
199
+ for (const f of resolved.fields) {
200
+ if (f.visibility === exclude) continue;
201
+ out[f.name] = jsonSample(f.type, ctx, visited, f.name, exclude);
202
+ }
203
+ return out;
204
+ }
205
+ const first = type.members[0];
206
+ return first ? jsonSample(first, ctx, visited, fieldName, exclude) : null;
207
+ }
208
+ case "lazy":
209
+ return jsonSample(type.inner, ctx, visited, fieldName, exclude);
210
+ case "inlineObject": {
211
+ const out = {};
212
+ for (const f of type.fields) {
213
+ if (f.visibility === exclude) continue;
214
+ out[f.name] = jsonSample(f.type, ctx, visited, f.name, exclude);
215
+ }
216
+ return out;
217
+ }
218
+ case "ref": {
219
+ if (visited.has(type.name)) return null;
220
+ const entry = ctx.models?.get(type.name);
221
+ if (!entry) return null;
222
+ const next = /* @__PURE__ */ new Set([
223
+ ...visited,
224
+ type.name
225
+ ]);
226
+ const resolved = resolveEffectiveFields(type.name, modelIndexFromCtx(ctx));
227
+ if (resolved.fields.length > 0) {
228
+ const out = {};
229
+ for (const f of resolved.fields) {
230
+ if (f.visibility === exclude) continue;
231
+ out[f.name] = jsonSample(f.type, ctx, next, f.name, exclude);
232
+ }
233
+ return out;
234
+ }
235
+ const model = entry.model;
236
+ if (model.type) return jsonSample(model.type, ctx, next, fieldName, exclude);
237
+ return null;
238
+ }
239
+ }
240
+ }
241
+ __name(jsonSample, "jsonSample");
242
+ function buildSampleJson(type, ctx, seed, exclude = "writeonly") {
243
+ faker.seed(hashString(seed));
244
+ return jsonSample(type, ctx, /* @__PURE__ */ new Set(), void 0, exclude);
245
+ }
246
+ __name(buildSampleJson, "buildSampleJson");
247
+ function scalarSample(s, fieldName) {
248
+ switch (s.name) {
249
+ case "boolean":
250
+ return faker.datatype.boolean();
251
+ case "number":
252
+ case "int":
253
+ case "bigint":
254
+ return fakerNumber(fieldName, s);
255
+ case "uuid":
256
+ return faker.string.uuid();
257
+ case "datetime":
258
+ return faker.date.recent({
259
+ days: 90
260
+ }).toISOString();
261
+ case "date":
262
+ return faker.date.recent({
263
+ days: 90
264
+ }).toISOString().slice(0, 10);
265
+ case "time":
266
+ return faker.date.recent({
267
+ days: 1
268
+ }).toISOString().slice(11, 19);
269
+ case "interval":
270
+ return "P1D";
271
+ case "email":
272
+ return faker.internet.email().toLowerCase();
273
+ case "url":
274
+ return faker.internet.url();
275
+ case "string":
276
+ return fakerString(fieldName);
277
+ default:
278
+ return fakerString(fieldName);
279
+ }
280
+ }
281
+ __name(scalarSample, "scalarSample");
282
+ function fakerString(fieldName) {
283
+ const name = (fieldName ?? "").toLowerCase();
284
+ if (/email/.test(name)) return faker.internet.email().toLowerCase();
285
+ if (/^url$|link|href|website/.test(name)) return faker.internet.url();
286
+ if (/^id$|_id$|guid|uuid/.test(name)) return faker.string.uuid();
287
+ if (/at$|_at$|date|time/.test(name)) return faker.date.recent({
288
+ days: 90
289
+ }).toISOString();
290
+ if (/firstname|first_name/.test(name)) return faker.person.firstName();
291
+ if (/lastname|last_name|surname/.test(name)) return faker.person.lastName();
292
+ if (/^name$|fullname|full_name|displayname|display_name/.test(name)) return faker.person.fullName();
293
+ if (/username|handle/.test(name)) return faker.internet.username();
294
+ if (/avatar|image|photo|picture/.test(name)) return faker.image.url();
295
+ if (/street|address/.test(name)) return faker.location.streetAddress();
296
+ if (/city/.test(name)) return faker.location.city();
297
+ if (/country/.test(name)) return faker.location.country();
298
+ if (/zip|postal/.test(name)) return faker.location.zipCode();
299
+ if (/state\b|region/.test(name)) return faker.location.state();
300
+ if (/company|organization|org\b/.test(name)) return faker.company.name();
301
+ if (/title|product|item/.test(name)) return faker.commerce.productName();
302
+ if (/color|colour/.test(name)) return faker.color.human();
303
+ if (/description|summary|bio|notes|message|content/.test(name)) return faker.lorem.sentence();
304
+ if (/phone/.test(name)) return faker.phone.number();
305
+ if (/password/.test(name)) return faker.internet.password();
306
+ if (/token|key|secret|apikey/.test(name)) return faker.string.alphanumeric(32);
307
+ if (/status|state/.test(name)) return faker.helpers.arrayElement([
308
+ "active",
309
+ "inactive",
310
+ "pending",
311
+ "archived",
312
+ "draft"
313
+ ]);
314
+ if (/currency/.test(name)) return faker.finance.currencyCode();
315
+ if (/locale|language/.test(name)) return faker.helpers.arrayElement([
316
+ "en-US",
317
+ "en-GB",
318
+ "de-DE",
319
+ "fr-FR",
320
+ "ja-JP",
321
+ "es-ES"
322
+ ]);
323
+ if (/slug/.test(name)) return faker.lorem.slug();
324
+ return faker.lorem.word();
325
+ }
326
+ __name(fakerString, "fakerString");
327
+ function fakerNumber(fieldName, s) {
328
+ const name = (fieldName ?? "").toLowerCase();
329
+ if (/amount|price|cost|total|balance|fee|salary|revenue/.test(name)) {
330
+ return Number(faker.finance.amount({
331
+ min: 1,
332
+ max: 9999,
333
+ dec: 2
334
+ }));
335
+ }
336
+ if (/^age$/.test(name)) return faker.number.int({
337
+ min: 18,
338
+ max: 80
339
+ });
340
+ if (/year/.test(name)) return faker.number.int({
341
+ min: 2e3,
342
+ max: 2024
343
+ });
344
+ if (/month/.test(name)) return faker.number.int({
345
+ min: 1,
346
+ max: 12
347
+ });
348
+ if (/day/.test(name)) return faker.number.int({
349
+ min: 1,
350
+ max: 28
351
+ });
352
+ if (/count|quantity|qty/.test(name)) return faker.number.int({
353
+ min: 1,
354
+ max: 100
355
+ });
356
+ if (/percent|pct|ratio/.test(name)) return faker.number.int({
357
+ min: 0,
358
+ max: 100
359
+ });
360
+ if (/latitude|lat\b/.test(name)) return Number(faker.location.latitude().toFixed(2));
361
+ if (/longitude|lng\b|lon\b/.test(name)) return Number(faker.location.longitude().toFixed(2));
362
+ const lo = toFiniteNumber(s.min, 0);
363
+ const hi = toFiniteNumber(s.max, Math.max(lo + 1, 999));
364
+ if (s.name === "int" || s.name === "bigint") {
365
+ return faker.number.int({
366
+ min: lo,
367
+ max: hi
368
+ });
369
+ }
370
+ return Number(faker.number.float({
371
+ min: lo,
372
+ max: hi,
373
+ fractionDigits: 2
374
+ }));
375
+ }
376
+ __name(fakerNumber, "fakerNumber");
377
+ function toFiniteNumber(value, fallback) {
378
+ if (typeof value === "number") return Number.isFinite(value) ? Math.floor(value) : fallback;
379
+ if (typeof value === "bigint") return Number(value);
380
+ if (typeof value === "string") {
381
+ const n = Number(value);
382
+ return Number.isFinite(n) ? Math.floor(n) : fallback;
383
+ }
384
+ return fallback;
385
+ }
386
+ __name(toFiniteNumber, "toFiniteNumber");
387
+ function modelIndexFromCtx(ctx) {
388
+ const out = /* @__PURE__ */ new Map();
389
+ if (ctx.models) {
390
+ for (const [name, resolved] of ctx.models) out.set(name, resolved.model);
391
+ }
392
+ return out;
393
+ }
394
+ __name(modelIndexFromCtx, "modelIndexFromCtx");
395
+ function hashString(s) {
396
+ let h = 5381;
397
+ for (let i = 0; i < s.length; i++) h = (h << 5) + h + s.charCodeAt(i) | 0;
398
+ return h >>> 0;
399
+ }
400
+ __name(hashString, "hashString");
401
+
402
+ // src/render-schema.ts
403
+ import { resolveEffectiveFields as resolveEffectiveFields2 } from "@contractkit/core/type-utils";
404
+
405
+ // src/constraints.ts
406
+ function constraintSummary(scalar) {
407
+ const parts = [];
408
+ if (scalar.min !== void 0) parts.push(`min=${scalar.min}`);
409
+ if (scalar.max !== void 0) parts.push(`max=${scalar.max}`);
410
+ if (scalar.len !== void 0) parts.push(`len=${scalar.len}`);
411
+ if (scalar.regex !== void 0) parts.push(`regex=/${scalar.regex}/`);
412
+ if (scalar.format !== void 0) parts.push(`format=${scalar.format}`);
413
+ return parts.length > 0 ? `(${parts.join(", ")})` : "";
414
+ }
415
+ __name(constraintSummary, "constraintSummary");
416
+
417
+ // src/render-model.ts
418
+ function modelAnchor(name) {
419
+ return `model-${encodeURIComponent(name)}`;
420
+ }
421
+ __name(modelAnchor, "modelAnchor");
422
+ function renderModel({ filePath, model }, ctx = {}) {
423
+ const badges = [];
424
+ if (model.deprecated) badges.push(badge("deprecated", "deprecated"));
425
+ if (model.mode) badges.push(badge(`mode=${model.mode}`, "mode"));
426
+ if (model.inputCase) badges.push(badge(`format(input=${model.inputCase})`, "format"));
427
+ if (model.outputCase) badges.push(badge(`format(output=${model.outputCase})`, "format"));
428
+ const fieldCtx = {
429
+ ...ctx,
430
+ visited: /* @__PURE__ */ new Set([
431
+ ...ctx.visited ?? [],
432
+ model.name
433
+ ])
434
+ };
435
+ const inheritance = model.bases && model.bases.length > 0 ? `<p class="ce-extends">extends ${model.bases.map((b) => `<a class="ce-ref" href="#model-${encodeURIComponent(b)}" data-open-model="${escapeHtml(b)}">${escapeHtml(b)}</a>`).join(", ")}</p>` : "";
436
+ const description = model.description ? `<div class="ce-description ce-markdown">${renderMarkdown(model.description)}</div>` : "";
437
+ const body = model.type ? `<div class="ce-type-alias">= ${renderType(model.type, fieldCtx)}</div>` : renderFieldRows(model.fields, fieldCtx);
438
+ return html`<section id="${raw(modelAnchor(model.name))}" class="ce-card ce-model-card">
439
+ <header class="ce-card-header">
440
+ <h2>
441
+ ${model.name}
442
+ ${raw(badges.join(""))}
443
+ <button
444
+ class="ce-jump"
445
+ data-jump-file="${filePath}"
446
+ data-jump-line="${model.loc.line}"
447
+ title="Open in editor"
448
+ type="button"
449
+ >
450
+
451
+ </button>
452
+ </h2>
453
+ </header>
454
+ ${raw(inheritance)}
455
+ ${raw(description)}
456
+ ${raw(body)}
457
+ </section>`;
458
+ }
459
+ __name(renderModel, "renderModel");
460
+ function renderFieldRows(fields, ctx = {}) {
461
+ if (fields.length === 0) return `<p class="ce-empty">No fields.</p>`;
462
+ const rows = fields.map((f) => {
463
+ const modifiers = [];
464
+ if (f.optional) modifiers.push("optional");
465
+ if (f.nullable) modifiers.push("nullable");
466
+ if (f.visibility === "readonly") modifiers.push("readonly");
467
+ if (f.visibility === "writeonly") modifiers.push("writeonly");
468
+ if (f.deprecated) modifiers.push("deprecated");
469
+ if (f.override) modifiers.push("override");
470
+ const modifierHtml = modifiers.map((m) => badge(m, m)).join("");
471
+ const defaultHtml = f.default !== void 0 ? html`<code class="ce-default">= ${String(f.default)}</code>` : "";
472
+ const descHtml = f.description ? `<div class="ce-field-desc ce-markdown">${renderMarkdown(f.description)}</div>` : "";
473
+ return `<tr>
474
+ <td class="ce-field-name"><code>${escapeHtml(f.name)}</code>${modifierHtml}</td>
475
+ <td class="ce-field-type">${renderType(f.type, ctx)}${defaultHtml}${descHtml}</td>
476
+ </tr>`;
477
+ });
478
+ return `<table class="ce-fields"><tbody>${rows.join("")}</tbody></table>`;
479
+ }
480
+ __name(renderFieldRows, "renderFieldRows");
481
+ function badge(label, kind) {
482
+ return `<span class="ce-badge ce-badge-${escapeHtml(kind)}">${escapeHtml(label)}</span>`;
483
+ }
484
+ __name(badge, "badge");
485
+
486
+ // src/render-type.ts
487
+ var DEFAULT_MAX_DEPTH = 4;
488
+ function renderType(type, ctx = {}) {
489
+ switch (type.kind) {
490
+ case "scalar": {
491
+ const constraint = constraintSummary(type);
492
+ const constraintHtml = constraint ? `<span class="ce-type-constraint">${escapeHtml(constraint)}</span>` : "";
493
+ return `<span class="ce-type-scalar">${escapeHtml(type.name)}${constraintHtml}</span>`;
494
+ }
495
+ case "enum":
496
+ return `<span class="ce-type-enum">enum(${type.values.map(escapeHtml).join(", ")})</span>`;
497
+ case "literal":
498
+ return `<span class="ce-type-literal">${escapeHtml(typeof type.value === "string" ? `"${type.value}"` : String(type.value))}</span>`;
499
+ case "ref":
500
+ return renderRef(type.name, ctx);
501
+ case "array":
502
+ return `${tok("Array&lt;")}${renderType(type.item, ctx)}${tok("&gt;")}`;
503
+ case "tuple":
504
+ return `${tok("[")}${type.items.map((t) => renderType(t, ctx)).join(tok(", "))}${tok("]")}`;
505
+ case "record":
506
+ return `${tok("Record&lt;")}${renderType(type.key, ctx)}${tok(", ")}${renderType(type.value, ctx)}${tok("&gt;")}`;
507
+ case "union":
508
+ return type.members.map((m) => renderType(m, ctx)).join(tok(" | "));
509
+ case "discriminatedUnion":
510
+ return `${tok(`Union by ${escapeHtml(type.discriminator)}:`)} ` + type.members.map((m) => renderType(m, ctx)).join(tok(" | "));
511
+ case "intersection":
512
+ return type.members.map((m) => renderType(m, ctx)).join(tok(" &amp; "));
513
+ case "lazy":
514
+ return `${tok("Lazy&lt;")}${renderType(type.inner, ctx)}${tok("&gt;")}`;
515
+ case "inlineObject":
516
+ return `<details class="ce-inline-object"><summary>${tok("{ \u2026 }")}</summary>${renderFieldRows(type.fields, ctx)}</details>`;
517
+ }
518
+ }
519
+ __name(renderType, "renderType");
520
+ function renderRef(name, ctx) {
521
+ const encName = encodeURIComponent(name);
522
+ const safeName = escapeHtml(name);
523
+ const entry = ctx.models?.get(name);
524
+ const visited = ctx.visited ?? /* @__PURE__ */ new Set();
525
+ const depth = ctx.depth ?? 0;
526
+ const maxDepth = ctx.maxDepth ?? DEFAULT_MAX_DEPTH;
527
+ if (!entry) {
528
+ return `<a class="ce-ref" href="#model-${encName}">${safeName}</a>`;
529
+ }
530
+ if (visited.has(name)) {
531
+ return `<span class="ce-ref ce-ref-cycle" title="recursive reference">${safeName} \u21BA</span>`;
532
+ }
533
+ const model = entry.model;
534
+ const jumpAttrs = `data-jump-file="${escapeHtml(entry.filePath)}" data-jump-line="${model.loc.line}"`;
535
+ if (depth >= maxDepth) {
536
+ return `<a class="ce-ref" href="#model-${encName}" ${jumpAttrs} title="Jump to source">${safeName}</a>`;
537
+ }
538
+ const nextCtx = {
539
+ models: ctx.models,
540
+ visited: /* @__PURE__ */ new Set([
541
+ ...visited,
542
+ name
543
+ ]),
544
+ maxDepth,
545
+ depth: depth + 1
546
+ };
547
+ const bases = model.bases && model.bases.length > 0 ? `<p class="ce-extends">extends ${model.bases.map((b) => renderRef(b, nextCtx)).join(", ")}</p>` : "";
548
+ const body = model.type ? `<div class="ce-type-alias">= ${renderType(model.type, nextCtx)}</div>` : renderFieldRows(model.fields, nextCtx);
549
+ const openButton = `<button class="ce-ref-open" type="button" ${jumpAttrs} title="Jump to source">\u2197</button>`;
550
+ return `<details class="ce-ref-expand">
551
+ <summary><span class="ce-ref-name">${safeName}</span>${openButton}</summary>
552
+ <div class="ce-ref-body">${bases}${body}</div>
553
+ </details>`;
554
+ }
555
+ __name(renderRef, "renderRef");
556
+ function tok(text) {
557
+ return `<span class="ce-type-token">${text}</span>`;
558
+ }
559
+ __name(tok, "tok");
560
+
561
+ // src/render-schema.ts
562
+ var STRING_SCALARS = /* @__PURE__ */ new Set([
563
+ "string",
564
+ "uuid",
565
+ "email",
566
+ "url"
567
+ ]);
568
+ function renderSchemaTree(type, ctx = {}, options = {}) {
569
+ return renderSchemaNode(type, ctx, options);
570
+ }
571
+ __name(renderSchemaTree, "renderSchemaTree");
572
+ function renderSchemaNode(type, ctx, options) {
573
+ if (type.kind === "inlineObject") {
574
+ return renderSchemaFields(type.fields, ctx, options);
575
+ }
576
+ if (type.kind === "ref") {
577
+ const entry = ctx.models?.get(type.name);
578
+ const visited = ctx.visited ?? /* @__PURE__ */ new Set();
579
+ if (entry && !visited.has(type.name)) {
580
+ const next = {
581
+ ...ctx,
582
+ visited: /* @__PURE__ */ new Set([
583
+ ...visited,
584
+ type.name
585
+ ]),
586
+ depth: (ctx.depth ?? 0) + 1
587
+ };
588
+ const resolved = resolveEffectiveFields2(type.name, modelIndexFromCtx2(ctx));
589
+ if (resolved.fields.length > 0) {
590
+ const fieldsHtml = renderSchemaFields(resolved.fields, next, options);
591
+ const unresolvedHtml = renderUnresolvedRefs(resolved.unresolved);
592
+ return `${fieldsHtml}${unresolvedHtml}`;
593
+ }
594
+ if (resolved.unresolved.length > 0) {
595
+ return renderUnresolvedRefs(resolved.unresolved);
596
+ }
597
+ const model = entry.model;
598
+ if (model.type) {
599
+ return renderSchemaNode(model.type, next, options);
600
+ }
601
+ }
602
+ if (!entry) {
603
+ return renderUnresolvedRefs([
604
+ type.name
605
+ ]);
606
+ }
607
+ return `<div class="ce-schema-fallback">${renderType(type, ctx)}</div>`;
608
+ }
609
+ if (type.kind === "array") {
610
+ return `<div class="ce-schema-array-label">array of:</div>${renderSchemaNode(type.item, ctx, options)}`;
611
+ }
612
+ if (type.kind === "intersection") {
613
+ const resolved = resolveEffectiveFields2(type, modelIndexFromCtx2(ctx));
614
+ if (resolved.fields.length > 0 || resolved.unresolved.length > 0) {
615
+ const fieldsHtml = resolved.fields.length > 0 ? renderSchemaFields(resolved.fields, ctx, options) : "";
616
+ const unresolvedHtml = renderUnresolvedRefs(resolved.unresolved);
617
+ return `${fieldsHtml}${unresolvedHtml}`;
618
+ }
619
+ return `<div class="ce-schema-fallback">${renderType(type, ctx)}</div>`;
620
+ }
621
+ if (type.kind === "union" || type.kind === "discriminatedUnion") {
622
+ return renderUnion(type, ctx, options);
623
+ }
624
+ return `<div class="ce-schema-fallback">${renderType(type, ctx)}</div>`;
625
+ }
626
+ __name(renderSchemaNode, "renderSchemaNode");
627
+ function modelIndexFromCtx2(ctx) {
628
+ const out = /* @__PURE__ */ new Map();
629
+ if (ctx.models) {
630
+ for (const [name, resolved] of ctx.models) out.set(name, resolved.model);
631
+ }
632
+ return out;
633
+ }
634
+ __name(modelIndexFromCtx2, "modelIndexFromCtx");
635
+ function renderUnresolvedRefs(names) {
636
+ if (names.length === 0) return "";
637
+ const chips = names.map((n) => `<a class="ce-schema-unresolved-ref ce-ref" href="#model-${encodeURIComponent(n)}">${escapeHtml(n)}</a>`).join(", ");
638
+ return `<div class="ce-schema-unresolved">
639
+ <span class="ce-schema-unresolved-label">Unresolved</span>
640
+ ${chips}
641
+ <span class="ce-schema-unresolved-hint">\u2014 not loaded in the workspace.</span>
642
+ </div>`;
643
+ }
644
+ __name(renderUnresolvedRefs, "renderUnresolvedRefs");
645
+ function renderUnion(type, ctx, options) {
646
+ const isDiscriminated = type.kind === "discriminatedUnion";
647
+ const label = isDiscriminated ? `One of <span class="ce-schema-union-disc">by <code>${escapeHtml(type.discriminator)}</code></span>` : "One of";
648
+ const variants = type.members.map((m) => renderVariant(m, ctx, isDiscriminated ? type.discriminator : void 0, options)).join("");
649
+ return `<div class="ce-schema-union">
650
+ <div class="ce-schema-union-label">${label}</div>
651
+ <div class="ce-schema-union-variants">${variants}</div>
652
+ </div>`;
653
+ }
654
+ __name(renderUnion, "renderUnion");
655
+ function renderVariant(member, ctx, discriminator, options) {
656
+ const info = extractVariantInfo(member, ctx, discriminator, options);
657
+ const discBadge = info.discriminatorValue !== void 0 ? `<code class="ce-schema-variant-disc-value">${escapeHtml(discriminator)}: ${escapeHtml(info.discriminatorValue)}</code>` : "";
658
+ return `<details class="ce-schema-variant">
659
+ <summary>
660
+ <span class="ce-schema-variant-name">${info.name}</span>
661
+ ${discBadge}
662
+ </summary>
663
+ <div class="ce-schema-variant-body">${info.body}</div>
664
+ </details>`;
665
+ }
666
+ __name(renderVariant, "renderVariant");
667
+ function extractVariantInfo(member, ctx, discriminator, options) {
668
+ if (member.kind === "ref") {
669
+ const entry = ctx.models?.get(member.name);
670
+ const visited = ctx.visited ?? /* @__PURE__ */ new Set();
671
+ if (entry && !visited.has(member.name)) {
672
+ const next = {
673
+ ...ctx,
674
+ visited: /* @__PURE__ */ new Set([
675
+ ...visited,
676
+ member.name
677
+ ]),
678
+ depth: (ctx.depth ?? 0) + 1
679
+ };
680
+ const model = entry.model;
681
+ const discriminatorValue = discriminator ? findDiscriminatorValue(model.fields, discriminator) : void 0;
682
+ const resolved = resolveEffectiveFields2(member.name, modelIndexFromCtx2(ctx));
683
+ let body;
684
+ if (resolved.fields.length > 0 || resolved.unresolved.length > 0) {
685
+ const fieldsHtml = resolved.fields.length > 0 ? renderSchemaFields(resolved.fields, next, options) : "";
686
+ body = `${fieldsHtml}${renderUnresolvedRefs(resolved.unresolved)}`;
687
+ } else if (model.type) {
688
+ body = renderSchemaNode(model.type, next, options);
689
+ } else {
690
+ body = `<p class="ce-empty">No fields.</p>`;
691
+ }
692
+ return {
693
+ name: escapeHtml(member.name),
694
+ body,
695
+ discriminatorValue
696
+ };
697
+ }
698
+ return {
699
+ name: escapeHtml(member.name),
700
+ body: `<div class="ce-schema-fallback">${renderType(member, ctx)}</div>`
701
+ };
702
+ }
703
+ if (member.kind === "inlineObject") {
704
+ return {
705
+ name: "object",
706
+ body: renderSchemaFields(member.fields, ctx, options),
707
+ discriminatorValue: discriminator ? findDiscriminatorValue(member.fields, discriminator) : void 0
708
+ };
709
+ }
710
+ return {
711
+ name: variantTypeName(member),
712
+ body: `<div class="ce-schema-fallback">${renderType(member, ctx)}</div>`
713
+ };
714
+ }
715
+ __name(extractVariantInfo, "extractVariantInfo");
716
+ function findDiscriminatorValue(fields, discriminator) {
717
+ if (!fields) return void 0;
718
+ const f = fields.find((field) => field.name === discriminator);
719
+ if (!f) return void 0;
720
+ if (f.type.kind === "literal") {
721
+ return typeof f.type.value === "string" ? `"${f.type.value}"` : String(f.type.value);
722
+ }
723
+ if (f.type.kind === "enum" && f.type.values.length === 1) {
724
+ return `"${f.type.values[0]}"`;
725
+ }
726
+ return void 0;
727
+ }
728
+ __name(findDiscriminatorValue, "findDiscriminatorValue");
729
+ function variantTypeName(type) {
730
+ switch (type.kind) {
731
+ case "scalar":
732
+ return escapeHtml(type.name);
733
+ case "literal":
734
+ return escapeHtml(typeof type.value === "string" ? `"${type.value}"` : String(type.value));
735
+ case "enum":
736
+ return "enum";
737
+ case "array":
738
+ return "array";
739
+ case "tuple":
740
+ return "tuple";
741
+ case "record":
742
+ return "record";
743
+ case "lazy":
744
+ return "lazy";
745
+ default:
746
+ return type.kind;
747
+ }
748
+ }
749
+ __name(variantTypeName, "variantTypeName");
750
+ function renderSchemaFields(fields, ctx, options) {
751
+ const visible = options.exclude ? fields.filter((f) => f.visibility !== options.exclude) : fields;
752
+ if (visible.length === 0) return '<p class="ce-empty">No fields.</p>';
753
+ return `<div class="ce-schema-fields">${visible.map((f) => renderSchemaField(f, ctx, options)).join("")}</div>`;
754
+ }
755
+ __name(renderSchemaFields, "renderSchemaFields");
756
+ function renderSchemaField(f, ctx, options) {
757
+ const required = !f.optional && f.default === void 0;
758
+ const typeLabel = renderTypeLabel(f.type);
759
+ const chips = renderConstraintChips(f.type);
760
+ const enumValues = renderEnumValues(f.type, ctx);
761
+ const desc = f.description ? `<div class="ce-schema-desc ce-markdown">${renderMarkdown(f.description)}</div>` : "";
762
+ const defaultHtml = f.default !== void 0 ? `<div class="ce-schema-default">Default: <code>${escapeHtml(String(f.default))}</code></div>` : "";
763
+ const requiredTag = required ? '<span class="ce-schema-required">required</span>' : "";
764
+ let nested = "";
765
+ if (f.type.kind === "inlineObject" || f.type.kind === "array" || f.type.kind === "ref" || f.type.kind === "intersection") {
766
+ const inner = renderSchemaNode(f.type, ctx, options);
767
+ if (inner.includes("ce-schema-fields") || inner.includes("ce-schema-array-label")) {
768
+ nested = `<div class="ce-schema-nested">${inner}</div>`;
769
+ }
770
+ }
771
+ return `<div class="ce-schema-row">
772
+ <div class="ce-schema-head">
773
+ <code class="ce-schema-name">${escapeHtml(f.name)}</code>
774
+ ${typeLabel}
775
+ ${requiredTag}
776
+ </div>
777
+ ${desc}
778
+ ${defaultHtml}
779
+ ${chips}
780
+ ${enumValues}
781
+ ${nested}
782
+ </div>`;
783
+ }
784
+ __name(renderSchemaField, "renderSchemaField");
785
+ function renderEnumValues(type, ctx) {
786
+ const values = resolveEnumValues(type, ctx);
787
+ if (!values || values.length === 0) return "";
788
+ const chips = values.map((v) => `<code class="ce-schema-enum-value">${escapeHtml(v)}</code>`).join("");
789
+ return `<div class="ce-schema-enum">
790
+ <span class="ce-schema-enum-label">Allowed values:</span>
791
+ <span class="ce-schema-enum-list">${chips}</span>
792
+ </div>`;
793
+ }
794
+ __name(renderEnumValues, "renderEnumValues");
795
+ function resolveEnumValues(type, ctx) {
796
+ if (type.kind === "enum") return type.values;
797
+ if (type.kind === "ref") {
798
+ const entry = ctx.models?.get(type.name);
799
+ if (entry?.model.type?.kind === "enum") return entry.model.type.values;
800
+ }
801
+ return null;
802
+ }
803
+ __name(resolveEnumValues, "resolveEnumValues");
804
+ function renderTypeLabel(type) {
805
+ switch (type.kind) {
806
+ case "scalar":
807
+ return `<span class="ce-schema-type">${escapeHtml(type.name)}</span>`;
808
+ case "enum":
809
+ return `<span class="ce-schema-type">enum</span>`;
810
+ case "literal":
811
+ return `<span class="ce-schema-type">literal</span>`;
812
+ case "ref":
813
+ return `<a class="ce-schema-type ce-ref" href="#model-${encodeURIComponent(type.name)}">${escapeHtml(type.name)}</a>`;
814
+ case "array":
815
+ return `<span class="ce-schema-type">array&lt;${innerTypeName(type.item)}&gt;</span>`;
816
+ case "tuple":
817
+ return `<span class="ce-schema-type">tuple</span>`;
818
+ case "record":
819
+ return `<span class="ce-schema-type">record</span>`;
820
+ case "union":
821
+ return `<span class="ce-schema-type">union</span>`;
822
+ case "discriminatedUnion":
823
+ return `<span class="ce-schema-type">union</span>`;
824
+ case "intersection": {
825
+ const parts = type.members.map(intersectionMemberLabel);
826
+ return `<span class="ce-schema-type">${parts.join(" &amp; ")}</span>`;
827
+ }
828
+ case "lazy":
829
+ return `<span class="ce-schema-type">lazy</span>`;
830
+ case "inlineObject":
831
+ return `<span class="ce-schema-type">object</span>`;
832
+ }
833
+ }
834
+ __name(renderTypeLabel, "renderTypeLabel");
835
+ function innerTypeName(type) {
836
+ if (type.kind === "scalar") return escapeHtml(type.name);
837
+ if (type.kind === "ref") return escapeHtml(type.name);
838
+ return type.kind;
839
+ }
840
+ __name(innerTypeName, "innerTypeName");
841
+ function intersectionMemberLabel(type) {
842
+ if (type.kind === "ref") return escapeHtml(type.name);
843
+ if (type.kind === "inlineObject") return "{ \u2026 }";
844
+ if (type.kind === "scalar") return escapeHtml(type.name);
845
+ return type.kind;
846
+ }
847
+ __name(intersectionMemberLabel, "intersectionMemberLabel");
848
+ function renderConstraintChips(type) {
849
+ if (type.kind !== "scalar") return "";
850
+ const chips = formatConstraints(type);
851
+ if (chips.length === 0) return "";
852
+ return `<div class="ce-schema-chips">${chips.map((c) => `<span class="ce-schema-chip">${escapeHtml(c)}</span>`).join("")}</div>`;
853
+ }
854
+ __name(renderConstraintChips, "renderConstraintChips");
855
+ function formatConstraints(s) {
856
+ const chips = [];
857
+ const isString = STRING_SCALARS.has(s.name);
858
+ const unit = isString ? " characters" : "";
859
+ if (s.min !== void 0) chips.push(`>= ${s.min}${unit}`);
860
+ if (s.max !== void 0) chips.push(`<= ${s.max}${unit}`);
861
+ if (s.len !== void 0) chips.push(`= ${s.len}${unit}`);
862
+ if (s.format !== void 0) chips.push(`format: ${s.format}`);
863
+ if (s.regex !== void 0) chips.push(`matches /${s.regex}/`);
864
+ return chips;
865
+ }
866
+ __name(formatConstraints, "formatConstraints");
867
+
868
+ // src/render-tryit.ts
869
+ function renderTryIt(op, baseUrl, ctx = {}) {
870
+ const id = operationAnchor(op);
871
+ const pathParams = extractParams(op.routeParams);
872
+ const queryParams = extractParams(op.op.query);
873
+ const headerParams = extractParams(op.op.headers);
874
+ const jsonBody = op.op.request?.bodies.find((b) => b.contentType === "application/json" || b.contentType.endsWith("+json"));
875
+ const bodySeed = `${op.method}:${op.routePath}:request-body`;
876
+ const bodyPrefill = jsonBody ? JSON.stringify(buildSampleJson(jsonBody.bodyType, ctx, bodySeed, "readonly"), null, 2) : "";
877
+ return `<details class="ce-tryit" data-tryit-id="${escapeHtml(id)}">
878
+ <summary>Try it</summary>
879
+ <form class="ce-tryit-form" onsubmit="return false;">
880
+ <label class="ce-tryit-row">
881
+ <span class="ce-tryit-label">Base URL</span>
882
+ <input type="text" name="baseUrl" value="${escapeHtml(baseUrl)}" placeholder="https://api.example.com" />
883
+ </label>
884
+ ${renderInputSection("Path params", "path", pathParams, ctx, op)}
885
+ ${renderInputSection("Query", "query", queryParams, ctx, op)}
886
+ ${renderInputSection("Headers", "header", headerParams, ctx, op)}
887
+ ${jsonBody ? `<label class="ce-tryit-row ce-tryit-col">
888
+ <span class="ce-tryit-label">Body (JSON)</span>
889
+ <textarea name="body" rows="8" placeholder="{}">${escapeHtml(bodyPrefill)}</textarea>
890
+ </label>` : ""}
891
+ <div class="ce-tryit-actions">
892
+ <button type="button" class="ce-tryit-send" data-tryit-action="send" data-tryit-target="${escapeHtml(id)}">
893
+ Send ${escapeHtml(op.method.toUpperCase())} ${escapeHtml(op.routePath)}
894
+ </button>
895
+ </div>
896
+ <div class="ce-tryit-response" data-tryit-response="${escapeHtml(id)}"></div>
897
+ </form>
898
+ </details>`;
899
+ }
900
+ __name(renderTryIt, "renderTryIt");
901
+ function extractParams(source) {
902
+ if (!source || source.kind !== "params") return [];
903
+ return source.nodes;
904
+ }
905
+ __name(extractParams, "extractParams");
906
+ function renderInputSection(label, scope, params, ctx, op) {
907
+ if (params.length === 0) return "";
908
+ const rows = params.map((p) => {
909
+ const prefill = paramPrefill(p, ctx, op, scope);
910
+ return `<label class="ce-tryit-row">
911
+ <span class="ce-tryit-label"><code>${escapeHtml(p.name)}</code>${p.optional ? "" : " *"}</span>
912
+ <input type="text"
913
+ name="${scope}.${escapeHtml(p.name)}"
914
+ placeholder="${escapeHtml(typeHint(p))}"
915
+ value="${escapeHtml(prefill)}" />
916
+ </label>`;
917
+ }).join("");
918
+ return `<fieldset class="ce-tryit-section"><legend>${escapeHtml(label)}</legend>${rows}</fieldset>`;
919
+ }
920
+ __name(renderInputSection, "renderInputSection");
921
+ function paramPrefill(p, ctx, op, scope) {
922
+ if (p.default !== void 0) return String(p.default);
923
+ const seed = `${op.method}:${op.routePath}:${scope}:${p.name}`;
924
+ const sample = buildSampleJson(p.type, ctx, seed, "readonly");
925
+ if (sample === null || sample === void 0) return "";
926
+ if (typeof sample === "object") return JSON.stringify(sample);
927
+ return String(sample);
928
+ }
929
+ __name(paramPrefill, "paramPrefill");
930
+ function typeHint(p) {
931
+ if (p.type.kind === "scalar") return p.type.name;
932
+ return p.type.kind;
933
+ }
934
+ __name(typeHint, "typeHint");
935
+
936
+ // src/render-operation.ts
937
+ function operationAnchor(op) {
938
+ const suffix = op.op.sdk ?? op.op.name ?? "";
939
+ const slug2 = `${op.method}-${op.routePath}-${suffix}`.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
940
+ return `op-${slug2}`;
941
+ }
942
+ __name(operationAnchor, "operationAnchor");
943
+ function renderOperation(op, options = {}) {
944
+ const ctx = options.ctx ?? {};
945
+ const badges = [];
946
+ for (const m of op.effectiveModifiers) {
947
+ badges.push(badge2(m, m));
948
+ }
949
+ const securityBadge = renderSecurityBadge(op.effectiveSecurity);
950
+ if (securityBadge) badges.push(securityBadge);
951
+ const description = op.op.description ? `<div class="ce-description ce-markdown">${renderMarkdown(op.op.description)}</div>` : "";
952
+ const service = op.op.service ? html`<p class="ce-meta"><strong>service:</strong> <code>${op.op.service}</code></p>` : "";
953
+ const signature = op.op.signature ? html`<p class="ce-meta"><strong>signature:</strong> <code>${op.op.signature}</code>${op.op.signatureDescription ? raw(` \u2014 ${escapeHtml(op.op.signatureDescription)}`) : ""}</p>` : "";
954
+ const pathParams = renderParamSection("Path Parameters", op.routeParams, ctx);
955
+ const queryParams = renderParamSection("Query Parameters", op.op.query, ctx);
956
+ const headerParams = renderParamSection("Request Headers", op.op.headers, ctx);
957
+ const requestBody = renderRequest(op.op.request, ctx);
958
+ const responses = renderResponses(op.op.responses, ctx);
959
+ const pluginExt = renderPluginExtensions(op.op.pluginExtensions);
960
+ const tryIt = options.tryItBaseUrl !== void 0 ? renderTryIt(op, options.tryItBaseUrl, ctx) : "";
961
+ const codeSamples = renderCodeSamples(op, options.tryItBaseUrl ?? "", ctx);
962
+ const title = op.op.name ?? op.op.sdk ?? `${op.method.toUpperCase()} ${op.routePath}`;
963
+ return html`<section id="${raw(operationAnchor(op))}" class="ce-card ce-op-card">
964
+ <header class="ce-card-header">
965
+ <div class="ce-op-title-row">
966
+ <h1 class="ce-op-title">${title}</h1>
967
+ <button
968
+ class="ce-jump"
969
+ data-jump-file="${op.filePath}"
970
+ data-jump-line="${op.op.loc.line}"
971
+ title="Open in editor"
972
+ type="button"
973
+ >
974
+
975
+ </button>
976
+ </div>
977
+ <div class="ce-endpoint-row">
978
+ <span class="ce-method ce-method-${raw(op.method)}">${op.method.toUpperCase()}</span>
979
+ <code class="ce-path">${op.routePath}</code>
980
+ ${raw(badges.length > 0 ? `<span class="ce-badge-row">${badges.join("")}</span>` : "")}
981
+ </div>
982
+ </header>
983
+ <div class="ce-op-body">
984
+ <div class="ce-op-main">
985
+ ${raw(description)}
986
+ ${raw(service)}
987
+ ${raw(signature)}
988
+ ${raw(pathParams)}
989
+ ${raw(queryParams)}
990
+ ${raw(headerParams)}
991
+ ${raw(requestBody)}
992
+ ${raw(responses)}
993
+ ${raw(pluginExt)}
994
+ </div>
995
+ <div class="ce-op-resize" data-resize-handle role="separator" aria-orientation="vertical" aria-label="Resize columns" tabindex="0"></div>
996
+ <aside class="ce-op-rail">
997
+ ${raw(tryIt)}
998
+ ${raw(codeSamples)}
999
+ </aside>
1000
+ </div>
1001
+ </section>`;
1002
+ }
1003
+ __name(renderOperation, "renderOperation");
1004
+ function renderSecurityBadge(security) {
1005
+ if (security === void 0) return badge2("security: secured", "security-secured");
1006
+ if (security === "none") return badge2("security: none", "security-none");
1007
+ if (security.policy === false) return badge2("security: no policy", "security-no-policy");
1008
+ if (typeof security.policy === "string") return badge2(`policy: ${security.policy}`, "security-policy");
1009
+ return "";
1010
+ }
1011
+ __name(renderSecurityBadge, "renderSecurityBadge");
1012
+ function renderParamSection(label, source, ctx) {
1013
+ if (!source) return "";
1014
+ if (source.kind === "params") {
1015
+ if (source.nodes.length === 0) return "";
1016
+ return html`<section class="ce-subsection">
1017
+ <h3>${label}</h3>
1018
+ ${raw(renderParamTable(source.nodes, ctx))}
1019
+ </section>`;
1020
+ }
1021
+ if (source.kind === "ref") {
1022
+ return html`<section class="ce-subsection">
1023
+ <h3>${label}</h3>
1024
+ ${raw(renderSchemaTree({
1025
+ kind: "ref",
1026
+ name: source.name
1027
+ }, ctx, {
1028
+ exclude: "readonly"
1029
+ }))}
1030
+ </section>`;
1031
+ }
1032
+ return html`<section class="ce-subsection">
1033
+ <h3>${label}</h3>
1034
+ ${raw(renderSchemaTree(source.node, ctx, {
1035
+ exclude: "readonly"
1036
+ }))}
1037
+ </section>`;
1038
+ }
1039
+ __name(renderParamSection, "renderParamSection");
1040
+ function renderParamTable(params, ctx) {
1041
+ const rows = params.map((p) => {
1042
+ const modifiers = [];
1043
+ if (p.optional) modifiers.push("optional");
1044
+ if (p.nullable) modifiers.push("nullable");
1045
+ const modifierHtml = modifiers.map((m) => badge2(m, m)).join("");
1046
+ const defaultHtml = p.default !== void 0 ? html`<code class="ce-default">= ${String(p.default)}</code>` : "";
1047
+ const descHtml = p.description ? `<div class="ce-field-desc ce-markdown">${renderMarkdown(p.description)}</div>` : "";
1048
+ return `<tr>
1049
+ <td class="ce-field-name"><code>${escapeHtml(p.name)}</code>${modifierHtml}</td>
1050
+ <td class="ce-field-type">${renderType(p.type, ctx)}${defaultHtml}${descHtml}</td>
1051
+ </tr>`;
1052
+ });
1053
+ return `<table class="ce-fields"><tbody>${rows.join("")}</tbody></table>`;
1054
+ }
1055
+ __name(renderParamTable, "renderParamTable");
1056
+ function renderRequest(request, ctx) {
1057
+ if (!request || request.bodies.length === 0) return "";
1058
+ const blocks = request.bodies.map((body) => {
1059
+ return `<div class="ce-body-block">
1060
+ <div class="ce-body-header">
1061
+ <span class="ce-body-title">Body</span>
1062
+ <span class="ce-content-type"><code>${escapeHtml(body.contentType)}</code></span>
1063
+ </div>
1064
+ ${renderSchemaTree(body.bodyType, ctx, {
1065
+ exclude: "readonly"
1066
+ })}
1067
+ </div>`;
1068
+ });
1069
+ return `<section class="ce-subsection"><h3>Request</h3>${blocks.join("")}</section>`;
1070
+ }
1071
+ __name(renderRequest, "renderRequest");
1072
+ function renderResponses(responses, ctx) {
1073
+ if (responses.length === 0) return "";
1074
+ const summary = responses.length > 1 ? responses.map((r) => {
1075
+ const statusClass = `ce-status-${Math.floor(r.statusCode / 100)}xx`;
1076
+ return `<a class="ce-status ${statusClass}" href="#response-${r.statusCode}">${r.statusCode}</a>`;
1077
+ }).join("") : "";
1078
+ const blocks = responses.map((r) => renderResponse(r, ctx));
1079
+ return `<section class="ce-subsection">
1080
+ <h3>Responses</h3>
1081
+ ${summary ? `<div class="ce-status-summary">${summary}</div>` : ""}
1082
+ ${blocks.join("")}
1083
+ </section>`;
1084
+ }
1085
+ __name(renderResponses, "renderResponses");
1086
+ function renderResponse(response, ctx) {
1087
+ const statusClass = `ce-status-${Math.floor(response.statusCode / 100)}xx`;
1088
+ const contentTypeHtml = response.contentType ? `<span class="ce-content-type"><code>${escapeHtml(response.contentType)}</code></span>` : "";
1089
+ const bodyHtml = response.bodyType ? renderSchemaTree(response.bodyType, ctx, {
1090
+ exclude: "writeonly"
1091
+ }) : '<p class="ce-empty">No response body.</p>';
1092
+ const headersHtml = renderResponseHeaders(response.headers, ctx);
1093
+ return `<div class="ce-response-block" id="response-${response.statusCode}">
1094
+ <div class="ce-body-header">
1095
+ <span class="ce-status ${statusClass}">${response.statusCode}</span>
1096
+ ${contentTypeHtml}
1097
+ </div>
1098
+ ${bodyHtml}
1099
+ ${headersHtml}
1100
+ </div>`;
1101
+ }
1102
+ __name(renderResponse, "renderResponse");
1103
+ function renderResponseHeaders(headers, ctx) {
1104
+ if (!headers || headers.length === 0) return "";
1105
+ const rows = headers.map((h) => {
1106
+ const optional = h.optional ? badge2("optional", "optional") : "";
1107
+ const desc = h.description ? `<div class="ce-field-desc ce-markdown">${renderMarkdown(h.description)}</div>` : "";
1108
+ return `<tr>
1109
+ <td class="ce-field-name"><code>${escapeHtml(h.name)}</code>${optional}</td>
1110
+ <td class="ce-field-type">${renderType(h.type, ctx)}${desc}</td>
1111
+ </tr>`;
1112
+ });
1113
+ return `<h5>Headers</h5><table class="ce-fields"><tbody>${rows.join("")}</tbody></table>`;
1114
+ }
1115
+ __name(renderResponseHeaders, "renderResponseHeaders");
1116
+ function renderPluginExtensions(ext) {
1117
+ if (!ext || Object.keys(ext).length === 0) return "";
1118
+ const json = JSON.stringify(ext, null, 2);
1119
+ return html`<section class="ce-subsection">
1120
+ <h3>Plugin extensions</h3>
1121
+ <details><summary>Show JSON</summary><pre class="ce-code">${json}</pre></details>
1122
+ </section>`;
1123
+ }
1124
+ __name(renderPluginExtensions, "renderPluginExtensions");
1125
+ function badge2(label, kind) {
1126
+ return `<span class="ce-badge ce-badge-${escapeHtml(kind)}">${escapeHtml(label)}</span>`;
1127
+ }
1128
+ __name(badge2, "badge");
1129
+
1130
+ // src/render.ts
1131
+ var METHOD_ORDER = [
1132
+ "get",
1133
+ "post",
1134
+ "put",
1135
+ "patch",
1136
+ "delete"
1137
+ ];
1138
+ var SIDEBAR_MARKER_SVG = '<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="6.25" /><circle cx="8" cy="8" r="3.25" /><circle cx="8" cy="8" r="0.75" fill="currentColor" /></svg>';
1139
+ function renderApp(data) {
1140
+ const warnings = renderWarnings(data.warnings);
1141
+ const overview = renderOverview(data);
1142
+ const sidebar = renderSidebar(data);
1143
+ const operations = sortOperations(data.operations);
1144
+ const opsHtml = operations.map((op) => renderOperation(op)).join("");
1145
+ const modelsHtml = [
1146
+ ...data.models
1147
+ ].sort((a, b) => a.model.name.localeCompare(b.model.name)).map((m) => renderModel(m)).join("");
1148
+ return html`<div class="ce-layout">
1149
+ ${raw(sidebar)}
1150
+ <main class="ce-detail">
1151
+ ${raw(warnings)}
1152
+ ${raw(overview)}
1153
+ ${operations.length > 0 ? raw(`<section class="ce-section"><h1 id="endpoints">Endpoints</h1>${opsHtml}</section>`) : ""}
1154
+ ${data.models.length > 0 ? raw(`<section class="ce-section"><h1 id="models">Models</h1>${modelsHtml}</section>`) : ""}
1155
+ </main>
1156
+ </div>`;
1157
+ }
1158
+ __name(renderApp, "renderApp");
1159
+ function renderOverview(data) {
1160
+ const { configMeta } = data;
1161
+ const description = configMeta.description ? html`<p class="ce-description">${configMeta.description}</p>` : "";
1162
+ const servers = configMeta.servers && configMeta.servers.length > 0 ? `<section class="ce-subsection"><h3>Servers</h3><ul>${configMeta.servers.map((s) => `<li><code>${escapeHtml(s.url)}</code>${s.description ? ` \u2014 ${escapeHtml(s.description)}` : ""}</li>`).join("")}</ul></section>` : "";
1163
+ return html`<section id="overview" class="ce-section ce-overview">
1164
+ <h1>${configMeta.title}</h1>
1165
+ <p class="ce-version">v${configMeta.version}</p>
1166
+ ${raw(description)}
1167
+ ${raw(servers)}
1168
+ </section>`;
1169
+ }
1170
+ __name(renderOverview, "renderOverview");
1171
+ function renderSidebar(data) {
1172
+ const operations = sortOperations(data.operations);
1173
+ const groups = groupBy(operations, (op) => op.fileGroup);
1174
+ const groupKeys = [
1175
+ ...groups.keys()
1176
+ ].sort();
1177
+ const endpointGroups = groupKeys.map((key) => {
1178
+ const items = groups.get(key).map((op) => {
1179
+ const name = op.op.name ?? op.op.sdk ?? op.routePath;
1180
+ return `<li><a class="ce-sidebar-row" href="#${escapeHtml(operationAnchor(op))}">
1181
+ <span class="ce-sidebar-marker" aria-hidden="true">${SIDEBAR_MARKER_SVG}</span>
1182
+ <span class="ce-sidebar-name">${escapeHtml(name)}</span>
1183
+ <span class="ce-sidebar-method ce-method-text-${escapeHtml(op.method)}">${escapeHtml(op.method.toUpperCase())}</span>
1184
+ </a></li>`;
1185
+ }).join("");
1186
+ return `<details open class="ce-sidebar-group">
1187
+ <summary>${escapeHtml(key)}</summary>
1188
+ <ul>${items}</ul>
1189
+ </details>`;
1190
+ }).join("");
1191
+ const modelLinks = [
1192
+ ...data.models
1193
+ ].sort((a, b) => a.model.name.localeCompare(b.model.name)).map((m) => `<li><a href="#${escapeHtml(modelAnchor(m.model.name))}">${escapeHtml(m.model.name)}</a></li>`).join("");
1194
+ return `<aside class="ce-sidebar">
1195
+ <nav>
1196
+ <section>
1197
+ <h4><a href="#overview">Overview</a></h4>
1198
+ </section>
1199
+ ${operations.length > 0 ? `<section><h4><a href="#endpoints">Endpoints</a></h4>${endpointGroups}</section>` : ""}
1200
+ ${data.models.length > 0 ? `<section><h4><a href="#models">Models</a></h4><ul class="ce-model-list">${modelLinks}</ul></section>` : ""}
1201
+ </nav>
1202
+ </aside>`;
1203
+ }
1204
+ __name(renderSidebar, "renderSidebar");
1205
+ function renderWarnings(warnings) {
1206
+ if (warnings.length === 0) return "";
1207
+ const items = warnings.map((w) => `<li>${escapeHtml(w.message)}${w.file ? ` <code>${escapeHtml(w.file)}${w.line ? `:${w.line}` : ""}</code>` : ""}</li>`).join("");
1208
+ return `<div class="ce-warnings"><strong>${warnings.length} warning${warnings.length === 1 ? "" : "s"}</strong><ul>${items}</ul></div>`;
1209
+ }
1210
+ __name(renderWarnings, "renderWarnings");
1211
+ function sortOperations(operations) {
1212
+ return [
1213
+ ...operations
1214
+ ].sort((a, b) => {
1215
+ const groupCmp = a.fileGroup.localeCompare(b.fileGroup);
1216
+ if (groupCmp !== 0) return groupCmp;
1217
+ const pathCmp = a.routePath.localeCompare(b.routePath);
1218
+ if (pathCmp !== 0) return pathCmp;
1219
+ return METHOD_ORDER.indexOf(a.method) - METHOD_ORDER.indexOf(b.method);
1220
+ });
1221
+ }
1222
+ __name(sortOperations, "sortOperations");
1223
+ function groupBy(items, keyFn) {
1224
+ const out = /* @__PURE__ */ new Map();
1225
+ for (const item of items) {
1226
+ const key = keyFn(item);
1227
+ const arr = out.get(key);
1228
+ if (arr) arr.push(item);
1229
+ else out.set(key, [
1230
+ item
1231
+ ]);
1232
+ }
1233
+ return out;
1234
+ }
1235
+ __name(groupBy, "groupBy");
1236
+
1237
+ // src/render-item.ts
1238
+ function renderItemPage(data, selection, options = {}) {
1239
+ const body = renderBody(data, selection, options);
1240
+ return html`<div class="ce-detail ce-detail-single">${raw(body)}</div>`;
1241
+ }
1242
+ __name(renderItemPage, "renderItemPage");
1243
+ function renderBody(data, selection, options) {
1244
+ if (selection.kind === "overview") return renderOverviewPage(data);
1245
+ const ctx = {
1246
+ models: buildModelMap(data)
1247
+ };
1248
+ if (selection.kind === "operation") {
1249
+ const op = data.operations.find((o) => operationAnchor(o) === selection.id);
1250
+ if (!op) return renderMissing(`Operation \`${selection.id}\` is not in the current workspace.`);
1251
+ return renderOperation(op, {
1252
+ tryItBaseUrl: options.tryItBaseUrl,
1253
+ ctx
1254
+ });
1255
+ }
1256
+ if (selection.kind === "file") {
1257
+ return renderFilePage(data, selection.path, options, ctx);
1258
+ }
1259
+ const model = data.models.find((m) => m.model.name === selection.name);
1260
+ if (!model) {
1261
+ return renderMissing(`Model \`${selection.name}\` isn't defined in any indexed \`.ck\` file. It's referenced from another contract but not declared anywhere in the workspace \u2014 check that the file containing \`contract ${selection.name}: { ... }\` (or its type alias) is present and saved. ${data.models.length} model${data.models.length === 1 ? "" : "s"} loaded.`);
1262
+ }
1263
+ return renderModel(model, ctx);
1264
+ }
1265
+ __name(renderBody, "renderBody");
1266
+ function renderFilePage(data, path, options, ctx) {
1267
+ const ops = data.operations.filter((o) => o.filePath === path);
1268
+ const models = data.models.filter((m) => m.filePath === path);
1269
+ if (ops.length === 0 && models.length === 0) {
1270
+ return renderMissing(`No contracts or operations found in \`${path}\`.`);
1271
+ }
1272
+ const opsHtml = ops.map((o) => renderOperation(o, {
1273
+ tryItBaseUrl: options.tryItBaseUrl,
1274
+ ctx
1275
+ })).join("");
1276
+ const modelsHtml = models.map((m) => renderModel(m, ctx)).join("");
1277
+ const fileLabel = path.split("/").pop() ?? path;
1278
+ return html`<section class="ce-section">
1279
+ <h1>${fileLabel}</h1>
1280
+ <p class="ce-version"><code>${path}</code></p>
1281
+ ${raw(ops.length > 0 ? `<section class="ce-section"><h1 id="endpoints">Endpoints</h1>${opsHtml}</section>` : "")}
1282
+ ${raw(models.length > 0 ? `<section class="ce-section"><h1 id="models">Models</h1>${modelsHtml}</section>` : "")}
1283
+ </section>`;
1284
+ }
1285
+ __name(renderFilePage, "renderFilePage");
1286
+ function buildModelMap(data) {
1287
+ const out = /* @__PURE__ */ new Map();
1288
+ for (const entry of data.models) out.set(entry.model.name, entry);
1289
+ return out;
1290
+ }
1291
+ __name(buildModelMap, "buildModelMap");
1292
+ function renderOverviewPage(data) {
1293
+ const { configMeta } = data;
1294
+ const description = configMeta.description ? `<div class="ce-description ce-markdown">${renderMarkdown(configMeta.description)}</div>` : "";
1295
+ const servers = renderServersList(configMeta.servers);
1296
+ const stats = `<dl class="ce-stats">
1297
+ <dt>Endpoints</dt><dd>${data.operations.length}</dd>
1298
+ <dt>Models</dt><dd>${data.models.length}</dd>
1299
+ </dl>`;
1300
+ return html`<section id="overview" class="ce-section ce-overview">
1301
+ <h1>${configMeta.title}</h1>
1302
+ <p class="ce-version">v${configMeta.version}</p>
1303
+ ${raw(description)}
1304
+ ${raw(stats)}
1305
+ ${raw(servers)}
1306
+ <p class="ce-hint">Pick an endpoint or model from the API Explorer in the sidebar to see details.</p>
1307
+ </section>`;
1308
+ }
1309
+ __name(renderOverviewPage, "renderOverviewPage");
1310
+ function renderServersList(servers) {
1311
+ if (!servers || servers.length === 0) return "";
1312
+ const items = servers.map((s) => `<li><code>${escapeHtml(s.url)}</code>${s.description ? ` \u2014 ${escapeHtml(s.description)}` : ""}</li>`).join("");
1313
+ return `<section class="ce-subsection"><h3>Servers</h3><ul>${items}</ul></section>`;
1314
+ }
1315
+ __name(renderServersList, "renderServersList");
1316
+ function renderMissing(message) {
1317
+ return `<section class="ce-card ce-missing"><p class="ce-empty">${escapeHtml(message)}</p></section>`;
1318
+ }
1319
+ __name(renderMissing, "renderMissing");
1320
+ var operationId = operationAnchor;
1321
+ var modelId = /* @__PURE__ */ __name((name) => modelAnchor(name), "modelId");
1322
+ function listSelections(data) {
1323
+ return [
1324
+ ...data.operations.map((operation) => ({
1325
+ kind: "operation",
1326
+ id: operationId(operation),
1327
+ operation
1328
+ })),
1329
+ ...data.models.map((model) => ({
1330
+ kind: "model",
1331
+ name: model.model.name,
1332
+ model
1333
+ }))
1334
+ ];
1335
+ }
1336
+ __name(listSelections, "listSelections");
1337
+ export {
1338
+ constraintSummary,
1339
+ escapeHtml,
1340
+ html,
1341
+ listSelections,
1342
+ modelAnchor,
1343
+ modelId,
1344
+ operationAnchor,
1345
+ operationId,
1346
+ raw,
1347
+ renderApp,
1348
+ renderCodeSamples,
1349
+ renderFieldRows,
1350
+ renderItemPage,
1351
+ renderMarkdown,
1352
+ renderModel,
1353
+ renderOperation,
1354
+ renderSchemaTree,
1355
+ renderTryIt,
1356
+ renderType,
1357
+ slug
1358
+ };
1359
+ //# sourceMappingURL=index.js.map