@next-feature/client 0.1.1-beta.2 → 0.1.1-beta.5
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/hooks/use-api-error.d.ts +10 -0
- package/{src/index.ts → index.d.ts} +0 -2
- package/index.js +4908 -0
- package/lib/client.d.ts +94 -0
- package/lib/error.d.ts +53 -0
- package/{src/lib/types/index.ts → lib/types/index.d.ts} +4 -5
- package/lib/utils/error.d.ts +26 -0
- package/lib/utils/helper.d.ts +8 -0
- package/package.json +1 -1
- package/server.d.ts +0 -0
- package/server.js +1 -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/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
package/lib/client.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { AxiosInstance, AxiosRequestConfig, InternalAxiosRequestConfig } from 'axios';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration options for the API client
|
|
4
|
+
*/
|
|
5
|
+
export interface ApiClientConfig {
|
|
6
|
+
baseURL: string;
|
|
7
|
+
timeout?: number;
|
|
8
|
+
enableRefreshToken?: boolean;
|
|
9
|
+
maxRetries?: number;
|
|
10
|
+
retryDelay?: number;
|
|
11
|
+
onAuthenticated?: (config: InternalAxiosRequestConfig) => void | Promise<void>;
|
|
12
|
+
onUnauthorized?: () => void | Promise<void>;
|
|
13
|
+
onRefreshTokenExpired?: () => void | Promise<void>;
|
|
14
|
+
onRefreshToken?: () => string | Promise<string>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Axios wrapper with JWT/Refresh token handling
|
|
18
|
+
*/
|
|
19
|
+
export declare class ApiClient {
|
|
20
|
+
private readonly instance;
|
|
21
|
+
private isRefreshing;
|
|
22
|
+
private pendingRequests;
|
|
23
|
+
private config;
|
|
24
|
+
constructor(config: ApiClientConfig);
|
|
25
|
+
/**
|
|
26
|
+
* Setup request and response interceptors
|
|
27
|
+
*/
|
|
28
|
+
private setupInterceptors;
|
|
29
|
+
/**
|
|
30
|
+
* Attach JWT token to request headers
|
|
31
|
+
*/
|
|
32
|
+
private handleRequestFulfilled;
|
|
33
|
+
/**
|
|
34
|
+
* Handle request errors
|
|
35
|
+
*/
|
|
36
|
+
private handleRequestRejected;
|
|
37
|
+
/**
|
|
38
|
+
* Pass through successful responses
|
|
39
|
+
*/
|
|
40
|
+
private handleResponseFulfilled;
|
|
41
|
+
/**
|
|
42
|
+
* Handle response errors with retry logic and token refresh
|
|
43
|
+
*/
|
|
44
|
+
private handleResponseRejected;
|
|
45
|
+
/**
|
|
46
|
+
* Handle 401 errors with token refresh
|
|
47
|
+
*/
|
|
48
|
+
private handleUnauthorizedError;
|
|
49
|
+
/**
|
|
50
|
+
* Refresh the JWT token using the refresh token
|
|
51
|
+
*/
|
|
52
|
+
private refreshToken;
|
|
53
|
+
/**
|
|
54
|
+
* Update session with new token
|
|
55
|
+
* Implement this based on your Next.js auth setup
|
|
56
|
+
*/
|
|
57
|
+
private updateSession;
|
|
58
|
+
/**
|
|
59
|
+
* Process all pending requests after token refresh
|
|
60
|
+
*/
|
|
61
|
+
private processPendingRequests;
|
|
62
|
+
/**
|
|
63
|
+
* Determine if request should be retried
|
|
64
|
+
*/
|
|
65
|
+
private shouldRetry;
|
|
66
|
+
/**
|
|
67
|
+
* Retry failed request with exponential backoff
|
|
68
|
+
*/
|
|
69
|
+
private retryRequest;
|
|
70
|
+
/**
|
|
71
|
+
* Sleep helper for retry delays
|
|
72
|
+
*/
|
|
73
|
+
private sleep;
|
|
74
|
+
/**
|
|
75
|
+
* Create ApiError from AxiosError
|
|
76
|
+
*/
|
|
77
|
+
private createApiError;
|
|
78
|
+
/**
|
|
79
|
+
* Extract ProblemDetail from error response
|
|
80
|
+
*/
|
|
81
|
+
private extractProblemDetail;
|
|
82
|
+
/**
|
|
83
|
+
* HTTP Methods with proper typing
|
|
84
|
+
*/
|
|
85
|
+
get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
|
|
86
|
+
post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
|
|
87
|
+
put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
|
|
88
|
+
patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
|
|
89
|
+
delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
|
|
90
|
+
/**
|
|
91
|
+
* Get the underlying Axios instance for advanced usage
|
|
92
|
+
*/
|
|
93
|
+
getAxiosInstance(): AxiosInstance;
|
|
94
|
+
}
|
package/lib/error.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { HttpStatusCode } from 'axios';
|
|
2
|
+
import { ZodError } from 'zod';
|
|
3
|
+
/**
|
|
4
|
+
* Spring Boot ProblemDetail structure
|
|
5
|
+
*/
|
|
6
|
+
export interface ProblemDetail {
|
|
7
|
+
type: string;
|
|
8
|
+
title: string;
|
|
9
|
+
status: HttpStatusCode;
|
|
10
|
+
detail?: string;
|
|
11
|
+
instance?: string;
|
|
12
|
+
errors?: Record<string, unknown>;
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Custom error class for API errors
|
|
17
|
+
*/
|
|
18
|
+
export declare class ApiError extends Error {
|
|
19
|
+
status: HttpStatusCode;
|
|
20
|
+
problemDetail: ProblemDetail | null;
|
|
21
|
+
originalError?: Error;
|
|
22
|
+
constructor(status: HttpStatusCode, problemDetail: ProblemDetail | null, originalError?: Error, message?: string);
|
|
23
|
+
get isClientError(): boolean;
|
|
24
|
+
get isServerError(): boolean;
|
|
25
|
+
get isUnauthorized(): boolean;
|
|
26
|
+
get isForbidden(): boolean;
|
|
27
|
+
get isNotFound(): boolean;
|
|
28
|
+
static builder<T>(): ApiErrorBuilder;
|
|
29
|
+
static of(error: Error | unknown): ApiError;
|
|
30
|
+
/**
|
|
31
|
+
* Create ApiError from Zod validation error
|
|
32
|
+
*/
|
|
33
|
+
static fromZodError(zodError: ZodError): ApiError;
|
|
34
|
+
}
|
|
35
|
+
export declare class ApiErrorBuilder<AdditionalProblemDetails = Record<string, unknown>> {
|
|
36
|
+
private _problemDetail;
|
|
37
|
+
private _status;
|
|
38
|
+
private _originalError;
|
|
39
|
+
private _message;
|
|
40
|
+
constructor();
|
|
41
|
+
/**
|
|
42
|
+
* Set standard ProblemDetail fields
|
|
43
|
+
*/
|
|
44
|
+
detail<K extends keyof (ProblemDetail & AdditionalProblemDetails)>(key: K, value: K extends keyof ProblemDetail ? ProblemDetail[K] : K extends keyof AdditionalProblemDetails ? AdditionalProblemDetails[K] : unknown): ApiErrorBuilder<AdditionalProblemDetails>;
|
|
45
|
+
/**
|
|
46
|
+
* Set standard ProblemDetail fields
|
|
47
|
+
*/
|
|
48
|
+
problemDetail(problemDetail: ProblemDetail): ApiErrorBuilder<AdditionalProblemDetails>;
|
|
49
|
+
originalError(error: Error): ApiErrorBuilder<AdditionalProblemDetails>;
|
|
50
|
+
status(status: HttpStatusCode): ApiErrorBuilder<AdditionalProblemDetails>;
|
|
51
|
+
message(msg: string): ApiErrorBuilder<AdditionalProblemDetails>;
|
|
52
|
+
build(): ApiError;
|
|
53
|
+
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { ProblemDetail } from '../error';
|
|
2
|
-
|
|
3
2
|
/**
|
|
4
3
|
* [api-response]
|
|
5
4
|
* next-feature@0.0.11-beta
|
|
6
5
|
* November 4th 2025, 6:37:27 pm
|
|
7
6
|
*/
|
|
8
7
|
export interface ApiResponse<Response> {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
success?: boolean;
|
|
9
|
+
message?: string;
|
|
10
|
+
error?: ProblemDetail | null;
|
|
11
|
+
data: Response;
|
|
13
12
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
* Format ProblemDetail for display
|
|
13
|
+
*/
|
|
14
|
+
export declare function formatProblemDetail(error: ApiError): string;
|
|
15
|
+
/**
|
|
16
|
+
* Check if error is a specific HTTP status
|
|
17
|
+
*/
|
|
18
|
+
export declare function isHttpStatus(error: unknown, status: number): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Handle common API errors
|
|
21
|
+
*/
|
|
22
|
+
export declare function handleApiError(error: unknown): void;
|
|
23
|
+
/**
|
|
24
|
+
* Validation error extractor for Spring Boot validation errors
|
|
25
|
+
*/
|
|
26
|
+
export declare function extractValidationErrors(error: ApiError): Record<string, string[]> | null;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ProblemDetail } from '../error';
|
|
2
|
+
/**
|
|
3
|
+
* [show-error-toast]
|
|
4
|
+
* next-feature@0.0.11-beta
|
|
5
|
+
* November 4th 2025, 11:58:52 am
|
|
6
|
+
*/
|
|
7
|
+
export declare function showErrorToast(data: any): any;
|
|
8
|
+
export declare function getFieldError(error: ProblemDetail | undefined, fieldName: string): string | undefined;
|
package/package.json
CHANGED
package/server.d.ts
ADDED
|
File without changes
|
package/server.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
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
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useState, useCallback } from 'react';
|
|
4
|
-
import { ApiError } from '../lib/error';
|
|
5
|
-
import { getErrorMessage } from '../lib/utils/error';
|
|
6
|
-
|
|
7
|
-
interface UseApiErrorResult {
|
|
8
|
-
error: ApiError | null;
|
|
9
|
-
setError: (error: ApiError | null) => void;
|
|
10
|
-
clearError: () => void;
|
|
11
|
-
handleError: (error: unknown) => void;
|
|
12
|
-
errorMessage: string | null;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function useApiError(): UseApiErrorResult {
|
|
16
|
-
const [error, setError] = useState<ApiError | null>(null);
|
|
17
|
-
|
|
18
|
-
const clearError = useCallback(() => {
|
|
19
|
-
setError(null);
|
|
20
|
-
}, []);
|
|
21
|
-
|
|
22
|
-
const handleError = useCallback((err: unknown) => {
|
|
23
|
-
if (err instanceof ApiError) {
|
|
24
|
-
setError(err);
|
|
25
|
-
} else {
|
|
26
|
-
console.error('Non-API error:', err);
|
|
27
|
-
}
|
|
28
|
-
}, []);
|
|
29
|
-
|
|
30
|
-
const errorMessage = error ? getErrorMessage(error) : null;
|
|
31
|
-
|
|
32
|
-
return {
|
|
33
|
-
error,
|
|
34
|
-
setError,
|
|
35
|
-
clearError,
|
|
36
|
-
handleError,
|
|
37
|
-
errorMessage,
|
|
38
|
-
};
|
|
39
|
-
}
|