@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/README.md +128 -0
- package/dist/index.cjs +42 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -94
- package/dist/index.d.ts +3 -94
- package/dist/index.js +39 -3
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +186 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +11 -0
- package/dist/react.d.ts +11 -0
- package/dist/react.js +161 -0
- package/dist/react.js.map +1 -0
- package/dist/types-D4LTWU9V.d.cts +97 -0
- package/dist/types-D4LTWU9V.d.ts +97 -0
- package/package.json +33 -8
- package/src/svelte/ApifnApiReference.svelte +141 -0
- package/src/svelte/ApifnApiReference.svelte.d.ts +4 -0
package/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# @apifn/docsfn
|
|
2
|
+
|
|
3
|
+
docsfn plugin for ApiFn API reference. Turn an OpenAPI document into docsfn content entries and render them as an interactive API reference (React or Svelte) with embedded Try-It consoles and performance metrics.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @apifn/docsfn
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`docsfn` is an **optional** peer dependency — this package defines the minimal `DocsContentProvider` / `RawContentEntry` interfaces itself, so you get correct types even without docsfn installed.
|
|
12
|
+
The package-local build declares its React type dependencies and the file-loading
|
|
13
|
+
tests create their own temporary OpenAPI fixtures. `npm install --workspaces=false`,
|
|
14
|
+
`npm run build`, and `npm test` do not require a sibling CLI checkout or workspace
|
|
15
|
+
type hoisting.
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- **Content provider** — Convert an OpenAPI spec into docsfn `RawContentEntry[]`
|
|
20
|
+
- **Tag splitting** — One page per tag (default) or a single full-spec page
|
|
21
|
+
- **Sidebar generation** — Auto-built navigation with per-endpoint anchors
|
|
22
|
+
- **Reference renderers** — `ApifnApiReference` for React and Svelte, wrapping the `@apifn/react` / `@apifn/svelte` components
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## createApifnProvider
|
|
27
|
+
|
|
28
|
+
Create a `DocsContentProvider` — a callable that returns `RawContentEntry[]` (memoized) and carries an `.entries` array.
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { createApifnProvider } from "@apifn/docsfn";
|
|
32
|
+
|
|
33
|
+
const provider = createApifnProvider({
|
|
34
|
+
specPath: "./openapi.yml", // or: spec: openApiDocument
|
|
35
|
+
basePath: "/api",
|
|
36
|
+
splitByTag: true,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const entries = await provider(); // RawContentEntry[]
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### CreateApifnProviderOptions
|
|
43
|
+
|
|
44
|
+
| Option | Type | Default | Description |
|
|
45
|
+
|--------|------|---------|-------------|
|
|
46
|
+
| `spec` | `OpenAPIDocument` | — | The document (provide this or `specPath`) |
|
|
47
|
+
| `specPath` | `string` | — | Path to a JSON or YAML spec |
|
|
48
|
+
| `basePath` | `string` | `"/api"` | Slug prefix for generated pages |
|
|
49
|
+
| `splitByTag` | `boolean` | `true` | One entry per tag, or a single full-spec entry |
|
|
50
|
+
| `baseUrl` | `string` | — | Base URL embedded in entries (for Try-It) |
|
|
51
|
+
|
|
52
|
+
With `splitByTag: true`, each tag becomes a `RawContentEntry`. The tag is lowercased and each run of non-alphanumeric characters becomes `-`, so `My Tag Group` produces `${basePath}/my-tag-group`. Sidebar links use the lowercased method followed by the path with each non-alphanumeric character replaced by `-`; for example, `GET /users/{id}` links to `${slug}#get--users--id-`. With `splitByTag: false`, a single entry contains every endpoint. Path-level parameters are merged into each operation.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Rendering the reference
|
|
57
|
+
|
|
58
|
+
The package root (`@apifn/docsfn`) exports the provider and types. Renderer components are available from framework-specific public subpaths:
|
|
59
|
+
|
|
60
|
+
- React: `@apifn/docsfn/react`
|
|
61
|
+
- Svelte: `@apifn/docsfn/svelte`
|
|
62
|
+
|
|
63
|
+
Pass each `RawContentEntry` returned by the provider to the renderer used by your docsfn integration.
|
|
64
|
+
|
|
65
|
+
React endpoint headers are native disclosure buttons with an expanded-state
|
|
66
|
+
announcement, so collapsed endpoints remain reachable and operable by keyboard.
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import { ApifnApiReference } from "@apifn/docsfn/react";
|
|
70
|
+
|
|
71
|
+
<ApifnApiReference
|
|
72
|
+
entry={entry}
|
|
73
|
+
tryIt
|
|
74
|
+
baseUrl="https://api.example.com"
|
|
75
|
+
theme="light"
|
|
76
|
+
performanceMetrics={{
|
|
77
|
+
"GET /users": { p50Ms: 12, p95Ms: 40, p99Ms: 90, errorRatePct: 0.5, requestsPerMinute: 320 },
|
|
78
|
+
}}
|
|
79
|
+
/>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The Svelte renderer takes the same props (`entry`, `tryIt`, `baseUrl`, `theme`, `performanceMetrics`):
|
|
83
|
+
|
|
84
|
+
```svelte
|
|
85
|
+
<script lang="ts">
|
|
86
|
+
import ApifnApiReference from "@apifn/docsfn/svelte";
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<ApifnApiReference {entry} tryIt theme="auto" />
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### ApifnApiReferenceProps
|
|
93
|
+
|
|
94
|
+
| Prop | Type | Default | Description |
|
|
95
|
+
|------|------|---------|-------------|
|
|
96
|
+
| `entry` | `RawContentEntry` | — | A single entry from `createApifnProvider` (required) |
|
|
97
|
+
| `tryIt` | `boolean` | `false` | Show an embedded Try-It console per endpoint |
|
|
98
|
+
| `baseUrl` | `string` | See below | Base URL for Try-It requests |
|
|
99
|
+
| `theme` | `"light" \| "dark" \| "auto"` | `"auto"` | Theme for embedded components |
|
|
100
|
+
| `performanceMetrics` | `Record<string, …>` | — | Per-endpoint metrics keyed `"METHOD /path"` (you supply `p50Ms`/`p95Ms`/`p99Ms`/`errorRatePct`/`requestsPerMinute`) |
|
|
101
|
+
|
|
102
|
+
Each endpoint renders as a collapsible card with Docs / Try It / Performance tabs, delegating to the `EndpointViewer`, `TryIt`, and `PerformanceOverlay` components from `@apifn/react` / `@apifn/svelte`.
|
|
103
|
+
|
|
104
|
+
The Try-It base URL is selected in this order: the renderer's `baseUrl` prop, the provider's `baseUrl` embedded in the entry, the first URL in `entry.spec.servers`, then `https://api.example.com` as a placeholder fallback.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## How it plugs into docsfn
|
|
109
|
+
|
|
110
|
+
`createApifnProvider(...)` yields a `DocsContentProvider` suitable for a docsfn content pipeline. Each `RawContentEntry` carries `kind: "api"`, a `sidebar` group for navigation, and the full `spec`. Register the provider and the appropriate `ApifnApiReference` renderer in your docsfn application according to its integration API.
|
|
111
|
+
|
|
112
|
+
## Exports
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
export { createApifnProvider }
|
|
116
|
+
export type {
|
|
117
|
+
DocsContentProvider, RawContentEntry, ApiEndpoint,
|
|
118
|
+
SidebarItem, SidebarLink, SidebarGroup,
|
|
119
|
+
CreateApifnProviderOptions, ApifnApiReferenceProps,
|
|
120
|
+
OpenAPIDocument, OperationObject,
|
|
121
|
+
}
|
|
122
|
+
// React: import { ApifnApiReference } from "@apifn/docsfn/react"
|
|
123
|
+
// Svelte: import ApifnApiReference from "@apifn/docsfn/svelte"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -18,11 +18,11 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
|
|
20
20
|
// src/index.ts
|
|
21
|
-
var
|
|
22
|
-
__export(
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
23
|
createApifnProvider: () => createApifnProvider
|
|
24
24
|
});
|
|
25
|
-
module.exports = __toCommonJS(
|
|
25
|
+
module.exports = __toCommonJS(src_exports);
|
|
26
26
|
|
|
27
27
|
// src/provider.ts
|
|
28
28
|
var import_promises = require("fs/promises");
|
|
@@ -45,15 +45,49 @@ function collectEndpoints(spec) {
|
|
|
45
45
|
const items = [];
|
|
46
46
|
for (const [path, pathItem] of Object.entries(spec.paths ?? {})) {
|
|
47
47
|
if (!pathItem) continue;
|
|
48
|
+
const pathParameters = Array.isArray(pathItem.parameters) ? pathItem.parameters : [];
|
|
48
49
|
for (const method of HTTP_METHODS) {
|
|
49
50
|
const op = pathItem[method];
|
|
50
51
|
if (!op) continue;
|
|
52
|
+
const operation = mergePathParameters(pathParameters, op);
|
|
51
53
|
const tags = op.tags ?? ["default"];
|
|
52
|
-
items.push({ path, method, operation
|
|
54
|
+
items.push({ path, method, operation, tag: tags[0] ?? "default" });
|
|
53
55
|
}
|
|
54
56
|
}
|
|
55
57
|
return items;
|
|
56
58
|
}
|
|
59
|
+
function parameterKey(parameter) {
|
|
60
|
+
if (!parameter || typeof parameter !== "object") {
|
|
61
|
+
return void 0;
|
|
62
|
+
}
|
|
63
|
+
const reference = parameter;
|
|
64
|
+
if (typeof reference.$ref === "string") {
|
|
65
|
+
return `$ref:${reference.$ref}`;
|
|
66
|
+
}
|
|
67
|
+
const candidate = parameter;
|
|
68
|
+
if (typeof candidate.name !== "string" || typeof candidate.in !== "string") {
|
|
69
|
+
return void 0;
|
|
70
|
+
}
|
|
71
|
+
return `${candidate.in}:${candidate.name}`;
|
|
72
|
+
}
|
|
73
|
+
function mergePathParameters(pathParameters, operation) {
|
|
74
|
+
if (pathParameters.length === 0) {
|
|
75
|
+
return operation;
|
|
76
|
+
}
|
|
77
|
+
const operationParameters = Array.isArray(operation.parameters) ? operation.parameters : [];
|
|
78
|
+
const operationKeys = new Set(operationParameters.map(parameterKey).filter((key) => Boolean(key)));
|
|
79
|
+
const inheritedParameters = pathParameters.filter((parameter) => {
|
|
80
|
+
const key = parameterKey(parameter);
|
|
81
|
+
return !key || !operationKeys.has(key);
|
|
82
|
+
});
|
|
83
|
+
if (inheritedParameters.length === 0) {
|
|
84
|
+
return operation;
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
...operation,
|
|
88
|
+
parameters: [...inheritedParameters, ...operationParameters]
|
|
89
|
+
};
|
|
90
|
+
}
|
|
57
91
|
function buildSidebarGroup(groupLabel, endpoints, slug) {
|
|
58
92
|
const links = endpoints.map((e) => ({
|
|
59
93
|
type: "link",
|
|
@@ -80,7 +114,8 @@ function buildEntries(spec, options) {
|
|
|
80
114
|
title,
|
|
81
115
|
endpoints: allEndpoints,
|
|
82
116
|
sidebar,
|
|
83
|
-
spec
|
|
117
|
+
spec,
|
|
118
|
+
...baseUrl !== void 0 ? { baseUrl } : {}
|
|
84
119
|
}];
|
|
85
120
|
}
|
|
86
121
|
const tagMap = /* @__PURE__ */ new Map();
|
|
@@ -99,7 +134,8 @@ function buildEntries(spec, options) {
|
|
|
99
134
|
tag,
|
|
100
135
|
endpoints,
|
|
101
136
|
sidebar,
|
|
102
|
-
spec
|
|
137
|
+
spec,
|
|
138
|
+
...baseUrl !== void 0 ? { baseUrl } : {}
|
|
103
139
|
};
|
|
104
140
|
});
|
|
105
141
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/provider.ts"],"sourcesContent":["/**\n * @apifn/docsfn — public API\n *\n * Provides a DocsContentProvider and API reference renderer components\n * for embedding ApiFn API documentation in docsfn sites.\n */\n\n// Provider\nexport { createApifnProvider } from \"./provider.js\";\n\n// Types\nexport type {\n DocsContentProvider,\n RawContentEntry,\n ApiEndpoint,\n SidebarItem,\n SidebarLink,\n SidebarGroup,\n CreateApifnProviderOptions,\n ApifnApiReferenceProps,\n OpenAPIDocument,\n OperationObject,\n} from \"./types.js\";\n","/**\n * createApifnProvider — DocsContentProvider implementation (DOCS-001, DOCS-004, DOCS-006).\n *\n * Accepts an OpenAPI document (object or file path), groups endpoints by tag,\n * and returns RawContentEntry[] suitable for docsfn's manifest pipeline.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { parseOpenAPI, type OpenAPIDocument, type OperationObject } from \"@apifn/core\";\nimport type {\n CreateApifnProviderOptions,\n DocsContentProvider,\n RawContentEntry,\n ApiEndpoint,\n SidebarGroup,\n SidebarLink,\n} from \"./types.js\";\n\nconst HTTP_METHODS = [\n \"get\", \"post\", \"put\", \"patch\", \"delete\", \"head\", \"options\",\n] as const;\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction tagToSlug(tag: string, basePath: string): string {\n const segment = tag.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n return `${basePath.replace(/\\/$/, \"\")}/${segment}`;\n}\n\nfunction collectEndpoints(spec: OpenAPIDocument): ApiEndpoint[] {\n const items: ApiEndpoint[] = [];\n for (const [path, pathItem] of Object.entries(spec.paths ?? {})) {\n if (!pathItem) continue;\n for (const method of HTTP_METHODS) {\n const op = pathItem[method] as OperationObject | undefined;\n if (!op) continue;\n const tags = (op.tags as string[]) ?? [\"default\"];\n items.push({ path, method, operation: op, tag: tags[0] ?? \"default\" });\n }\n }\n return items;\n}\n\nfunction buildSidebarGroup(\n groupLabel: string,\n endpoints: ApiEndpoint[],\n slug: string\n): SidebarGroup {\n const links: SidebarLink[] = endpoints.map((e) => ({\n type: \"link\",\n label: `${e.method.toUpperCase()} ${e.path}`,\n href: `${slug}#${e.method.toLowerCase()}-${e.path.replace(/[^a-z0-9]/gi, \"-\")}`,\n }));\n return { type: \"group\", label: groupLabel, items: links };\n}\n\nfunction buildEntries(\n spec: OpenAPIDocument,\n options: CreateApifnProviderOptions\n): RawContentEntry[] {\n const {\n basePath = \"/api\",\n splitByTag = true,\n baseUrl,\n } = options;\n\n const info = spec.info as { title?: string; version?: string } | undefined;\n const allEndpoints = collectEndpoints(spec);\n\n if (!splitByTag) {\n // Single full-spec entry\n const slug = basePath.replace(/\\/$/, \"\") || \"/api\";\n const title = info?.title ?? \"API Reference\";\n const sidebar = buildSidebarGroup(title, allEndpoints, slug);\n return [{\n kind: \"api\",\n slug,\n title,\n endpoints: allEndpoints,\n sidebar,\n spec,\n }];\n }\n\n // Split by tag — one entry per tag (DOCS-006)\n const tagMap = new Map<string, ApiEndpoint[]>();\n for (const endpoint of allEndpoints) {\n if (!tagMap.has(endpoint.tag)) tagMap.set(endpoint.tag, []);\n tagMap.get(endpoint.tag)!.push(endpoint);\n }\n\n return [...tagMap.entries()].map(([tag, endpoints]) => {\n const slug = tagToSlug(tag, basePath);\n const title = `${tag.charAt(0).toUpperCase()}${tag.slice(1)}`;\n const sidebar = buildSidebarGroup(title, endpoints, slug);\n return {\n kind: \"api\" as const,\n slug,\n title,\n tag,\n endpoints,\n sidebar,\n spec,\n };\n });\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Creates a DocsContentProvider that produces RawContentEntry[] for docsfn.\n *\n * @example\n * const provider = createApifnProvider({ specPath: \"./openapi.yml\", splitByTag: true });\n * const entries = await provider();\n */\nexport function createApifnProvider(\n options: CreateApifnProviderOptions\n): DocsContentProvider {\n let cachedEntries: RawContentEntry[] | null = null;\n\n async function loadSpec(): Promise<OpenAPIDocument> {\n if (options.spec) return options.spec;\n if (options.specPath) {\n const abs = resolve(options.specPath);\n const raw = await readFile(abs, \"utf8\");\n return parseOpenAPI(raw);\n }\n throw new Error(\"createApifnProvider: must provide either `spec` or `specPath`\");\n }\n\n const provider = async (): Promise<RawContentEntry[]> => {\n if (cachedEntries) return cachedEntries;\n const spec = await loadSpec();\n cachedEntries = buildEntries(spec, options);\n provider.entries = cachedEntries;\n return cachedEntries;\n };\n\n provider.entries = [] as RawContentEntry[];\n return provider as DocsContentProvider;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/provider.ts"],"sourcesContent":["/**\n * @apifn/docsfn — public API\n *\n * Provides a DocsContentProvider and API reference renderer components\n * for embedding ApiFn API documentation in docsfn sites.\n */\n\n// Provider\nexport { createApifnProvider } from \"./provider.js\";\n\n// Types\nexport type {\n DocsContentProvider,\n RawContentEntry,\n ApiEndpoint,\n SidebarItem,\n SidebarLink,\n SidebarGroup,\n CreateApifnProviderOptions,\n ApifnApiReferenceProps,\n OpenAPIDocument,\n OperationObject,\n} from \"./types.js\";\n","/**\n * createApifnProvider — DocsContentProvider implementation (DOCS-001, DOCS-004, DOCS-006).\n *\n * Accepts an OpenAPI document (object or file path), groups endpoints by tag,\n * and returns RawContentEntry[] suitable for docsfn's manifest pipeline.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { parseOpenAPI, type OpenAPIDocument, type OperationObject } from \"@apifn/core\";\nimport type {\n CreateApifnProviderOptions,\n DocsContentProvider,\n RawContentEntry,\n ApiEndpoint,\n SidebarGroup,\n SidebarLink,\n} from \"./types.js\";\n\nconst HTTP_METHODS = [\n \"get\", \"post\", \"put\", \"patch\", \"delete\", \"head\", \"options\",\n] as const;\n\ntype OperationParameters = NonNullable<OperationObject[\"parameters\"]>;\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction tagToSlug(tag: string, basePath: string): string {\n const segment = tag.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n return `${basePath.replace(/\\/$/, \"\")}/${segment}`;\n}\n\nfunction collectEndpoints(spec: OpenAPIDocument): ApiEndpoint[] {\n const items: ApiEndpoint[] = [];\n for (const [path, pathItem] of Object.entries(spec.paths ?? {})) {\n if (!pathItem) continue;\n const pathParameters = Array.isArray(pathItem.parameters) ? pathItem.parameters : [];\n for (const method of HTTP_METHODS) {\n const op = pathItem[method] as OperationObject | undefined;\n if (!op) continue;\n const operation = mergePathParameters(pathParameters, op);\n const tags = (op.tags as string[]) ?? [\"default\"];\n items.push({ path, method, operation, tag: tags[0] ?? \"default\" });\n }\n }\n return items;\n}\n\nfunction parameterKey(parameter: unknown): string | undefined {\n if (!parameter || typeof parameter !== \"object\") {\n return undefined;\n }\n\n const reference = parameter as { $ref?: unknown };\n if (typeof reference.$ref === \"string\") {\n return `$ref:${reference.$ref}`;\n }\n\n const candidate = parameter as { name?: unknown; in?: unknown };\n if (typeof candidate.name !== \"string\" || typeof candidate.in !== \"string\") {\n return undefined;\n }\n\n return `${candidate.in}:${candidate.name}`;\n}\n\nfunction mergePathParameters(pathParameters: OperationParameters, operation: OperationObject): OperationObject {\n if (pathParameters.length === 0) {\n return operation;\n }\n\n const operationParameters: OperationParameters = Array.isArray(operation.parameters) ? operation.parameters : [];\n const operationKeys = new Set(operationParameters.map(parameterKey).filter((key): key is string => Boolean(key)));\n const inheritedParameters = pathParameters.filter((parameter) => {\n const key = parameterKey(parameter);\n return !key || !operationKeys.has(key);\n });\n\n if (inheritedParameters.length === 0) {\n return operation;\n }\n\n return {\n ...operation,\n parameters: [...inheritedParameters, ...operationParameters],\n };\n}\n\nfunction buildSidebarGroup(\n groupLabel: string,\n endpoints: ApiEndpoint[],\n slug: string\n): SidebarGroup {\n const links: SidebarLink[] = endpoints.map((e) => ({\n type: \"link\",\n label: `${e.method.toUpperCase()} ${e.path}`,\n href: `${slug}#${e.method.toLowerCase()}-${e.path.replace(/[^a-z0-9]/gi, \"-\")}`,\n }));\n return { type: \"group\", label: groupLabel, items: links };\n}\n\nfunction buildEntries(\n spec: OpenAPIDocument,\n options: CreateApifnProviderOptions\n): RawContentEntry[] {\n const {\n basePath = \"/api\",\n splitByTag = true,\n baseUrl,\n } = options;\n\n const info = spec.info as { title?: string; version?: string } | undefined;\n const allEndpoints = collectEndpoints(spec);\n\n if (!splitByTag) {\n // Single full-spec entry\n const slug = basePath.replace(/\\/$/, \"\") || \"/api\";\n const title = info?.title ?? \"API Reference\";\n const sidebar = buildSidebarGroup(title, allEndpoints, slug);\n return [{\n kind: \"api\",\n slug,\n title,\n endpoints: allEndpoints,\n sidebar,\n spec,\n ...(baseUrl !== undefined ? { baseUrl } : {}),\n }];\n }\n\n // Split by tag — one entry per tag (DOCS-006)\n const tagMap = new Map<string, ApiEndpoint[]>();\n for (const endpoint of allEndpoints) {\n if (!tagMap.has(endpoint.tag)) tagMap.set(endpoint.tag, []);\n tagMap.get(endpoint.tag)!.push(endpoint);\n }\n\n return [...tagMap.entries()].map(([tag, endpoints]) => {\n const slug = tagToSlug(tag, basePath);\n const title = `${tag.charAt(0).toUpperCase()}${tag.slice(1)}`;\n const sidebar = buildSidebarGroup(title, endpoints, slug);\n return {\n kind: \"api\" as const,\n slug,\n title,\n tag,\n endpoints,\n sidebar,\n spec,\n ...(baseUrl !== undefined ? { baseUrl } : {}),\n };\n });\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Creates a DocsContentProvider that produces RawContentEntry[] for docsfn.\n *\n * @example\n * const provider = createApifnProvider({ specPath: \"./openapi.yml\", splitByTag: true });\n * const entries = await provider();\n */\nexport function createApifnProvider(\n options: CreateApifnProviderOptions\n): DocsContentProvider {\n let cachedEntries: RawContentEntry[] | null = null;\n\n async function loadSpec(): Promise<OpenAPIDocument> {\n if (options.spec) return options.spec;\n if (options.specPath) {\n const abs = resolve(options.specPath);\n const raw = await readFile(abs, \"utf8\");\n return parseOpenAPI(raw);\n }\n throw new Error(\"createApifnProvider: must provide either `spec` or `specPath`\");\n }\n\n const provider = async (): Promise<RawContentEntry[]> => {\n if (cachedEntries) return cachedEntries;\n const spec = await loadSpec();\n cachedEntries = buildEntries(spec, options);\n provider.entries = cachedEntries;\n return cachedEntries;\n };\n\n provider.entries = [] as RawContentEntry[];\n return provider as DocsContentProvider;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACOA,sBAAyB;AACzB,uBAAwB;AACxB,kBAAyE;AAUzE,IAAM,eAAe;AAAA,EACjB;AAAA,EAAO;AAAA,EAAQ;AAAA,EAAO;AAAA,EAAS;AAAA,EAAU;AAAA,EAAQ;AACrD;AAMA,SAAS,UAAU,KAAa,UAA0B;AACtD,QAAM,UAAU,IAAI,YAAY,EAAE,QAAQ,eAAe,GAAG;AAC5D,SAAO,GAAG,SAAS,QAAQ,OAAO,EAAE,CAAC,IAAI,OAAO;AACpD;AAEA,SAAS,iBAAiB,MAAsC;AAC5D,QAAM,QAAuB,CAAC;AAC9B,aAAW,CAAC,MAAM,QAAQ,KAAK,OAAO,QAAQ,KAAK,SAAS,CAAC,CAAC,GAAG;AAC7D,QAAI,CAAC,SAAU;AACf,UAAM,iBAAiB,MAAM,QAAQ,SAAS,UAAU,IAAI,SAAS,aAAa,CAAC;AACnF,eAAW,UAAU,cAAc;AAC/B,YAAM,KAAK,SAAS,MAAM;AAC1B,UAAI,CAAC,GAAI;AACT,YAAM,YAAY,oBAAoB,gBAAgB,EAAE;AACxD,YAAM,OAAQ,GAAG,QAAqB,CAAC,SAAS;AAChD,YAAM,KAAK,EAAE,MAAM,QAAQ,WAAW,KAAK,KAAK,CAAC,KAAK,UAAU,CAAC;AAAA,IACrE;AAAA,EACJ;AACA,SAAO;AACX;AAEA,SAAS,aAAa,WAAwC;AAC1D,MAAI,CAAC,aAAa,OAAO,cAAc,UAAU;AAC7C,WAAO;AAAA,EACX;AAEA,QAAM,YAAY;AAClB,MAAI,OAAO,UAAU,SAAS,UAAU;AACpC,WAAO,QAAQ,UAAU,IAAI;AAAA,EACjC;AAEA,QAAM,YAAY;AAClB,MAAI,OAAO,UAAU,SAAS,YAAY,OAAO,UAAU,OAAO,UAAU;AACxE,WAAO;AAAA,EACX;AAEA,SAAO,GAAG,UAAU,EAAE,IAAI,UAAU,IAAI;AAC5C;AAEA,SAAS,oBAAoB,gBAAqC,WAA6C;AAC3G,MAAI,eAAe,WAAW,GAAG;AAC7B,WAAO;AAAA,EACX;AAEA,QAAM,sBAA2C,MAAM,QAAQ,UAAU,UAAU,IAAI,UAAU,aAAa,CAAC;AAC/G,QAAM,gBAAgB,IAAI,IAAI,oBAAoB,IAAI,YAAY,EAAE,OAAO,CAAC,QAAuB,QAAQ,GAAG,CAAC,CAAC;AAChH,QAAM,sBAAsB,eAAe,OAAO,CAAC,cAAc;AAC7D,UAAM,MAAM,aAAa,SAAS;AAClC,WAAO,CAAC,OAAO,CAAC,cAAc,IAAI,GAAG;AAAA,EACzC,CAAC;AAED,MAAI,oBAAoB,WAAW,GAAG;AAClC,WAAO;AAAA,EACX;AAEA,SAAO;AAAA,IACH,GAAG;AAAA,IACH,YAAY,CAAC,GAAG,qBAAqB,GAAG,mBAAmB;AAAA,EAC/D;AACJ;AAEA,SAAS,kBACL,YACA,WACA,MACY;AACZ,QAAM,QAAuB,UAAU,IAAI,CAAC,OAAO;AAAA,IAC/C,MAAM;AAAA,IACN,OAAO,GAAG,EAAE,OAAO,YAAY,CAAC,IAAI,EAAE,IAAI;AAAA,IAC1C,MAAM,GAAG,IAAI,IAAI,EAAE,OAAO,YAAY,CAAC,IAAI,EAAE,KAAK,QAAQ,eAAe,GAAG,CAAC;AAAA,EACjF,EAAE;AACF,SAAO,EAAE,MAAM,SAAS,OAAO,YAAY,OAAO,MAAM;AAC5D;AAEA,SAAS,aACL,MACA,SACiB;AACjB,QAAM;AAAA,IACF,WAAW;AAAA,IACX,aAAa;AAAA,IACb;AAAA,EACJ,IAAI;AAEJ,QAAM,OAAO,KAAK;AAClB,QAAM,eAAe,iBAAiB,IAAI;AAE1C,MAAI,CAAC,YAAY;AAEb,UAAM,OAAO,SAAS,QAAQ,OAAO,EAAE,KAAK;AAC5C,UAAM,QAAQ,MAAM,SAAS;AAC7B,UAAM,UAAU,kBAAkB,OAAO,cAAc,IAAI;AAC3D,WAAO,CAAC;AAAA,MACJ,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA;AAAA,MACA,GAAI,YAAY,SAAY,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC/C,CAAC;AAAA,EACL;AAGA,QAAM,SAAS,oBAAI,IAA2B;AAC9C,aAAW,YAAY,cAAc;AACjC,QAAI,CAAC,OAAO,IAAI,SAAS,GAAG,EAAG,QAAO,IAAI,SAAS,KAAK,CAAC,CAAC;AAC1D,WAAO,IAAI,SAAS,GAAG,EAAG,KAAK,QAAQ;AAAA,EAC3C;AAEA,SAAO,CAAC,GAAG,OAAO,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,SAAS,MAAM;AACnD,UAAM,OAAO,UAAU,KAAK,QAAQ;AACpC,UAAM,QAAQ,GAAG,IAAI,OAAO,CAAC,EAAE,YAAY,CAAC,GAAG,IAAI,MAAM,CAAC,CAAC;AAC3D,UAAM,UAAU,kBAAkB,OAAO,WAAW,IAAI;AACxD,WAAO;AAAA,MACH,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAI,YAAY,SAAY,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC/C;AAAA,EACJ,CAAC;AACL;AAWO,SAAS,oBACZ,SACmB;AACnB,MAAI,gBAA0C;AAE9C,iBAAe,WAAqC;AAChD,QAAI,QAAQ,KAAM,QAAO,QAAQ;AACjC,QAAI,QAAQ,UAAU;AAClB,YAAM,UAAM,0BAAQ,QAAQ,QAAQ;AACpC,YAAM,MAAM,UAAM,0BAAS,KAAK,MAAM;AACtC,iBAAO,0BAAa,GAAG;AAAA,IAC3B;AACA,UAAM,IAAI,MAAM,+DAA+D;AAAA,EACnF;AAEA,QAAM,WAAW,YAAwC;AACrD,QAAI,cAAe,QAAO;AAC1B,UAAM,OAAO,MAAM,SAAS;AAC5B,oBAAgB,aAAa,MAAM,OAAO;AAC1C,aAAS,UAAU;AACnB,WAAO;AAAA,EACX;AAEA,WAAS,UAAU,CAAC;AACpB,SAAO;AACX;","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,98 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as CreateApifnProviderOptions, D as DocsContentProvider } from './types-D4LTWU9V.cjs';
|
|
2
|
+
export { A as ApiEndpoint, a as ApifnApiReferenceProps, R as RawContentEntry, S as SidebarGroup, b as SidebarItem, c as SidebarLink } from './types-D4LTWU9V.cjs';
|
|
2
3
|
export { OpenAPIDocument, OperationObject } from '@apifn/core';
|
|
3
4
|
|
|
4
|
-
/**
|
|
5
|
-
* docsfn integration types for @apifn/docsfn.
|
|
6
|
-
*
|
|
7
|
-
* docsfn is an optional peer dependency that may not be installed.
|
|
8
|
-
* We define the minimal interfaces needed for our provider here so
|
|
9
|
-
* consumers get correct types even without the full docsfn package.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/** Sidebar leaf link. */
|
|
13
|
-
interface SidebarLink {
|
|
14
|
-
type: "link";
|
|
15
|
-
label: string;
|
|
16
|
-
href: string;
|
|
17
|
-
}
|
|
18
|
-
/** Sidebar group containing links. */
|
|
19
|
-
interface SidebarGroup {
|
|
20
|
-
type: "group";
|
|
21
|
-
label: string;
|
|
22
|
-
items: Array<SidebarLink | SidebarGroup>;
|
|
23
|
-
}
|
|
24
|
-
type SidebarItem = SidebarLink | SidebarGroup;
|
|
25
|
-
/** A processed endpoint grouped in a docsfn page. */
|
|
26
|
-
interface ApiEndpoint {
|
|
27
|
-
path: string;
|
|
28
|
-
method: string;
|
|
29
|
-
operation: OperationObject;
|
|
30
|
-
tag: string;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* A single raw content entry produced by a DocsContentProvider.
|
|
34
|
-
* `kind: "api"` signals to docsfn that this is an API reference page.
|
|
35
|
-
*/
|
|
36
|
-
interface RawContentEntry {
|
|
37
|
-
kind: "api";
|
|
38
|
-
/** URL slug for this page (e.g. "/api/users") */
|
|
39
|
-
slug: string;
|
|
40
|
-
/** Human-readable page title */
|
|
41
|
-
title: string;
|
|
42
|
-
/** Tag name when split by tag */
|
|
43
|
-
tag?: string;
|
|
44
|
-
/** Endpoint list for this page */
|
|
45
|
-
endpoints: ApiEndpoint[];
|
|
46
|
-
/** Sidebar items generated for this entry */
|
|
47
|
-
sidebar: SidebarGroup;
|
|
48
|
-
/** Full OpenAPI spec (so renderers can look up $refs etc.) */
|
|
49
|
-
spec: OpenAPIDocument;
|
|
50
|
-
}
|
|
51
|
-
/** A DocsContentProvider is a callable that returns RawContentEntry[]. */
|
|
52
|
-
interface DocsContentProvider {
|
|
53
|
-
(): Promise<RawContentEntry[]>;
|
|
54
|
-
entries: RawContentEntry[];
|
|
55
|
-
}
|
|
56
|
-
interface CreateApifnProviderOptions {
|
|
57
|
-
/** OpenAPI document (object) */
|
|
58
|
-
spec?: OpenAPIDocument;
|
|
59
|
-
/** File path to a JSON or YAML OpenAPI spec */
|
|
60
|
-
specPath?: string;
|
|
61
|
-
/** Base path prefix for slug generation (default: "/api") */
|
|
62
|
-
basePath?: string;
|
|
63
|
-
/**
|
|
64
|
-
* Split the spec into one page per tag.
|
|
65
|
-
* - `true` → one RawContentEntry per tag
|
|
66
|
-
* - `false` → one RawContentEntry with all endpoints
|
|
67
|
-
* @default true
|
|
68
|
-
*/
|
|
69
|
-
splitByTag?: boolean;
|
|
70
|
-
/** Optional base URL to embed in entries (for TryIt) */
|
|
71
|
-
baseUrl?: string;
|
|
72
|
-
}
|
|
73
|
-
interface PerformanceMetrics {
|
|
74
|
-
endpoint: string;
|
|
75
|
-
method: string;
|
|
76
|
-
p50Ms: number;
|
|
77
|
-
p95Ms: number;
|
|
78
|
-
p99Ms: number;
|
|
79
|
-
errorRatePct: number;
|
|
80
|
-
requestsPerMinute: number;
|
|
81
|
-
lastUpdated: string;
|
|
82
|
-
}
|
|
83
|
-
interface ApifnApiReferenceProps {
|
|
84
|
-
/** Single content entry from createApifnProvider */
|
|
85
|
-
entry: RawContentEntry;
|
|
86
|
-
/** Show embedded Try-It console per endpoint (DOCS-005) */
|
|
87
|
-
tryIt?: boolean;
|
|
88
|
-
/** Base URL for Try-It requests */
|
|
89
|
-
baseUrl?: string;
|
|
90
|
-
/** Theme for the embedded components */
|
|
91
|
-
theme?: "light" | "dark" | "auto";
|
|
92
|
-
/** Optional watchfn metrics per endpoint key ("METHOD /path") */
|
|
93
|
-
performanceMetrics?: Record<string, Omit<PerformanceMetrics, "endpoint" | "method" | "lastUpdated">>;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
5
|
/**
|
|
97
6
|
* createApifnProvider — DocsContentProvider implementation (DOCS-001, DOCS-004, DOCS-006).
|
|
98
7
|
*
|
|
@@ -109,4 +18,4 @@ interface ApifnApiReferenceProps {
|
|
|
109
18
|
*/
|
|
110
19
|
declare function createApifnProvider(options: CreateApifnProviderOptions): DocsContentProvider;
|
|
111
20
|
|
|
112
|
-
export {
|
|
21
|
+
export { CreateApifnProviderOptions, DocsContentProvider, createApifnProvider };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,98 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as CreateApifnProviderOptions, D as DocsContentProvider } from './types-D4LTWU9V.js';
|
|
2
|
+
export { A as ApiEndpoint, a as ApifnApiReferenceProps, R as RawContentEntry, S as SidebarGroup, b as SidebarItem, c as SidebarLink } from './types-D4LTWU9V.js';
|
|
2
3
|
export { OpenAPIDocument, OperationObject } from '@apifn/core';
|
|
3
4
|
|
|
4
|
-
/**
|
|
5
|
-
* docsfn integration types for @apifn/docsfn.
|
|
6
|
-
*
|
|
7
|
-
* docsfn is an optional peer dependency that may not be installed.
|
|
8
|
-
* We define the minimal interfaces needed for our provider here so
|
|
9
|
-
* consumers get correct types even without the full docsfn package.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/** Sidebar leaf link. */
|
|
13
|
-
interface SidebarLink {
|
|
14
|
-
type: "link";
|
|
15
|
-
label: string;
|
|
16
|
-
href: string;
|
|
17
|
-
}
|
|
18
|
-
/** Sidebar group containing links. */
|
|
19
|
-
interface SidebarGroup {
|
|
20
|
-
type: "group";
|
|
21
|
-
label: string;
|
|
22
|
-
items: Array<SidebarLink | SidebarGroup>;
|
|
23
|
-
}
|
|
24
|
-
type SidebarItem = SidebarLink | SidebarGroup;
|
|
25
|
-
/** A processed endpoint grouped in a docsfn page. */
|
|
26
|
-
interface ApiEndpoint {
|
|
27
|
-
path: string;
|
|
28
|
-
method: string;
|
|
29
|
-
operation: OperationObject;
|
|
30
|
-
tag: string;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* A single raw content entry produced by a DocsContentProvider.
|
|
34
|
-
* `kind: "api"` signals to docsfn that this is an API reference page.
|
|
35
|
-
*/
|
|
36
|
-
interface RawContentEntry {
|
|
37
|
-
kind: "api";
|
|
38
|
-
/** URL slug for this page (e.g. "/api/users") */
|
|
39
|
-
slug: string;
|
|
40
|
-
/** Human-readable page title */
|
|
41
|
-
title: string;
|
|
42
|
-
/** Tag name when split by tag */
|
|
43
|
-
tag?: string;
|
|
44
|
-
/** Endpoint list for this page */
|
|
45
|
-
endpoints: ApiEndpoint[];
|
|
46
|
-
/** Sidebar items generated for this entry */
|
|
47
|
-
sidebar: SidebarGroup;
|
|
48
|
-
/** Full OpenAPI spec (so renderers can look up $refs etc.) */
|
|
49
|
-
spec: OpenAPIDocument;
|
|
50
|
-
}
|
|
51
|
-
/** A DocsContentProvider is a callable that returns RawContentEntry[]. */
|
|
52
|
-
interface DocsContentProvider {
|
|
53
|
-
(): Promise<RawContentEntry[]>;
|
|
54
|
-
entries: RawContentEntry[];
|
|
55
|
-
}
|
|
56
|
-
interface CreateApifnProviderOptions {
|
|
57
|
-
/** OpenAPI document (object) */
|
|
58
|
-
spec?: OpenAPIDocument;
|
|
59
|
-
/** File path to a JSON or YAML OpenAPI spec */
|
|
60
|
-
specPath?: string;
|
|
61
|
-
/** Base path prefix for slug generation (default: "/api") */
|
|
62
|
-
basePath?: string;
|
|
63
|
-
/**
|
|
64
|
-
* Split the spec into one page per tag.
|
|
65
|
-
* - `true` → one RawContentEntry per tag
|
|
66
|
-
* - `false` → one RawContentEntry with all endpoints
|
|
67
|
-
* @default true
|
|
68
|
-
*/
|
|
69
|
-
splitByTag?: boolean;
|
|
70
|
-
/** Optional base URL to embed in entries (for TryIt) */
|
|
71
|
-
baseUrl?: string;
|
|
72
|
-
}
|
|
73
|
-
interface PerformanceMetrics {
|
|
74
|
-
endpoint: string;
|
|
75
|
-
method: string;
|
|
76
|
-
p50Ms: number;
|
|
77
|
-
p95Ms: number;
|
|
78
|
-
p99Ms: number;
|
|
79
|
-
errorRatePct: number;
|
|
80
|
-
requestsPerMinute: number;
|
|
81
|
-
lastUpdated: string;
|
|
82
|
-
}
|
|
83
|
-
interface ApifnApiReferenceProps {
|
|
84
|
-
/** Single content entry from createApifnProvider */
|
|
85
|
-
entry: RawContentEntry;
|
|
86
|
-
/** Show embedded Try-It console per endpoint (DOCS-005) */
|
|
87
|
-
tryIt?: boolean;
|
|
88
|
-
/** Base URL for Try-It requests */
|
|
89
|
-
baseUrl?: string;
|
|
90
|
-
/** Theme for the embedded components */
|
|
91
|
-
theme?: "light" | "dark" | "auto";
|
|
92
|
-
/** Optional watchfn metrics per endpoint key ("METHOD /path") */
|
|
93
|
-
performanceMetrics?: Record<string, Omit<PerformanceMetrics, "endpoint" | "method" | "lastUpdated">>;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
5
|
/**
|
|
97
6
|
* createApifnProvider — DocsContentProvider implementation (DOCS-001, DOCS-004, DOCS-006).
|
|
98
7
|
*
|
|
@@ -109,4 +18,4 @@ interface ApifnApiReferenceProps {
|
|
|
109
18
|
*/
|
|
110
19
|
declare function createApifnProvider(options: CreateApifnProviderOptions): DocsContentProvider;
|
|
111
20
|
|
|
112
|
-
export {
|
|
21
|
+
export { CreateApifnProviderOptions, DocsContentProvider, createApifnProvider };
|
package/dist/index.js
CHANGED
|
@@ -19,15 +19,49 @@ function collectEndpoints(spec) {
|
|
|
19
19
|
const items = [];
|
|
20
20
|
for (const [path, pathItem] of Object.entries(spec.paths ?? {})) {
|
|
21
21
|
if (!pathItem) continue;
|
|
22
|
+
const pathParameters = Array.isArray(pathItem.parameters) ? pathItem.parameters : [];
|
|
22
23
|
for (const method of HTTP_METHODS) {
|
|
23
24
|
const op = pathItem[method];
|
|
24
25
|
if (!op) continue;
|
|
26
|
+
const operation = mergePathParameters(pathParameters, op);
|
|
25
27
|
const tags = op.tags ?? ["default"];
|
|
26
|
-
items.push({ path, method, operation
|
|
28
|
+
items.push({ path, method, operation, tag: tags[0] ?? "default" });
|
|
27
29
|
}
|
|
28
30
|
}
|
|
29
31
|
return items;
|
|
30
32
|
}
|
|
33
|
+
function parameterKey(parameter) {
|
|
34
|
+
if (!parameter || typeof parameter !== "object") {
|
|
35
|
+
return void 0;
|
|
36
|
+
}
|
|
37
|
+
const reference = parameter;
|
|
38
|
+
if (typeof reference.$ref === "string") {
|
|
39
|
+
return `$ref:${reference.$ref}`;
|
|
40
|
+
}
|
|
41
|
+
const candidate = parameter;
|
|
42
|
+
if (typeof candidate.name !== "string" || typeof candidate.in !== "string") {
|
|
43
|
+
return void 0;
|
|
44
|
+
}
|
|
45
|
+
return `${candidate.in}:${candidate.name}`;
|
|
46
|
+
}
|
|
47
|
+
function mergePathParameters(pathParameters, operation) {
|
|
48
|
+
if (pathParameters.length === 0) {
|
|
49
|
+
return operation;
|
|
50
|
+
}
|
|
51
|
+
const operationParameters = Array.isArray(operation.parameters) ? operation.parameters : [];
|
|
52
|
+
const operationKeys = new Set(operationParameters.map(parameterKey).filter((key) => Boolean(key)));
|
|
53
|
+
const inheritedParameters = pathParameters.filter((parameter) => {
|
|
54
|
+
const key = parameterKey(parameter);
|
|
55
|
+
return !key || !operationKeys.has(key);
|
|
56
|
+
});
|
|
57
|
+
if (inheritedParameters.length === 0) {
|
|
58
|
+
return operation;
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
...operation,
|
|
62
|
+
parameters: [...inheritedParameters, ...operationParameters]
|
|
63
|
+
};
|
|
64
|
+
}
|
|
31
65
|
function buildSidebarGroup(groupLabel, endpoints, slug) {
|
|
32
66
|
const links = endpoints.map((e) => ({
|
|
33
67
|
type: "link",
|
|
@@ -54,7 +88,8 @@ function buildEntries(spec, options) {
|
|
|
54
88
|
title,
|
|
55
89
|
endpoints: allEndpoints,
|
|
56
90
|
sidebar,
|
|
57
|
-
spec
|
|
91
|
+
spec,
|
|
92
|
+
...baseUrl !== void 0 ? { baseUrl } : {}
|
|
58
93
|
}];
|
|
59
94
|
}
|
|
60
95
|
const tagMap = /* @__PURE__ */ new Map();
|
|
@@ -73,7 +108,8 @@ function buildEntries(spec, options) {
|
|
|
73
108
|
tag,
|
|
74
109
|
endpoints,
|
|
75
110
|
sidebar,
|
|
76
|
-
spec
|
|
111
|
+
spec,
|
|
112
|
+
...baseUrl !== void 0 ? { baseUrl } : {}
|
|
77
113
|
};
|
|
78
114
|
});
|
|
79
115
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/provider.ts"],"sourcesContent":["/**\n * createApifnProvider — DocsContentProvider implementation (DOCS-001, DOCS-004, DOCS-006).\n *\n * Accepts an OpenAPI document (object or file path), groups endpoints by tag,\n * and returns RawContentEntry[] suitable for docsfn's manifest pipeline.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { parseOpenAPI, type OpenAPIDocument, type OperationObject } from \"@apifn/core\";\nimport type {\n CreateApifnProviderOptions,\n DocsContentProvider,\n RawContentEntry,\n ApiEndpoint,\n SidebarGroup,\n SidebarLink,\n} from \"./types.js\";\n\nconst HTTP_METHODS = [\n \"get\", \"post\", \"put\", \"patch\", \"delete\", \"head\", \"options\",\n] as const;\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction tagToSlug(tag: string, basePath: string): string {\n const segment = tag.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n return `${basePath.replace(/\\/$/, \"\")}/${segment}`;\n}\n\nfunction collectEndpoints(spec: OpenAPIDocument): ApiEndpoint[] {\n const items: ApiEndpoint[] = [];\n for (const [path, pathItem] of Object.entries(spec.paths ?? {})) {\n if (!pathItem) continue;\n for (const method of HTTP_METHODS) {\n const op = pathItem[method] as OperationObject | undefined;\n if (!op) continue;\n const tags = (op.tags as string[]) ?? [\"default\"];\n items.push({ path, method, operation
|
|
1
|
+
{"version":3,"sources":["../src/provider.ts"],"sourcesContent":["/**\n * createApifnProvider — DocsContentProvider implementation (DOCS-001, DOCS-004, DOCS-006).\n *\n * Accepts an OpenAPI document (object or file path), groups endpoints by tag,\n * and returns RawContentEntry[] suitable for docsfn's manifest pipeline.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { parseOpenAPI, type OpenAPIDocument, type OperationObject } from \"@apifn/core\";\nimport type {\n CreateApifnProviderOptions,\n DocsContentProvider,\n RawContentEntry,\n ApiEndpoint,\n SidebarGroup,\n SidebarLink,\n} from \"./types.js\";\n\nconst HTTP_METHODS = [\n \"get\", \"post\", \"put\", \"patch\", \"delete\", \"head\", \"options\",\n] as const;\n\ntype OperationParameters = NonNullable<OperationObject[\"parameters\"]>;\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction tagToSlug(tag: string, basePath: string): string {\n const segment = tag.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n return `${basePath.replace(/\\/$/, \"\")}/${segment}`;\n}\n\nfunction collectEndpoints(spec: OpenAPIDocument): ApiEndpoint[] {\n const items: ApiEndpoint[] = [];\n for (const [path, pathItem] of Object.entries(spec.paths ?? {})) {\n if (!pathItem) continue;\n const pathParameters = Array.isArray(pathItem.parameters) ? pathItem.parameters : [];\n for (const method of HTTP_METHODS) {\n const op = pathItem[method] as OperationObject | undefined;\n if (!op) continue;\n const operation = mergePathParameters(pathParameters, op);\n const tags = (op.tags as string[]) ?? [\"default\"];\n items.push({ path, method, operation, tag: tags[0] ?? \"default\" });\n }\n }\n return items;\n}\n\nfunction parameterKey(parameter: unknown): string | undefined {\n if (!parameter || typeof parameter !== \"object\") {\n return undefined;\n }\n\n const reference = parameter as { $ref?: unknown };\n if (typeof reference.$ref === \"string\") {\n return `$ref:${reference.$ref}`;\n }\n\n const candidate = parameter as { name?: unknown; in?: unknown };\n if (typeof candidate.name !== \"string\" || typeof candidate.in !== \"string\") {\n return undefined;\n }\n\n return `${candidate.in}:${candidate.name}`;\n}\n\nfunction mergePathParameters(pathParameters: OperationParameters, operation: OperationObject): OperationObject {\n if (pathParameters.length === 0) {\n return operation;\n }\n\n const operationParameters: OperationParameters = Array.isArray(operation.parameters) ? operation.parameters : [];\n const operationKeys = new Set(operationParameters.map(parameterKey).filter((key): key is string => Boolean(key)));\n const inheritedParameters = pathParameters.filter((parameter) => {\n const key = parameterKey(parameter);\n return !key || !operationKeys.has(key);\n });\n\n if (inheritedParameters.length === 0) {\n return operation;\n }\n\n return {\n ...operation,\n parameters: [...inheritedParameters, ...operationParameters],\n };\n}\n\nfunction buildSidebarGroup(\n groupLabel: string,\n endpoints: ApiEndpoint[],\n slug: string\n): SidebarGroup {\n const links: SidebarLink[] = endpoints.map((e) => ({\n type: \"link\",\n label: `${e.method.toUpperCase()} ${e.path}`,\n href: `${slug}#${e.method.toLowerCase()}-${e.path.replace(/[^a-z0-9]/gi, \"-\")}`,\n }));\n return { type: \"group\", label: groupLabel, items: links };\n}\n\nfunction buildEntries(\n spec: OpenAPIDocument,\n options: CreateApifnProviderOptions\n): RawContentEntry[] {\n const {\n basePath = \"/api\",\n splitByTag = true,\n baseUrl,\n } = options;\n\n const info = spec.info as { title?: string; version?: string } | undefined;\n const allEndpoints = collectEndpoints(spec);\n\n if (!splitByTag) {\n // Single full-spec entry\n const slug = basePath.replace(/\\/$/, \"\") || \"/api\";\n const title = info?.title ?? \"API Reference\";\n const sidebar = buildSidebarGroup(title, allEndpoints, slug);\n return [{\n kind: \"api\",\n slug,\n title,\n endpoints: allEndpoints,\n sidebar,\n spec,\n ...(baseUrl !== undefined ? { baseUrl } : {}),\n }];\n }\n\n // Split by tag — one entry per tag (DOCS-006)\n const tagMap = new Map<string, ApiEndpoint[]>();\n for (const endpoint of allEndpoints) {\n if (!tagMap.has(endpoint.tag)) tagMap.set(endpoint.tag, []);\n tagMap.get(endpoint.tag)!.push(endpoint);\n }\n\n return [...tagMap.entries()].map(([tag, endpoints]) => {\n const slug = tagToSlug(tag, basePath);\n const title = `${tag.charAt(0).toUpperCase()}${tag.slice(1)}`;\n const sidebar = buildSidebarGroup(title, endpoints, slug);\n return {\n kind: \"api\" as const,\n slug,\n title,\n tag,\n endpoints,\n sidebar,\n spec,\n ...(baseUrl !== undefined ? { baseUrl } : {}),\n };\n });\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Creates a DocsContentProvider that produces RawContentEntry[] for docsfn.\n *\n * @example\n * const provider = createApifnProvider({ specPath: \"./openapi.yml\", splitByTag: true });\n * const entries = await provider();\n */\nexport function createApifnProvider(\n options: CreateApifnProviderOptions\n): DocsContentProvider {\n let cachedEntries: RawContentEntry[] | null = null;\n\n async function loadSpec(): Promise<OpenAPIDocument> {\n if (options.spec) return options.spec;\n if (options.specPath) {\n const abs = resolve(options.specPath);\n const raw = await readFile(abs, \"utf8\");\n return parseOpenAPI(raw);\n }\n throw new Error(\"createApifnProvider: must provide either `spec` or `specPath`\");\n }\n\n const provider = async (): Promise<RawContentEntry[]> => {\n if (cachedEntries) return cachedEntries;\n const spec = await loadSpec();\n cachedEntries = buildEntries(spec, options);\n provider.entries = cachedEntries;\n return cachedEntries;\n };\n\n provider.entries = [] as RawContentEntry[];\n return provider as DocsContentProvider;\n}\n"],"mappings":"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","names":[]}
|