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.
- package/dist/app/demo.js +0 -1
- package/dist/app/demo.js.map +1 -1
- package/dist/app/standalone.js +0 -1
- package/dist/app/standalone.js.map +1 -1
- package/dist/config/validators/InputSidebarSchema.d.ts +2 -2
- package/dist/config/validators/validate.d.ts +4 -4
- package/dist/lib/authentication/providers/openid.js +1 -1
- package/dist/lib/authentication/providers/openid.js.map +1 -1
- package/dist/lib/oas/graphql/index.d.ts +3 -0
- package/dist/lib/oas/graphql/index.js +12 -13
- package/dist/lib/oas/graphql/index.js.map +1 -1
- package/dist/lib/plugins/openapi/ColorizedParam.d.ts +10 -2
- package/dist/lib/plugins/openapi/ColorizedParam.js +16 -7
- package/dist/lib/plugins/openapi/ColorizedParam.js.map +1 -1
- package/dist/lib/plugins/openapi/ParameterListItem.js +3 -2
- package/dist/lib/plugins/openapi/ParameterListItem.js.map +1 -1
- package/dist/lib/plugins/openapi/PlaygroundDialogWrapper.js +2 -0
- package/dist/lib/plugins/openapi/PlaygroundDialogWrapper.js.map +1 -1
- package/dist/lib/plugins/openapi/RequestBodySidecarBox.d.ts +1 -1
- package/dist/lib/plugins/openapi/RequestBodySidecarBox.js +2 -5
- package/dist/lib/plugins/openapi/RequestBodySidecarBox.js.map +1 -1
- package/dist/lib/plugins/openapi/ResponsesSidecarBox.js +2 -6
- package/dist/lib/plugins/openapi/ResponsesSidecarBox.js.map +1 -1
- package/dist/lib/plugins/openapi/{ExampleDisplay.d.ts → SidecarExamples.d.ts} +2 -6
- package/dist/lib/plugins/openapi/SidecarExamples.js +62 -0
- package/dist/lib/plugins/openapi/SidecarExamples.js.map +1 -0
- package/dist/lib/plugins/openapi/client/GraphQLClient.d.ts +1 -1
- package/dist/lib/plugins/openapi/client/GraphQLClient.js +22 -93
- package/dist/lib/plugins/openapi/client/GraphQLClient.js.map +1 -1
- package/dist/lib/plugins/openapi/client/createServer.d.ts +2 -1
- package/dist/lib/plugins/openapi/client/createServer.js +5 -2
- package/dist/lib/plugins/openapi/client/createServer.js.map +1 -1
- package/dist/lib/plugins/openapi/client/useCreateQuery.d.ts +1 -1
- package/dist/lib/plugins/openapi/client/useCreateQuery.js +2 -13
- package/dist/lib/plugins/openapi/client/useCreateQuery.js.map +1 -1
- package/dist/lib/plugins/openapi/index.d.ts +2 -1
- package/dist/lib/plugins/openapi/index.js.map +1 -1
- package/dist/lib/plugins/openapi/playground/EnumSelector.d.ts +8 -0
- package/dist/lib/plugins/openapi/playground/EnumSelector.js +21 -0
- package/dist/lib/plugins/openapi/playground/EnumSelector.js.map +1 -0
- package/dist/lib/plugins/openapi/playground/PathParams.js +9 -4
- package/dist/lib/plugins/openapi/playground/PathParams.js.map +1 -1
- package/dist/lib/plugins/openapi/playground/Playground.d.ts +3 -0
- package/dist/lib/plugins/openapi/playground/Playground.js +5 -2
- package/dist/lib/plugins/openapi/playground/Playground.js.map +1 -1
- package/dist/lib/plugins/openapi/playground/QueryParams.js +23 -8
- package/dist/lib/plugins/openapi/playground/QueryParams.js.map +1 -1
- package/dist/lib/plugins/openapi/schema/SchemaComponents.js +2 -1
- package/dist/lib/plugins/openapi/schema/SchemaComponents.js.map +1 -1
- package/dist/lib/plugins/openapi/util/generateSchemaExample.js +19 -11
- package/dist/lib/plugins/openapi/util/generateSchemaExample.js.map +1 -1
- package/dist/lib/ui/Badge.js +1 -1
- package/dist/lib/ui/Badge.js.map +1 -1
- package/dist/lib/ui/Button.d.ts +1 -1
- package/dist/lib/ui/Checkbox.d.ts +8 -2
- package/dist/lib/ui/Checkbox.js +13 -1
- package/dist/lib/ui/Checkbox.js.map +1 -1
- package/dist/lib/util/traverse.d.ts +8 -1
- package/dist/lib/util/traverse.js +7 -3
- package/dist/lib/util/traverse.js.map +1 -1
- package/dist/vite/api/schema-codegen.d.ts +12 -0
- package/dist/vite/api/schema-codegen.js +62 -0
- package/dist/vite/api/schema-codegen.js.map +1 -0
- package/dist/vite/api/schema-codegen.test.d.ts +1 -0
- package/dist/vite/api/schema-codegen.test.js +247 -0
- package/dist/vite/api/schema-codegen.test.js.map +1 -0
- package/dist/vite/config.js +0 -7
- package/dist/vite/config.js.map +1 -1
- package/dist/vite/plugin-api.js +110 -82
- package/dist/vite/plugin-api.js.map +1 -1
- package/dist/vite/plugin-component.js +0 -1
- package/dist/vite/plugin-component.js.map +1 -1
- package/lib/Command-9x_kZHr4.js +611 -0
- package/lib/Command-9x_kZHr4.js.map +1 -0
- package/lib/{OperationList-BLdHAQ39.js → OperationList-B8bHMKme.js} +1440 -1434
- package/lib/OperationList-B8bHMKme.js.map +1 -0
- package/lib/{createServer-Bf5_6o6G.js → createServer-BznDkeSA.js} +4227 -5154
- package/lib/createServer-BznDkeSA.js.map +1 -0
- package/lib/index-TaRXY2w1.js +43 -0
- package/lib/index-TaRXY2w1.js.map +1 -0
- package/lib/index-sD8L1_Dl.js +1292 -0
- package/lib/index-sD8L1_Dl.js.map +1 -0
- package/lib/post-processors/traverse.js +11 -8
- package/lib/post-processors/traverse.js.map +1 -1
- package/lib/ui/Badge.js +1 -1
- package/lib/ui/Badge.js.map +1 -1
- package/lib/ui/Checkbox.js +25 -14
- package/lib/ui/Checkbox.js.map +1 -1
- package/lib/ui/Command.js +14 -550
- package/lib/ui/Command.js.map +1 -1
- package/lib/zudoku.auth-openid.js +36 -36
- package/lib/zudoku.auth-openid.js.map +1 -1
- package/lib/zudoku.plugin-openapi.js +1 -1
- package/package.json +1 -6
- package/src/app/demo.tsx +0 -1
- package/src/app/standalone.tsx +0 -1
- package/src/lib/authentication/providers/openid.tsx +1 -1
- package/src/lib/oas/graphql/index.ts +19 -15
- package/src/lib/plugins/openapi/ColorizedParam.tsx +29 -12
- package/src/lib/plugins/openapi/ParameterListItem.tsx +9 -7
- package/src/lib/plugins/openapi/PlaygroundDialogWrapper.tsx +2 -0
- package/src/lib/plugins/openapi/RequestBodySidecarBox.tsx +2 -7
- package/src/lib/plugins/openapi/ResponsesSidecarBox.tsx +5 -8
- package/src/lib/plugins/openapi/SidecarExamples.tsx +155 -0
- package/src/lib/plugins/openapi/client/GraphQLClient.tsx +28 -120
- package/src/lib/plugins/openapi/client/createServer.ts +6 -2
- package/src/lib/plugins/openapi/client/useCreateQuery.ts +2 -17
- package/src/lib/plugins/openapi/index.tsx +2 -1
- package/src/lib/plugins/openapi/playground/EnumSelector.tsx +86 -0
- package/src/lib/plugins/openapi/playground/PathParams.tsx +72 -64
- package/src/lib/plugins/openapi/playground/Playground.tsx +26 -13
- package/src/lib/plugins/openapi/playground/QueryParams.tsx +102 -73
- package/src/lib/plugins/openapi/schema/SchemaComponents.tsx +4 -7
- package/src/lib/plugins/openapi/util/generateSchemaExample.ts +26 -11
- package/src/lib/ui/Badge.tsx +1 -1
- package/src/lib/ui/Checkbox.tsx +24 -7
- package/src/lib/util/traverse.ts +15 -5
- package/dist/lib/plugins/openapi/ExampleDisplay.js +0 -78
- package/dist/lib/plugins/openapi/ExampleDisplay.js.map +0 -1
- package/dist/lib/plugins/openapi/client/worker.d.ts +0 -4
- package/dist/lib/plugins/openapi/client/worker.js +0 -29
- package/dist/lib/plugins/openapi/client/worker.js.map +0 -1
- package/dist/lib/plugins/openapi-worker.d.ts +0 -1
- package/dist/lib/plugins/openapi-worker.js +0 -8
- package/dist/lib/plugins/openapi-worker.js.map +0 -1
- package/lib/Dialog-Bxv1yEIg.js +0 -67
- package/lib/Dialog-Bxv1yEIg.js.map +0 -1
- package/lib/OperationList-BLdHAQ39.js.map +0 -1
- package/lib/assets/index-C7jnHK4b.js +0 -4841
- package/lib/assets/index-C7jnHK4b.js.map +0 -1
- package/lib/assets/worker-Cbp2r2BQ.js +0 -18592
- package/lib/assets/worker-Cbp2r2BQ.js.map +0 -1
- package/lib/createServer-Bf5_6o6G.js.map +0 -1
- package/lib/index-BNx95gkf.js +0 -1284
- package/lib/index-BNx95gkf.js.map +0 -1
- package/lib/index-DyBL--Kz.js +0 -826
- package/lib/index-DyBL--Kz.js.map +0 -1
- package/lib/zudoku.openapi-worker.js +0 -15
- package/lib/zudoku.openapi-worker.js.map +0 -1
- package/src/lib/plugins/openapi/ExampleDisplay.tsx +0 -163
- package/src/lib/plugins/openapi/client/worker.ts +0 -44
- 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-
|
|
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.
|
|
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
package/src/app/standalone.tsx
CHANGED
|
@@ -307,7 +307,7 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
|
|
|
307
307
|
}
|
|
308
308
|
|
|
309
309
|
const redirectUrl = new URL(url);
|
|
310
|
-
redirectUrl.pathname = this.
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
47
|
+
const normalized = name.replace(/[{}]/g, "");
|
|
48
|
+
const { text, background } = usePastellizedColor(normalized);
|
|
37
49
|
|
|
38
|
-
const
|
|
39
|
-
const backgroundColor = `hsl(${
|
|
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
|
|
80
|
-
"
|
|
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
|
-
<
|
|
50
|
+
<Badge variant="secondary">
|
|
54
51
|
{paramSchema.type === "array"
|
|
55
52
|
? `${paramSchema.items.type}[]`
|
|
56
53
|
: paramSchema.type}
|
|
57
|
-
</
|
|
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
|
-
<
|
|
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
|
-
<
|
|
18
|
-
|
|
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
|
|
43
|
-
#pendingRequests = new Map<string, (value: any) => void>();
|
|
44
|
-
#port: MessagePort | undefined;
|
|
24
|
+
constructor(private readonly config: OpenApiPluginOptions) {}
|
|
45
25
|
|
|
46
|
-
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
#
|
|
57
|
-
|
|
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
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
151
|
-
|
|
60
|
+
const result = (await response.json()) as GraphQLResponse<TResult>;
|
|
61
|
+
throwIfError(result);
|
|
152
62
|
|
|
153
|
-
|
|
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
|
-
|
|
29
|
+
`[zudoku:debug] ${args.operationName} query took ${performance.now() - start}ms`,
|
|
26
30
|
);
|
|
27
31
|
map.delete(`${startEvent}-${args.operationName}`);
|
|
28
32
|
}
|