@apifn/docsfn 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.
package/dist/index.cjs ADDED
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ createApifnProvider: () => createApifnProvider
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/provider.ts
28
+ var import_promises = require("fs/promises");
29
+ var import_node_path = require("path");
30
+ var import_core = require("@apifn/core");
31
+ var HTTP_METHODS = [
32
+ "get",
33
+ "post",
34
+ "put",
35
+ "patch",
36
+ "delete",
37
+ "head",
38
+ "options"
39
+ ];
40
+ function tagToSlug(tag, basePath) {
41
+ const segment = tag.toLowerCase().replace(/[^a-z0-9]+/g, "-");
42
+ return `${basePath.replace(/\/$/, "")}/${segment}`;
43
+ }
44
+ function collectEndpoints(spec) {
45
+ const items = [];
46
+ for (const [path, pathItem] of Object.entries(spec.paths ?? {})) {
47
+ if (!pathItem) continue;
48
+ for (const method of HTTP_METHODS) {
49
+ const op = pathItem[method];
50
+ if (!op) continue;
51
+ const tags = op.tags ?? ["default"];
52
+ items.push({ path, method, operation: op, tag: tags[0] ?? "default" });
53
+ }
54
+ }
55
+ return items;
56
+ }
57
+ function buildSidebarGroup(groupLabel, endpoints, slug) {
58
+ const links = endpoints.map((e) => ({
59
+ type: "link",
60
+ label: `${e.method.toUpperCase()} ${e.path}`,
61
+ href: `${slug}#${e.method.toLowerCase()}-${e.path.replace(/[^a-z0-9]/gi, "-")}`
62
+ }));
63
+ return { type: "group", label: groupLabel, items: links };
64
+ }
65
+ function buildEntries(spec, options) {
66
+ const {
67
+ basePath = "/api",
68
+ splitByTag = true,
69
+ baseUrl
70
+ } = options;
71
+ const info = spec.info;
72
+ const allEndpoints = collectEndpoints(spec);
73
+ if (!splitByTag) {
74
+ const slug = basePath.replace(/\/$/, "") || "/api";
75
+ const title = info?.title ?? "API Reference";
76
+ const sidebar = buildSidebarGroup(title, allEndpoints, slug);
77
+ return [{
78
+ kind: "api",
79
+ slug,
80
+ title,
81
+ endpoints: allEndpoints,
82
+ sidebar,
83
+ spec
84
+ }];
85
+ }
86
+ const tagMap = /* @__PURE__ */ new Map();
87
+ for (const endpoint of allEndpoints) {
88
+ if (!tagMap.has(endpoint.tag)) tagMap.set(endpoint.tag, []);
89
+ tagMap.get(endpoint.tag).push(endpoint);
90
+ }
91
+ return [...tagMap.entries()].map(([tag, endpoints]) => {
92
+ const slug = tagToSlug(tag, basePath);
93
+ const title = `${tag.charAt(0).toUpperCase()}${tag.slice(1)}`;
94
+ const sidebar = buildSidebarGroup(title, endpoints, slug);
95
+ return {
96
+ kind: "api",
97
+ slug,
98
+ title,
99
+ tag,
100
+ endpoints,
101
+ sidebar,
102
+ spec
103
+ };
104
+ });
105
+ }
106
+ function createApifnProvider(options) {
107
+ let cachedEntries = null;
108
+ async function loadSpec() {
109
+ if (options.spec) return options.spec;
110
+ if (options.specPath) {
111
+ const abs = (0, import_node_path.resolve)(options.specPath);
112
+ const raw = await (0, import_promises.readFile)(abs, "utf8");
113
+ return (0, import_core.parseOpenAPI)(raw);
114
+ }
115
+ throw new Error("createApifnProvider: must provide either `spec` or `specPath`");
116
+ }
117
+ const provider = async () => {
118
+ if (cachedEntries) return cachedEntries;
119
+ const spec = await loadSpec();
120
+ cachedEntries = buildEntries(spec, options);
121
+ provider.entries = cachedEntries;
122
+ return cachedEntries;
123
+ };
124
+ provider.entries = [];
125
+ return provider;
126
+ }
127
+ // Annotate the CommonJS export names for ESM import in node:
128
+ 0 && (module.exports = {
129
+ createApifnProvider
130
+ });
131
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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":[]}
@@ -0,0 +1,112 @@
1
+ import { OperationObject, OpenAPIDocument } from '@apifn/core';
2
+ export { OpenAPIDocument, OperationObject } from '@apifn/core';
3
+
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
+ /**
97
+ * createApifnProvider — DocsContentProvider implementation (DOCS-001, DOCS-004, DOCS-006).
98
+ *
99
+ * Accepts an OpenAPI document (object or file path), groups endpoints by tag,
100
+ * and returns RawContentEntry[] suitable for docsfn's manifest pipeline.
101
+ */
102
+
103
+ /**
104
+ * Creates a DocsContentProvider that produces RawContentEntry[] for docsfn.
105
+ *
106
+ * @example
107
+ * const provider = createApifnProvider({ specPath: "./openapi.yml", splitByTag: true });
108
+ * const entries = await provider();
109
+ */
110
+ declare function createApifnProvider(options: CreateApifnProviderOptions): DocsContentProvider;
111
+
112
+ export { type ApiEndpoint, type ApifnApiReferenceProps, type CreateApifnProviderOptions, type DocsContentProvider, type RawContentEntry, type SidebarGroup, type SidebarItem, type SidebarLink, createApifnProvider };
@@ -0,0 +1,112 @@
1
+ import { OperationObject, OpenAPIDocument } from '@apifn/core';
2
+ export { OpenAPIDocument, OperationObject } from '@apifn/core';
3
+
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
+ /**
97
+ * createApifnProvider — DocsContentProvider implementation (DOCS-001, DOCS-004, DOCS-006).
98
+ *
99
+ * Accepts an OpenAPI document (object or file path), groups endpoints by tag,
100
+ * and returns RawContentEntry[] suitable for docsfn's manifest pipeline.
101
+ */
102
+
103
+ /**
104
+ * Creates a DocsContentProvider that produces RawContentEntry[] for docsfn.
105
+ *
106
+ * @example
107
+ * const provider = createApifnProvider({ specPath: "./openapi.yml", splitByTag: true });
108
+ * const entries = await provider();
109
+ */
110
+ declare function createApifnProvider(options: CreateApifnProviderOptions): DocsContentProvider;
111
+
112
+ export { type ApiEndpoint, type ApifnApiReferenceProps, type CreateApifnProviderOptions, type DocsContentProvider, type RawContentEntry, type SidebarGroup, type SidebarItem, type SidebarLink, createApifnProvider };
package/dist/index.js ADDED
@@ -0,0 +1,104 @@
1
+ // src/provider.ts
2
+ import { readFile } from "fs/promises";
3
+ import { resolve } from "path";
4
+ import { parseOpenAPI } from "@apifn/core";
5
+ var HTTP_METHODS = [
6
+ "get",
7
+ "post",
8
+ "put",
9
+ "patch",
10
+ "delete",
11
+ "head",
12
+ "options"
13
+ ];
14
+ function tagToSlug(tag, basePath) {
15
+ const segment = tag.toLowerCase().replace(/[^a-z0-9]+/g, "-");
16
+ return `${basePath.replace(/\/$/, "")}/${segment}`;
17
+ }
18
+ function collectEndpoints(spec) {
19
+ const items = [];
20
+ for (const [path, pathItem] of Object.entries(spec.paths ?? {})) {
21
+ if (!pathItem) continue;
22
+ for (const method of HTTP_METHODS) {
23
+ const op = pathItem[method];
24
+ if (!op) continue;
25
+ const tags = op.tags ?? ["default"];
26
+ items.push({ path, method, operation: op, tag: tags[0] ?? "default" });
27
+ }
28
+ }
29
+ return items;
30
+ }
31
+ function buildSidebarGroup(groupLabel, endpoints, slug) {
32
+ const links = endpoints.map((e) => ({
33
+ type: "link",
34
+ label: `${e.method.toUpperCase()} ${e.path}`,
35
+ href: `${slug}#${e.method.toLowerCase()}-${e.path.replace(/[^a-z0-9]/gi, "-")}`
36
+ }));
37
+ return { type: "group", label: groupLabel, items: links };
38
+ }
39
+ function buildEntries(spec, options) {
40
+ const {
41
+ basePath = "/api",
42
+ splitByTag = true,
43
+ baseUrl
44
+ } = options;
45
+ const info = spec.info;
46
+ const allEndpoints = collectEndpoints(spec);
47
+ if (!splitByTag) {
48
+ const slug = basePath.replace(/\/$/, "") || "/api";
49
+ const title = info?.title ?? "API Reference";
50
+ const sidebar = buildSidebarGroup(title, allEndpoints, slug);
51
+ return [{
52
+ kind: "api",
53
+ slug,
54
+ title,
55
+ endpoints: allEndpoints,
56
+ sidebar,
57
+ spec
58
+ }];
59
+ }
60
+ const tagMap = /* @__PURE__ */ new Map();
61
+ for (const endpoint of allEndpoints) {
62
+ if (!tagMap.has(endpoint.tag)) tagMap.set(endpoint.tag, []);
63
+ tagMap.get(endpoint.tag).push(endpoint);
64
+ }
65
+ return [...tagMap.entries()].map(([tag, endpoints]) => {
66
+ const slug = tagToSlug(tag, basePath);
67
+ const title = `${tag.charAt(0).toUpperCase()}${tag.slice(1)}`;
68
+ const sidebar = buildSidebarGroup(title, endpoints, slug);
69
+ return {
70
+ kind: "api",
71
+ slug,
72
+ title,
73
+ tag,
74
+ endpoints,
75
+ sidebar,
76
+ spec
77
+ };
78
+ });
79
+ }
80
+ function createApifnProvider(options) {
81
+ let cachedEntries = null;
82
+ async function loadSpec() {
83
+ if (options.spec) return options.spec;
84
+ if (options.specPath) {
85
+ const abs = resolve(options.specPath);
86
+ const raw = await readFile(abs, "utf8");
87
+ return parseOpenAPI(raw);
88
+ }
89
+ throw new Error("createApifnProvider: must provide either `spec` or `specPath`");
90
+ }
91
+ const provider = async () => {
92
+ if (cachedEntries) return cachedEntries;
93
+ const spec = await loadSpec();
94
+ cachedEntries = buildEntries(spec, options);
95
+ provider.entries = cachedEntries;
96
+ return cachedEntries;
97
+ };
98
+ provider.entries = [];
99
+ return provider;
100
+ }
101
+ export {
102
+ createApifnProvider
103
+ };
104
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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: 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":";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;AAIA,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,eAAW,UAAU,cAAc;AAC/B,YAAM,KAAK,SAAS,MAAM;AAC1B,UAAI,CAAC,GAAI;AACT,YAAM,OAAQ,GAAG,QAAqB,CAAC,SAAS;AAChD,YAAM,KAAK,EAAE,MAAM,QAAQ,WAAW,IAAI,KAAK,KAAK,CAAC,KAAK,UAAU,CAAC;AAAA,IACzE;AAAA,EACJ;AACA,SAAO;AACX;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,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 ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@apifn/docsfn",
3
+ "version": "0.0.1",
4
+ "description": "docsfn plugin for ApiFn API reference",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "scripts": {
17
+ "build": "tsup",
18
+ "dev": "tsup --watch",
19
+ "test": "vitest --run"
20
+ },
21
+ "dependencies": {
22
+ "@apifn/core": "0.0.1",
23
+ "@apifn/react": "0.0.1",
24
+ "@apifn/svelte": "0.0.1"
25
+ },
26
+ "peerDependencies": {
27
+ "docsfn": "*"
28
+ },
29
+ "peerDependenciesMeta": {
30
+ "docsfn": {
31
+ "optional": true
32
+ }
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^20.0.0",
36
+ "tsup": "^8.0.0",
37
+ "typescript": "^5.3.0",
38
+ "vitest": "^1.6.0"
39
+ },
40
+ "author": "21n",
41
+ "license": "MIT",
42
+ "bugs": {
43
+ "url": "https://github.com/21nCo/super-functions/issues"
44
+ },
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/21nCo/super-functions.git",
48
+ "directory": "apifn/docsfn"
49
+ },
50
+ "publishConfig": {
51
+ "access": "public"
52
+ },
53
+ "files": [
54
+ "dist"
55
+ ]
56
+ }