@apifn/docsfn 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +36 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +36 -3
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -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",
|
|
@@ -65,8 +99,7 @@ function buildSidebarGroup(groupLabel, endpoints, slug) {
|
|
|
65
99
|
function buildEntries(spec, options) {
|
|
66
100
|
const {
|
|
67
101
|
basePath = "/api",
|
|
68
|
-
splitByTag = true
|
|
69
|
-
baseUrl
|
|
102
|
+
splitByTag = true
|
|
70
103
|
} = options;
|
|
71
104
|
const info = spec.info;
|
|
72
105
|
const allEndpoints = collectEndpoints(spec);
|
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 } = 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":[]}
|
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",
|
|
@@ -39,8 +73,7 @@ function buildSidebarGroup(groupLabel, endpoints, slug) {
|
|
|
39
73
|
function buildEntries(spec, options) {
|
|
40
74
|
const {
|
|
41
75
|
basePath = "/api",
|
|
42
|
-
splitByTag = true
|
|
43
|
-
baseUrl
|
|
76
|
+
splitByTag = true
|
|
44
77
|
} = options;
|
|
45
78
|
const info = spec.info;
|
|
46
79
|
const allEndpoints = collectEndpoints(spec);
|
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 } = 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":";AAOA,SAAS,gBAAgB;AACzB,SAAS,eAAe;AACxB,SAAS,oBAAgE;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,EACjB,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,IACJ,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,IACJ;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,MAAM,QAAQ,QAAQ,QAAQ;AACpC,YAAM,MAAM,MAAM,SAAS,KAAK,MAAM;AACtC,aAAO,aAAa,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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apifn/docsfn",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "docsfn plugin for ApiFn API reference",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
"test": "vitest --run"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@apifn/core": "0.0.
|
|
23
|
-
"@apifn/react": "0.0.
|
|
24
|
-
"@apifn/svelte": "0.0.
|
|
22
|
+
"@apifn/core": "0.0.2",
|
|
23
|
+
"@apifn/react": "0.0.2",
|
|
24
|
+
"@apifn/svelte": "0.0.2"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"docsfn": "*"
|