@owlmeans/error 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 OwlMeans Common — Fullstack typescript framework
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,390 @@
1
+ # @owlmeans/error
2
+
3
+ A fully typed error system for seamless error handling between backend and frontend, allowing errors to be marshaled/unmarshaled while preserving type information across network boundaries.
4
+
5
+ ## Overview
6
+
7
+ The `@owlmeans/error` package provides a robust error handling system that enables type-safe error propagation between server and client applications. The core concept revolves around the `ResilientError` class, which can be:
8
+
9
+ - **Marshaled** (serialized) on the server side
10
+ - **Transmitted** over the network as regular Error objects
11
+ - **Unmarshaled** (deserialized) on the client side back to the same typed error class
12
+
13
+ This ensures that error handling remains consistent and type-safe across the entire application stack.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @owlmeans/error
19
+ ```
20
+
21
+ ## Core Concepts
22
+
23
+ ### ResilientError Class
24
+
25
+ The `ResilientError` class is the foundation of the error system. It extends the standard JavaScript `Error` class and provides additional functionality for marshaling, unmarshaling, and type conversion.
26
+
27
+ ### Error Conversion System
28
+
29
+ The package includes a flexible converter system that allows registration of custom error types and their conversion logic through the `Converter` interface.
30
+
31
+ ### Marshaling & Unmarshaling
32
+
33
+ - **Marshaling**: Converting a ResilientError into a transferable format
34
+ - **Unmarshaling**: Reconstructing a ResilientError from its marshaled form
35
+
36
+ ## API Reference
37
+
38
+ ### ResilientError Class
39
+
40
+ #### Properties
41
+
42
+ - `type: string` - The error type identifier
43
+ - `oiriginalStack?: string` - The original stack trace when the error was created
44
+
45
+ #### Static Properties
46
+
47
+ - `separator: string` - The separator used in marshaled error messages (default: `'|||'`)
48
+ - `typeName: string` - The default type name for ResilientError instances (default: `'ResilientError'`)
49
+ - `converters: Converter[]` - Array of registered error converters
50
+
51
+ #### Static Methods
52
+
53
+ ##### `registerErrorClass(resilientErrorClass, errorClass?)`
54
+
55
+ Registers a custom error class with the converter system.
56
+
57
+ **Parameters:**
58
+ - `resilientErrorClass: ResilientErrorConstructor` - The ResilientError subclass to register
59
+ - `errorClass?: ErrorConstructor` - Optional native Error class to convert from
60
+
61
+ **Returns:** `Converter` - The created converter instance
62
+
63
+ **Example:**
64
+ ```typescript
65
+ class CustomError extends ResilientError {
66
+ static typeName = 'CustomError'
67
+ }
68
+
69
+ ResilientError.registerErrorClass(CustomError, TypeError)
70
+ ```
71
+
72
+ ##### `ensure(err, throwOnUnknown?)`
73
+
74
+ Ensures an error is converted to a ResilientError instance.
75
+
76
+ **Parameters:**
77
+ - `err: Error | string` - The error to convert
78
+ - `throwOnUnknown?: boolean` - Whether to throw on unknown error types (default: false)
79
+
80
+ **Returns:** `ResilientError` - The converted error
81
+
82
+ **Example:**
83
+ ```typescript
84
+ const resilientError = ResilientError.ensure(new Error('Something went wrong'))
85
+ const typedError = ResilientError.ensure('Error message')
86
+ ```
87
+
88
+ ##### `marshal(err)`
89
+
90
+ Marshals an error for network transmission.
91
+
92
+ **Parameters:**
93
+ - `err: Error` - The error to marshal
94
+
95
+ **Returns:** `Error` - A marshaled error object
96
+
97
+ **Example:**
98
+ ```typescript
99
+ const original = new ResilientError('CustomError', 'Something failed')
100
+ const marshaled = ResilientError.marshal(original)
101
+ // marshaled.message contains: "CustomError|||Something failed|||[stack trace]"
102
+ ```
103
+
104
+ #### Instance Methods
105
+
106
+ ##### `marshal()`
107
+
108
+ Marshals the current error instance.
109
+
110
+ **Returns:** `Error` - A marshaled error object
111
+
112
+ **Example:**
113
+ ```typescript
114
+ const error = new ResilientError('MyError', 'Description')
115
+ const marshaled = error.marshal()
116
+ ```
117
+
118
+ ##### `finalizeUnmarshal()`
119
+
120
+ Called after unmarshaling to perform any post-processing. Override in subclasses for custom behavior.
121
+
122
+ **Returns:** `void`
123
+
124
+ #### Constructor
125
+
126
+ ```typescript
127
+ constructor(type: string, message: string, stack?: string)
128
+ constructor(message: string, stack?: string)
129
+ ```
130
+
131
+ **Parameters:**
132
+ - `type: string` - The error type identifier
133
+ - `message: string` - The error message
134
+ - `stack?: string` - Optional stack trace
135
+
136
+ **Example:**
137
+ ```typescript
138
+ const error1 = new ResilientError('ValidationError', 'Invalid input')
139
+ const error2 = new ResilientError('Network error') // uses default type
140
+ ```
141
+
142
+ ### Helper Functions
143
+
144
+ #### `enuserError<T>(err, throwOnUnknown?)`
145
+
146
+ Convenience function that ensures an error is a ResilientError instance.
147
+
148
+ **Parameters:**
149
+ - `err: Error | string` - The error to ensure
150
+ - `throwOnUnknown?: boolean` - Whether to throw on unknown error types
151
+
152
+ **Returns:** `T extends ResilientError` - The ensured error
153
+
154
+ **Example:**
155
+ ```typescript
156
+ import { enuserError } from '@owlmeans/error'
157
+
158
+ const resilientError = enuserError(new Error('Something went wrong'))
159
+ const typedError = enuserError<MyCustomError>(someError)
160
+ ```
161
+
162
+ #### `marshalError(err)`
163
+
164
+ Convenience function that marshals an error after ensuring it's a ResilientError.
165
+
166
+ **Parameters:**
167
+ - `err: Error | string` - The error to marshal
168
+
169
+ **Returns:** `Error` - The marshaled error
170
+
171
+ **Example:**
172
+ ```typescript
173
+ import { marshalError } from '@owlmeans/error'
174
+
175
+ const marshaled = marshalError(new Error('Server error'))
176
+ ```
177
+
178
+ ### Utility Functions
179
+
180
+ #### `createErrorConverter(resilientErrorClass, errorClass?)`
181
+
182
+ Creates a converter for transforming native errors to ResilientError instances.
183
+
184
+ **Parameters:**
185
+ - `resilientErrorClass: ResilientErrorConstructor` - The target ResilientError class
186
+ - `errorClass?: ErrorConstructor` - Optional source Error class to match against
187
+
188
+ **Returns:** `Converter` - The created converter
189
+
190
+ **Example:**
191
+ ```typescript
192
+ import { createErrorConverter } from '@owlmeans/error'
193
+
194
+ const converter = createErrorConverter(MyResilientError, TypeError)
195
+ ResilientError.converters.push(converter)
196
+ ```
197
+
198
+ #### `unmarshal<T>(errorClass)`
199
+
200
+ Creates an unmarshaling function for a specific error class.
201
+
202
+ **Parameters:**
203
+ - `errorClass: ResilientErrorConstructor` - The error class to unmarshal to
204
+
205
+ **Returns:** `(err: Error) => T` - Function that unmarshals errors to the specified type
206
+
207
+ **Example:**
208
+ ```typescript
209
+ import { unmarshal } from '@owlmeans/error'
210
+
211
+ const unmarshalMyError = unmarshal(MyResilientError)
212
+ const restored = unmarshalMyError(marshaledError)
213
+ ```
214
+
215
+ ### Type Definitions
216
+
217
+ #### `ValueOrError<T>`
218
+
219
+ A utility type that represents either a value or a ResilientError.
220
+
221
+ ```typescript
222
+ type ValueOrError<T> = T | ResilientError
223
+ ```
224
+
225
+ **Example:**
226
+ ```typescript
227
+ function processData(): ValueOrError<string> {
228
+ if (someCondition) {
229
+ return "success"
230
+ }
231
+ return new ResilientError('ProcessingError', 'Failed to process')
232
+ }
233
+ ```
234
+
235
+ #### `Converter`
236
+
237
+ Interface for error conversion logic.
238
+
239
+ ```typescript
240
+ interface Converter {
241
+ match: (err: Error) => boolean
242
+ convert: (err: Error) => ResilientError
243
+ isMarshaled: (err: Error) => boolean
244
+ unmarshal: (err: Error) => ResilientError
245
+ }
246
+ ```
247
+
248
+ **Methods:**
249
+ - `match(err)` - Determines if the converter can handle the error
250
+ - `convert(err)` - Converts the error to a ResilientError
251
+ - `isMarshaled(err)` - Checks if the error is in marshaled form
252
+ - `unmarshal(err)` - Unmarshals the error back to ResilientError
253
+
254
+ #### `ResilientErrorConstructor<T>`
255
+
256
+ Constructor interface for ResilientError classes.
257
+
258
+ ```typescript
259
+ interface ResilientErrorConstructor<T extends ResilientError = ResilientError> {
260
+ new (type: string, message: string, stack?: string): T
261
+ new (message: string, stack?: string): T
262
+ typeName: string
263
+ }
264
+ ```
265
+
266
+ ### Constants
267
+
268
+ #### `SEPARATOR`
269
+
270
+ The default separator used in marshaled error messages.
271
+
272
+ ```typescript
273
+ const SEPARATOR = '|||'
274
+ ```
275
+
276
+ #### `RESILENT_ERROR`
277
+
278
+ The default type name for ResilientError instances.
279
+
280
+ ```typescript
281
+ const RESILENT_ERROR = 'ResilientError'
282
+ ```
283
+
284
+ ## Usage Examples
285
+
286
+ ### Basic Error Handling
287
+
288
+ ```typescript
289
+ import { ResilientError, enuserError } from '@owlmeans/error'
290
+
291
+ // Create a resilient error
292
+ const error = new ResilientError('ValidationError', 'Invalid email format')
293
+
294
+ // Ensure any error is resilient
295
+ const resilientError = enuserError(new Error('Something went wrong'))
296
+ ```
297
+
298
+ ### Custom Error Types
299
+
300
+ ```typescript
301
+ import { ResilientError } from '@owlmeans/error'
302
+
303
+ class ValidationError extends ResilientError {
304
+ static typeName = 'ValidationError'
305
+
306
+ constructor(field: string, message: string) {
307
+ super(ValidationError.typeName, `${field}: ${message}`)
308
+ }
309
+ }
310
+
311
+ // Register the custom error type
312
+ ResilientError.registerErrorClass(ValidationError)
313
+ ```
314
+
315
+ ### Network Error Transmission
316
+
317
+ ```typescript
318
+ import { ResilientError, marshalError, enuserError } from '@owlmeans/error'
319
+
320
+ // Server side - marshal error for transmission
321
+ function handleServerError(error: Error) {
322
+ const marshaled = marshalError(error)
323
+ return { error: marshaled.message }
324
+ }
325
+
326
+ // Client side - unmarshal received error
327
+ function handleClientError(errorMessage: string) {
328
+ const error = new Error(errorMessage)
329
+ const resilientError = enuserError(error)
330
+ return resilientError
331
+ }
332
+ ```
333
+
334
+ ### Error Conversion System
335
+
336
+ ```typescript
337
+ import { ResilientError, createErrorConverter } from '@owlmeans/error'
338
+
339
+ class NetworkError extends ResilientError {
340
+ static typeName = 'NetworkError'
341
+ }
342
+
343
+ // Register converter for fetch errors
344
+ const converter = createErrorConverter(NetworkError, TypeError)
345
+ ResilientError.converters.push(converter)
346
+
347
+ // Now TypeError instances will be automatically converted
348
+ const converted = ResilientError.ensure(new TypeError('Network failure'))
349
+ // converted will be a NetworkError instance
350
+ ```
351
+
352
+ ### Working with ValueOrError
353
+
354
+ ```typescript
355
+ import { ValueOrError, ResilientError } from '@owlmeans/error'
356
+
357
+ function fetchUserData(id: string): ValueOrError<User> {
358
+ try {
359
+ // Simulate API call
360
+ return { id, name: 'John Doe' }
361
+ } catch (error) {
362
+ return new ResilientError('FetchError', 'Failed to fetch user')
363
+ }
364
+ }
365
+
366
+ const result = fetchUserData('123')
367
+ if (result instanceof ResilientError) {
368
+ console.error('Error:', result.message)
369
+ } else {
370
+ console.log('User:', result.name)
371
+ }
372
+ ```
373
+
374
+ ## Best Practices
375
+
376
+ 1. **Define Custom Error Types**: Create specific error classes for different error categories
377
+ 2. **Register Error Classes**: Use `registerErrorClass()` to enable automatic conversion
378
+ 3. **Use Helper Functions**: Leverage `enuserError()` and `marshalError()` for common operations
379
+ 4. **Implement finalizeUnmarshal()**: Override in custom error classes for post-processing
380
+ 5. **Type Safety**: Use `ValueOrError<T>` type for functions that may return errors
381
+
382
+ ## Integration with OwlMeans Common
383
+
384
+ This package integrates with the broader OwlMeans Common library ecosystem, following the established patterns for:
385
+ - **Types**: Error-related type definitions
386
+ - **Helpers**: Utility functions for error handling
387
+ - **Service**: Domain-specific error handling logic
388
+ - **i18n**: Internationalization support for error messages
389
+
390
+ The error system is designed to work seamlessly across different OwlMeans packages including server, client, and web implementations.
package/build/.gitkeep ADDED
File without changes
@@ -0,0 +1,3 @@
1
+ export declare const SEPARATOR = "|||";
2
+ export declare const RESILENT_ERROR = "ResilientError";
3
+ //# sourceMappingURL=consts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,SAAS,QAAQ,CAAA;AAE9B,eAAO,MAAM,cAAc,mBAAmB,CAAA"}
@@ -0,0 +1,3 @@
1
+ export const SEPARATOR = '|||';
2
+ export const RESILENT_ERROR = 'ResilientError';
3
+ //# sourceMappingURL=consts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,CAAA;AAE9B,MAAM,CAAC,MAAM,cAAc,GAAG,gBAAgB,CAAA"}
@@ -0,0 +1,4 @@
1
+ import { ResilientError } from './resilient.js';
2
+ export declare const enuserError: <T extends ResilientError = ResilientError>(err: Error | string, throwOnUnknown?: boolean) => T;
3
+ export declare const marshalError: (err: Error | string) => Error;
4
+ //# sourceMappingURL=helper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAE/C,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,cAAc,wBAAwB,KAAK,GAAG,MAAM,mBAAmB,OAAO,KAAG,CACtE,CAAA;AAEjD,eAAO,MAAM,YAAY,QAAS,KAAK,GAAG,MAAM,KAAG,KACC,CAAA"}
@@ -0,0 +1,4 @@
1
+ import { ResilientError } from './resilient.js';
2
+ export const enuserError = (err, throwOnUnknown) => ResilientError.ensure(err, throwOnUnknown);
3
+ export const marshalError = (err) => ResilientError.marshal(ResilientError.ensure(err));
4
+ //# sourceMappingURL=helper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAE/C,MAAM,CAAC,MAAM,WAAW,GAAG,CAA4C,GAAmB,EAAE,cAAwB,EAAK,EAAE,CACzH,cAAc,CAAC,MAAM,CAAC,GAAG,EAAE,cAAc,CAAM,CAAA;AAEjD,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,GAAmB,EAAS,EAAE,CACzD,cAAc,CAAC,OAAO,CAAC,cAAc,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA"}
@@ -0,0 +1,4 @@
1
+ {
2
+ "minLength": "The value is too short",
3
+ "maxLength": "The value is too long"
4
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=i18n.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":""}
package/build/i18n.js ADDED
@@ -0,0 +1,4 @@
1
+ import { addI18nLib } from '@owlmeans/i18n';
2
+ import en from './i18n/en.json' with { type: 'json' };
3
+ addI18nLib('en', 'errors', en);
4
+ //# sourceMappingURL=i18n.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAE3C,OAAO,EAAE,MAAM,gBAAgB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AAErD,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAA"}
@@ -0,0 +1,6 @@
1
+ export * from './resilient.js';
2
+ export * from './helper.js';
3
+ export * from './consts.js';
4
+ export * from './types.js';
5
+ export * from './i18n.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA"}
package/build/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export * from './resilient.js';
2
+ export * from './helper.js';
3
+ export * from './consts.js';
4
+ export * from './types.js';
5
+ export * from './i18n.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA"}
@@ -0,0 +1,15 @@
1
+ import type { Converter, ResilientErrorConstructor } from './types.js';
2
+ export declare class ResilientError extends Error {
3
+ static separator: string;
4
+ static typeName: string;
5
+ static converters: Converter[];
6
+ static registerErrorClass(resilientErrorClass: ResilientErrorConstructor, errorClass?: ErrorConstructor): Converter;
7
+ static ensure(err: Error | string, throwOnUnknown?: boolean): ResilientError;
8
+ static marshal(err: Error): Error;
9
+ type: string;
10
+ oiriginalStack?: string;
11
+ constructor(type: string, message: string, stack?: string);
12
+ marshal(): Error;
13
+ finalizeUnmarshal(): void;
14
+ }
15
+ //# sourceMappingURL=resilient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resilient.d.ts","sourceRoot":"","sources":["../src/resilient.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAA;AAGtE,qBAAa,cAAe,SAAQ,KAAK;IACvC,OAAc,SAAS,EAAE,MAAM,CAAY;IAE3C,OAAc,QAAQ,EAAE,MAAM,CAAiB;IAE/C,OAAc,UAAU,EAAE,SAAS,EAAE,CAAK;WAE5B,kBAAkB,CAAC,mBAAmB,EAAE,yBAAyB,EAAE,UAAU,CAAC,EAAE,gBAAgB,GAAG,SAAS;WAO5G,MAAM,CAAC,GAAG,EAAE,KAAK,GAAG,MAAM,EAAE,cAAc,CAAC,EAAE,OAAO,GAAG,cAAc;WA8BrE,OAAO,CAAC,GAAG,EAAE,KAAK,GAAG,KAAK;IAQjC,IAAI,EAAE,MAAM,CAAiB;IAE7B,cAAc,CAAC,EAAE,MAAM,CAAA;gBAElB,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM;IAUzD,OAAO,IAAI,KAAK;IAIhB,iBAAiB,IAAI,IAAI;CAC1B"}
@@ -0,0 +1,63 @@
1
+ import { RESILENT_ERROR, SEPARATOR } from './consts.js';
2
+ import { createErrorConverter } from './utils.js';
3
+ export class ResilientError extends Error {
4
+ static separator = SEPARATOR;
5
+ static typeName = RESILENT_ERROR;
6
+ static converters = [];
7
+ static registerErrorClass(resilientErrorClass, errorClass) {
8
+ const converter = createErrorConverter(resilientErrorClass, errorClass);
9
+ this.converters.push(converter);
10
+ return converter;
11
+ }
12
+ static ensure(err, throwOnUnknown) {
13
+ err = typeof err === 'string' ? new Error(err) : err;
14
+ if (err instanceof ResilientError) {
15
+ return err;
16
+ }
17
+ // We don't proceed SyntaxError - system should crash in this case
18
+ if (err instanceof SyntaxError) {
19
+ throw err;
20
+ }
21
+ // Umarshal marhalled error that is wrapepd to ordinary error
22
+ const unmarhaller = this.converters.toReversed().find(converter => converter.isMarshaled(err));
23
+ if (unmarhaller != null) {
24
+ return unmarhaller.unmarshal(err);
25
+ }
26
+ // Convert object of Error subtypes to ResilientError subtype
27
+ const converter = this.converters.find(converter => converter.match(err));
28
+ if (converter != null) {
29
+ return converter.convert(err);
30
+ }
31
+ if (throwOnUnknown === true) {
32
+ throw err;
33
+ }
34
+ return new ResilientError(this.typeName, err.message, err.stack);
35
+ }
36
+ static marshal(err) {
37
+ if (err instanceof ResilientError) {
38
+ return new Error([err.type, err.message, err.oiriginalStack].join(this.separator));
39
+ }
40
+ return new Error([this.typeName, err.message, err.stack].join(this.separator));
41
+ }
42
+ type = RESILENT_ERROR;
43
+ oiriginalStack;
44
+ constructor(type, message, stack) {
45
+ super(message);
46
+ this.type = type;
47
+ if (stack != null) {
48
+ this.oiriginalStack = stack;
49
+ }
50
+ else {
51
+ this.oiriginalStack = this.stack;
52
+ }
53
+ }
54
+ marshal() {
55
+ return ResilientError.marshal(this);
56
+ }
57
+ finalizeUnmarshal() { }
58
+ }
59
+ ResilientError.converters.push({
60
+ ...createErrorConverter(ResilientError),
61
+ match: () => true
62
+ });
63
+ //# sourceMappingURL=resilient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resilient.js","sourceRoot":"","sources":["../src/resilient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { ResilientError } from './resilient.js';
2
+ export type ValueOrError<T> = T | ResilientError;
3
+ export interface Converter {
4
+ match: (err: Error) => boolean;
5
+ convert: (err: Error) => ResilientError;
6
+ isMarshaled: (err: Error) => boolean;
7
+ unmarshal: (err: Error) => ResilientError;
8
+ }
9
+ export interface ResilientErrorConstructor<T extends ResilientError = ResilientError> {
10
+ new (type: string, message: string, stack?: string): T;
11
+ new (message: string, stack?: string): T;
12
+ typeName: string;
13
+ }
14
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAEpD,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,cAAc,CAAA;AAEhD,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,OAAO,CAAA;IAC9B,OAAO,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,cAAc,CAAA;IACvC,WAAW,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,OAAO,CAAA;IACpC,SAAS,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,cAAc,CAAA;CAC1C;AAED,MAAM,WAAW,yBAAyB,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc;IAClF,KAAK,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,CAAC,CAAA;IACtD,KAAK,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,CAAC,CAAA;IACxC,QAAQ,EAAE,MAAM,CAAA;CACjB"}
package/build/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,5 @@
1
+ import { ResilientError } from './resilient.js';
2
+ import type { Converter, ResilientErrorConstructor } from './types.js';
3
+ export declare const createErrorConverter: (resilientErrorClass: ResilientErrorConstructor, errorClass?: ErrorConstructor) => Converter;
4
+ export declare const unmarshal: <T extends ResilientError = ResilientError>(errorClass: ResilientErrorConstructor) => (err: Error) => T;
5
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAA;AAEtE,eAAO,MAAM,oBAAoB,wBACV,yBAAyB,eACjC,gBAAgB,KAC5B,SAQF,CAAA;AAED,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS,cAAc,+BAA+B,yBAAyB,WAClG,KAAK,KAAG,CAiBb,CAAA"}
package/build/utils.js ADDED
@@ -0,0 +1,26 @@
1
+ import { ResilientError } from './resilient.js';
2
+ export const createErrorConverter = (resilientErrorClass, errorClass) => {
3
+ return {
4
+ match: err => errorClass != null && err instanceof errorClass,
5
+ convert: err => new resilientErrorClass(err.message, err.stack),
6
+ isMarshaled: err => err.message.startsWith(resilientErrorClass.typeName + ResilientError.separator),
7
+ unmarshal: unmarshal(resilientErrorClass)
8
+ };
9
+ };
10
+ export const unmarshal = (errorClass) => (err) => {
11
+ if (err instanceof errorClass) {
12
+ return err;
13
+ }
14
+ const args = err.message.split(ResilientError.separator, 3);
15
+ if (args.length < 2) {
16
+ throw SyntaxError('Invalid marshaled error');
17
+ }
18
+ if (args[0] === errorClass.typeName) {
19
+ args.shift();
20
+ }
21
+ const error = new errorClass(...args);
22
+ error.message = args[0];
23
+ error.finalizeUnmarshal();
24
+ return error;
25
+ };
26
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAG/C,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,mBAA8C,EAC9C,UAA6B,EAClB,EAAE;IACb,OAAO;QACL,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC,UAAU,IAAI,IAAI,IAAI,GAAG,YAAY,UAAU;QAC7D,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC,IAAI,mBAAmB,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC;QAC/D,WAAW,EAAE,GAAG,CAAC,EAAE,CACjB,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,mBAAmB,CAAC,QAAQ,GAAG,cAAc,CAAC,SAAS,CAAC;QACjF,SAAS,EAAE,SAAS,CAAC,mBAAmB,CAAC;KAC1C,CAAA;AACH,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,SAAS,GAAG,CAA4C,UAAqC,EAAE,EAAE,CAC5G,CAAC,GAAU,EAAK,EAAE;IAChB,IAAI,GAAG,YAAY,UAAU,EAAE,CAAC;QAC9B,OAAO,GAAQ,CAAA;IACjB,CAAC;IACD,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,SAAS,EAAE,CAAC,CAA8B,CAAA;IACxF,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpB,MAAM,WAAW,CAAC,yBAAyB,CAAC,CAAA;IAC9C,CAAC;IAED,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,UAAU,CAAC,QAAQ,EAAE,CAAC;QACpC,IAAI,CAAC,KAAK,EAAE,CAAA;IACd,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,GAAG,IAAI,CAAC,CAAA;IACrC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;IACvB,KAAK,CAAC,iBAAiB,EAAE,CAAA;IACzB,OAAO,KAAU,CAAA;AACnB,CAAC,CAAA"}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@owlmeans/error",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "build": "tsc -b",
7
+ "dev": "sleep 156 && nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
8
+ "watch": "tsc -b -w --preserveWatchOutput --pretty"
9
+ },
10
+ "main": "build/index.js",
11
+ "module": "build/index.js",
12
+ "types": "build/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "import": "./build/index.js",
16
+ "require": "./build/index.js",
17
+ "default": "./build/index.js",
18
+ "module": "./build/index.js",
19
+ "types": "./build/index.d.ts"
20
+ }
21
+ },
22
+ "dependencies": {
23
+ "@owlmeans/i18n": "^0.1.0"
24
+ },
25
+ "devDependencies": {
26
+ "nodemon": "^3.1.7",
27
+ "npm-check": "^6.0.1",
28
+ "typescript": "^5.6.3"
29
+ },
30
+ "private": false,
31
+ "publishConfig": {
32
+ "access": "public"
33
+ }
34
+ }
package/src/consts.ts ADDED
@@ -0,0 +1,4 @@
1
+
2
+ export const SEPARATOR = '|||'
3
+
4
+ export const RESILENT_ERROR = 'ResilientError'
package/src/helper.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { ResilientError } from './resilient.js'
2
+
3
+ export const enuserError = <T extends ResilientError = ResilientError>(err: Error | string, throwOnUnknown?: boolean): T =>
4
+ ResilientError.ensure(err, throwOnUnknown) as T
5
+
6
+ export const marshalError = (err: Error | string): Error =>
7
+ ResilientError.marshal(ResilientError.ensure(err))
@@ -0,0 +1,4 @@
1
+ {
2
+ "minLength": "The value is too short",
3
+ "maxLength": "The value is too long"
4
+ }
package/src/i18n.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { addI18nLib } from '@owlmeans/i18n'
2
+
3
+ import en from './i18n/en.json' with { type: 'json' }
4
+
5
+ addI18nLib('en', 'errors', en)
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+
2
+ export * from './resilient.js'
3
+ export * from './helper.js'
4
+ export * from './consts.js'
5
+ export * from './types.js'
6
+ export * from './i18n.js'
@@ -0,0 +1,81 @@
1
+ import { RESILENT_ERROR, SEPARATOR } from './consts.js'
2
+ import type { Converter, ResilientErrorConstructor } from './types.js'
3
+ import { createErrorConverter } from './utils.js'
4
+
5
+ export class ResilientError extends Error {
6
+ public static separator: string = SEPARATOR
7
+
8
+ public static typeName: string = RESILENT_ERROR
9
+
10
+ public static converters: Converter[] = []
11
+
12
+ public static registerErrorClass(resilientErrorClass: ResilientErrorConstructor, errorClass?: ErrorConstructor): Converter {
13
+ const converter = createErrorConverter(resilientErrorClass, errorClass)
14
+ this.converters.push(converter)
15
+
16
+ return converter
17
+ }
18
+
19
+ public static ensure(err: Error | string, throwOnUnknown?: boolean): ResilientError {
20
+ err = typeof err === 'string' ? new Error(err) : err
21
+ if (err instanceof ResilientError) {
22
+ return err
23
+ }
24
+
25
+ // We don't proceed SyntaxError - system should crash in this case
26
+ if (err instanceof SyntaxError) {
27
+ throw err
28
+ }
29
+
30
+ // Umarshal marhalled error that is wrapepd to ordinary error
31
+ const unmarhaller = this.converters.toReversed().find(converter => converter.isMarshaled(err))
32
+ if (unmarhaller != null) {
33
+ return unmarhaller.unmarshal(err)
34
+ }
35
+
36
+ // Convert object of Error subtypes to ResilientError subtype
37
+ const converter = this.converters.find(converter => converter.match(err))
38
+ if (converter != null) {
39
+ return converter.convert(err)
40
+ }
41
+
42
+ if (throwOnUnknown === true) {
43
+ throw err
44
+ }
45
+
46
+ return new ResilientError(this.typeName, err.message, err.stack)
47
+ }
48
+
49
+ public static marshal(err: Error): Error {
50
+ if (err instanceof ResilientError) {
51
+ return new Error([err.type, err.message, err.oiriginalStack].join(this.separator))
52
+ }
53
+
54
+ return new Error([this.typeName, err.message, err.stack].join(this.separator))
55
+ }
56
+
57
+ public type: string = RESILENT_ERROR
58
+
59
+ public oiriginalStack?: string
60
+
61
+ constructor(type: string, message: string, stack?: string) {
62
+ super(message)
63
+ this.type = type
64
+ if (stack != null) {
65
+ this.oiriginalStack = stack
66
+ } else {
67
+ this.oiriginalStack = this.stack
68
+ }
69
+ }
70
+
71
+ marshal(): Error {
72
+ return ResilientError.marshal(this)
73
+ }
74
+
75
+ finalizeUnmarshal(): void { }
76
+ }
77
+
78
+ ResilientError.converters.push({
79
+ ...createErrorConverter(ResilientError as ResilientErrorConstructor),
80
+ match: () => true
81
+ })
package/src/types.ts ADDED
@@ -0,0 +1,16 @@
1
+ import type { ResilientError } from './resilient.js'
2
+
3
+ export type ValueOrError<T> = T | ResilientError
4
+
5
+ export interface Converter {
6
+ match: (err: Error) => boolean
7
+ convert: (err: Error) => ResilientError
8
+ isMarshaled: (err: Error) => boolean
9
+ unmarshal: (err: Error) => ResilientError
10
+ }
11
+
12
+ export interface ResilientErrorConstructor<T extends ResilientError = ResilientError> {
13
+ new (type: string, message: string, stack?: string): T
14
+ new (message: string, stack?: string): T
15
+ typeName: string
16
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,35 @@
1
+ import { ResilientError } from './resilient.js'
2
+ import type { Converter, ResilientErrorConstructor } from './types.js'
3
+
4
+ export const createErrorConverter = (
5
+ resilientErrorClass: ResilientErrorConstructor,
6
+ errorClass?: ErrorConstructor
7
+ ): Converter => {
8
+ return {
9
+ match: err => errorClass != null && err instanceof errorClass,
10
+ convert: err => new resilientErrorClass(err.message, err.stack),
11
+ isMarshaled: err =>
12
+ err.message.startsWith(resilientErrorClass.typeName + ResilientError.separator),
13
+ unmarshal: unmarshal(resilientErrorClass)
14
+ }
15
+ }
16
+
17
+ export const unmarshal = <T extends ResilientError = ResilientError>(errorClass: ResilientErrorConstructor) =>
18
+ (err: Error): T => {
19
+ if (err instanceof errorClass) {
20
+ return err as T
21
+ }
22
+ const args = err.message.split(ResilientError.separator, 3) as [string, string, string?]
23
+ if (args.length < 2) {
24
+ throw SyntaxError('Invalid marshaled error')
25
+ }
26
+
27
+ if (args[0] === errorClass.typeName) {
28
+ args.shift()
29
+ }
30
+
31
+ const error = new errorClass(...args)
32
+ error.message = args[0]
33
+ error.finalizeUnmarshal()
34
+ return error as T
35
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": [
3
+ "../tsconfig.default.json",
4
+ ],
5
+ "compilerOptions": {
6
+ "rootDir": "./src/", /* Specify the root folder within your source files. */
7
+ "outDir": "./build/", /* Specify an output folder for all emitted files. */
8
+ },
9
+ "exclude": [
10
+ "./dist/**/*",
11
+ "./build/**/*",
12
+ "./*.ts"
13
+ ]
14
+ }
@@ -0,0 +1 @@
1
+ {"root":["./src/consts.ts","./src/helper.ts","./src/i18n.ts","./src/index.ts","./src/resilient.ts","./src/types.ts","./src/utils.ts"],"version":"5.6.3"}