zudoku 0.27.0 → 0.28.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.
Files changed (152) hide show
  1. package/dist/app/main.js +1 -2
  2. package/dist/app/main.js.map +1 -1
  3. package/dist/config/validators/InputSidebarSchema.d.ts +2 -2
  4. package/dist/lib/components/PathRenderer.d.ts +11 -0
  5. package/dist/lib/components/PathRenderer.js +25 -0
  6. package/dist/lib/components/PathRenderer.js.map +1 -0
  7. package/dist/lib/components/ThemeSwitch.js +4 -4
  8. package/dist/lib/components/ThemeSwitch.js.map +1 -1
  9. package/dist/lib/components/index.d.ts +1 -0
  10. package/dist/lib/components/index.js +4 -2
  11. package/dist/lib/components/index.js.map +1 -1
  12. package/dist/lib/components/navigation/SidebarCategory.js +17 -15
  13. package/dist/lib/components/navigation/SidebarCategory.js.map +1 -1
  14. package/dist/lib/oas/graphql/circular.js +17 -6
  15. package/dist/lib/oas/graphql/circular.js.map +1 -1
  16. package/dist/lib/oas/graphql/index.d.ts +1 -0
  17. package/dist/lib/oas/graphql/index.js +41 -23
  18. package/dist/lib/oas/graphql/index.js.map +1 -1
  19. package/dist/lib/plugins/openapi/ColorizedParam.js +3 -1
  20. package/dist/lib/plugins/openapi/ColorizedParam.js.map +1 -1
  21. package/dist/lib/plugins/openapi/Endpoint.js +2 -2
  22. package/dist/lib/plugins/openapi/Endpoint.js.map +1 -1
  23. package/dist/lib/plugins/openapi/{Route.d.ts → OpenApiRoute.d.ts} +2 -1
  24. package/dist/lib/plugins/openapi/{Route.js → OpenApiRoute.js} +3 -4
  25. package/dist/lib/plugins/openapi/OpenApiRoute.js.map +1 -0
  26. package/dist/lib/plugins/openapi/OperationList.d.ts +4 -1
  27. package/dist/lib/plugins/openapi/OperationList.js +20 -14
  28. package/dist/lib/plugins/openapi/OperationList.js.map +1 -1
  29. package/dist/lib/plugins/openapi/OperationListItem.js +1 -1
  30. package/dist/lib/plugins/openapi/OperationListItem.js.map +1 -1
  31. package/dist/lib/plugins/openapi/ParameterListItem.js +1 -1
  32. package/dist/lib/plugins/openapi/ParameterListItem.js.map +1 -1
  33. package/dist/lib/plugins/openapi/RequestBodySidecarBox.d.ts +1 -1
  34. package/dist/lib/plugins/openapi/RequestBodySidecarBox.js +2 -0
  35. package/dist/lib/plugins/openapi/RequestBodySidecarBox.js.map +1 -1
  36. package/dist/lib/plugins/openapi/Sidecar.js +6 -11
  37. package/dist/lib/plugins/openapi/Sidecar.js.map +1 -1
  38. package/dist/lib/plugins/openapi/SidecarExamples.js +17 -14
  39. package/dist/lib/plugins/openapi/SidecarExamples.js.map +1 -1
  40. package/dist/lib/plugins/openapi/graphql/gql.d.ts +6 -2
  41. package/dist/lib/plugins/openapi/graphql/gql.js +3 -2
  42. package/dist/lib/plugins/openapi/graphql/gql.js.map +1 -1
  43. package/dist/lib/plugins/openapi/graphql/graphql.d.ts +47 -26
  44. package/dist/lib/plugins/openapi/graphql/graphql.js +20 -16
  45. package/dist/lib/plugins/openapi/graphql/graphql.js.map +1 -1
  46. package/dist/lib/plugins/openapi/index.js +97 -54
  47. package/dist/lib/plugins/openapi/index.js.map +1 -1
  48. package/dist/lib/plugins/openapi/interfaces.d.ts +1 -0
  49. package/dist/lib/plugins/openapi/playground/PathParams.js +1 -1
  50. package/dist/lib/plugins/openapi/playground/PathParams.js.map +1 -1
  51. package/dist/lib/plugins/openapi/playground/Playground.js +7 -15
  52. package/dist/lib/plugins/openapi/playground/Playground.js.map +1 -1
  53. package/dist/lib/plugins/openapi/schema/{SchemaComponents.js → SchemaPropertyItem.js} +10 -8
  54. package/dist/lib/plugins/openapi/schema/SchemaPropertyItem.js.map +1 -0
  55. package/dist/lib/plugins/openapi/schema/SchemaView.js +1 -1
  56. package/dist/lib/plugins/openapi/schema/SchemaView.js.map +1 -1
  57. package/dist/lib/plugins/openapi/schema/utils.d.ts +1 -0
  58. package/dist/lib/plugins/openapi/schema/utils.js +2 -0
  59. package/dist/lib/plugins/openapi/schema/utils.js.map +1 -1
  60. package/dist/lib/util/joinUrl.js +1 -1
  61. package/dist/lib/util/joinUrl.js.map +1 -1
  62. package/dist/lib/util/joinUrl.test.d.ts +1 -0
  63. package/dist/lib/util/joinUrl.test.js +43 -0
  64. package/dist/lib/util/joinUrl.test.js.map +1 -0
  65. package/dist/vite/plugin-api.js +9 -1
  66. package/dist/vite/plugin-api.js.map +1 -1
  67. package/dist/vite/prerender.js +0 -1
  68. package/dist/vite/prerender.js.map +1 -1
  69. package/lib/{AuthenticationPlugin-CO_YCd2x.js → AuthenticationPlugin-Du8cLBSr.js} +2 -2
  70. package/lib/{AuthenticationPlugin-CO_YCd2x.js.map → AuthenticationPlugin-Du8cLBSr.js.map} +1 -1
  71. package/lib/{Markdown-B8o9Qz4q.js → Markdown-Cyrx_JrO.js} +8 -9
  72. package/lib/{Markdown-B8o9Qz4q.js.map → Markdown-Cyrx_JrO.js.map} +1 -1
  73. package/lib/{MdxPage-BxRt3Ly7.js → MdxPage-BuG8Tuwc.js} +5 -5
  74. package/lib/{MdxPage-BxRt3Ly7.js.map → MdxPage-BuG8Tuwc.js.map} +1 -1
  75. package/lib/OpenApiRoute-UrC_t0e5.js +36 -0
  76. package/lib/OpenApiRoute-UrC_t0e5.js.map +1 -0
  77. package/lib/{OperationList-DH-zIgtq.js → OperationList-CDt1xdc4.js} +1312 -1303
  78. package/lib/OperationList-CDt1xdc4.js.map +1 -0
  79. package/lib/{Select-B7UXR0SB.js → Select-CnCZ4WhS.js} +3 -3
  80. package/lib/{Select-B7UXR0SB.js.map → Select-CnCZ4WhS.js.map} +1 -1
  81. package/lib/{SlotletProvider-CtIp8rP3.js → SlotletProvider-mQiPDQIH.js} +2 -2
  82. package/lib/{SlotletProvider-CtIp8rP3.js.map → SlotletProvider-mQiPDQIH.js.map} +1 -1
  83. package/lib/{SyntaxHighlight-C1w1QPdY.js → SyntaxHighlight-B0L4SC_N.js} +11 -5
  84. package/lib/SyntaxHighlight-B0L4SC_N.js.map +1 -0
  85. package/lib/{ZudokuContext-8jts0fF3.js → ZudokuContext-BTUJPpQl.js} +21 -21
  86. package/lib/{ZudokuContext-8jts0fF3.js.map → ZudokuContext-BTUJPpQl.js.map} +1 -1
  87. package/lib/{circular-Dgpd6AN-.js → circular-DxaIIlWD.js} +251 -239
  88. package/lib/{circular-Dgpd6AN-.js.map → circular-DxaIIlWD.js.map} +1 -1
  89. package/lib/{createServer-BV0tHzLK.js → createServer-CjNktZzL.js} +821 -808
  90. package/lib/{createServer-BV0tHzLK.js.map → createServer-CjNktZzL.js.map} +1 -1
  91. package/lib/{hook-BG02esyv.js → hook-FT3SJLe_.js} +2 -2
  92. package/lib/{hook-BG02esyv.js.map → hook-FT3SJLe_.js.map} +1 -1
  93. package/lib/{index-LNp6rxyU.js → index-CjJS0l4l.js} +2 -2
  94. package/lib/{index-LNp6rxyU.js.map → index-CjJS0l4l.js.map} +1 -1
  95. package/lib/{index-DmqsUPcm.js → index-Eb1oiHbM.js} +881 -799
  96. package/lib/index-Eb1oiHbM.js.map +1 -0
  97. package/lib/{joinUrl-BTy9bvoK.js → joinUrl-nLx9pD-Z.js} +2 -2
  98. package/lib/joinUrl-nLx9pD-Z.js.map +1 -0
  99. package/lib/{useScrollToAnchor-Bl6mz9_x.js → useScrollToAnchor-BZsGmBng.js} +86 -90
  100. package/lib/useScrollToAnchor-BZsGmBng.js.map +1 -0
  101. package/lib/zudoku.auth-clerk.js +1 -1
  102. package/lib/zudoku.auth-openid.js +3 -3
  103. package/lib/zudoku.components.js +364 -348
  104. package/lib/zudoku.components.js.map +1 -1
  105. package/lib/zudoku.plugin-api-catalog.js +3 -3
  106. package/lib/zudoku.plugin-api-keys.js +4 -4
  107. package/lib/zudoku.plugin-custom-pages.js +1 -1
  108. package/lib/zudoku.plugin-markdown.js +1 -1
  109. package/lib/zudoku.plugin-openapi.js +6 -5
  110. package/lib/zudoku.plugin-openapi.js.map +1 -1
  111. package/package.json +1 -1
  112. package/src/app/main.tsx +1 -2
  113. package/src/lib/components/PathRenderer.tsx +59 -0
  114. package/src/lib/components/ThemeSwitch.tsx +15 -14
  115. package/src/lib/components/index.ts +7 -5
  116. package/src/lib/components/navigation/SidebarCategory.tsx +44 -41
  117. package/src/lib/oas/graphql/circular.ts +27 -6
  118. package/src/lib/oas/graphql/index.ts +63 -35
  119. package/src/lib/plugins/openapi/ColorizedParam.tsx +3 -3
  120. package/src/lib/plugins/openapi/Endpoint.tsx +2 -2
  121. package/src/lib/plugins/openapi/{Route.tsx → OpenApiRoute.tsx} +3 -3
  122. package/src/lib/plugins/openapi/OperationList.tsx +34 -12
  123. package/src/lib/plugins/openapi/OperationListItem.tsx +6 -1
  124. package/src/lib/plugins/openapi/ParameterListItem.tsx +1 -1
  125. package/src/lib/plugins/openapi/RequestBodySidecarBox.tsx +2 -0
  126. package/src/lib/plugins/openapi/Sidecar.tsx +18 -27
  127. package/src/lib/plugins/openapi/SidecarExamples.tsx +24 -24
  128. package/src/lib/plugins/openapi/graphql/gql.ts +12 -4
  129. package/src/lib/plugins/openapi/graphql/graphql.ts +66 -43
  130. package/src/lib/plugins/openapi/index.tsx +125 -67
  131. package/src/lib/plugins/openapi/interfaces.ts +1 -0
  132. package/src/lib/plugins/openapi/playground/PathParams.tsx +1 -1
  133. package/src/lib/plugins/openapi/playground/Playground.tsx +23 -33
  134. package/src/lib/plugins/openapi/schema/{SchemaComponents.tsx → SchemaPropertyItem.tsx} +10 -6
  135. package/src/lib/plugins/openapi/schema/SchemaView.tsx +4 -1
  136. package/src/lib/plugins/openapi/schema/utils.ts +4 -0
  137. package/src/lib/util/joinUrl.test.ts +62 -0
  138. package/src/lib/util/joinUrl.ts +1 -1
  139. package/dist/lib/plugins/openapi/Route.js.map +0 -1
  140. package/dist/lib/plugins/openapi/schema/SchemaComponents.js.map +0 -1
  141. package/lib/OperationList-DH-zIgtq.js.map +0 -1
  142. package/lib/Route-DJ0ZlVq1.js +0 -35
  143. package/lib/Route-DJ0ZlVq1.js.map +0 -1
  144. package/lib/StaggeredRender-DgsamH_G.js +0 -17
  145. package/lib/StaggeredRender-DgsamH_G.js.map +0 -1
  146. package/lib/SyntaxHighlight-C1w1QPdY.js.map +0 -1
  147. package/lib/index-Bn6Lc9tq.js +0 -9
  148. package/lib/index-Bn6Lc9tq.js.map +0 -1
  149. package/lib/index-DmqsUPcm.js.map +0 -1
  150. package/lib/joinUrl-BTy9bvoK.js.map +0 -1
  151. package/lib/useScrollToAnchor-Bl6mz9_x.js.map +0 -1
  152. /package/dist/lib/plugins/openapi/schema/{SchemaComponents.d.ts → SchemaPropertyItem.d.ts} +0 -0
@@ -1,15 +1,15 @@
1
- import { matchPath } from "react-router";
2
- import { type ZudokuPlugin } from "../../core/plugins.js";
3
- import { graphql } from "./graphql/index.js";
4
-
1
+ import slugify from "@sindresorhus/slugify";
5
2
  import { CirclePlayIcon, LogInIcon } from "lucide-react";
3
+ import { matchPath, redirect, RouteObject } from "react-router";
6
4
  import type { SidebarItem } from "../../../config/validators/SidebarSchema.js";
7
5
  import { useAuth } from "../../authentication/hook.js";
8
6
  import { ColorMap } from "../../components/navigation/SidebarBadge.js";
7
+ import { type ZudokuPlugin } from "../../core/plugins.js";
9
8
  import type { SchemaImports } from "../../oas/graphql/index.js";
10
9
  import { Button } from "../../ui/Button.js";
11
- import { joinPath } from "../../util/joinPath.js";
10
+ import { joinUrl } from "../../util/joinUrl.js";
12
11
  import { GraphQLClient } from "./client/GraphQLClient.js";
12
+ import { graphql } from "./graphql/index.js";
13
13
  import { OasPluginConfig } from "./interfaces.js";
14
14
  import type { PlaygroundContentProps } from "./playground/Playground.js";
15
15
  import { PlaygroundDialog } from "./playground/PlaygroundDialog.js";
@@ -19,17 +19,27 @@ const GetCategoriesQuery = graphql(`
19
19
  schema(input: $input, type: $type) {
20
20
  url
21
21
  tags {
22
- __typename
23
22
  name
24
- operations {
25
- __typename
26
- slug
27
- deprecated
28
- method
29
- summary
30
- operationId
31
- path
32
- }
23
+ }
24
+ }
25
+ }
26
+ `);
27
+
28
+ const GetOperationsQuery = graphql(`
29
+ query GetOperations(
30
+ $input: JSON!
31
+ $type: SchemaType!
32
+ $tag: String
33
+ $untagged: Boolean
34
+ ) {
35
+ schema(input: $input, type: $type) {
36
+ operations(tag: $tag, untagged: $untagged) {
37
+ slug
38
+ deprecated
39
+ method
40
+ summary
41
+ operationId
42
+ path
33
43
  }
34
44
  }
35
45
  }
@@ -49,9 +59,10 @@ const MethodColorMap: Record<string, keyof typeof ColorMap> = {
49
59
 
50
60
  export type OpenApiPluginOptions = OasPluginConfig & InternalOasPluginConfig;
51
61
 
52
- export const openApiPlugin = (config: OpenApiPluginOptions): ZudokuPlugin => {
53
- const basePath = joinPath(config.navigationId ?? "/reference");
62
+ const UNTAGGED_PATH = "~endpoints";
54
63
 
64
+ export const openApiPlugin = (config: OpenApiPluginOptions): ZudokuPlugin => {
65
+ const basePath = joinUrl(config.navigationId ?? "/reference");
55
66
  const versions = config.type === "file" ? Object.keys(config.input) : [];
56
67
 
57
68
  const client = new GraphQLClient(config);
@@ -122,41 +133,60 @@ export const openApiPlugin = (config: OpenApiPluginOptions): ZudokuPlugin => {
122
133
  }
123
134
 
124
135
  try {
125
- const version =
126
- versions.find((v) => path === joinPath(basePath, v)) ??
127
- Object.keys(config.input).at(0);
136
+ const urlVersion = versions.find((v) =>
137
+ path.startsWith(joinUrl(basePath, v)),
138
+ );
139
+ const version = urlVersion ?? Object.keys(config.input).at(0);
128
140
 
129
141
  const data = await client.fetch(GetCategoriesQuery, {
130
142
  type: config.type,
131
143
  input: config.type === "file" ? config.input[version!] : config.input,
132
- version,
133
144
  });
134
145
 
135
- const categories = data.schema.tags
136
- .filter((tag) => tag.operations.length > 0)
137
- .map<SidebarItem>((tag) => ({
138
- type: "category",
139
- label: tag.name || "Other endpoints",
140
- collapsible: true,
141
- collapsed: false,
142
- items: tag.operations.map((operation) => ({
143
- type: "link",
144
- label: operation.summary ?? operation.path,
145
- href: `#${operation.slug}`,
146
- badge: {
147
- label: operation.method,
148
- color: MethodColorMap[operation.method.toLowerCase()]!,
149
- invert: true,
150
- },
151
- })),
152
- }));
146
+ const tag = config.tagPages?.find(
147
+ (tag) => path.split("/").at(-1) === slugify(tag),
148
+ );
153
149
 
154
- categories.unshift({
155
- type: "link",
156
- label: "Overview",
157
- href: "#description",
150
+ const operationsData = await client.fetch(GetOperationsQuery, {
151
+ type: config.type,
152
+ input: config.type === "file" ? config.input[version!] : config.input,
153
+ tag,
154
+ untagged: tag === undefined,
158
155
  });
159
156
 
157
+ const items = operationsData.schema.operations.map((operation) => ({
158
+ type: "link" as const,
159
+ label: operation.summary ?? operation.path,
160
+ href: `#${operation.slug}`,
161
+ badge: {
162
+ label: operation.method,
163
+ color: MethodColorMap[operation.method.toLowerCase()]!,
164
+ invert: true,
165
+ } as const,
166
+ }));
167
+
168
+ const categories = data.schema.tags
169
+ // .filter((tag) => tag.operations.length > 0)
170
+ .map<SidebarItem>((tag) => {
171
+ const categoryLink = joinUrl(
172
+ basePath,
173
+ urlVersion,
174
+ tag.name ? slugify(tag.name) : UNTAGGED_PATH,
175
+ );
176
+ return {
177
+ type: "category",
178
+ label: tag.name || "Other endpoints",
179
+ link: {
180
+ type: "doc" as const,
181
+ id: categoryLink,
182
+ label: tag.name!,
183
+ },
184
+ collapsible: false,
185
+ collapsed: true,
186
+ items: path === categoryLink ? items : [],
187
+ };
188
+ });
189
+
160
190
  return categories;
161
191
  } catch {
162
192
  return [];
@@ -165,31 +195,59 @@ export const openApiPlugin = (config: OpenApiPluginOptions): ZudokuPlugin => {
165
195
  getRoutes: () => {
166
196
  const versionsInPath = [null, ...versions];
167
197
 
168
- return versionsInPath.map((version) => ({
169
- path: basePath + (version ? `/${version}` : ""),
170
- async lazy() {
171
- const { OpenApiRoute } = await import("./Route.js");
172
- return {
173
- element: (
174
- <OpenApiRoute
175
- basePath={basePath}
176
- versions={versions}
177
- client={client}
178
- config={config}
179
- />
180
- ),
181
- };
182
- },
183
- children: [
184
- {
185
- index: true,
186
- async lazy() {
187
- const { OperationList } = await import("./OperationList.js");
188
- return { element: <OperationList /> };
189
- },
190
- },
191
- ],
198
+ const tagPages = (config.tagPages ?? []).map((tag) => ({
199
+ tag,
200
+ path: slugify(tag),
192
201
  }));
202
+
203
+ return versionsInPath.map((version) => {
204
+ const versionPath = joinUrl(basePath, version ? `/${version}` : "");
205
+
206
+ return {
207
+ path: versionPath,
208
+ async lazy() {
209
+ const { OpenApiRoute } = await import("./OpenApiRoute.js");
210
+ return {
211
+ element: (
212
+ <OpenApiRoute
213
+ version={version ?? undefined}
214
+ basePath={basePath}
215
+ versions={versions}
216
+ client={client}
217
+ config={config}
218
+ />
219
+ ),
220
+ };
221
+ },
222
+ children: [
223
+ {
224
+ index: true,
225
+ loader: () =>
226
+ redirect(
227
+ joinUrl(versionPath, tagPages.at(0)?.path ?? UNTAGGED_PATH),
228
+ ),
229
+ },
230
+ {
231
+ path: joinUrl(versionPath, UNTAGGED_PATH),
232
+ async lazy() {
233
+ const { OperationList } = await import("./OperationList.js");
234
+ return { element: <OperationList untagged={true} /> };
235
+ },
236
+ },
237
+ ...tagPages.map<RouteObject>((tag) => {
238
+ return {
239
+ path: joinUrl(versionPath, tag.path),
240
+ async lazy() {
241
+ const { OperationList } = await import("./OperationList.js");
242
+ return {
243
+ element: <OperationList tag={tag.tag} />,
244
+ };
245
+ },
246
+ };
247
+ }),
248
+ ],
249
+ };
250
+ });
193
251
  },
194
252
  };
195
253
  };
@@ -20,6 +20,7 @@ export type OasPluginConfig = {
20
20
  server?: string;
21
21
  navigationId?: string;
22
22
  skipPreload?: boolean;
23
+ tagPages?: Array<string>;
23
24
  } & OasPluginConfigOptions &
24
25
  OasSource;
25
26
 
@@ -22,7 +22,7 @@ export const PathParams = ({
22
22
  <>
23
23
  <Controller
24
24
  control={control}
25
- name={`pathParams.${i}.value`}
25
+ name={`pathParams.${i}.name`}
26
26
  render={() => (
27
27
  <div>
28
28
  <ColorizedParam
@@ -3,6 +3,7 @@ import { InfoIcon } from "lucide-react";
3
3
  import { Fragment, useEffect, useRef, useTransition } from "react";
4
4
  import { FormProvider, useForm } from "react-hook-form";
5
5
  import { Alert, AlertDescription, AlertTitle } from "zudoku/ui/Alert.js";
6
+ import { PathRenderer } from "../../../components/PathRenderer.js";
6
7
 
7
8
  import { Label } from "zudoku/ui/Label.js";
8
9
  import { RadioGroup, RadioGroupItem } from "zudoku/ui/RadioGroup.js";
@@ -222,36 +223,27 @@ export const Playground = ({
222
223
  },
223
224
  });
224
225
 
225
- const path = url.split("/").map((part, i, arr) => {
226
- const isPathParam =
227
- (part.startsWith("{") && part.endsWith("}")) || part.startsWith(":");
228
- const replaced = part.replace(/[:{}]/g, "");
229
- const value = formState.pathParams.find((p) => p.name === replaced)?.value;
226
+ const path = (
227
+ <PathRenderer
228
+ path={url}
229
+ renderParam={({ name, originalValue, index }) => {
230
+ const formValue = formState.pathParams.find(
231
+ (param) => param.name === name,
232
+ )?.value;
230
233
 
231
- const pathParamValue = (
232
- <ColorizedParam
233
- backgroundOpacity="25%"
234
- name={part}
235
- slug={part}
236
- title={
237
- !value
238
- ? `Missing value for path parameter \`${replaced}\``
239
- : undefined
240
- }
241
- >
242
- {value ? encodeURIComponent(value) : part}
243
- </ColorizedParam>
244
- );
245
-
246
- return (
247
- // eslint-disable-next-line react/no-array-index-key
248
- <Fragment key={part + i}>
249
- {isPathParam ? pathParamValue : part}
250
- {i < arr.length - 1 && "/"}
251
- <wbr />
252
- </Fragment>
253
- );
254
- });
234
+ return (
235
+ <ColorizedParam
236
+ name={name}
237
+ backgroundOpacity="0"
238
+ slug={name}
239
+ onClick={() => form.setFocus(`pathParams.${index}.value`)}
240
+ >
241
+ {formValue || originalValue}
242
+ </ColorizedParam>
243
+ );
244
+ }}
245
+ />
246
+ );
255
247
 
256
248
  const urlQueryParams = formState.queryParams
257
249
  .filter((p) => p.active)
@@ -268,9 +260,7 @@ export const Playground = ({
268
260
  {servers && servers.length > 1 ? (
269
261
  <Select
270
262
  onValueChange={(value) => {
271
- startTransition(() => {
272
- setSelectedServer(value);
273
- });
263
+ startTransition(() => setSelectedServer(value));
274
264
  }}
275
265
  value={selectedServer}
276
266
  defaultValue={selectedServer}
@@ -307,7 +297,7 @@ export const Playground = ({
307
297
  <div className="border-r p-2 bg-muted rounded-l-md self-stretch font-semibold font-mono flex items-center">
308
298
  {method.toUpperCase()}
309
299
  </div>
310
- <div className="flex items-center flex-wrap p-2 font-mono text-xs break-all">
300
+ <div className="items-center p-2 font-mono text-xs break-words">
311
301
  {serverSelect}
312
302
  {path}
313
303
  {urlQueryParams.length > 0 ? "?" : ""}
@@ -3,7 +3,6 @@ import { ListPlusIcon, RefreshCcwDotIcon } from "lucide-react";
3
3
  import { useCallback, useState } from "react";
4
4
  import { Badge } from "zudoku/ui/Badge.js";
5
5
  import { Markdown, ProseClasses } from "../../../components/Markdown.js";
6
- import { CIRCULAR_REF } from "../../../oas/graphql/circular.js";
7
6
  import type { SchemaObject } from "../../../oas/parser/index.js";
8
7
  import { Button } from "../../../ui/Button.js";
9
8
  import { cn } from "../../../util/cn.js";
@@ -12,6 +11,7 @@ import { LogicalGroup } from "./LogicalGroup/LogicalGroup.js";
12
11
  import { SchemaView } from "./SchemaView.js";
13
12
  import {
14
13
  hasLogicalGroupings,
14
+ isCircularRef,
15
15
  isComplexType,
16
16
  LogicalSchemaTypeMap,
17
17
  } from "./utils.js";
@@ -41,13 +41,10 @@ export const SchemaLogicalGroup = ({
41
41
  }
42
42
  };
43
43
 
44
- const isCircularRef = (schema: unknown): schema is string =>
45
- schema === CIRCULAR_REF;
46
-
47
44
  const RecursiveIndicator = () => (
48
45
  <div className="flex items-center gap-2 italic text-sm text-muted-foreground font-mono bg-muted px-2 py-0.5 rounded-md">
49
46
  <RefreshCcwDotIcon size={16} />
50
- <span>recursive</span>
47
+ <span>circular</span>
51
48
  </div>
52
49
  );
53
50
 
@@ -74,6 +71,8 @@ export const SchemaPropertyItem = ({
74
71
  <div className="flex flex-col gap-1 justify-between text-sm">
75
72
  <div className="flex gap-2 items-center">
76
73
  <code>{name}</code>
74
+ <Badge variant="muted">object</Badge>
75
+ {group === "optional" && <Badge variant="outline">optional</Badge>}
77
76
  <RecursiveIndicator />
78
77
  </div>
79
78
  </div>
@@ -96,6 +95,9 @@ export const SchemaPropertyItem = ({
96
95
  )}
97
96
  </Badge>
98
97
  {group === "optional" && <Badge variant="outline">optional</Badge>}
98
+ {schema.type === "array" &&
99
+ "items" in schema &&
100
+ isCircularRef(schema.items) && <RecursiveIndicator />}
99
101
  </div>
100
102
 
101
103
  {schema.description && (
@@ -133,7 +135,9 @@ export const SchemaPropertyItem = ({
133
135
  <SchemaView schema={schema} level={level + 1} />
134
136
  ) : (
135
137
  schema.type === "array" &&
136
- typeof schema.items === "object" && (
138
+ "items" in schema &&
139
+ typeof schema.items === "object" &&
140
+ !isCircularRef(schema.items) && (
137
141
  <SchemaView schema={schema.items} level={level + 1} />
138
142
  )
139
143
  )}
@@ -3,7 +3,10 @@ import type { SchemaObject } from "../../../oas/parser/index.js";
3
3
  import { Card, CardContent, CardHeader, CardTitle } from "../../../ui/Card.js";
4
4
  import { cn } from "../../../util/cn.js";
5
5
  import { groupBy } from "../../../util/groupBy.js";
6
- import { SchemaLogicalGroup, SchemaPropertyItem } from "./SchemaComponents.js";
6
+ import {
7
+ SchemaLogicalGroup,
8
+ SchemaPropertyItem,
9
+ } from "./SchemaPropertyItem.js";
7
10
  import { hasLogicalGroupings } from "./utils.js";
8
11
 
9
12
  export const SchemaView = ({
@@ -1,3 +1,4 @@
1
+ import { CIRCULAR_REF } from "../../../oas/graphql/circular.js";
1
2
  import type { SchemaObject } from "../../../oas/parser/index.js";
2
3
 
3
4
  export const isComplexType = (value: SchemaObject) =>
@@ -16,3 +17,6 @@ export const LogicalSchemaTypeMap = {
16
17
  } as const;
17
18
 
18
19
  export type LogicalGroupType = "AND" | "OR" | "ONE";
20
+
21
+ export const isCircularRef = (schema: unknown): schema is string =>
22
+ schema === CIRCULAR_REF;
@@ -0,0 +1,62 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { joinUrl } from "./joinUrl.js";
3
+
4
+ describe("joinUrl", () => {
5
+ test("handles basic URL joining", () => {
6
+ expect(joinUrl("https://example.com", "path")).toBe(
7
+ "https://example.com/path",
8
+ );
9
+ expect(joinUrl("https://example.com/", "path")).toBe(
10
+ "https://example.com/path",
11
+ );
12
+ expect(joinUrl("https://example.com/", "/path")).toBe(
13
+ "https://example.com/path",
14
+ );
15
+ });
16
+
17
+ test("handles multiple path segments", () => {
18
+ expect(joinUrl("https://example.com", "api", "v1", "users")).toBe(
19
+ "https://example.com/api/v1/users",
20
+ );
21
+ });
22
+
23
+ // test("handles query parameters", () => {
24
+ // expect(joinUrl("https://example.com", "path?query=1")).toBe(
25
+ // "https://example.com/path?query=1",
26
+ // );
27
+ // expect(joinUrl("https://example.com?base=1", "path?query=1")).toBe(
28
+ // "https://example.com/path?query=1",
29
+ // );
30
+ // });
31
+
32
+ // test("handles repeated dots and question marks", () => {
33
+ // expect(joinUrl("https://example.com", "path/../../../test")).toBe(
34
+ // "https://example.com/path/../../../test",
35
+ // );
36
+ // expect(joinUrl("https://example.com", "test????a=1")).toBe(
37
+ // "https://example.com/test????a=1",
38
+ // );
39
+ // });
40
+
41
+ test("handles falsy values", () => {
42
+ expect(joinUrl("https://example.com", null, undefined, false, "path")).toBe(
43
+ "https://example.com/path",
44
+ );
45
+ });
46
+
47
+ test("handles numeric values", () => {
48
+ expect(joinUrl("https://example.com", "api", 123, "test")).toBe(
49
+ "https://example.com/api/123/test",
50
+ );
51
+ });
52
+
53
+ test("handles relative paths", () => {
54
+ expect(joinUrl("/api", "v1", "users")).toBe("/api/v1/users");
55
+ expect(joinUrl("api", "v1", "users")).toBe("/api/v1/users");
56
+ });
57
+
58
+ test("handles empty input", () => {
59
+ expect(joinUrl()).toBe("/");
60
+ expect(joinUrl("")).toBe("/");
61
+ });
62
+ });
@@ -1,5 +1,5 @@
1
1
  // Mostly adapted from https://github.com/moxystudio/js-proper-url-join
2
- const defaultUrlRegExp = /^(\w+:\/\/[^/?]+)?(.*?)(\?.+)?$/;
2
+ const defaultUrlRegExp = /^(\w+:\/\/[^/?]+)?([^?]*)(\?.*)?$/;
3
3
 
4
4
  const normalizeParts = (
5
5
  parts: (string | number | null | undefined | false)[],
@@ -1 +0,0 @@
1
- {"version":3,"file":"Route.js","sourceRoot":"","sources":["../../../../src/lib/plugins/openapi/Route.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAElD,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGjD,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,EAC3B,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,MAAM,GAMP,EAAE,EAAE;IACH,MAAM,EAAE,OAAO,EAAE,GAAG,SAAS,EAAa,CAAC;IAE3C,MAAM,KAAK,GACT,MAAM,CAAC,IAAI,KAAK,MAAM;QACpB,CAAC,CAAC;YACE,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,KAAK,EAAE,OAAO;gBACZ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAE;gBACxB,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAE;SACvC;QACH,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;IAEjD,MAAM,cAAc,GAAG,OAAO,IAAI,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAEjD,OAAO,CACL,KAAC,iBAAiB,IAChB,KAAK,EAAE;YACL,MAAM,EAAE;gBACN,GAAG,MAAM;gBACT,OAAO,EAAE,cAAc;gBACvB,QAAQ,EAAE,MAAM,CAAC,WAAW,CAC1B,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAClE;gBACD,GAAG,KAAK;aACT;SACF,YAED,KAAC,eAAe,IAAC,MAAM,EAAE,MAAM,YAC7B,KAAC,MAAM,KAAG,GACM,GACA,CACrB,CAAC;AACJ,CAAC,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"SchemaComponents.js","sourceRoot":"","sources":["../../../../../src/lib/plugins/openapi/schema/SchemaComponents.tsx"],"names":[],"mappings":"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"}