@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 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 messages**
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('processError', 'notFound', { id: '123' });
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('information', 'information', 'ageParsed', {
104
- input: '25',
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
@@ -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
- declare const nativeErrorDefinitions: {
20
+ type NativeErrorDefinitions = {
17
21
  readonly pureTraceInternalError: z.ZodObject<{
18
- message: z.ZodObject<{}, z.core.$loose>;
19
- zodError: z.ZodObject<{}, z.core.$loose>;
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<(typeof nativeErrorDefinitions)[T]>;
27
- export declare function generateError<T extends NativeErrorType>(type: T, code: string, data?: NativeErrorData<T>): Error;
28
- declare const nativeMessageDefinitions: {
29
- readonly error: {
30
- readonly pureTraceInternalError: z.ZodObject<{
31
- message: z.ZodObject<{}, z.core.$loose>;
32
- zodError: z.ZodObject<{}, z.core.$loose>;
33
- }, z.core.$strip>;
34
- readonly technicalIssue: z.ZodJSONSchema;
35
- readonly processError: z.ZodJSONSchema;
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
- readonly information: {
38
- readonly warning: z.ZodJSONSchema;
39
- readonly information: z.ZodJSONSchema;
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>>(kind: K, type: T, code: string, data?: NativeMessageData<K, T>): Message;
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 {};
@@ -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
- const nativeErrorDefinitions = {
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
- type,
31
- code,
32
- data,
24
+ ...options,
33
25
  };
34
26
  }
35
- const nativeMessageDefinitions = {
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
- kind,
49
- type,
50
- code,
51
- data,
29
+ ...options,
52
30
  };
53
31
  }
@@ -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(f: (errors: Message[]) => Failure): Result<S>;
27
- mapBoth<S2>(onSuccess: (value: S) => Success<S2>, onFailure: (errors: Message[]) => Failure): Result<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>(f: (errors: Message[]) => Result<S2>): Result<S | S2>;
30
- chainBoth<S2, S3>(onSuccess: (value: S) => Result<S2>, onFailure: (errors: Message[]) => Result<S3>): Result<S2 | S3>;
31
- convertFailureToSuccess(defaultValue: S): Success<S>;
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>(f: (value: never) => Success<S2>): Result<S2>;
40
+ mapSuccess<S2>(_: (value: never) => Success<S2>): Result<S2>;
41
41
  mapFailure(f: (errors: Message[]) => Failure): Result<never>;
42
- mapBoth<S2>(onSuccess: (value: never) => Success<S2>, onFailure: (errors: Message[]) => Failure): Result<S2>;
43
- chainSuccess<S2>(f: (value: never) => Result<S2>): Result<S2>;
44
- chainFailure<S2>(f: (errors: Message[]) => Result<S2>): Result<never | S2>;
45
- chainBoth<S2, S3>(onSuccess: (value: never) => Result<S2>, onFailure: (errors: Message[]) => Result<S3>): Result<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>): Failure;
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[]>;
@@ -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)('pureTraceInternalError', 'invalidTraceMessage', {
25
- message: trace,
26
- zodError: zod_1.default.treeifyError(zodParseResult.error),
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(f) {
60
+ mapFailure(_) {
57
61
  return this;
58
62
  }
59
- mapBoth(onSuccess, onFailure) {
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(f) {
69
+ chainFailure(_) {
66
70
  return this;
67
71
  }
68
- chainBoth(onSuccess, onFailure) {
72
+ chainBoth(onSuccess, _) {
69
73
  return this.chainSuccess(onSuccess);
70
74
  }
71
- convertFailureToSuccess(defaultValue) {
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)('pureTraceInternalError', 'invalidError', {
90
- message: error,
91
- zodError: zod_1.default.treeifyError(zodParseResult.error),
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(f) {
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(onSuccess, onFailure) {
118
+ mapBoth(_, onFailure) {
111
119
  return this.mapFailure(onFailure);
112
120
  }
113
- chainSuccess(f) {
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(onSuccess, onFailure) {
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)(type, code, data));
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.5",
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",