@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 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
+ });
@@ -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 };
@@ -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
+ }