@clidey/dory 0.9.0 → 0.10.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.
@@ -0,0 +1,135 @@
1
+ ---
2
+ title: 'WebSocket Playground Demo'
3
+ description: 'Interactive WebSocket playground for testing real-time connections'
4
+ ---
5
+
6
+ # WebSocket Playground Demo
7
+
8
+ This page demonstrates the WebSocket playground component with various configuration options and real-time connection testing capabilities.
9
+
10
+ ## Basic WebSocket Connection
11
+
12
+ <WebSocketPlayground
13
+ url="/ws/chat"
14
+ title="Basic Chat WebSocket"
15
+ description="Connect to a WebSocket server and send/receive messages in real-time"
16
+ servers={[
17
+ {
18
+ url: "wss://echo.websocket.org",
19
+ description: "WebSocket Echo Server"
20
+ },
21
+ {
22
+ url: "wss://ws.postman-echo.com/raw",
23
+ description: "Postman Echo WebSocket"
24
+ }
25
+ ]}
26
+ />
27
+
28
+ ## WebSocket with Subprotocols
29
+
30
+ <WebSocketPlayground
31
+ url="/ws/protocol"
32
+ title="WebSocket with Subprotocols"
33
+ description="WebSocket connection with custom subprotocols for specialized communication"
34
+ subprotocols={["chat", "v1.0"]}
35
+ servers={[
36
+ {
37
+ url: "wss://echo.websocket.org",
38
+ description: "Echo Server with Subprotocols"
39
+ }
40
+ ]}
41
+ />
42
+
43
+ ## WebSocket with Authentication
44
+
45
+ <WebSocketPlayground
46
+ url="/ws/secure"
47
+ title="Authenticated WebSocket"
48
+ description="WebSocket connection with authentication headers"
49
+ authType="bearer"
50
+ headers={{
51
+ "User-Agent": "Dory WebSocket Client",
52
+ "X-Client-Version": "1.0.0"
53
+ }}
54
+ servers={[
55
+ {
56
+ url: "wss://echo.websocket.org",
57
+ description: "Secure Echo Server"
58
+ }
59
+ ]}
60
+ />
61
+
62
+ ## Real-time Chat Example
63
+
64
+ <WebSocketPlayground
65
+ url="/chat"
66
+ title="Real-time Chat"
67
+ description="Connect to a chat server and participate in real-time conversations"
68
+ subprotocols={["chat.v1", "json"]}
69
+ headers={{
70
+ "X-Client-Type": "web",
71
+ "X-Protocol-Version": "1.0"
72
+ }}
73
+ servers={[
74
+ {
75
+ url: "wss://echo.websocket.org",
76
+ description: "Chat Echo Server"
77
+ }
78
+ ]}
79
+ />
80
+
81
+ ## Testing Instructions
82
+
83
+ 1. **Connect**: Click the "Connect" button to establish a WebSocket connection
84
+ 2. **Send Messages**: Type messages in the input field and click "Send" or press Enter
85
+ 3. **View Messages**: All sent and received messages appear in the message history with timestamps
86
+ 4. **Configuration**: Use the tabs to configure headers, subprotocols, and authentication
87
+ 5. **Server Selection**: Choose different servers from the dropdown to test various endpoints
88
+
89
+ ## Features
90
+
91
+ - **Real-time Connection**: Live WebSocket connection with status indicators
92
+ - **Message History**: View all sent/received messages with timestamps
93
+ - **Custom Headers**: Add authentication and custom headers
94
+ - **Subprotocols**: Configure WebSocket subprotocols
95
+ - **Multiple Servers**: Easy switching between different WebSocket endpoints
96
+ - **Error Handling**: Clear error messages for connection issues
97
+ - **Binary Support**: Handles both text and binary messages
98
+
99
+ ## Message Types
100
+
101
+ The playground supports different message types:
102
+ - **Sent**: Messages you send (blue background)
103
+ - **Received**: Messages from the server (green background)
104
+ - **Error**: Connection or sending errors (red background)
105
+ - **System**: Connection status updates (gray background)
106
+
107
+ ## Example Messages
108
+
109
+ Try sending these JSON messages to test structured data:
110
+
111
+ ```json
112
+ {
113
+ "type": "chat",
114
+ "message": "Hello, WebSocket!",
115
+ "userId": "user123",
116
+ "timestamp": "2024-01-15T10:30:00Z"
117
+ }
118
+ ```
119
+
120
+ ```json
121
+ {
122
+ "type": "presence",
123
+ "status": "online",
124
+ "userId": "user123"
125
+ }
126
+ ```
127
+
128
+ ```json
129
+ {
130
+ "type": "notification",
131
+ "title": "Test Notification",
132
+ "message": "This is a test notification",
133
+ "level": "info"
134
+ }
135
+ ```
package/k8s/Dockerfile CHANGED
@@ -2,6 +2,9 @@ FROM node:22.16-alpine
2
2
 
3
3
  WORKDIR /app
4
4
 
5
+ # Install git
6
+ RUN apk add --no-cache git
7
+
5
8
  COPY ./k8s/config/nginx-default.conf /app/k8s/config/nginx-default.conf
6
9
 
7
10
  # Install dory globally
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@clidey/dory",
3
3
  "private": false,
4
- "version": "0.9.0",
4
+ "version": "0.10.0",
5
5
  "type": "module",
6
6
  "description": "A CLI tool for building and previewing documentation sites",
7
7
  "keywords": [
@@ -11,6 +11,10 @@ export const ALL_OPENAPI = Object.fromEntries(
11
11
  Object.entries(import.meta.glob<{ default: string }>('../../docs/**/openapi.json', { query: 'raw', eager: true }))
12
12
  .map(([path, loader]) => [pathFromFilename(path), loader])
13
13
  );
14
+ export const ALL_ASYNCAPI = Object.fromEntries(
15
+ Object.entries(import.meta.glob<{ default: string }>('../../docs/**/asyncapi.json', { query: 'raw', eager: true }))
16
+ .map(([path, loader]) => [pathFromFilename(path), loader])
17
+ );
14
18
 
15
19
  export interface Route {
16
20
  path: string;
@@ -0,0 +1,369 @@
1
+ import { motion } from "motion/react"
2
+ import { useState } from "preact/hooks"
3
+ import { APIGroup } from "./api"
4
+ import { PlayButton } from "./api-playground"
5
+ import { Fence } from "./fence"
6
+ import { Properties, Property } from "./open-api"
7
+ import { Tag } from "./tag"
8
+ import { Col, Row } from "./text"
9
+ import { WebSocketPlayground, type WebSocketServer } from "./websocket-playground"
10
+
11
+ interface AsyncAPIMessage {
12
+ name?: string
13
+ title?: string
14
+ summary?: string
15
+ description?: string
16
+ payload?: Schema
17
+ headers?: Schema
18
+ correlationId?: {
19
+ description?: string
20
+ location?: string
21
+ }
22
+ schemaFormat?: string
23
+ contentType?: string
24
+ examples?: any[]
25
+ }
26
+
27
+ interface AsyncAPIOperation {
28
+ operationId?: string
29
+ summary?: string
30
+ description?: string
31
+ tags?: Array<{
32
+ name: string
33
+ description?: string
34
+ }>
35
+ message?: AsyncAPIMessage | {
36
+ oneOf?: AsyncAPIMessage[]
37
+ }
38
+ bindings?: {
39
+ ws?: {
40
+ method?: string
41
+ query?: Schema
42
+ headers?: Schema
43
+ bindingVersion?: string
44
+ }
45
+ }
46
+ }
47
+
48
+ interface AsyncAPIChannel {
49
+ address?: string
50
+ title?: string
51
+ summary?: string
52
+ description?: string
53
+ subscribe?: AsyncAPIOperation
54
+ publish?: AsyncAPIOperation
55
+ parameters?: Record<string, Parameter>
56
+ messages?: Record<string, AsyncAPIMessage>
57
+ bindings?: {
58
+ ws?: {
59
+ method?: string
60
+ query?: Schema
61
+ headers?: Schema
62
+ bindingVersion?: string
63
+ }
64
+ }
65
+ }
66
+
67
+ interface Schema {
68
+ $ref?: string
69
+ type?: string
70
+ items?: Schema
71
+ properties?: Record<string, Schema>
72
+ required?: string[]
73
+ description?: string
74
+ default?: any
75
+ example?: any
76
+ format?: string
77
+ enum?: any[]
78
+ oneOf?: Schema[]
79
+ anyOf?: Schema[]
80
+ allOf?: Schema[]
81
+ }
82
+
83
+ interface Parameter {
84
+ description?: string
85
+ schema?: Schema
86
+ location?: string
87
+ default?: any
88
+ }
89
+
90
+ interface Components {
91
+ schemas?: Record<string, Schema>
92
+ messages?: Record<string, AsyncAPIMessage>
93
+ parameters?: Record<string, Parameter>
94
+ }
95
+
96
+ interface Info {
97
+ title: string
98
+ version: string
99
+ description?: string
100
+ termsOfService?: string
101
+ contact?: {
102
+ name?: string
103
+ url?: string
104
+ email?: string
105
+ }
106
+ license?: {
107
+ name: string
108
+ url?: string
109
+ }
110
+ }
111
+
112
+ export interface AsyncAPIJson {
113
+ asyncapi: string
114
+ info: Info
115
+ servers?: Record<string, WebSocketServer & {
116
+ host?: string
117
+ pathname?: string
118
+ protocol?: string
119
+ protocolVersion?: string
120
+ description?: string
121
+ title?: string
122
+ summary?: string
123
+ variables?: Record<string, any>
124
+ security?: any[]
125
+ tags?: Array<{
126
+ name: string
127
+ description?: string
128
+ }>
129
+ bindings?: any
130
+ }>
131
+ channels: Record<string, AsyncAPIChannel>
132
+ components?: Components
133
+ defaultContentType?: string
134
+ }
135
+
136
+ function resolveSchema(schema: Schema | undefined, components: Components): Schema {
137
+ if (!schema) return {}
138
+
139
+ if (schema.$ref) {
140
+ const refPath = schema.$ref.replace(/^#\/components\/schemas\//, '')
141
+ return resolveSchema(components.schemas?.[refPath], components)
142
+ }
143
+
144
+ if (schema.type === 'array' && schema.items) {
145
+ return {
146
+ ...schema,
147
+ items: resolveSchema(schema.items, components)
148
+ }
149
+ }
150
+
151
+ if (schema.properties) {
152
+ const resolvedProps: Record<string, Schema> = {}
153
+ for (const [key, value] of Object.entries(schema.properties)) {
154
+ resolvedProps[key] = resolveSchema(value, components)
155
+ }
156
+ return {
157
+ ...schema,
158
+ properties: resolvedProps
159
+ }
160
+ }
161
+
162
+ return schema
163
+ }
164
+
165
+ function isDirectMessage(message: AsyncAPIMessage | { oneOf?: AsyncAPIMessage[] } | undefined): message is AsyncAPIMessage {
166
+ return message != null && !('oneOf' in message);
167
+ }
168
+
169
+ export function AsyncAPI({
170
+ asyncAPIJson,
171
+ channel: selectedChannel,
172
+ operation: selectedOperation
173
+ }: {
174
+ asyncAPIJson: AsyncAPIJson
175
+ channel: string
176
+ operation: 'subscribe' | 'publish'
177
+ }) {
178
+ const { channels, components, servers } = asyncAPIJson;
179
+ const [showPlayground, setShowPlayground] = useState(false)
180
+
181
+ const handleShowPlayground = () => {
182
+ setShowPlayground(true)
183
+ }
184
+
185
+ // Convert AsyncAPI servers to WebSocket servers
186
+ const wsServers: WebSocketServer[] = servers ? Object.entries(servers).map(([key, server]) => ({
187
+ url: `${server.protocol}://${server.host}${server.pathname || ''}`,
188
+ description: server.description || server.title || key,
189
+ protocol: server.protocol
190
+ })) : [];
191
+
192
+ return (
193
+ <div className="max-w-none w-full">
194
+ {showPlayground && (
195
+ <motion.div
196
+ initial={{ x: "100%" }}
197
+ animate={{ x: 0 }}
198
+ exit={{ x: "100%" }}
199
+ transition={{ type: "spring", damping: 20 }}
200
+ className="fixed inset-y-0 right-0 w-full max-w-3xl bg-white dark:bg-[#1e1e1e] shadow-xl z-50 overflow-y-auto border-l border-zinc-200 dark:border-zinc-800"
201
+ >
202
+ <div className="p-6">
203
+ <div className="flex justify-between items-center mb-6">
204
+ <h2 className="text-xl font-semibold">WebSocket Playground</h2>
205
+ <button
206
+ onClick={() => setShowPlayground(false)}
207
+ className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
208
+ >
209
+ <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
210
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
211
+ </svg>
212
+ </button>
213
+ </div>
214
+ <WebSocketPlayground
215
+ url={selectedChannel}
216
+ servers={wsServers}
217
+ />
218
+ </div>
219
+ </motion.div>
220
+ )}
221
+
222
+ {Object.entries(channels).map(([channelPath, channelDetails]) => {
223
+ if (channelPath !== selectedChannel) {
224
+ return null
225
+ }
226
+
227
+ const operation = channelDetails[selectedOperation]
228
+ if (!operation) {
229
+ return null
230
+ }
231
+
232
+ const { summary, description, message } = operation
233
+ const resolvedMessage = isDirectMessage(message) ? message :
234
+ (message?.oneOf && message.oneOf.length > 0 ? message.oneOf[0] : undefined)
235
+
236
+ return (
237
+ <div key={channelPath} className="my-8">
238
+ <Row cols={2}>
239
+ <Col>
240
+ <h2 className="flex items-center mt-4">
241
+ <Tag variant="medium" className="mr-2 py-1 px-4 rounded-lg">
242
+ {selectedOperation.toUpperCase()}
243
+ </Tag>
244
+ <span className="ml-2 font-mono">{channelPath}</span>
245
+ </h2>
246
+
247
+ {(summary || description) && (
248
+ <p>{summary || description}</p>
249
+ )}
250
+
251
+ {resolvedMessage && (
252
+ <div className="mt-4">
253
+ <h3>Message</h3>
254
+ {resolvedMessage.title && <h4>{resolvedMessage.title}</h4>}
255
+ {resolvedMessage.description && <p>{resolvedMessage.description}</p>}
256
+
257
+ {resolvedMessage.payload && (
258
+ <div className="mt-4">
259
+ <h4>Payload</h4>
260
+ {(() => {
261
+ const resolvedPayload = resolveSchema(resolvedMessage.payload, components || {})
262
+ return (
263
+ <Properties>
264
+ {Object.entries(resolvedPayload.properties || {}).map(([propName, prop]) => (
265
+ <Property
266
+ key={propName}
267
+ name={propName}
268
+ type={prop.type || 'string'}
269
+ required={resolvedPayload.required?.includes(propName)}
270
+ >
271
+ {prop.description}
272
+ {prop.default !== undefined && (
273
+ <span className="text-gray-500 ml-1">(default: {JSON.stringify(prop.default)})</span>
274
+ )}
275
+ </Property>
276
+ ))}
277
+ </Properties>
278
+ )
279
+ })()}
280
+ </div>
281
+ )}
282
+
283
+ {resolvedMessage.headers && (
284
+ <div className="mt-4">
285
+ <h4>Headers</h4>
286
+ {(() => {
287
+ const resolvedHeaders = resolveSchema(resolvedMessage.headers, components || {})
288
+ return (
289
+ <Properties>
290
+ {Object.entries(resolvedHeaders.properties || {}).map(([headerName, header]) => (
291
+ <Property
292
+ key={headerName}
293
+ name={headerName}
294
+ type={header.type || 'string'}
295
+ required={resolvedHeaders.required?.includes(headerName)}
296
+ >
297
+ {header.description}
298
+ </Property>
299
+ ))}
300
+ </Properties>
301
+ )
302
+ })()}
303
+ </div>
304
+ )}
305
+
306
+ {resolvedMessage?.examples && resolvedMessage.examples.length > 0 && (
307
+ <div className="mt-4">
308
+ <h4>Examples</h4>
309
+ {resolvedMessage.examples.map((example: any, idx: number) => (
310
+ <Fence key={idx} language="json">
311
+ {JSON.stringify(example, null, 2)}
312
+ </Fence>
313
+ ))}
314
+ </div>
315
+ )}
316
+ </div>
317
+ )}
318
+
319
+ {components?.schemas && (
320
+ <div className="mt-4">
321
+ <h3>Schemas</h3>
322
+ {Object.entries(components.schemas).filter(([, schema]) => schema.properties).map(([name, schema]) => (
323
+ <div key={name} className="mt-4">
324
+ <h4>{name}</h4>
325
+ <Properties>
326
+ {Object.entries(schema.properties || {}).map(([propName, prop]) => (
327
+ <Property
328
+ key={propName}
329
+ name={propName}
330
+ type={prop.type || 'string'}
331
+ required={schema.required?.includes(propName)}
332
+ >
333
+ {prop.description}
334
+ </Property>
335
+ ))}
336
+ </Properties>
337
+ </div>
338
+ ))}
339
+ </div>
340
+ )}
341
+ </Col>
342
+ <Col sticky>
343
+ <button
344
+ onClick={handleShowPlayground}
345
+ className='ml-auto mt-3 flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition bg-sky-600 text-white hover:bg-sky-700'
346
+ >
347
+ Try
348
+ <PlayButton className="h-4 w-4" />
349
+ </button>
350
+ <APIGroup title="Message" tag={selectedOperation.toUpperCase()} label={channelPath}>
351
+ <>
352
+ {resolvedMessage?.payload && (() => {
353
+ const resolvedPayload = resolveSchema(resolvedMessage.payload, components || {})
354
+ return (
355
+ <Fence language="json">
356
+ {JSON.stringify(resolvedPayload, null, 2)}
357
+ </Fence>
358
+ )
359
+ })()}
360
+ </>
361
+ </APIGroup>
362
+ </Col>
363
+ </Row>
364
+ </div>
365
+ )
366
+ })}
367
+ </div>
368
+ )
369
+ }
package/src/mdx/mdx.tsx CHANGED
@@ -18,6 +18,8 @@ export { Steps, Step } from './step';
18
18
  export { Table as table, Th as th, Td as td } from './table';
19
19
  export { UnorderedList as ul, OrderedList as ol, ListItem as li } from './list';
20
20
  export { APIPlayground } from './api-playground';
21
+ export { WebSocketPlayground } from './websocket-playground';
22
+ export { AsyncAPI } from './async-api';
21
23
  export { Source } from './source';
22
24
 
23
25
  export const wrapper = ({ children }: { children: preact.ComponentChildren }) => {
@@ -40,4 +42,6 @@ export const wrapper = ({ children }: { children: preact.ComponentChildren }) =>
40
42
  };
41
43
 
42
44
 
43
- export const hr = () => <hr className="my-4 text-gray-500/25 dark:text-gray-200/25" />;
45
+ export const hr = () => <hr className="my-4 text-gray-500/25 dark:text-gray-200/25" />;
46
+
47
+ export const br = () => <br />;