@gilles-coudert/pure-trace 1.0.5 → 1.0.7
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 +48 -5
- package/lib/pure_message.d.ts +31 -23
- package/lib/pure_message.js +6 -28
- package/lib/pure_result.d.ts +11 -11
- package/lib/pure_result.js +31 -17
- package/lib/pure_result_async.d.ts +3 -3
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ if (result.isSuccess()) {
|
|
|
56
56
|
## Mental model
|
|
57
57
|
|
|
58
58
|
- A **Result** is either `Success<S>` or `Failure`
|
|
59
|
-
- A **Failure** contains **error
|
|
59
|
+
- A **Failure** contains **error message**
|
|
60
60
|
- Both Success and Failure can accumulate **trace messages**
|
|
61
61
|
- Errors are just messages with `kind: "error"`
|
|
62
62
|
|
|
@@ -68,6 +68,8 @@ type Message = {
|
|
|
68
68
|
type: string;
|
|
69
69
|
code: string;
|
|
70
70
|
data?: unknown;
|
|
71
|
+
issuer?: string; // optional: who emitted the message
|
|
72
|
+
localizedMessage?: string; // optional: localized user message
|
|
71
73
|
};
|
|
72
74
|
```
|
|
73
75
|
|
|
@@ -78,7 +80,13 @@ type Message = {
|
|
|
78
80
|
```ts
|
|
79
81
|
const ok = new Success(42);
|
|
80
82
|
|
|
81
|
-
const ko = generateFailure(
|
|
83
|
+
const ko = generateFailure(
|
|
84
|
+
'processError',
|
|
85
|
+
'notFound',
|
|
86
|
+
{ id: '123' },
|
|
87
|
+
'userService', // issuer (optional)
|
|
88
|
+
'Utilisateur non trouvé.', // localizedMessage (optional)
|
|
89
|
+
);
|
|
82
90
|
```
|
|
83
91
|
|
|
84
92
|
### Compose (sync)
|
|
@@ -100,13 +108,33 @@ import { generateMessage } from '@gilles-coudert/pure-trace';
|
|
|
100
108
|
|
|
101
109
|
const traced = parseAge('25').tap((r) => {
|
|
102
110
|
r.addTraces(
|
|
103
|
-
generateMessage(
|
|
104
|
-
|
|
105
|
-
|
|
111
|
+
generateMessage(
|
|
112
|
+
'information',
|
|
113
|
+
'information',
|
|
114
|
+
'ageParsed',
|
|
115
|
+
{ input: '25' },
|
|
116
|
+
'userService', // issuer (optional)
|
|
117
|
+
'Âge analysé avec succès.', // localizedMessage (optional)
|
|
118
|
+
),
|
|
106
119
|
);
|
|
107
120
|
});
|
|
108
121
|
```
|
|
109
122
|
|
|
123
|
+
### Enrich error messages
|
|
124
|
+
|
|
125
|
+
You can add issuer to errors and traces:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { generateError } from '@gilles-coudert/pure-trace';
|
|
129
|
+
|
|
130
|
+
const error = generateError({
|
|
131
|
+
type: 'processError',
|
|
132
|
+
code: 'invalidAgeFormat',
|
|
133
|
+
data: { input: 'abc' },
|
|
134
|
+
issuer: 'userService', // optional
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
110
138
|
### Async flows
|
|
111
139
|
|
|
112
140
|
```ts
|
|
@@ -138,6 +166,21 @@ generateFailure('processError', 'userNotFound', { userId: '123' });
|
|
|
138
166
|
// fr: "L'utilisateur {userId} n'a pas été trouvé"
|
|
139
167
|
```
|
|
140
168
|
|
|
169
|
+
Sometimes, translations have to be done at the source.
|
|
170
|
+
In these cases, the localized message can be specified directly.
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import { generateError } from '@gilles-coudert/pure-trace';
|
|
174
|
+
|
|
175
|
+
const error = generateError({
|
|
176
|
+
type: 'processError',
|
|
177
|
+
code: 'invalidAgeFormat',
|
|
178
|
+
data: { input: 'abc' },
|
|
179
|
+
issuer: 'userService', // optional
|
|
180
|
+
localizedMessage: "L'âge indiqué est incorrect.",
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
141
184
|
## What makes PureTrace different?
|
|
142
185
|
|
|
143
186
|
- Traces propagate automatically with the Result
|
package/lib/pure_message.d.ts
CHANGED
|
@@ -4,48 +4,56 @@ export declare const messageSchema: z.ZodObject<{
|
|
|
4
4
|
type: z.ZodString;
|
|
5
5
|
code: z.ZodString;
|
|
6
6
|
data: z.ZodOptional<z.ZodJSONSchema>;
|
|
7
|
+
issuer: z.ZodOptional<z.ZodString>;
|
|
8
|
+
localizedMessage: z.ZodOptional<z.ZodString>;
|
|
7
9
|
}, z.core.$strip>;
|
|
8
10
|
export type Message = z.infer<typeof messageSchema>;
|
|
9
11
|
export declare const errorSchema: z.ZodObject<{
|
|
10
12
|
type: z.ZodString;
|
|
11
13
|
code: z.ZodString;
|
|
12
14
|
data: z.ZodOptional<z.ZodJSONSchema>;
|
|
15
|
+
issuer: z.ZodOptional<z.ZodString>;
|
|
16
|
+
localizedMessage: z.ZodOptional<z.ZodString>;
|
|
13
17
|
kind: z.ZodLiteral<"error">;
|
|
14
18
|
}, z.core.$strip>;
|
|
15
19
|
export type Error = z.infer<typeof errorSchema>;
|
|
16
|
-
|
|
20
|
+
type NativeErrorDefinitions = {
|
|
17
21
|
readonly pureTraceInternalError: z.ZodObject<{
|
|
18
|
-
message: z.ZodObject<
|
|
19
|
-
zodError: z.ZodObject<
|
|
22
|
+
message: z.ZodObject<z.core.$ZodLooseShape, z.core.$loose>;
|
|
23
|
+
zodError: z.ZodObject<z.core.$ZodLooseShape, z.core.$loose>;
|
|
20
24
|
}, z.core.$strip>;
|
|
21
25
|
readonly technicalIssue: z.ZodJSONSchema;
|
|
22
26
|
readonly processError: z.ZodJSONSchema;
|
|
23
27
|
};
|
|
24
|
-
type NativeErrorDefinitions = typeof nativeErrorDefinitions;
|
|
25
28
|
export type NativeErrorType = keyof NativeErrorDefinitions;
|
|
26
|
-
export type NativeErrorData<T extends NativeErrorType> = z.infer<
|
|
27
|
-
export declare function generateError<T extends NativeErrorType>(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
export type NativeErrorData<T extends NativeErrorType> = z.infer<NativeErrorDefinitions[T]>;
|
|
30
|
+
export declare function generateError<T extends NativeErrorType>(options: {
|
|
31
|
+
type: T;
|
|
32
|
+
code: string;
|
|
33
|
+
data?: NativeErrorData<T>;
|
|
34
|
+
issuer?: string;
|
|
35
|
+
localizedMessage?: string;
|
|
36
|
+
}): Error;
|
|
37
|
+
type NativeMessageDefinitions = {
|
|
38
|
+
error: NativeErrorDefinitions;
|
|
39
|
+
information: {
|
|
40
|
+
warning: z.ZodJSONSchema;
|
|
41
|
+
information: z.ZodJSONSchema;
|
|
36
42
|
};
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
};
|
|
41
|
-
readonly metric: {
|
|
42
|
-
readonly start: z.ZodISODateTime;
|
|
43
|
-
readonly stop: z.ZodISODateTime;
|
|
43
|
+
metric: {
|
|
44
|
+
start: z.ZodISODateTime;
|
|
45
|
+
stop: z.ZodISODateTime;
|
|
44
46
|
};
|
|
45
47
|
};
|
|
46
|
-
type NativeMessageDefinitions = typeof nativeMessageDefinitions;
|
|
47
48
|
type NativeMessageKind = keyof NativeMessageDefinitions;
|
|
48
49
|
type NativeMessageType<K extends NativeMessageKind> = keyof NativeMessageDefinitions[K];
|
|
49
50
|
type NativeMessageData<K extends NativeMessageKind, T extends NativeMessageType<K>> = NativeMessageDefinitions[K][T];
|
|
50
|
-
export declare function generateMessage<K extends NativeMessageKind, T extends NativeMessageType<K>>(
|
|
51
|
+
export declare function generateMessage<K extends NativeMessageKind, T extends NativeMessageType<K>>(options: {
|
|
52
|
+
kind: K;
|
|
53
|
+
type: T;
|
|
54
|
+
code: string;
|
|
55
|
+
data?: NativeMessageData<K, T>;
|
|
56
|
+
issuer?: string;
|
|
57
|
+
localizedMessage?: string;
|
|
58
|
+
}): Message;
|
|
51
59
|
export {};
|
package/lib/pure_message.js
CHANGED
|
@@ -12,42 +12,20 @@ exports.messageSchema = zod_1.default.object({
|
|
|
12
12
|
type: zod_1.default.string(),
|
|
13
13
|
code: zod_1.default.string(),
|
|
14
14
|
data: zod_1.default.json().optional(),
|
|
15
|
+
issuer: zod_1.default.string().min(1).optional(),
|
|
16
|
+
localizedMessage: zod_1.default.string().min(1).optional(),
|
|
15
17
|
});
|
|
16
18
|
exports.errorSchema = exports.messageSchema.extend({
|
|
17
19
|
kind: zod_1.default.literal('error'),
|
|
18
20
|
});
|
|
19
|
-
|
|
20
|
-
pureTraceInternalError: zod_1.default.object({
|
|
21
|
-
message: zod_1.default.looseObject({}),
|
|
22
|
-
zodError: zod_1.default.looseObject({}),
|
|
23
|
-
}),
|
|
24
|
-
technicalIssue: zod_1.default.json(),
|
|
25
|
-
processError: zod_1.default.json(),
|
|
26
|
-
};
|
|
27
|
-
function generateError(type, code, data) {
|
|
21
|
+
function generateError(options) {
|
|
28
22
|
return {
|
|
29
23
|
kind: 'error',
|
|
30
|
-
|
|
31
|
-
code,
|
|
32
|
-
data,
|
|
24
|
+
...options,
|
|
33
25
|
};
|
|
34
26
|
}
|
|
35
|
-
|
|
36
|
-
error: nativeErrorDefinitions,
|
|
37
|
-
information: {
|
|
38
|
-
warning: zod_1.default.json(),
|
|
39
|
-
information: zod_1.default.json(),
|
|
40
|
-
},
|
|
41
|
-
metric: {
|
|
42
|
-
start: zod_1.default.iso.datetime(),
|
|
43
|
-
stop: zod_1.default.iso.datetime(),
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
function generateMessage(kind, type, code, data) {
|
|
27
|
+
function generateMessage(options) {
|
|
47
28
|
return {
|
|
48
|
-
|
|
49
|
-
type,
|
|
50
|
-
code,
|
|
51
|
-
data,
|
|
29
|
+
...options,
|
|
52
30
|
};
|
|
53
31
|
}
|
package/lib/pure_result.d.ts
CHANGED
|
@@ -23,12 +23,12 @@ export declare class Success<S> extends PureResult<S> {
|
|
|
23
23
|
isSuccess(): this is Success<S>;
|
|
24
24
|
isFailure(): this is Failure;
|
|
25
25
|
mapSuccess<S2>(f: (value: S) => Success<S2>): Result<S2>;
|
|
26
|
-
mapFailure(
|
|
27
|
-
mapBoth<S2>(onSuccess: (value: S) => Success<S2>,
|
|
26
|
+
mapFailure(_: (errors: Message[]) => Failure): Result<S>;
|
|
27
|
+
mapBoth<S2>(onSuccess: (value: S) => Success<S2>, _: (errors: Message[]) => Failure): Result<S2>;
|
|
28
28
|
chainSuccess<S2>(f: (value: S) => Result<S2>): Result<S2>;
|
|
29
|
-
chainFailure<S2>(
|
|
30
|
-
chainBoth<S2, S3>(onSuccess: (value: S) => Result<S2>,
|
|
31
|
-
convertFailureToSuccess(
|
|
29
|
+
chainFailure<S2>(_: (errors: Message[]) => Result<S2>): Result<S | S2>;
|
|
30
|
+
chainBoth<S2, S3>(onSuccess: (value: S) => Result<S2>, _: (errors: Message[]) => Result<S3>): Result<S2 | S3>;
|
|
31
|
+
convertFailureToSuccess(_: S): Success<S>;
|
|
32
32
|
}
|
|
33
33
|
export declare class Failure extends PureResult<never> {
|
|
34
34
|
private readonly errors;
|
|
@@ -37,15 +37,15 @@ export declare class Failure extends PureResult<never> {
|
|
|
37
37
|
addErrors(errors: Error[]): this;
|
|
38
38
|
isSuccess(): this is Success<never>;
|
|
39
39
|
isFailure(): this is Failure;
|
|
40
|
-
mapSuccess<S2>(
|
|
40
|
+
mapSuccess<S2>(_: (value: never) => Success<S2>): Result<S2>;
|
|
41
41
|
mapFailure(f: (errors: Message[]) => Failure): Result<never>;
|
|
42
|
-
mapBoth<S2>(
|
|
43
|
-
chainSuccess<S2>(
|
|
44
|
-
chainFailure<S2>(f: (errors: Message[]) => Result<S2>): Result<
|
|
45
|
-
chainBoth<S2, S3>(
|
|
42
|
+
mapBoth<S2>(_: (value: never) => Success<S2>, onFailure: (errors: Message[]) => Failure): Result<S2>;
|
|
43
|
+
chainSuccess<S2>(_: (value: never) => Result<S2>): Result<S2>;
|
|
44
|
+
chainFailure<S2>(f: (errors: Message[]) => Result<S2>): Result<S2>;
|
|
45
|
+
chainBoth<S2, S3>(_: (value: never) => Result<S2>, onFailure: (errors: Message[]) => Result<S3>): Result<S2 | S3>;
|
|
46
46
|
convertFailureToSuccess(defaultValue: never): Success<never>;
|
|
47
47
|
}
|
|
48
|
-
export declare function generateFailure<T extends NativeErrorType>(type: T, code: string, data: NativeErrorData<T
|
|
48
|
+
export declare function generateFailure<T extends NativeErrorType>(type: T, code: string, data: NativeErrorData<T>, issuer?: string, localizedMessage?: string): Failure;
|
|
49
49
|
export declare class GetResult {
|
|
50
50
|
static fromThrowable<X>(functionToAudit: () => X, onFailure: (catchedError: unknown) => Failure): Result<X>;
|
|
51
51
|
static fromResultArray<X>(results: Result<X>[], firstFailureOnly?: boolean): Result<X[]>;
|
package/lib/pure_result.js
CHANGED
|
@@ -21,9 +21,13 @@ class PureResult {
|
|
|
21
21
|
for (let trace of traces) {
|
|
22
22
|
const zodParseResult = pure_message_1.messageSchema.safeParse(trace);
|
|
23
23
|
if (!zodParseResult.success) {
|
|
24
|
-
trace = (0, pure_message_1.generateError)(
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
trace = (0, pure_message_1.generateError)({
|
|
25
|
+
type: 'pureTraceInternalError',
|
|
26
|
+
code: 'invalidTraceMessage',
|
|
27
|
+
data: {
|
|
28
|
+
message: trace,
|
|
29
|
+
zodError: zod_1.default.treeifyError(zodParseResult.error),
|
|
30
|
+
},
|
|
27
31
|
});
|
|
28
32
|
}
|
|
29
33
|
this.traces.push(trace);
|
|
@@ -53,22 +57,22 @@ class Success extends PureResult {
|
|
|
53
57
|
mapSuccess(f) {
|
|
54
58
|
return f(this.value).addTraces(...this.getTraces());
|
|
55
59
|
}
|
|
56
|
-
mapFailure(
|
|
60
|
+
mapFailure(_) {
|
|
57
61
|
return this;
|
|
58
62
|
}
|
|
59
|
-
mapBoth(onSuccess,
|
|
63
|
+
mapBoth(onSuccess, _) {
|
|
60
64
|
return this.mapSuccess(onSuccess);
|
|
61
65
|
}
|
|
62
66
|
chainSuccess(f) {
|
|
63
67
|
return f(this.value).addTraces(...this.getTraces());
|
|
64
68
|
}
|
|
65
|
-
chainFailure(
|
|
69
|
+
chainFailure(_) {
|
|
66
70
|
return this;
|
|
67
71
|
}
|
|
68
|
-
chainBoth(onSuccess,
|
|
72
|
+
chainBoth(onSuccess, _) {
|
|
69
73
|
return this.chainSuccess(onSuccess);
|
|
70
74
|
}
|
|
71
|
-
convertFailureToSuccess(
|
|
75
|
+
convertFailureToSuccess(_) {
|
|
72
76
|
return this;
|
|
73
77
|
}
|
|
74
78
|
}
|
|
@@ -86,9 +90,13 @@ class Failure extends PureResult {
|
|
|
86
90
|
for (let error of errors) {
|
|
87
91
|
const zodParseResult = pure_message_1.messageSchema.safeParse(error);
|
|
88
92
|
if (!zodParseResult.success) {
|
|
89
|
-
error = (0, pure_message_1.generateError)(
|
|
90
|
-
|
|
91
|
-
|
|
93
|
+
error = (0, pure_message_1.generateError)({
|
|
94
|
+
type: 'pureTraceInternalError',
|
|
95
|
+
code: 'invalidError',
|
|
96
|
+
data: {
|
|
97
|
+
message: error,
|
|
98
|
+
zodError: zod_1.default.treeifyError(zodParseResult.error),
|
|
99
|
+
},
|
|
92
100
|
});
|
|
93
101
|
}
|
|
94
102
|
this.errors.push(error);
|
|
@@ -101,22 +109,22 @@ class Failure extends PureResult {
|
|
|
101
109
|
isFailure() {
|
|
102
110
|
return true;
|
|
103
111
|
}
|
|
104
|
-
mapSuccess(
|
|
112
|
+
mapSuccess(_) {
|
|
105
113
|
return this;
|
|
106
114
|
}
|
|
107
115
|
mapFailure(f) {
|
|
108
116
|
return f(this.getErrors()).addTraces(...this.getTraces());
|
|
109
117
|
}
|
|
110
|
-
mapBoth(
|
|
118
|
+
mapBoth(_, onFailure) {
|
|
111
119
|
return this.mapFailure(onFailure);
|
|
112
120
|
}
|
|
113
|
-
chainSuccess(
|
|
121
|
+
chainSuccess(_) {
|
|
114
122
|
return this;
|
|
115
123
|
}
|
|
116
124
|
chainFailure(f) {
|
|
117
125
|
return f(this.getErrors()).addTraces(...this.getTraces());
|
|
118
126
|
}
|
|
119
|
-
chainBoth(
|
|
127
|
+
chainBoth(_, onFailure) {
|
|
120
128
|
return this.chainFailure(onFailure);
|
|
121
129
|
}
|
|
122
130
|
convertFailureToSuccess(defaultValue) {
|
|
@@ -124,8 +132,14 @@ class Failure extends PureResult {
|
|
|
124
132
|
}
|
|
125
133
|
}
|
|
126
134
|
exports.Failure = Failure;
|
|
127
|
-
function generateFailure(type, code, data) {
|
|
128
|
-
return new Failure((0, pure_message_1.generateError)(
|
|
135
|
+
function generateFailure(type, code, data, issuer, localizedMessage) {
|
|
136
|
+
return new Failure((0, pure_message_1.generateError)({
|
|
137
|
+
type,
|
|
138
|
+
code,
|
|
139
|
+
data,
|
|
140
|
+
issuer,
|
|
141
|
+
localizedMessage,
|
|
142
|
+
}));
|
|
129
143
|
}
|
|
130
144
|
class GetResult {
|
|
131
145
|
static fromThrowable(functionToAudit, onFailure) {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Message } from './pure_message';
|
|
2
2
|
import { Success, Failure, Result } from './pure_result';
|
|
3
|
-
type ResultAsyncValue<S> = PromiseLike<Success<S>>;
|
|
4
|
-
interface ResultAsyncHelpers {
|
|
3
|
+
export type ResultAsyncValue<S> = PromiseLike<Success<S>>;
|
|
4
|
+
export interface ResultAsyncHelpers {
|
|
5
5
|
liftResult<S>(result: Result<S>): ResultAsyncValue<S>;
|
|
6
6
|
liftSuccess<S>(value: S): ResultAsyncValue<S>;
|
|
7
7
|
fromResultPromise<S>(promise: PromiseLike<Result<S>>): ResultAsyncValue<S>;
|
|
8
8
|
fromPromise<S>(promise: PromiseLike<S>, failure: (reason: unknown) => Failure): ResultAsyncValue<S>;
|
|
9
9
|
}
|
|
10
|
-
interface ResultAsync<S> extends PromiseLike<Result<S>> {
|
|
10
|
+
export interface ResultAsync<S> extends PromiseLike<Result<S>> {
|
|
11
11
|
resolve(): Promise<Result<S>>;
|
|
12
12
|
tap(f: (result: Result<S>) => void): ResultAsync<S>;
|
|
13
13
|
mapSuccess<S2>(onSuccess: (value: S) => Success<S2>): ResultAsync<S2>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gilles-coudert/pure-trace",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "A TypeScript library providing standardized, localizable, and traceable error handling by design, with observability built-in.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"prebuild": "node -e \"require('fs').rmSync('lib', { recursive: true, force: true })\"",
|
|
16
16
|
"build": "tsc",
|
|
17
|
-
"test": "jest --runInBand"
|
|
17
|
+
"test": "jest --runInBand",
|
|
18
|
+
"lint": "eslint src/**/*.ts"
|
|
18
19
|
},
|
|
19
20
|
"repository": {
|
|
20
21
|
"type": "git",
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
"globals": "^17.3.0",
|
|
46
47
|
"jest": "^30.2.0",
|
|
47
48
|
"jest-cucumber": "^4.5.0",
|
|
49
|
+
"jiti": "^2.6.1",
|
|
48
50
|
"prettier": "^3.8.1",
|
|
49
51
|
"ts-jest": "^29.4.6",
|
|
50
52
|
"typescript": "^5.9.3",
|