@next-feature/client 0.1.0-beta → 0.1.1

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/src/lib/error.ts CHANGED
@@ -1,166 +1,169 @@
1
- import { AxiosError, HttpStatusCode } from 'axios';
2
- import { ZodError } from 'zod';
3
- import { CredentialsSignin } from 'next-auth';
4
-
5
- /**
6
- * Spring Boot ProblemDetail structure
7
- */
8
- export interface ProblemDetail {
9
- type: string;
10
- title: string;
11
- status: HttpStatusCode;
12
- detail?: string;
13
- instance?: string;
14
- errors?: Record<string, unknown>;
15
- [key: string]: unknown;
16
- }
17
-
18
- /**
19
- * Custom error class for API errors
20
- */
21
- export class ApiError extends Error {
22
- constructor(
23
- public status: HttpStatusCode,
24
- public problemDetail: ProblemDetail | null,
25
- public originalError: Error,
26
- message?: string
27
- ) {
28
- super(
29
- message ||
30
- problemDetail?.detail ||
31
- problemDetail?.title ||
32
- originalError?.message ||
33
- 'An error occurred'
34
- );
35
- this.name = 'ApiError';
36
- Object.setPrototypeOf(this, ApiError.prototype);
37
- }
38
-
39
- get isClientError(): boolean {
40
- return this.status >= 400 && this.status < 500;
41
- }
42
-
43
- get isServerError(): boolean {
44
- return this.status >= 500;
45
- }
46
-
47
- get isUnauthorized(): boolean {
48
- return this.status === 401;
49
- }
50
-
51
- get isForbidden(): boolean {
52
- return this.status === 403;
53
- }
54
-
55
- get isNotFound(): boolean {
56
- return this.status === 404;
57
- }
58
-
59
- static builder<T>(): ApiErrorBuilder {
60
- return new ApiErrorBuilder<T>();
61
- }
62
-
63
- static of(error: Error) {
64
- if (error instanceof ApiError) {
65
- return error;
66
- }
67
-
68
- return ApiError.builder().originalError(error).build();
69
- }
70
-
71
- /**
72
- * Create ApiError from Zod validation error
73
- */
74
- static fromZodError(zodError: ZodError) {
75
- const errors: Record<string, string> = {};
76
-
77
- zodError.errors.forEach((error) => {
78
- error.path.forEach((path) => {
79
- errors[path] = error.message;
80
- })
81
- })
82
-
83
- return ApiError.builder()
84
- .originalError(zodError)
85
- .status(HttpStatusCode.BadRequest)
86
- .message('Validation error')
87
- .problemDetail('errors', errors)
88
- .build();
89
- }
90
- }
91
-
92
- export class ApiErrorBuilder<
93
- AdditionalProblemDetails = Record<string, unknown>
94
- > {
95
- private readonly _problemDetail: ProblemDetail;
96
- private _status: HttpStatusCode;
97
- private _originalError: Error;
98
- private _message: string;
99
-
100
- constructor() {
101
- this._status = HttpStatusCode.InternalServerError;
102
- this._problemDetail = {
103
- status: this._status,
104
- title: '',
105
- type: 'about:blank',
106
- };
107
- }
108
-
109
- /**
110
- * Set standard ProblemDetail fields
111
- */
112
- problemDetail<K extends keyof (ProblemDetail & AdditionalProblemDetails)>(
113
- key: K,
114
- value: K extends keyof ProblemDetail
115
- ? ProblemDetail[K]
116
- : K extends keyof AdditionalProblemDetails
117
- ? AdditionalProblemDetails[K]
118
- : unknown
119
- ): ApiErrorBuilder<AdditionalProblemDetails> {
120
- (this._problemDetail as any)[key] = value;
121
- return this;
122
- }
123
-
124
- originalError(error: Error): ApiErrorBuilder<AdditionalProblemDetails> {
125
- this._originalError = error;
126
- this._problemDetail.title = error.name;
127
-
128
- if (error instanceof AxiosError) {
129
- this.status(error.status);
130
- }
131
- if (error instanceof ZodError) {
132
- this.status(HttpStatusCode.BadRequest);
133
- this.message('Validation error');
134
- }
135
- return this;
136
- }
137
-
138
- status(status: HttpStatusCode): ApiErrorBuilder<AdditionalProblemDetails> {
139
- this._status = status;
140
- this._problemDetail.status = status;
141
- return this;
142
- }
143
-
144
- message(msg: string): ApiErrorBuilder<AdditionalProblemDetails> {
145
- this._message = msg;
146
- this._problemDetail.detail = msg;
147
- return this;
148
- }
149
-
150
- build(): ApiError {
151
- return new ApiError(
152
- this._status,
153
- this._problemDetail,
154
- this._originalError,
155
- this._message
156
- );
157
- }
158
- }
159
-
160
- export class CredentialsApiError extends CredentialsSignin {
161
- constructor(public readonly problemDetail: ProblemDetail) {
162
- super();
163
- this.code = problemDetail.title;
164
- this.message = problemDetail.detail;
165
- }
166
- }
1
+ import { AxiosError, HttpStatusCode } from 'axios';
2
+ import { ZodError } from 'zod';
3
+
4
+ /**
5
+ * Spring Boot ProblemDetail structure
6
+ */
7
+ export interface ProblemDetail {
8
+ type: string;
9
+ title: string;
10
+ status: HttpStatusCode;
11
+ detail?: string;
12
+ instance?: string;
13
+ errors?: Record<string, unknown>;
14
+ [key: string]: unknown;
15
+ }
16
+
17
+ /**
18
+ * Custom error class for API errors
19
+ */
20
+ export class ApiError extends Error {
21
+ constructor(
22
+ public status: HttpStatusCode,
23
+ public problemDetail: ProblemDetail | null,
24
+ public originalError?: Error,
25
+ message?: string
26
+ ) {
27
+ super(
28
+ message ||
29
+ problemDetail?.detail ||
30
+ problemDetail?.title ||
31
+ originalError?.message ||
32
+ 'An error occurred'
33
+ );
34
+ this.name = 'ApiError';
35
+ Object.setPrototypeOf(this, ApiError.prototype);
36
+ }
37
+
38
+ get isClientError(): boolean {
39
+ return this.status >= 400 && this.status < 500;
40
+ }
41
+
42
+ get isServerError(): boolean {
43
+ return this.status >= 500;
44
+ }
45
+
46
+ get isUnauthorized(): boolean {
47
+ return this.status === 401;
48
+ }
49
+
50
+ get isForbidden(): boolean {
51
+ return this.status === 403;
52
+ }
53
+
54
+ get isNotFound(): boolean {
55
+ return this.status === 404;
56
+ }
57
+
58
+ static builder<T>(): ApiErrorBuilder {
59
+ return new ApiErrorBuilder<T>();
60
+ }
61
+
62
+ static of(error: Error): ApiError {
63
+ if (error instanceof ApiError) {
64
+ return error;
65
+ }
66
+
67
+ return ApiError.builder().originalError(error).build();
68
+ }
69
+
70
+ /**
71
+ * Create ApiError from Zod validation error
72
+ */
73
+ static fromZodError(zodError: ZodError) {
74
+ const errors: Record<string, string> = {};
75
+
76
+ zodError.errors.forEach((error) => {
77
+ error.path.forEach((path) => {
78
+ errors[path] = error.message;
79
+ });
80
+ });
81
+
82
+ return ApiError.builder()
83
+ .originalError(zodError)
84
+ .status(HttpStatusCode.BadRequest)
85
+ .message('Validation error')
86
+ .detail('errors', errors)
87
+ .build();
88
+ }
89
+ }
90
+
91
+ export class ApiErrorBuilder<
92
+ AdditionalProblemDetails = Record<string, unknown>
93
+ > {
94
+ private _problemDetail: ProblemDetail;
95
+ private _status: HttpStatusCode;
96
+ private _originalError: Error;
97
+ private _message: string;
98
+
99
+ constructor() {
100
+ this._status = HttpStatusCode.InternalServerError;
101
+ this._problemDetail = {
102
+ status: this._status,
103
+ title: '',
104
+ type: 'about:blank',
105
+ };
106
+ }
107
+
108
+ /**
109
+ * Set standard ProblemDetail fields
110
+ */
111
+ detail<K extends keyof (ProblemDetail & AdditionalProblemDetails)>(
112
+ key: K,
113
+ value: K extends keyof ProblemDetail
114
+ ? ProblemDetail[K]
115
+ : K extends keyof AdditionalProblemDetails
116
+ ? AdditionalProblemDetails[K]
117
+ : unknown
118
+ ): ApiErrorBuilder<AdditionalProblemDetails> {
119
+ (this._problemDetail as any)[key] = value;
120
+ return this;
121
+ }
122
+
123
+ /**
124
+ * Set standard ProblemDetail fields
125
+ */
126
+ problemDetail(
127
+ problemDetail: ProblemDetail
128
+ ): ApiErrorBuilder<AdditionalProblemDetails> {
129
+ if (problemDetail) {
130
+ this._problemDetail = problemDetail;
131
+ }
132
+ return this;
133
+ }
134
+
135
+ originalError(error: Error): ApiErrorBuilder<AdditionalProblemDetails> {
136
+ this._originalError = error;
137
+ this._problemDetail.title = error.name;
138
+
139
+ if (error instanceof AxiosError) {
140
+ this.status(error.status);
141
+ }
142
+ if (error instanceof ZodError) {
143
+ this.status(HttpStatusCode.BadRequest);
144
+ this.message('Validation error');
145
+ }
146
+ return this;
147
+ }
148
+
149
+ status(status: HttpStatusCode): ApiErrorBuilder<AdditionalProblemDetails> {
150
+ this._status = status;
151
+ this._problemDetail.status = status;
152
+ return this;
153
+ }
154
+
155
+ message(msg: string): ApiErrorBuilder<AdditionalProblemDetails> {
156
+ this._message = msg;
157
+ this._problemDetail.detail = msg;
158
+ return this;
159
+ }
160
+
161
+ build(): ApiError {
162
+ return new ApiError(
163
+ this._status,
164
+ this._problemDetail,
165
+ this._originalError,
166
+ this._message
167
+ );
168
+ }
169
+ }
@@ -1,13 +1,13 @@
1
- import { ApiError } from '../error';
2
-
3
- /**
4
- * [api-response]
5
- * next-feature@0.0.11-beta
6
- * November 4th 2025, 6:37:27 pm
7
- */
8
- export interface ApiResponse<Response> {
9
- success?: boolean;
10
- message?: string;
11
- error?: ApiError;
12
- data: Response;
13
- }
1
+ import { ProblemDetail } from '../error';
2
+
3
+ /**
4
+ * [api-response]
5
+ * next-feature@0.0.11-beta
6
+ * November 4th 2025, 6:37:27 pm
7
+ */
8
+ export interface ApiResponse<Response> {
9
+ success?: boolean;
10
+ message?: string;
11
+ error?: ProblemDetail;
12
+ data: Response;
13
+ }
@@ -1,136 +1,136 @@
1
- import { ApiError } from '../error';
2
-
3
- /**
4
- * Extract user-friendly error message from ApiError
5
- *
6
- * [get-error-message]
7
- * next-feature@0.0.11-beta
8
- * November 4th 2025, 11:47:45 am
9
- *
10
- */
11
- export function getErrorMessage(error: unknown): string {
12
- if (error instanceof ApiError) {
13
- // Use Spring Boot ProblemDetail information
14
- if (error.problemDetail?.detail) {
15
- return error.problemDetail.detail;
16
- }
17
- if (error.problemDetail?.title) {
18
- return error.problemDetail.title;
19
- }
20
- return error.message;
21
- }
22
-
23
- if (error instanceof Error) {
24
- return error.message;
25
- }
26
-
27
- return 'An unexpected error occurred';
28
- }
29
-
30
- /**
31
- * Format ProblemDetail for display
32
- */
33
- export function formatProblemDetail(error: ApiError): string {
34
- if (!error.problemDetail) {
35
- return error.message;
36
- }
37
-
38
- const parts: string[] = [];
39
-
40
- if (error.problemDetail.title) {
41
- parts.push(`Title: ${error.problemDetail.title}`);
42
- }
43
-
44
- if (error.problemDetail.detail) {
45
- parts.push(`Detail: ${error.problemDetail.detail}`);
46
- }
47
-
48
- if (error.problemDetail.instance) {
49
- parts.push(`Instance: ${error.problemDetail.instance}`);
50
- }
51
-
52
- return parts.join('\n');
53
- }
54
-
55
- /**
56
- * Check if error is a specific HTTP status
57
- */
58
- export function isHttpStatus(error: unknown, status: number): boolean {
59
- return error instanceof ApiError && error.status === status;
60
- }
61
-
62
- /**
63
- * Handle common API errors
64
- */
65
- export function handleApiError(error: unknown): void {
66
- if (!(error instanceof ApiError)) {
67
- console.error('Unexpected error:', error);
68
- return;
69
- }
70
-
71
- switch (error.status) {
72
- case 400:
73
- console.error('Bad Request:', error.problemDetail?.detail);
74
- break;
75
- case 401:
76
- console.error('Unauthorized - Please login');
77
- break;
78
- case 403:
79
- console.error('Forbidden - Access denied');
80
- break;
81
- case 404:
82
- console.error('Not Found:', error.problemDetail?.detail);
83
- break;
84
- case 409:
85
- console.error('Conflict:', error.problemDetail?.detail);
86
- break;
87
- case 422:
88
- console.error('Validation Error:', error.problemDetail);
89
- break;
90
- case 429:
91
- console.error('Too Many Requests - Please slow down');
92
- break;
93
- case 500:
94
- console.error('Server Error:', error.problemDetail?.detail);
95
- break;
96
- case 503:
97
- console.error('Service Unavailable - Please try again later');
98
- break;
99
- default:
100
- console.error(`Error ${error.status}:`, error.message);
101
- }
102
- }
103
-
104
- /**
105
- * Validation error extractor for Spring Boot validation errors
106
- */
107
- export function extractValidationErrors(
108
- error: ApiError
109
- ): Record<string, string[]> | null {
110
- if (!error.problemDetail || error.status !== 400) {
111
- return null;
112
- }
113
-
114
- // Spring Boot often includes validation errors in a 'errors' or 'fieldErrors' property
115
- const errors = error.problemDetail.errors || error.problemDetail.fieldErrors;
116
-
117
- if (!errors) {
118
- return null;
119
- }
120
-
121
- // Convert to a more usable format
122
- if (Array.isArray(errors)) {
123
- const result: Record<string, string[]> = {};
124
- errors.forEach((err: any) => {
125
- const field = err.field || err.property || 'general';
126
- const message = err.message || err.defaultMessage || 'Validation error';
127
- if (!result[field]) {
128
- result[field] = [];
129
- }
130
- result[field].push(message);
131
- });
132
- return result;
133
- }
134
-
135
- return errors as Record<string, string[]>;
136
- }
1
+ import { ApiError } from '../error';
2
+
3
+ /**
4
+ * Extract user-friendly error message from ApiError
5
+ *
6
+ * [get-error-message]
7
+ * next-feature@0.0.11-beta
8
+ * November 4th 2025, 11:47:45 am
9
+ *
10
+ */
11
+ export function getErrorMessage(error: unknown): string {
12
+ if (error instanceof ApiError) {
13
+ // Use Spring Boot ProblemDetail information
14
+ if (error.problemDetail?.detail) {
15
+ return error.problemDetail.detail;
16
+ }
17
+ if (error.problemDetail?.title) {
18
+ return error.problemDetail.title;
19
+ }
20
+ return error.message;
21
+ }
22
+
23
+ if (error instanceof Error) {
24
+ return error.message;
25
+ }
26
+
27
+ return 'An unexpected error occurred';
28
+ }
29
+
30
+ /**
31
+ * Format ProblemDetail for display
32
+ */
33
+ export function formatProblemDetail(error: ApiError): string {
34
+ if (!error.problemDetail) {
35
+ return error.message;
36
+ }
37
+
38
+ const parts: string[] = [];
39
+
40
+ if (error.problemDetail.title) {
41
+ parts.push(`Title: ${error.problemDetail.title}`);
42
+ }
43
+
44
+ if (error.problemDetail.detail) {
45
+ parts.push(`Detail: ${error.problemDetail.detail}`);
46
+ }
47
+
48
+ if (error.problemDetail.instance) {
49
+ parts.push(`Instance: ${error.problemDetail.instance}`);
50
+ }
51
+
52
+ return parts.join('\n');
53
+ }
54
+
55
+ /**
56
+ * Check if error is a specific HTTP status
57
+ */
58
+ export function isHttpStatus(error: unknown, status: number): boolean {
59
+ return error instanceof ApiError && error.status === status;
60
+ }
61
+
62
+ /**
63
+ * Handle common API errors
64
+ */
65
+ export function handleApiError(error: unknown): void {
66
+ if (!(error instanceof ApiError)) {
67
+ console.error('Unexpected error:', error);
68
+ return;
69
+ }
70
+
71
+ switch (error.status) {
72
+ case 400:
73
+ console.error('Bad Request:', error.problemDetail?.detail);
74
+ break;
75
+ case 401:
76
+ console.error('Unauthorized - Please login');
77
+ break;
78
+ case 403:
79
+ console.error('Forbidden - Access denied');
80
+ break;
81
+ case 404:
82
+ console.error('Not Found:', error.problemDetail?.detail);
83
+ break;
84
+ case 409:
85
+ console.error('Conflict:', error.problemDetail?.detail);
86
+ break;
87
+ case 422:
88
+ console.error('Validation Error:', error.problemDetail);
89
+ break;
90
+ case 429:
91
+ console.error('Too Many Requests - Please slow down');
92
+ break;
93
+ case 500:
94
+ console.error('Server Error:', error.problemDetail?.detail);
95
+ break;
96
+ case 503:
97
+ console.error('Service Unavailable - Please try again later');
98
+ break;
99
+ default:
100
+ console.error(`Error ${error.status}:`, error.message);
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Validation error extractor for Spring Boot validation errors
106
+ */
107
+ export function extractValidationErrors(
108
+ error: ApiError
109
+ ): Record<string, string[]> | null {
110
+ if (!error.problemDetail || error.status !== 400) {
111
+ return null;
112
+ }
113
+
114
+ // Spring Boot often includes validation errors in a 'errors' or 'fieldErrors' property
115
+ const errors = error.problemDetail.errors || error.problemDetail.fieldErrors;
116
+
117
+ if (!errors) {
118
+ return null;
119
+ }
120
+
121
+ // Convert to a more usable format
122
+ if (Array.isArray(errors)) {
123
+ const result: Record<string, string[]> = {};
124
+ errors.forEach((err: any) => {
125
+ const field = err.field || err.property || 'general';
126
+ const message = err.message || err.defaultMessage || 'Validation error';
127
+ if (!result[field]) {
128
+ result[field] = [];
129
+ }
130
+ result[field].push(message);
131
+ });
132
+ return result;
133
+ }
134
+
135
+ return errors as Record<string, string[]>;
136
+ }
@@ -1,20 +1,20 @@
1
- import { ProblemDetail } from '../error';
2
-
3
- /**
4
- * [show-error-toast]
5
- * next-feature@0.0.11-beta
6
- * November 4th 2025, 11:58:52 am
7
- */
8
- export function showErrorToast(data: any) {
9
- return data;
10
- }
11
-
12
- export function getFieldError(
13
- error: ProblemDetail | undefined,
14
- fieldName: string
15
- ): string | undefined {
16
- if (!error) return undefined;
17
-
18
- const errors = error.errors as Record<string, string>;
19
- return errors?.[fieldName];
20
- }
1
+ import { ProblemDetail } from '../error';
2
+
3
+ /**
4
+ * [show-error-toast]
5
+ * next-feature@0.0.11-beta
6
+ * November 4th 2025, 11:58:52 am
7
+ */
8
+ export function showErrorToast(data: any) {
9
+ return data;
10
+ }
11
+
12
+ export function getFieldError(
13
+ error: ProblemDetail | undefined,
14
+ fieldName: string
15
+ ): string | undefined {
16
+ if (!error) return undefined;
17
+
18
+ const errors = error.errors as Record<string, string>;
19
+ return errors?.[fieldName];
20
+ }