startx 1.0.1 → 1.0.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/.dockerignore +4 -0
- package/apps/cli/src/commands/index.ts +1 -1
- package/apps/cli/src/commands/{common → test}/test.ts +4 -2
- package/apps/cli/tsconfig.json +0 -1
- package/apps/core-server/Dockerfile +5 -4
- package/apps/core-server/package.json +1 -1
- package/apps/core-server/tsconfig.json +1 -1
- package/apps/queue-worker/package.json +1 -1
- package/apps/queue-worker/tsconfig.json +1 -1
- package/apps/startx-cli/dist/index.mjs +68 -53
- package/apps/startx-cli/src/commands/package.ts +453 -0
- package/apps/startx-cli/src/configs/scripts.ts +18 -2
- package/apps/startx-cli/src/index.ts +2 -4
- package/apps/startx-cli/src/types.ts +2 -4
- package/apps/startx-cli/src/utils/inquirer.ts +8 -1
- package/apps/web-client/.dockerignore +4 -0
- package/apps/web-client/app/app.css +1 -0
- package/apps/web-client/app/components.json +23 -0
- package/apps/web-client/app/config/auth/auth-state.ts +59 -0
- package/apps/web-client/app/config/axios-client.ts +87 -0
- package/apps/web-client/app/config/env.ts +5 -0
- package/apps/web-client/app/entry.client.tsx +7 -0
- package/apps/web-client/app/eslint.config.ts +4 -0
- package/apps/web-client/app/root.tsx +77 -0
- package/apps/web-client/app/routes/home.tsx +12 -0
- package/apps/web-client/app/routes.ts +3 -0
- package/apps/web-client/eslint.config.ts +4 -0
- package/apps/web-client/package.json +55 -0
- package/apps/web-client/react-router.config.ts +7 -0
- package/apps/web-client/tsconfig.json +22 -0
- package/apps/web-client/vite-env.d.ts +8 -0
- package/apps/web-client/vite.config.ts +30 -0
- package/biome.json +5 -0
- package/configs/eslint-config/eslint.config.ts +1 -0
- package/configs/eslint-config/src/configs/base.ts +0 -1
- package/configs/eslint-config/src/configs/frontend.ts +1 -1
- package/configs/eslint-config/tsconfig.json +1 -1
- package/configs/typescript-config/tsconfig.frontend.json +1 -1
- package/configs/vitest-config/tsconfig.json +1 -1
- package/package.json +1 -1
- package/packages/@db/drizzle/tsconfig.json +1 -1
- package/packages/@db/sqlite/tsconfig.json +1 -1
- package/packages/@repo/env/package.json +1 -2
- package/packages/@repo/env/src/utils.ts +17 -11
- package/packages/@repo/lib/package.json +3 -1
- package/packages/@repo/lib/src/session-module/i-session.ts +108 -0
- package/packages/@repo/lib/src/session-module/index.ts +8 -111
- package/packages/@repo/lib/src/session-module/redis-session.ts +44 -0
- package/packages/@repo/lib/tsconfig.json +0 -1
- package/packages/@repo/logger/package.json +0 -1
- package/packages/@repo/logger/tsconfig.json +1 -1
- package/packages/@repo/mail/tsconfig.json +1 -1
- package/packages/@repo/redis/tsconfig.json +1 -1
- package/packages/aix/package.json +2 -0
- package/packages/aix/src/providers/ai-interface.ts +4 -4
- package/packages/aix/src/providers/bedrock/bedrock.ts +261 -0
- package/packages/aix/src/providers/default-models.ts +65 -0
- package/packages/aix/src/providers/openai/openai.ts +2 -2
- package/packages/aix/src/providers/providers.ts +11 -0
- package/packages/aix/src/providers/types.ts +1 -1
- package/packages/{constants → common}/package.json +4 -2
- package/packages/{constants/src/index.ts → common/src/constants.ts} +0 -5
- package/packages/common/src/types/users.ts +10 -0
- package/packages/{constants → common}/tsconfig.json +0 -3
- package/packages/ui/components.json +15 -8
- package/packages/ui/package.json +23 -36
- package/packages/ui/src/api/axios/i-client.ts +40 -0
- package/packages/ui/src/api/index.ts +6 -0
- package/packages/ui/src/api/query-provider.tsx +34 -0
- package/packages/ui/src/api/use-api/api-builder.ts +139 -0
- package/packages/ui/src/api/use-api/api-helpers.ts +165 -0
- package/packages/ui/src/api/use-api/api-types.ts +138 -0
- package/packages/ui/src/api/use-api/query-factory.ts +66 -0
- package/packages/ui/src/api/use-api/react-query/types.ts +64 -0
- package/packages/ui/src/api/use-api/react-query/use-api-client.ts +56 -0
- package/packages/ui/src/api/use-api/react-query/use-api.ts +297 -0
- package/packages/ui/src/components/custom/form-wrapper.tsx +113 -160
- package/packages/ui/src/components/custom/grid-component.tsx +4 -4
- package/packages/ui/src/components/custom/hover-tool.tsx +1 -1
- package/packages/ui/src/components/custom/image-picker.tsx +18 -20
- package/packages/ui/src/components/custom/no-content.tsx +6 -16
- package/packages/ui/src/components/custom/page-section.tsx +14 -17
- package/packages/ui/src/components/custom/simple-popover.tsx +5 -9
- package/packages/ui/src/components/custom/theme-provider.tsx +117 -42
- package/packages/ui/src/components/custom/typography.tsx +20 -22
- package/packages/ui/src/components/extensions/timeline.tsx +100 -0
- package/packages/ui/src/components/ui/alert-dialog.tsx +46 -108
- package/packages/ui/src/components/ui/avatar.tsx +79 -42
- package/packages/ui/src/components/ui/badge.tsx +29 -34
- package/packages/ui/src/components/ui/breadcrumb.tsx +65 -81
- package/packages/ui/src/components/ui/button.tsx +80 -80
- package/packages/ui/src/components/ui/card.tsx +48 -69
- package/packages/ui/src/components/ui/carousel.tsx +184 -211
- package/packages/ui/src/components/ui/checkbox.tsx +21 -24
- package/packages/ui/src/components/ui/command.tsx +121 -102
- package/packages/ui/src/components/ui/dialog.tsx +45 -32
- package/packages/ui/src/components/ui/dropdown-menu.tsx +45 -33
- package/packages/ui/src/components/ui/field.tsx +218 -0
- package/packages/ui/src/components/ui/form.tsx +63 -76
- package/packages/ui/src/components/ui/input-group.tsx +137 -0
- package/packages/ui/src/components/ui/input-otp.tsx +60 -50
- package/packages/ui/src/components/ui/input.tsx +16 -15
- package/packages/ui/src/components/ui/label.tsx +14 -17
- package/packages/ui/src/components/ui/multiple-select.tsx +22 -33
- package/packages/ui/src/components/ui/popover.tsx +20 -8
- package/packages/ui/src/components/ui/select.tsx +33 -34
- package/packages/ui/src/components/ui/separator.tsx +8 -8
- package/packages/ui/src/components/ui/sheet.tsx +32 -59
- package/packages/ui/src/components/ui/sidebar.tsx +654 -0
- package/packages/ui/src/components/ui/skeleton.tsx +2 -8
- package/packages/ui/src/components/ui/sonner.tsx +39 -0
- package/packages/ui/src/components/ui/spinner.tsx +6 -13
- package/packages/ui/src/components/ui/switch.tsx +15 -10
- package/packages/ui/src/components/ui/table.tsx +48 -89
- package/packages/ui/src/components/ui/tabs.tsx +37 -15
- package/packages/ui/src/components/ui/textarea.tsx +13 -13
- package/packages/ui/src/components/ui/tooltip.tsx +37 -23
- package/packages/ui/src/{components/hooks → hooks}/event/use-click.tsx +6 -10
- package/packages/ui/src/hooks/time/use-timer.tsx +51 -0
- package/packages/ui/src/hooks/use-media-query.tsx +19 -0
- package/packages/ui/src/hooks/use-mobile.tsx +17 -0
- package/packages/ui/src/{components/hooks → hooks}/use-update-effect.tsx +2 -2
- package/packages/ui/src/lib/utils.ts +113 -0
- package/packages/ui/src/styles/globals.css +311 -0
- package/packages/ui/src/styles/tailwind.css +89 -0
- package/packages/ui/tsconfig.json +7 -9
- package/pnpm-workspace.yaml +74 -64
- package/packages/ui/postcss.config.mjs +0 -9
- package/packages/ui/src/components/extensions/carousel.tsx +0 -392
- package/packages/ui/src/components/hooks/time/useTimer.tsx +0 -51
- package/packages/ui/src/components/hooks/use-media-query.tsx +0 -19
- package/packages/ui/src/components/lib/utils.ts +0 -242
- package/packages/ui/src/components/ui/timeline.tsx +0 -118
- package/packages/ui/src/components/util/n-formattor.ts +0 -22
- package/packages/ui/src/components/util/storage.ts +0 -37
- package/packages/ui/src/globals.css +0 -87
- package/packages/ui/tailwind.config.ts +0 -94
- /package/packages/{constants → common}/eslint.config.ts +0 -0
- /package/packages/{constants → common}/src/api.ts +0 -0
- /package/packages/{constants → common}/src/time.ts +0 -0
- /package/packages/{constants → common}/vitest.config.ts +0 -0
- /package/packages/ui/src/{components/hooks/time/useDebounce.tsx → hooks/time/use-debounce.tsx} +0 -0
- /package/packages/ui/src/{components/hooks/time/useInterval.tsx → hooks/time/use-interval.tsx} +0 -0
- /package/packages/ui/src/{components/hooks/time/useTimeout.tsx → hooks/time/use-timeout.tsx} +0 -0
- /package/packages/ui/src/{components/hooks → hooks}/use-persistent-storage.tsx +0 -0
- /package/packages/ui/src/{components/hooks → hooks}/use-window-dimension.tsx +0 -0
- /package/packages/ui/src/{components/sonner.tsx → sonner.ts} +0 -0
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
-
"style": "
|
|
4
|
-
"rsc":
|
|
3
|
+
"style": "radix-sera",
|
|
4
|
+
"rsc": false,
|
|
5
5
|
"tsx": true,
|
|
6
6
|
"tailwind": {
|
|
7
|
-
"config": "
|
|
8
|
-
"css": "src/globals.css",
|
|
9
|
-
"baseColor": "
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "src/styles/globals.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
10
|
"cssVariables": true
|
|
11
11
|
},
|
|
12
|
+
"iconLibrary": "phosphor",
|
|
12
13
|
"aliases": {
|
|
13
|
-
"components": "
|
|
14
|
-
"utils": "
|
|
15
|
-
|
|
14
|
+
"components": "@repo/ui/components",
|
|
15
|
+
"utils": "@repo/ui/lib/utils",
|
|
16
|
+
"hooks": "@repo/ui/hooks",
|
|
17
|
+
"lib": "@repo/ui/lib",
|
|
18
|
+
"ui": "@repo/ui/components"
|
|
19
|
+
},
|
|
20
|
+
"rtl": false,
|
|
21
|
+
"menuColor": "inverted-translucent",
|
|
22
|
+
"menuAccent": "subtle"
|
|
16
23
|
}
|
package/packages/ui/package.json
CHANGED
|
@@ -1,69 +1,59 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "ui",
|
|
2
|
+
"name": "@repo/ui",
|
|
3
3
|
"version": "0.0.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"clean": "rimraf dist .turbo",
|
|
8
|
-
"watch:dev": "pnpm watch",
|
|
9
8
|
"typecheck": "tsc --noEmit",
|
|
10
9
|
"format": "biome format --write .",
|
|
11
10
|
"format:check": "biome ci .",
|
|
12
11
|
"test": "vitest run",
|
|
13
12
|
"lint": "eslint .",
|
|
14
|
-
"lint:fix": "eslint . --fix"
|
|
15
|
-
"watch": "tsc -p tsconfig.build.json --watch"
|
|
13
|
+
"lint:fix": "eslint . --fix"
|
|
16
14
|
},
|
|
17
15
|
"peerDependencies": {
|
|
18
16
|
"react": "^19.0.0"
|
|
19
17
|
},
|
|
20
18
|
"devDependencies": {
|
|
21
|
-
"@
|
|
19
|
+
"@tailwindcss/vite": "catalog:",
|
|
22
20
|
"@types/react": "catalog:",
|
|
21
|
+
"@types/react-dom": "catalog:",
|
|
23
22
|
"autoprefixer": "^10",
|
|
24
23
|
"eslint-config": "workspace:*",
|
|
25
|
-
"postcss": "^8.4.49",
|
|
26
|
-
"postcss-load-config": "^6",
|
|
27
24
|
"tailwindcss": "catalog:",
|
|
28
25
|
"typescript-config": "workspace:*",
|
|
29
26
|
"vitest-config": "workspace:*"
|
|
30
27
|
},
|
|
31
28
|
"dependencies": {
|
|
29
|
+
"@fontsource-variable/eb-garamond": "^5.2.7",
|
|
30
|
+
"@fontsource-variable/inter": "^5.2.8",
|
|
32
31
|
"@hookform/resolvers": "^3.9.1",
|
|
33
|
-
"@
|
|
34
|
-
"@radix-ui/react-avatar": "^1.1.1",
|
|
35
|
-
"@radix-ui/react-checkbox": "^1.1.2",
|
|
36
|
-
"@radix-ui/react-dialog": "^1.1.15",
|
|
37
|
-
"@radix-ui/react-dropdown-menu": "^2.1.2",
|
|
38
|
-
"@radix-ui/react-label": "^2.1.0",
|
|
39
|
-
"@radix-ui/react-popover": "^1.1.2",
|
|
40
|
-
"@radix-ui/react-select": "^2.1.2",
|
|
41
|
-
"@radix-ui/react-separator": "^1.1.0",
|
|
42
|
-
"@radix-ui/react-slot": "^1.2.3",
|
|
43
|
-
"@radix-ui/react-switch": "^1.1.1",
|
|
44
|
-
"@radix-ui/react-tabs": "^1.1.1",
|
|
45
|
-
"@radix-ui/react-tooltip": "^1.1.3",
|
|
32
|
+
"@phosphor-icons/react": "^2.1.10",
|
|
46
33
|
"@tailwindcss/postcss": "^4",
|
|
47
|
-
"class-variance-authority": "^0.7.
|
|
34
|
+
"class-variance-authority": "^0.7.1",
|
|
48
35
|
"clsx": "^2.1.1",
|
|
49
36
|
"cmdk": "^1.1.1",
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"embla-carousel-react": "^8.5.2",
|
|
53
|
-
"input-otp": "^1.4.1",
|
|
37
|
+
"embla-carousel-react": "^8.6.0",
|
|
38
|
+
"input-otp": "^1.4.2",
|
|
54
39
|
"lucide-react": "catalog:",
|
|
55
|
-
"
|
|
40
|
+
"next-themes": "^0.4.6",
|
|
41
|
+
"radix-ui": "^1.4.3",
|
|
42
|
+
"react-hook-form": "^7.75.0",
|
|
56
43
|
"react-icons": "^5.5.0",
|
|
57
|
-
"sonner": "^
|
|
44
|
+
"sonner": "^2.0.7",
|
|
58
45
|
"tailwind-merge": "^2.3.0",
|
|
59
|
-
"
|
|
46
|
+
"tw-animate-css": "^1.4.0",
|
|
47
|
+
"@tanstack/react-query": "catalog:",
|
|
48
|
+
"@tanstack/react-query-devtools": "catalog:"
|
|
60
49
|
},
|
|
61
50
|
"exports": {
|
|
62
|
-
"./globals.css": "./src/globals.css",
|
|
63
|
-
"
|
|
64
|
-
"./
|
|
65
|
-
"./
|
|
66
|
-
"
|
|
51
|
+
"./globals.css": "./src/styles/globals.css",
|
|
52
|
+
"./*": "./src/*.ts",
|
|
53
|
+
"./lib/*": "./src/lib/utils.ts",
|
|
54
|
+
"./components/*": "./src/components/*.tsx",
|
|
55
|
+
"./hooks/*": "./src/hooks/*.ts",
|
|
56
|
+
"./api": "./src/api/index.ts"
|
|
67
57
|
},
|
|
68
58
|
"startx": {
|
|
69
59
|
"iTags": [
|
|
@@ -72,9 +62,6 @@
|
|
|
72
62
|
],
|
|
73
63
|
"requiredDevDeps": [
|
|
74
64
|
"typescript-config"
|
|
75
|
-
],
|
|
76
|
-
"ignore": [
|
|
77
|
-
"vitest-config"
|
|
78
65
|
]
|
|
79
66
|
}
|
|
80
67
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import axios, { AxiosError, type AxiosInstance } from "axios";
|
|
2
|
+
|
|
3
|
+
type IAxiosClientOpts = {
|
|
4
|
+
includeDefaultInterceptors?: boolean;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export abstract class IAxiosClient {
|
|
8
|
+
public readonly privateClient: AxiosInstance;
|
|
9
|
+
public readonly publicClient: AxiosInstance;
|
|
10
|
+
protected accessToken: string | null = null;
|
|
11
|
+
protected opts: IAxiosClientOpts;
|
|
12
|
+
constructor(baseURL: string, opts: IAxiosClientOpts) {
|
|
13
|
+
this.publicClient = axios.create({ baseURL });
|
|
14
|
+
|
|
15
|
+
this.privateClient = axios.create({
|
|
16
|
+
baseURL,
|
|
17
|
+
withCredentials: true,
|
|
18
|
+
});
|
|
19
|
+
this.opts = opts;
|
|
20
|
+
|
|
21
|
+
this.defaultInterceptors();
|
|
22
|
+
this.setupInterceptors();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
protected defaultInterceptors(): void {
|
|
26
|
+
if (!this.opts.includeDefaultInterceptors) return;
|
|
27
|
+
this.privateClient.interceptors.request.use(
|
|
28
|
+
config => {
|
|
29
|
+
if (this.accessToken) {
|
|
30
|
+
config.headers.Authorization = `Bearer ${this.accessToken}`;
|
|
31
|
+
}
|
|
32
|
+
return config;
|
|
33
|
+
},
|
|
34
|
+
(error: AxiosError) => Promise.reject(error)
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
abstract setupInterceptors(): void;
|
|
39
|
+
abstract getAccessToken(): Promise<string>;
|
|
40
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { QueryProvider } from "./query-provider";
|
|
2
|
+
import { ApiSchema } from "./use-api/api-builder";
|
|
3
|
+
import { useApi } from "./use-api/react-query/use-api";
|
|
4
|
+
import { useApiClient } from "./use-api/react-query/use-api-client";
|
|
5
|
+
|
|
6
|
+
export { QueryProvider, ApiSchema, useApi, useApiClient };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
2
|
+
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
|
3
|
+
import { type ReactNode } from "react";
|
|
4
|
+
import { ApiHelper } from "./use-api/api-helpers";
|
|
5
|
+
|
|
6
|
+
const queryClient = new QueryClient({
|
|
7
|
+
defaultOptions: {
|
|
8
|
+
queries: {
|
|
9
|
+
refetchOnMount: false,
|
|
10
|
+
refetchOnWindowFocus: false,
|
|
11
|
+
staleTime: ApiHelper.parseTime("5:min"),
|
|
12
|
+
},
|
|
13
|
+
mutations: {
|
|
14
|
+
onError(error: any) {
|
|
15
|
+
console.error(error?.response?.data);
|
|
16
|
+
// toast.error(error.response?.data?.message ?? error.message);
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
export const QueryProvider = ({
|
|
22
|
+
children,
|
|
23
|
+
mode,
|
|
24
|
+
}: {
|
|
25
|
+
children: ReactNode;
|
|
26
|
+
mode?: "development" | "production" | "staging";
|
|
27
|
+
}) => {
|
|
28
|
+
return (
|
|
29
|
+
<QueryClientProvider client={queryClient}>
|
|
30
|
+
{mode === "development" && <ReactQueryDevtools initialIsOpen={false} />}
|
|
31
|
+
{children}
|
|
32
|
+
</QueryClientProvider>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { AxiosError, AxiosInstance } from "axios";
|
|
2
|
+
import { z, ZodTypeAny } from "zod";
|
|
3
|
+
import type {
|
|
4
|
+
IFetchOptions,
|
|
5
|
+
IPaginatedFetchOptions,
|
|
6
|
+
IFetchMutationOptions,
|
|
7
|
+
IRefetch,
|
|
8
|
+
QueryEvent,
|
|
9
|
+
IPaginatedData,
|
|
10
|
+
RawSchema,
|
|
11
|
+
RawSchemaKeys,
|
|
12
|
+
CbAction,
|
|
13
|
+
QueryEventWithKeys,
|
|
14
|
+
QueryKey,
|
|
15
|
+
ZQuery,
|
|
16
|
+
ZParams,
|
|
17
|
+
} from "./api-types";
|
|
18
|
+
import { makeQueryKeyFactory, createQueryKeysProxy, type QueryKeyFactory } from "./query-factory";
|
|
19
|
+
import { useApi } from "./react-query/use-api";
|
|
20
|
+
|
|
21
|
+
type EnsureUnique<Key extends string, ExistingKeys extends string> = Key extends ExistingKeys
|
|
22
|
+
? `❌ Api key '${Key}' already exists.`
|
|
23
|
+
: Key;
|
|
24
|
+
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
26
|
+
export class ApiSchema<Schema extends RawSchema = {}> {
|
|
27
|
+
readonly schema: Schema;
|
|
28
|
+
|
|
29
|
+
constructor(schema?: Schema) {
|
|
30
|
+
this.schema = schema ?? ({} as Schema);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private wrapRefetch<IK extends string, IQ extends z.output<ZQuery>, IP extends z.output<ZParams>, IB, ID>(
|
|
34
|
+
refetch: IRefetch<IK, IQ, IP, IB, ID, Schema> | undefined
|
|
35
|
+
): IRefetch<IK, IQ, IP, IB, ID> | undefined {
|
|
36
|
+
if (!refetch) return undefined;
|
|
37
|
+
const { cb, ...rest } = refetch;
|
|
38
|
+
return {
|
|
39
|
+
...rest,
|
|
40
|
+
cb: props => (typeof cb === "function" ? cb(props, createQueryKeysProxy(this.schema)) : cb),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private wrapQueryEvent<IK extends string, IQ extends z.output<ZQuery>, IP extends z.output<ZParams>, IB, ID>(
|
|
45
|
+
event: QueryEventWithKeys<IK, IQ, IP, IB, ID, Schema> | undefined
|
|
46
|
+
): QueryEvent<IK, IQ, IP, IB, ID> | undefined {
|
|
47
|
+
if (!event) return undefined;
|
|
48
|
+
const wrapped: QueryEvent<IK, IQ, IP, IB, ID> = {};
|
|
49
|
+
const wrapCb = (cb: CbAction<Schema, IP, IQ, IB, ID, IK, Array<QueryKey<string>>>) =>
|
|
50
|
+
typeof cb === "function" ? (props: Parameters<typeof cb>[0]) => cb(props, createQueryKeysProxy(this.schema)) : cb;
|
|
51
|
+
if (event.fn) wrapped.fn = event.fn;
|
|
52
|
+
if (event.invalidateQuery) wrapped.invalidateQuery = wrapCb(event.invalidateQuery);
|
|
53
|
+
if (event.refetchQuery) wrapped.refetchQuery = wrapCb(event.refetchQuery);
|
|
54
|
+
if (event.clearQuery) wrapped.clearQuery = wrapCb(event.clearQuery);
|
|
55
|
+
return wrapped;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fetch<KEY extends string, ZQ extends ZQuery, ZP extends ZParams, ID = unknown>(
|
|
59
|
+
key: EnsureUnique<KEY, RawSchemaKeys<Schema>>,
|
|
60
|
+
options: Omit<IFetchOptions<ID, ZQ, ZP, string>, "apiType" | "refetch"> & {
|
|
61
|
+
refetch?: IRefetch<KEY | RawSchemaKeys<Schema>, z.output<ZQ>, z.output<ZP>, undefined, ID, Schema>;
|
|
62
|
+
}
|
|
63
|
+
): ApiSchema<Schema & Record<KEY, IFetchOptions<ID, ZQ, ZP, KEY> & { queryKey: QueryKeyFactory<ZQ, ZP> }>> {
|
|
64
|
+
const { refetch, ...rest } = options;
|
|
65
|
+
const entry = {
|
|
66
|
+
...rest,
|
|
67
|
+
apiType: "fetch" as const,
|
|
68
|
+
queryKey: makeQueryKeyFactory(options.key ?? [], options.zParams, options.zQuery),
|
|
69
|
+
refetch: this.wrapRefetch(refetch),
|
|
70
|
+
};
|
|
71
|
+
return new ApiSchema({ ...this.schema, [key]: entry });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
paginatedFetch<KEY extends string, ID, IO, ZQ extends ZQuery, ZP extends ZParams>(
|
|
75
|
+
key: EnsureUnique<KEY, RawSchemaKeys<Schema>>,
|
|
76
|
+
options: Omit<IPaginatedFetchOptions<ID, IO, ZQ, ZP, string>, "apiType" | "refetch"> & {
|
|
77
|
+
refetch?: IRefetch<
|
|
78
|
+
KEY | RawSchemaKeys<Schema>,
|
|
79
|
+
z.output<ZQ>,
|
|
80
|
+
z.output<ZP>,
|
|
81
|
+
undefined,
|
|
82
|
+
IPaginatedData<ID, IO>,
|
|
83
|
+
Schema
|
|
84
|
+
>;
|
|
85
|
+
}
|
|
86
|
+
): ApiSchema<
|
|
87
|
+
Schema & Record<KEY, IPaginatedFetchOptions<ID, IO, ZQ, ZP, KEY> & { queryKey: QueryKeyFactory<ZQ, ZP> }>
|
|
88
|
+
> {
|
|
89
|
+
const { refetch, ...rest } = options;
|
|
90
|
+
const entry = {
|
|
91
|
+
...rest,
|
|
92
|
+
apiType: "paginated-fetch" as const,
|
|
93
|
+
queryKey: makeQueryKeyFactory(options.key ?? [], options.zParams, options.zQuery),
|
|
94
|
+
refetch: this.wrapRefetch(refetch),
|
|
95
|
+
};
|
|
96
|
+
return new ApiSchema({ ...this.schema, [key]: entry });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
mutation<KEY extends string, ZQ extends ZQuery, ZB extends ZodTypeAny, ZP extends ZParams, ID = unknown>(
|
|
100
|
+
key: EnsureUnique<KEY, RawSchemaKeys<Schema>>,
|
|
101
|
+
options: Omit<
|
|
102
|
+
IFetchMutationOptions<KEY | RawSchemaKeys<Schema>, ZQ, ZB, ZP, ID>,
|
|
103
|
+
"apiType" | "onSuccess" | "onFetch" | "onError" | "refetch"
|
|
104
|
+
> & {
|
|
105
|
+
onSuccess?: QueryEventWithKeys<KEY | RawSchemaKeys<Schema>, z.output<ZQ>, z.output<ZP>, z.output<ZB>, ID, Schema>;
|
|
106
|
+
onFetch?: QueryEventWithKeys<KEY | RawSchemaKeys<Schema>, z.output<ZQ>, z.output<ZP>, z.output<ZB>, ID, Schema>;
|
|
107
|
+
onError?: QueryEventWithKeys<
|
|
108
|
+
KEY | RawSchemaKeys<Schema>,
|
|
109
|
+
z.output<ZQ>,
|
|
110
|
+
z.output<ZP>,
|
|
111
|
+
z.output<ZB>,
|
|
112
|
+
AxiosError<{ message?: string }>,
|
|
113
|
+
Schema
|
|
114
|
+
>;
|
|
115
|
+
refetch?: IRefetch<KEY | RawSchemaKeys<Schema>, z.output<ZQ>, z.output<ZP>, z.output<ZB>, ID, Schema>;
|
|
116
|
+
}
|
|
117
|
+
): ApiSchema<
|
|
118
|
+
Schema & Record<KEY, IFetchMutationOptions<KEY, ZQ, ZB, ZP, ID> & { queryKey: QueryKeyFactory<ZQ, ZP> }>
|
|
119
|
+
> {
|
|
120
|
+
const { onSuccess, onFetch, onError, refetch, ...rest } = options;
|
|
121
|
+
const entry = {
|
|
122
|
+
...rest,
|
|
123
|
+
apiType: "mutation" as const,
|
|
124
|
+
queryKey: makeQueryKeyFactory([], options.zParams, options.zQuery),
|
|
125
|
+
onSuccess: this.wrapQueryEvent(onSuccess),
|
|
126
|
+
onFetch: this.wrapQueryEvent(onFetch),
|
|
127
|
+
onError: this.wrapQueryEvent(onError),
|
|
128
|
+
refetch: this.wrapRefetch(refetch),
|
|
129
|
+
};
|
|
130
|
+
return new ApiSchema({ ...this.schema, [key]: entry });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
getSchema(): Schema {
|
|
134
|
+
return this.schema;
|
|
135
|
+
}
|
|
136
|
+
getReactQuery(axios: AxiosInstance) {
|
|
137
|
+
return useApi(this.schema, axios);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { ZodTypeAny, z } from "zod";
|
|
2
|
+
import type { QueryKey, TimeString, TimeUnit, ZParams, ZQuery } from "./api-types";
|
|
3
|
+
|
|
4
|
+
type AnyObject = Record<PropertyKey, unknown>;
|
|
5
|
+
|
|
6
|
+
type DeepMerge<A, B> = A extends AnyObject
|
|
7
|
+
? B extends AnyObject
|
|
8
|
+
? {
|
|
9
|
+
[K in keyof A | keyof B]: K extends keyof A
|
|
10
|
+
? K extends keyof B
|
|
11
|
+
? DeepMerge<A[K], B[K]>
|
|
12
|
+
: A[K]
|
|
13
|
+
: K extends keyof B
|
|
14
|
+
? B[K]
|
|
15
|
+
: never;
|
|
16
|
+
}
|
|
17
|
+
: A | B
|
|
18
|
+
: A | B;
|
|
19
|
+
|
|
20
|
+
export class ApiHelper {
|
|
21
|
+
static parseTime(input?: TimeString | number): number | undefined {
|
|
22
|
+
if (input === undefined || input === null) return undefined;
|
|
23
|
+
if (typeof input === "number") return input;
|
|
24
|
+
|
|
25
|
+
const [numStr, unit] = input.split(":") as [string, TimeUnit];
|
|
26
|
+
const value = Number(numStr);
|
|
27
|
+
if (isNaN(value)) return undefined;
|
|
28
|
+
|
|
29
|
+
switch (unit) {
|
|
30
|
+
case "ms":
|
|
31
|
+
return value;
|
|
32
|
+
case "sec":
|
|
33
|
+
return value * 1000;
|
|
34
|
+
case "min":
|
|
35
|
+
return value * 1000 * 60;
|
|
36
|
+
case "hour":
|
|
37
|
+
return value * 1000 * 60 * 60;
|
|
38
|
+
default:
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static validateSchema(schema: ZodTypeAny, value: unknown) {
|
|
44
|
+
const result = schema.safeParse(value);
|
|
45
|
+
if (result.success) return { success: true as const, data: result.data };
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
success: false as const,
|
|
49
|
+
errors: result.error.issues.map(i => `${i.path.join(".")}: ${i.message}`),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
static buildUrl<ZP extends ZParams = ZParams, ZQ extends ZQuery = ZQuery>({
|
|
54
|
+
route,
|
|
55
|
+
params,
|
|
56
|
+
searchParams,
|
|
57
|
+
}: {
|
|
58
|
+
route: string;
|
|
59
|
+
params?: z.output<ZP>;
|
|
60
|
+
searchParams?: z.output<ZQ>;
|
|
61
|
+
}): string {
|
|
62
|
+
let url = route;
|
|
63
|
+
|
|
64
|
+
if (params) {
|
|
65
|
+
for (const [key, value] of Object.entries(params)) {
|
|
66
|
+
if (value === undefined || value === null) continue;
|
|
67
|
+
|
|
68
|
+
url = url.replace(`:${key}`, encodeURIComponent(String(value)));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (searchParams) {
|
|
73
|
+
const search = new URLSearchParams();
|
|
74
|
+
|
|
75
|
+
const sortedKeys = Object.keys(searchParams).sort();
|
|
76
|
+
|
|
77
|
+
for (const key of sortedKeys) {
|
|
78
|
+
const value = searchParams[key];
|
|
79
|
+
|
|
80
|
+
if (value === undefined || value === null) continue;
|
|
81
|
+
|
|
82
|
+
if (Array.isArray(value)) {
|
|
83
|
+
for (const item of value) {
|
|
84
|
+
search.append(key, String(item));
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
search.set(key, String(value));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const qs = search.toString();
|
|
92
|
+
if (qs) {
|
|
93
|
+
url += `?${qs}`;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return url;
|
|
98
|
+
}
|
|
99
|
+
static getQueryKey<IK extends string, ZQ extends ZQuery = ZQuery, ZP extends ZParams = ZParams>(
|
|
100
|
+
key: IK,
|
|
101
|
+
extraKey?: string[],
|
|
102
|
+
input?: {
|
|
103
|
+
query?: z.output<ZQ>;
|
|
104
|
+
params?: z.output<ZP>;
|
|
105
|
+
}
|
|
106
|
+
): QueryKey<IK> {
|
|
107
|
+
const keys: string[] = [key];
|
|
108
|
+
|
|
109
|
+
const append = (prefix: "query" | "params", obj?: z.output<ZQ | ZP>) => {
|
|
110
|
+
if (!obj) return;
|
|
111
|
+
const sortedKeys = Object.keys(obj).sort();
|
|
112
|
+
for (const k of sortedKeys) {
|
|
113
|
+
const v = obj[k];
|
|
114
|
+
if (v === undefined) continue;
|
|
115
|
+
if (Array.isArray(v)) {
|
|
116
|
+
for (const item of v) {
|
|
117
|
+
keys.push(`${prefix}:${k}:${item}`);
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
keys.push(`${prefix}:${k}:${v}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
append("params", input?.params);
|
|
126
|
+
append("query", input?.query);
|
|
127
|
+
|
|
128
|
+
if (extraKey?.length) {
|
|
129
|
+
keys.push(...extraKey.filter(Boolean));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return keys as QueryKey<IK>;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private static isPlainObject(value: unknown): value is AnyObject {
|
|
136
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private static isEmpty(value: unknown): boolean {
|
|
140
|
+
if (value == null) return true;
|
|
141
|
+
if (typeof value === "string") return value.length === 0;
|
|
142
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
143
|
+
if (ApiHelper.isPlainObject(value)) return Object.keys(value).length === 0;
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
static merge<A, B>(first: A, second: B): DeepMerge<A, B> {
|
|
148
|
+
if (ApiHelper.isEmpty(first)) return second as DeepMerge<A, B>;
|
|
149
|
+
if (ApiHelper.isEmpty(second)) return first as DeepMerge<A, B>;
|
|
150
|
+
|
|
151
|
+
if (ApiHelper.isPlainObject(first) && ApiHelper.isPlainObject(second)) {
|
|
152
|
+
const out: AnyObject = { ...second, ...first };
|
|
153
|
+
|
|
154
|
+
for (const key of Object.keys(second)) {
|
|
155
|
+
if (key in first) {
|
|
156
|
+
out[key] = ApiHelper.merge((first as AnyObject)[key], (second as AnyObject)[key]);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return out as DeepMerge<A, B>;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return first as DeepMerge<A, B>;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { AxiosError } from "axios";
|
|
2
|
+
import { z, ZodTypeAny, type ZodObject, type ZodType } from "zod";
|
|
3
|
+
|
|
4
|
+
export type ApiType = "fetch" | "mutation" | "paginated-fetch";
|
|
5
|
+
export type ApiMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
6
|
+
|
|
7
|
+
export type TimeUnit = "ms" | "sec" | "min" | "hour";
|
|
8
|
+
export type TimeString = `${number}:${TimeUnit}`;
|
|
9
|
+
|
|
10
|
+
export type AutoSuggest<T extends string> = T | (string & {});
|
|
11
|
+
export type QueryKey<K extends string> = [AutoSuggest<K>, ...string[]];
|
|
12
|
+
|
|
13
|
+
export type RawSchema = Record<string, any>;
|
|
14
|
+
export type RawSchemaKeys<Schema extends RawSchema> = Extract<keyof Schema, string>;
|
|
15
|
+
|
|
16
|
+
export type SchemaQueryKeys<Schema extends RawSchema> = {
|
|
17
|
+
[K in keyof Schema & string]: Schema[K] extends { queryKey: (...args: any[]) => any } ? Schema[K]["queryKey"] : never;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type ValidParamValue = string | number | boolean | null | undefined;
|
|
21
|
+
export type ValidQueryValue = ValidParamValue | ValidParamValue[];
|
|
22
|
+
export type ZParams = ZodObject<Record<string, ZodType<ValidParamValue>>>;
|
|
23
|
+
export type ZQuery = ZodObject<Record<string, ZodType<ValidQueryValue>>>;
|
|
24
|
+
|
|
25
|
+
export interface IBaseApi<ID, ZQ extends ZQuery, ZP extends ZParams> {
|
|
26
|
+
apiType: ApiType;
|
|
27
|
+
route: string;
|
|
28
|
+
method?: ApiMethod;
|
|
29
|
+
zQuery?: ZQ;
|
|
30
|
+
zParams?: ZP;
|
|
31
|
+
data?: ID;
|
|
32
|
+
retry?: { count: number; interval: number };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type CbAction<
|
|
36
|
+
Schema extends RawSchema,
|
|
37
|
+
IP extends z.output<ZParams>,
|
|
38
|
+
IQ extends z.output<ZQuery>,
|
|
39
|
+
IB,
|
|
40
|
+
ID,
|
|
41
|
+
IK extends string,
|
|
42
|
+
CB,
|
|
43
|
+
> = NoInfer<
|
|
44
|
+
| Array<QueryKey<IK>>
|
|
45
|
+
| ((props: { query?: IQ; params?: IP; body?: IB; data?: ID }, queryKeys?: SchemaQueryKeys<Schema>) => CB)
|
|
46
|
+
>;
|
|
47
|
+
|
|
48
|
+
export type QueryEvent<
|
|
49
|
+
IK extends string,
|
|
50
|
+
IQ extends z.output<ZQuery>,
|
|
51
|
+
IP extends z.output<ZParams>,
|
|
52
|
+
IB,
|
|
53
|
+
ID,
|
|
54
|
+
Schema extends RawSchema = RawSchema,
|
|
55
|
+
> = {
|
|
56
|
+
fn?: CbAction<Schema, IP, IQ, IB, ID, IK, Promise<void>>;
|
|
57
|
+
invalidateQuery?: CbAction<Schema, IP, IQ, IB, ID, IK, Array<QueryKey<IK>>>;
|
|
58
|
+
refetchQuery?: CbAction<Schema, IP, IQ, IB, ID, IK, Array<QueryKey<IK>>>;
|
|
59
|
+
clearQuery?: CbAction<Schema, IP, IQ, IB, ID, IK, Array<QueryKey<IK>>>;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export type QueryEventWithKeys<
|
|
63
|
+
IK extends string,
|
|
64
|
+
IQ extends z.output<ZQuery>,
|
|
65
|
+
IP extends z.output<ZParams>,
|
|
66
|
+
IB,
|
|
67
|
+
ID,
|
|
68
|
+
Schema extends RawSchema,
|
|
69
|
+
> = {
|
|
70
|
+
fn?: (props: { query?: IQ; params?: IP; body?: IB; data?: ID }) => Promise<void>;
|
|
71
|
+
invalidateQuery?: CbAction<Schema, IP, IQ, IB, ID, IK, Array<QueryKey<IK>>>;
|
|
72
|
+
refetchQuery?: CbAction<Schema, IP, IQ, IB, ID, IK, Array<QueryKey<IK>>>;
|
|
73
|
+
clearQuery?: CbAction<Schema, IP, IQ, IB, ID, IK, Array<QueryKey<IK>>>;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export type IRefetch<
|
|
77
|
+
IK extends string,
|
|
78
|
+
IQ extends z.output<ZQuery>,
|
|
79
|
+
IP extends z.output<ZParams>,
|
|
80
|
+
IB,
|
|
81
|
+
ID,
|
|
82
|
+
Schema extends RawSchema = RawSchema,
|
|
83
|
+
> = {
|
|
84
|
+
count?: number;
|
|
85
|
+
delay?: number;
|
|
86
|
+
onMount?: boolean;
|
|
87
|
+
onFocus?: boolean;
|
|
88
|
+
interval?: { ms: number; inBackground?: boolean };
|
|
89
|
+
cb: CbAction<Schema, IP, IQ, IB, ID, IK, Array<QueryKey<IK>>>;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export interface IFetchOptions<ID, ZQ extends ZQuery = ZQuery, ZP extends ZParams = ZParams, IK extends string = string>
|
|
93
|
+
extends IBaseApi<ID, ZQ, ZP> {
|
|
94
|
+
apiType: "fetch";
|
|
95
|
+
staleTime?: TimeString | number;
|
|
96
|
+
enable?: { isEnable?: boolean; autoEnable?: boolean };
|
|
97
|
+
key?: string[];
|
|
98
|
+
refetch?: IRefetch<IK, z.output<ZQ>, z.output<ZP>, undefined, ID>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export type IPaginatedData<IData, IOther = unknown> = {
|
|
102
|
+
data: IData[];
|
|
103
|
+
other?: IOther;
|
|
104
|
+
pagination: { total: number; totalPages: number; currentPage: number; pageSize: number };
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export interface IPaginatedFetchOptions<
|
|
108
|
+
ID,
|
|
109
|
+
IO,
|
|
110
|
+
ZQ extends ZQuery = ZQuery,
|
|
111
|
+
ZP extends ZParams = ZParams,
|
|
112
|
+
IK extends string = string,
|
|
113
|
+
> extends IBaseApi<ID, ZQ, ZP> {
|
|
114
|
+
apiType: "paginated-fetch";
|
|
115
|
+
staleTime?: TimeString | number;
|
|
116
|
+
enable?: { isEnable?: boolean; autoEnable?: boolean };
|
|
117
|
+
other?: IO;
|
|
118
|
+
key?: string[];
|
|
119
|
+
refetch?: IRefetch<IK, z.output<ZQ>, z.output<ZP>, undefined, IPaginatedData<ID, IO>>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface IFetchMutationOptions<
|
|
123
|
+
IK extends string,
|
|
124
|
+
ZQ extends ZQuery = ZQuery,
|
|
125
|
+
ZB extends ZodTypeAny = ZodTypeAny,
|
|
126
|
+
ZP extends ZParams = ZParams,
|
|
127
|
+
ID = unknown,
|
|
128
|
+
> extends IBaseApi<ID, ZQ, ZP> {
|
|
129
|
+
apiType: "mutation";
|
|
130
|
+
isFormData?: boolean;
|
|
131
|
+
validateBody?: boolean;
|
|
132
|
+
staleTime?: TimeString | number;
|
|
133
|
+
zBody?: ZB;
|
|
134
|
+
onSuccess?: QueryEvent<IK, z.output<ZQ>, z.output<ZP>, z.output<ZB>, ID>;
|
|
135
|
+
onFetch?: QueryEvent<IK, z.output<ZQ>, z.output<ZP>, z.output<ZB>, ID>;
|
|
136
|
+
onError?: QueryEvent<IK, z.output<ZQ>, z.output<ZP>, z.output<ZB>, AxiosError<{ message?: string }>>;
|
|
137
|
+
refetch?: IRefetch<IK, z.output<ZQ>, z.output<ZP>, z.output<ZB>, ID>;
|
|
138
|
+
}
|