@kingsleyweb/bun-ui 0.0.0-stage → 3.0.0-rc.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.
Files changed (240) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +80 -2
  3. package/app/docs/App.tsx +72 -0
  4. package/app/docs/api/client.ts +303 -0
  5. package/app/docs/api/contract.ts +24 -0
  6. package/app/docs/api/docs.ts +38 -0
  7. package/app/docs/api/errors.ts +144 -0
  8. package/app/docs/boot.tsx +58 -0
  9. package/app/docs/components/Badge.tsx +65 -0
  10. package/app/docs/components/Button.tsx +37 -0
  11. package/app/docs/components/Card.tsx +41 -0
  12. package/app/docs/components/ConfirmDialog.tsx +173 -0
  13. package/app/docs/components/CopyButton.tsx +68 -0
  14. package/app/docs/components/Dialog.tsx +230 -0
  15. package/app/docs/components/EmptyState.tsx +34 -0
  16. package/app/docs/components/ErrorView.tsx +70 -0
  17. package/app/docs/components/Field.tsx +93 -0
  18. package/app/docs/components/JsonEditor.tsx +151 -0
  19. package/app/docs/components/JsonView.tsx +338 -0
  20. package/app/docs/components/KeyValue.tsx +63 -0
  21. package/app/docs/components/ProblemBanner.tsx +116 -0
  22. package/app/docs/components/ScreenErrorBoundary.tsx +141 -0
  23. package/app/docs/components/Spinner.tsx +33 -0
  24. package/app/docs/components/Table.tsx +33 -0
  25. package/app/docs/components/Tabs.tsx +226 -0
  26. package/app/docs/components/classNames.ts +4 -0
  27. package/app/docs/components/components.css +691 -0
  28. package/app/docs/components/copy.ts +89 -0
  29. package/app/docs/components/fieldContext.ts +29 -0
  30. package/app/docs/components/inputValues.ts +142 -0
  31. package/app/docs/components/inputs.tsx +387 -0
  32. package/app/docs/components/jsonParse.ts +338 -0
  33. package/app/docs/components/listKeys.ts +30 -0
  34. package/app/docs/config.ts +86 -0
  35. package/app/docs/context.ts +33 -0
  36. package/app/docs/embed.ts +74 -0
  37. package/app/docs/env.d.ts +2 -0
  38. package/app/docs/format.ts +9 -0
  39. package/app/docs/hooks/useUrlTab.ts +16 -0
  40. package/app/docs/main.tsx +11 -0
  41. package/app/docs/queryClient.ts +54 -0
  42. package/app/docs/router.tsx +194 -0
  43. package/app/docs/routing.ts +194 -0
  44. package/app/docs/screens/docs/document.ts +27 -0
  45. package/app/docs/screens/docs/http/OperationView.tsx +407 -0
  46. package/app/docs/screens/docs/http/Sidebar.tsx +190 -0
  47. package/app/docs/screens/docs/http/TryItPanel.tsx +603 -0
  48. package/app/docs/screens/docs/http/http.css +405 -0
  49. package/app/docs/screens/docs/http/index.tsx +395 -0
  50. package/app/docs/screens/docs/http/model.ts +604 -0
  51. package/app/docs/screens/docs/http/tryIt.ts +455 -0
  52. package/app/docs/screens/docs/permissions.ts +6 -0
  53. package/app/docs/screens/docs/schema/Prose.tsx +43 -0
  54. package/app/docs/screens/docs/schema/SchemaTree.tsx +319 -0
  55. package/app/docs/screens/docs/schema/index.ts +30 -0
  56. package/app/docs/screens/docs/schema/resolve.ts +466 -0
  57. package/app/docs/screens/docs/schema/schema.css +137 -0
  58. package/app/docs/screens/docs/schema/skeleton.ts +92 -0
  59. package/app/docs/screens/docs/viewer.tsx +154 -0
  60. package/app/docs/screens/docs/ws/InlineText.tsx +25 -0
  61. package/app/docs/screens/docs/ws/PayloadSchema.tsx +39 -0
  62. package/app/docs/screens/docs/ws/format.ts +82 -0
  63. package/app/docs/screens/docs/ws/index.tsx +286 -0
  64. package/app/docs/screens/docs/ws/items.tsx +786 -0
  65. package/app/docs/screens/docs/ws/model.ts +1178 -0
  66. package/app/docs/screens/docs/ws/panels.tsx +440 -0
  67. package/app/docs/screens/docs/ws/ws.css +231 -0
  68. package/app/docs/shortcuts.ts +94 -0
  69. package/app/docs/styles/base.css +847 -0
  70. package/app/docs/styles/tokens.css +143 -0
  71. package/app/docs/theme.ts +56 -0
  72. package/app/kit/unauthorized.ts +222 -0
  73. package/dist/docs/assets/main-0rchxtv5.js +18 -0
  74. package/dist/docs/assets/main-0rchxtv5.js.map +99 -0
  75. package/dist/docs/assets/main-cqgyvs86.css +1 -0
  76. package/dist/docs/manifest.json +26 -0
  77. package/dist/jobs/assets/AddFlowDialog-h6xkgx9b.js +6 -0
  78. package/dist/jobs/assets/AddFlowDialog-h6xkgx9b.js.map +11 -0
  79. package/dist/jobs/assets/AddFlowDialog-n7gtc84v.css +1 -0
  80. package/dist/jobs/assets/added-cbwsxv3e.js +4 -0
  81. package/dist/jobs/assets/added-cbwsxv3e.js.map +11 -0
  82. package/dist/jobs/assets/bodies-yyw5hmrp.js +6 -0
  83. package/dist/jobs/assets/bodies-yyw5hmrp.js.map +10 -0
  84. package/dist/jobs/assets/channel-tgkvqm8y.js +4 -0
  85. package/dist/jobs/assets/channel-tgkvqm8y.js.map +10 -0
  86. package/dist/jobs/assets/channelChecks-s4yz4yhw.js +4 -0
  87. package/dist/jobs/assets/channelChecks-s4yz4yhw.js.map +10 -0
  88. package/dist/jobs/assets/index-02427smp.css +1 -0
  89. package/dist/jobs/assets/index-09z2cjft.js +4 -0
  90. package/dist/jobs/assets/index-09z2cjft.js.map +16 -0
  91. package/dist/jobs/assets/index-18bn0gvy.css +1 -0
  92. package/dist/jobs/assets/index-47pxmyxn.js +4 -0
  93. package/dist/jobs/assets/index-47pxmyxn.js.map +10 -0
  94. package/dist/jobs/assets/index-4e55z986.js +4 -0
  95. package/dist/jobs/assets/index-4e55z986.js.map +11 -0
  96. package/dist/jobs/assets/index-5c933424.js +4 -0
  97. package/dist/jobs/assets/index-5c933424.js.map +18 -0
  98. package/dist/jobs/assets/index-5kbyqspm.css +1 -0
  99. package/dist/jobs/assets/index-5ng37hk6.css +1 -0
  100. package/dist/jobs/assets/index-5w8w7fvc.css +1 -0
  101. package/dist/jobs/assets/index-71qs5qyx.css +1 -0
  102. package/dist/jobs/assets/index-btpg4r8b.css +1 -0
  103. package/dist/jobs/assets/index-d5r5n0mm.js +4 -0
  104. package/dist/jobs/assets/index-d5r5n0mm.js.map +23 -0
  105. package/dist/jobs/assets/index-e0yy2skq.css +1 -0
  106. package/dist/jobs/assets/index-hs60b25v.js +10 -0
  107. package/dist/jobs/assets/index-hs60b25v.js.map +62 -0
  108. package/dist/jobs/assets/index-k2smrr70.js +4 -0
  109. package/dist/jobs/assets/index-k2smrr70.js.map +9 -0
  110. package/dist/jobs/assets/index-q24jw3w9.js +4 -0
  111. package/dist/jobs/assets/index-q24jw3w9.js.map +32 -0
  112. package/dist/jobs/assets/index-qfb80xen.css +1 -0
  113. package/dist/jobs/assets/index-rtynx6wj.js +4 -0
  114. package/dist/jobs/assets/index-rtynx6wj.js.map +10 -0
  115. package/dist/jobs/assets/live-6k7s6358.js +4 -0
  116. package/dist/jobs/assets/live-6k7s6358.js.map +10 -0
  117. package/dist/jobs/assets/main-0jmeh6mq.js +6 -0
  118. package/dist/jobs/assets/main-0jmeh6mq.js.map +12 -0
  119. package/dist/jobs/assets/main-3yj6bv90.js +5 -0
  120. package/dist/jobs/assets/main-3yj6bv90.js.map +10 -0
  121. package/dist/jobs/assets/main-4g69fn3x.js +5 -0
  122. package/dist/jobs/assets/main-4g69fn3x.js.map +10 -0
  123. package/dist/jobs/assets/main-4gte3eqq.js +5 -0
  124. package/dist/jobs/assets/main-4gte3eqq.js.map +10 -0
  125. package/dist/jobs/assets/main-4j1fcevh.js +5 -0
  126. package/dist/jobs/assets/main-4j1fcevh.js.map +10 -0
  127. package/dist/jobs/assets/main-54qwkz28.css +1 -0
  128. package/dist/jobs/assets/main-5grjvsy7.js +5 -0
  129. package/dist/jobs/assets/main-5grjvsy7.js.map +11 -0
  130. package/dist/jobs/assets/main-5zbx60gq.js +5 -0
  131. package/dist/jobs/assets/main-5zbx60gq.js.map +11 -0
  132. package/dist/jobs/assets/main-9prgjhg4.js +5 -0
  133. package/dist/jobs/assets/main-9prgjhg4.js.map +10 -0
  134. package/dist/jobs/assets/main-a250ffk7.js +17 -0
  135. package/dist/jobs/assets/main-a250ffk7.js.map +115 -0
  136. package/dist/jobs/assets/main-d6ge7w08.js +5 -0
  137. package/dist/jobs/assets/main-d6ge7w08.js.map +10 -0
  138. package/dist/jobs/assets/main-gngzht43.js +5 -0
  139. package/dist/jobs/assets/main-gngzht43.js.map +10 -0
  140. package/dist/jobs/assets/main-hg0hrg8j.js +5 -0
  141. package/dist/jobs/assets/main-hg0hrg8j.js.map +10 -0
  142. package/dist/jobs/assets/main-hm1ng3yg.js +5 -0
  143. package/dist/jobs/assets/main-hm1ng3yg.js.map +12 -0
  144. package/dist/jobs/assets/main-jkvd92z9.js +5 -0
  145. package/dist/jobs/assets/main-jkvd92z9.js.map +10 -0
  146. package/dist/jobs/assets/main-jv5xqd13.js +5 -0
  147. package/dist/jobs/assets/main-jv5xqd13.js.map +21 -0
  148. package/dist/jobs/assets/main-jxre7w93.js +5 -0
  149. package/dist/jobs/assets/main-jxre7w93.js.map +23 -0
  150. package/dist/jobs/assets/main-kwmqkjfx.js +5 -0
  151. package/dist/jobs/assets/main-kwmqkjfx.js.map +11 -0
  152. package/dist/jobs/assets/main-n9cf6bxm.js +5 -0
  153. package/dist/jobs/assets/main-n9cf6bxm.js.map +11 -0
  154. package/dist/jobs/assets/main-pegp0ygb.js +5 -0
  155. package/dist/jobs/assets/main-pegp0ygb.js.map +10 -0
  156. package/dist/jobs/assets/main-qr573853.js +5 -0
  157. package/dist/jobs/assets/main-qr573853.js.map +12 -0
  158. package/dist/jobs/assets/main-rq97rdr4.js +4 -0
  159. package/dist/jobs/assets/main-rq97rdr4.js.map +22 -0
  160. package/dist/jobs/assets/main-szjdmydc.js +5 -0
  161. package/dist/jobs/assets/main-szjdmydc.js.map +13 -0
  162. package/dist/jobs/assets/main-tnbhrm9n.js +7 -0
  163. package/dist/jobs/assets/main-tnbhrm9n.js.map +11 -0
  164. package/dist/jobs/assets/main-ts19j4n0.js +5 -0
  165. package/dist/jobs/assets/main-ts19j4n0.js.map +25 -0
  166. package/dist/jobs/assets/main-w93x1dzs.js +5 -0
  167. package/dist/jobs/assets/main-w93x1dzs.js.map +10 -0
  168. package/dist/jobs/assets/sections-0ksdxs8m.css +1 -0
  169. package/dist/jobs/assets/sections-0y7w4fb4.js +4 -0
  170. package/dist/jobs/assets/sections-0y7w4fb4.js.map +10 -0
  171. package/dist/jobs/assets/sse-mn0t0fh7.js +5 -0
  172. package/dist/jobs/assets/sse-mn0t0fh7.js.map +11 -0
  173. package/dist/jobs/assets/sseChecks-t7tcm7ra.js +4 -0
  174. package/dist/jobs/assets/sseChecks-t7tcm7ra.js.map +10 -0
  175. package/dist/jobs/assets/validation-ysjfy072.js +4 -0
  176. package/dist/jobs/assets/validation-ysjfy072.js.map +9 -0
  177. package/dist/jobs/manifest.json +511 -0
  178. package/dts/docs/assets.d.ts +144 -0
  179. package/dts/docs/assets.d.ts.map +1 -0
  180. package/dts/docs/browser/config.d.ts +61 -0
  181. package/dts/docs/browser/config.d.ts.map +1 -0
  182. package/dts/docs/browser/index.d.ts +12 -0
  183. package/dts/docs/browser/index.d.ts.map +1 -0
  184. package/dts/docs/docsUi.d.ts +42 -0
  185. package/dts/docs/docsUi.d.ts.map +1 -0
  186. package/dts/docs/hosted.d.ts +22 -0
  187. package/dts/docs/hosted.d.ts.map +1 -0
  188. package/dts/docs/index.d.ts +18 -0
  189. package/dts/docs/index.d.ts.map +1 -0
  190. package/dts/docs/shell.d.ts +57 -0
  191. package/dts/docs/shell.d.ts.map +1 -0
  192. package/dts/docs/types.d.ts +137 -0
  193. package/dts/docs/types.d.ts.map +1 -0
  194. package/dts/index.d.ts +21 -0
  195. package/dts/index.d.ts.map +1 -0
  196. package/dts/jobs/assets.d.ts +157 -0
  197. package/dts/jobs/assets.d.ts.map +1 -0
  198. package/dts/jobs/browser/config.d.ts +67 -0
  199. package/dts/jobs/browser/config.d.ts.map +1 -0
  200. package/dts/jobs/browser/index.d.ts +10 -0
  201. package/dts/jobs/browser/index.d.ts.map +1 -0
  202. package/dts/jobs/browser/unauthorized.d.ts +9 -0
  203. package/dts/jobs/browser/unauthorized.d.ts.map +1 -0
  204. package/dts/jobs/index.d.ts +24 -0
  205. package/dts/jobs/index.d.ts.map +1 -0
  206. package/dts/jobs/jobsUi.d.ts +63 -0
  207. package/dts/jobs/jobsUi.d.ts.map +1 -0
  208. package/dts/jobs/shell.d.ts +77 -0
  209. package/dts/jobs/shell.d.ts.map +1 -0
  210. package/dts/jobs/types.d.ts +146 -0
  211. package/dts/jobs/types.d.ts.map +1 -0
  212. package/dts/kit/browser/shared.d.ts +10 -0
  213. package/dts/kit/browser/shared.d.ts.map +1 -0
  214. package/dts/kit/browser/unauthorized.d.ts +74 -0
  215. package/dts/kit/browser/unauthorized.d.ts.map +1 -0
  216. package/dts/kit/unauthorized.d.ts +47 -0
  217. package/dts/kit/unauthorized.d.ts.map +1 -0
  218. package/guides/docs.md +170 -0
  219. package/guides/jobs.md +1120 -0
  220. package/lib/docs/assets.ts +427 -0
  221. package/lib/docs/browser/config.ts +66 -0
  222. package/lib/docs/browser/index.ts +24 -0
  223. package/lib/docs/docsUi.ts +623 -0
  224. package/lib/docs/hosted.ts +31 -0
  225. package/lib/docs/index.ts +37 -0
  226. package/lib/docs/shell.ts +156 -0
  227. package/lib/docs/types.ts +149 -0
  228. package/lib/index.ts +27 -0
  229. package/lib/jobs/assets.ts +448 -0
  230. package/lib/jobs/browser/config.ts +65 -0
  231. package/lib/jobs/browser/index.ts +9 -0
  232. package/lib/jobs/browser/unauthorized.ts +11 -0
  233. package/lib/jobs/index.ts +41 -0
  234. package/lib/jobs/jobsUi.ts +709 -0
  235. package/lib/jobs/shell.ts +205 -0
  236. package/lib/jobs/types.ts +162 -0
  237. package/lib/kit/browser/shared.ts +11 -0
  238. package/lib/kit/browser/unauthorized.ts +114 -0
  239. package/lib/kit/unauthorized.ts +222 -0
  240. package/package.json +198 -4
@@ -0,0 +1,440 @@
1
+ /**
2
+ * The WebSocket reference's panels read from the document's extensions and
3
+ * header: the server and its security, `<prefix>-limits`,
4
+ * `<prefix>-close-codes` and `<prefix>-upgrade-refusals`.
5
+ */
6
+ import type { LimitInfo } from "./format";
7
+ import type { WsDoc, WsLimits } from "./model";
8
+ import { Badge } from "../../../components/Badge";
9
+ import { Card } from "../../../components/Card";
10
+ import { KeyValue } from "../../../components/KeyValue";
11
+ import { Table } from "../../../components/Table";
12
+ import { formatNumber } from "../../../format";
13
+ import { Prose } from "../schema";
14
+ import { formatLimit, formatMs } from "./format";
15
+ import { InlineText } from "./InlineText";
16
+ import { subprotocolSource } from "./model";
17
+
18
+ /** The server: its URL, host, path, protocol and subprotocol. */
19
+ export function ServerPanel({ doc }: { doc: WsDoc }) {
20
+ const { server, subprotocol } = doc;
21
+ return (
22
+ <Card title="Server">
23
+ {server === null ? (
24
+ <p className="muted">The document names no server.</p>
25
+ ) : (
26
+ <KeyValue
27
+ items={[
28
+ {
29
+ label: "URL",
30
+ value: server.url && (
31
+ <code data-testid="ws-server-url">{server.url}</code>
32
+ ),
33
+ },
34
+ {
35
+ label: "Host",
36
+ value: <code>{server.host}</code>,
37
+ hint: server.hostIsTemplate
38
+ ? `A template: the document served over HTTP fills in the request's own host${server.hostDefault ? ` (default ${server.hostDefault})` : ""}.`
39
+ : "Taken from the request that fetched this document: the host you reached the API on.",
40
+ },
41
+ {
42
+ label: "Path",
43
+ value: server.pathname && <code>{server.pathname}</code>,
44
+ },
45
+ {
46
+ label: "Protocol",
47
+ value: server.protocol && <code>{server.protocol}</code>,
48
+ hint: "wss when the API was reached over HTTPS.",
49
+ },
50
+ {
51
+ label: "Subprotocol",
52
+ value:
53
+ subprotocol.value === undefined ? (
54
+ <span
55
+ className="muted"
56
+ data-testid="ws-subprotocol"
57
+ >
58
+ Not stated
59
+ </span>
60
+ ) : (
61
+ <code data-testid="ws-subprotocol">{subprotocol.value}</code>
62
+ ),
63
+ hint: (
64
+ <>
65
+ <span data-testid="ws-subprotocol-source">
66
+ {subprotocolSource(doc)}
67
+ </span>
68
+ {subprotocol.fromDocument
69
+ ? ". Optional: offer it in Sec-WebSocket-Protocol, or offer none. Offering others without it may be refused."
70
+ : "."}
71
+ </>
72
+ ),
73
+ },
74
+ server.description !== undefined && {
75
+ label: "About",
76
+ value: server.description,
77
+ },
78
+ ]}
79
+ />
80
+ )}
81
+ </Card>
82
+ );
83
+ }
84
+
85
+ /** Security: every requirement (any one suffices), and each scheme with what a browser cannot send. */
86
+ export function SecurityPanel({ doc }: { doc: WsDoc }) {
87
+ const { requirements, securitySchemes, conjunctive } = doc;
88
+ return (
89
+ <Card title="Security">
90
+ <div data-testid="ws-security">
91
+ {requirements.length === 0 ? (
92
+ <p className="muted">
93
+ No security schemes are declared. The host application decides, on
94
+ the upgrade and on each subscription; a browser sends its cookies
95
+ with the upgrade.
96
+ </p>
97
+ ) : (
98
+ <>
99
+ <p>
100
+ {requirements.length === 1
101
+ ? "The connection must satisfy:"
102
+ : "The connection must satisfy any one of:"}
103
+ </p>
104
+ <ul
105
+ className="ws-requirements"
106
+ aria-label="Security requirements"
107
+ >
108
+ {requirements.map((requirement) => {
109
+ const key = requirement
110
+ .map((part) => `${part.scheme}:${part.scopes.join(",")}`)
111
+ .join("+");
112
+ return (
113
+ <li key={key}>
114
+ {requirement.map((part, index) => (
115
+ <span key={part.scheme}>
116
+ {index > 0 && <strong> and </strong>}
117
+ <code>{part.scheme}</code>
118
+ {part.scopes.length > 0 && (
119
+ <span className="muted">
120
+ {" "}
121
+ (scopes: {part.scopes.join(", ")})
122
+ </span>
123
+ )}
124
+ </span>
125
+ ))}
126
+ </li>
127
+ );
128
+ })}
129
+ </ul>
130
+ {conjunctive && (
131
+ <p
132
+ className="ws-note"
133
+ data-testid="ws-security-conjunctive"
134
+ >
135
+ AsyncAPI 3 can list only one scheme per requirement, so a
136
+ requirement naming several is only in{" "}
137
+ <code>
138
+ {doc.extensionKeys.security ?? "the security extension"}
139
+ </code>
140
+ , which is what this list shows: every scheme within one
141
+ requirement is needed together.
142
+ </p>
143
+ )}
144
+ <Table label="Security schemes">
145
+ <thead>
146
+ <tr>
147
+ <th scope="col">Scheme</th>
148
+ <th scope="col">Type</th>
149
+ <th scope="col">In a browser</th>
150
+ </tr>
151
+ </thead>
152
+ <tbody>
153
+ {securitySchemes.map((scheme) => (
154
+ <tr
155
+ key={scheme.name}
156
+ data-testid={`ws-scheme-${scheme.name}`}
157
+ >
158
+ <td>
159
+ <code>{scheme.name}</code>
160
+ {scheme.description && (
161
+ <div className="muted">{scheme.description}</div>
162
+ )}
163
+ </td>
164
+ <td>
165
+ {scheme.type}
166
+ {scheme.detail && (
167
+ <div className="muted">{scheme.detail}</div>
168
+ )}
169
+ </td>
170
+ <td>
171
+ {scheme.note ? (
172
+ <span className="ws-note">
173
+ <InlineText text={scheme.note} />
174
+ </span>
175
+ ) : (
176
+ <span className="muted">Can be sent.</span>
177
+ )}
178
+ </td>
179
+ </tr>
180
+ ))}
181
+ </tbody>
182
+ </Table>
183
+ </>
184
+ )}
185
+ </div>
186
+ </Card>
187
+ );
188
+ }
189
+
190
+ /** The replay setting in words. */
191
+ function replayText(replay: WsLimits["replay"]): string {
192
+ if (replay === false) {
193
+ return "Off: a resume after a reconnect always reports a gap.";
194
+ }
195
+ if (replay === undefined) {
196
+ return "Not stated.";
197
+ }
198
+ const parts = [
199
+ replay.size !== undefined && `the last ${formatNumber(replay.size)} events`,
200
+ replay.maxAgeMs !== undefined && `at most ${formatMs(replay.maxAgeMs)} old`,
201
+ ].filter(Boolean);
202
+ return parts.length > 0 ? `Keeps ${parts.join(", ")}.` : "On.";
203
+ }
204
+
205
+ /** `<prefix>-limits`: what a connection is held to. */
206
+ export function LimitsPanel({
207
+ limits,
208
+ extensionKey,
209
+ limitsInfo,
210
+ }: {
211
+ /** The limits, read. */
212
+ limits: WsLimits;
213
+ /** The extension's full key (e.g. `x-bun-jobs-limits`), when one was read. */
214
+ extensionKey?: string;
215
+ /** Labels, meanings and units per limit name; the name's suffix decides the unit without one. */
216
+ limitsInfo?: Readonly<Record<string, LimitInfo>>;
217
+ }) {
218
+ return (
219
+ <Card title="Limits">
220
+ <p className="muted">
221
+ What one connection is held to
222
+ {extensionKey === undefined ? (
223
+ ""
224
+ ) : (
225
+ <>
226
+ {" "}
227
+ (<code>{extensionKey}</code>)
228
+ </>
229
+ )}
230
+ .
231
+ </p>
232
+ <Table
233
+ label="Limits"
234
+ className="ws-table"
235
+ >
236
+ <thead>
237
+ <tr>
238
+ <th scope="col">Limit</th>
239
+ <th scope="col">Value</th>
240
+ <th scope="col">Meaning</th>
241
+ </tr>
242
+ </thead>
243
+ <tbody>
244
+ {limits.values.map(([name, value]) => (
245
+ <tr
246
+ key={name}
247
+ data-testid={`ws-limit-${name}`}
248
+ >
249
+ <td>
250
+ {limitsInfo?.[name]?.label ?? name}
251
+ <div className="muted">
252
+ <code>{name}</code>
253
+ </div>
254
+ </td>
255
+ <td className="ws-value">
256
+ {formatLimit(name, value, limitsInfo?.[name])}
257
+ </td>
258
+ <td>{limitsInfo?.[name]?.meaning ?? ""}</td>
259
+ </tr>
260
+ ))}
261
+ <tr data-testid="ws-limit-replay">
262
+ <td>
263
+ Replay
264
+ <div className="muted">
265
+ <code>replay</code>
266
+ </div>
267
+ </td>
268
+ <td className="ws-value">
269
+ {limits.replay === false
270
+ ? "off"
271
+ : limits.replay === undefined
272
+ ? "—"
273
+ : [
274
+ limits.replay.size !== undefined &&
275
+ `size ${formatNumber(limits.replay.size)}`,
276
+ limits.replay.maxAgeMs !== undefined &&
277
+ `maxAgeMs ${formatMs(limits.replay.maxAgeMs)}`,
278
+ ]
279
+ .filter(Boolean)
280
+ .join(", ")}
281
+ </td>
282
+ <td>The events kept for a resume. {replayText(limits.replay)}</td>
283
+ </tr>
284
+ </tbody>
285
+ </Table>
286
+ </Card>
287
+ );
288
+ }
289
+
290
+ /** `<prefix>-close-codes`: why an open connection is closed. */
291
+ export function CloseCodesPanel({ doc }: { doc: WsDoc }) {
292
+ return (
293
+ <Card title="Close codes">
294
+ <p className="muted">
295
+ How an open connection is closed
296
+ {doc.extensionKeys.closeCodes === undefined ? (
297
+ ""
298
+ ) : (
299
+ <>
300
+ {" "}
301
+ (<code>{doc.extensionKeys.closeCodes}</code>)
302
+ </>
303
+ )}
304
+ .
305
+ </p>
306
+ <Table
307
+ label="Close codes"
308
+ className="ws-table"
309
+ >
310
+ <thead>
311
+ <tr>
312
+ <th scope="col">Code</th>
313
+ <th scope="col">Name</th>
314
+ <th scope="col">When</th>
315
+ </tr>
316
+ </thead>
317
+ <tbody>
318
+ {doc.closeCodes.map((entry) => (
319
+ <tr
320
+ key={`${entry.code}-${entry.name}`}
321
+ data-testid={`ws-close-${entry.code}`}
322
+ >
323
+ <td className="ws-value">{entry.code}</td>
324
+ <td>
325
+ <code>{entry.name}</code>
326
+ </td>
327
+ <td>
328
+ <InlineText text={entry.description} />
329
+ </td>
330
+ </tr>
331
+ ))}
332
+ </tbody>
333
+ </Table>
334
+ </Card>
335
+ );
336
+ }
337
+
338
+ /** `<prefix>-upgrade-refusals`: how an upgrade is refused before a socket exists. */
339
+ export function RefusalsPanel({ doc }: { doc: WsDoc }) {
340
+ return (
341
+ <Card title="Upgrade refusals">
342
+ <p className="muted">
343
+ An upgrade can be refused before any socket exists, with an ordinary
344
+ HTTP response whose body is a problem carrying <code>code</code>
345
+ {doc.extensionKeys.upgradeRefusals === undefined ? (
346
+ ""
347
+ ) : (
348
+ <>
349
+ {" "}
350
+ (<code>{doc.extensionKeys.upgradeRefusals}</code>)
351
+ </>
352
+ )}
353
+ .
354
+ </p>
355
+ <p
356
+ className="ws-note"
357
+ data-testid="ws-refusal-1006"
358
+ >
359
+ A browser never sees these: its <code>WebSocket</code> exposes no status
360
+ or body for a refused upgrade, only a close with code{" "}
361
+ <strong>1006</strong> (abnormal closure). To tell them apart, make the
362
+ same request over HTTP, or read the server&apos;s logs.
363
+ </p>
364
+ <Table
365
+ label="Upgrade refusals"
366
+ className="ws-table"
367
+ >
368
+ <thead>
369
+ <tr>
370
+ <th scope="col">Status</th>
371
+ <th scope="col">Code</th>
372
+ <th scope="col">When</th>
373
+ <th scope="col">Content type</th>
374
+ <th scope="col">Headers</th>
375
+ </tr>
376
+ </thead>
377
+ <tbody>
378
+ {doc.refusals.map((entry) => (
379
+ <tr
380
+ key={`${entry.status}-${entry.code}`}
381
+ data-testid={`ws-refusal-${entry.code}`}
382
+ >
383
+ <td className="ws-value">
384
+ <Badge tone={entry.status >= 500 ? "danger" : "warning"}>
385
+ {entry.status}
386
+ </Badge>
387
+ </td>
388
+ <td>
389
+ <code>{entry.code}</code>
390
+ </td>
391
+ <td>
392
+ <InlineText text={entry.description} />
393
+ </td>
394
+ <td>{entry.contentType && <code>{entry.contentType}</code>}</td>
395
+ <td>
396
+ {Object.keys(entry.headers).length === 0 ? (
397
+ <span className="muted">—</span>
398
+ ) : (
399
+ Object.entries(entry.headers).map(([name, value]) => (
400
+ <div key={name}>
401
+ <code>
402
+ {name}: {value}
403
+ </code>
404
+ </div>
405
+ ))
406
+ )}
407
+ </td>
408
+ </tr>
409
+ ))}
410
+ </tbody>
411
+ </Table>
412
+ </Card>
413
+ );
414
+ }
415
+
416
+ /** The document's header: title, version and description. */
417
+ export function DocHeader({ doc }: { doc: WsDoc }) {
418
+ return (
419
+ <header className="ws-header">
420
+ <h1 className="screen-title">
421
+ {doc.title}{" "}
422
+ {doc.version && (
423
+ <Badge
424
+ tone="neutral"
425
+ title="info.version"
426
+ >
427
+ v{doc.version}
428
+ </Badge>
429
+ )}
430
+ </h1>
431
+ <p className="muted ws-kicker">WebSocket API (AsyncAPI 3.0)</p>
432
+ {doc.description && (
433
+ <details className="ws-description">
434
+ <summary>About this socket</summary>
435
+ <Prose text={doc.description} />
436
+ </details>
437
+ )}
438
+ </header>
439
+ );
440
+ }
@@ -0,0 +1,231 @@
1
+ /* The WebSocket reference: header, overview cards, sidebar and item pane. */
2
+
3
+ .ws-docs {
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: var(--space-4);
7
+ }
8
+
9
+ .ws-header .screen-title {
10
+ display: flex;
11
+ flex-wrap: wrap;
12
+ align-items: center;
13
+ gap: var(--space-2);
14
+ overflow-wrap: anywhere;
15
+ }
16
+
17
+ .ws-kicker {
18
+ margin: 0;
19
+ }
20
+
21
+ .ws-description summary {
22
+ cursor: pointer;
23
+ }
24
+
25
+ .ws-description p {
26
+ max-width: 72ch;
27
+ overflow-wrap: anywhere;
28
+ }
29
+
30
+ .ws-overview {
31
+ display: grid;
32
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr));
33
+ gap: var(--space-4);
34
+ }
35
+
36
+ .ws-layout {
37
+ display: grid;
38
+ grid-template-columns: minmax(14rem, 18rem) minmax(0, 1fr);
39
+ gap: var(--space-4);
40
+ align-items: start;
41
+ }
42
+
43
+ @media (max-width: 800px) {
44
+ .ws-layout {
45
+ grid-template-columns: minmax(0, 1fr);
46
+ }
47
+ }
48
+
49
+ .ws-sidebar {
50
+ position: sticky;
51
+ top: var(--space-4);
52
+ max-height: calc(100vh - 2 * var(--space-4));
53
+ overflow-y: auto;
54
+ padding: var(--space-3);
55
+ border: 1px solid var(--border);
56
+ border-radius: var(--radius);
57
+ background: var(--surface);
58
+ }
59
+
60
+ @media (max-width: 800px) {
61
+ .ws-sidebar {
62
+ position: static;
63
+ max-height: 22rem;
64
+ }
65
+ }
66
+
67
+ .ws-group + .ws-group {
68
+ margin-top: var(--space-3);
69
+ }
70
+
71
+ .ws-group-title {
72
+ margin: 0 0 var(--space-1);
73
+ font-size: var(--text-xs);
74
+ font-weight: 600;
75
+ letter-spacing: 0.04em;
76
+ text-transform: uppercase;
77
+ color: var(--text-muted);
78
+ }
79
+
80
+ .ws-group ul {
81
+ margin: 0;
82
+ padding: 0;
83
+ list-style: none;
84
+ }
85
+
86
+ .ws-nav-link {
87
+ display: flex;
88
+ flex-direction: column;
89
+ padding: var(--space-1) var(--space-2);
90
+ border-radius: var(--radius-sm);
91
+ color: var(--text);
92
+ text-decoration: none;
93
+ }
94
+
95
+ .ws-nav-link:hover {
96
+ background: var(--surface-2);
97
+ }
98
+
99
+ .ws-nav-link[data-selected="true"] {
100
+ background: var(--info-bg);
101
+ color: var(--info-text);
102
+ }
103
+
104
+ .ws-nav-label {
105
+ font-family: var(--font-mono);
106
+ font-size: var(--text-sm);
107
+ overflow-wrap: anywhere;
108
+ }
109
+
110
+ .ws-nav-hint {
111
+ font-size: var(--text-xs);
112
+ color: var(--text-muted);
113
+ overflow-wrap: anywhere;
114
+ }
115
+
116
+ .ws-main,
117
+ .ws-pane {
118
+ display: flex;
119
+ flex-direction: column;
120
+ gap: var(--space-4);
121
+ min-width: 0;
122
+ }
123
+
124
+ .ws-links {
125
+ display: flex;
126
+ flex-wrap: wrap;
127
+ gap: var(--space-1) var(--space-3);
128
+ margin: 0;
129
+ padding: 0;
130
+ list-style: none;
131
+ }
132
+
133
+ .ws-permissions {
134
+ display: flex;
135
+ flex-wrap: wrap;
136
+ gap: var(--space-2);
137
+ }
138
+
139
+ .ws-permission {
140
+ display: inline-flex;
141
+ flex-wrap: wrap;
142
+ align-items: center;
143
+ gap: var(--space-1);
144
+ }
145
+
146
+ .ws-note {
147
+ display: block;
148
+ padding: var(--space-2) var(--space-3);
149
+ border-radius: var(--radius-sm);
150
+ background: var(--warning-bg);
151
+ color: var(--warning-text);
152
+ overflow-wrap: anywhere;
153
+ }
154
+
155
+ .ws-requirements {
156
+ margin: 0 0 var(--space-3);
157
+ padding-left: var(--space-5);
158
+ }
159
+
160
+ .ws-table td {
161
+ vertical-align: top;
162
+ overflow-wrap: anywhere;
163
+ }
164
+
165
+ .ws-value {
166
+ white-space: nowrap;
167
+ font-variant-numeric: tabular-nums;
168
+ }
169
+
170
+ .ws-highlight {
171
+ border-color: var(--accent);
172
+ box-shadow: inset 3px 0 0 var(--accent);
173
+ }
174
+
175
+ .ws-schema {
176
+ overflow-x: auto;
177
+ }
178
+
179
+ .ws-example + .ws-example {
180
+ margin-top: var(--space-3);
181
+ }
182
+
183
+ .ws-try {
184
+ display: flex;
185
+ flex-direction: column;
186
+ align-items: flex-start;
187
+ gap: var(--space-2);
188
+ }
189
+
190
+ .ws-try p {
191
+ margin: 0;
192
+ }
193
+
194
+ .ws-try-fields {
195
+ display: flex;
196
+ flex-wrap: wrap;
197
+ gap: var(--space-2) var(--space-3);
198
+ }
199
+
200
+ .ws-try-fields .field {
201
+ min-width: 12rem;
202
+ margin: 0;
203
+ }
204
+
205
+ .ws-try-channel {
206
+ overflow-wrap: anywhere;
207
+ }
208
+
209
+ .ws-disabled {
210
+ opacity: 0.55;
211
+ cursor: not-allowed;
212
+ }
213
+
214
+ .ws-example-head {
215
+ display: flex;
216
+ align-items: center;
217
+ justify-content: space-between;
218
+ gap: var(--space-2);
219
+ }
220
+
221
+ .ws-example-event {
222
+ margin-block: var(--space-2);
223
+ padding: var(--space-2) var(--space-3);
224
+ border: 1px solid var(--accent);
225
+ border-radius: var(--radius, 6px);
226
+ }
227
+
228
+ .ws-try-reason {
229
+ margin: 0;
230
+ padding-left: var(--space-4);
231
+ }