@devua-lab/error-serialization 1.0.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/README.md +134 -0
- package/dist/index.cjs +303 -0
- package/dist/index.d.cts +232 -0
- package/dist/index.d.ts +232 -0
- package/dist/index.js +273 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Error Serializer Library
|
|
2
|
+
|
|
3
|
+
A lightweight, highly extensible TypeScript library for standardized error serialization. It provides a unified pipeline to transform any errorโfrom Zod validation issues and Axios HTTP failures to native JavaScript errors and raw stringsโinto a consistent, strictly typed response format.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## ๐ Table of Contents
|
|
8
|
+
|
|
9
|
+
1. [Features](#features)
|
|
10
|
+
2. [Standardized Output Format](#standardized-output-format)
|
|
11
|
+
3. [Out-of-the-Box Functionality](#out-of-the-box-functionality)
|
|
12
|
+
- [ZodErrorPlugin](#zoderrorplugin)
|
|
13
|
+
- [AxiosErrorPlugin](#axioserrorplugin)
|
|
14
|
+
- [StandardErrorPlugin](#standarderrorplugin)
|
|
15
|
+
4. [Core Orchestration](#core-orchestration)
|
|
16
|
+
- [Priority System](#priority-system)
|
|
17
|
+
- [Subscription System (Callbacks)](#subscription-system-callbacks)
|
|
18
|
+
5. [Advanced Usage](#advanced-usage)
|
|
19
|
+
6. [Test Coverage & Reliability](#test-coverage--reliability)
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## โจ Features
|
|
24
|
+
|
|
25
|
+
- **๐ฏ Universal Standardization**: Regardless of the error source, the output always follows the same predictable interface.
|
|
26
|
+
- **๐๏ธ Structured Validation Mapping**: Advanced transformation of Zod issues into flat keys or deep object hierarchies.
|
|
27
|
+
- **๐ Generic API Extraction**: Smart parsing of backend error messages and codes from HTTP responses.
|
|
28
|
+
- **๐ Priority-Based Execution**: Automatically selects the most appropriate handler for any given error object.
|
|
29
|
+
- **๐ Real-time Subscriptions**: Lightweight callback system for global error monitoring and analytics.
|
|
30
|
+
- **๐ก๏ธ Preservation of Context**: The original error object is always preserved, allowing access to low-level details (like Axios request configs).
|
|
31
|
+
- **๐ Plugin-Driven**: Easily register new handlers for custom application-specific error classes.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## ๐ Standardized Output Format
|
|
36
|
+
|
|
37
|
+
The serialization process produces an `AppErrorResponse` object:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
{
|
|
41
|
+
metadata: {
|
|
42
|
+
plugin: string; // The plugin that successfully handled the error
|
|
43
|
+
priority: number; // Priority level of the handling plugin
|
|
44
|
+
},
|
|
45
|
+
error: unknown; // The ORIGINAL error object (preserved for logging/debugging)
|
|
46
|
+
global?: string; // A primary human-readable error message
|
|
47
|
+
code?: string[]; // Standardized error identifiers (e.g., ["102", "CONFLICT"])
|
|
48
|
+
status?: number; // Numeric status code (e.g., 422, 404, 500, or 0 for network issues)
|
|
49
|
+
validation?: { // Detailed field-level errors (primarily for Zod)
|
|
50
|
+
[key: string]: any;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## ๐ฆ Out-of-the-Box Functionality
|
|
58
|
+
|
|
59
|
+
### ZodErrorPlugin
|
|
60
|
+
Designed for `ZodError` instances. It translates complex validation issues into formats suitable for frontend state.
|
|
61
|
+
- **Status Code**: Returns `422`.
|
|
62
|
+
- **Default Code**: `["102"]`.
|
|
63
|
+
- **Flexible Pathing**: Correctly handles array indices (e.g., `list.0.name`) and nested properties.
|
|
64
|
+
- **Customizable**: Use `mapIssue` to dynamically rewrite messages or inject specific codes based on validation parameters.
|
|
65
|
+
|
|
66
|
+
### AxiosErrorPlugin
|
|
67
|
+
A generic handler for `AxiosError`. It is designed to work with virtually any backend error structure.
|
|
68
|
+
- **Message Parsing**: Scans the response body for `message` or `error.message`.
|
|
69
|
+
- **Code Parsing**: Extracts `code` or `errorCode` from response data.
|
|
70
|
+
- **Network Awareness**: Provides fallback status `0` and generic codes (e.g., `HTTP_0`) when a server response is absent (Network Error).
|
|
71
|
+
|
|
72
|
+
### StandardErrorPlugin
|
|
73
|
+
The baseline handler for native `Error` objects, ensuring even basic exceptions are standardized.
|
|
74
|
+
- **Code**: `["INTERNAL_ERROR"]`.
|
|
75
|
+
- **Message**: Uses the native `error.message`.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## โ๏ธ Core Orchestration
|
|
80
|
+
|
|
81
|
+
### Priority System
|
|
82
|
+
Plugins are ordered by priority. The `ErrorSerializer` iterates through them until it finds a match.
|
|
83
|
+
|
|
84
|
+
| Plugin | Priority | Matching Criteria |
|
|
85
|
+
|:------------------------|:---------|:---------------------------------------|
|
|
86
|
+
| **ZodErrorPlugin** | 2 | `instanceof ZodError` |
|
|
87
|
+
| **AxiosErrorPlugin** | 1 | `axios.isAxiosError(error)` |
|
|
88
|
+
| **StandardErrorPlugin** | 0 | `instanceof Error` |
|
|
89
|
+
| **FallbackError** | -1 | Fallback priority for unhandled errors |
|
|
90
|
+
|
|
91
|
+
### Subscription System (Callbacks)
|
|
92
|
+
Register callbacks to listen to every serialization event. Since the original error is preserved in the output, you can extract any context needed.
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
serializer.subscribe((context) => {
|
|
96
|
+
// Access Axios config via the original error preservation
|
|
97
|
+
if (context.metadata.plugin === 'AxiosErrorPlugin') {
|
|
98
|
+
const originalAxiosError = context.error as AxiosError;
|
|
99
|
+
console.log('Request URL:', originalAxiosError.config?.url);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Log all critical status codes
|
|
103
|
+
if (context.status && context.status >= 500) {
|
|
104
|
+
MyMonitor.log(context.global);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## ๐ Advanced Usage
|
|
112
|
+
|
|
113
|
+
### Custom Zod Mapping
|
|
114
|
+
```typescript
|
|
115
|
+
const plugin = new ZodErrorPlugin({
|
|
116
|
+
mapIssue: (issue) => {
|
|
117
|
+
if (issue.params?.apiCode) {
|
|
118
|
+
return { code: issue.params.apiCode, message: issue.message };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## ๐งช Test Coverage & Reliability
|
|
127
|
+
|
|
128
|
+
The library is fully verified with `vitest` against a wide range of scenarios:
|
|
129
|
+
- **General Backend Errors**: Extraction of custom error codes and messages from various API response formats.
|
|
130
|
+
- **Deeply Nested Validation**: Handling of paths like `['a', 0, 'b', 1, 'c']` in both flat and nested modes.
|
|
131
|
+
- **Symbol Key Safety**: Automatic filtering of `Symbol` keys in Zod paths to prevent serialization issues.
|
|
132
|
+
- **Boundary Inputs**: Graceful handling of `null`, `undefined`, and numeric inputs.
|
|
133
|
+
- **Connectivity Issues**: Accurate serialization of Axios network-level failures.
|
|
134
|
+
- **Subscription Integrity**: Confirmation that simplified callbacks receive the full `AppErrorResponse`.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AxiosErrorPlugin: () => AxiosErrorPlugin,
|
|
24
|
+
ErrorSerializer: () => ErrorSerializer,
|
|
25
|
+
StandardErrorPlugin: () => StandardErrorPlugin,
|
|
26
|
+
ZodErrorPlugin: () => ZodErrorPlugin
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
|
|
30
|
+
// src/contants.ts
|
|
31
|
+
var ErrorPriority = {
|
|
32
|
+
/**
|
|
33
|
+
* Fallback priority for unhandled errors
|
|
34
|
+
*/
|
|
35
|
+
FallbackError: -1,
|
|
36
|
+
/**
|
|
37
|
+
* Default priority for generic errors
|
|
38
|
+
*/
|
|
39
|
+
StandardError: 0,
|
|
40
|
+
/**
|
|
41
|
+
* Priority for HTTP-related errors
|
|
42
|
+
*/
|
|
43
|
+
AxiosError: 1,
|
|
44
|
+
/**
|
|
45
|
+
* High priority for validation-related errors
|
|
46
|
+
*/
|
|
47
|
+
ZodError: 2
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// src/ErrorSerializer.ts
|
|
51
|
+
var ErrorSerializer = class {
|
|
52
|
+
/**
|
|
53
|
+
* List of registered serialization plugins.
|
|
54
|
+
*/
|
|
55
|
+
plugins = [];
|
|
56
|
+
/**
|
|
57
|
+
* Registered callbacks triggered after serialization.
|
|
58
|
+
*/
|
|
59
|
+
callbacks = [];
|
|
60
|
+
/**
|
|
61
|
+
* Registers a plugin and sorts the pipeline by priority.
|
|
62
|
+
*/
|
|
63
|
+
register(plugin) {
|
|
64
|
+
this.plugins.push(plugin);
|
|
65
|
+
this.plugins.sort((a, b) => b.priority - a.priority);
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Adds a global callback triggered for every processed error.
|
|
70
|
+
*/
|
|
71
|
+
subscribe(callback) {
|
|
72
|
+
this.callbacks.push(callback);
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Orchestrates the serialization process.
|
|
77
|
+
* Returns a standardized response even for primitive types or null.
|
|
78
|
+
*/
|
|
79
|
+
process(error) {
|
|
80
|
+
let output = null;
|
|
81
|
+
for (const plugin of this.plugins) {
|
|
82
|
+
if (plugin.match(error)) {
|
|
83
|
+
output = plugin.serialize(error);
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (!output) {
|
|
88
|
+
output = {
|
|
89
|
+
metadata: {
|
|
90
|
+
plugin: "ErrorSerializer",
|
|
91
|
+
priority: ErrorPriority.FallbackError
|
|
92
|
+
},
|
|
93
|
+
error,
|
|
94
|
+
global: String(error),
|
|
95
|
+
code: ["UNHANDLED_EXCEPTION"]
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
for (const callback of this.callbacks) {
|
|
99
|
+
callback(output);
|
|
100
|
+
}
|
|
101
|
+
return output;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// src/plugins/AxiosErrorPlugin.ts
|
|
106
|
+
var import_axios = require("axios");
|
|
107
|
+
|
|
108
|
+
// src/types.ts
|
|
109
|
+
var ErrorPlugin = class {
|
|
110
|
+
/**
|
|
111
|
+
* @param priority - Determines execution order (higher values run first)
|
|
112
|
+
*/
|
|
113
|
+
constructor(priority = ErrorPriority.StandardError) {
|
|
114
|
+
this.priority = priority;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Internal helper to maintain consistent metadata and structure.
|
|
118
|
+
*/
|
|
119
|
+
createResponse(error, params) {
|
|
120
|
+
return {
|
|
121
|
+
metadata: {
|
|
122
|
+
plugin: this.name,
|
|
123
|
+
priority: this.priority
|
|
124
|
+
},
|
|
125
|
+
error,
|
|
126
|
+
...params
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// src/plugins/AxiosErrorPlugin.ts
|
|
132
|
+
var AxiosErrorPlugin = class extends ErrorPlugin {
|
|
133
|
+
/**
|
|
134
|
+
* Plugin identifier
|
|
135
|
+
*/
|
|
136
|
+
name = "AxiosErrorPlugin";
|
|
137
|
+
constructor() {
|
|
138
|
+
super(ErrorPriority.AxiosError);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Uses Axios type guard to match errors
|
|
142
|
+
*/
|
|
143
|
+
match(error) {
|
|
144
|
+
return (0, import_axios.isAxiosError)(error);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Maps HTTP response details to the global schema
|
|
148
|
+
*/
|
|
149
|
+
serialize(error) {
|
|
150
|
+
const responseData = error.response?.data;
|
|
151
|
+
const backendMessage = responseData?.message || responseData?.error?.message;
|
|
152
|
+
const backendCode = responseData?.code || responseData?.errorCode;
|
|
153
|
+
const finalMessage = backendMessage || error.message || "Network Error";
|
|
154
|
+
const finalCodes = backendCode ? Array.isArray(backendCode) ? backendCode : [String(backendCode)] : [`HTTP_${error.response?.status || 0}`];
|
|
155
|
+
return this.createResponse(error, {
|
|
156
|
+
global: finalMessage,
|
|
157
|
+
code: finalCodes,
|
|
158
|
+
status: error.response?.status || 0
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// src/plugins/StandardErrorPlugin.ts
|
|
164
|
+
var StandardErrorPlugin = class extends ErrorPlugin {
|
|
165
|
+
/**
|
|
166
|
+
* Plugin identifier
|
|
167
|
+
*/
|
|
168
|
+
name = "StandardErrorPlugin";
|
|
169
|
+
constructor() {
|
|
170
|
+
super(ErrorPriority.StandardError);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Matches native Error class
|
|
174
|
+
*/
|
|
175
|
+
match(error) {
|
|
176
|
+
return error instanceof Error;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Converts native Error to standardized response
|
|
180
|
+
*/
|
|
181
|
+
serialize(error) {
|
|
182
|
+
return this.createResponse(error, {
|
|
183
|
+
global: error.message,
|
|
184
|
+
code: ["INTERNAL_ERROR"]
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// src/plugins/ZodErrorPlugin.ts
|
|
190
|
+
var import_zod = require("zod");
|
|
191
|
+
var ZodErrorPlugin = class extends ErrorPlugin {
|
|
192
|
+
/**
|
|
193
|
+
* Plugin identifier
|
|
194
|
+
*/
|
|
195
|
+
name = "ZodErrorPlugin";
|
|
196
|
+
/**
|
|
197
|
+
* Private storage for serialization settings.
|
|
198
|
+
*/
|
|
199
|
+
options;
|
|
200
|
+
/**
|
|
201
|
+
* @param options - Customization for error path and message formatting
|
|
202
|
+
*/
|
|
203
|
+
constructor(options = {}) {
|
|
204
|
+
super(ErrorPriority.ZodError);
|
|
205
|
+
this.options = {
|
|
206
|
+
structure: "flat",
|
|
207
|
+
messageFormat: "array",
|
|
208
|
+
keySeparator: "_",
|
|
209
|
+
...options
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Checks if the error is an instance of ZodError
|
|
214
|
+
*/
|
|
215
|
+
match(error) {
|
|
216
|
+
return error instanceof import_zod.ZodError;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Serializes ZodError into the standardized format.
|
|
220
|
+
* Collects custom error codes if they are provided via mapIssue.
|
|
221
|
+
*/
|
|
222
|
+
serialize(error) {
|
|
223
|
+
const codes = /* @__PURE__ */ new Set(["102"]);
|
|
224
|
+
const validation = this.formatIssues(error.issues, codes);
|
|
225
|
+
return this.createResponse(error, {
|
|
226
|
+
global: "Validation error",
|
|
227
|
+
code: Array.from(codes),
|
|
228
|
+
status: 422,
|
|
229
|
+
validation
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Processes Zod issues into the final validation object.
|
|
234
|
+
*/
|
|
235
|
+
formatIssues(issues, codes) {
|
|
236
|
+
const result = {};
|
|
237
|
+
for (const issue of issues) {
|
|
238
|
+
const path = issue.path.filter(
|
|
239
|
+
(k) => typeof k !== "symbol"
|
|
240
|
+
);
|
|
241
|
+
let message = issue.message;
|
|
242
|
+
if (this.options.mapIssue) {
|
|
243
|
+
const mapped = this.options.mapIssue(issue);
|
|
244
|
+
if (mapped) {
|
|
245
|
+
if (mapped.message) message = mapped.message;
|
|
246
|
+
if (mapped.code) codes.add(mapped.code);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (this.options.structure === "nested") {
|
|
250
|
+
this.setNestedValue(result, path, message);
|
|
251
|
+
} else {
|
|
252
|
+
const key = path.join(this.options.keySeparator);
|
|
253
|
+
this.setFlatValue(result, key, message);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return result;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Handles flat object key generation
|
|
260
|
+
*/
|
|
261
|
+
setFlatValue(obj, key, message) {
|
|
262
|
+
if (this.options.messageFormat === "array") {
|
|
263
|
+
if (!obj[key]) obj[key] = [];
|
|
264
|
+
obj[key].push(message);
|
|
265
|
+
} else {
|
|
266
|
+
if (!obj[key]) obj[key] = message;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Recursively builds nested object paths
|
|
271
|
+
*/
|
|
272
|
+
setNestedValue(obj, path, message) {
|
|
273
|
+
let current = obj;
|
|
274
|
+
for (let i = 0; i < path.length; i++) {
|
|
275
|
+
const key = path[i];
|
|
276
|
+
const isLast = i === path.length - 1;
|
|
277
|
+
if (isLast) {
|
|
278
|
+
if (this.options.messageFormat === "array") {
|
|
279
|
+
if (!current[key]) current[key] = [];
|
|
280
|
+
if (Array.isArray(current[key])) {
|
|
281
|
+
current[key].push(message);
|
|
282
|
+
} else {
|
|
283
|
+
current[key] = [message];
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
current[key] = message;
|
|
287
|
+
}
|
|
288
|
+
} else {
|
|
289
|
+
if (!current[key] || typeof current[key] !== "object") {
|
|
290
|
+
current[key] = {};
|
|
291
|
+
}
|
|
292
|
+
current = current[key];
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
298
|
+
0 && (module.exports = {
|
|
299
|
+
AxiosErrorPlugin,
|
|
300
|
+
ErrorSerializer,
|
|
301
|
+
StandardErrorPlugin,
|
|
302
|
+
ZodErrorPlugin
|
|
303
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { AxiosError } from 'axios';
|
|
2
|
+
import { ZodError } from 'zod';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Priority levels for different error types.
|
|
6
|
+
* Plugins with higher priority are checked first in the pipeline.
|
|
7
|
+
*/
|
|
8
|
+
declare const ErrorPriority: {
|
|
9
|
+
/**
|
|
10
|
+
* Fallback priority for unhandled errors
|
|
11
|
+
*/
|
|
12
|
+
readonly FallbackError: -1;
|
|
13
|
+
/**
|
|
14
|
+
* Default priority for generic errors
|
|
15
|
+
*/
|
|
16
|
+
readonly StandardError: 0;
|
|
17
|
+
/**
|
|
18
|
+
* Priority for HTTP-related errors
|
|
19
|
+
*/
|
|
20
|
+
readonly AxiosError: 1;
|
|
21
|
+
/**
|
|
22
|
+
* High priority for validation-related errors
|
|
23
|
+
*/
|
|
24
|
+
readonly ZodError: 2;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Standardized error response structure used by all plugins.
|
|
29
|
+
*/
|
|
30
|
+
interface AppErrorResponse {
|
|
31
|
+
/**
|
|
32
|
+
* Metadata about the serialization process
|
|
33
|
+
*/
|
|
34
|
+
metadata: {
|
|
35
|
+
/**
|
|
36
|
+
* Name of the plugin that handled the error
|
|
37
|
+
*/
|
|
38
|
+
plugin: string;
|
|
39
|
+
/**
|
|
40
|
+
* Priority level of the handler
|
|
41
|
+
*/
|
|
42
|
+
priority: number;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* The original error object that was processed
|
|
46
|
+
*/
|
|
47
|
+
error: unknown;
|
|
48
|
+
/**
|
|
49
|
+
* Global error message or description
|
|
50
|
+
*/
|
|
51
|
+
global?: string;
|
|
52
|
+
/**
|
|
53
|
+
* List of specific error codes
|
|
54
|
+
*/
|
|
55
|
+
code?: string[];
|
|
56
|
+
/**
|
|
57
|
+
* HTTP status code if applicable
|
|
58
|
+
*/
|
|
59
|
+
status?: number;
|
|
60
|
+
/**
|
|
61
|
+
* Map of field-level validation errors
|
|
62
|
+
*/
|
|
63
|
+
validation?: Record<string, ExpectedAny>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Callback function triggered after error processing.
|
|
67
|
+
*/
|
|
68
|
+
type SerializationCallback = (context: AppErrorResponse) => void;
|
|
69
|
+
/**
|
|
70
|
+
* Configuration options for Zod error serialization.
|
|
71
|
+
*/
|
|
72
|
+
interface ZodSerializationOptions {
|
|
73
|
+
/**
|
|
74
|
+
* Structure of the validation object:
|
|
75
|
+
* - 'flat': keys like "user_name"
|
|
76
|
+
* - 'nested': hierarchical objects
|
|
77
|
+
*/
|
|
78
|
+
structure: "flat" | "nested";
|
|
79
|
+
/**
|
|
80
|
+
* Format of validation messages:
|
|
81
|
+
* - 'array': list of strings
|
|
82
|
+
* - 'string': first error message only
|
|
83
|
+
*/
|
|
84
|
+
messageFormat: "array" | "string";
|
|
85
|
+
/**
|
|
86
|
+
* Separator for flat keys
|
|
87
|
+
*/
|
|
88
|
+
keySeparator?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Custom mapper to override message or code for specific Zod issues.
|
|
91
|
+
*/
|
|
92
|
+
mapIssue?: (issue: ExpectedAny) => {
|
|
93
|
+
code?: string;
|
|
94
|
+
message?: string;
|
|
95
|
+
} | undefined;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Abstract base class for error plugins with unified response formatting.
|
|
99
|
+
*/
|
|
100
|
+
declare abstract class ErrorPlugin<T = unknown> {
|
|
101
|
+
priority: (typeof ErrorPriority)[keyof typeof ErrorPriority];
|
|
102
|
+
/**
|
|
103
|
+
* Unique identifier for the plugin
|
|
104
|
+
*/
|
|
105
|
+
abstract readonly name: string;
|
|
106
|
+
/**
|
|
107
|
+
* @param priority - Determines execution order (higher values run first)
|
|
108
|
+
*/
|
|
109
|
+
protected constructor(priority?: (typeof ErrorPriority)[keyof typeof ErrorPriority]);
|
|
110
|
+
/**
|
|
111
|
+
* Determines if the error is compatible with this plugin.
|
|
112
|
+
*/
|
|
113
|
+
abstract match(error: unknown): error is T;
|
|
114
|
+
/**
|
|
115
|
+
* Transforms the error into the standardized AppErrorResponse.
|
|
116
|
+
*/
|
|
117
|
+
abstract serialize(error: T): AppErrorResponse;
|
|
118
|
+
/**
|
|
119
|
+
* Internal helper to maintain consistent metadata and structure.
|
|
120
|
+
*/
|
|
121
|
+
protected createResponse(error: T, params: Omit<AppErrorResponse, "metadata" | "error">): AppErrorResponse;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Type alias for any-type values in structured objects.
|
|
125
|
+
*/
|
|
126
|
+
type ExpectedAny = any;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Core engine for error processing that manages plugin lifecycle and post-processing callbacks.
|
|
130
|
+
*/
|
|
131
|
+
declare class ErrorSerializer {
|
|
132
|
+
/**
|
|
133
|
+
* List of registered serialization plugins.
|
|
134
|
+
*/
|
|
135
|
+
private plugins;
|
|
136
|
+
/**
|
|
137
|
+
* Registered callbacks triggered after serialization.
|
|
138
|
+
*/
|
|
139
|
+
private callbacks;
|
|
140
|
+
/**
|
|
141
|
+
* Registers a plugin and sorts the pipeline by priority.
|
|
142
|
+
*/
|
|
143
|
+
register(plugin: ErrorPlugin<ExpectedAny>): this;
|
|
144
|
+
/**
|
|
145
|
+
* Adds a global callback triggered for every processed error.
|
|
146
|
+
*/
|
|
147
|
+
subscribe(callback: SerializationCallback): this;
|
|
148
|
+
/**
|
|
149
|
+
* Orchestrates the serialization process.
|
|
150
|
+
* Returns a standardized response even for primitive types or null.
|
|
151
|
+
*/
|
|
152
|
+
process(error: unknown): AppErrorResponse;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Plugin for handling Axios errors and extracting server-side data.
|
|
157
|
+
*/
|
|
158
|
+
declare class AxiosErrorPlugin extends ErrorPlugin<AxiosError> {
|
|
159
|
+
/**
|
|
160
|
+
* Plugin identifier
|
|
161
|
+
*/
|
|
162
|
+
readonly name = "AxiosErrorPlugin";
|
|
163
|
+
constructor();
|
|
164
|
+
/**
|
|
165
|
+
* Uses Axios type guard to match errors
|
|
166
|
+
*/
|
|
167
|
+
match(error: unknown): error is AxiosError;
|
|
168
|
+
/**
|
|
169
|
+
* Maps HTTP response details to the global schema
|
|
170
|
+
*/
|
|
171
|
+
serialize(error: AxiosError): AppErrorResponse;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Basic plugin for standard JavaScript Error objects.
|
|
176
|
+
*/
|
|
177
|
+
declare class StandardErrorPlugin extends ErrorPlugin<Error> {
|
|
178
|
+
/**
|
|
179
|
+
* Plugin identifier
|
|
180
|
+
*/
|
|
181
|
+
readonly name = "StandardErrorPlugin";
|
|
182
|
+
constructor();
|
|
183
|
+
/**
|
|
184
|
+
* Matches native Error class
|
|
185
|
+
*/
|
|
186
|
+
match(error: unknown): error is Error;
|
|
187
|
+
/**
|
|
188
|
+
* Converts native Error to standardized response
|
|
189
|
+
*/
|
|
190
|
+
serialize(error: Error): AppErrorResponse;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Plugin specifically designed for Zod errors with advanced structural formatting.
|
|
195
|
+
*/
|
|
196
|
+
declare class ZodErrorPlugin extends ErrorPlugin<ZodError> {
|
|
197
|
+
/**
|
|
198
|
+
* Plugin identifier
|
|
199
|
+
*/
|
|
200
|
+
readonly name = "ZodErrorPlugin";
|
|
201
|
+
/**
|
|
202
|
+
* Private storage for serialization settings.
|
|
203
|
+
*/
|
|
204
|
+
private options;
|
|
205
|
+
/**
|
|
206
|
+
* @param options - Customization for error path and message formatting
|
|
207
|
+
*/
|
|
208
|
+
constructor(options?: Partial<ZodSerializationOptions>);
|
|
209
|
+
/**
|
|
210
|
+
* Checks if the error is an instance of ZodError
|
|
211
|
+
*/
|
|
212
|
+
match(error: unknown): error is ZodError;
|
|
213
|
+
/**
|
|
214
|
+
* Serializes ZodError into the standardized format.
|
|
215
|
+
* Collects custom error codes if they are provided via mapIssue.
|
|
216
|
+
*/
|
|
217
|
+
serialize(error: ZodError): AppErrorResponse;
|
|
218
|
+
/**
|
|
219
|
+
* Processes Zod issues into the final validation object.
|
|
220
|
+
*/
|
|
221
|
+
protected formatIssues(issues: ZodError["issues"], codes: Set<string>): Record<string, ExpectedAny>;
|
|
222
|
+
/**
|
|
223
|
+
* Handles flat object key generation
|
|
224
|
+
*/
|
|
225
|
+
private setFlatValue;
|
|
226
|
+
/**
|
|
227
|
+
* Recursively builds nested object paths
|
|
228
|
+
*/
|
|
229
|
+
private setNestedValue;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export { AxiosErrorPlugin, ErrorSerializer, StandardErrorPlugin, ZodErrorPlugin };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { AxiosError } from 'axios';
|
|
2
|
+
import { ZodError } from 'zod';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Priority levels for different error types.
|
|
6
|
+
* Plugins with higher priority are checked first in the pipeline.
|
|
7
|
+
*/
|
|
8
|
+
declare const ErrorPriority: {
|
|
9
|
+
/**
|
|
10
|
+
* Fallback priority for unhandled errors
|
|
11
|
+
*/
|
|
12
|
+
readonly FallbackError: -1;
|
|
13
|
+
/**
|
|
14
|
+
* Default priority for generic errors
|
|
15
|
+
*/
|
|
16
|
+
readonly StandardError: 0;
|
|
17
|
+
/**
|
|
18
|
+
* Priority for HTTP-related errors
|
|
19
|
+
*/
|
|
20
|
+
readonly AxiosError: 1;
|
|
21
|
+
/**
|
|
22
|
+
* High priority for validation-related errors
|
|
23
|
+
*/
|
|
24
|
+
readonly ZodError: 2;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Standardized error response structure used by all plugins.
|
|
29
|
+
*/
|
|
30
|
+
interface AppErrorResponse {
|
|
31
|
+
/**
|
|
32
|
+
* Metadata about the serialization process
|
|
33
|
+
*/
|
|
34
|
+
metadata: {
|
|
35
|
+
/**
|
|
36
|
+
* Name of the plugin that handled the error
|
|
37
|
+
*/
|
|
38
|
+
plugin: string;
|
|
39
|
+
/**
|
|
40
|
+
* Priority level of the handler
|
|
41
|
+
*/
|
|
42
|
+
priority: number;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* The original error object that was processed
|
|
46
|
+
*/
|
|
47
|
+
error: unknown;
|
|
48
|
+
/**
|
|
49
|
+
* Global error message or description
|
|
50
|
+
*/
|
|
51
|
+
global?: string;
|
|
52
|
+
/**
|
|
53
|
+
* List of specific error codes
|
|
54
|
+
*/
|
|
55
|
+
code?: string[];
|
|
56
|
+
/**
|
|
57
|
+
* HTTP status code if applicable
|
|
58
|
+
*/
|
|
59
|
+
status?: number;
|
|
60
|
+
/**
|
|
61
|
+
* Map of field-level validation errors
|
|
62
|
+
*/
|
|
63
|
+
validation?: Record<string, ExpectedAny>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Callback function triggered after error processing.
|
|
67
|
+
*/
|
|
68
|
+
type SerializationCallback = (context: AppErrorResponse) => void;
|
|
69
|
+
/**
|
|
70
|
+
* Configuration options for Zod error serialization.
|
|
71
|
+
*/
|
|
72
|
+
interface ZodSerializationOptions {
|
|
73
|
+
/**
|
|
74
|
+
* Structure of the validation object:
|
|
75
|
+
* - 'flat': keys like "user_name"
|
|
76
|
+
* - 'nested': hierarchical objects
|
|
77
|
+
*/
|
|
78
|
+
structure: "flat" | "nested";
|
|
79
|
+
/**
|
|
80
|
+
* Format of validation messages:
|
|
81
|
+
* - 'array': list of strings
|
|
82
|
+
* - 'string': first error message only
|
|
83
|
+
*/
|
|
84
|
+
messageFormat: "array" | "string";
|
|
85
|
+
/**
|
|
86
|
+
* Separator for flat keys
|
|
87
|
+
*/
|
|
88
|
+
keySeparator?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Custom mapper to override message or code for specific Zod issues.
|
|
91
|
+
*/
|
|
92
|
+
mapIssue?: (issue: ExpectedAny) => {
|
|
93
|
+
code?: string;
|
|
94
|
+
message?: string;
|
|
95
|
+
} | undefined;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Abstract base class for error plugins with unified response formatting.
|
|
99
|
+
*/
|
|
100
|
+
declare abstract class ErrorPlugin<T = unknown> {
|
|
101
|
+
priority: (typeof ErrorPriority)[keyof typeof ErrorPriority];
|
|
102
|
+
/**
|
|
103
|
+
* Unique identifier for the plugin
|
|
104
|
+
*/
|
|
105
|
+
abstract readonly name: string;
|
|
106
|
+
/**
|
|
107
|
+
* @param priority - Determines execution order (higher values run first)
|
|
108
|
+
*/
|
|
109
|
+
protected constructor(priority?: (typeof ErrorPriority)[keyof typeof ErrorPriority]);
|
|
110
|
+
/**
|
|
111
|
+
* Determines if the error is compatible with this plugin.
|
|
112
|
+
*/
|
|
113
|
+
abstract match(error: unknown): error is T;
|
|
114
|
+
/**
|
|
115
|
+
* Transforms the error into the standardized AppErrorResponse.
|
|
116
|
+
*/
|
|
117
|
+
abstract serialize(error: T): AppErrorResponse;
|
|
118
|
+
/**
|
|
119
|
+
* Internal helper to maintain consistent metadata and structure.
|
|
120
|
+
*/
|
|
121
|
+
protected createResponse(error: T, params: Omit<AppErrorResponse, "metadata" | "error">): AppErrorResponse;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Type alias for any-type values in structured objects.
|
|
125
|
+
*/
|
|
126
|
+
type ExpectedAny = any;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Core engine for error processing that manages plugin lifecycle and post-processing callbacks.
|
|
130
|
+
*/
|
|
131
|
+
declare class ErrorSerializer {
|
|
132
|
+
/**
|
|
133
|
+
* List of registered serialization plugins.
|
|
134
|
+
*/
|
|
135
|
+
private plugins;
|
|
136
|
+
/**
|
|
137
|
+
* Registered callbacks triggered after serialization.
|
|
138
|
+
*/
|
|
139
|
+
private callbacks;
|
|
140
|
+
/**
|
|
141
|
+
* Registers a plugin and sorts the pipeline by priority.
|
|
142
|
+
*/
|
|
143
|
+
register(plugin: ErrorPlugin<ExpectedAny>): this;
|
|
144
|
+
/**
|
|
145
|
+
* Adds a global callback triggered for every processed error.
|
|
146
|
+
*/
|
|
147
|
+
subscribe(callback: SerializationCallback): this;
|
|
148
|
+
/**
|
|
149
|
+
* Orchestrates the serialization process.
|
|
150
|
+
* Returns a standardized response even for primitive types or null.
|
|
151
|
+
*/
|
|
152
|
+
process(error: unknown): AppErrorResponse;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Plugin for handling Axios errors and extracting server-side data.
|
|
157
|
+
*/
|
|
158
|
+
declare class AxiosErrorPlugin extends ErrorPlugin<AxiosError> {
|
|
159
|
+
/**
|
|
160
|
+
* Plugin identifier
|
|
161
|
+
*/
|
|
162
|
+
readonly name = "AxiosErrorPlugin";
|
|
163
|
+
constructor();
|
|
164
|
+
/**
|
|
165
|
+
* Uses Axios type guard to match errors
|
|
166
|
+
*/
|
|
167
|
+
match(error: unknown): error is AxiosError;
|
|
168
|
+
/**
|
|
169
|
+
* Maps HTTP response details to the global schema
|
|
170
|
+
*/
|
|
171
|
+
serialize(error: AxiosError): AppErrorResponse;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Basic plugin for standard JavaScript Error objects.
|
|
176
|
+
*/
|
|
177
|
+
declare class StandardErrorPlugin extends ErrorPlugin<Error> {
|
|
178
|
+
/**
|
|
179
|
+
* Plugin identifier
|
|
180
|
+
*/
|
|
181
|
+
readonly name = "StandardErrorPlugin";
|
|
182
|
+
constructor();
|
|
183
|
+
/**
|
|
184
|
+
* Matches native Error class
|
|
185
|
+
*/
|
|
186
|
+
match(error: unknown): error is Error;
|
|
187
|
+
/**
|
|
188
|
+
* Converts native Error to standardized response
|
|
189
|
+
*/
|
|
190
|
+
serialize(error: Error): AppErrorResponse;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Plugin specifically designed for Zod errors with advanced structural formatting.
|
|
195
|
+
*/
|
|
196
|
+
declare class ZodErrorPlugin extends ErrorPlugin<ZodError> {
|
|
197
|
+
/**
|
|
198
|
+
* Plugin identifier
|
|
199
|
+
*/
|
|
200
|
+
readonly name = "ZodErrorPlugin";
|
|
201
|
+
/**
|
|
202
|
+
* Private storage for serialization settings.
|
|
203
|
+
*/
|
|
204
|
+
private options;
|
|
205
|
+
/**
|
|
206
|
+
* @param options - Customization for error path and message formatting
|
|
207
|
+
*/
|
|
208
|
+
constructor(options?: Partial<ZodSerializationOptions>);
|
|
209
|
+
/**
|
|
210
|
+
* Checks if the error is an instance of ZodError
|
|
211
|
+
*/
|
|
212
|
+
match(error: unknown): error is ZodError;
|
|
213
|
+
/**
|
|
214
|
+
* Serializes ZodError into the standardized format.
|
|
215
|
+
* Collects custom error codes if they are provided via mapIssue.
|
|
216
|
+
*/
|
|
217
|
+
serialize(error: ZodError): AppErrorResponse;
|
|
218
|
+
/**
|
|
219
|
+
* Processes Zod issues into the final validation object.
|
|
220
|
+
*/
|
|
221
|
+
protected formatIssues(issues: ZodError["issues"], codes: Set<string>): Record<string, ExpectedAny>;
|
|
222
|
+
/**
|
|
223
|
+
* Handles flat object key generation
|
|
224
|
+
*/
|
|
225
|
+
private setFlatValue;
|
|
226
|
+
/**
|
|
227
|
+
* Recursively builds nested object paths
|
|
228
|
+
*/
|
|
229
|
+
private setNestedValue;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export { AxiosErrorPlugin, ErrorSerializer, StandardErrorPlugin, ZodErrorPlugin };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
// src/contants.ts
|
|
2
|
+
var ErrorPriority = {
|
|
3
|
+
/**
|
|
4
|
+
* Fallback priority for unhandled errors
|
|
5
|
+
*/
|
|
6
|
+
FallbackError: -1,
|
|
7
|
+
/**
|
|
8
|
+
* Default priority for generic errors
|
|
9
|
+
*/
|
|
10
|
+
StandardError: 0,
|
|
11
|
+
/**
|
|
12
|
+
* Priority for HTTP-related errors
|
|
13
|
+
*/
|
|
14
|
+
AxiosError: 1,
|
|
15
|
+
/**
|
|
16
|
+
* High priority for validation-related errors
|
|
17
|
+
*/
|
|
18
|
+
ZodError: 2
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// src/ErrorSerializer.ts
|
|
22
|
+
var ErrorSerializer = class {
|
|
23
|
+
/**
|
|
24
|
+
* List of registered serialization plugins.
|
|
25
|
+
*/
|
|
26
|
+
plugins = [];
|
|
27
|
+
/**
|
|
28
|
+
* Registered callbacks triggered after serialization.
|
|
29
|
+
*/
|
|
30
|
+
callbacks = [];
|
|
31
|
+
/**
|
|
32
|
+
* Registers a plugin and sorts the pipeline by priority.
|
|
33
|
+
*/
|
|
34
|
+
register(plugin) {
|
|
35
|
+
this.plugins.push(plugin);
|
|
36
|
+
this.plugins.sort((a, b) => b.priority - a.priority);
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Adds a global callback triggered for every processed error.
|
|
41
|
+
*/
|
|
42
|
+
subscribe(callback) {
|
|
43
|
+
this.callbacks.push(callback);
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Orchestrates the serialization process.
|
|
48
|
+
* Returns a standardized response even for primitive types or null.
|
|
49
|
+
*/
|
|
50
|
+
process(error) {
|
|
51
|
+
let output = null;
|
|
52
|
+
for (const plugin of this.plugins) {
|
|
53
|
+
if (plugin.match(error)) {
|
|
54
|
+
output = plugin.serialize(error);
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (!output) {
|
|
59
|
+
output = {
|
|
60
|
+
metadata: {
|
|
61
|
+
plugin: "ErrorSerializer",
|
|
62
|
+
priority: ErrorPriority.FallbackError
|
|
63
|
+
},
|
|
64
|
+
error,
|
|
65
|
+
global: String(error),
|
|
66
|
+
code: ["UNHANDLED_EXCEPTION"]
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
for (const callback of this.callbacks) {
|
|
70
|
+
callback(output);
|
|
71
|
+
}
|
|
72
|
+
return output;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// src/plugins/AxiosErrorPlugin.ts
|
|
77
|
+
import { isAxiosError } from "axios";
|
|
78
|
+
|
|
79
|
+
// src/types.ts
|
|
80
|
+
var ErrorPlugin = class {
|
|
81
|
+
/**
|
|
82
|
+
* @param priority - Determines execution order (higher values run first)
|
|
83
|
+
*/
|
|
84
|
+
constructor(priority = ErrorPriority.StandardError) {
|
|
85
|
+
this.priority = priority;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Internal helper to maintain consistent metadata and structure.
|
|
89
|
+
*/
|
|
90
|
+
createResponse(error, params) {
|
|
91
|
+
return {
|
|
92
|
+
metadata: {
|
|
93
|
+
plugin: this.name,
|
|
94
|
+
priority: this.priority
|
|
95
|
+
},
|
|
96
|
+
error,
|
|
97
|
+
...params
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// src/plugins/AxiosErrorPlugin.ts
|
|
103
|
+
var AxiosErrorPlugin = class extends ErrorPlugin {
|
|
104
|
+
/**
|
|
105
|
+
* Plugin identifier
|
|
106
|
+
*/
|
|
107
|
+
name = "AxiosErrorPlugin";
|
|
108
|
+
constructor() {
|
|
109
|
+
super(ErrorPriority.AxiosError);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Uses Axios type guard to match errors
|
|
113
|
+
*/
|
|
114
|
+
match(error) {
|
|
115
|
+
return isAxiosError(error);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Maps HTTP response details to the global schema
|
|
119
|
+
*/
|
|
120
|
+
serialize(error) {
|
|
121
|
+
const responseData = error.response?.data;
|
|
122
|
+
const backendMessage = responseData?.message || responseData?.error?.message;
|
|
123
|
+
const backendCode = responseData?.code || responseData?.errorCode;
|
|
124
|
+
const finalMessage = backendMessage || error.message || "Network Error";
|
|
125
|
+
const finalCodes = backendCode ? Array.isArray(backendCode) ? backendCode : [String(backendCode)] : [`HTTP_${error.response?.status || 0}`];
|
|
126
|
+
return this.createResponse(error, {
|
|
127
|
+
global: finalMessage,
|
|
128
|
+
code: finalCodes,
|
|
129
|
+
status: error.response?.status || 0
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// src/plugins/StandardErrorPlugin.ts
|
|
135
|
+
var StandardErrorPlugin = class extends ErrorPlugin {
|
|
136
|
+
/**
|
|
137
|
+
* Plugin identifier
|
|
138
|
+
*/
|
|
139
|
+
name = "StandardErrorPlugin";
|
|
140
|
+
constructor() {
|
|
141
|
+
super(ErrorPriority.StandardError);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Matches native Error class
|
|
145
|
+
*/
|
|
146
|
+
match(error) {
|
|
147
|
+
return error instanceof Error;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Converts native Error to standardized response
|
|
151
|
+
*/
|
|
152
|
+
serialize(error) {
|
|
153
|
+
return this.createResponse(error, {
|
|
154
|
+
global: error.message,
|
|
155
|
+
code: ["INTERNAL_ERROR"]
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// src/plugins/ZodErrorPlugin.ts
|
|
161
|
+
import { ZodError } from "zod";
|
|
162
|
+
var ZodErrorPlugin = class extends ErrorPlugin {
|
|
163
|
+
/**
|
|
164
|
+
* Plugin identifier
|
|
165
|
+
*/
|
|
166
|
+
name = "ZodErrorPlugin";
|
|
167
|
+
/**
|
|
168
|
+
* Private storage for serialization settings.
|
|
169
|
+
*/
|
|
170
|
+
options;
|
|
171
|
+
/**
|
|
172
|
+
* @param options - Customization for error path and message formatting
|
|
173
|
+
*/
|
|
174
|
+
constructor(options = {}) {
|
|
175
|
+
super(ErrorPriority.ZodError);
|
|
176
|
+
this.options = {
|
|
177
|
+
structure: "flat",
|
|
178
|
+
messageFormat: "array",
|
|
179
|
+
keySeparator: "_",
|
|
180
|
+
...options
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Checks if the error is an instance of ZodError
|
|
185
|
+
*/
|
|
186
|
+
match(error) {
|
|
187
|
+
return error instanceof ZodError;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Serializes ZodError into the standardized format.
|
|
191
|
+
* Collects custom error codes if they are provided via mapIssue.
|
|
192
|
+
*/
|
|
193
|
+
serialize(error) {
|
|
194
|
+
const codes = /* @__PURE__ */ new Set(["102"]);
|
|
195
|
+
const validation = this.formatIssues(error.issues, codes);
|
|
196
|
+
return this.createResponse(error, {
|
|
197
|
+
global: "Validation error",
|
|
198
|
+
code: Array.from(codes),
|
|
199
|
+
status: 422,
|
|
200
|
+
validation
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Processes Zod issues into the final validation object.
|
|
205
|
+
*/
|
|
206
|
+
formatIssues(issues, codes) {
|
|
207
|
+
const result = {};
|
|
208
|
+
for (const issue of issues) {
|
|
209
|
+
const path = issue.path.filter(
|
|
210
|
+
(k) => typeof k !== "symbol"
|
|
211
|
+
);
|
|
212
|
+
let message = issue.message;
|
|
213
|
+
if (this.options.mapIssue) {
|
|
214
|
+
const mapped = this.options.mapIssue(issue);
|
|
215
|
+
if (mapped) {
|
|
216
|
+
if (mapped.message) message = mapped.message;
|
|
217
|
+
if (mapped.code) codes.add(mapped.code);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (this.options.structure === "nested") {
|
|
221
|
+
this.setNestedValue(result, path, message);
|
|
222
|
+
} else {
|
|
223
|
+
const key = path.join(this.options.keySeparator);
|
|
224
|
+
this.setFlatValue(result, key, message);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Handles flat object key generation
|
|
231
|
+
*/
|
|
232
|
+
setFlatValue(obj, key, message) {
|
|
233
|
+
if (this.options.messageFormat === "array") {
|
|
234
|
+
if (!obj[key]) obj[key] = [];
|
|
235
|
+
obj[key].push(message);
|
|
236
|
+
} else {
|
|
237
|
+
if (!obj[key]) obj[key] = message;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Recursively builds nested object paths
|
|
242
|
+
*/
|
|
243
|
+
setNestedValue(obj, path, message) {
|
|
244
|
+
let current = obj;
|
|
245
|
+
for (let i = 0; i < path.length; i++) {
|
|
246
|
+
const key = path[i];
|
|
247
|
+
const isLast = i === path.length - 1;
|
|
248
|
+
if (isLast) {
|
|
249
|
+
if (this.options.messageFormat === "array") {
|
|
250
|
+
if (!current[key]) current[key] = [];
|
|
251
|
+
if (Array.isArray(current[key])) {
|
|
252
|
+
current[key].push(message);
|
|
253
|
+
} else {
|
|
254
|
+
current[key] = [message];
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
current[key] = message;
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
if (!current[key] || typeof current[key] !== "object") {
|
|
261
|
+
current[key] = {};
|
|
262
|
+
}
|
|
263
|
+
current = current[key];
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
export {
|
|
269
|
+
AxiosErrorPlugin,
|
|
270
|
+
ErrorSerializer,
|
|
271
|
+
StandardErrorPlugin,
|
|
272
|
+
ZodErrorPlugin
|
|
273
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@devua-lab/error-serialization",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.cts",
|
|
16
|
+
"default": "./dist/index.cjs"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
22
|
+
"dev": "tsup src/index.ts --format cjs,esm --watch --dts",
|
|
23
|
+
"format": "biome format --write .",
|
|
24
|
+
"lint": "biome lint .",
|
|
25
|
+
"check": "biome check --write .",
|
|
26
|
+
"ci": "biome ci .",
|
|
27
|
+
"test": "vitest",
|
|
28
|
+
"test:run": "vitest run",
|
|
29
|
+
"test:cov": "vitest run --coverage"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@biomejs/biome": "2.3.14",
|
|
33
|
+
"@types/node": "^25.2.2",
|
|
34
|
+
"axios": "^1.13.5",
|
|
35
|
+
"tsup": "^8.5.1",
|
|
36
|
+
"typescript": "^5.9.3",
|
|
37
|
+
"vite": "^7.3.1",
|
|
38
|
+
"vitest": "^4.0.18",
|
|
39
|
+
"zod": "^4.3.6"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"axios": ">=1.0.0",
|
|
43
|
+
"zod": ">=3.0.0"
|
|
44
|
+
},
|
|
45
|
+
"peerDependenciesMeta": {
|
|
46
|
+
"axios": {
|
|
47
|
+
"optional": true
|
|
48
|
+
},
|
|
49
|
+
"zod": {
|
|
50
|
+
"optional": true
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"files": [
|
|
54
|
+
"dist",
|
|
55
|
+
"README.md"
|
|
56
|
+
]
|
|
57
|
+
}
|