zudoku 0.25.1 → 0.25.3

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 (142) hide show
  1. package/dist/app/demo.js +0 -1
  2. package/dist/app/demo.js.map +1 -1
  3. package/dist/app/standalone.js +0 -1
  4. package/dist/app/standalone.js.map +1 -1
  5. package/dist/config/validators/InputSidebarSchema.d.ts +2 -2
  6. package/dist/config/validators/validate.d.ts +4 -4
  7. package/dist/lib/authentication/providers/openid.js +1 -1
  8. package/dist/lib/authentication/providers/openid.js.map +1 -1
  9. package/dist/lib/oas/graphql/index.d.ts +3 -0
  10. package/dist/lib/oas/graphql/index.js +12 -13
  11. package/dist/lib/oas/graphql/index.js.map +1 -1
  12. package/dist/lib/plugins/openapi/ColorizedParam.d.ts +10 -2
  13. package/dist/lib/plugins/openapi/ColorizedParam.js +16 -7
  14. package/dist/lib/plugins/openapi/ColorizedParam.js.map +1 -1
  15. package/dist/lib/plugins/openapi/ParameterListItem.js +3 -2
  16. package/dist/lib/plugins/openapi/ParameterListItem.js.map +1 -1
  17. package/dist/lib/plugins/openapi/PlaygroundDialogWrapper.js +2 -0
  18. package/dist/lib/plugins/openapi/PlaygroundDialogWrapper.js.map +1 -1
  19. package/dist/lib/plugins/openapi/RequestBodySidecarBox.d.ts +1 -1
  20. package/dist/lib/plugins/openapi/RequestBodySidecarBox.js +2 -5
  21. package/dist/lib/plugins/openapi/RequestBodySidecarBox.js.map +1 -1
  22. package/dist/lib/plugins/openapi/ResponsesSidecarBox.js +2 -6
  23. package/dist/lib/plugins/openapi/ResponsesSidecarBox.js.map +1 -1
  24. package/dist/lib/plugins/openapi/{ExampleDisplay.d.ts → SidecarExamples.d.ts} +2 -6
  25. package/dist/lib/plugins/openapi/SidecarExamples.js +62 -0
  26. package/dist/lib/plugins/openapi/SidecarExamples.js.map +1 -0
  27. package/dist/lib/plugins/openapi/client/GraphQLClient.d.ts +1 -1
  28. package/dist/lib/plugins/openapi/client/GraphQLClient.js +22 -93
  29. package/dist/lib/plugins/openapi/client/GraphQLClient.js.map +1 -1
  30. package/dist/lib/plugins/openapi/client/createServer.d.ts +2 -1
  31. package/dist/lib/plugins/openapi/client/createServer.js +5 -2
  32. package/dist/lib/plugins/openapi/client/createServer.js.map +1 -1
  33. package/dist/lib/plugins/openapi/client/useCreateQuery.d.ts +1 -1
  34. package/dist/lib/plugins/openapi/client/useCreateQuery.js +2 -13
  35. package/dist/lib/plugins/openapi/client/useCreateQuery.js.map +1 -1
  36. package/dist/lib/plugins/openapi/index.d.ts +2 -1
  37. package/dist/lib/plugins/openapi/index.js.map +1 -1
  38. package/dist/lib/plugins/openapi/playground/EnumSelector.d.ts +8 -0
  39. package/dist/lib/plugins/openapi/playground/EnumSelector.js +21 -0
  40. package/dist/lib/plugins/openapi/playground/EnumSelector.js.map +1 -0
  41. package/dist/lib/plugins/openapi/playground/PathParams.js +9 -4
  42. package/dist/lib/plugins/openapi/playground/PathParams.js.map +1 -1
  43. package/dist/lib/plugins/openapi/playground/Playground.d.ts +3 -0
  44. package/dist/lib/plugins/openapi/playground/Playground.js +5 -2
  45. package/dist/lib/plugins/openapi/playground/Playground.js.map +1 -1
  46. package/dist/lib/plugins/openapi/playground/QueryParams.js +23 -8
  47. package/dist/lib/plugins/openapi/playground/QueryParams.js.map +1 -1
  48. package/dist/lib/plugins/openapi/schema/SchemaComponents.js +2 -1
  49. package/dist/lib/plugins/openapi/schema/SchemaComponents.js.map +1 -1
  50. package/dist/lib/plugins/openapi/util/generateSchemaExample.js +19 -11
  51. package/dist/lib/plugins/openapi/util/generateSchemaExample.js.map +1 -1
  52. package/dist/lib/ui/Badge.js +1 -1
  53. package/dist/lib/ui/Badge.js.map +1 -1
  54. package/dist/lib/ui/Button.d.ts +1 -1
  55. package/dist/lib/ui/Checkbox.d.ts +8 -2
  56. package/dist/lib/ui/Checkbox.js +13 -1
  57. package/dist/lib/ui/Checkbox.js.map +1 -1
  58. package/dist/lib/util/traverse.d.ts +8 -1
  59. package/dist/lib/util/traverse.js +7 -3
  60. package/dist/lib/util/traverse.js.map +1 -1
  61. package/dist/vite/api/schema-codegen.d.ts +12 -0
  62. package/dist/vite/api/schema-codegen.js +62 -0
  63. package/dist/vite/api/schema-codegen.js.map +1 -0
  64. package/dist/vite/api/schema-codegen.test.d.ts +1 -0
  65. package/dist/vite/api/schema-codegen.test.js +247 -0
  66. package/dist/vite/api/schema-codegen.test.js.map +1 -0
  67. package/dist/vite/config.js +0 -7
  68. package/dist/vite/config.js.map +1 -1
  69. package/dist/vite/plugin-api.js +110 -82
  70. package/dist/vite/plugin-api.js.map +1 -1
  71. package/dist/vite/plugin-component.js +0 -1
  72. package/dist/vite/plugin-component.js.map +1 -1
  73. package/lib/Command-9x_kZHr4.js +611 -0
  74. package/lib/Command-9x_kZHr4.js.map +1 -0
  75. package/lib/{OperationList-BLdHAQ39.js → OperationList-B8bHMKme.js} +1440 -1434
  76. package/lib/OperationList-B8bHMKme.js.map +1 -0
  77. package/lib/{createServer-Bf5_6o6G.js → createServer-BznDkeSA.js} +4227 -5154
  78. package/lib/createServer-BznDkeSA.js.map +1 -0
  79. package/lib/index-TaRXY2w1.js +43 -0
  80. package/lib/index-TaRXY2w1.js.map +1 -0
  81. package/lib/index-sD8L1_Dl.js +1292 -0
  82. package/lib/index-sD8L1_Dl.js.map +1 -0
  83. package/lib/post-processors/traverse.js +11 -8
  84. package/lib/post-processors/traverse.js.map +1 -1
  85. package/lib/ui/Badge.js +1 -1
  86. package/lib/ui/Badge.js.map +1 -1
  87. package/lib/ui/Checkbox.js +25 -14
  88. package/lib/ui/Checkbox.js.map +1 -1
  89. package/lib/ui/Command.js +14 -550
  90. package/lib/ui/Command.js.map +1 -1
  91. package/lib/zudoku.auth-openid.js +36 -36
  92. package/lib/zudoku.auth-openid.js.map +1 -1
  93. package/lib/zudoku.plugin-openapi.js +1 -1
  94. package/package.json +1 -6
  95. package/src/app/demo.tsx +0 -1
  96. package/src/app/standalone.tsx +0 -1
  97. package/src/lib/authentication/providers/openid.tsx +1 -1
  98. package/src/lib/oas/graphql/index.ts +19 -15
  99. package/src/lib/plugins/openapi/ColorizedParam.tsx +29 -12
  100. package/src/lib/plugins/openapi/ParameterListItem.tsx +9 -7
  101. package/src/lib/plugins/openapi/PlaygroundDialogWrapper.tsx +2 -0
  102. package/src/lib/plugins/openapi/RequestBodySidecarBox.tsx +2 -7
  103. package/src/lib/plugins/openapi/ResponsesSidecarBox.tsx +5 -8
  104. package/src/lib/plugins/openapi/SidecarExamples.tsx +155 -0
  105. package/src/lib/plugins/openapi/client/GraphQLClient.tsx +28 -120
  106. package/src/lib/plugins/openapi/client/createServer.ts +6 -2
  107. package/src/lib/plugins/openapi/client/useCreateQuery.ts +2 -17
  108. package/src/lib/plugins/openapi/index.tsx +2 -1
  109. package/src/lib/plugins/openapi/playground/EnumSelector.tsx +86 -0
  110. package/src/lib/plugins/openapi/playground/PathParams.tsx +72 -64
  111. package/src/lib/plugins/openapi/playground/Playground.tsx +26 -13
  112. package/src/lib/plugins/openapi/playground/QueryParams.tsx +102 -73
  113. package/src/lib/plugins/openapi/schema/SchemaComponents.tsx +4 -7
  114. package/src/lib/plugins/openapi/util/generateSchemaExample.ts +26 -11
  115. package/src/lib/ui/Badge.tsx +1 -1
  116. package/src/lib/ui/Checkbox.tsx +24 -7
  117. package/src/lib/util/traverse.ts +15 -5
  118. package/dist/lib/plugins/openapi/ExampleDisplay.js +0 -78
  119. package/dist/lib/plugins/openapi/ExampleDisplay.js.map +0 -1
  120. package/dist/lib/plugins/openapi/client/worker.d.ts +0 -4
  121. package/dist/lib/plugins/openapi/client/worker.js +0 -29
  122. package/dist/lib/plugins/openapi/client/worker.js.map +0 -1
  123. package/dist/lib/plugins/openapi-worker.d.ts +0 -1
  124. package/dist/lib/plugins/openapi-worker.js +0 -8
  125. package/dist/lib/plugins/openapi-worker.js.map +0 -1
  126. package/lib/Dialog-Bxv1yEIg.js +0 -67
  127. package/lib/Dialog-Bxv1yEIg.js.map +0 -1
  128. package/lib/OperationList-BLdHAQ39.js.map +0 -1
  129. package/lib/assets/index-C7jnHK4b.js +0 -4841
  130. package/lib/assets/index-C7jnHK4b.js.map +0 -1
  131. package/lib/assets/worker-Cbp2r2BQ.js +0 -18592
  132. package/lib/assets/worker-Cbp2r2BQ.js.map +0 -1
  133. package/lib/createServer-Bf5_6o6G.js.map +0 -1
  134. package/lib/index-BNx95gkf.js +0 -1284
  135. package/lib/index-BNx95gkf.js.map +0 -1
  136. package/lib/index-DyBL--Kz.js +0 -826
  137. package/lib/index-DyBL--Kz.js.map +0 -1
  138. package/lib/zudoku.openapi-worker.js +0 -15
  139. package/lib/zudoku.openapi-worker.js.map +0 -1
  140. package/src/lib/plugins/openapi/ExampleDisplay.tsx +0 -163
  141. package/src/lib/plugins/openapi/client/worker.ts +0 -44
  142. package/src/lib/plugins/openapi-worker.ts +0 -11
@@ -1,6 +1,6 @@
1
1
  import "./jsx-runtime-Dx-03ztt.js";
2
2
  import "./chunk-D52XG6IA-Dl7HLe6j.js";
3
- import { o as a } from "./index-BNx95gkf.js";
3
+ import { o as a } from "./index-sD8L1_Dl.js";
4
4
  import "./ZudokuContext-hmLMUdf2.js";
5
5
  import "lucide-react";
6
6
  import "./hook-CHq7pFyz.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zudoku",
3
- "version": "0.25.1",
3
+ "version": "0.25.3",
4
4
  "type": "module",
5
5
  "homepage": "https://zudoku.dev",
6
6
  "repository": {
@@ -78,10 +78,6 @@
78
78
  "import": "./lib/zudoku.plugin-search-inkeep.js",
79
79
  "types": "./dist/lib/plugins/search-inkeep/index.d.ts"
80
80
  },
81
- "./openapi-worker": {
82
- "import": "./lib/zudoku.openapi-worker.js",
83
- "types": "./dist/lib/plugins/openapi-worker.d.ts"
84
- },
85
81
  "./components": {
86
82
  "import": "./lib/zudoku.components.js",
87
83
  "types": "./dist/lib/components/index.d.ts"
@@ -258,7 +254,6 @@
258
254
  "generate:icon-types": "tsx ./scripts/generate-icon-types.ts",
259
255
  "build:standalone:vite": "vite build --mode standalone --config vite.standalone.config.ts",
260
256
  "build:standalone:html": "cp ./src/app/standalone.html ./standalone/standalone.html && cp ./src/app/demo.html ./standalone/demo.html && cp ./src/app/demo-cdn.html ./standalone/index.html",
261
- "hack:fix-worker-paths": "node ./scripts/hack-worker.mjs",
262
257
  "clean": "tsc --build --clean",
263
258
  "codegen": "graphql-codegen --config ./src/codegen.ts",
264
259
  "test": "vitest run"
package/src/app/demo.tsx CHANGED
@@ -48,7 +48,6 @@ const config = {
48
48
  type: "url",
49
49
  input: apiUrl,
50
50
  navigationId: "/",
51
- inMemory: true,
52
51
  }),
53
52
  ],
54
53
  } satisfies ZudokuConfig;
@@ -43,7 +43,6 @@ const config = {
43
43
  type: "url",
44
44
  input: apiUrl!,
45
45
  navigationId: "/",
46
- inMemory: true,
47
46
  }),
48
47
  ],
49
48
  } satisfies ZudokuConfig;
@@ -307,7 +307,7 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
307
307
  }
308
308
 
309
309
  const redirectUrl = new URL(url);
310
- redirectUrl.pathname = this.redirectToAfterSignIn;
310
+ redirectUrl.pathname = this.callbackUrlPath;
311
311
  redirectUrl.search = "";
312
312
 
313
313
  const response = await oauth.authorizationCodeGrantRequest(
@@ -6,8 +6,6 @@ import {
6
6
  } from "@sindresorhus/slugify";
7
7
  import { GraphQLJSON, GraphQLJSONObject } from "graphql-type-json";
8
8
  import { createYoga, type YogaServerOptions } from "graphql-yoga";
9
- import { LRUCache } from "lru-cache";
10
- import hashit from "object-hash";
11
9
  import {
12
10
  HttpMethods,
13
11
  validate,
@@ -58,11 +56,10 @@ export const createOperationSlug = (
58
56
  );
59
57
  };
60
58
 
61
- const cache = new LRUCache<string, OpenAPIDocument>({
62
- ttl: 60 * 10 * 1000,
63
- ttlAutopurge: true,
64
- fetchMethod: (_key, _oldValue, { context }) => validate(context as string),
65
- });
59
+ export type SchemaImports = Record<
60
+ string,
61
+ () => Promise<{ schema: OpenAPIDocument }>
62
+ >;
66
63
 
67
64
  const builder = new SchemaBuilder<{
68
65
  Scalars: {
@@ -71,6 +68,7 @@ const builder = new SchemaBuilder<{
71
68
  };
72
69
  Context: {
73
70
  schema: OpenAPIDocument;
71
+ schemaImports?: SchemaImports;
74
72
  };
75
73
  }>({});
76
74
 
@@ -441,11 +439,6 @@ const Schema = builder.objectRef<OpenAPIDocument>("Schema").implement({
441
439
  }),
442
440
  });
443
441
 
444
- const loadOpenAPISchema = async (input: NonNullable<unknown>) => {
445
- const hash = hashit(input);
446
- return await cache.forceFetch(hash, { context: input });
447
- };
448
-
449
442
  const SchemaSource = builder.enumType("SchemaType", {
450
443
  values: ["url", "file", "raw"] as const,
451
444
  });
@@ -459,10 +452,21 @@ builder.queryType({
459
452
  input: t.arg({ type: JSONScalar, required: true }),
460
453
  },
461
454
  resolve: async (_, args, ctx) => {
462
- const schema = await loadOpenAPISchema(args.input!);
463
- // for easier access of the whole schema in children resolvers
464
- ctx.schema = schema;
455
+ let schema: OpenAPIDocument;
465
456
 
457
+ if (args.type === "file" && typeof args.input === "string") {
458
+ const loadSchema = ctx.schemaImports?.[args.input];
459
+
460
+ if (!loadSchema) {
461
+ throw new Error(`No schema loader found for path: ${args.input}`);
462
+ }
463
+ const module = await loadSchema();
464
+ schema = module.schema;
465
+ } else {
466
+ schema = await validate(args.input as string);
467
+ }
468
+
469
+ ctx.schema = schema;
466
470
  return schema;
467
471
  },
468
472
  }),
@@ -7,18 +7,29 @@ export const DATA_ATTR = "data-linked-param";
7
7
 
8
8
  export const usePastellizedColor = (name: string) => {
9
9
  const { resolvedTheme } = useTheme();
10
- return pastellize(
11
- name,
12
- resolvedTheme === "light" ? { saturation: 85, lightness: 50 } : undefined,
13
- );
10
+
11
+ return {
12
+ text: pastellize(
13
+ name,
14
+ resolvedTheme === "light" ? { saturation: 95, lightness: 38 } : {},
15
+ ),
16
+ background: pastellize(
17
+ name,
18
+ resolvedTheme === "light" ? { saturation: 85, lightness: 40 } : {},
19
+ ),
20
+ };
21
+ };
22
+
23
+ export const useParamColor = (name: string) => {
24
+ const normalized = name.replace(/[{}]/g, "");
25
+ return usePastellizedColor(normalized);
14
26
  };
15
27
 
16
28
  export const ColorizedParam = ({
17
29
  name,
18
30
  className,
19
- backgroundOpacity = "100%",
20
- borderOpacity = "100%",
21
31
  slug,
32
+ title,
22
33
  children,
23
34
  onClick,
24
35
  }: {
@@ -28,15 +39,17 @@ export const ColorizedParam = ({
28
39
  borderOpacity?: string;
29
40
  slug?: string;
30
41
  children?: ReactNode;
42
+ title?: string;
31
43
  onClick?: () => void;
32
44
  }) => {
33
45
  const ref = useRef<HTMLSpanElement>(null);
34
- const normalized = name.replace(/[{}]/g, "");
35
46
  const normalizedSlug = slug?.replace(/[{}]/g, "");
36
- const color = usePastellizedColor(normalized);
47
+ const normalized = name.replace(/[{}]/g, "");
48
+ const { text, background } = usePastellizedColor(normalized);
37
49
 
38
- const borderColor = `hsl(${color} / ${borderOpacity})`;
39
- const backgroundColor = `hsl(${color} / ${backgroundOpacity})`;
50
+ const textColor = `hsl(${text} / 100%)`;
51
+ const backgroundColor = `hsl(${background} / 10%)`;
52
+ const borderColor = `hsl(${background} / 50%)`;
40
53
 
41
54
  useEffect(() => {
42
55
  if (!normalizedSlug) return;
@@ -76,15 +89,19 @@ export const ColorizedParam = ({
76
89
  <span
77
90
  {...{ [DATA_ATTR]: normalizedSlug }}
78
91
  className={cn(
79
- "relative after:rounded after:absolute after:inset-0 after:-bottom-0.5 after:border-b-2 after:transition-opacity after:duration-200",
80
- "after:pointer-events-none after:border-[--border-color] after:opacity-30 after:data-[active=true]:opacity-100",
92
+ "relative inline-block rounded transition-all duration-100",
93
+ "rounded-lg",
94
+ "border border-[--border-color] p-0.5 text-[--param-color] bg-[--background-color]",
95
+ "data-[active=true]:border-[--param-color] data-[active=true]:shadow data-[active=true]:-translate-y-px",
81
96
  className,
82
97
  )}
98
+ title={title}
83
99
  suppressHydrationWarning
84
100
  ref={ref}
85
101
  onClick={onClick}
86
102
  style={
87
103
  {
104
+ "--param-color": textColor,
88
105
  "--border-color": borderColor,
89
106
  "--background-color": backgroundColor,
90
107
  } as CSSProperties
@@ -1,3 +1,4 @@
1
+ import { Badge } from "zudoku/ui/Badge.js";
1
2
  import { Markdown } from "../../components/Markdown.js";
2
3
  import { type SchemaObject } from "../../oas/graphql/index.js";
3
4
  import { ColorizedParam } from "./ColorizedParam.js";
@@ -38,24 +39,25 @@ export const ParameterListItem = ({
38
39
  <ColorizedParam
39
40
  name={parameter.name}
40
41
  backgroundOpacity="15%"
42
+ className="px-1"
41
43
  slug={id + "-" + parameter.name.toLocaleLowerCase()}
42
44
  />
43
45
  ) : (
44
46
  parameter.name
45
47
  )}
46
48
  </code>
47
- {parameter.required && (
48
- <span className="py-px px-1.5 font-medium bg-primary/75 text-primary-foreground rounded-lg">
49
- required
50
- </span>
51
- )}
52
49
  {paramSchema.type && (
53
- <span className="text-muted-foreground">
50
+ <Badge variant="secondary">
54
51
  {paramSchema.type === "array"
55
52
  ? `${paramSchema.items.type}[]`
56
53
  : paramSchema.type}
57
- </span>
54
+ </Badge>
58
55
  )}
56
+ {parameter.required && <Badge variant="outline">required</Badge>}
57
+ {/* <span className="py-px px-1.5 font-medium bg-primary/75 text-primary-foreground rounded-lg">
58
+ required
59
+ </span>
60
+ )} */}
59
61
  </div>
60
62
  {parameter.description && (
61
63
  <Markdown
@@ -23,6 +23,8 @@ export const PlaygroundDialogWrapper = ({
23
23
  name: p.name,
24
24
  defaultActive: p.required ?? false,
25
25
  isRequired: p.required ?? false,
26
+ enum: p.schema?.type == "array" ? p.schema?.items?.enum : p.schema?.enum,
27
+ type: p.schema?.type ?? "string",
26
28
  }));
27
29
  const pathParams = operation.parameters
28
30
  ?.filter((p) => p.in === "path")
@@ -1,18 +1,13 @@
1
- import { Content, useSidecarExamples } from "./ExampleDisplay.js";
2
1
  import * as SidecarBox from "./SidecarBox.js";
2
+ import { type Content, SidecarExamples } from "./SidecarExamples.js";
3
3
 
4
4
  export const RequestBodySidecarBox = ({ content }: { content: Content }) => {
5
- const { SidecarBody, SidebarFooter, hasContent } = useSidecarExamples({
6
- content,
7
- });
8
-
9
5
  return (
10
6
  <SidecarBox.Root>
11
7
  <SidecarBox.Head className="text-xs flex justify-between items-center">
12
8
  <span className="font-mono">Request Body Example</span>
13
9
  </SidecarBox.Head>
14
- <SidecarBody />
15
- {hasContent && <SidebarFooter />}
10
+ <SidecarExamples content={content} />
16
11
  </SidecarBox.Root>
17
12
  );
18
13
  };
@@ -1,21 +1,18 @@
1
1
  import * as Tabs from "@radix-ui/react-tabs";
2
2
  import { cn } from "../../util/cn.js";
3
- import { useSidecarExamples } from "./ExampleDisplay.js";
4
3
  import type { OperationListItemResult } from "./OperationList.js";
5
4
  import * as SidecarBox from "./SidecarBox.js";
5
+ import { SidecarExamples } from "./SidecarExamples.js";
6
6
 
7
7
  type Responses = OperationListItemResult["responses"];
8
8
 
9
9
  const ResponseContent = ({ response }: { response: Responses[number] }) => {
10
- const { SidecarBody, SidebarFooter, hasContent } = useSidecarExamples({
11
- content: response.content ?? [],
12
- description: response.description ?? undefined,
13
- });
14
-
15
10
  return (
16
11
  <Tabs.Content value={response.statusCode}>
17
- <SidecarBody />
18
- {hasContent && <SidebarFooter />}
12
+ <SidecarExamples
13
+ content={response.content ?? []}
14
+ description={response.description ?? undefined}
15
+ />
19
16
  </Tabs.Content>
20
17
  );
21
18
  };
@@ -0,0 +1,155 @@
1
+ import { useState } from "react";
2
+ import { SyntaxHighlight } from "../../components/SyntaxHighlight.js";
3
+ import { type SchemaObject } from "../../oas/graphql/index.js";
4
+ import { CollapsibleCode } from "./CollapsibleCode.js";
5
+ import type { OperationListItemResult } from "./OperationList.js";
6
+ import * as SidecarBox from "./SidecarBox.js";
7
+ import { SimpleSelect } from "./SimpleSelect.js";
8
+ import { generateSchemaExample } from "./util/generateSchemaExample.js";
9
+
10
+ export type Content = NonNullable<
11
+ NonNullable<OperationListItemResult["requestBody"]>["content"]
12
+ >;
13
+ export type Example = NonNullable<
14
+ NonNullable<Content[number]["examples"]>
15
+ >[number];
16
+
17
+ const formatForDisplay = (value: unknown): string => {
18
+ if (value == null) return "No example";
19
+ if (typeof value === "string") return value.trim();
20
+ return JSON.stringify(value, null, 2);
21
+ };
22
+
23
+ const getLanguage = (mediaType?: string): string => {
24
+ if (!mediaType) return "plain";
25
+ const languages: Record<string, string> = {
26
+ "application/json": "json",
27
+ "application/xml": "xml",
28
+ "application/x-yaml": "yaml",
29
+ "text/csv": "csv",
30
+ "application/javascript": "javascript",
31
+ "application/graphql": "graphql",
32
+ };
33
+ return languages[mediaType] ?? "plain";
34
+ };
35
+
36
+ export type SidecarExamplesProps = {
37
+ content: Content;
38
+ description?: string;
39
+ };
40
+
41
+ export const SidecarExamples = ({
42
+ content,
43
+ description,
44
+ }: SidecarExamplesProps) => {
45
+ const [selectedContentTypeIndex, setSelectedContentTypeIndex] = useState(0);
46
+ const [selectedExampleIndex, setSelectedExampleIndex] = useState(0);
47
+
48
+ // Get the effective content (handle single item array case)
49
+ const effectiveContent =
50
+ Array.isArray(content) && content.length === 1
51
+ ? content[0]
52
+ : content[selectedContentTypeIndex];
53
+
54
+ // Get example value, with fallback to schema-generated example
55
+ const examples = effectiveContent?.examples ?? [];
56
+ const selectedExample = examples[selectedExampleIndex];
57
+
58
+ let exampleValue = undefined;
59
+ if (selectedExample) {
60
+ // If it's a wrapped example with a value field, use that
61
+ exampleValue =
62
+ "value" in selectedExample ? selectedExample.value : selectedExample;
63
+ } else if (effectiveContent?.schema) {
64
+ // No example provided, generate one from schema
65
+ exampleValue = generateSchemaExample(
66
+ effectiveContent.schema as SchemaObject,
67
+ );
68
+ }
69
+
70
+ const formattedExample = formatForDisplay(exampleValue);
71
+ const language = getLanguage(effectiveContent?.mediaType);
72
+
73
+ const hasContent = examples.length > 0 || content.length > 0;
74
+
75
+ return (
76
+ <>
77
+ <SidecarBox.Body className="p-0">
78
+ {selectedExample?.externalValue ? (
79
+ <div className="p-2">
80
+ <a
81
+ href={selectedExample.externalValue}
82
+ target="_blank"
83
+ rel="noopener noreferrer"
84
+ className="text-xs text-primary hover:underline"
85
+ >
86
+ View External Example →
87
+ </a>
88
+ </div>
89
+ ) : (
90
+ <CollapsibleCode>
91
+ <SyntaxHighlight
92
+ language={language}
93
+ noBackground
94
+ copyable
95
+ className="[--scrollbar-color:gray] text-xs max-h-[500px] p-2"
96
+ code={formattedExample}
97
+ />
98
+ </CollapsibleCode>
99
+ )}
100
+ {selectedExample?.description && (
101
+ <div className="border-t text-xs px-3 py-1.5 text-muted-foreground">
102
+ {selectedExample.description}
103
+ </div>
104
+ )}
105
+ </SidecarBox.Body>
106
+ {hasContent && (
107
+ <SidecarBox.Footer className="flex flex-col gap-2 text-xs py-1">
108
+ <div className="flex items-center gap-2 justify-between min-w-0">
109
+ <div className="flex items-center gap-2 min-w-0">
110
+ {content.length > 1 ? (
111
+ <SimpleSelect
112
+ className="max-w-[200px]"
113
+ value={selectedContentTypeIndex.toString()}
114
+ onChange={(e) =>
115
+ setSelectedContentTypeIndex(Number(e.target.value))
116
+ }
117
+ options={content.map((c, index) => ({
118
+ value: index.toString(),
119
+ label: c.mediaType,
120
+ }))}
121
+ />
122
+ ) : (
123
+ <span className="font-mono text-[11px]">
124
+ {content[0]?.mediaType}
125
+ </span>
126
+ )}
127
+ </div>
128
+ {examples.length > 1 && (
129
+ <div className="flex items-center gap-1">
130
+ <SimpleSelect
131
+ className="max-w-[180px]"
132
+ value={selectedExampleIndex.toString()}
133
+ onChange={(e) =>
134
+ setSelectedExampleIndex(Number(e.target.value))
135
+ }
136
+ options={examples.map((example, index) => ({
137
+ value: index.toString(),
138
+ label:
139
+ example.summary ||
140
+ example.name ||
141
+ example.description ||
142
+ `Example ${index + 1}`,
143
+ }))}
144
+ />
145
+ </div>
146
+ )}
147
+ </div>
148
+ {description && (
149
+ <div className="text-muted-foreground text-xs">{description}</div>
150
+ )}
151
+ </SidecarBox.Footer>
152
+ )}
153
+ </>
154
+ );
155
+ };
@@ -1,34 +1,16 @@
1
1
  import type { GraphQLError } from "graphql/error/index.js";
2
- import { ulid } from "ulidx";
3
- import { initializeWorker } from "zudoku/openapi-worker";
4
2
  import { ZudokuError } from "../../../util/invariant.js";
5
3
  import type { TypedDocumentString } from "../graphql/graphql.js";
6
4
  import type { OpenApiPluginOptions } from "../index.js";
7
5
  import type { LocalServer } from "./createServer.js";
8
- import type { WorkerGraphQLMessage } from "./worker.js";
9
6
 
10
7
  let localServerPromise: Promise<LocalServer> | undefined;
11
- let worker: SharedWorker | undefined;
12
8
 
13
9
  type GraphQLResponse<TResult> = {
14
10
  errors?: GraphQLError[];
15
11
  data: TResult;
16
12
  };
17
13
 
18
- const resolveVariables = async (variables?: unknown) => {
19
- if (!variables) return;
20
-
21
- if (
22
- typeof variables === "object" &&
23
- "type" in variables &&
24
- variables.type === "file" &&
25
- "input" in variables &&
26
- typeof variables.input === "function"
27
- ) {
28
- variables.input = await variables.input();
29
- }
30
- };
31
-
32
14
  const throwIfError = (response: GraphQLResponse<unknown>) => {
33
15
  if (!response.errors?.[0]) return;
34
16
 
@@ -39,119 +21,45 @@ const throwIfError = (response: GraphQLResponse<unknown>) => {
39
21
  };
40
22
 
41
23
  export class GraphQLClient {
42
- readonly #mode: "remote" | "in-memory" | "worker";
43
- #pendingRequests = new Map<string, (value: any) => void>();
44
- #port: MessagePort | undefined;
24
+ constructor(private readonly config: OpenApiPluginOptions) {}
45
25
 
46
- constructor(private config: OpenApiPluginOptions) {
47
- if (config.server) {
48
- this.#mode = "remote";
49
- } else if (config.inMemory || typeof SharedWorker === "undefined") {
50
- this.#mode = "in-memory";
51
- } else {
52
- this.#mode = "worker";
26
+ #getLocalServer = async () => {
27
+ if (!localServerPromise) {
28
+ localServerPromise = import("./createServer.js").then((m) =>
29
+ m.createServer(this.config),
30
+ );
53
31
  }
54
- }
32
+ return localServerPromise;
33
+ };
55
34
 
56
- #initializeLocalServer = () =>
57
- import("./createServer.js").then((m) => m.createServer());
35
+ #executeFetch = async (init: RequestInit): Promise<Response> => {
36
+ if (this.config.server) {
37
+ return fetch(this.config.server, init);
38
+ }
39
+
40
+ const localServer = await this.#getLocalServer();
41
+ return localServer.fetch("http://localhost/graphql", init);
42
+ };
58
43
 
59
44
  fetch = async <TResult, TVariables>(
60
45
  query: TypedDocumentString<TResult, TVariables>,
61
46
  ...[variables]: TVariables extends Record<string, never> ? [] : [TVariables]
62
- ) => {
47
+ ): Promise<TResult> => {
63
48
  const operationName = query.match(/query (\w+)/)?.[1];
64
49
 
65
- await resolveVariables(variables);
66
-
67
- const body = JSON.stringify({ query, variables, operationName });
68
-
69
- switch (this.#mode) {
70
- case "remote": {
71
- const response = await fetch(this.config.server!, {
72
- method: "POST",
73
- body,
74
- headers: { "Content-Type": "application/json" },
75
- });
76
-
77
- if (!response.ok) {
78
- throw new Error("Network response was not ok");
79
- }
80
-
81
- const result = (await response.json()) as GraphQLResponse<TResult>;
82
- throwIfError(result);
83
-
84
- return result.data;
85
- }
86
-
87
- case "in-memory": {
88
- if (!localServerPromise) {
89
- localServerPromise = this.#initializeLocalServer();
90
- }
91
-
92
- const localServer = await localServerPromise;
93
- if (!localServer) throw new Error("Local server not initialized");
50
+ const response = await this.#executeFetch({
51
+ method: "POST",
52
+ body: JSON.stringify({ query, variables, operationName }),
53
+ headers: { "Content-Type": "application/json" },
54
+ });
94
55
 
95
- const response = await localServer.fetch(
96
- new Request("http://localhost/graphql", {
97
- method: "POST",
98
- body,
99
- headers: { "Content-Type": "application/json" },
100
- }),
101
- );
102
-
103
- if (!response.ok) {
104
- throw new Error("Network response was not ok");
105
- }
106
-
107
- const result = (await response.json()) as GraphQLResponse<TResult>;
108
- throwIfError(result);
109
-
110
- return result.data;
111
- }
112
-
113
- case "worker": {
114
- if (!worker) {
115
- worker = initializeWorker();
116
- }
117
-
118
- if (!this.#port) {
119
- const channel = new MessageChannel();
120
-
121
- worker.port.postMessage({ port: channel.port2 }, [channel.port2]);
122
-
123
- this.#port = channel.port1;
124
-
125
- this.#port.onmessage = (e: MessageEvent<WorkerGraphQLMessage>) => {
126
- const { id, body } = e.data;
127
- const resolve = this.#pendingRequests.get(id);
128
- if (resolve) {
129
- const result = JSON.parse(body);
130
- resolve(result);
131
- this.#pendingRequests.delete(id);
132
- } else {
133
- // eslint-disable-next-line no-console
134
- console.error(`No pending request found for id: ${id}`);
135
- }
136
- };
137
-
138
- this.#port.start();
139
- }
140
-
141
- const id = ulid();
142
-
143
- const resultPromise = new Promise<GraphQLResponse<TResult>>(
144
- (resolve) => {
145
- this.#pendingRequests.set(id, resolve);
146
- this.#port!.postMessage({ id, body } as WorkerGraphQLMessage);
147
- },
148
- );
56
+ if (!response.ok) {
57
+ throw new Error("Network response was not ok");
58
+ }
149
59
 
150
- const result = await resultPromise;
151
- throwIfError(result);
60
+ const result = (await response.json()) as GraphQLResponse<TResult>;
61
+ throwIfError(result);
152
62
 
153
- return result.data;
154
- }
155
- }
63
+ return result.data;
156
64
  };
157
65
  }
@@ -1,13 +1,17 @@
1
1
  import { useLogger } from "@envelop/core";
2
2
  import { createGraphQLServer } from "../../../oas/graphql/index.js";
3
+ import type { OpenApiPluginOptions } from "../index.js";
3
4
 
4
5
  const map = new Map<string, number>();
5
6
 
6
7
  /**
7
8
  * Creates the GraphQL server
8
9
  */
9
- export const createServer = () =>
10
+ export const createServer = (config: OpenApiPluginOptions) =>
10
11
  createGraphQLServer({
12
+ context: {
13
+ schemaImports: config.schemaImports,
14
+ },
11
15
  plugins: [
12
16
  // eslint-disable-next-line react-hooks/rules-of-hooks
13
17
  useLogger({
@@ -22,7 +26,7 @@ export const createServer = () =>
22
26
  if (start) {
23
27
  // eslint-disable-next-line no-console
24
28
  console.log(
25
- `${args.operationName} query took ${performance.now() - start}ms`,
29
+ `[zudoku:debug] ${args.operationName} query took ${performance.now() - start}ms`,
26
30
  );
27
31
  map.delete(`${startEvent}-${args.operationName}`);
28
32
  }