@apifn/docsfn 0.0.1 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apifn/docsfn",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "docsfn plugin for ApiFn API reference",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -11,6 +11,16 @@
11
11
  "types": "./dist/index.d.ts",
12
12
  "import": "./dist/index.js",
13
13
  "require": "./dist/index.cjs"
14
+ },
15
+ "./react": {
16
+ "types": "./dist/react.d.ts",
17
+ "import": "./dist/react.js",
18
+ "require": "./dist/react.cjs"
19
+ },
20
+ "./svelte": {
21
+ "types": "./src/svelte/ApifnApiReference.svelte.d.ts",
22
+ "svelte": "./src/svelte/ApifnApiReference.svelte",
23
+ "default": "./src/svelte/ApifnApiReference.svelte"
14
24
  }
15
25
  },
16
26
  "scripts": {
@@ -19,23 +29,36 @@
19
29
  "test": "vitest --run"
20
30
  },
21
31
  "dependencies": {
22
- "@apifn/core": "0.0.1",
23
- "@apifn/react": "0.0.1",
24
- "@apifn/svelte": "0.0.1"
32
+ "@apifn/core": "0.0.3",
33
+ "@apifn/react": "0.0.3",
34
+ "@apifn/svelte": "0.0.3"
25
35
  },
26
36
  "peerDependencies": {
27
- "docsfn": "*"
37
+ "docsfn": "*",
38
+ "react": "^18.0.0 || ^19.0.0",
39
+ "react-dom": "^18.0.0 || ^19.0.0",
40
+ "svelte": "^4.0.0 || ^5.0.0"
28
41
  },
29
42
  "peerDependenciesMeta": {
30
43
  "docsfn": {
31
44
  "optional": true
45
+ },
46
+ "react": {
47
+ "optional": true
48
+ },
49
+ "react-dom": {
50
+ "optional": true
51
+ },
52
+ "svelte": {
53
+ "optional": true
32
54
  }
33
55
  },
34
56
  "devDependencies": {
35
57
  "@types/node": "^20.0.0",
58
+ "@types/react": "^18.3.12 || ^19.0.0",
36
59
  "tsup": "^8.0.0",
37
60
  "typescript": "^5.3.0",
38
- "vitest": "^1.6.0"
61
+ "vitest": "^3.2.4"
39
62
  },
40
63
  "author": "21n",
41
64
  "license": "MIT",
@@ -51,6 +74,8 @@
51
74
  "access": "public"
52
75
  },
53
76
  "files": [
54
- "dist"
55
- ]
77
+ "dist",
78
+ "src/svelte"
79
+ ],
80
+ "gitHead": "42a6d4536385c678ec76132f47ad13d2df0b9b93"
56
81
  }
@@ -0,0 +1,141 @@
1
+ <!-- ApifnApiReference.svelte — Svelte API reference renderer (DOCS-003, DOCS-005) -->
2
+ <script lang="ts">
3
+ import EndpointViewer from "@apifn/svelte/EndpointViewer.svelte";
4
+ import TryIt from "@apifn/svelte/TryIt.svelte";
5
+ import PerformanceOverlay from "@apifn/svelte/PerformanceOverlay.svelte";
6
+ import type { ApifnApiReferenceProps, RawContentEntry, ApiEndpoint } from "@apifn/docsfn";
7
+
8
+ export let entry: RawContentEntry;
9
+ export let tryIt: boolean = false;
10
+ export let baseUrl: string | undefined = undefined;
11
+ export let theme: "light" | "dark" | "auto" = "auto";
12
+ export let performanceMetrics: ApifnApiReferenceProps["performanceMetrics"] = undefined;
13
+
14
+ const METHOD_COLORS: Record<string, { bg: string; text: string }> = {
15
+ get: { bg: "#065f46", text: "#6ee7b7" }, post: { bg: "#1e3a5f", text: "#93c5fd" },
16
+ put: { bg: "#78350f", text: "#fcd34d" }, patch: { bg: "#5b21b6", text: "#c4b5fd" },
17
+ delete: { bg: "#7f1d1d", text: "#fca5a5" },
18
+ };
19
+
20
+ function mc(method: string) {
21
+ return METHOD_COLORS[method.toLowerCase()] ?? { bg: "#2d3748", text: "#9ca3af" };
22
+ }
23
+
24
+ $: effectiveBaseUrl = baseUrl
25
+ ?? entry.baseUrl
26
+ ?? (entry.spec as { servers?: Array<{ url: string }> }).servers?.[0]?.url
27
+ ?? "https://api.example.com";
28
+
29
+ // Per-endpoint card open/tab state
30
+ let openStates: Record<string, boolean> = {};
31
+ let tabStates: Record<string, "docs" | "tryit" | "perf"> = {};
32
+
33
+ function toggle(key: string) { openStates[key] = !openStates[key]; openStates = openStates; }
34
+ function setTab(key: string, t: "docs" | "tryit" | "perf") { tabStates[key] = t; tabStates = tabStates; }
35
+
36
+ function anchorId(ep: ApiEndpoint) { return `${ep.method.toLowerCase()}-${ep.path.replace(/[^a-z0-9]/gi, "-")}`; }
37
+ function perfKey(ep: ApiEndpoint) { return `${ep.method.toUpperCase()} ${ep.path}`; }
38
+
39
+ function getThemeVars(t: "light" | "dark" | "auto"): string {
40
+ const dark = `--apifn-bg:#0f1117;--apifn-bg-surface:#1a1d2e;--apifn-bg-surface-hover:#252840;--apifn-border:#2d3748;--apifn-text:#e2e8f0;--apifn-text-muted:#64748b;--apifn-accent:#7c3aed;--apifn-accent-text:#c4b5fd;--apifn-green:#6ee7b7;--apifn-blue:#93c5fd;--apifn-yellow:#fcd34d;--apifn-red:#fca5a5;--apifn-radius:6px;--apifn-font-mono:'JetBrains Mono',monospace;--apifn-font-sans:-apple-system,sans-serif`;
41
+ const light = `--apifn-bg:#ffffff;--apifn-bg-surface:#f8fafc;--apifn-bg-surface-hover:#f1f5f9;--apifn-border:#e2e8f0;--apifn-text:#0f172a;--apifn-text-muted:#64748b;--apifn-accent:#7c3aed;--apifn-accent-text:#6d28d9;--apifn-green:#059669;--apifn-blue:#2563eb;--apifn-yellow:#d97706;--apifn-red:#dc2626;--apifn-radius:6px;--apifn-font-mono:'JetBrains Mono',monospace;--apifn-font-sans:-apple-system,sans-serif`;
42
+ if (t === "dark") return dark;
43
+ if (t === "light") return light;
44
+ return "";
45
+ }
46
+ </script>
47
+
48
+ <div class="apifn-root" class:auto-theme={theme === "auto"} style={getThemeVars(theme)}>
49
+ <div class="page">
50
+ <h1 class="page-title">{entry.title}</h1>
51
+ {#if entry.tag}
52
+ <div class="page-subtitle">
53
+ Tag: <code>{entry.tag}</code>
54
+ — {entry.endpoints.length} endpoint{entry.endpoints.length !== 1 ? "s" : ""}
55
+ </div>
56
+ {/if}
57
+
58
+ {#each entry.endpoints as ep}
59
+ {@const key = `${ep.method}-${ep.path}`}
60
+ {@const isOpen = openStates[key] ?? false}
61
+ {@const tab = tabStates[key] ?? "docs"}
62
+ {@const metrics = performanceMetrics?.[perfKey(ep)]}
63
+
64
+ <div id={anchorId(ep)} class="card">
65
+ <!-- Card Header -->
66
+ <div class="card-header" role="button" tabindex="0"
67
+ on:click={() => toggle(key)}
68
+ on:keydown={(e) => e.key === "Enter" && toggle(key)}
69
+ >
70
+ <span class="method-badge" style="background:{mc(ep.method).bg};color:{mc(ep.method).text}">
71
+ {ep.method.toUpperCase()}
72
+ </span>
73
+ <span class="card-path">{ep.path}</span>
74
+ {#if ep.operation.summary}
75
+ <span class="card-summary">{ep.operation.summary}</span>
76
+ {/if}
77
+ <span class="chevron" class:open={isOpen}>▼</span>
78
+ </div>
79
+
80
+ {#if isOpen}
81
+ <!-- Tabs -->
82
+ <div class="tabs">
83
+ <button class="tab" class:active={tab === "docs"} on:click={() => setTab(key, "docs")}>Docs</button>
84
+ {#if tryIt}
85
+ <button class="tab" class:active={tab === "tryit"} on:click={() => setTab(key, "tryit")}>Try It</button>
86
+ {/if}
87
+ {#if metrics}
88
+ <button class="tab" class:active={tab === "perf"} on:click={() => setTab(key, "perf")}>Performance</button>
89
+ {/if}
90
+ </div>
91
+
92
+ <!-- Tab Content -->
93
+ {#if tab === "docs"}
94
+ <EndpointViewer path={ep.path} method={ep.method} operation={ep.operation} />
95
+ {:else if tab === "tryit" && tryIt}
96
+ <TryIt path={ep.path} method={ep.method} operation={ep.operation} baseUrl={effectiveBaseUrl} />
97
+ {:else if tab === "perf" && metrics}
98
+ <div class="perf-wrap">
99
+ <PerformanceOverlay metrics={{ endpoint: ep.path, method: ep.method, lastUpdated: new Date().toISOString(), ...metrics }} />
100
+ </div>
101
+ {/if}
102
+ {/if}
103
+ </div>
104
+ {/each}
105
+ </div>
106
+ </div>
107
+
108
+ <style>
109
+ .apifn-root {
110
+ --apifn-bg:#ffffff;--apifn-bg-surface:#f8fafc;--apifn-bg-surface-hover:#f1f5f9;
111
+ --apifn-border:#e2e8f0;--apifn-text:#0f172a;--apifn-text-muted:#64748b;
112
+ --apifn-accent:#7c3aed;--apifn-accent-text:#6d28d9;--apifn-green:#059669;
113
+ --apifn-blue:#2563eb;--apifn-yellow:#d97706;--apifn-red:#dc2626;
114
+ --apifn-radius:6px;--apifn-font-mono:'JetBrains Mono',monospace;
115
+ --apifn-font-sans:-apple-system,sans-serif;
116
+ font-family: var(--apifn-font-sans, sans-serif); color: var(--apifn-text); background: var(--apifn-bg);
117
+ }
118
+ @media (prefers-color-scheme: dark) {
119
+ .apifn-root.auto-theme {
120
+ --apifn-bg:#0f1117;--apifn-bg-surface:#1a1d2e;--apifn-bg-surface-hover:#252840;
121
+ --apifn-border:#2d3748;--apifn-text:#e2e8f0;--apifn-text-muted:#64748b;
122
+ --apifn-accent:#7c3aed;--apifn-accent-text:#c4b5fd;--apifn-green:#6ee7b7;
123
+ --apifn-blue:#93c5fd;--apifn-yellow:#fcd34d;--apifn-red:#fca5a5;
124
+ }
125
+ }
126
+ .page { max-width: 960px; margin: 0 auto; padding: 0 24px 48px; }
127
+ .page-title { font-size: 28px; font-weight: 800; margin-bottom: 8px; }
128
+ .page-subtitle { font-size: 14px; color: var(--apifn-text-muted); margin-bottom: 40px; }
129
+ code { font-family: var(--apifn-font-mono); }
130
+ .card { border: 1px solid var(--apifn-border); border-radius: var(--apifn-radius); margin-bottom: 24px; overflow: hidden; background: var(--apifn-bg-surface); }
131
+ .card-header { display: flex; align-items: center; gap: 12px; padding: 14px 20px; cursor: pointer; border-bottom: 1px solid var(--apifn-border); }
132
+ .method-badge { padding: 3px 10px; border-radius: 3px; font-weight: 700; font-size: 12px; font-family: var(--apifn-font-mono); text-transform: uppercase; }
133
+ .card-path { font-family: var(--apifn-font-mono); font-size: 14px; font-weight: 600; flex: 1; }
134
+ .card-summary { font-size: 13px; color: var(--apifn-text-muted); }
135
+ .chevron { color: var(--apifn-text-muted); font-size: 12px; transition: transform .15s; }
136
+ .chevron.open { transform: rotate(180deg); }
137
+ .tabs { display: flex; border-bottom: 1px solid var(--apifn-border); padding: 0 20px; }
138
+ .tab { padding: 10px 14px; font-size: 13px; font-weight: 400; color: var(--apifn-text-muted); border: none; border-bottom: 2px solid transparent; background: none; cursor: pointer; }
139
+ .tab.active { font-weight: 600; color: var(--apifn-text); border-bottom-color: var(--apifn-accent); }
140
+ .perf-wrap { padding: 20px; }
141
+ </style>
@@ -0,0 +1,4 @@
1
+ import { SvelteComponent } from "svelte";
2
+ import type { ApifnApiReferenceProps } from "@apifn/docsfn";
3
+
4
+ export default class ApifnApiReference extends SvelteComponent<ApifnApiReferenceProps> {}