zudoku 0.1.1-dev.18 → 0.1.1-dev.19

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 (139) hide show
  1. package/dist/lib/plugins/openapi/worker/createSharedWorkerClient.js +2 -3
  2. package/dist/lib/plugins/openapi/worker/createSharedWorkerClient.js.map +1 -1
  3. package/dist/lib/plugins/openapi/worker/shared-worker.d.ts +1 -0
  4. package/dist/lib/plugins/openapi/worker/shared-worker.js +6 -0
  5. package/dist/lib/plugins/openapi/worker/shared-worker.js.map +1 -0
  6. package/dist/vite/config.d.ts +1 -1
  7. package/dist/vite/config.js +13 -13
  8. package/dist/vite/config.js.map +1 -1
  9. package/dist/vite/dev-server.js +1 -1
  10. package/dist/vite/dev-server.js.map +1 -1
  11. package/lib/zudoku.openapi-worker.js +12 -0
  12. package/lib/zudoku.plugins.js +1318 -1323
  13. package/package.json +5 -2
  14. package/src/cli/build/handler.ts +14 -0
  15. package/src/cli/cli.ts +77 -0
  16. package/src/cli/cmds/build.ts +24 -0
  17. package/src/cli/cmds/dev.ts +29 -0
  18. package/src/cli/common/analytics/lib.ts +89 -0
  19. package/src/cli/common/constants.ts +10 -0
  20. package/src/cli/common/logger.ts +5 -0
  21. package/src/cli/common/machine-id/lib.ts +85 -0
  22. package/src/cli/common/outdated.ts +102 -0
  23. package/src/cli/common/output.ts +86 -0
  24. package/src/cli/common/utils/box.license.txt +202 -0
  25. package/src/cli/common/utils/box.ts +116 -0
  26. package/src/cli/common/utils/ports.ts +21 -0
  27. package/src/cli/common/validators/lib.ts +43 -0
  28. package/src/cli/common/xdg/lib.ts +36 -0
  29. package/src/cli/dev/handler.ts +42 -0
  30. package/src/config/config.ts +56 -0
  31. package/src/index.ts +8 -0
  32. package/src/lib/DevPortal.tsx +93 -0
  33. package/src/lib/Heading.tsx +60 -0
  34. package/src/lib/Router.tsx +28 -0
  35. package/src/lib/auth.ts +1 -0
  36. package/src/lib/authentication/authentication.ts +18 -0
  37. package/src/lib/authentication/clerk.ts +45 -0
  38. package/src/lib/authentication/openid.ts +192 -0
  39. package/src/lib/components/AnchorLink.tsx +19 -0
  40. package/src/lib/components/CategoryHeading.tsx +16 -0
  41. package/src/lib/components/Dialog.tsx +119 -0
  42. package/src/lib/components/DynamicIcon.tsx +60 -0
  43. package/src/lib/components/Header.tsx +69 -0
  44. package/src/lib/components/Input.tsx +24 -0
  45. package/src/lib/components/Layout.tsx +56 -0
  46. package/src/lib/components/Markdown.tsx +37 -0
  47. package/src/lib/components/SyntaxHighlight.tsx +94 -0
  48. package/src/lib/components/TopNavigation.tsx +32 -0
  49. package/src/lib/components/context/ComponentsContext.tsx +24 -0
  50. package/src/lib/components/context/DevPortalProvider.ts +54 -0
  51. package/src/lib/components/context/PluginSystem.ts +0 -0
  52. package/src/lib/components/context/ThemeContext.tsx +46 -0
  53. package/src/lib/components/context/ViewportAnchorContext.tsx +139 -0
  54. package/src/lib/components/navigation/SideNavigation.tsx +18 -0
  55. package/src/lib/components/navigation/SideNavigationCategory.tsx +74 -0
  56. package/src/lib/components/navigation/SideNavigationItem.tsx +143 -0
  57. package/src/lib/components/navigation/SideNavigationWrapper.tsx +15 -0
  58. package/src/lib/components/navigation/useNavigationCollapsibleState.ts +27 -0
  59. package/src/lib/components/navigation/util.ts +38 -0
  60. package/src/lib/components.ts +3 -0
  61. package/src/lib/core/DevPortalContext.ts +164 -0
  62. package/src/lib/core/helmet.ts +5 -0
  63. package/src/lib/core/icons.tsx +1 -0
  64. package/src/lib/core/plugins.ts +43 -0
  65. package/src/lib/core/router.tsx +1 -0
  66. package/src/lib/core/types/combine.ts +16 -0
  67. package/src/lib/oas/graphql/index.ts +422 -0
  68. package/src/lib/oas/graphql/server.ts +10 -0
  69. package/src/lib/oas/parser/dereference/index.ts +59 -0
  70. package/src/lib/oas/parser/dereference/resolveRef.ts +32 -0
  71. package/src/lib/oas/parser/index.ts +94 -0
  72. package/src/lib/oas/parser/schemas/v3.0.json +1489 -0
  73. package/src/lib/oas/parser/schemas/v3.1.json +1298 -0
  74. package/src/lib/oas/parser/upgrade/index.ts +108 -0
  75. package/src/lib/plugins/api-key/SettingsApiKeys.tsx +22 -0
  76. package/src/lib/plugins/api-key/index.tsx +123 -0
  77. package/src/lib/plugins/markdown/MdxPage.tsx +128 -0
  78. package/src/lib/plugins/markdown/Toc.tsx +122 -0
  79. package/src/lib/plugins/markdown/generateRoutes.tsx +72 -0
  80. package/src/lib/plugins/markdown/index.tsx +31 -0
  81. package/src/lib/plugins/openapi/ColorizedParam.tsx +82 -0
  82. package/src/lib/plugins/openapi/MakeRequest.tsx +49 -0
  83. package/src/lib/plugins/openapi/MethodBadge.tsx +36 -0
  84. package/src/lib/plugins/openapi/OperationList.tsx +117 -0
  85. package/src/lib/plugins/openapi/OperationListItem.tsx +55 -0
  86. package/src/lib/plugins/openapi/ParameterList.tsx +32 -0
  87. package/src/lib/plugins/openapi/ParameterListItem.tsx +60 -0
  88. package/src/lib/plugins/openapi/RequestBodySidecarBox.tsx +51 -0
  89. package/src/lib/plugins/openapi/ResponsesSidecarBox.tsx +60 -0
  90. package/src/lib/plugins/openapi/Select.tsx +35 -0
  91. package/src/lib/plugins/openapi/Sidecar.tsx +160 -0
  92. package/src/lib/plugins/openapi/SidecarBox.tsx +36 -0
  93. package/src/lib/plugins/openapi/graphql/fragment-masking.ts +111 -0
  94. package/src/lib/plugins/openapi/graphql/gql.ts +70 -0
  95. package/src/lib/plugins/openapi/graphql/graphql.ts +795 -0
  96. package/src/lib/plugins/openapi/graphql/index.ts +2 -0
  97. package/src/lib/plugins/openapi/index.tsx +142 -0
  98. package/src/lib/plugins/openapi/playground/Playground.tsx +309 -0
  99. package/src/lib/plugins/openapi/queries.graphql +6 -0
  100. package/src/lib/plugins/openapi/util/generateSchemaExample.ts +59 -0
  101. package/src/lib/plugins/openapi/util/urql.ts +8 -0
  102. package/src/lib/plugins/openapi/worker/createSharedWorkerClient.ts +60 -0
  103. package/src/lib/plugins/openapi/worker/shared-worker.ts +5 -0
  104. package/src/lib/plugins/openapi/worker/worker.ts +30 -0
  105. package/src/lib/plugins/redirect/index.tsx +20 -0
  106. package/src/lib/plugins.ts +5 -0
  107. package/src/lib/ui/Button.tsx +56 -0
  108. package/src/lib/ui/Callout.tsx +87 -0
  109. package/src/lib/ui/Card.tsx +82 -0
  110. package/src/lib/ui/Note.tsx +58 -0
  111. package/src/lib/ui/Tabs.tsx +52 -0
  112. package/src/lib/util/MdxComponents.tsx +70 -0
  113. package/src/lib/util/cn.ts +6 -0
  114. package/src/lib/util/createVariantComponent.tsx +30 -0
  115. package/src/lib/util/createWaitForNotify.ts +18 -0
  116. package/src/lib/util/groupBy.ts +24 -0
  117. package/src/lib/util/joinPath.tsx +10 -0
  118. package/src/lib/util/pastellize.ts +25 -0
  119. package/src/lib/util/slugify.ts +3 -0
  120. package/src/lib/util/traverseNavigation.ts +55 -0
  121. package/src/lib/util/useScrollToAnchor.ts +38 -0
  122. package/src/lib/util/useScrollToTop.ts +13 -0
  123. package/src/ts.ts +94 -0
  124. package/src/types.d.ts +24 -0
  125. package/src/vite/build.ts +33 -0
  126. package/src/vite/config.test.ts +10 -0
  127. package/src/vite/config.ts +183 -0
  128. package/src/vite/dev-server.ts +64 -0
  129. package/src/vite/html.ts +37 -0
  130. package/src/vite/plugin-api.ts +57 -0
  131. package/src/vite/plugin-auth.ts +32 -0
  132. package/src/vite/plugin-component.ts +26 -0
  133. package/src/vite/plugin-config.ts +31 -0
  134. package/src/vite/plugin-docs.test.ts +32 -0
  135. package/src/vite/plugin-docs.ts +52 -0
  136. package/src/vite/plugin-html.ts +50 -0
  137. package/src/vite/plugin-mdx.ts +74 -0
  138. package/src/vite/plugin-metadata.ts +30 -0
  139. package/src/vite/plugin.ts +23 -0
@@ -0,0 +1,2 @@
1
+ export * from "./fragment-masking.js";
2
+ export * from "./gql.js";
@@ -0,0 +1,142 @@
1
+ import { createContext, useContext } from "react";
2
+ import {
3
+ type DevPortalPlugin,
4
+ type PluginNavigationCategory,
5
+ } from "../../core/plugins.js";
6
+ import { Outlet, matchPath, type RouteObject } from "../../core/router.js";
7
+ import { MethodBadge } from "./MethodBadge.js";
8
+ import { OperationList } from "./OperationList.js";
9
+ import { graphql } from "./graphql/index.js";
10
+ import {
11
+ Provider,
12
+ Client as UrqlClient,
13
+ cacheExchange,
14
+ fetchExchange,
15
+ } from "./util/urql.js";
16
+ import { createSharedWorkerClient } from "./worker/createSharedWorkerClient.js";
17
+
18
+ const OasContext = createContext<{ config: OasPluginConfig } | undefined>(
19
+ undefined,
20
+ );
21
+
22
+ export const OasConfigProvider = OasContext.Provider;
23
+
24
+ export const useOasConfig = () => {
25
+ const ctx = useContext(OasContext);
26
+ if (!ctx) {
27
+ throw new Error("useOasConfig must be used within a OasConfigProvider");
28
+ }
29
+ return ctx.config;
30
+ };
31
+
32
+ type OasSource =
33
+ | { type: "url"; input: string }
34
+ | { type: "yaml"; input: string }
35
+ | { type: "json"; input: object };
36
+
37
+ export type OasPluginConfig = {
38
+ server?: string;
39
+ path?: string;
40
+ } & OasSource;
41
+
42
+ const OasContextProvider = ({
43
+ config,
44
+ client,
45
+ }: {
46
+ config: OasPluginConfig;
47
+ client: typeof UrqlClient;
48
+ }) => {
49
+ return (
50
+ <Provider value={client}>
51
+ <OasConfigProvider value={{ config }}>
52
+ <Outlet />
53
+ </OasConfigProvider>
54
+ </Provider>
55
+ );
56
+ };
57
+
58
+ const GetCategoriesQuery = graphql(`
59
+ query GetCategories($input: JSON!, $type: SchemaType!) {
60
+ schema(input: $input, type: $type) {
61
+ tags {
62
+ __typename
63
+ name
64
+ operations {
65
+ __typename
66
+ slug
67
+ method
68
+ summary
69
+ operationId
70
+ path
71
+ }
72
+ }
73
+ }
74
+ }
75
+ `);
76
+
77
+ export const openApiPlugin = (config: OasPluginConfig): DevPortalPlugin => {
78
+ const basePath = config.path ?? "/reference";
79
+
80
+ const client = config.server
81
+ ? new UrqlClient({
82
+ url: config.server,
83
+ exchanges: [cacheExchange, fetchExchange],
84
+ })
85
+ : createSharedWorkerClient();
86
+
87
+ return {
88
+ getNavigation: async (path: string) => {
89
+ if (!matchPath({ path: basePath, end: false }, path)) {
90
+ return [];
91
+ }
92
+
93
+ const { data } = await client.query(GetCategoriesQuery, {
94
+ input: config.input,
95
+ type: config.type,
96
+ });
97
+
98
+ if (!data) return [];
99
+
100
+ const categories = data.schema.tags
101
+ .filter((tag) => tag.operations.length > 0)
102
+ .map<PluginNavigationCategory>((tag) => ({
103
+ path,
104
+ label: tag.name ?? "",
105
+ collapsible: false,
106
+ children: tag.operations.map((operation) => ({
107
+ path: `#${operation.slug}`,
108
+ title: operation.summary ?? operation.path,
109
+ label: (
110
+ <div className="flex flex-1 min-w-0 justify-between gap-2">
111
+ <span className="truncate">
112
+ {operation.summary ?? operation.path}
113
+ </span>
114
+ <MethodBadge method={operation.method} />
115
+ </div>
116
+ ),
117
+ })),
118
+ }));
119
+
120
+ categories.unshift({
121
+ path,
122
+ label: "Overview",
123
+ collapsible: false,
124
+ children: [{ path: "#description", label: "Description" }],
125
+ });
126
+
127
+ return categories;
128
+ },
129
+ getRoutes: () =>
130
+ [
131
+ {
132
+ element: <OasContextProvider config={config} client={client} />,
133
+ children: [
134
+ {
135
+ path: basePath,
136
+ children: [{ index: true, element: <OperationList /> }],
137
+ },
138
+ ],
139
+ },
140
+ ] satisfies RouteObject[],
141
+ };
142
+ };
@@ -0,0 +1,309 @@
1
+ import {
2
+ Dialog,
3
+ DialogContent,
4
+ DialogDescription,
5
+ DialogHeader,
6
+ DialogTitle,
7
+ DialogTrigger,
8
+ } from "../../../components/Dialog.js";
9
+ import { Card, CardContent } from "../../../ui/Card.js";
10
+ import { Button } from "../../../ui/Button.js";
11
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../../ui/Tabs.js";
12
+ import { Header } from "har-format";
13
+ import {
14
+ Fragment,
15
+ InputHTMLAttributes,
16
+ useEffect,
17
+ useRef,
18
+ useState,
19
+ } from "react";
20
+ import { TrashIcon } from "lucide-react";
21
+ import createVariantComponent from "../../../util/createVariantComponent.js";
22
+ import { SyntaxHighlight } from "../../../components/SyntaxHighlight.js";
23
+ import {
24
+ ColorizedParam,
25
+ DATA_ATTR,
26
+ usePastellizedColor,
27
+ } from "../ColorizedParam.js";
28
+ import { cn } from "../../../util/cn.js";
29
+ import { useMutation } from "@tanstack/react-query";
30
+
31
+ const InlineInput = createVariantComponent(
32
+ "input",
33
+ "px-2 bg-transparent h-8 font-mono text-xs m-2",
34
+ );
35
+
36
+ const ParameterValue = ({
37
+ part,
38
+ className,
39
+ ...props
40
+ }: {
41
+ part: string;
42
+ } & InputHTMLAttributes<HTMLInputElement>) => {
43
+ const color = usePastellizedColor(part);
44
+ return (
45
+ <InlineInput
46
+ {...props}
47
+ className={cn(className, "opacity-80 data-[active=true]:opacity-100")}
48
+ style={{ color: `hsl(${color})` }}
49
+ />
50
+ );
51
+ };
52
+
53
+ const Playground = ({
54
+ url,
55
+ host,
56
+ method,
57
+ defaultHeaders,
58
+ }: {
59
+ host: string;
60
+ url: string;
61
+ method: string;
62
+ defaultHeaders?: Header[];
63
+ }) => {
64
+ const [headers, setHeaders] = useState<Header[]>(
65
+ defaultHeaders ?? [{ name: "", value: "" }],
66
+ );
67
+ const [urlParts, setUrlParts] = useState<Header[]>(
68
+ url
69
+ .split("/")
70
+ .filter((part) => part.startsWith("{") && part.endsWith("}"))
71
+ .map((part) => ({ name: part, value: "" })),
72
+ );
73
+
74
+ useEffect(() => {
75
+ const lastHeader = headers.at(-1);
76
+ if (lastHeader?.value !== "" || lastHeader?.name !== "") {
77
+ setHeaders([...headers, { name: "", value: "" }]);
78
+ }
79
+ }, [headers]);
80
+
81
+ const x = useMutation({
82
+ mutationFn: async () => {
83
+ const fullUrl =
84
+ host +
85
+ url
86
+ .split("/")
87
+ .map((v) => urlParts.find((part) => part.name === v)?.value ?? v)
88
+ .join("/");
89
+ console.log(
90
+ fullUrl,
91
+ Object.fromEntries(
92
+ headers.map((header) => [header.name, header.value]),
93
+ ),
94
+ );
95
+ const response = await fetch(fullUrl, {
96
+ // method,
97
+ headers: Object.fromEntries(
98
+ headers
99
+ .filter((h) => h.name)
100
+ .map((header) => [header.name, header.value]),
101
+ ),
102
+ });
103
+
104
+ return {
105
+ status: response.status,
106
+ body: await response.text(),
107
+ };
108
+ },
109
+ });
110
+
111
+ const path = url.split("/").map((part) => (
112
+ <Fragment key={part}>
113
+ {part.startsWith("{") && part.endsWith("}") ? (
114
+ <ColorizedParam
115
+ name={part.slice(1, -1)}
116
+ backgroundOpacity="0"
117
+ onClick={() => {
118
+ console.log("asd");
119
+ }}
120
+ slug={part.slice(1, -1)}
121
+ >
122
+ {urlParts.find((p) => p.name === part)?.value}
123
+ </ColorizedParam>
124
+ ) : (
125
+ part
126
+ )}
127
+ /
128
+ <wbr />
129
+ </Fragment>
130
+ ));
131
+
132
+ return (
133
+ <Dialog>
134
+ <DialogTrigger>Open</DialogTrigger>
135
+ <DialogContent className="max-w-screen-xl">
136
+ <DialogHeader>
137
+ <DialogTitle>API Playground</DialogTitle>
138
+ <DialogDescription>
139
+ <div className="grid grid-cols-2 gap-2">
140
+ <Card>
141
+ <CardContent className="border-b border-border pt-4">
142
+ <div>URL</div>
143
+ <div className="flex gap-2">
144
+ <div className="border rounded border-border p-1.5 w-full">
145
+ {path}
146
+ </div>
147
+ <Button onClick={() => x.mutateAsync()}>Send</Button>
148
+ </div>
149
+ </CardContent>
150
+ <Tabs defaultValue="headers">
151
+ <CardContent className="border-b border-border py-4">
152
+ <TabsList>
153
+ <TabsTrigger value="headers">
154
+ Headers ({headers.length})
155
+ </TabsTrigger>
156
+ <TabsTrigger value="parameters">Parameters</TabsTrigger>
157
+ <TabsTrigger value="auth">Auth</TabsTrigger>
158
+ </TabsList>
159
+ </CardContent>
160
+ <CardContent>
161
+ <TabsContent value="headers">
162
+ <div className="grid grid-cols-[1fr_1fr_auto]">
163
+ {headers.map((header, i) => (
164
+ <div
165
+ key={i}
166
+ className="grid-cols-subgrid col-span-full grid items-center gap-x-2 has-[:focus]:bg-muted hover:bg-muted rounded overflow-hidden group"
167
+ >
168
+ {/*<input type="checkbox" />*/}
169
+ <InlineInput
170
+ onChange={(e) =>
171
+ setHeaders((headers) => {
172
+ const newHeaders = [...headers];
173
+ newHeaders[i] = {
174
+ ...headers[i],
175
+ name: e.target.value,
176
+ };
177
+ return newHeaders;
178
+ })
179
+ }
180
+ value={header.name}
181
+ placeholder={"Name"}
182
+ className="peer"
183
+ />
184
+ <InlineInput
185
+ onChange={(e) =>
186
+ setHeaders((headers) => {
187
+ const newHeaders = [...headers];
188
+ newHeaders[i] = {
189
+ name: "",
190
+ ...headers.at(i),
191
+ value: e.target.value,
192
+ };
193
+ return newHeaders;
194
+ })
195
+ }
196
+ value={header.value}
197
+ placeholder={"Value"}
198
+ className="peer"
199
+ />
200
+ <button
201
+ className="hover:bg-black/5 p-1 rounded mr-2 text-muted-foreground invisible group-hover:visible peer-focus:visible"
202
+ onClick={() => {
203
+ setHeaders((headers) =>
204
+ [...headers].toSpliced(i, 1),
205
+ );
206
+ }}
207
+ >
208
+ <TrashIcon size={16} />
209
+ </button>
210
+ <div className="col-span-full border-b border-border"></div>
211
+ </div>
212
+ ))}
213
+ </div>
214
+ </TabsContent>
215
+ <TabsContent value="parameters">
216
+ Parameters
217
+ <div className="grid grid-cols-[1fr_1fr_auto]">
218
+ {urlParts.map(({ name, value }, i) => (
219
+ <div
220
+ key={i}
221
+ className="grid-cols-subgrid col-span-full grid items-center gap-x-2 has-[:focus]:bg-muted hover:bg-muted rounded overflow-hidden group"
222
+ >
223
+ {/*<input type="checkbox" />*/}
224
+ <InlineInput
225
+ onChange={(e) =>
226
+ setHeaders((headers) => {
227
+ const newHeaders = [...headers];
228
+ newHeaders[i] = {
229
+ ...headers[i],
230
+ name: e.target.value,
231
+ };
232
+ return newHeaders;
233
+ })
234
+ }
235
+ value={name.slice(1, -1)}
236
+ disabled
237
+ placeholder={"Name"}
238
+ className="peer"
239
+ />
240
+ <ParameterValue
241
+ part={name.slice(1, -1)}
242
+ onChange={(e) =>
243
+ setUrlParts((parts) => {
244
+ const newParts = [...parts];
245
+ newParts[i] = {
246
+ name: "",
247
+ ...parts.at(i),
248
+ value: e.target.value,
249
+ };
250
+ return newParts;
251
+ })
252
+ }
253
+ {...{ [DATA_ATTR]: name.slice(1, -1) }}
254
+ value={value}
255
+ placeholder={"Value"}
256
+ className="peer"
257
+ />
258
+
259
+ <div className="col-span-full border-b border-border"></div>
260
+ </div>
261
+ ))}
262
+ </div>
263
+ </TabsContent>
264
+ <TabsContent value="auth">
265
+ <CardContent>
266
+ {url
267
+ .split("/")
268
+ .map((part, i) =>
269
+ part.startsWith("{") && part.endsWith("}")
270
+ ? urlParts[i]
271
+ : part,
272
+ )
273
+ .join("/")}
274
+ </CardContent>
275
+ Change your password here.
276
+ </TabsContent>
277
+ </CardContent>
278
+ </Tabs>
279
+ </Card>
280
+ <Card>
281
+ <CardContent>
282
+ {method.toUpperCase()}
283
+ {url
284
+ .split("/")
285
+ .map((v) =>
286
+ v.startsWith("{") && v.endsWith("}")
287
+ ? urlParts.find((part) => part.name === v)?.value ?? v
288
+ : v,
289
+ )
290
+ .join("/")}
291
+ <SyntaxHighlight
292
+ language="json"
293
+ noBackground
294
+ copyable={false}
295
+ className="text-xs"
296
+ code={x.data?.body ?? JSON.stringify("")}
297
+ />
298
+ </CardContent>
299
+ {x.data?.status}
300
+ </Card>
301
+ </div>
302
+ </DialogDescription>
303
+ </DialogHeader>
304
+ </DialogContent>
305
+ </Dialog>
306
+ );
307
+ };
308
+
309
+ export { Playground };
@@ -0,0 +1,6 @@
1
+ query getServerUrl($userId: ID!) {
2
+ schema(input: {userId: $userId}) {
3
+ serverUrl
4
+ }
5
+ }
6
+
@@ -0,0 +1,59 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { type SchemaObject } from "../../../oas/graphql/index.js";
3
+
4
+ export const isObject = (value: unknown): boolean =>
5
+ typeof value === "object" && value !== null && !Array.isArray(value);
6
+
7
+ export const generateSchemaExample = (schema: SchemaObject): any => {
8
+ // Directly return the example or default if they exist
9
+ if (schema.example !== undefined) {
10
+ return schema.example;
11
+ } else if (schema.default !== undefined) {
12
+ return schema.default;
13
+ }
14
+
15
+ // Process examples object
16
+ if (schema.examples && isObject(schema.examples)) {
17
+ return Object.values(schema.examples)[0];
18
+ }
19
+
20
+ // Recursively process objects and arrays
21
+ return processComplexTypes(schema);
22
+ };
23
+
24
+ function processComplexTypes(schema: SchemaObject): any {
25
+ if (schema.type === "object" && schema.properties) {
26
+ const obj: { [key: string]: any } = {};
27
+ Object.keys(schema.properties!).forEach((key) => {
28
+ obj[key] = generateSchemaExample(schema.properties![key]);
29
+ });
30
+ return obj;
31
+ } else if (schema.type === "array" && schema.items) {
32
+ return [generateSchemaExample(schema.items)];
33
+ }
34
+ // Fallback for missing or undefined types
35
+ return getDefaultForType(schema.type);
36
+ }
37
+
38
+ function getDefaultForType(type?: string | string[]): any {
39
+ if (Array.isArray(type)) {
40
+ return getDefaultForSingleType(type[0]);
41
+ }
42
+ return getDefaultForSingleType(type);
43
+ }
44
+
45
+ function getDefaultForSingleType(type?: string): any {
46
+ switch (type) {
47
+ case "string":
48
+ return "";
49
+ case "number":
50
+ case "integer":
51
+ return 0;
52
+ case "boolean":
53
+ return false;
54
+ case "null":
55
+ return null;
56
+ default:
57
+ return undefined;
58
+ }
59
+ }
@@ -0,0 +1,8 @@
1
+ import * as urql from "urql";
2
+
3
+ export const useQuery = urql.useQuery;
4
+ export const Provider = urql.Provider;
5
+ export const cacheExchange = urql.cacheExchange;
6
+ export const fetchExchange = urql.fetchExchange;
7
+ export const mapExchange = urql.mapExchange;
8
+ export const Client = urql.Client;
@@ -0,0 +1,60 @@
1
+ import { monotonicFactory } from "ulid";
2
+ import { createWaitForNotify } from "../../../util/createWaitForNotify.js";
3
+ import {
4
+ cacheExchange,
5
+ Client,
6
+ fetchExchange,
7
+ mapExchange,
8
+ } from "../util/urql.js";
9
+
10
+ import { createSharedWorker } from "zudoku/open-api-worker";
11
+
12
+ export type WorkerGraphQLMessage = { id: string; body: string };
13
+
14
+ const ulid = monotonicFactory();
15
+ export const createSharedWorkerClient = () => {
16
+ const worker = createSharedWorker();
17
+
18
+ worker.port.start();
19
+
20
+ const [waitFor, notify] = createWaitForNotify<string>();
21
+
22
+ worker.port.onmessage = (e: MessageEvent<WorkerGraphQLMessage>) => {
23
+ notify(e.data.id, e.data.body);
24
+ };
25
+
26
+ return new Client({
27
+ url: "/__z/graphql",
28
+ // Custom fetch to send the GraphQL request to the worker and convert the response back to a `Response` object
29
+ fetch: async (_req, init) => {
30
+ if (!init?.body) throw new Error("No body");
31
+
32
+ const id = ulid();
33
+ worker.port.postMessage({
34
+ id,
35
+ body: init.body as string,
36
+ } satisfies WorkerGraphQLMessage);
37
+
38
+ const body = await waitFor(id);
39
+
40
+ return new Response(body, {
41
+ headers: {
42
+ "Content-Type": "application/json",
43
+ },
44
+ });
45
+ },
46
+ exchanges: [
47
+ cacheExchange,
48
+ mapExchange({
49
+ onError(error, operation) {
50
+ console.error(error);
51
+ console.groupCollapsed("Operation info");
52
+ console.log("body", operation.query.loc?.source.body.trim());
53
+ console.log("variables", operation.variables);
54
+ console.groupEnd();
55
+ },
56
+ }),
57
+ fetchExchange,
58
+ ],
59
+ });
60
+ };
@@ -0,0 +1,5 @@
1
+ export function createSharedWorker() {
2
+ return new SharedWorker(new URL("./worker.ts", import.meta.url), {
3
+ type: "module",
4
+ });
5
+ }
@@ -0,0 +1,30 @@
1
+ import { createGraphQLServer } from "../../../oas/graphql/index.js";
2
+ import { type WorkerGraphQLMessage } from "./createSharedWorkerClient.js";
3
+
4
+ const localServer = createGraphQLServer();
5
+
6
+ const worker = self as unknown as SharedWorkerGlobalScope;
7
+
8
+ worker.addEventListener(
9
+ "connect",
10
+ function (event: MessageEvent<{ id: string; body: string }>) {
11
+ const port = event.ports[0];
12
+
13
+ port.onmessage = async function (e) {
14
+ const response = await localServer.fetch(
15
+ new Request("/__z/graphql", {
16
+ method: "POST",
17
+ body: e.data.body,
18
+ headers: {
19
+ "Content-Type": "application/json",
20
+ },
21
+ }),
22
+ );
23
+
24
+ port.postMessage({
25
+ id: e.data.id,
26
+ body: await response.text(),
27
+ } satisfies WorkerGraphQLMessage);
28
+ };
29
+ },
30
+ );
@@ -0,0 +1,20 @@
1
+ import { Navigate } from "react-router-dom";
2
+ import type { DevPortalPlugin } from "../../core/plugins.js";
3
+
4
+ type Redirect = {
5
+ from: string;
6
+ to: string;
7
+ replace?: boolean;
8
+ };
9
+
10
+ export const redirectPlugin = (options: {
11
+ redirects: Redirect[];
12
+ }): DevPortalPlugin => {
13
+ return {
14
+ getRoutes: () =>
15
+ options.redirects.map(({ from, to, replace }) => ({
16
+ path: from,
17
+ element: <Navigate to={to} replace={replace} />,
18
+ })),
19
+ };
20
+ };
@@ -0,0 +1,5 @@
1
+ // Plugins
2
+ export { apiKeyPlugin } from "./plugins/api-key/index.js";
3
+ export { markdownPlugin } from "./plugins/markdown/index.js";
4
+ export { openApiPlugin } from "./plugins/openapi/index.js";
5
+ export { redirectPlugin } from "./plugins/redirect/index.js";