@next-feature/client 0.1.1 → 0.1.2-10
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/components/api-error-boundary.d.ts +18 -0
- package/error-CN4QrYXy.js +4792 -0
- package/hooks/use-api-error.d.ts +10 -0
- package/{src/index.ts → index.d.ts} +6 -6
- package/index.js +195 -0
- package/lib/actions/with-api.d.ts +7 -0
- package/lib/client.d.ts +72 -0
- package/lib/config/env.d.ts +20 -0
- package/lib/error.d.ts +35 -0
- package/lib/types/client.d.ts +24 -0
- package/lib/types/index.d.ts +27 -0
- package/lib/utils/axios.d.ts +7 -0
- package/lib/utils/error.d.ts +19 -0
- package/lib/utils/zod.d.ts +10 -0
- package/package.json +10 -7
- package/server.d.ts +1 -0
- package/server.js +29 -0
- package/.babelrc +0 -12
- package/eslint.config.mjs +0 -12
- package/jest.config.cts +0 -10
- package/project.json +0 -32
- package/src/components/api-error-boundary.tsx +0 -58
- package/src/hooks/use-api-error.tsx +0 -39
- package/src/lib/client.ts +0 -431
- package/src/lib/error.ts +0 -169
- package/src/lib/types/index.ts +0 -13
- package/src/lib/utils/error.ts +0 -136
- package/src/lib/utils/helper.ts +0 -20
- package/tsconfig.json +0 -20
- package/tsconfig.lib.json +0 -29
- package/tsconfig.spec.json +0 -23
- package/vite.config.ts +0 -51
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ApiError } from '../lib/error';
|
|
2
|
+
interface UseApiErrorResult {
|
|
3
|
+
error: ApiError | null;
|
|
4
|
+
setError: (error: ApiError | null) => void;
|
|
5
|
+
clearError: () => void;
|
|
6
|
+
handleError: (error: unknown) => void;
|
|
7
|
+
errorMessage: string | null;
|
|
8
|
+
}
|
|
9
|
+
export declare function useApiError(): UseApiErrorResult;
|
|
10
|
+
export {};
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
* - Customize src/hooks/use-api-error.tsx
|
|
22
22
|
* - Customize src/components/api-error-boundary.tsx
|
|
23
23
|
*/
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
export
|
|
27
|
-
export
|
|
28
|
-
export * from './lib/
|
|
24
|
+
export { ApiClient } from './lib/client';
|
|
25
|
+
export { ApiError } from './lib/error';
|
|
26
|
+
export * from './lib/types/client';
|
|
27
|
+
export * from './lib/types/index';
|
|
28
|
+
export * from './lib/utils/axios';
|
|
29
29
|
export * from './lib/utils/error';
|
|
30
|
-
export * from './lib/utils/
|
|
30
|
+
export * from './lib/utils/zod';
|
package/index.js
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { z as i, a, h as r } from "./error-CN4QrYXy.js";
|
|
2
|
+
import { A as m, g as E, b as k, c as w, i as A } from "./error-CN4QrYXy.js";
|
|
3
|
+
i.object({
|
|
4
|
+
NODE_ENV: i.enum(["development", "production"]),
|
|
5
|
+
CLIENT_API_URL: i.string().url()
|
|
6
|
+
});
|
|
7
|
+
process.env.NODE_ENV;
|
|
8
|
+
const h = process.env.CLIENT_API_URL, c = "@next-feature/client", u = "0.1.2-6", p = {
|
|
9
|
+
name: c,
|
|
10
|
+
version: u
|
|
11
|
+
}, { name: o, version: d } = p;
|
|
12
|
+
class R {
|
|
13
|
+
constructor(e) {
|
|
14
|
+
this.isRefreshing = !1, this.pendingRequests = [], this.config = {
|
|
15
|
+
baseURL: h,
|
|
16
|
+
timeout: 3e4,
|
|
17
|
+
enableRefreshToken: !1,
|
|
18
|
+
maxRetries: 1,
|
|
19
|
+
retryDelay: 1e3,
|
|
20
|
+
skipRefreshPaths: [],
|
|
21
|
+
onUnauthorized: async () => {
|
|
22
|
+
console.log(`[${o}] Unauthorized`);
|
|
23
|
+
},
|
|
24
|
+
onRefreshTokenExpired: async () => {
|
|
25
|
+
console.log("Refresh token expired");
|
|
26
|
+
},
|
|
27
|
+
onAuthenticated: async (t) => {
|
|
28
|
+
console.log(
|
|
29
|
+
`[${o}]`,
|
|
30
|
+
t.method?.toUpperCase(),
|
|
31
|
+
t.url,
|
|
32
|
+
t.data ?? ""
|
|
33
|
+
);
|
|
34
|
+
},
|
|
35
|
+
onRefreshToken: async () => "",
|
|
36
|
+
...e
|
|
37
|
+
}, this.instance = a.create({
|
|
38
|
+
baseURL: this.config.baseURL,
|
|
39
|
+
timeout: this.config.timeout,
|
|
40
|
+
headers: {
|
|
41
|
+
"Content-Type": "application/json",
|
|
42
|
+
"User-Agent": `${o}:${d}`
|
|
43
|
+
}
|
|
44
|
+
}), this.setupInterceptors();
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Setup request and response interceptors
|
|
48
|
+
*/
|
|
49
|
+
setupInterceptors() {
|
|
50
|
+
this.instance.interceptors.request.use(
|
|
51
|
+
this.handleRequestFulfilled.bind(this),
|
|
52
|
+
this.handleRequestRejected.bind(this)
|
|
53
|
+
), this.instance.interceptors.response.use(
|
|
54
|
+
this.handleResponseFulfilled.bind(this),
|
|
55
|
+
this.handleResponseRejected.bind(this)
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Attach JWT token to request headers
|
|
60
|
+
*/
|
|
61
|
+
async handleRequestFulfilled(e) {
|
|
62
|
+
try {
|
|
63
|
+
return this.config.onAuthenticated && await this.config.onAuthenticated(e), e;
|
|
64
|
+
} catch (t) {
|
|
65
|
+
return console.error("onAuthenticated error on request:", t), e;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Handle request errors
|
|
70
|
+
*/
|
|
71
|
+
handleRequestRejected(e) {
|
|
72
|
+
return console.error("Request configuration error:", e), Promise.reject(e);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Pass through successful responses
|
|
76
|
+
*/
|
|
77
|
+
handleResponseFulfilled(e) {
|
|
78
|
+
return e;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Check if a path should skip the refresh token logic
|
|
82
|
+
*/
|
|
83
|
+
shouldSkipRefresh(e) {
|
|
84
|
+
return !e || !this.config.skipRefreshPaths?.length ? !1 : this.config.skipRefreshPaths.some((t) => t.test(e));
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Handle response errors with retry logic and token refresh
|
|
88
|
+
*/
|
|
89
|
+
async handleResponseRejected(e) {
|
|
90
|
+
const t = e.config;
|
|
91
|
+
return t ? e.response?.status === 401 && this.config.enableRefreshToken && !this.shouldSkipRefresh(t.url) ? this.handleUnauthorizedError(e, t) : this.shouldRetry(e, t) ? this.retryRequest(t) : Promise.reject(r(e)) : Promise.reject(r(e));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Handle 401 errors with token refresh
|
|
95
|
+
*/
|
|
96
|
+
async handleUnauthorizedError(e, t) {
|
|
97
|
+
if (t._retry)
|
|
98
|
+
return this.config.onRefreshTokenExpired && await this.config.onRefreshTokenExpired(), Promise.reject(r(e));
|
|
99
|
+
if (t._retry = !0, this.isRefreshing)
|
|
100
|
+
return new Promise((s, n) => {
|
|
101
|
+
this.pendingRequests.push({ resolve: s, reject: n });
|
|
102
|
+
}).then((s) => (t.headers.Authorization = `Bearer ${s}`, this.instance(t))).catch((s) => Promise.reject(r(s)));
|
|
103
|
+
this.isRefreshing = !0;
|
|
104
|
+
try {
|
|
105
|
+
const s = await this.refreshToken(t);
|
|
106
|
+
return t.headers.Authorization = `Bearer ${s}`, this.processPendingRequests(null, s), this.instance(t);
|
|
107
|
+
} catch (s) {
|
|
108
|
+
return this.processPendingRequests(s, null), this.config.onUnauthorized && await this.config.onUnauthorized(), Promise.reject(r(e));
|
|
109
|
+
} finally {
|
|
110
|
+
this.isRefreshing = !1;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Refresh the JWT token using the refresh token
|
|
115
|
+
*/
|
|
116
|
+
async refreshToken(e) {
|
|
117
|
+
try {
|
|
118
|
+
const t = await this.config.onRefreshToken();
|
|
119
|
+
return await this.post("/api/auth/refresh", {
|
|
120
|
+
refreshToken: t,
|
|
121
|
+
headers: e.headers
|
|
122
|
+
});
|
|
123
|
+
} catch (t) {
|
|
124
|
+
throw console.error("Token refresh failed:", t), t;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Process all pending requests after token refresh
|
|
129
|
+
*/
|
|
130
|
+
processPendingRequests(e, t) {
|
|
131
|
+
this.pendingRequests.forEach((s) => {
|
|
132
|
+
e ? s.reject(e) : t && s.resolve(t);
|
|
133
|
+
}), this.pendingRequests = [];
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Determine if request should be retried
|
|
137
|
+
*/
|
|
138
|
+
shouldRetry(e, t) {
|
|
139
|
+
if ((t._retryCount || 0) >= this.config.maxRetries)
|
|
140
|
+
return !1;
|
|
141
|
+
if (!e.response)
|
|
142
|
+
return !0;
|
|
143
|
+
const n = e.response.status;
|
|
144
|
+
return n >= 500 && n !== 501 || n === 429;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Retry failed request with exponential backoff
|
|
148
|
+
*/
|
|
149
|
+
async retryRequest(e) {
|
|
150
|
+
e._retryCount = (e._retryCount || 0) + 1;
|
|
151
|
+
const t = this.config.retryDelay * Math.pow(2, e._retryCount - 1);
|
|
152
|
+
return await this.sleep(t), console.log(
|
|
153
|
+
`Retrying request (attempt ${e._retryCount}):`,
|
|
154
|
+
e.url
|
|
155
|
+
), this.instance(e);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Sleep helper for retry delays
|
|
159
|
+
*/
|
|
160
|
+
sleep(e) {
|
|
161
|
+
return new Promise((t) => setTimeout(t, e));
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* HTTP Methods with proper typing
|
|
165
|
+
*/
|
|
166
|
+
async get(e, t) {
|
|
167
|
+
return (await this.instance.get(e, t)).data;
|
|
168
|
+
}
|
|
169
|
+
async post(e, t, s) {
|
|
170
|
+
return (await this.instance.post(e, t, s)).data;
|
|
171
|
+
}
|
|
172
|
+
async put(e, t, s) {
|
|
173
|
+
return (await this.instance.put(e, t, s)).data;
|
|
174
|
+
}
|
|
175
|
+
async patch(e, t, s) {
|
|
176
|
+
return (await this.instance.patch(e, t, s)).data;
|
|
177
|
+
}
|
|
178
|
+
async delete(e, t) {
|
|
179
|
+
return (await this.instance.delete(e, t)).data;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get the underlying Axios instance for advanced usage
|
|
183
|
+
*/
|
|
184
|
+
getAxiosInstance() {
|
|
185
|
+
return this.instance;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
export {
|
|
189
|
+
R as ApiClient,
|
|
190
|
+
m as ApiError,
|
|
191
|
+
E as getErrorMessage,
|
|
192
|
+
k as handleAxiosError,
|
|
193
|
+
w as handleZodError,
|
|
194
|
+
A as isHttpStatus
|
|
195
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ApiResponse } from '../types';
|
|
2
|
+
interface WithApiOptions<T> {
|
|
3
|
+
fallbackData?: T;
|
|
4
|
+
successMessage?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function withApi<F extends (...args: any[]) => Promise<any>>(fn: F, options?: WithApiOptions<Awaited<ReturnType<F>>>): (...args: Parameters<F>) => Promise<ApiResponse<Awaited<ReturnType<F>>>>;
|
|
7
|
+
export {};
|
package/lib/client.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { AxiosInstance, AxiosRequestConfig } from 'axios';
|
|
2
|
+
import { ApiClientConfig } from './types/client';
|
|
3
|
+
/**
|
|
4
|
+
* Axios wrapper with JWT/Refresh token handling
|
|
5
|
+
*/
|
|
6
|
+
export declare class ApiClient {
|
|
7
|
+
private readonly instance;
|
|
8
|
+
private isRefreshing;
|
|
9
|
+
private pendingRequests;
|
|
10
|
+
private config;
|
|
11
|
+
constructor(config: ApiClientConfig);
|
|
12
|
+
/**
|
|
13
|
+
* Setup request and response interceptors
|
|
14
|
+
*/
|
|
15
|
+
private setupInterceptors;
|
|
16
|
+
/**
|
|
17
|
+
* Attach JWT token to request headers
|
|
18
|
+
*/
|
|
19
|
+
private handleRequestFulfilled;
|
|
20
|
+
/**
|
|
21
|
+
* Handle request errors
|
|
22
|
+
*/
|
|
23
|
+
private handleRequestRejected;
|
|
24
|
+
/**
|
|
25
|
+
* Pass through successful responses
|
|
26
|
+
*/
|
|
27
|
+
private handleResponseFulfilled;
|
|
28
|
+
/**
|
|
29
|
+
* Check if a path should skip the refresh token logic
|
|
30
|
+
*/
|
|
31
|
+
private shouldSkipRefresh;
|
|
32
|
+
/**
|
|
33
|
+
* Handle response errors with retry logic and token refresh
|
|
34
|
+
*/
|
|
35
|
+
private handleResponseRejected;
|
|
36
|
+
/**
|
|
37
|
+
* Handle 401 errors with token refresh
|
|
38
|
+
*/
|
|
39
|
+
private handleUnauthorizedError;
|
|
40
|
+
/**
|
|
41
|
+
* Refresh the JWT token using the refresh token
|
|
42
|
+
*/
|
|
43
|
+
private refreshToken;
|
|
44
|
+
/**
|
|
45
|
+
* Process all pending requests after token refresh
|
|
46
|
+
*/
|
|
47
|
+
private processPendingRequests;
|
|
48
|
+
/**
|
|
49
|
+
* Determine if request should be retried
|
|
50
|
+
*/
|
|
51
|
+
private shouldRetry;
|
|
52
|
+
/**
|
|
53
|
+
* Retry failed request with exponential backoff
|
|
54
|
+
*/
|
|
55
|
+
private retryRequest;
|
|
56
|
+
/**
|
|
57
|
+
* Sleep helper for retry delays
|
|
58
|
+
*/
|
|
59
|
+
private sleep;
|
|
60
|
+
/**
|
|
61
|
+
* HTTP Methods with proper typing
|
|
62
|
+
*/
|
|
63
|
+
get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
|
|
64
|
+
post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
|
|
65
|
+
put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
|
|
66
|
+
patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
|
|
67
|
+
delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
|
|
68
|
+
/**
|
|
69
|
+
* Get the underlying Axios instance for advanced usage
|
|
70
|
+
*/
|
|
71
|
+
getAxiosInstance(): AxiosInstance;
|
|
72
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
declare const envSchema: z.ZodObject<{
|
|
3
|
+
NODE_ENV: z.ZodEnum<["development", "production"]>;
|
|
4
|
+
CLIENT_API_URL: z.ZodString;
|
|
5
|
+
}, "strip", z.ZodTypeAny, {
|
|
6
|
+
NODE_ENV?: "development" | "production";
|
|
7
|
+
CLIENT_API_URL?: string;
|
|
8
|
+
}, {
|
|
9
|
+
NODE_ENV?: "development" | "production";
|
|
10
|
+
CLIENT_API_URL?: string;
|
|
11
|
+
}>;
|
|
12
|
+
declare global {
|
|
13
|
+
namespace NodeJS {
|
|
14
|
+
interface ProcessEnv extends z.infer<typeof envSchema> {
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export declare const NODE_ENV: "development" | "production" | "test";
|
|
19
|
+
export declare const BACKEND_API_URL: string;
|
|
20
|
+
export {};
|
package/lib/error.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { HttpStatusCode } from 'axios';
|
|
2
|
+
import { ProblemDetail } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Custom error class for API errors
|
|
5
|
+
*/
|
|
6
|
+
export declare class ApiError extends Error {
|
|
7
|
+
problemDetail: ProblemDetail;
|
|
8
|
+
originalError?: Error;
|
|
9
|
+
constructor(problemDetail: ProblemDetail, originalError?: Error);
|
|
10
|
+
get status(): HttpStatusCode;
|
|
11
|
+
get body(): ProblemDetail;
|
|
12
|
+
get isClientError(): boolean;
|
|
13
|
+
get isServerError(): boolean;
|
|
14
|
+
get isUnauthorized(): boolean;
|
|
15
|
+
get isForbidden(): boolean;
|
|
16
|
+
get isNotFound(): boolean;
|
|
17
|
+
static builder(): ApiErrorBuilder;
|
|
18
|
+
static of(error: Error | unknown): ApiError;
|
|
19
|
+
}
|
|
20
|
+
export declare class ApiErrorBuilder {
|
|
21
|
+
private _originalError;
|
|
22
|
+
private _problemDetail;
|
|
23
|
+
constructor(_originalError: Error | null);
|
|
24
|
+
/**
|
|
25
|
+
* Set standard ProblemDetail
|
|
26
|
+
*/
|
|
27
|
+
problemDetail(problemDetail: ProblemDetail): ApiErrorBuilder;
|
|
28
|
+
originalError(error: Error): ApiErrorBuilder;
|
|
29
|
+
message(msg: string): ApiErrorBuilder;
|
|
30
|
+
status(status: HttpStatusCode): ApiErrorBuilder;
|
|
31
|
+
errors(errors: Record<string, string>): ApiErrorBuilder;
|
|
32
|
+
title(title: string): ApiErrorBuilder;
|
|
33
|
+
instance(instance: string): ApiErrorBuilder;
|
|
34
|
+
build(): ApiError;
|
|
35
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { InternalAxiosRequestConfig } from 'axios';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration options for the API client
|
|
4
|
+
*
|
|
5
|
+
* [api-client-config]
|
|
6
|
+
* next-feature@0.1.1-beta.5
|
|
7
|
+
* January 11th 2026, 9:00:22 pm
|
|
8
|
+
*/
|
|
9
|
+
export interface ApiClientConfig {
|
|
10
|
+
baseURL: string;
|
|
11
|
+
timeout?: number;
|
|
12
|
+
enableRefreshToken?: boolean;
|
|
13
|
+
maxRetries?: number;
|
|
14
|
+
retryDelay?: number;
|
|
15
|
+
/**
|
|
16
|
+
* Paths that should skip the refresh token logic on 401.
|
|
17
|
+
* Useful for login/register endpoints that expect 401 as a valid response.
|
|
18
|
+
*/
|
|
19
|
+
skipRefreshPaths?: RegExp[];
|
|
20
|
+
onAuthenticated?: (config: InternalAxiosRequestConfig) => void | Promise<void>;
|
|
21
|
+
onUnauthorized?: () => void | Promise<void>;
|
|
22
|
+
onRefreshTokenExpired?: () => void | Promise<void>;
|
|
23
|
+
onRefreshToken?: () => string | Promise<string>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { HttpStatusCode } from 'axios';
|
|
2
|
+
/**
|
|
3
|
+
* [api-response]
|
|
4
|
+
* next-feature@0.0.11-beta
|
|
5
|
+
* November 4th 2025, 6:37:27 pm
|
|
6
|
+
*/
|
|
7
|
+
export interface ApiResponse<Response> {
|
|
8
|
+
success?: boolean;
|
|
9
|
+
message?: string;
|
|
10
|
+
error?: ProblemDetail;
|
|
11
|
+
data: Response;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Spring Boot ProblemDetail structure
|
|
15
|
+
*
|
|
16
|
+
* [problem-detail]
|
|
17
|
+
* next-feature@0.1.1-beta.5
|
|
18
|
+
* January 11th 2026, 8:55:50 pm
|
|
19
|
+
*/
|
|
20
|
+
export interface ProblemDetail {
|
|
21
|
+
type: string;
|
|
22
|
+
title: string;
|
|
23
|
+
status: HttpStatusCode;
|
|
24
|
+
detail: string;
|
|
25
|
+
instance?: string;
|
|
26
|
+
errors?: Record<string, string>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ApiError } from '../error';
|
|
2
|
+
/**
|
|
3
|
+
* Extract user-friendly error message from ApiError
|
|
4
|
+
*
|
|
5
|
+
* [get-error-message]
|
|
6
|
+
* next-feature@0.0.11-beta
|
|
7
|
+
* November 4th 2025, 11:47:45 am
|
|
8
|
+
*
|
|
9
|
+
*/
|
|
10
|
+
export declare function getErrorMessage(error: unknown): string;
|
|
11
|
+
/**
|
|
12
|
+
* Check if error is a specific HTTP status
|
|
13
|
+
*/
|
|
14
|
+
export declare function isHttpStatus(error: unknown, status: number): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Handle common API errors
|
|
17
|
+
*/
|
|
18
|
+
declare function handleApiError(error: unknown): ApiError;
|
|
19
|
+
export default handleApiError;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ZodError } from 'zod';
|
|
2
|
+
import { ApiError } from '../error';
|
|
3
|
+
/**
|
|
4
|
+
* Create ApiError from Zod validation error
|
|
5
|
+
*
|
|
6
|
+
* [handle-zod-error]
|
|
7
|
+
* next-feature@0.1.1-beta.5
|
|
8
|
+
* January 11th 2026, 9:40:46 pm
|
|
9
|
+
*/
|
|
10
|
+
export declare function handleZodError(zodError: ZodError): ApiError;
|
package/package.json
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@next-feature/client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2-10",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"main": "./index.js",
|
|
5
6
|
"types": "./index.d.ts",
|
|
6
7
|
"exports": {
|
|
7
8
|
".": {
|
|
8
|
-
"
|
|
9
|
-
"
|
|
9
|
+
"types": "./index.d.ts",
|
|
10
|
+
"import": "./index.js",
|
|
11
|
+
"default": "./index.js"
|
|
10
12
|
},
|
|
11
13
|
"./server": {
|
|
12
|
-
"types": "./
|
|
13
|
-
"import": "./
|
|
14
|
-
"default": "./
|
|
15
|
-
}
|
|
14
|
+
"types": "./server.d.ts",
|
|
15
|
+
"import": "./server.js",
|
|
16
|
+
"default": "./server.js"
|
|
17
|
+
},
|
|
18
|
+
"./package.json": "./package.json"
|
|
16
19
|
}
|
|
17
20
|
}
|
package/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lib/actions/with-api';
|
package/server.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { A as o } from "./error-CN4QrYXy.js";
|
|
2
|
+
function u(r, c) {
|
|
3
|
+
const e = {
|
|
4
|
+
fallbackData: {},
|
|
5
|
+
successMessage: "success",
|
|
6
|
+
...c
|
|
7
|
+
};
|
|
8
|
+
return async (...t) => {
|
|
9
|
+
try {
|
|
10
|
+
const s = await r(...t);
|
|
11
|
+
return {
|
|
12
|
+
success: !0,
|
|
13
|
+
message: e.successMessage,
|
|
14
|
+
data: s
|
|
15
|
+
};
|
|
16
|
+
} catch (s) {
|
|
17
|
+
const a = o.of(s);
|
|
18
|
+
return {
|
|
19
|
+
data: e.fallbackData,
|
|
20
|
+
error: a.body,
|
|
21
|
+
message: a.message,
|
|
22
|
+
success: !1
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
u as withApi
|
|
29
|
+
};
|
package/.babelrc
DELETED
package/eslint.config.mjs
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import nx from '@nx/eslint-plugin';
|
|
2
|
-
import baseConfig from '../../eslint.config.mjs';
|
|
3
|
-
|
|
4
|
-
export default [
|
|
5
|
-
...baseConfig,
|
|
6
|
-
...nx.configs['flat/react'],
|
|
7
|
-
{
|
|
8
|
-
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
|
9
|
-
// Override or add rules here
|
|
10
|
-
rules: {},
|
|
11
|
-
},
|
|
12
|
-
];
|
package/jest.config.cts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
displayName: 'client',
|
|
3
|
-
preset: '../../jest.preset.js',
|
|
4
|
-
transform: {
|
|
5
|
-
'^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest',
|
|
6
|
-
'^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/react/babel'] }],
|
|
7
|
-
},
|
|
8
|
-
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
|
9
|
-
coverageDirectory: '../../coverage/clients/client',
|
|
10
|
-
};
|
package/project.json
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "client",
|
|
3
|
-
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
-
"sourceRoot": "clients/client/src",
|
|
5
|
-
"projectType": "library",
|
|
6
|
-
"tags": [],
|
|
7
|
-
"targets": {
|
|
8
|
-
"build": {
|
|
9
|
-
"executor": "@nx/vite:build",
|
|
10
|
-
"outputs": ["{options.outputPath}"],
|
|
11
|
-
"defaultConfiguration": "production",
|
|
12
|
-
"options": {
|
|
13
|
-
"outputPath": "dist/clients/client"
|
|
14
|
-
},
|
|
15
|
-
"configurations": {
|
|
16
|
-
"development": {
|
|
17
|
-
"mode": "development"
|
|
18
|
-
},
|
|
19
|
-
"production": {
|
|
20
|
-
"mode": "production"
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
"test": {
|
|
25
|
-
"executor": "@nx/jest:jest",
|
|
26
|
-
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
|
27
|
-
"options": {
|
|
28
|
-
"jestConfig": "clients/client/jest.config.cts"
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React, { Component, ErrorInfo, ReactNode } from 'react';
|
|
4
|
-
import { ApiError } from '../lib/error';
|
|
5
|
-
import { getErrorMessage } from '../lib/utils/error';
|
|
6
|
-
|
|
7
|
-
interface Props {
|
|
8
|
-
children: ReactNode;
|
|
9
|
-
fallback?: (error: ApiError) => ReactNode;
|
|
10
|
-
onError?: (error: Error, errorInfo: ErrorInfo) => void;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface State {
|
|
14
|
-
hasError: boolean;
|
|
15
|
-
error: Error | null;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export class ApiErrorBoundary extends Component<Props, State> {
|
|
19
|
-
constructor(props: Props) {
|
|
20
|
-
super(props);
|
|
21
|
-
this.state = { hasError: false, error: null };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
static getDerivedStateFromError(error: Error): State {
|
|
25
|
-
return { hasError: true, error };
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
|
29
|
-
console.error('ApiErrorBoundary caught error:', error, errorInfo);
|
|
30
|
-
|
|
31
|
-
if (this.props.onError) {
|
|
32
|
-
this.props.onError(error, errorInfo);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
render() {
|
|
37
|
-
if (this.state.hasError && this.state.error) {
|
|
38
|
-
if (this.state.error instanceof ApiError && this.props.fallback) {
|
|
39
|
-
return this.props.fallback(this.state.error);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Default error UI
|
|
43
|
-
return (
|
|
44
|
-
<div className="error-container">
|
|
45
|
-
<h2>Something went wrong</h2>
|
|
46
|
-
<p>{getErrorMessage(this.state.error)}</p>
|
|
47
|
-
<button
|
|
48
|
-
onClick={() => this.setState({ hasError: false, error: null })}
|
|
49
|
-
>
|
|
50
|
-
Try again
|
|
51
|
-
</button>
|
|
52
|
-
</div>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return this.props.children;
|
|
57
|
-
}
|
|
58
|
-
}
|