@doccov/fumadocs-adapter 0.0.1

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,193 @@
1
+ 'use client';
2
+
3
+ import type { SpecMember, OpenPkg } from '@openpkg-ts/spec';
4
+ import { TypeTable } from './type-table';
5
+
6
+ export interface MembersSectionProps {
7
+ members: SpecMember[];
8
+ spec?: OpenPkg;
9
+ title?: string;
10
+ }
11
+
12
+ function formatSchema(schema: unknown): string {
13
+ if (!schema) return 'unknown';
14
+ if (typeof schema === 'string') return schema;
15
+ if (typeof schema === 'object' && schema !== null) {
16
+ const s = schema as Record<string, unknown>;
17
+ if (s.$ref && typeof s.$ref === 'string') {
18
+ return s.$ref.replace('#/types/', '');
19
+ }
20
+ if (s.tsType) return String(s.tsType);
21
+ if (s.type) return String(s.type);
22
+ }
23
+ return 'unknown';
24
+ }
25
+
26
+ interface MemberGroups {
27
+ constructors: SpecMember[];
28
+ properties: SpecMember[];
29
+ methods: SpecMember[];
30
+ accessors: SpecMember[];
31
+ other: SpecMember[];
32
+ }
33
+
34
+ function groupMembersByKind(members: SpecMember[]): MemberGroups {
35
+ const groups: MemberGroups = {
36
+ constructors: [],
37
+ properties: [],
38
+ methods: [],
39
+ accessors: [],
40
+ other: [],
41
+ };
42
+
43
+ for (const member of members) {
44
+ const kind = member.kind?.toLowerCase() ?? 'other';
45
+ if (kind === 'constructor') {
46
+ groups.constructors.push(member);
47
+ } else if (kind === 'property' || kind === 'field') {
48
+ groups.properties.push(member);
49
+ } else if (kind === 'method' || kind === 'function') {
50
+ groups.methods.push(member);
51
+ } else if (kind === 'getter' || kind === 'setter' || kind === 'accessor') {
52
+ groups.accessors.push(member);
53
+ } else {
54
+ groups.other.push(member);
55
+ }
56
+ }
57
+
58
+ return groups;
59
+ }
60
+
61
+ function MemberRow({ member }: { member: SpecMember }) {
62
+ const visibility = member.visibility ?? 'public';
63
+ const isStatic = member.flags?.static;
64
+ const isAbstract = member.flags?.abstract;
65
+ const isReadonly = member.flags?.readonly;
66
+
67
+ const badges: string[] = [];
68
+ if (visibility !== 'public') badges.push(visibility);
69
+ if (isStatic) badges.push('static');
70
+ if (isAbstract) badges.push('abstract');
71
+ if (isReadonly) badges.push('readonly');
72
+
73
+ const type = formatSchema(member.schema);
74
+
75
+ // For methods, show signature
76
+ const sig = member.signatures?.[0];
77
+ let signature = '';
78
+ if (sig) {
79
+ const params = sig.parameters?.map((p) => {
80
+ const optional = p.required === false ? '?' : '';
81
+ return `${p.name}${optional}: ${formatSchema(p.schema)}`;
82
+ }) ?? [];
83
+ const returnType = sig.returns?.tsType ?? formatSchema(sig.returns?.schema) ?? 'void';
84
+ signature = `(${params.join(', ')}): ${returnType}`;
85
+ }
86
+
87
+ return (
88
+ <div className="py-3 border-b border-fd-border last:border-0">
89
+ <div className="flex items-start gap-2">
90
+ <code className="font-mono text-sm text-fd-primary">
91
+ {member.name}
92
+ {signature}
93
+ </code>
94
+ {badges.length > 0 && (
95
+ <div className="flex gap-1">
96
+ {badges.map((badge) => (
97
+ <span
98
+ key={badge}
99
+ className="text-xs px-1.5 py-0.5 rounded bg-fd-secondary text-fd-muted-foreground"
100
+ >
101
+ {badge}
102
+ </span>
103
+ ))}
104
+ </div>
105
+ )}
106
+ </div>
107
+ {!signature && type !== 'unknown' && (
108
+ <code className="text-xs text-fd-muted-foreground font-mono mt-1 block">{type}</code>
109
+ )}
110
+ {member.description && (
111
+ <p className="text-sm text-fd-muted-foreground mt-1">{member.description}</p>
112
+ )}
113
+ </div>
114
+ );
115
+ }
116
+
117
+ export function MembersSection({ members, spec, title = 'Members' }: MembersSectionProps) {
118
+ if (!members?.length) return null;
119
+
120
+ const groups = groupMembersByKind(members);
121
+
122
+ return (
123
+ <div className="my-6">
124
+ <h3 className="text-lg font-semibold mb-3">{title}</h3>
125
+
126
+ {groups.constructors.length > 0 && (
127
+ <div className="mb-6">
128
+ <h4 className="text-sm font-medium text-fd-muted-foreground mb-2 uppercase tracking-wide">
129
+ Constructor
130
+ </h4>
131
+ <div className="rounded-lg border border-fd-border bg-fd-card">
132
+ {groups.constructors.map((member, index) => (
133
+ <MemberRow key={member.name ?? index} member={member} />
134
+ ))}
135
+ </div>
136
+ </div>
137
+ )}
138
+
139
+ {groups.properties.length > 0 && (
140
+ <div className="mb-6">
141
+ <h4 className="text-sm font-medium text-fd-muted-foreground mb-2 uppercase tracking-wide">
142
+ Properties
143
+ </h4>
144
+ <div className="rounded-lg border border-fd-border bg-fd-card">
145
+ {groups.properties.map((member, index) => (
146
+ <MemberRow key={member.name ?? index} member={member} />
147
+ ))}
148
+ </div>
149
+ </div>
150
+ )}
151
+
152
+ {groups.methods.length > 0 && (
153
+ <div className="mb-6">
154
+ <h4 className="text-sm font-medium text-fd-muted-foreground mb-2 uppercase tracking-wide">
155
+ Methods
156
+ </h4>
157
+ <div className="rounded-lg border border-fd-border bg-fd-card">
158
+ {groups.methods.map((member, index) => (
159
+ <MemberRow key={member.name ?? index} member={member} />
160
+ ))}
161
+ </div>
162
+ </div>
163
+ )}
164
+
165
+ {groups.accessors.length > 0 && (
166
+ <div className="mb-6">
167
+ <h4 className="text-sm font-medium text-fd-muted-foreground mb-2 uppercase tracking-wide">
168
+ Accessors
169
+ </h4>
170
+ <div className="rounded-lg border border-fd-border bg-fd-card">
171
+ {groups.accessors.map((member, index) => (
172
+ <MemberRow key={member.name ?? index} member={member} />
173
+ ))}
174
+ </div>
175
+ </div>
176
+ )}
177
+
178
+ {groups.other.length > 0 && (
179
+ <div className="mb-6">
180
+ <h4 className="text-sm font-medium text-fd-muted-foreground mb-2 uppercase tracking-wide">
181
+ Other
182
+ </h4>
183
+ <div className="rounded-lg border border-fd-border bg-fd-card">
184
+ {groups.other.map((member, index) => (
185
+ <MemberRow key={member.name ?? index} member={member} />
186
+ ))}
187
+ </div>
188
+ </div>
189
+ )}
190
+ </div>
191
+ );
192
+ }
193
+
@@ -0,0 +1,18 @@
1
+ 'use client';
2
+
3
+ import type { SpecMember } from '@openpkg-ts/spec';
4
+ import { CollapsibleMethod } from './collapsible-method';
5
+
6
+ export interface MethodSectionProps {
7
+ member: SpecMember;
8
+ /** @deprecated Use CollapsibleMethod directly with defaultExpanded */
9
+ defaultExpanded?: boolean;
10
+ }
11
+
12
+ /**
13
+ * Method display section with collapsible behavior
14
+ * @deprecated Use CollapsibleMethod directly for more control
15
+ */
16
+ export function MethodSection({ member, defaultExpanded = false }: MethodSectionProps) {
17
+ return <CollapsibleMethod member={member} defaultExpanded={defaultExpanded} />;
18
+ }
@@ -0,0 +1,53 @@
1
+ 'use client';
2
+
3
+ import type { OpenPkg, SpecSignatureParameter } from '@openpkg-ts/spec';
4
+
5
+ export interface ParameterCardProps {
6
+ param: SpecSignatureParameter;
7
+ spec?: OpenPkg;
8
+ }
9
+
10
+ function formatSchema(schema: unknown): string {
11
+ if (!schema) return 'unknown';
12
+ if (typeof schema === 'string') return schema;
13
+ if (typeof schema === 'object' && schema !== null) {
14
+ const s = schema as Record<string, unknown>;
15
+ if (s.$ref && typeof s.$ref === 'string') {
16
+ return s.$ref.replace('#/types/', '');
17
+ }
18
+ if (s.tsType) return String(s.tsType);
19
+ if (s.type) return String(s.type);
20
+ }
21
+ return 'unknown';
22
+ }
23
+
24
+ export function ParameterCard({ param, spec }: ParameterCardProps) {
25
+ const type = formatSchema(param.schema);
26
+ const isRequired = param.required !== false;
27
+
28
+ return (
29
+ <div className="rounded-lg border border-fd-border bg-fd-card/50 p-4">
30
+ {/* Header row: name + badge */}
31
+ <div className="flex items-center gap-2 mb-1">
32
+ <span className="font-mono text-sm text-fd-foreground">{param.name}</span>
33
+ {isRequired && (
34
+ <span className="text-[10px] font-semibold px-1.5 py-0.5 rounded border border-fd-border bg-fd-muted text-fd-muted-foreground uppercase tracking-wide">
35
+ Required
36
+ </span>
37
+ )}
38
+ </div>
39
+
40
+ {/* Type */}
41
+ <div className="text-sm text-fd-muted-foreground font-mono">
42
+ {type}
43
+ </div>
44
+
45
+ {/* Description */}
46
+ {param.description && (
47
+ <p className="text-sm text-fd-muted-foreground mt-2">
48
+ {param.description}
49
+ </p>
50
+ )}
51
+ </div>
52
+ );
53
+ }
@@ -0,0 +1,108 @@
1
+ 'use client';
2
+
3
+ import { ClientDocsKitCode } from '@doccov/ui/docskit';
4
+ import type { SpecExport, SpecSignature, SpecTypeParameter } from '@openpkg-ts/spec';
5
+ import type { RawCode } from 'codehike/code';
6
+
7
+ export interface SignatureProps {
8
+ export: SpecExport;
9
+ signatureIndex?: number;
10
+ }
11
+
12
+ function formatTypeParameters(typeParams?: SpecTypeParameter[]): string {
13
+ if (!typeParams?.length) return '';
14
+ const params = typeParams.map((tp) => {
15
+ let str = tp.name;
16
+ if (tp.constraint) str += ` extends ${tp.constraint}`;
17
+ if (tp.default) str += ` = ${tp.default}`;
18
+ return str;
19
+ });
20
+ return `<${params.join(', ')}>`;
21
+ }
22
+
23
+ function formatParameters(sig?: SpecSignature): string {
24
+ if (!sig?.parameters?.length) return '()';
25
+ const params = sig.parameters.map((p) => {
26
+ const optional = p.required === false ? '?' : '';
27
+ const type = formatSchema(p.schema);
28
+ return `${p.name}${optional}: ${type}`;
29
+ });
30
+ return `(${params.join(', ')})`;
31
+ }
32
+
33
+ function formatSchema(schema: unknown): string {
34
+ if (!schema) return 'unknown';
35
+ if (typeof schema === 'string') return schema;
36
+ if (typeof schema === 'object' && schema !== null) {
37
+ const s = schema as Record<string, unknown>;
38
+ if (s.$ref && typeof s.$ref === 'string') {
39
+ return s.$ref.replace('#/types/', '');
40
+ }
41
+ if (s.tsType) return String(s.tsType);
42
+ if (s.type) return String(s.type);
43
+ }
44
+ return 'unknown';
45
+ }
46
+
47
+ function formatReturnType(sig?: SpecSignature): string {
48
+ if (!sig?.returns) return 'void';
49
+ if (sig.returns.tsType) return sig.returns.tsType;
50
+ return formatSchema(sig.returns.schema);
51
+ }
52
+
53
+ function buildSignatureString(exp: SpecExport, sigIndex = 0): string {
54
+ const sig = exp.signatures?.[sigIndex];
55
+ const typeParams = formatTypeParameters(exp.typeParameters || sig?.typeParameters);
56
+
57
+ switch (exp.kind) {
58
+ case 'function': {
59
+ const params = formatParameters(sig);
60
+ const returnType = formatReturnType(sig);
61
+ return `function ${exp.name}${typeParams}${params}: ${returnType}`;
62
+ }
63
+ case 'class': {
64
+ const ext = exp.extends ? ` extends ${exp.extends}` : '';
65
+ const impl = exp.implements?.length ? ` implements ${exp.implements.join(', ')}` : '';
66
+ return `class ${exp.name}${typeParams}${ext}${impl}`;
67
+ }
68
+ case 'interface': {
69
+ const ext = exp.extends ? ` extends ${exp.extends}` : '';
70
+ return `interface ${exp.name}${typeParams}${ext}`;
71
+ }
72
+ case 'type': {
73
+ const typeValue = typeof exp.type === 'string' ? exp.type : formatSchema(exp.schema);
74
+ return `type ${exp.name}${typeParams} = ${typeValue}`;
75
+ }
76
+ case 'enum': {
77
+ return `enum ${exp.name}`;
78
+ }
79
+ case 'variable': {
80
+ const typeValue = typeof exp.type === 'string' ? exp.type : formatSchema(exp.schema);
81
+ return `const ${exp.name}: ${typeValue}`;
82
+ }
83
+ default:
84
+ return exp.name;
85
+ }
86
+ }
87
+
88
+ export function Signature({ export: exp, signatureIndex = 0 }: SignatureProps) {
89
+ const signature = buildSignatureString(exp, signatureIndex);
90
+
91
+ // Build RawCode for syntax highlighting
92
+ const codeblock: RawCode = {
93
+ value: signature,
94
+ lang: 'typescript',
95
+ meta: 'c', // copyButton flag
96
+ };
97
+
98
+ return (
99
+ <div className="not-prose">
100
+ <ClientDocsKitCode codeblock={codeblock} />
101
+ {exp.deprecated && (
102
+ <div className="mt-2 rounded-md bg-yellow-500/10 border border-yellow-500/20 px-3 py-2 text-sm text-yellow-600 dark:text-yellow-400">
103
+ <strong>Deprecated:</strong> This export is deprecated.
104
+ </div>
105
+ )}
106
+ </div>
107
+ );
108
+ }
@@ -0,0 +1,80 @@
1
+ 'use client';
2
+
3
+ import type { OpenPkg, SpecSignatureParameter, SpecMember } from '@openpkg-ts/spec';
4
+
5
+ export interface TypeTableProps {
6
+ items: (SpecSignatureParameter | SpecMember)[];
7
+ spec?: OpenPkg;
8
+ showRequired?: boolean;
9
+ }
10
+
11
+ function formatSchema(schema: unknown): string {
12
+ if (!schema) return 'unknown';
13
+ if (typeof schema === 'string') return schema;
14
+ if (typeof schema === 'object' && schema !== null) {
15
+ const s = schema as Record<string, unknown>;
16
+ // Handle $ref
17
+ if (s.$ref && typeof s.$ref === 'string') {
18
+ const refName = s.$ref.replace('#/types/', '');
19
+ return refName;
20
+ }
21
+ // Handle type
22
+ if (s.type) return String(s.type);
23
+ // Handle tsType
24
+ if (s.tsType) return String(s.tsType);
25
+ }
26
+ return 'unknown';
27
+ }
28
+
29
+ function isParameter(item: SpecSignatureParameter | SpecMember): item is SpecSignatureParameter {
30
+ return 'required' in item;
31
+ }
32
+
33
+ export function TypeTable({ items, showRequired = true }: TypeTableProps) {
34
+ if (!items?.length) return null;
35
+
36
+ return (
37
+ <div className="my-4 overflow-x-auto">
38
+ <table className="w-full text-sm border-collapse">
39
+ <thead>
40
+ <tr className="border-b border-fd-border">
41
+ <th className="text-left py-2 px-3 font-medium text-fd-muted-foreground">Name</th>
42
+ <th className="text-left py-2 px-3 font-medium text-fd-muted-foreground">Type</th>
43
+ <th className="text-left py-2 px-3 font-medium text-fd-muted-foreground">Description</th>
44
+ </tr>
45
+ </thead>
46
+ <tbody>
47
+ {items.map((item, index) => {
48
+ const name = item.name ?? `arg${index}`;
49
+ const type = formatSchema(item.schema);
50
+ const description = item.description ?? '';
51
+ const required = isParameter(item) ? item.required : true;
52
+
53
+ return (
54
+ <tr key={name} className="border-b border-fd-border last:border-0">
55
+ <td className="py-2 px-3 align-top">
56
+ <code className="text-fd-primary font-mono text-xs bg-fd-secondary px-1.5 py-0.5 rounded">
57
+ {name}
58
+ </code>
59
+ {showRequired && required && (
60
+ <span className="ml-1 text-red-500 text-xs">*</span>
61
+ )}
62
+ {showRequired && !required && (
63
+ <span className="ml-1 text-fd-muted-foreground text-xs">?</span>
64
+ )}
65
+ </td>
66
+ <td className="py-2 px-3 align-top">
67
+ <code className="font-mono text-xs text-fd-muted-foreground">{type}</code>
68
+ </td>
69
+ <td className="py-2 px-3 align-top text-fd-muted-foreground">
70
+ {description}
71
+ </td>
72
+ </tr>
73
+ );
74
+ })}
75
+ </tbody>
76
+ </table>
77
+ </div>
78
+ );
79
+ }
80
+
@@ -0,0 +1,78 @@
1
+ 'use client';
2
+
3
+ import type { OpenPkg, SpecExport } from '@openpkg-ts/spec';
4
+ import { CodeExample } from './code-example';
5
+ import { CoverageBadge } from './coverage-badge';
6
+
7
+ export interface VariablePageProps {
8
+ export: SpecExport;
9
+ spec: OpenPkg;
10
+ }
11
+
12
+ function formatSchema(schema: unknown): string {
13
+ if (!schema) return 'unknown';
14
+ if (typeof schema === 'string') return schema;
15
+ if (typeof schema === 'object' && schema !== null) {
16
+ const s = schema as Record<string, unknown>;
17
+ if (s.$ref && typeof s.$ref === 'string') {
18
+ return s.$ref.replace('#/types/', '');
19
+ }
20
+ if (s.tsType) return String(s.tsType);
21
+ if (s.type) return String(s.type);
22
+ }
23
+ return 'unknown';
24
+ }
25
+
26
+ export function VariablePage({ export: exp, spec }: VariablePageProps) {
27
+ const typeValue = typeof exp.type === 'string' ? exp.type : formatSchema(exp.schema);
28
+ const hasExamples = exp.examples && exp.examples.length > 0;
29
+
30
+ return (
31
+ <div className="space-y-8">
32
+ {/* Description */}
33
+ {exp.description && (
34
+ <p className="text-fd-muted-foreground text-lg leading-relaxed">
35
+ {exp.description}
36
+ </p>
37
+ )}
38
+
39
+ {/* Declaration */}
40
+ <div className="rounded-lg border border-fd-border bg-fd-muted/30 p-4 overflow-x-auto">
41
+ <code className="font-mono text-sm text-fd-foreground whitespace-pre">
42
+ const {exp.name}: {typeValue}
43
+ </code>
44
+ </div>
45
+
46
+ {/* Two-column layout */}
47
+ <div className={`grid gap-8 ${hasExamples ? 'lg:grid-cols-2' : 'grid-cols-1'}`}>
48
+ {/* Left column: Type info */}
49
+ <div className="space-y-6">
50
+ <div>
51
+ <h3 className="text-sm font-semibold uppercase tracking-wide text-fd-muted-foreground mb-3">
52
+ Type
53
+ </h3>
54
+ <div className="rounded-lg border border-fd-border bg-fd-card p-4">
55
+ <code className="font-mono text-sm text-fd-primary">{typeValue}</code>
56
+ </div>
57
+ </div>
58
+ </div>
59
+
60
+ {/* Right column: Examples */}
61
+ {hasExamples && (
62
+ <div>
63
+ <h3 className="text-sm font-semibold uppercase tracking-wide text-fd-muted-foreground mb-3">
64
+ {exp.name} usage
65
+ </h3>
66
+ <CodeExample
67
+ code={exp.examples![0]}
68
+ filename={`${exp.name.toLowerCase()}.ts`}
69
+ />
70
+ </div>
71
+ )}
72
+ </div>
73
+
74
+ {/* Coverage */}
75
+ {exp.docs && <CoverageBadge docs={exp.docs} />}
76
+ </div>
77
+ );
78
+ }
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ // Server
2
+ export { createOpenPkg } from './server';
3
+ export type { OpenPkgInstance, OpenPkgOptions } from './server';
4
+
5
+ // Components
6
+ export { APIPage } from './components/api-page';
7
+ export type { APIPageProps } from './components/api-page';
8
+
package/src/server.ts ADDED
@@ -0,0 +1,80 @@
1
+ import * as fs from 'node:fs';
2
+ import type { OpenPkg, SpecExport, SpecExportKind, SpecType } from '@openpkg-ts/spec';
3
+
4
+ export interface OpenPkgOptions {
5
+ /** Path to openpkg.json file or the spec object directly */
6
+ input: string | OpenPkg;
7
+ }
8
+
9
+ export interface OpenPkgInstance {
10
+ /** The parsed OpenPkg spec */
11
+ spec: OpenPkg;
12
+ /** Get an export by its ID */
13
+ getExport(id: string): SpecExport | undefined;
14
+ /** Get a type definition by its ID */
15
+ getType(id: string): SpecType | undefined;
16
+ /** Get all exports of a specific kind */
17
+ getExportsByKind(kind: SpecExportKind): SpecExport[];
18
+ /** Get all exports */
19
+ getAllExports(): SpecExport[];
20
+ /** Get all type definitions */
21
+ getAllTypes(): SpecType[];
22
+ }
23
+
24
+ /**
25
+ * Creates an OpenPkg instance for use with Fumadocs components.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * // From file path
30
+ * const openpkg = createOpenPkg({ input: './openpkg.json' });
31
+ *
32
+ * // From spec object
33
+ * import spec from './openpkg.json';
34
+ * const openpkg = createOpenPkg({ input: spec });
35
+ * ```
36
+ */
37
+ export function createOpenPkg(options: OpenPkgOptions): OpenPkgInstance {
38
+ const spec: OpenPkg =
39
+ typeof options.input === 'string'
40
+ ? JSON.parse(fs.readFileSync(options.input, 'utf-8'))
41
+ : options.input;
42
+
43
+ const exportsById = new Map<string, SpecExport>();
44
+ const typesById = new Map<string, SpecType>();
45
+
46
+ for (const exp of spec.exports) {
47
+ exportsById.set(exp.id, exp);
48
+ }
49
+
50
+ if (spec.types) {
51
+ for (const type of spec.types) {
52
+ typesById.set(type.id, type);
53
+ }
54
+ }
55
+
56
+ return {
57
+ spec,
58
+
59
+ getExport(id: string): SpecExport | undefined {
60
+ return exportsById.get(id);
61
+ },
62
+
63
+ getType(id: string): SpecType | undefined {
64
+ return typesById.get(id);
65
+ },
66
+
67
+ getExportsByKind(kind: SpecExportKind): SpecExport[] {
68
+ return spec.exports.filter((exp) => exp.kind === kind);
69
+ },
70
+
71
+ getAllExports(): SpecExport[] {
72
+ return spec.exports;
73
+ },
74
+
75
+ getAllTypes(): SpecType[] {
76
+ return spec.types ?? [];
77
+ },
78
+ };
79
+ }
80
+