@publier/openapi 0.3.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,136 @@
1
+ //#region src/types.d.ts
2
+ interface OpenApiSpec {
3
+ openapi: string;
4
+ info: OpenApiInfo;
5
+ servers?: OpenApiServer[];
6
+ paths: Record<string, OpenApiPathItem>;
7
+ /**
8
+ * OpenAPI 3.1 webhooks — server-to-client push endpoints, keyed by name.
9
+ * Shape-identical to path items but not part of the HTTP routing table.
10
+ */
11
+ webhooks?: Record<string, OpenApiPathItem>;
12
+ components?: OpenApiComponents;
13
+ tags?: OpenApiTag[];
14
+ }
15
+ interface OpenApiInfo {
16
+ title: string;
17
+ description?: string;
18
+ version: string;
19
+ }
20
+ interface OpenApiServer {
21
+ url: string;
22
+ description?: string;
23
+ }
24
+ interface OpenApiPathItem {
25
+ get?: OpenApiOperation;
26
+ post?: OpenApiOperation;
27
+ put?: OpenApiOperation;
28
+ delete?: OpenApiOperation;
29
+ patch?: OpenApiOperation;
30
+ head?: OpenApiOperation;
31
+ options?: OpenApiOperation;
32
+ summary?: string;
33
+ description?: string;
34
+ parameters?: OpenApiParameter[];
35
+ }
36
+ interface OpenApiOperation {
37
+ operationId?: string;
38
+ summary?: string;
39
+ description?: string;
40
+ tags?: string[];
41
+ parameters?: OpenApiParameter[];
42
+ requestBody?: OpenApiRequestBody;
43
+ responses: Record<string, OpenApiResponse>;
44
+ /**
45
+ * OpenAPI 3.0+ callbacks — out-of-band requests the service will make in
46
+ * response to this operation.
47
+ */
48
+ callbacks?: Record<string, Record<string, OpenApiPathItem>>;
49
+ security?: Record<string, string[]>[];
50
+ deprecated?: boolean;
51
+ 'x-codeSamples'?: OpenApiCodeSample[];
52
+ }
53
+ interface OpenApiParameter {
54
+ name: string;
55
+ in: 'path' | 'query' | 'header' | 'cookie';
56
+ description?: string;
57
+ required?: boolean;
58
+ schema?: OpenApiSchema;
59
+ deprecated?: boolean;
60
+ }
61
+ interface OpenApiRequestBody {
62
+ description?: string;
63
+ required?: boolean;
64
+ content: Record<string, OpenApiMediaType>;
65
+ }
66
+ interface OpenApiResponse {
67
+ description: string;
68
+ content?: Record<string, OpenApiMediaType>;
69
+ headers?: Record<string, {
70
+ description?: string;
71
+ schema?: OpenApiSchema;
72
+ }>;
73
+ }
74
+ interface OpenApiMediaType {
75
+ schema?: OpenApiSchema;
76
+ example?: unknown;
77
+ examples?: Record<string, {
78
+ value: unknown;
79
+ summary?: string;
80
+ }>;
81
+ }
82
+ interface OpenApiSchema {
83
+ type?: string;
84
+ format?: string;
85
+ description?: string;
86
+ properties?: Record<string, OpenApiSchema>;
87
+ items?: OpenApiSchema;
88
+ required?: string[];
89
+ enum?: unknown[];
90
+ default?: unknown;
91
+ oneOf?: OpenApiSchema[];
92
+ anyOf?: OpenApiSchema[];
93
+ allOf?: OpenApiSchema[];
94
+ $ref?: string;
95
+ nullable?: boolean;
96
+ readOnly?: boolean;
97
+ writeOnly?: boolean;
98
+ deprecated?: boolean;
99
+ example?: unknown;
100
+ title?: string;
101
+ minimum?: number;
102
+ maximum?: number;
103
+ pattern?: string;
104
+ minLength?: number;
105
+ maxLength?: number;
106
+ additionalProperties?: boolean | OpenApiSchema;
107
+ }
108
+ interface OpenApiComponents {
109
+ schemas?: Record<string, OpenApiSchema>;
110
+ parameters?: Record<string, OpenApiParameter>;
111
+ responses?: Record<string, OpenApiResponse>;
112
+ requestBodies?: Record<string, OpenApiRequestBody>;
113
+ securitySchemes?: Record<string, OpenApiSecurityScheme>;
114
+ }
115
+ interface OpenApiSecurityScheme {
116
+ type: 'apiKey' | 'http' | 'oauth2' | 'openIdConnect';
117
+ description?: string;
118
+ name?: string;
119
+ in?: 'query' | 'header' | 'cookie';
120
+ scheme?: string;
121
+ bearerFormat?: string;
122
+ }
123
+ interface OpenApiTag {
124
+ name: string;
125
+ description?: string;
126
+ }
127
+ interface OpenApiCodeSample {
128
+ lang: string;
129
+ label?: string;
130
+ source: string;
131
+ }
132
+ //#endregion
133
+ //#region src/parser.d.ts
134
+ declare function parseOpenApiSpec(input: string): OpenApiSpec;
135
+ //#endregion
136
+ export { parseOpenApiSpec };
@@ -0,0 +1 @@
1
+ import{openapiParseOpenapiSpec as e}from"@publier/native";import{parse as t}from"yaml";function n(n){let r;try{JSON.parse(n),r=n}catch{r=JSON.stringify(t(n))}return JSON.parse(e(r))}export{n as parseOpenApiSpec};
@@ -0,0 +1,238 @@
1
+ //#region src/types.d.ts
2
+ interface OpenApiSpec {
3
+ openapi: string;
4
+ info: OpenApiInfo;
5
+ servers?: OpenApiServer[];
6
+ paths: Record<string, OpenApiPathItem>;
7
+ /**
8
+ * OpenAPI 3.1 webhooks — server-to-client push endpoints, keyed by name.
9
+ * Shape-identical to path items but not part of the HTTP routing table.
10
+ */
11
+ webhooks?: Record<string, OpenApiPathItem>;
12
+ components?: OpenApiComponents;
13
+ tags?: OpenApiTag[];
14
+ }
15
+ interface OpenApiInfo {
16
+ title: string;
17
+ description?: string;
18
+ version: string;
19
+ }
20
+ interface OpenApiServer {
21
+ url: string;
22
+ description?: string;
23
+ }
24
+ interface OpenApiPathItem {
25
+ get?: OpenApiOperation;
26
+ post?: OpenApiOperation;
27
+ put?: OpenApiOperation;
28
+ delete?: OpenApiOperation;
29
+ patch?: OpenApiOperation;
30
+ head?: OpenApiOperation;
31
+ options?: OpenApiOperation;
32
+ summary?: string;
33
+ description?: string;
34
+ parameters?: OpenApiParameter[];
35
+ }
36
+ interface OpenApiOperation {
37
+ operationId?: string;
38
+ summary?: string;
39
+ description?: string;
40
+ tags?: string[];
41
+ parameters?: OpenApiParameter[];
42
+ requestBody?: OpenApiRequestBody;
43
+ responses: Record<string, OpenApiResponse>;
44
+ /**
45
+ * OpenAPI 3.0+ callbacks — out-of-band requests the service will make in
46
+ * response to this operation.
47
+ */
48
+ callbacks?: Record<string, Record<string, OpenApiPathItem>>;
49
+ security?: Record<string, string[]>[];
50
+ deprecated?: boolean;
51
+ 'x-codeSamples'?: OpenApiCodeSample[];
52
+ }
53
+ interface OpenApiParameter {
54
+ name: string;
55
+ in: 'path' | 'query' | 'header' | 'cookie';
56
+ description?: string;
57
+ required?: boolean;
58
+ schema?: OpenApiSchema;
59
+ deprecated?: boolean;
60
+ }
61
+ interface OpenApiRequestBody {
62
+ description?: string;
63
+ required?: boolean;
64
+ content: Record<string, OpenApiMediaType>;
65
+ }
66
+ interface OpenApiResponse {
67
+ description: string;
68
+ content?: Record<string, OpenApiMediaType>;
69
+ headers?: Record<string, {
70
+ description?: string;
71
+ schema?: OpenApiSchema;
72
+ }>;
73
+ }
74
+ interface OpenApiMediaType {
75
+ schema?: OpenApiSchema;
76
+ example?: unknown;
77
+ examples?: Record<string, {
78
+ value: unknown;
79
+ summary?: string;
80
+ }>;
81
+ }
82
+ interface OpenApiSchema {
83
+ type?: string;
84
+ format?: string;
85
+ description?: string;
86
+ properties?: Record<string, OpenApiSchema>;
87
+ items?: OpenApiSchema;
88
+ required?: string[];
89
+ enum?: unknown[];
90
+ default?: unknown;
91
+ oneOf?: OpenApiSchema[];
92
+ anyOf?: OpenApiSchema[];
93
+ allOf?: OpenApiSchema[];
94
+ $ref?: string;
95
+ nullable?: boolean;
96
+ readOnly?: boolean;
97
+ writeOnly?: boolean;
98
+ deprecated?: boolean;
99
+ example?: unknown;
100
+ title?: string;
101
+ minimum?: number;
102
+ maximum?: number;
103
+ pattern?: string;
104
+ minLength?: number;
105
+ maxLength?: number;
106
+ additionalProperties?: boolean | OpenApiSchema;
107
+ }
108
+ interface OpenApiComponents {
109
+ schemas?: Record<string, OpenApiSchema>;
110
+ parameters?: Record<string, OpenApiParameter>;
111
+ responses?: Record<string, OpenApiResponse>;
112
+ requestBodies?: Record<string, OpenApiRequestBody>;
113
+ securitySchemes?: Record<string, OpenApiSecurityScheme>;
114
+ }
115
+ interface OpenApiSecurityScheme {
116
+ type: 'apiKey' | 'http' | 'oauth2' | 'openIdConnect';
117
+ description?: string;
118
+ name?: string;
119
+ in?: 'query' | 'header' | 'cookie';
120
+ scheme?: string;
121
+ bearerFormat?: string;
122
+ }
123
+ interface OpenApiTag {
124
+ name: string;
125
+ description?: string;
126
+ }
127
+ interface OpenApiCodeSample {
128
+ lang: string;
129
+ label?: string;
130
+ source: string;
131
+ }
132
+ type HttpMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head' | 'options';
133
+ //#endregion
134
+ //#region src/endpoints.d.ts
135
+ /** The kind of endpoint — a regular HTTP path, or an OpenAPI 3.1 webhook. */
136
+ type EndpointKind = 'path' | 'webhook';
137
+ /** A single endpoint extracted from an OpenAPI spec. */
138
+ interface EndpointEntry {
139
+ method: HttpMethod;
140
+ path: string;
141
+ operation: OpenApiOperation;
142
+ /** `'path'` for `spec.paths`, `'webhook'` for `spec.webhooks`. Default `'path'`. */
143
+ kind?: EndpointKind;
144
+ }
145
+ //#endregion
146
+ //#region src/navigation.d.ts
147
+ /** A single navigation entry — typically one endpoint. */
148
+ interface NavigationItem {
149
+ /** Human-readable label (operation summary or fall-back). */
150
+ label: string;
151
+ /** URL-safe slug usable as a sidebar link or a page id. */
152
+ slug: string;
153
+ /** HTTP method, uppercase. */
154
+ method: string;
155
+ /** Path template (e.g. `/users/{id}`). */
156
+ path: string;
157
+ /** Present when the upstream operation is flagged deprecated. */
158
+ deprecated?: boolean;
159
+ /** `'webhook'` when this came from `spec.webhooks`, else `'path'`. */
160
+ kind?: 'path' | 'webhook';
161
+ }
162
+ /**
163
+ * A group of related endpoints — one per tag (default) or per first path
164
+ * segment. Maps naturally to a sidebar group entry.
165
+ */
166
+ interface NavigationGroup {
167
+ label: string;
168
+ description?: string;
169
+ items: NavigationItem[];
170
+ }
171
+ interface BuildNavigationOptions {
172
+ /**
173
+ * How to group endpoints. `'tag'` is the OpenAPI norm; `'path'` groups by
174
+ * first non-parameter path segment (useful for specs without tags).
175
+ */
176
+ groupBy?: 'tag' | 'path';
177
+ /**
178
+ * URL path prefixed onto every item slug. When the generated docs live
179
+ * under `/api`, pass `'api'` to get `api/<tag>/<operation>` slugs.
180
+ */
181
+ slugPrefix?: string;
182
+ /**
183
+ * Override the default label (operation.summary or `METHOD path`).
184
+ * Note: this callback cannot cross the Rust boundary and is not applied.
185
+ * Apply a post-pass on the returned groups if custom labels are needed.
186
+ */
187
+ formatLabel?: (ep: EndpointEntry) => string;
188
+ /**
189
+ * Drop deprecated operations from the output entirely. Default: keep them
190
+ * and expose the `deprecated` flag for renderers to style as needed.
191
+ */
192
+ excludeDeprecated?: boolean;
193
+ /** Forwarded to `extractEndpoints` — see that function's docs. */
194
+ includeWebhooks?: boolean;
195
+ }
196
+ //#endregion
197
+ //#region src/server.d.ts
198
+ interface CreateOpenAPIOptions {
199
+ /**
200
+ * OpenAPI spec input — file paths (JSON/YAML), URLs, or raw spec objects.
201
+ * Multiple specs are merged by combining their paths.
202
+ */
203
+ input: string[] | OpenApiSpec[];
204
+ /** Disable spec caching (re-parse on every access). */
205
+ disableCache?: boolean;
206
+ }
207
+ /** Resolved operation lookup result. */
208
+ interface ResolvedOperation {
209
+ method: HttpMethod;
210
+ path: string;
211
+ operation: OpenApiOperation;
212
+ }
213
+ interface OpenAPIServer {
214
+ /** Get the parsed and merged OpenAPI spec. */
215
+ getSpec(): Promise<OpenApiSpec>;
216
+ /**
217
+ * Look up an operation by its `operationId`. Returns `undefined` when no
218
+ * operation matches.
219
+ */
220
+ getOperation(operationId: string): Promise<ResolvedOperation | undefined>;
221
+ /**
222
+ * Look up an operation by path + method (e.g. `'/users'`, `'get'`).
223
+ * Returns `undefined` when no operation matches.
224
+ */
225
+ getOperationByPath(path: string, method: HttpMethod): Promise<ResolvedOperation | undefined>;
226
+ /**
227
+ * Return every operation tagged with the given tag (case-sensitive, matches
228
+ * OpenAPI's `tags: [...]` array).
229
+ */
230
+ getOperationsByTag(tag: string): Promise<ResolvedOperation[]>;
231
+ /** Flat list of every extracted endpoint — paths and (by default) webhooks. */
232
+ getEndpoints(): Promise<EndpointEntry[]>;
233
+ /** Build a navigation tree from the merged spec. */
234
+ getNavigation(options?: BuildNavigationOptions): Promise<NavigationGroup[]>;
235
+ }
236
+ declare function createOpenAPI(options: CreateOpenAPIOptions): OpenAPIServer;
237
+ //#endregion
238
+ export { CreateOpenAPIOptions, OpenAPIServer, ResolvedOperation, createOpenAPI };
@@ -0,0 +1 @@
1
+ import{readFileSync as e}from"node:fs";import{openapiExtractEndpoints as t,openapiMergeSpecs as n,openapiNavigationBuild as r,openapiParseOpenapiSpec as i}from"@publier/native";import{parse as a}from"yaml";const o=[`get`,`post`,`put`,`delete`,`patch`,`head`,`options`];function s(e,n={}){let{mergePathParameters:r=!1,includeWebhooks:i=!0}=n;return JSON.parse(t(JSON.stringify(e),r,i))}function c(e,t={}){let{groupBy:n=`tag`,slugPrefix:i,excludeDeprecated:a=!1,includeWebhooks:o=!0}=t;return JSON.parse(r(JSON.stringify(e),n,i??null,a,o))}function l(e){let t;try{JSON.parse(e),t=e}catch{t=JSON.stringify(a(e))}return JSON.parse(i(t))}function u(t){let r=null;async function i(){if(r&&!t.disableCache)return r;let i=[];for(let n of t.input)if(typeof n==`string`){let t;if(n.startsWith(`http:`)||n.startsWith(`https:`)){let e=await fetch(n);if(!e.ok)throw Error(`Failed to fetch OpenAPI spec from "${n}": ${e.status} ${e.statusText}`);t=await e.text()}else t=e(n,`utf-8`);i.push(l(t))}else i.push(n);if(i.length===0)throw Error(`No OpenAPI specs provided`);return i.length===1?(r=i[0],r):(r=JSON.parse(n(JSON.stringify(i))),r)}return{getSpec:i,async getOperation(e){let t=await i();for(let[n,r]of Object.entries(t.paths))for(let t of o){let i=r[t];if(i?.operationId===e)return{method:t,path:n,operation:i}}},async getOperationByPath(e,t){let n=(await i()).paths[e]?.[t];return n?{method:t,path:e,operation:n}:void 0},async getOperationsByTag(e){return s(await i(),{mergePathParameters:!0}).filter(t=>t.operation.tags?.includes(e)).map(({method:e,path:t,operation:n})=>({method:e,path:t,operation:n}))},async getEndpoints(){return s(await i(),{mergePathParameters:!0})},async getNavigation(e){return c(await i(),e)}}}export{u as createOpenAPI};
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "@publier/openapi",
3
+ "version": "0.3.38",
4
+ "license": "UNLICENSED",
5
+ "_filesRationale": "dist-only: TS → tsdown → dist; publishConfig.exports remaps to dist/*.mjs",
6
+ "type": "module",
7
+ "files": [
8
+ "dist",
9
+ "src/components/**/*.astro",
10
+ "src/components/**/*.ts",
11
+ "src/components/props.ts",
12
+ "!src/**/__tests__/**",
13
+ "!src/**/*.test.ts"
14
+ ],
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.mts",
18
+ "import": "./dist/index.mjs"
19
+ },
20
+ "./server": {
21
+ "types": "./dist/server.d.mts",
22
+ "import": "./dist/server.mjs"
23
+ },
24
+ "./generate": {
25
+ "types": "./dist/generate.d.mts",
26
+ "import": "./dist/generate.mjs"
27
+ },
28
+ "./parser": {
29
+ "types": "./dist/parser.d.mts",
30
+ "import": "./dist/parser.mjs"
31
+ },
32
+ "./components/api-reference": "./src/components/api-reference.astro",
33
+ "./components/api-page": "./src/components/api-page.astro",
34
+ "./components/api-playground": "./src/components/api-playground.astro",
35
+ "./components/async-channel": "./src/components/async-channel.astro",
36
+ "./components/async-message": "./src/components/async-message.astro",
37
+ "./components/code-samples": "./src/components/code-samples.astro",
38
+ "./components/request-example": "./src/components/request-example.astro",
39
+ "./components/response-example": "./src/components/response-example.astro",
40
+ "./components/schema-view": "./src/components/schema-view.astro",
41
+ "./components/param-field": "./src/components/ParamField.astro",
42
+ "./components/response-field": "./src/components/ResponseField.astro",
43
+ "./components/method-badge": "./src/components/method-badge.astro"
44
+ },
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "dependencies": {
49
+ "@readme/httpsnippet": "^11.1.0",
50
+ "openapi-sampler": "^1.7.3",
51
+ "yaml": "^2.9.0",
52
+ "@publier/native": "0.10.1"
53
+ },
54
+ "peerDependencies": {
55
+ "astro": "^6.0.0 || >=7.0.0-alpha.0",
56
+ "tailwindcss": "^4"
57
+ },
58
+ "peerDependenciesMeta": {
59
+ "tailwindcss": {
60
+ "optional": true
61
+ }
62
+ },
63
+ "devDependencies": {
64
+ "@types/node": "^22",
65
+ "astro": "7.0.0-beta.3",
66
+ "tsdown": "^0.22.0",
67
+ "typescript": "^6.0.3"
68
+ },
69
+ "repository": {
70
+ "type": "git",
71
+ "url": "https://github.com/Publier/releases.git",
72
+ "directory": "packages/openapi"
73
+ },
74
+ "scripts": {
75
+ "build": "tsdown",
76
+ "dev": "tsdown --watch",
77
+ "check-types": "tsc --noEmit",
78
+ "test": "echo \"@publier/openapi: covered by root vitest workspace\" && exit 0"
79
+ }
80
+ }
@@ -0,0 +1,14 @@
1
+ ---
2
+ import { openapiRenderCollapsibleSection } from '@publier/native';
3
+
4
+ interface Props {
5
+ title: string;
6
+ defaultOpen?: boolean;
7
+ }
8
+
9
+ const { title, defaultOpen = false } = Astro.props;
10
+ const slotHtml = await Astro.slots.render('default');
11
+ const html = openapiRenderCollapsibleSection(title, defaultOpen, slotHtml);
12
+ ---
13
+
14
+ <Fragment set:html={html} />
@@ -0,0 +1,11 @@
1
+ ---
2
+ import { openapiRenderSchemaRow } from '@publier/native';
3
+ import type { OpenApiSchema } from '../types.ts';
4
+
5
+ interface Props { schema: OpenApiSchema; name?: string; required?: boolean; depth?: number }
6
+
7
+ const { schema, name, required = false, depth = 0 } = Astro.props;
8
+ const html = openapiRenderSchemaRow(JSON.stringify({ schema, name, required, depth }));
9
+ ---
10
+
11
+ <Fragment set:html={html} />
@@ -0,0 +1,21 @@
1
+ ---
2
+ import { openapiRenderApiPage } from '@publier/native';
3
+ import type { APIPageProps } from './props.ts';
4
+ type Props = APIPageProps;
5
+
6
+ const { method, path, operation } = Astro.props;
7
+ const html = openapiRenderApiPage(method, path, JSON.stringify(operation));
8
+ ---
9
+
10
+ <Fragment set:html={html} />
11
+
12
+ <style is:global>
13
+ .res-row {
14
+ border-left-color: var(--color-zinc-400);
15
+ background: color-mix(in oklch, var(--color-zinc-500) 5%, transparent);
16
+ }
17
+ .res-row[data-family="2"] { border-left-color: var(--color-green-500); background: color-mix(in oklch, var(--color-green-500) 5%, transparent); }
18
+ .res-row[data-family="3"] { border-left-color: var(--color-blue-500); background: color-mix(in oklch, var(--color-blue-500) 5%, transparent); }
19
+ .res-row[data-family="4"] { border-left-color: var(--color-yellow-500); background: color-mix(in oklch, var(--color-yellow-500) 5%, transparent); }
20
+ .res-row[data-family="5"] { border-left-color: var(--color-red-500); background: color-mix(in oklch, var(--color-red-500) 5%, transparent); }
21
+ </style>
@@ -0,0 +1,90 @@
1
+ export class PublierApiPlayground extends HTMLElement {
2
+ connectedCallback() {
3
+ this.querySelector('form')?.addEventListener('submit', this.#send.bind(this));
4
+ }
5
+
6
+ async #send(e: SubmitEvent) {
7
+ e.preventDefault();
8
+ const form = e.target as HTMLFormElement;
9
+ const data = new FormData(form);
10
+
11
+ const method = (form.dataset.method ?? 'get').toUpperCase();
12
+ let path = form.dataset.path ?? '/';
13
+
14
+ for (const [key, val] of data.entries()) {
15
+ if (key.startsWith('path:')) {
16
+ const paramName = key.slice(5);
17
+ path = path.replace(`{${paramName}}`, encodeURIComponent((val as string) || paramName));
18
+ }
19
+ }
20
+
21
+ const qs = new URLSearchParams();
22
+ for (const [key, val] of data.entries()) {
23
+ if (key.startsWith('query:') && val) qs.set(key.slice(6), val as string);
24
+ }
25
+ const server = (data.get('server') as string) ?? '';
26
+ const fullUrl = `${server}${path}${qs.size > 0 ? `?${qs}` : ''}`;
27
+
28
+ const headers: Record<string, string> = { 'Content-Type': 'application/json' };
29
+ const bearer = data.get('auth:bearer') as string;
30
+ const apiKey = data.get('auth:apikey') as string;
31
+ if (bearer) headers.Authorization = `Bearer ${bearer}`;
32
+ if (apiKey) headers['X-API-Key'] = apiKey;
33
+ for (const [key, val] of data.entries()) {
34
+ if (key.startsWith('header:') && val) headers[key.slice(7)] = val as string;
35
+ }
36
+
37
+ const hasBody = !['GET', 'HEAD', 'OPTIONS'].includes(method);
38
+ const body = hasBody ? (data.get('body') as string) || undefined : undefined;
39
+
40
+ const sendBtn = this.querySelector<HTMLButtonElement>('[data-pg-send]');
41
+ const responseEl = this.querySelector('[data-pg-response]');
42
+ const errorEl = this.querySelector('[data-pg-error]');
43
+
44
+ if (sendBtn) {
45
+ sendBtn.disabled = true;
46
+ sendBtn.textContent = 'Sending…';
47
+ }
48
+ if (errorEl) errorEl.textContent = '';
49
+ if (responseEl) responseEl.innerHTML = '';
50
+
51
+ try {
52
+ const res = await fetch(fullUrl, { method, headers, body });
53
+ const bodyText = await res.text();
54
+
55
+ let formatted = bodyText;
56
+ try {
57
+ formatted = JSON.stringify(JSON.parse(bodyText), null, 2);
58
+ } catch {}
59
+
60
+ const isSuccess = res.status >= 200 && res.status < 300;
61
+ const statusColor = isSuccess ? 'var(--color-green-400)' : 'var(--color-red-400)';
62
+
63
+ if (responseEl) {
64
+ responseEl.innerHTML = `
65
+ <div style="display:flex;align-items:center;padding:0.375rem 0.75rem;border-bottom:1px solid var(--color-border,rgba(0,0,0,.12))">
66
+ <span style="font-family:ui-monospace,monospace;font-size:0.75rem;font-weight:600;color:${statusColor}">
67
+ ${this.#escHtml(String(res.status))} ${this.#escHtml(res.statusText)}
68
+ </span>
69
+ </div>
70
+ <pre style="margin:0;overflow:auto;padding:0.625rem 0.75rem;font-family:ui-monospace,monospace;font-size:0.75rem;line-height:1.6"><code>${this.#escHtml(formatted)}</code></pre>
71
+ `;
72
+ }
73
+ } catch (err) {
74
+ if (errorEl) errorEl.textContent = err instanceof Error ? err.message : 'Request failed';
75
+ } finally {
76
+ if (sendBtn) {
77
+ sendBtn.disabled = false;
78
+ sendBtn.textContent = 'Send Request';
79
+ }
80
+ }
81
+ }
82
+
83
+ #escHtml(str: string): string {
84
+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
85
+ }
86
+ }
87
+
88
+ if (typeof customElements !== 'undefined' && !customElements.get('publier-api-playground')) {
89
+ customElements.define('publier-api-playground', PublierApiPlayground);
90
+ }
@@ -0,0 +1,14 @@
1
+ ---
2
+ import { openapiRenderApiPlayground } from '@publier/native';
3
+ import type { APIPlaygroundProps } from './props.ts';
4
+ type Props = APIPlaygroundProps;
5
+
6
+ const { method, path, operation, servers = [] } = Astro.props;
7
+ const html = openapiRenderApiPlayground(JSON.stringify({ method, path, operation, servers }));
8
+ ---
9
+
10
+ <Fragment set:html={html} />
11
+
12
+ <script>
13
+ import './api-playground-ce-element.ts';
14
+ </script>
@@ -0,0 +1,21 @@
1
+ ---
2
+ import { openapiRenderApiReferenceShell, openapiBuildInstanceId, openapiResolveBaseUrl } from '@publier/native';
3
+ import APIPage from './api-page.astro';
4
+ import CodeSamples from './code-samples.astro';
5
+ import RequestExample from './request-example.astro';
6
+ import ResponseExample from './response-example.astro';
7
+ import type { APIReferenceProps } from './props.ts';
8
+ type Props = APIReferenceProps;
9
+
10
+ const { method, path, operation, spec, baseUrl: baseUrlProp } = Astro.props;
11
+ const baseUrl = openapiResolveBaseUrl(baseUrlProp ?? null, spec?.servers?.[0]?.url ?? null);
12
+ const instanceId = openapiBuildInstanceId(method, path);
13
+ const L = JSON.parse(openapiRenderApiReferenceShell());
14
+ ---
15
+ <Fragment set:html={L.rootOpen + L.mainOpen} />
16
+ <APIPage method={method} path={path} operation={operation} />
17
+ <Fragment set:html={L.mainCloseAsideOpen} />
18
+ <RequestExample method={method} path={path} operation={operation} baseUrl={baseUrl} />
19
+ <CodeSamples method={method} path={path} operation={operation} baseUrl={baseUrl} />
20
+ <ResponseExample responses={operation.responses} instanceId={instanceId} />
21
+ <Fragment set:html={L.close} />
@@ -0,0 +1,11 @@
1
+ ---
2
+ import { openapiRenderAsyncChannel } from '@publier/native';
3
+ import type { AsyncChannelProps } from './props.ts';
4
+ type Props = AsyncChannelProps;
5
+
6
+ const { channelName, channel, operations, servers } = Astro.props;
7
+ const protocol = ((servers ? Object.values(servers)[0]?.protocol : undefined) ?? 'ws').toLowerCase();
8
+ const html = openapiRenderAsyncChannel(channelName, JSON.stringify(channel), JSON.stringify(operations), protocol);
9
+ ---
10
+
11
+ <Fragment set:html={html} />
@@ -0,0 +1,10 @@
1
+ ---
2
+ import { openapiRenderAsyncMessage } from '@publier/native';
3
+ import type { AsyncMessageProps } from './props.ts';
4
+ type Props = AsyncMessageProps;
5
+
6
+ const { message } = Astro.props;
7
+ const html = openapiRenderAsyncMessage(JSON.stringify(message));
8
+ ---
9
+
10
+ <Fragment set:html={html} />