zudoku 0.53.5 → 0.54.0
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/config/config.d.ts +1 -0
- package/dist/config/validators/InputNavigationSchema.d.ts +254 -158
- package/dist/config/validators/InputNavigationSchema.js +4 -1
- package/dist/config/validators/InputNavigationSchema.js.map +1 -1
- package/dist/config/validators/ProtectedRoutesSchema.d.ts +12 -0
- package/dist/config/validators/ProtectedRoutesSchema.js +19 -0
- package/dist/config/validators/ProtectedRoutesSchema.js.map +1 -0
- package/dist/config/validators/icon-types.d.ts +1 -1
- package/dist/config/validators/icon-types.js +16 -0
- package/dist/config/validators/icon-types.js.map +1 -1
- package/dist/config/validators/validate.d.ts +32 -19
- package/dist/config/validators/validate.js +6 -2
- package/dist/config/validators/validate.js.map +1 -1
- package/dist/lib/authentication/components/CallbackHandler.js +11 -9
- package/dist/lib/authentication/components/CallbackHandler.js.map +1 -1
- package/dist/lib/authentication/components/OAuthErrorPage.d.ts +3 -0
- package/dist/lib/authentication/components/OAuthErrorPage.js +99 -0
- package/dist/lib/authentication/components/OAuthErrorPage.js.map +1 -0
- package/dist/lib/authentication/errors.d.ts +6 -12
- package/dist/lib/authentication/errors.js +2 -1
- package/dist/lib/authentication/errors.js.map +1 -1
- package/dist/lib/authentication/hook.d.ts +1 -0
- package/dist/lib/authentication/hook.js.map +1 -1
- package/dist/lib/authentication/providers/azureb2c.js +4 -2
- package/dist/lib/authentication/providers/azureb2c.js.map +1 -1
- package/dist/lib/authentication/providers/clerk.js +4 -2
- package/dist/lib/authentication/providers/clerk.js.map +1 -1
- package/dist/lib/authentication/providers/openid.js +3 -1
- package/dist/lib/authentication/providers/openid.js.map +1 -1
- package/dist/lib/components/Heading.js +1 -1
- package/dist/lib/components/Heading.js.map +1 -1
- package/dist/lib/components/MobileTopNavigation.js +5 -3
- package/dist/lib/components/MobileTopNavigation.js.map +1 -1
- package/dist/lib/components/TopNavigation.js +4 -3
- package/dist/lib/components/TopNavigation.js.map +1 -1
- package/dist/lib/components/context/ZudokuContext.js +21 -13
- package/dist/lib/components/context/ZudokuContext.js.map +1 -1
- package/dist/lib/components/navigation/NavigationItem.d.ts +1 -1
- package/dist/lib/components/navigation/NavigationItem.js +8 -1
- package/dist/lib/components/navigation/NavigationItem.js.map +1 -1
- package/dist/lib/components/navigation/utils.d.ts +3 -1
- package/dist/lib/components/navigation/utils.js +6 -3
- package/dist/lib/components/navigation/utils.js.map +1 -1
- package/dist/lib/core/RouteGuard.js +9 -9
- package/dist/lib/core/RouteGuard.js.map +1 -1
- package/dist/lib/core/ZudokuContext.d.ts +2 -1
- package/dist/lib/core/ZudokuContext.js +13 -1
- package/dist/lib/core/ZudokuContext.js.map +1 -1
- package/dist/lib/core/plugins.d.ts +2 -1
- package/dist/lib/core/plugins.js.map +1 -1
- package/dist/lib/plugins/api-keys/CreateApiKey.js +7 -3
- package/dist/lib/plugins/api-keys/CreateApiKey.js.map +1 -1
- package/dist/lib/plugins/api-keys/SettingsApiKeys.js +3 -1
- package/dist/lib/plugins/api-keys/SettingsApiKeys.js.map +1 -1
- package/dist/lib/plugins/api-keys/index.d.ts +1 -0
- package/dist/lib/plugins/api-keys/index.js +3 -7
- package/dist/lib/plugins/api-keys/index.js.map +1 -1
- package/dist/lib/plugins/openapi/graphql/gql.d.ts +1 -1
- package/dist/lib/plugins/openapi/graphql/gql.js +1 -1
- package/dist/lib/plugins/openapi/graphql/gql.js.map +1 -1
- package/dist/lib/plugins/openapi/graphql/graphql.d.ts +1 -0
- package/dist/lib/plugins/openapi/graphql/graphql.js +1 -0
- package/dist/lib/plugins/openapi/graphql/graphql.js.map +1 -1
- package/dist/lib/plugins/openapi/index.js +42 -10
- package/dist/lib/plugins/openapi/index.js.map +1 -1
- package/dist/lib/ui/ActionButton.js +1 -1
- package/dist/lib/ui/ActionButton.js.map +1 -1
- package/dist/lib/ui/Badge.d.ts +1 -1
- package/dist/lib/ui/Button.d.ts +2 -2
- package/dist/lib/ui/Command.d.ts +1 -1
- package/dist/lib/util/invariant.d.ts +6 -5
- package/dist/lib/util/invariant.js +1 -1
- package/dist/lib/util/invariant.js.map +1 -1
- package/dist/vite/dev-server.js +1 -1
- package/dist/vite/dev-server.js.map +1 -1
- package/dist/vite/plugin-docs.js +15 -10
- package/dist/vite/plugin-docs.js.map +1 -1
- package/dist/vite/prerender/worker.js +5 -1
- package/dist/vite/prerender/worker.js.map +1 -1
- package/dist/vite/shadcn-registry.d.ts +8 -8
- package/lib/{Command-C9AC5cf-.js → Command-BYukybsa.js} +2 -2
- package/lib/{Command-C9AC5cf-.js.map → Command-BYukybsa.js.map} +1 -1
- package/lib/{Dialog-DMWw1doX.js → Dialog-u9Uz9sTt.js} +4 -4
- package/lib/{Dialog-DMWw1doX.js.map → Dialog-u9Uz9sTt.js.map} +1 -1
- package/lib/{MdxPage-DVI4iYgW.js → MdxPage-Bsko6_kb.js} +11 -11
- package/lib/{MdxPage-DVI4iYgW.js.map → MdxPage-Bsko6_kb.js.map} +1 -1
- package/lib/OAuthErrorPage-DJzGiIBt.js +150 -0
- package/lib/OAuthErrorPage-DJzGiIBt.js.map +1 -0
- package/lib/{OasProvider-CbwsKPNc.js → OasProvider-DQQRt3oS.js} +3 -3
- package/lib/{OasProvider-CbwsKPNc.js.map → OasProvider-DQQRt3oS.js.map} +1 -1
- package/lib/{OperationList-Bn9ggxw8.js → OperationList-DpmkHf26.js} +45 -43
- package/lib/{OperationList-Bn9ggxw8.js.map → OperationList-DpmkHf26.js.map} +1 -1
- package/lib/{Pagination-bavPec-z.js → Pagination-kqFNgtnI.js} +3 -3
- package/lib/{Pagination-bavPec-z.js.map → Pagination-kqFNgtnI.js.map} +1 -1
- package/lib/{RouteGuard-Vnlz_t51.js → RouteGuard-0wPUKdxJ.js} +166 -165
- package/lib/{RouteGuard-Vnlz_t51.js.map → RouteGuard-0wPUKdxJ.js.map} +1 -1
- package/lib/{SchemaList-DETyCVqu.js → SchemaList-DS-pMd6B.js} +8 -8
- package/lib/{SchemaList-DETyCVqu.js.map → SchemaList-DS-pMd6B.js.map} +1 -1
- package/lib/{SchemaView-Dvxo2RNe.js → SchemaView-BnN6WHjw.js} +4 -4
- package/lib/{SchemaView-Dvxo2RNe.js.map → SchemaView-BnN6WHjw.js.map} +1 -1
- package/lib/Select-BmTTKNPp.js +273 -0
- package/lib/Select-BmTTKNPp.js.map +1 -0
- package/lib/{SignUp-ClYhZq9H.js → SignUp-BwOSCD-6.js} +9 -9
- package/lib/{SignUp-ClYhZq9H.js.map → SignUp-BwOSCD-6.js.map} +1 -1
- package/lib/{Slot-B31yZlfB.js → Slot-DAyXieeZ.js} +1352 -1349
- package/lib/{Slot-B31yZlfB.js.map → Slot-DAyXieeZ.js.map} +1 -1
- package/lib/{SyntaxHighlight-bm761HDo.js → SyntaxHighlight-BMKR4pl6.js} +3 -3
- package/lib/{SyntaxHighlight-bm761HDo.js.map → SyntaxHighlight-BMKR4pl6.js.map} +1 -1
- package/lib/{Toc-D4oBWE8D.js → Toc-BKDRCQzU.js} +2 -2
- package/lib/{Toc-D4oBWE8D.js.map → Toc-BKDRCQzU.js.map} +1 -1
- package/lib/ZudokuContext-CLl5w57E.js +1278 -0
- package/lib/ZudokuContext-CLl5w57E.js.map +1 -0
- package/lib/{chunk-DQRVZFIR-DHK7_Ilc.js → chunk-QMGIS6GS-CEOk3lro.js} +3 -3
- package/lib/chunk-QMGIS6GS-CEOk3lro.js.map +1 -0
- package/lib/{circular-CRbFI6Zl.js → circular-8GWQDvCW.js} +2 -2
- package/lib/{circular-CRbFI6Zl.js.map → circular-8GWQDvCW.js.map} +1 -1
- package/lib/{createServer-DNyGJJNX.js → createServer-BsezSzvV.js} +5 -5
- package/lib/{createServer-DNyGJJNX.js.map → createServer-BsezSzvV.js.map} +1 -1
- package/lib/{errors-C1GlNcV3.js → errors-Cs7hKmdL.js} +11 -10
- package/lib/errors-Cs7hKmdL.js.map +1 -0
- package/lib/hook-DbUCLQNg.js +247 -0
- package/lib/hook-DbUCLQNg.js.map +1 -0
- package/lib/{index-D09PbNex.js → index-A5Qdwj1B.js} +1521 -1420
- package/lib/index-A5Qdwj1B.js.map +1 -0
- package/lib/{index-C_PXQ8Bx.js → index-Bg7Js3jB.js} +832 -912
- package/lib/index-Bg7Js3jB.js.map +1 -0
- package/lib/{index-CZTEgYDd.js → index-BkW9tJ6j.js} +2 -2
- package/lib/{index-CZTEgYDd.js.map → index-BkW9tJ6j.js.map} +1 -1
- package/lib/index.esm-CdzlRw50.js +1254 -0
- package/lib/index.esm-CdzlRw50.js.map +1 -0
- package/lib/{invariant-DAFpPywt.js → invariant-Bm-FVUQE.js} +2 -6
- package/lib/invariant-Bm-FVUQE.js.map +1 -0
- package/lib/ui/ActionButton.js +9 -9
- package/lib/ui/ActionButton.js.map +1 -1
- package/lib/ui/Command.js +1 -1
- package/lib/ui/Form.js +1 -1
- package/lib/ui/SyntaxHighlight.js +3 -3
- package/lib/{useExposedProps-BIYjecPD.js → useExposedProps-KcgXHKeE.js} +2 -2
- package/lib/{useExposedProps-BIYjecPD.js.map → useExposedProps-KcgXHKeE.js.map} +1 -1
- package/lib/zudoku.auth-auth0.js +1 -1
- package/lib/zudoku.auth-azureb2c.js +25 -17
- package/lib/zudoku.auth-azureb2c.js.map +1 -1
- package/lib/zudoku.auth-clerk.js +24 -21
- package/lib/zudoku.auth-clerk.js.map +1 -1
- package/lib/zudoku.auth-openid.js +213 -205
- package/lib/zudoku.auth-openid.js.map +1 -1
- package/lib/zudoku.auth-supabase.js +2 -2
- package/lib/zudoku.components.js +29 -28
- package/lib/zudoku.components.js.map +1 -1
- package/lib/zudoku.hooks.js +16 -15
- package/lib/zudoku.hooks.js.map +1 -1
- package/lib/zudoku.plugin-api-catalog.js +26 -25
- package/lib/zudoku.plugin-api-catalog.js.map +1 -1
- package/lib/zudoku.plugin-api-keys.js +404 -289
- package/lib/zudoku.plugin-api-keys.js.map +1 -1
- package/lib/zudoku.plugin-custom-pages.js +1 -1
- package/lib/zudoku.plugin-markdown.js +1 -1
- package/lib/zudoku.plugin-openapi.js +7 -6
- package/lib/zudoku.plugin-openapi.js.map +1 -1
- package/lib/zudoku.plugin-redirect.js +1 -1
- package/lib/zudoku.plugin-search-pagefind.js +28 -27
- package/lib/zudoku.plugin-search-pagefind.js.map +1 -1
- package/lib/zudoku.plugins.js.map +1 -1
- package/package.json +8 -8
- package/src/lib/authentication/components/CallbackHandler.tsx +22 -15
- package/src/lib/authentication/components/OAuthErrorPage.tsx +171 -0
- package/src/lib/authentication/errors.ts +27 -13
- package/src/lib/authentication/hook.ts +2 -0
- package/src/lib/authentication/providers/azureb2c.tsx +8 -3
- package/src/lib/authentication/providers/clerk.tsx +4 -1
- package/src/lib/authentication/providers/openid.tsx +7 -1
- package/src/lib/components/Heading.tsx +1 -1
- package/src/lib/components/MobileTopNavigation.tsx +6 -3
- package/src/lib/components/TopNavigation.tsx +4 -4
- package/src/lib/components/context/ZudokuContext.ts +25 -18
- package/src/lib/components/navigation/NavigationItem.tsx +9 -1
- package/src/lib/components/navigation/utils.ts +9 -3
- package/src/lib/core/RouteGuard.tsx +13 -13
- package/src/lib/core/ZudokuContext.ts +18 -5
- package/src/lib/core/plugins.ts +2 -1
- package/src/lib/plugins/api-keys/CreateApiKey.tsx +12 -1
- package/src/lib/plugins/api-keys/SettingsApiKeys.tsx +24 -4
- package/src/lib/plugins/api-keys/index.tsx +7 -8
- package/src/lib/plugins/openapi/graphql/gql.ts +3 -3
- package/src/lib/plugins/openapi/graphql/graphql.ts +2 -0
- package/src/lib/plugins/openapi/index.tsx +66 -16
- package/src/lib/ui/ActionButton.tsx +3 -1
- package/src/lib/util/invariant.ts +7 -5
- package/lib/Alert-CWApD0CL.js +0 -161
- package/lib/Alert-CWApD0CL.js.map +0 -1
- package/lib/CallbackHandler-Dr5Lva9x.js +0 -38
- package/lib/CallbackHandler-Dr5Lva9x.js.map +0 -1
- package/lib/chunk-DQRVZFIR-DHK7_Ilc.js.map +0 -1
- package/lib/errors-C1GlNcV3.js.map +0 -1
- package/lib/hook-CZjW2buS.js +0 -1510
- package/lib/hook-CZjW2buS.js.map +0 -1
- package/lib/index-C_PXQ8Bx.js.map +0 -1
- package/lib/index-D09PbNex.js.map +0 -1
- package/lib/index.esm-Cp4wkyud.js +0 -1236
- package/lib/index.esm-Cp4wkyud.js.map +0 -1
- package/lib/invariant-DAFpPywt.js.map +0 -1
|
@@ -2,7 +2,6 @@ import { useQuery, useSuspenseQuery } from "@tanstack/react-query";
|
|
|
2
2
|
import { createContext, useContext } from "react";
|
|
3
3
|
import { matchPath, useLocation } from "react-router";
|
|
4
4
|
import { type NavigationItem } from "../../../config/validators/NavigationSchema.js";
|
|
5
|
-
import { useAuth } from "../../authentication/hook.js";
|
|
6
5
|
import type { ZudokuContext } from "../../core/ZudokuContext.js";
|
|
7
6
|
import { joinUrl } from "../../util/joinUrl.js";
|
|
8
7
|
import { CACHE_KEYS } from "../cache.js";
|
|
@@ -45,14 +44,28 @@ const getItemPath = (item: NavigationItem) => {
|
|
|
45
44
|
return undefined;
|
|
46
45
|
}
|
|
47
46
|
};
|
|
47
|
+
|
|
48
|
+
const extractAllPaths = (items: NavigationItem[]) => {
|
|
49
|
+
const paths = new Set<string>();
|
|
50
|
+
|
|
51
|
+
const collectPaths = (items: NavigationItem[]) => {
|
|
52
|
+
for (const item of items) {
|
|
53
|
+
const itemPath = getItemPath(item)?.split("?").at(0)?.split("#").at(0);
|
|
54
|
+
|
|
55
|
+
if (itemPath) paths.add(itemPath);
|
|
56
|
+
if (item.type === "category") {
|
|
57
|
+
collectPaths(item.items);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
collectPaths(items);
|
|
62
|
+
|
|
63
|
+
return [...paths];
|
|
64
|
+
};
|
|
65
|
+
|
|
48
66
|
export const useCurrentNavigation = () => {
|
|
49
|
-
const { getPluginNavigation, navigation
|
|
67
|
+
const { getPluginNavigation, navigation } = useZudoku();
|
|
50
68
|
const location = useLocation();
|
|
51
|
-
const auth = useAuth();
|
|
52
|
-
|
|
53
|
-
const isProtectedRoute = options.protectedRoutes?.some((route) =>
|
|
54
|
-
matchPath(route, location.pathname),
|
|
55
|
-
);
|
|
56
69
|
|
|
57
70
|
const navItem = traverseNavigation(navigation, (item, parentCategories) => {
|
|
58
71
|
if (getItemPath(item) === location.pathname) {
|
|
@@ -67,12 +80,8 @@ export const useCurrentNavigation = () => {
|
|
|
67
80
|
|
|
68
81
|
let topNavItem = navItem;
|
|
69
82
|
if (!navItem && data.length > 0) {
|
|
70
|
-
|
|
71
|
-
const pluginBasePaths = data.flatMap((item) => {
|
|
72
|
-
return getItemPath(item)?.split("?").at(0)?.split("#").at(0) ?? [];
|
|
73
|
-
});
|
|
83
|
+
const pluginBasePaths = extractAllPaths(data);
|
|
74
84
|
|
|
75
|
-
// Find top-level nav item that matches any plugin base path
|
|
76
85
|
topNavItem = navigation
|
|
77
86
|
.flatMap((item) => {
|
|
78
87
|
const itemPath = getItemPath(item);
|
|
@@ -88,13 +97,11 @@ export const useCurrentNavigation = () => {
|
|
|
88
97
|
})?.item;
|
|
89
98
|
}
|
|
90
99
|
|
|
91
|
-
const hasNavigation =
|
|
92
|
-
auth.isAuthEnabled && !auth.isAuthenticated && isProtectedRoute;
|
|
93
|
-
|
|
94
100
|
return {
|
|
95
|
-
navigation:
|
|
96
|
-
? []
|
|
97
|
-
|
|
101
|
+
navigation: [
|
|
102
|
+
...(navItem?.type === "category" ? navItem.items : []),
|
|
103
|
+
...data,
|
|
104
|
+
],
|
|
98
105
|
topNavItem,
|
|
99
106
|
};
|
|
100
107
|
};
|
|
@@ -9,13 +9,15 @@ import {
|
|
|
9
9
|
TooltipTrigger,
|
|
10
10
|
} from "zudoku/ui/Tooltip.js";
|
|
11
11
|
import type { NavigationItem as NavigationItemType } from "../../../config/validators/NavigationSchema.js";
|
|
12
|
+
import { useAuth } from "../../authentication/hook.js";
|
|
12
13
|
import { cn } from "../../util/cn.js";
|
|
13
14
|
import { joinUrl } from "../../util/joinUrl.js";
|
|
14
15
|
import { AnchorLink } from "../AnchorLink.js";
|
|
15
16
|
import { useViewportAnchor } from "../context/ViewportAnchorContext.js";
|
|
17
|
+
import { useZudoku } from "../context/ZudokuContext.js";
|
|
16
18
|
import { NavigationBadge } from "./NavigationBadge.js";
|
|
17
19
|
import { NavigationCategory } from "./NavigationCategory.js";
|
|
18
|
-
import { navigationListItem } from "./utils.js";
|
|
20
|
+
import { isHiddenItem, navigationListItem } from "./utils.js";
|
|
19
21
|
|
|
20
22
|
const TruncatedLabel = ({
|
|
21
23
|
label,
|
|
@@ -74,6 +76,12 @@ export const NavigationItem = ({
|
|
|
74
76
|
}) => {
|
|
75
77
|
const location = useLocation();
|
|
76
78
|
const { activeAnchor } = useViewportAnchor();
|
|
79
|
+
const auth = useAuth();
|
|
80
|
+
const context = useZudoku();
|
|
81
|
+
|
|
82
|
+
if (!isHiddenItem(auth, context)(item)) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
77
85
|
|
|
78
86
|
switch (item.type) {
|
|
79
87
|
case "category":
|
|
@@ -4,6 +4,8 @@ import type {
|
|
|
4
4
|
NavigationCategory,
|
|
5
5
|
NavigationItem,
|
|
6
6
|
} from "../../../config/validators/NavigationSchema.js";
|
|
7
|
+
import type { UseAuthReturn } from "../../authentication/hook.js";
|
|
8
|
+
import type { ZudokuContext } from "../../core/ZudokuContext.js";
|
|
7
9
|
import { joinUrl } from "../../util/joinUrl.js";
|
|
8
10
|
import { useCurrentNavigation } from "../context/ZudokuContext.js";
|
|
9
11
|
|
|
@@ -133,14 +135,18 @@ export const navigationListItem = cva(
|
|
|
133
135
|
);
|
|
134
136
|
|
|
135
137
|
export const isHiddenItem =
|
|
136
|
-
(
|
|
138
|
+
(auth: UseAuthReturn, context: ZudokuContext) =>
|
|
137
139
|
(item: NavigationItem): boolean => {
|
|
140
|
+
if (typeof item.display === "function") {
|
|
141
|
+
return item.display({ context, auth });
|
|
142
|
+
}
|
|
143
|
+
|
|
138
144
|
if (item.display === "hide") return false;
|
|
139
145
|
if (!item.label) return false;
|
|
140
146
|
|
|
141
147
|
return (
|
|
142
|
-
(item.display === "auth" && isAuthenticated) ||
|
|
143
|
-
(item.display === "anon" && !isAuthenticated) ||
|
|
148
|
+
(item.display === "auth" && auth.isAuthenticated) ||
|
|
149
|
+
(item.display === "anon" && !auth.isAuthenticated) ||
|
|
144
150
|
!item.display ||
|
|
145
151
|
item.display === "always"
|
|
146
152
|
);
|
|
@@ -24,14 +24,18 @@ export const RouteGuard = () => {
|
|
|
24
24
|
const location = useLocation();
|
|
25
25
|
const latestPath = useLatest(location.pathname);
|
|
26
26
|
const shouldBypass = use(BypassProtectedRoutesContext);
|
|
27
|
+
const { protectedRoutes } = zudoku.options;
|
|
27
28
|
|
|
28
|
-
const
|
|
29
|
+
const authCheckFn =
|
|
30
|
+
!shouldBypass && protectedRoutes
|
|
31
|
+
? Object.entries(protectedRoutes).find(([path]) =>
|
|
32
|
+
matchPath({ path, end: true }, location.pathname),
|
|
33
|
+
)?.[1]
|
|
34
|
+
: undefined;
|
|
29
35
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
matchPath({ path, end: true }, location.pathname),
|
|
34
|
-
);
|
|
36
|
+
const isProtectedRoute = authCheckFn !== undefined;
|
|
37
|
+
const needsToSignIn =
|
|
38
|
+
isProtectedRoute && !authCheckFn({ auth, context: zudoku });
|
|
35
39
|
|
|
36
40
|
useQuery({
|
|
37
41
|
queryKey: ["login-redirect"],
|
|
@@ -42,14 +46,10 @@ export const RouteGuard = () => {
|
|
|
42
46
|
});
|
|
43
47
|
return true;
|
|
44
48
|
},
|
|
45
|
-
enabled:
|
|
46
|
-
typeof window !== "undefined" &&
|
|
47
|
-
isProtected &&
|
|
48
|
-
!auth.isPending &&
|
|
49
|
-
!auth.isAuthenticated,
|
|
49
|
+
enabled: typeof window !== "undefined" && needsToSignIn && !auth.isPending,
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
if (
|
|
52
|
+
if (needsToSignIn) {
|
|
53
53
|
return (
|
|
54
54
|
<Dialog
|
|
55
55
|
open={true}
|
|
@@ -71,7 +71,7 @@ export const RouteGuard = () => {
|
|
|
71
71
|
);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
if (
|
|
74
|
+
if (isProtectedRoute && !auth.isAuthEnabled) {
|
|
75
75
|
throw new ZudokuError("Authentication is not enabled", {
|
|
76
76
|
title: "Authentication is not enabled",
|
|
77
77
|
developerHint:
|
|
@@ -5,6 +5,10 @@ import type { Location } from "react-router";
|
|
|
5
5
|
import type { BundledTheme, HighlighterCore } from "shiki";
|
|
6
6
|
import type { z } from "zod/v4";
|
|
7
7
|
import type { Navigation } from "../../config/validators/NavigationSchema.js";
|
|
8
|
+
import {
|
|
9
|
+
type ProtectedRoutesInput,
|
|
10
|
+
ProtectedRoutesSchema,
|
|
11
|
+
} from "../../config/validators/ProtectedRoutesSchema.js";
|
|
8
12
|
import type { FooterSchema } from "../../config/validators/validate.js";
|
|
9
13
|
import type { AuthenticationPlugin } from "../authentication/authentication.js";
|
|
10
14
|
import { type AuthState, useAuthState } from "../authentication/state.js";
|
|
@@ -94,7 +98,7 @@ export type ZudokuContextOptions = {
|
|
|
94
98
|
components?: MdxComponentsType;
|
|
95
99
|
};
|
|
96
100
|
overrides?: ComponentsContextType;
|
|
97
|
-
protectedRoutes?:
|
|
101
|
+
protectedRoutes?: ProtectedRoutesInput;
|
|
98
102
|
syntaxHighlighting?: {
|
|
99
103
|
highlighter: HighlighterCore;
|
|
100
104
|
themes?: { light: BundledTheme; dark: BundledTheme };
|
|
@@ -113,12 +117,21 @@ export class ZudokuContext {
|
|
|
113
117
|
private emitter = createNanoEvents<ZudokuEvents>();
|
|
114
118
|
|
|
115
119
|
constructor(options: ZudokuContextOptions, queryClient: QueryClient) {
|
|
116
|
-
const
|
|
117
|
-
options.plugins
|
|
118
|
-
isNavigationPlugin(plugin)
|
|
119
|
-
|
|
120
|
+
const pluginProtectedRoutes = Object.fromEntries(
|
|
121
|
+
(options.plugins ?? []).flatMap((plugin) => {
|
|
122
|
+
if (!isNavigationPlugin(plugin)) return [];
|
|
123
|
+
const routes = plugin.getProtectedRoutes?.();
|
|
124
|
+
if (!routes) return [];
|
|
125
|
+
|
|
126
|
+
return Object.entries(ProtectedRoutesSchema.parse(routes) ?? {});
|
|
127
|
+
}),
|
|
120
128
|
);
|
|
121
129
|
|
|
130
|
+
const protectedRoutes = {
|
|
131
|
+
...pluginProtectedRoutes,
|
|
132
|
+
...ProtectedRoutesSchema.parse(options.protectedRoutes),
|
|
133
|
+
};
|
|
134
|
+
|
|
122
135
|
this.queryClient = queryClient;
|
|
123
136
|
this.options = { ...options, protectedRoutes };
|
|
124
137
|
this.plugins = options.plugins ?? [];
|
package/src/lib/core/plugins.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { LucideIcon } from "lucide-react";
|
|
|
2
2
|
import type { ReactElement } from "react";
|
|
3
3
|
import type { Location, RouteObject } from "react-router";
|
|
4
4
|
import type { Navigation } from "../../config/validators/NavigationSchema.js";
|
|
5
|
+
import type { ProtectedRoutesInput } from "../../config/validators/ProtectedRoutesSchema.js";
|
|
5
6
|
import type { AuthenticationPlugin } from "../authentication/authentication.js";
|
|
6
7
|
import type { MdxComponentsType } from "../util/MdxComponents.js";
|
|
7
8
|
import type {
|
|
@@ -24,7 +25,7 @@ export type { AuthenticationPlugin, RouteObject };
|
|
|
24
25
|
export interface NavigationPlugin {
|
|
25
26
|
getRoutes: () => RouteObject[];
|
|
26
27
|
getNavigation?: (path: string, context: ZudokuContext) => Promise<Navigation>;
|
|
27
|
-
getProtectedRoutes?: () =>
|
|
28
|
+
getProtectedRoutes?: () => ProtectedRoutesInput;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
export const createApiIdentityPlugin = (
|
|
@@ -2,6 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
|
2
2
|
import { useForm } from "react-hook-form";
|
|
3
3
|
import { useNavigate } from "react-router";
|
|
4
4
|
import { ActionButton } from "zudoku/ui/ActionButton.js";
|
|
5
|
+
import { Alert, AlertDescription, AlertTitle } from "zudoku/ui/Alert.js";
|
|
5
6
|
import { DialogClose, DialogFooter } from "zudoku/ui/Dialog.js";
|
|
6
7
|
import {
|
|
7
8
|
Select,
|
|
@@ -33,6 +34,7 @@ export const CreateApiKey = ({
|
|
|
33
34
|
expiresOn: "30",
|
|
34
35
|
},
|
|
35
36
|
});
|
|
37
|
+
|
|
36
38
|
const createKeyMutation = useMutation({
|
|
37
39
|
mutationFn: ({ description, expiresOn }: CreateApiKey) => {
|
|
38
40
|
if (!service.createKey) {
|
|
@@ -43,7 +45,10 @@ export const CreateApiKey = ({
|
|
|
43
45
|
expiresOn !== "never" ? addDaysToDate(Number(expiresOn)) : undefined;
|
|
44
46
|
|
|
45
47
|
return service.createKey(
|
|
46
|
-
{
|
|
48
|
+
{
|
|
49
|
+
description: description || "Secret Key",
|
|
50
|
+
expiresOn: expiresOnDate,
|
|
51
|
+
},
|
|
47
52
|
context,
|
|
48
53
|
);
|
|
49
54
|
},
|
|
@@ -68,6 +73,12 @@ export const CreateApiKey = ({
|
|
|
68
73
|
),
|
|
69
74
|
)}
|
|
70
75
|
>
|
|
76
|
+
{createKeyMutation.error && (
|
|
77
|
+
<Alert variant="destructive" className="mb-4">
|
|
78
|
+
<AlertTitle>Error</AlertTitle>
|
|
79
|
+
<AlertDescription>{createKeyMutation.error.message}</AlertDescription>
|
|
80
|
+
</Alert>
|
|
81
|
+
)}
|
|
71
82
|
<div className="flex gap-2 flex-col text-sm font-medium">
|
|
72
83
|
Name
|
|
73
84
|
<Input {...form.register("description")} />
|
|
@@ -35,6 +35,7 @@ import { Button } from "../../ui/Button.js";
|
|
|
35
35
|
import { Input } from "../../ui/Input.js";
|
|
36
36
|
import { cn } from "../../util/cn.js";
|
|
37
37
|
import { useCopyToClipboard } from "../../util/useCopyToClipboard.js";
|
|
38
|
+
import { CreateApiKey } from "./CreateApiKey.js";
|
|
38
39
|
import { type ApiConsumer, type ApiKey, type ApiKeyService } from "./index.js";
|
|
39
40
|
|
|
40
41
|
export const SettingsApiKeys = ({ service }: { service: ApiKeyService }) => {
|
|
@@ -50,6 +51,8 @@ export const SettingsApiKeys = ({ service }: { service: ApiKeyService }) => {
|
|
|
50
51
|
retry: false,
|
|
51
52
|
});
|
|
52
53
|
|
|
54
|
+
const [isCreateApiKeyOpen, setIsCreateApiKeyOpen] = useState(false);
|
|
55
|
+
|
|
53
56
|
const deleteKeyMutation = useMutation({
|
|
54
57
|
mutationFn: ({
|
|
55
58
|
consumerId,
|
|
@@ -180,11 +183,28 @@ export const SettingsApiKeys = ({ service }: { service: ApiKeyService }) => {
|
|
|
180
183
|
<Slot.Target name="api-keys-list-page" />
|
|
181
184
|
|
|
182
185
|
<div className="flex justify-between pb-3">
|
|
183
|
-
<h1 className="font-medium text-2xl">
|
|
186
|
+
<h1 className="font-medium text-2xl">
|
|
187
|
+
API Keys {typeof service.createKey}
|
|
188
|
+
</h1>
|
|
189
|
+
|
|
184
190
|
{service.createKey && (
|
|
185
|
-
<
|
|
186
|
-
|
|
187
|
-
|
|
191
|
+
<Dialog
|
|
192
|
+
open={isCreateApiKeyOpen}
|
|
193
|
+
onOpenChange={setIsCreateApiKeyOpen}
|
|
194
|
+
>
|
|
195
|
+
<DialogTrigger asChild>
|
|
196
|
+
<Button variant="outline">Create API Key</Button>
|
|
197
|
+
</DialogTrigger>
|
|
198
|
+
<DialogContent>
|
|
199
|
+
<DialogHeader>
|
|
200
|
+
<DialogTitle>Create API Key</DialogTitle>
|
|
201
|
+
</DialogHeader>
|
|
202
|
+
<CreateApiKey
|
|
203
|
+
service={service}
|
|
204
|
+
onOpenChange={setIsCreateApiKeyOpen}
|
|
205
|
+
/>
|
|
206
|
+
</DialogContent>
|
|
207
|
+
</Dialog>
|
|
188
208
|
)}
|
|
189
209
|
</div>
|
|
190
210
|
<p>Create, manage, and monitor your API keys</p>
|
|
@@ -27,7 +27,7 @@ export type ApiKeyService = {
|
|
|
27
27
|
) => Promise<void>;
|
|
28
28
|
getUsage?: (apiKeys: string[], context: ZudokuContext) => Promise<void>;
|
|
29
29
|
createKey?: (
|
|
30
|
-
apiKey: { description: string; expiresOn?: string },
|
|
30
|
+
apiKey: { description: string; expiresOn?: string; expiresValue?: string },
|
|
31
31
|
context: ZudokuContext,
|
|
32
32
|
) => Promise<void>;
|
|
33
33
|
};
|
|
@@ -74,7 +74,10 @@ const throwIfProblemJson = async (response: Response) => {
|
|
|
74
74
|
}
|
|
75
75
|
};
|
|
76
76
|
|
|
77
|
-
const createDefaultHandler = (
|
|
77
|
+
const createDefaultHandler = (
|
|
78
|
+
deploymentName: string,
|
|
79
|
+
options: ApiKeyPluginOptions,
|
|
80
|
+
): ApiKeyService => {
|
|
78
81
|
return {
|
|
79
82
|
deleteKey: async (consumerId, keyId, context) => {
|
|
80
83
|
const request = new Request(
|
|
@@ -159,6 +162,7 @@ const createDefaultHandler = (deploymentName: string): ApiKeyService => {
|
|
|
159
162
|
key: consumer.apiKeys.data.at(0),
|
|
160
163
|
}));
|
|
161
164
|
},
|
|
165
|
+
...options,
|
|
162
166
|
};
|
|
163
167
|
};
|
|
164
168
|
|
|
@@ -170,7 +174,7 @@ export const apiKeyPlugin = (
|
|
|
170
174
|
): ZudokuPlugin & ApiIdentityPlugin & ProfileMenuPlugin => {
|
|
171
175
|
const service: ApiKeyService =
|
|
172
176
|
"deploymentName" in options
|
|
173
|
-
? createDefaultHandler(options.deploymentName)
|
|
177
|
+
? createDefaultHandler(options.deploymentName, options)
|
|
174
178
|
: options;
|
|
175
179
|
|
|
176
180
|
return {
|
|
@@ -203,7 +207,6 @@ export const apiKeyPlugin = (
|
|
|
203
207
|
}
|
|
204
208
|
},
|
|
205
209
|
getRoutes: (): RouteObject[] => {
|
|
206
|
-
// TODO: Make lazy
|
|
207
210
|
return [
|
|
208
211
|
{
|
|
209
212
|
element: <ProtectedRoute />,
|
|
@@ -213,10 +216,6 @@ export const apiKeyPlugin = (
|
|
|
213
216
|
path: "/settings/api-keys",
|
|
214
217
|
element: <SettingsApiKeys service={service} />,
|
|
215
218
|
},
|
|
216
|
-
// {
|
|
217
|
-
// path: "/settings/api-keys/new",
|
|
218
|
-
// element: <CreateApiKey service={service} />,
|
|
219
|
-
// },
|
|
220
219
|
],
|
|
221
220
|
},
|
|
222
221
|
];
|
|
@@ -19,7 +19,7 @@ type Documents = {
|
|
|
19
19
|
"\n query OperationsForTag(\n $input: JSON!\n $type: SchemaType!\n $tag: String\n $untagged: Boolean\n ) {\n schema(input: $input, type: $type) {\n servers {\n url\n }\n description\n summary\n title\n url\n version\n tag(slug: $tag, untagged: $untagged) {\n name\n description\n operations {\n slug\n ...OperationsFragment\n }\n next {\n name\n slug\n }\n prev {\n name\n slug\n }\n }\n }\n }\n": typeof types.OperationsForTagDocument;
|
|
20
20
|
"\n query GetSchemas($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n title\n description\n summary\n components {\n schemas {\n name\n schema\n extensions\n }\n }\n }\n }\n": typeof types.GetSchemasDocument;
|
|
21
21
|
"\n query getServerQuery($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n url\n servers {\n url\n }\n }\n }\n": typeof types.GetServerQueryDocument;
|
|
22
|
-
"\n query GetNavigationOperations($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n tags {\n slug\n name\n extensions\n operations {\n summary\n slug\n method\n operationId\n path\n }\n }\n components {\n schemas {\n __typename\n }\n }\n }\n }\n": typeof types.GetNavigationOperationsDocument;
|
|
22
|
+
"\n query GetNavigationOperations($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n extensions\n tags {\n slug\n name\n extensions\n operations {\n summary\n slug\n method\n operationId\n path\n }\n }\n components {\n schemas {\n __typename\n }\n }\n }\n }\n": typeof types.GetNavigationOperationsDocument;
|
|
23
23
|
};
|
|
24
24
|
const documents: Documents = {
|
|
25
25
|
"\n query ServersQuery($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n url\n servers {\n url\n }\n }\n }\n":
|
|
@@ -34,7 +34,7 @@ const documents: Documents = {
|
|
|
34
34
|
types.GetSchemasDocument,
|
|
35
35
|
"\n query getServerQuery($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n url\n servers {\n url\n }\n }\n }\n":
|
|
36
36
|
types.GetServerQueryDocument,
|
|
37
|
-
"\n query GetNavigationOperations($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n tags {\n slug\n name\n extensions\n operations {\n summary\n slug\n method\n operationId\n path\n }\n }\n components {\n schemas {\n __typename\n }\n }\n }\n }\n":
|
|
37
|
+
"\n query GetNavigationOperations($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n extensions\n tags {\n slug\n name\n extensions\n operations {\n summary\n slug\n method\n operationId\n path\n }\n }\n components {\n schemas {\n __typename\n }\n }\n }\n }\n":
|
|
38
38
|
types.GetNavigationOperationsDocument,
|
|
39
39
|
};
|
|
40
40
|
|
|
@@ -78,7 +78,7 @@ export function graphql(
|
|
|
78
78
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
|
79
79
|
*/
|
|
80
80
|
export function graphql(
|
|
81
|
-
source: "\n query GetNavigationOperations($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n tags {\n slug\n name\n extensions\n operations {\n summary\n slug\n method\n operationId\n path\n }\n }\n components {\n schemas {\n __typename\n }\n }\n }\n }\n",
|
|
81
|
+
source: "\n query GetNavigationOperations($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n extensions\n tags {\n slug\n name\n extensions\n operations {\n summary\n slug\n method\n operationId\n path\n }\n }\n components {\n schemas {\n __typename\n }\n }\n }\n }\n",
|
|
82
82
|
): typeof import("./graphql.js").GetNavigationOperationsDocument;
|
|
83
83
|
|
|
84
84
|
export function graphql(source: string) {
|
|
@@ -383,6 +383,7 @@ export type GetNavigationOperationsQuery = {
|
|
|
383
383
|
__typename?: "Query";
|
|
384
384
|
schema: {
|
|
385
385
|
__typename?: "Schema";
|
|
386
|
+
extensions?: any | null;
|
|
386
387
|
tags: Array<{
|
|
387
388
|
__typename?: "SchemaTag";
|
|
388
389
|
slug?: string | null;
|
|
@@ -645,6 +646,7 @@ export const GetServerQueryDocument = new TypedDocumentString(`
|
|
|
645
646
|
export const GetNavigationOperationsDocument = new TypedDocumentString(`
|
|
646
647
|
query GetNavigationOperations($input: JSON!, $type: SchemaType!) {
|
|
647
648
|
schema(input: $input, type: $type) {
|
|
649
|
+
extensions
|
|
648
650
|
tags {
|
|
649
651
|
slug
|
|
650
652
|
name
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CirclePlayIcon, LogInIcon } from "lucide-react";
|
|
2
2
|
import { type ReactNode } from "react";
|
|
3
3
|
import { matchPath } from "react-router";
|
|
4
|
+
import type { NavigationItem } from "../../../config/validators/NavigationSchema.js";
|
|
4
5
|
import { useAuth } from "../../authentication/hook.js";
|
|
5
6
|
import { type ZudokuPlugin } from "../../core/plugins.js";
|
|
6
7
|
import { Button } from "../../ui/Button.js";
|
|
@@ -18,6 +19,7 @@ import { getRoutes, getVersions } from "./util/getRoutes.js";
|
|
|
18
19
|
export const GetNavigationOperationsQuery = graphql(`
|
|
19
20
|
query GetNavigationOperations($input: JSON!, $type: SchemaType!) {
|
|
20
21
|
schema(input: $input, type: $type) {
|
|
22
|
+
extensions
|
|
21
23
|
tags {
|
|
22
24
|
slug
|
|
23
25
|
name
|
|
@@ -139,25 +141,73 @@ export const openApiPlugin = (config: OasPluginConfig): ZudokuPlugin => {
|
|
|
139
141
|
});
|
|
140
142
|
const data = await context.queryClient.ensureQueryData(query);
|
|
141
143
|
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
+
const tagCategories = new Map(
|
|
145
|
+
data.schema.tags
|
|
146
|
+
.filter((tag) => tag.name && tag.operations.length > 0)
|
|
147
|
+
.map((tag) => {
|
|
148
|
+
if (!tag.name) {
|
|
149
|
+
throw new Error(`Tag ${tag.slug} has no name`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const categoryPath = joinUrl(basePath, versionParam, tag.slug);
|
|
153
|
+
|
|
154
|
+
const isCollapsed =
|
|
155
|
+
tag.extensions?.["x-zudoku-collapsed"] ??
|
|
156
|
+
!config.options?.expandAllTags;
|
|
157
|
+
const isCollapsible =
|
|
158
|
+
tag.extensions?.["x-zudoku-collapsible"] ?? true;
|
|
159
|
+
|
|
160
|
+
return [
|
|
161
|
+
tag.name,
|
|
162
|
+
createNavigationCategory({
|
|
163
|
+
label: tag.name,
|
|
164
|
+
path: categoryPath,
|
|
165
|
+
operations: tag.operations,
|
|
166
|
+
collapsed: isCollapsed,
|
|
167
|
+
collapsible: isCollapsible,
|
|
168
|
+
}),
|
|
169
|
+
];
|
|
170
|
+
}),
|
|
171
|
+
);
|
|
144
172
|
|
|
145
|
-
|
|
173
|
+
const tagGroups =
|
|
174
|
+
(data.schema.extensions?.["x-tagGroups"] as
|
|
175
|
+
| { name: string; tags: string[] }[]
|
|
176
|
+
| undefined) ?? [];
|
|
146
177
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
178
|
+
const groupedTags = new Set(
|
|
179
|
+
tagGroups.flatMap((group) =>
|
|
180
|
+
group.tags.filter((name) => tagCategories.has(name)),
|
|
181
|
+
),
|
|
182
|
+
);
|
|
152
183
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
184
|
+
const groupedCategories: NavigationItem[] = tagGroups.flatMap(
|
|
185
|
+
(group) => {
|
|
186
|
+
const items = group.tags
|
|
187
|
+
.map((name) => tagCategories.get(name))
|
|
188
|
+
.filter(Boolean) as NavigationItem[];
|
|
189
|
+
|
|
190
|
+
if (items.length === 0) {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
return [
|
|
194
|
+
{
|
|
195
|
+
type: "category",
|
|
196
|
+
label: group.name,
|
|
197
|
+
items,
|
|
198
|
+
collapsible: true,
|
|
199
|
+
collapsed: !config.options?.expandAllTags,
|
|
200
|
+
},
|
|
201
|
+
];
|
|
202
|
+
},
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const categories: NavigationItem[] = [
|
|
206
|
+
...groupedCategories,
|
|
207
|
+
...Array.from(tagCategories.entries())
|
|
208
|
+
.filter(([name]) => !groupedTags.has(name))
|
|
209
|
+
.map(([, cat]) => cat),
|
|
210
|
+
];
|
|
161
211
|
|
|
162
212
|
const untaggedOperations = data.schema.tags.find(
|
|
163
213
|
(tag) => !tag.name,
|
|
@@ -19,7 +19,9 @@ export const ActionButton = forwardRef<HTMLButtonElement, ActionButtonProps>(
|
|
|
19
19
|
<Spinner />
|
|
20
20
|
</div>
|
|
21
21
|
)}
|
|
22
|
-
<
|
|
22
|
+
<span className={cn("block", isPending && "invisible")}>
|
|
23
|
+
{children}
|
|
24
|
+
</span>
|
|
23
25
|
</Button>
|
|
24
26
|
);
|
|
25
27
|
},
|
|
@@ -18,17 +18,19 @@ export default function invariant(
|
|
|
18
18
|
throw new ZudokuError(provided ?? "Invariant failed");
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
export type ZudokuErrorOptions = {
|
|
22
|
+
developerHint?: string;
|
|
23
|
+
title?: string;
|
|
24
|
+
cause?: Error;
|
|
25
|
+
};
|
|
26
|
+
|
|
21
27
|
export class ZudokuError extends Error {
|
|
22
28
|
public developerHint: string | undefined;
|
|
23
29
|
public title: string | undefined;
|
|
24
30
|
|
|
25
31
|
constructor(
|
|
26
32
|
message: string,
|
|
27
|
-
{
|
|
28
|
-
developerHint,
|
|
29
|
-
title,
|
|
30
|
-
cause,
|
|
31
|
-
}: { developerHint?: string; title?: string; cause?: Error } = {},
|
|
33
|
+
{ developerHint, title, cause }: ZudokuErrorOptions = {},
|
|
32
34
|
) {
|
|
33
35
|
super(message, { cause });
|
|
34
36
|
this.name = "ZudokuError";
|