@reapi/spec-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.
@@ -0,0 +1,333 @@
1
+ // src/schema/types.ts
2
+ var SCHEMA_TYPE_OPTIONS = [
3
+ "object",
4
+ "array",
5
+ "string",
6
+ "number",
7
+ "integer",
8
+ "boolean",
9
+ "null"
10
+ ];
11
+ var COMPOSITE_KEYWORDS = ["allOf", "oneOf", "anyOf"];
12
+
13
+ // src/schema/guards.ts
14
+ function isRef(s) {
15
+ return !!s && typeof s === "object" && typeof s.$ref === "string";
16
+ }
17
+ function compositeKeyword(s) {
18
+ if (!s || isRef(s)) return null;
19
+ for (const kw of COMPOSITE_KEYWORDS) {
20
+ if (Array.isArray(s[kw])) return kw;
21
+ }
22
+ return null;
23
+ }
24
+ function schemaKind(s) {
25
+ if (!s || typeof s !== "object") return "any";
26
+ if (isRef(s)) return "ref";
27
+ const schema = s;
28
+ if (compositeKeyword(schema)) return "composite";
29
+ if (schema.type === "object" || schema.properties) return "object";
30
+ if (schema.type === "array" || schema.items) return "array";
31
+ if (Array.isArray(schema.enum)) return "enum";
32
+ if (schema.type) return "primitive";
33
+ return "any";
34
+ }
35
+ function schemaTypeLabel(s) {
36
+ if (!s || typeof s !== "object") return "any";
37
+ if (isRef(s)) return refName(s.$ref);
38
+ const schema = s;
39
+ const kw = compositeKeyword(schema);
40
+ if (kw) return kw;
41
+ if (schemaKind(schema) === "array") {
42
+ const inner = schema.items ? schemaTypeLabel(schema.items) : "any";
43
+ return `array<${inner}>`;
44
+ }
45
+ if (Array.isArray(schema.enum)) return `enum<${schema.type ?? "string"}>`;
46
+ return schema.type ?? "any";
47
+ }
48
+ function refName(ref) {
49
+ const seg = ref.split("/").pop();
50
+ return seg ? decodeURIComponent(seg) : ref;
51
+ }
52
+
53
+ // src/schema/create-schema.ts
54
+ var RETAINED_KEYS = [
55
+ "title",
56
+ "description",
57
+ "nullable",
58
+ "readOnly",
59
+ "writeOnly",
60
+ "deprecated",
61
+ "x-internal",
62
+ "x-debug"
63
+ ];
64
+ function retainedMetadata(prev) {
65
+ if (!prev || isRef(prev)) return {};
66
+ const out = {};
67
+ for (const key of RETAINED_KEYS) {
68
+ const v = prev[key];
69
+ if (v !== void 0) out[key] = v;
70
+ }
71
+ return out;
72
+ }
73
+ function baseForType(type) {
74
+ switch (type) {
75
+ case "object":
76
+ return { type: "object", properties: {} };
77
+ case "array":
78
+ return { type: "array", items: { type: "string" } };
79
+ default:
80
+ return { type };
81
+ }
82
+ }
83
+ function createSchemaForType(type, prev) {
84
+ return { ...baseForType(type), ...retainedMetadata(prev) };
85
+ }
86
+ function createCompositeSchema(keyword, prev) {
87
+ const seed = [];
88
+ if (prev && (isRef(prev) || prev.type)) {
89
+ if (isRef(prev)) {
90
+ seed.push(prev);
91
+ } else {
92
+ const clone = { ...prev };
93
+ for (const key of RETAINED_KEYS) delete clone[key];
94
+ if (Object.keys(clone).length) seed.push(clone);
95
+ }
96
+ }
97
+ return { [keyword]: seed, ...retainedMetadata(prev) };
98
+ }
99
+ function createRefSchema(ref) {
100
+ return { $ref: ref };
101
+ }
102
+
103
+ // src/schema/patch.ts
104
+ function applyPatch(schema, patch) {
105
+ const next = { ...schema };
106
+ for (const [key, value] of Object.entries(patch)) {
107
+ if (value === void 0) delete next[key];
108
+ else next[key] = value;
109
+ }
110
+ return next;
111
+ }
112
+
113
+ // src/schema/object-utils.ts
114
+ function renameKey(record, oldKey, newKey) {
115
+ if (oldKey === newKey || !(oldKey in record) || newKey in record) return record;
116
+ const next = {};
117
+ for (const [k, v] of Object.entries(record)) {
118
+ next[k === oldKey ? newKey : k] = v;
119
+ }
120
+ return next;
121
+ }
122
+ function uniqueKey(record, base = "property") {
123
+ if (!(base in record)) return base;
124
+ let i = 2;
125
+ while (`${base}${i}` in record) i++;
126
+ return `${base}${i}`;
127
+ }
128
+ function insertKeyAt(record, key, value, index) {
129
+ const entries = Object.entries(record).filter(([k]) => k !== key);
130
+ const clamped = Math.max(0, Math.min(entries.length, index));
131
+ entries.splice(clamped, 0, [key, value]);
132
+ return Object.fromEntries(entries);
133
+ }
134
+ function moveKey(record, key, toIndex) {
135
+ const entries = Object.entries(record);
136
+ const from = entries.findIndex(([k]) => k === key);
137
+ if (from === -1) return record;
138
+ const clamped = Math.max(0, Math.min(entries.length - 1, toIndex));
139
+ if (clamped === from) return record;
140
+ const [entry] = entries.splice(from, 1);
141
+ entries.splice(clamped, 0, entry);
142
+ return Object.fromEntries(entries);
143
+ }
144
+
145
+ // src/schema/refs.ts
146
+ var SHARED_SLUG = "shared";
147
+ function extractRefOptions(spec) {
148
+ const components = spec.components ?? {};
149
+ const schemas = components.schemas ?? {};
150
+ return Object.keys(schemas).map((name) => `#/components/schemas/${name}`);
151
+ }
152
+ function extractSharedRefOptions(sharedSpec) {
153
+ return extractRefOptions(sharedSpec).map((ref) => `${SHARED_SLUG}${ref}`);
154
+ }
155
+ function isSharedRef(ref) {
156
+ return ref.startsWith(`${SHARED_SLUG}#/`);
157
+ }
158
+ function buildSharedRef(name) {
159
+ return `${SHARED_SLUG}#/components/schemas/${name}`;
160
+ }
161
+ function walkRefs(node, path = [], out = []) {
162
+ if (Array.isArray(node)) {
163
+ node.forEach((item, i) => walkRefs(item, [...path, i], out));
164
+ } else if (node && typeof node === "object") {
165
+ const obj = node;
166
+ if (typeof obj.$ref === "string") out.push({ path, ref: obj.$ref });
167
+ for (const [key, value] of Object.entries(obj)) walkRefs(value, [...path, key], out);
168
+ }
169
+ return out;
170
+ }
171
+ function resolvePointer(spec, pointer) {
172
+ const hash = pointer.startsWith("#") ? pointer.slice(1) : pointer;
173
+ if (!hash.startsWith("/")) return void 0;
174
+ let cur = spec;
175
+ for (const raw of hash.slice(1).split("/")) {
176
+ const seg = raw.replace(/~1/g, "/").replace(/~0/g, "~");
177
+ if (cur == null || typeof cur !== "object") return void 0;
178
+ cur = cur[seg];
179
+ }
180
+ return cur;
181
+ }
182
+ function findDanglingRefs(spec, sharedSpec) {
183
+ return walkRefs(spec).filter(({ ref }) => {
184
+ if (ref.startsWith("#/")) return resolvePointer(spec, ref) === void 0;
185
+ if (isSharedRef(ref)) {
186
+ if (!sharedSpec) return true;
187
+ return resolvePointer(sharedSpec, ref.slice(SHARED_SLUG.length)) === void 0;
188
+ }
189
+ return false;
190
+ });
191
+ }
192
+
193
+ // src/schema/component-skeleton.ts
194
+ function camelCase(name) {
195
+ const parts = name.split(/[^a-zA-Z0-9]+/).filter(Boolean);
196
+ return parts.map((p, i) => i === 0 ? p[0].toLowerCase() + p.slice(1) : p[0].toUpperCase() + p.slice(1)).join("");
197
+ }
198
+ function componentSkeleton(typeKey, name) {
199
+ switch (typeKey) {
200
+ case "schemas":
201
+ return { type: "object", properties: {} };
202
+ case "responses":
203
+ return { description: name };
204
+ case "parameters":
205
+ return { name: camelCase(name), in: "query", schema: { type: "string" } };
206
+ case "requestBodies":
207
+ return {
208
+ content: { "application/json": { schema: { type: "object", properties: {} } } }
209
+ };
210
+ case "examples":
211
+ return { summary: name, value: {} };
212
+ case "headers":
213
+ return { schema: { type: "string" } };
214
+ case "securitySchemes":
215
+ return { type: "apiKey", name: camelCase(name), in: "header" };
216
+ default:
217
+ return {};
218
+ }
219
+ }
220
+ var COMPONENT_NAME_RE = /^[a-zA-Z0-9.\-_]+$/;
221
+ function validateComponentName(name, existing) {
222
+ const trimmed = name.trim();
223
+ if (!trimmed) return "Name is required";
224
+ if (!COMPONENT_NAME_RE.test(trimmed)) {
225
+ return 'Only letters, digits, ".", "-" and "_" are allowed';
226
+ }
227
+ if (existing.includes(trimmed)) return `"${trimmed}" already exists`;
228
+ return null;
229
+ }
230
+
231
+ // src/schema/preview-utils.ts
232
+ function createRefResolver(spec, sharedSpec) {
233
+ return (ref) => {
234
+ if (ref.startsWith("#/")) return resolvePointer(spec, ref);
235
+ if (isSharedRef(ref) && sharedSpec) {
236
+ return resolvePointer(sharedSpec, ref.slice(SHARED_SLUG.length));
237
+ }
238
+ return void 0;
239
+ };
240
+ }
241
+ function humanTypeLabel(schema, resolve) {
242
+ if (!schema || typeof schema !== "object") return "any";
243
+ if (isRef(schema)) return refName(schema.$ref);
244
+ const s = schema;
245
+ const kw = compositeKeyword(s);
246
+ if (kw) return kw === "allOf" ? "all of" : kw === "oneOf" ? "one of" : "any of";
247
+ const kind = schemaKind(s);
248
+ let label;
249
+ if (kind === "array") {
250
+ const items = s.items;
251
+ const inner = humanTypeLabel(items, resolve);
252
+ const plural = /^(string|integer|number|boolean|object)$/.test(inner) ? `${inner}s` : inner;
253
+ label = `array of ${plural}`;
254
+ } else if (Array.isArray(s.enum)) {
255
+ label = `enum<${s.type ?? "string"}>`;
256
+ } else {
257
+ label = s.type ?? "object";
258
+ }
259
+ if (s.nullable) label += " | null";
260
+ return label;
261
+ }
262
+ function resolveChildren(schema, resolve, seen = /* @__PURE__ */ new Set()) {
263
+ if (!schema || typeof schema !== "object") return { fields: [], seen };
264
+ if (isRef(schema)) {
265
+ if (!resolve || seen.has(schema.$ref)) return { fields: [], seen };
266
+ const target = resolve(schema.$ref);
267
+ if (!target) return { fields: [], seen };
268
+ return resolveChildren(target, resolve, /* @__PURE__ */ new Set([...seen, schema.$ref]));
269
+ }
270
+ const s = schema;
271
+ const kw = compositeKeyword(s);
272
+ if (kw) {
273
+ const fields = (s[kw] ?? []).map((branch, i) => ({
274
+ name: isRef(branch) ? refName(branch.$ref) : `option ${i + 1}`,
275
+ schema: branch,
276
+ required: false
277
+ }));
278
+ return { fields, seen };
279
+ }
280
+ const kind = schemaKind(s);
281
+ if (kind === "array") return resolveChildren(s.items, resolve, seen);
282
+ if (kind === "object") {
283
+ const props = s.properties ?? {};
284
+ const required = s.required ?? [];
285
+ const fields = Object.entries(props).map(([name, child]) => ({
286
+ name,
287
+ schema: child,
288
+ required: required.includes(name)
289
+ }));
290
+ return { fields, seen };
291
+ }
292
+ return { fields: [], seen };
293
+ }
294
+ function childFields(schema, resolve, seen = /* @__PURE__ */ new Set()) {
295
+ return resolveChildren(schema, resolve, seen).fields;
296
+ }
297
+ function hasChildren(schema, resolve, seen = /* @__PURE__ */ new Set()) {
298
+ return resolveChildren(schema, resolve, seen).fields.length > 0;
299
+ }
300
+
301
+ export {
302
+ SCHEMA_TYPE_OPTIONS,
303
+ COMPOSITE_KEYWORDS,
304
+ isRef,
305
+ compositeKeyword,
306
+ schemaKind,
307
+ schemaTypeLabel,
308
+ refName,
309
+ createSchemaForType,
310
+ createCompositeSchema,
311
+ createRefSchema,
312
+ applyPatch,
313
+ renameKey,
314
+ uniqueKey,
315
+ insertKeyAt,
316
+ moveKey,
317
+ SHARED_SLUG,
318
+ extractRefOptions,
319
+ extractSharedRefOptions,
320
+ isSharedRef,
321
+ buildSharedRef,
322
+ walkRefs,
323
+ resolvePointer,
324
+ findDanglingRefs,
325
+ componentSkeleton,
326
+ COMPONENT_NAME_RE,
327
+ validateComponentName,
328
+ createRefResolver,
329
+ humanTypeLabel,
330
+ resolveChildren,
331
+ childFields,
332
+ hasChildren
333
+ };
@@ -0,0 +1,84 @@
1
+ import { SchemaOrRef, RefResolver } from './schema/index.js';
2
+ export { COMPONENT_NAME_RE, COMPOSITE_KEYWORDS, ChildField, CompositeKeyword, FoundRef, RefObject, ResolvedChildren, SCHEMA_TYPE_OPTIONS, SHARED_SLUG, SchemaKind, SchemaObject, SchemaType, applyPatch, buildSharedRef, childFields, componentSkeleton, compositeKeyword, createCompositeSchema, createRefResolver, createRefSchema, createSchemaForType, extractRefOptions, extractSharedRefOptions, findDanglingRefs, hasChildren, humanTypeLabel, insertKeyAt, isRef, isSharedRef, moveKey, refName, renameKey, resolveChildren, resolvePointer, schemaKind, schemaTypeLabel, uniqueKey, validateComponentName, walkRefs } from './schema/index.js';
3
+ import * as react from 'react';
4
+ import { ClassValue } from 'clsx';
5
+
6
+ interface MarkdownProps {
7
+ children: string;
8
+ /** Compact variant for field-row descriptions (smaller, muted). */
9
+ compact?: boolean;
10
+ className?: string;
11
+ }
12
+ /**
13
+ * Markdown viewer for OpenAPI `description` fields (CommonMark + GFM).
14
+ * Raw HTML is NOT rendered (react-markdown default — safe for untrusted
15
+ * specs); links open in a new tab.
16
+ */
17
+ declare function Markdown({ children, compact, className }: MarkdownProps): react.JSX.Element;
18
+
19
+ declare function ConstraintPills({ schema }: {
20
+ schema: SchemaOrRef | undefined;
21
+ }): react.JSX.Element | null;
22
+
23
+ declare function SchemaFields({ schema, resolve, seen, }: {
24
+ schema: SchemaOrRef | undefined;
25
+ resolve?: RefResolver;
26
+ seen?: Set<string>;
27
+ }): react.JSX.Element;
28
+
29
+ interface NavOperation {
30
+ id: string;
31
+ method: string;
32
+ path: string;
33
+ label: string;
34
+ deprecated?: boolean;
35
+ }
36
+ interface NavArtifact {
37
+ groups: {
38
+ tag: string;
39
+ operations: NavOperation[];
40
+ }[];
41
+ schemas: string[];
42
+ }
43
+ interface MetaArtifact {
44
+ title: string;
45
+ description?: string;
46
+ specVersion: string;
47
+ openapi: string;
48
+ counts: {
49
+ operations: number;
50
+ schemas: number;
51
+ };
52
+ }
53
+
54
+ /**
55
+ * The published API reference (Stripe-style): sticky sidebar (tag >
56
+ * operations + schemas) and one long document — operation sections with
57
+ * parameters / request body / responses, then a schema section. A client
58
+ * component because SchemaFields expands refs through a resolver function;
59
+ * Next still server-renders it to HTML, which is what SEO needs.
60
+ *
61
+ * Phase 1 renders from the full publication spec; page-level chunking is a
62
+ * follow-up (portal.md, deferred list).
63
+ */
64
+ type AnyRecord = Record<string, unknown>;
65
+ declare function MethodBadge({ method, small }: {
66
+ method: string;
67
+ small?: boolean;
68
+ }): react.JSX.Element;
69
+ declare function OperationSection({ spec, id, method, path, resolve, }: {
70
+ spec: AnyRecord;
71
+ id: string;
72
+ method: string;
73
+ path: string;
74
+ resolve: RefResolver;
75
+ }): react.JSX.Element | null;
76
+ declare function ApiReference({ spec, nav, meta, }: {
77
+ spec: AnyRecord;
78
+ nav: NavArtifact;
79
+ meta: MetaArtifact;
80
+ }): react.JSX.Element;
81
+
82
+ declare function cn(...inputs: ClassValue[]): string;
83
+
84
+ export { ApiReference, ConstraintPills, Markdown, type MarkdownProps, type MetaArtifact, MethodBadge, type NavArtifact, type NavOperation, OperationSection, RefResolver, SchemaFields, SchemaOrRef, cn };
package/dist/index.js ADDED
@@ -0,0 +1,449 @@
1
+ import {
2
+ COMPONENT_NAME_RE,
3
+ COMPOSITE_KEYWORDS,
4
+ SCHEMA_TYPE_OPTIONS,
5
+ SHARED_SLUG,
6
+ applyPatch,
7
+ buildSharedRef,
8
+ childFields,
9
+ componentSkeleton,
10
+ compositeKeyword,
11
+ createCompositeSchema,
12
+ createRefResolver,
13
+ createRefSchema,
14
+ createSchemaForType,
15
+ extractRefOptions,
16
+ extractSharedRefOptions,
17
+ findDanglingRefs,
18
+ hasChildren,
19
+ humanTypeLabel,
20
+ insertKeyAt,
21
+ isRef,
22
+ isSharedRef,
23
+ moveKey,
24
+ refName,
25
+ renameKey,
26
+ resolveChildren,
27
+ resolvePointer,
28
+ schemaKind,
29
+ schemaTypeLabel,
30
+ uniqueKey,
31
+ validateComponentName,
32
+ walkRefs
33
+ } from "./chunk-WO4ZCICV.js";
34
+
35
+ // src/components/markdown.tsx
36
+ import ReactMarkdown from "react-markdown";
37
+ import remarkGfm from "remark-gfm";
38
+
39
+ // src/cn.ts
40
+ import { clsx } from "clsx";
41
+ import { twMerge } from "tailwind-merge";
42
+ function cn(...inputs) {
43
+ return twMerge(clsx(inputs));
44
+ }
45
+
46
+ // src/components/markdown.tsx
47
+ import { jsx } from "react/jsx-runtime";
48
+ function Markdown({ children, compact, className }) {
49
+ return /* @__PURE__ */ jsx(
50
+ "div",
51
+ {
52
+ className: cn(
53
+ "prose prose-sm max-w-none dark:prose-invert",
54
+ // tighten spacing for inline-doc contexts
55
+ "prose-p:my-1 prose-ul:my-1 prose-ol:my-1 prose-li:my-0 prose-headings:mb-1 prose-headings:mt-2",
56
+ "prose-pre:my-2 prose-pre:rounded-md prose-pre:bg-muted prose-pre:p-3 prose-pre:text-foreground",
57
+ "prose-code:rounded prose-code:bg-muted prose-code:px-1 prose-code:py-px prose-code:font-mono",
58
+ "prose-code:before:content-none prose-code:after:content-none",
59
+ "prose-table:my-2 prose-th:px-2 prose-th:py-1 prose-td:px-2 prose-td:py-1",
60
+ compact && "text-[13px] leading-snug text-muted-foreground prose-code:text-xs",
61
+ className
62
+ ),
63
+ children: /* @__PURE__ */ jsx(
64
+ ReactMarkdown,
65
+ {
66
+ remarkPlugins: [remarkGfm],
67
+ components: {
68
+ a: ({ href, children: kids }) => /* @__PURE__ */ jsx("a", { href, target: "_blank", rel: "noopener noreferrer", children: kids })
69
+ },
70
+ children
71
+ }
72
+ )
73
+ }
74
+ );
75
+ }
76
+
77
+ // src/components/constraint-pills.tsx
78
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
79
+ function Pill({ highlight, children }) {
80
+ return /* @__PURE__ */ jsx2(
81
+ "span",
82
+ {
83
+ className: cn(
84
+ "inline-flex max-w-[460px] items-center gap-1 truncate rounded border px-1.5 py-px text-[11px] whitespace-nowrap",
85
+ highlight && "border-transparent bg-orange-600 text-white dark:bg-orange-700"
86
+ ),
87
+ children
88
+ }
89
+ );
90
+ }
91
+ function KV({ k, v }) {
92
+ return /* @__PURE__ */ jsxs(Pill, { children: [
93
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
94
+ k,
95
+ ":"
96
+ ] }),
97
+ /* @__PURE__ */ jsx2("span", { className: "truncate font-mono", children: typeof v === "string" ? v : JSON.stringify(v) })
98
+ ] });
99
+ }
100
+ function rangeText(name, min, max, exclusiveMin, exclusiveMax) {
101
+ const parts = [];
102
+ if (min !== void 0) parts.push(`${name} ${exclusiveMin ? ">" : ">="} ${min}`);
103
+ if (max !== void 0) parts.push(`${name} ${exclusiveMax ? "<" : "<="} ${max}`);
104
+ return parts.length ? parts.join(" and ") : null;
105
+ }
106
+ function ConstraintPills({ schema }) {
107
+ if (!schema || isRef(schema)) return null;
108
+ const s = schema;
109
+ const pills = [];
110
+ const push = (key, node) => pills.push(/* @__PURE__ */ jsx2("span", { children: node }, key));
111
+ if (s.deprecated) push("deprecated", /* @__PURE__ */ jsx2(Pill, { highlight: true, children: "deprecated" }));
112
+ if (s["x-internal"]) push("internal", /* @__PURE__ */ jsx2(Pill, { highlight: true, children: "internal" }));
113
+ if (s["x-debug"]) push("debug", /* @__PURE__ */ jsx2(Pill, { highlight: true, children: "debug" }));
114
+ if (s.format) push("format", /* @__PURE__ */ jsx2(KV, { k: "format", v: s.format }));
115
+ if (Array.isArray(s.enum) && s.enum.length) {
116
+ push("enum", /* @__PURE__ */ jsx2(KV, { k: "enum", v: s.enum.map((v) => String(v)).join(", ") }));
117
+ }
118
+ if (s.pattern) push("pattern", /* @__PURE__ */ jsx2(KV, { k: "pattern", v: s.pattern }));
119
+ {
120
+ const r = rangeText("length", s.minLength, s.maxLength, false, false);
121
+ if (r) push("length", /* @__PURE__ */ jsx2(Pill, { children: r }));
122
+ }
123
+ {
124
+ const r = rangeText(
125
+ "value",
126
+ s.minimum,
127
+ s.maximum,
128
+ s.exclusiveMinimum === true,
129
+ s.exclusiveMaximum === true
130
+ );
131
+ if (r) push("range", /* @__PURE__ */ jsx2(Pill, { children: r }));
132
+ }
133
+ if (s.multipleOf !== void 0) push("multipleOf", /* @__PURE__ */ jsx2(KV, { k: "multipleOf", v: s.multipleOf }));
134
+ {
135
+ const r = rangeText("items", s.minItems, s.maxItems, false, false);
136
+ if (r) push("items", /* @__PURE__ */ jsx2(Pill, { children: r }));
137
+ }
138
+ if (s.uniqueItems) push("unique", /* @__PURE__ */ jsx2(Pill, { children: "uniqueItems" }));
139
+ if (s.title) push("title", /* @__PURE__ */ jsx2(KV, { k: "title", v: s.title }));
140
+ if (s.default !== void 0) push("default", /* @__PURE__ */ jsx2(KV, { k: "default", v: s.default }));
141
+ if (s.const !== void 0) push("const", /* @__PURE__ */ jsx2(KV, { k: "const", v: s.const }));
142
+ if (s.readOnly) push("readOnly", /* @__PURE__ */ jsx2(Pill, { children: "readOnly" }));
143
+ if (s.writeOnly) push("writeOnly", /* @__PURE__ */ jsx2(Pill, { children: "writeOnly" }));
144
+ if (s.example !== void 0) push("example", /* @__PURE__ */ jsx2(KV, { k: "example", v: s.example }));
145
+ if (!pills.length) return null;
146
+ return /* @__PURE__ */ jsx2("div", { className: "mt-1 flex flex-wrap items-center gap-1", children: pills });
147
+ }
148
+
149
+ // src/components/schema-fields.tsx
150
+ import { useState } from "react";
151
+ import { ChevronDown, ChevronRight } from "lucide-react";
152
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
153
+ function FieldRow({
154
+ name,
155
+ schema,
156
+ required,
157
+ resolve,
158
+ seen
159
+ }) {
160
+ const [open, setOpen] = useState(false);
161
+ const expandable = hasChildren(schema, resolve, seen);
162
+ const description = !isRef(schema) ? schema.description : void 0;
163
+ return /* @__PURE__ */ jsxs2("div", { className: "border-b py-2.5 last:border-b-0", children: [
164
+ /* @__PURE__ */ jsxs2("div", { className: "flex flex-wrap items-baseline gap-x-2", children: [
165
+ /* @__PURE__ */ jsx3("span", { className: "font-mono text-[13px] font-medium", children: name }),
166
+ /* @__PURE__ */ jsx3("span", { className: "font-mono text-xs text-muted-foreground", children: humanTypeLabel(schema, resolve) }),
167
+ required && /* @__PURE__ */ jsx3("span", { className: "text-[11px] font-medium text-red-600 dark:text-red-400", children: "required" })
168
+ ] }),
169
+ description && /* @__PURE__ */ jsx3(Markdown, { compact: true, className: "mt-0.5", children: description }),
170
+ /* @__PURE__ */ jsx3(ConstraintPills, { schema }),
171
+ expandable && /* @__PURE__ */ jsxs2("div", { className: "mt-1.5", children: [
172
+ /* @__PURE__ */ jsxs2(
173
+ "button",
174
+ {
175
+ type: "button",
176
+ onClick: () => setOpen((o) => !o),
177
+ className: "inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[11px] text-muted-foreground hover:bg-accent",
178
+ children: [
179
+ open ? /* @__PURE__ */ jsx3(ChevronDown, { className: "size-3" }) : /* @__PURE__ */ jsx3(ChevronRight, { className: "size-3" }),
180
+ open ? "Hide child attributes" : "Show child attributes"
181
+ ]
182
+ }
183
+ ),
184
+ open && /* @__PURE__ */ jsx3("div", { className: "ml-1.5 mt-1.5 border-l pl-3", children: /* @__PURE__ */ jsx3(SchemaFields, { schema, resolve, seen }) })
185
+ ] })
186
+ ] });
187
+ }
188
+ function SchemaFields({
189
+ schema,
190
+ resolve,
191
+ seen = /* @__PURE__ */ new Set()
192
+ }) {
193
+ const { fields, seen: childSeen } = resolveChildren(schema, resolve, seen);
194
+ if (!fields.length) {
195
+ return /* @__PURE__ */ jsx3("p", { className: "py-1 text-xs text-muted-foreground", children: "No attributes" });
196
+ }
197
+ return /* @__PURE__ */ jsx3("div", { children: fields.map((f) => /* @__PURE__ */ jsx3(
198
+ FieldRow,
199
+ {
200
+ name: f.name,
201
+ schema: f.schema,
202
+ required: f.required,
203
+ resolve,
204
+ seen: childSeen
205
+ },
206
+ f.name
207
+ )) });
208
+ }
209
+
210
+ // src/components/api-reference.tsx
211
+ import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
212
+ var METHOD_COLOR = {
213
+ GET: "bg-emerald-600",
214
+ POST: "bg-sky-600",
215
+ PUT: "bg-amber-600",
216
+ PATCH: "bg-orange-600",
217
+ DELETE: "bg-red-600",
218
+ HEAD: "bg-slate-500",
219
+ OPTIONS: "bg-slate-500",
220
+ TRACE: "bg-slate-500"
221
+ };
222
+ function MethodBadge({ method, small }) {
223
+ return /* @__PURE__ */ jsx4(
224
+ "span",
225
+ {
226
+ className: `inline-flex items-center justify-center rounded font-mono font-semibold text-white ${METHOD_COLOR[method] ?? "bg-slate-500"} ${small ? "px-1 py-px text-[9px]" : "px-1.5 py-0.5 text-[11px]"}`,
227
+ children: method
228
+ }
229
+ );
230
+ }
231
+ function deref(spec, node) {
232
+ let current = node;
233
+ for (let hops = 0; hops < 8; hops++) {
234
+ if (!current || typeof current !== "object") break;
235
+ const ref = current.$ref;
236
+ if (typeof ref !== "string" || !ref.startsWith("#/")) break;
237
+ current = resolvePointer(spec, ref);
238
+ }
239
+ return current;
240
+ }
241
+ function pickContent(content) {
242
+ if (!content || typeof content !== "object") return null;
243
+ const entries = Object.entries(content);
244
+ if (!entries.length) return null;
245
+ const [mediaType, body] = entries.find(([k]) => k.includes("json")) ?? entries[0];
246
+ const schema = body?.schema;
247
+ return { mediaType, schema };
248
+ }
249
+ function ParameterRows({
250
+ spec,
251
+ parameters,
252
+ resolve
253
+ }) {
254
+ return /* @__PURE__ */ jsx4("div", { className: "divide-y rounded-md border", children: parameters.map((raw, i) => {
255
+ const p = deref(spec, raw);
256
+ if (!p || typeof p !== "object") return null;
257
+ const schema = p.schema;
258
+ return /* @__PURE__ */ jsxs3("div", { className: "px-3 py-2", children: [
259
+ /* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap items-baseline gap-2", children: [
260
+ /* @__PURE__ */ jsx4("code", { className: "font-mono text-[13px] font-semibold", children: String(p.name ?? "") }),
261
+ /* @__PURE__ */ jsx4("span", { className: "text-xs text-muted-foreground", children: String(p.in ?? "") }),
262
+ /* @__PURE__ */ jsx4("span", { className: "text-xs text-muted-foreground", children: humanTypeLabel(schema, resolve) }),
263
+ p.required === true && /* @__PURE__ */ jsx4("span", { className: "text-[11px] font-medium text-red-600", children: "required" }),
264
+ p.deprecated === true && /* @__PURE__ */ jsx4("span", { className: "text-[11px] font-medium text-orange-600", children: "deprecated" })
265
+ ] }),
266
+ /* @__PURE__ */ jsx4(ConstraintPills, { schema }),
267
+ typeof p.description === "string" && p.description && /* @__PURE__ */ jsx4(Markdown, { compact: true, children: p.description })
268
+ ] }, i);
269
+ }) });
270
+ }
271
+ function SectionLabel({ children }) {
272
+ return /* @__PURE__ */ jsx4("h4", { className: "mb-2 mt-5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground", children });
273
+ }
274
+ function OperationSection({
275
+ spec,
276
+ id,
277
+ method,
278
+ path,
279
+ resolve
280
+ }) {
281
+ const op = spec.paths?.[path]?.[method.toLowerCase()];
282
+ if (!op) return null;
283
+ const pathItem = spec.paths?.[path];
284
+ const parameters = [
285
+ ...Array.isArray(pathItem?.parameters) ? pathItem.parameters : [],
286
+ ...Array.isArray(op.parameters) ? op.parameters : []
287
+ ];
288
+ const requestBody = op.requestBody ? deref(spec, op.requestBody) : null;
289
+ const requestContent = requestBody ? pickContent(requestBody.content) : null;
290
+ const responses = Object.entries(op.responses ?? {});
291
+ return /* @__PURE__ */ jsxs3("section", { id, className: "scroll-mt-20 border-t py-8 first:border-t-0", children: [
292
+ /* @__PURE__ */ jsxs3("h3", { className: "text-lg font-semibold", children: [
293
+ typeof op.summary === "string" && op.summary ? op.summary : typeof op.operationId === "string" && op.operationId ? op.operationId : `${method} ${path}`,
294
+ op.deprecated === true && /* @__PURE__ */ jsx4("span", { className: "ml-2 align-middle text-xs font-medium text-orange-600", children: "deprecated" })
295
+ ] }),
296
+ /* @__PURE__ */ jsxs3("div", { className: "mt-2 flex items-center gap-2", children: [
297
+ /* @__PURE__ */ jsx4(MethodBadge, { method }),
298
+ /* @__PURE__ */ jsx4("code", { className: "font-mono text-[13px]", children: path })
299
+ ] }),
300
+ typeof op.description === "string" && op.description && /* @__PURE__ */ jsx4("div", { className: "mt-3", children: /* @__PURE__ */ jsx4(Markdown, { children: op.description }) }),
301
+ parameters.length > 0 && /* @__PURE__ */ jsxs3(Fragment, { children: [
302
+ /* @__PURE__ */ jsx4(SectionLabel, { children: "Parameters" }),
303
+ /* @__PURE__ */ jsx4(ParameterRows, { spec, parameters, resolve })
304
+ ] }),
305
+ requestContent && /* @__PURE__ */ jsxs3(Fragment, { children: [
306
+ /* @__PURE__ */ jsxs3(SectionLabel, { children: [
307
+ "Request body",
308
+ " ",
309
+ /* @__PURE__ */ jsx4("span", { className: "font-mono font-normal normal-case", children: requestContent.mediaType })
310
+ ] }),
311
+ typeof requestBody?.description === "string" && requestBody.description && /* @__PURE__ */ jsx4(Markdown, { compact: true, children: requestBody.description }),
312
+ /* @__PURE__ */ jsx4("div", { className: "rounded-md border px-3 py-1", children: /* @__PURE__ */ jsx4(SchemaFields, { schema: requestContent.schema, resolve }) })
313
+ ] }),
314
+ responses.length > 0 && /* @__PURE__ */ jsxs3(Fragment, { children: [
315
+ /* @__PURE__ */ jsx4(SectionLabel, { children: "Responses" }),
316
+ /* @__PURE__ */ jsx4("div", { className: "space-y-3", children: responses.map(([status, raw]) => {
317
+ const res = deref(spec, raw);
318
+ if (!res || typeof res !== "object") return null;
319
+ const content = pickContent(res.content);
320
+ return /* @__PURE__ */ jsxs3("div", { className: "rounded-md border", children: [
321
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-baseline gap-2 border-b bg-muted/50 px-3 py-1.5", children: [
322
+ /* @__PURE__ */ jsx4("code", { className: "font-mono text-[13px] font-semibold", children: status }),
323
+ typeof res.description === "string" && res.description && /* @__PURE__ */ jsx4("span", { className: "text-xs text-muted-foreground", children: res.description })
324
+ ] }),
325
+ content?.schema ? /* @__PURE__ */ jsx4("div", { className: "px-3 py-1", children: /* @__PURE__ */ jsx4(SchemaFields, { schema: content.schema, resolve }) }) : /* @__PURE__ */ jsx4("p", { className: "px-3 py-2 text-xs text-muted-foreground", children: "No content" })
326
+ ] }, status);
327
+ }) })
328
+ ] })
329
+ ] });
330
+ }
331
+ function ApiReference({
332
+ spec,
333
+ nav,
334
+ meta
335
+ }) {
336
+ const resolve = createRefResolver(spec, null);
337
+ const schemas = spec.components?.schemas ?? {};
338
+ return /* @__PURE__ */ jsxs3("div", { className: "mx-auto flex max-w-6xl gap-10 px-6", children: [
339
+ /* @__PURE__ */ jsxs3("aside", { className: "sticky top-0 hidden h-screen w-60 shrink-0 overflow-y-auto py-10 md:block", children: [
340
+ /* @__PURE__ */ jsx4("p", { className: "truncate text-sm font-semibold", children: meta.title }),
341
+ /* @__PURE__ */ jsx4("p", { className: "font-mono text-xs text-muted-foreground", children: meta.specVersion }),
342
+ /* @__PURE__ */ jsxs3("nav", { className: "mt-6 space-y-5", children: [
343
+ nav.groups.map((group) => /* @__PURE__ */ jsxs3("div", { children: [
344
+ /* @__PURE__ */ jsx4("p", { className: "mb-1 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground", children: group.tag }),
345
+ /* @__PURE__ */ jsx4("ul", { children: group.operations.map((op) => /* @__PURE__ */ jsx4("li", { children: /* @__PURE__ */ jsxs3(
346
+ "a",
347
+ {
348
+ href: `#${op.id}`,
349
+ className: "flex items-center gap-1.5 rounded px-1 py-0.5 text-[13px] hover:bg-accent",
350
+ children: [
351
+ /* @__PURE__ */ jsx4(MethodBadge, { method: op.method, small: true }),
352
+ /* @__PURE__ */ jsx4("span", { className: `truncate ${op.deprecated ? "line-through opacity-60" : ""}`, children: op.label })
353
+ ]
354
+ }
355
+ ) }, op.id)) })
356
+ ] }, group.tag)),
357
+ nav.schemas.length > 0 && /* @__PURE__ */ jsxs3("div", { children: [
358
+ /* @__PURE__ */ jsx4("p", { className: "mb-1 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground", children: "Schemas" }),
359
+ /* @__PURE__ */ jsx4("ul", { children: nav.schemas.map((name) => /* @__PURE__ */ jsx4("li", { children: /* @__PURE__ */ jsx4(
360
+ "a",
361
+ {
362
+ href: `#schema-${name}`,
363
+ className: "block truncate rounded px-1 py-0.5 font-mono text-[13px] hover:bg-accent",
364
+ children: name
365
+ }
366
+ ) }, name)) })
367
+ ] })
368
+ ] })
369
+ ] }),
370
+ /* @__PURE__ */ jsxs3("main", { className: "min-w-0 flex-1 py-10", children: [
371
+ /* @__PURE__ */ jsxs3("header", { className: "pb-6", children: [
372
+ /* @__PURE__ */ jsx4("h1", { className: "text-3xl font-semibold", children: meta.title }),
373
+ /* @__PURE__ */ jsxs3("p", { className: "mt-1 font-mono text-xs text-muted-foreground", children: [
374
+ "v",
375
+ meta.specVersion,
376
+ " \xB7 OpenAPI ",
377
+ meta.openapi
378
+ ] }),
379
+ meta.description && /* @__PURE__ */ jsx4("div", { className: "mt-4", children: /* @__PURE__ */ jsx4(Markdown, { children: meta.description }) })
380
+ ] }),
381
+ nav.groups.map((group) => /* @__PURE__ */ jsxs3("div", { children: [
382
+ /* @__PURE__ */ jsx4("h2", { className: "mt-10 border-b pb-2 text-xl font-semibold", children: group.tag }),
383
+ group.operations.map((op) => /* @__PURE__ */ jsx4(
384
+ OperationSection,
385
+ {
386
+ spec,
387
+ id: op.id,
388
+ method: op.method,
389
+ path: op.path,
390
+ resolve
391
+ },
392
+ op.id
393
+ ))
394
+ ] }, group.tag)),
395
+ nav.schemas.length > 0 && /* @__PURE__ */ jsxs3("div", { children: [
396
+ /* @__PURE__ */ jsx4("h2", { className: "mt-10 border-b pb-2 text-xl font-semibold", children: "Schemas" }),
397
+ nav.schemas.map((name) => {
398
+ const schema = schemas[name];
399
+ return /* @__PURE__ */ jsxs3("section", { id: `schema-${name}`, className: "scroll-mt-20 border-t py-6 first:border-t-0", children: [
400
+ /* @__PURE__ */ jsx4("h3", { className: "font-mono text-base font-semibold", children: name }),
401
+ !isRef(schema) && typeof schema?.description === "string" && schema.description && /* @__PURE__ */ jsx4(Markdown, { compact: true, children: schema.description }),
402
+ /* @__PURE__ */ jsx4(ConstraintPills, { schema }),
403
+ /* @__PURE__ */ jsx4("div", { className: "mt-2 rounded-md border px-3 py-1", children: /* @__PURE__ */ jsx4(SchemaFields, { schema, resolve }) })
404
+ ] }, name);
405
+ })
406
+ ] })
407
+ ] })
408
+ ] });
409
+ }
410
+ export {
411
+ ApiReference,
412
+ COMPONENT_NAME_RE,
413
+ COMPOSITE_KEYWORDS,
414
+ ConstraintPills,
415
+ Markdown,
416
+ MethodBadge,
417
+ OperationSection,
418
+ SCHEMA_TYPE_OPTIONS,
419
+ SHARED_SLUG,
420
+ SchemaFields,
421
+ applyPatch,
422
+ buildSharedRef,
423
+ childFields,
424
+ cn,
425
+ componentSkeleton,
426
+ compositeKeyword,
427
+ createCompositeSchema,
428
+ createRefResolver,
429
+ createRefSchema,
430
+ createSchemaForType,
431
+ extractRefOptions,
432
+ extractSharedRefOptions,
433
+ findDanglingRefs,
434
+ hasChildren,
435
+ humanTypeLabel,
436
+ insertKeyAt,
437
+ isRef,
438
+ isSharedRef,
439
+ moveKey,
440
+ refName,
441
+ renameKey,
442
+ resolveChildren,
443
+ resolvePointer,
444
+ schemaKind,
445
+ schemaTypeLabel,
446
+ uniqueKey,
447
+ validateComponentName,
448
+ walkRefs
449
+ };
@@ -0,0 +1,165 @@
1
+ /**
2
+ * OpenAPI 3.x Schema Object — the editor's working type.
3
+ *
4
+ * document-centric: this IS the plain spec object (no internal model).
5
+ * `$ref` nodes are `{ $ref: '#/components/schemas/X' }`.
6
+ */
7
+ interface RefObject {
8
+ $ref: string;
9
+ [key: string]: unknown;
10
+ }
11
+ type SchemaType = 'object' | 'array' | 'string' | 'number' | 'integer' | 'boolean' | 'null';
12
+ interface SchemaObject {
13
+ type?: SchemaType;
14
+ properties?: Record<string, SchemaOrRef>;
15
+ required?: string[];
16
+ additionalProperties?: boolean | SchemaOrRef;
17
+ items?: SchemaOrRef;
18
+ minItems?: number;
19
+ maxItems?: number;
20
+ uniqueItems?: boolean;
21
+ allOf?: SchemaOrRef[];
22
+ oneOf?: SchemaOrRef[];
23
+ anyOf?: SchemaOrRef[];
24
+ not?: SchemaOrRef;
25
+ format?: string;
26
+ pattern?: string;
27
+ minLength?: number;
28
+ maxLength?: number;
29
+ minimum?: number;
30
+ maximum?: number;
31
+ exclusiveMinimum?: boolean | number;
32
+ exclusiveMaximum?: boolean | number;
33
+ multipleOf?: number;
34
+ enum?: unknown[];
35
+ const?: unknown;
36
+ default?: unknown;
37
+ example?: unknown;
38
+ examples?: unknown[];
39
+ title?: string;
40
+ description?: string;
41
+ nullable?: boolean;
42
+ readOnly?: boolean;
43
+ writeOnly?: boolean;
44
+ deprecated?: boolean;
45
+ 'x-internal'?: boolean;
46
+ 'x-debug'?: boolean;
47
+ [key: string]: unknown;
48
+ }
49
+ type SchemaOrRef = SchemaObject | RefObject;
50
+ /** What a node "is", driving which editor renders it. */
51
+ type SchemaKind = 'ref' | 'object' | 'array' | 'composite' | 'enum' | 'primitive' | 'any';
52
+ type CompositeKeyword = 'allOf' | 'oneOf' | 'anyOf';
53
+ /** Type options offered by the TypePicker. */
54
+ declare const SCHEMA_TYPE_OPTIONS: readonly ["object", "array", "string", "number", "integer", "boolean", "null"];
55
+ declare const COMPOSITE_KEYWORDS: readonly ["allOf", "oneOf", "anyOf"];
56
+
57
+ declare function isRef(s: SchemaOrRef | undefined | null): s is RefObject;
58
+ declare function compositeKeyword(s: SchemaOrRef | undefined | null): CompositeKeyword | null;
59
+ /** Classify a node — drives which node editor renders it. */
60
+ declare function schemaKind(s: SchemaOrRef | undefined | null): SchemaKind;
61
+ /** Short display label for a node's type (tree row, previews). */
62
+ declare function schemaTypeLabel(s: SchemaOrRef | undefined | null): string;
63
+ /** Last segment of a $ref, e.g. '#/components/schemas/Pet' → 'Pet'. */
64
+ declare function refName(ref: string): string;
65
+
66
+ /**
67
+ * Build the schema for a node whose type just changed, retaining
68
+ * cross-type metadata from the previous schema.
69
+ */
70
+ declare function createSchemaForType(type: SchemaType, prev?: SchemaOrRef): SchemaObject;
71
+ /** Switch a node to a composite keyword, seeding it with the previous schema when sensible. */
72
+ declare function createCompositeSchema(keyword: 'allOf' | 'oneOf' | 'anyOf', prev?: SchemaOrRef): SchemaObject;
73
+ /** Switch a node to a $ref (metadata cannot live on a ref; it is dropped by design). */
74
+ declare function createRefSchema(ref: string): SchemaOrRef;
75
+
76
+ /**
77
+ * Merge a partial edit into a schema. Keys whose patch value is `undefined`
78
+ * are REMOVED — so clearing a field truly deletes it from the spec
79
+ * (document-centric: no `minLength: undefined` noise).
80
+ */
81
+ declare function applyPatch(schema: SchemaObject, patch: Partial<SchemaObject>): SchemaObject;
82
+
83
+ /**
84
+ * Helpers for editing `properties` records. JS objects preserve insertion
85
+ * order, and property order matters in the editor (move up/down), so these
86
+ * always rebuild records order-preservingly.
87
+ */
88
+ /** Rename a key, keeping its position. No-op if oldKey is missing or newKey exists. */
89
+ declare function renameKey<T>(record: Record<string, T>, oldKey: string, newKey: string): Record<string, T>;
90
+ /** First `base`, `base2`, `base3`… not present in the record. */
91
+ declare function uniqueKey(record: Record<string, unknown>, base?: string): string;
92
+ /** Insert a key at an index (clamped), preserving the order of the rest. */
93
+ declare function insertKeyAt<T>(record: Record<string, T>, key: string, value: T, index: number): Record<string, T>;
94
+ /** Move a key to a new index (clamped), preserving the order of the rest. */
95
+ declare function moveKey<T>(record: Record<string, T>, key: string, toIndex: number): Record<string, T>;
96
+
97
+ /** The reserved document slug for a project's shared components. */
98
+ declare const SHARED_SLUG = "shared";
99
+ /** All `#/components/schemas/X` pointers a TypePicker can offer, from a spec. */
100
+ declare function extractRefOptions(spec: Record<string, unknown>): string[];
101
+ /** Cross-document pointers into the project's shared components. */
102
+ declare function extractSharedRefOptions(sharedSpec: Record<string, unknown>): string[];
103
+ declare function isSharedRef(ref: string): boolean;
104
+ declare function buildSharedRef(name: string): string;
105
+ interface FoundRef {
106
+ /** JSON path of the node holding the $ref. */
107
+ path: (string | number)[];
108
+ ref: string;
109
+ }
110
+ /** Walk a spec and collect every `$ref` with its location. */
111
+ declare function walkRefs(node: unknown, path?: (string | number)[], out?: FoundRef[]): FoundRef[];
112
+ /** Resolve a JSON pointer fragment ('#/components/schemas/X') against a spec. */
113
+ declare function resolvePointer(spec: Record<string, unknown>, pointer: string): unknown;
114
+ /**
115
+ * Find dangling `$ref`s: local refs are resolved against the spec itself,
116
+ * `shared#/…` refs against the shared spec. Anything else is left alone
117
+ * (external URLs etc. are not our business here).
118
+ */
119
+ declare function findDanglingRefs(spec: Record<string, unknown>, sharedSpec: Record<string, unknown> | null): FoundRef[];
120
+
121
+ /**
122
+ * Default (standard-OpenAPI) skeletons for newly created components — the
123
+ * modern counterpart of the old editor's `defaultDefinition`.
124
+ */
125
+ declare function componentSkeleton(typeKey: string, name: string): Record<string, unknown>;
126
+ /** OpenAPI component-key rule: ^[a-zA-Z0-9.\-_]+$ */
127
+ declare const COMPONENT_NAME_RE: RegExp;
128
+ declare function validateComponentName(name: string, existing: string[]): string | null;
129
+
130
+ type RefResolver = (ref: string) => SchemaOrRef | undefined;
131
+ /**
132
+ * Resolver over the current spec + the project's shared spec — used by the
133
+ * Stripe-style preview to expand `$ref`s inline.
134
+ */
135
+ declare function createRefResolver(spec: Record<string, unknown>, sharedSpec: Record<string, unknown> | null): RefResolver;
136
+ /**
137
+ * Stripe-style human type label: 'string', 'integer', 'object',
138
+ * 'array of strings', 'array of Pet', 'enum<string>', 'one of', 'Pet'…
139
+ * Nullable is appended as ' | null' (kept subtle by the renderer).
140
+ */
141
+ declare function humanTypeLabel(schema: SchemaOrRef | undefined, resolve?: RefResolver): string;
142
+ interface ChildField {
143
+ name: string;
144
+ schema: SchemaOrRef;
145
+ required: boolean;
146
+ }
147
+ interface ResolvedChildren {
148
+ fields: ChildField[];
149
+ /** `seen` accumulated through any $refs crossed while resolving — thread
150
+ * this to the child rows so circular refs stop expanding. */
151
+ seen: Set<string>;
152
+ }
153
+ /**
154
+ * The expandable children of a node, Stripe-style:
155
+ * - object → its properties
156
+ * - array → its items' children (arrays expand straight into the item shape)
157
+ * - composite → each branch as a pseudo-field
158
+ * - $ref → children of the resolved target (ref recorded into `seen`)
159
+ */
160
+ declare function resolveChildren(schema: SchemaOrRef | undefined, resolve: RefResolver | undefined, seen?: Set<string>): ResolvedChildren;
161
+ declare function childFields(schema: SchemaOrRef | undefined, resolve: RefResolver | undefined, seen?: Set<string>): ChildField[];
162
+ /** Whether the preview should offer a "Show child attributes" expander. */
163
+ declare function hasChildren(schema: SchemaOrRef | undefined, resolve: RefResolver | undefined, seen?: Set<string>): boolean;
164
+
165
+ export { COMPONENT_NAME_RE, COMPOSITE_KEYWORDS, type ChildField, type CompositeKeyword, type FoundRef, type RefObject, type RefResolver, type ResolvedChildren, SCHEMA_TYPE_OPTIONS, SHARED_SLUG, type SchemaKind, type SchemaObject, type SchemaOrRef, type SchemaType, applyPatch, buildSharedRef, childFields, componentSkeleton, compositeKeyword, createCompositeSchema, createRefResolver, createRefSchema, createSchemaForType, extractRefOptions, extractSharedRefOptions, findDanglingRefs, hasChildren, humanTypeLabel, insertKeyAt, isRef, isSharedRef, moveKey, refName, renameKey, resolveChildren, resolvePointer, schemaKind, schemaTypeLabel, uniqueKey, validateComponentName, walkRefs };
@@ -0,0 +1,66 @@
1
+ import {
2
+ COMPONENT_NAME_RE,
3
+ COMPOSITE_KEYWORDS,
4
+ SCHEMA_TYPE_OPTIONS,
5
+ SHARED_SLUG,
6
+ applyPatch,
7
+ buildSharedRef,
8
+ childFields,
9
+ componentSkeleton,
10
+ compositeKeyword,
11
+ createCompositeSchema,
12
+ createRefResolver,
13
+ createRefSchema,
14
+ createSchemaForType,
15
+ extractRefOptions,
16
+ extractSharedRefOptions,
17
+ findDanglingRefs,
18
+ hasChildren,
19
+ humanTypeLabel,
20
+ insertKeyAt,
21
+ isRef,
22
+ isSharedRef,
23
+ moveKey,
24
+ refName,
25
+ renameKey,
26
+ resolveChildren,
27
+ resolvePointer,
28
+ schemaKind,
29
+ schemaTypeLabel,
30
+ uniqueKey,
31
+ validateComponentName,
32
+ walkRefs
33
+ } from "../chunk-WO4ZCICV.js";
34
+ export {
35
+ COMPONENT_NAME_RE,
36
+ COMPOSITE_KEYWORDS,
37
+ SCHEMA_TYPE_OPTIONS,
38
+ SHARED_SLUG,
39
+ applyPatch,
40
+ buildSharedRef,
41
+ childFields,
42
+ componentSkeleton,
43
+ compositeKeyword,
44
+ createCompositeSchema,
45
+ createRefResolver,
46
+ createRefSchema,
47
+ createSchemaForType,
48
+ extractRefOptions,
49
+ extractSharedRefOptions,
50
+ findDanglingRefs,
51
+ hasChildren,
52
+ humanTypeLabel,
53
+ insertKeyAt,
54
+ isRef,
55
+ isSharedRef,
56
+ moveKey,
57
+ refName,
58
+ renameKey,
59
+ resolveChildren,
60
+ resolvePointer,
61
+ schemaKind,
62
+ schemaTypeLabel,
63
+ uniqueKey,
64
+ validateComponentName,
65
+ walkRefs
66
+ };
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@reapi/spec-ui",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./schema": {
14
+ "types": "./dist/schema/index.d.ts",
15
+ "default": "./dist/schema/index.js"
16
+ }
17
+ },
18
+ "dependencies": {
19
+ "clsx": "^2.1.1",
20
+ "lucide-react": "^0.469.0",
21
+ "react-markdown": "^10.1.0",
22
+ "remark-gfm": "^4.0.1",
23
+ "tailwind-merge": "^3.0.0"
24
+ },
25
+ "peerDependencies": {
26
+ "react": "^19.0.0"
27
+ },
28
+ "devDependencies": {
29
+ "@types/react": "^19.0.0",
30
+ "react": "^19.1.0",
31
+ "tsup": "^8.4.0",
32
+ "typescript": "^5.7.3",
33
+ "vitest": "^4.1.8"
34
+ },
35
+ "files": [
36
+ "dist"
37
+ ],
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "scripts": {
42
+ "typecheck": "tsc --noEmit",
43
+ "test": "vitest --run",
44
+ "build": "tsup"
45
+ }
46
+ }