@signaltree/events 7.3.5 → 7.4.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.
Files changed (54) hide show
  1. package/dist/angular/handlers.cjs +38 -0
  2. package/dist/angular/handlers.js +35 -0
  3. package/dist/angular/index.cjs +15 -0
  4. package/dist/angular/index.js +3 -0
  5. package/dist/angular/optimistic-updates.cjs +161 -0
  6. package/dist/angular/optimistic-updates.js +159 -0
  7. package/dist/angular/websocket.service.cjs +357 -0
  8. package/{angular.esm.js → dist/angular/websocket.service.js} +1 -191
  9. package/dist/core/error-classification.cjs +282 -0
  10. package/dist/core/error-classification.js +276 -0
  11. package/dist/core/factory.cjs +148 -0
  12. package/{factory.esm.js → dist/core/factory.js} +2 -37
  13. package/dist/core/idempotency.cjs +252 -0
  14. package/dist/core/idempotency.js +247 -0
  15. package/dist/core/registry.cjs +183 -0
  16. package/dist/core/registry.js +180 -0
  17. package/dist/core/types.cjs +41 -0
  18. package/dist/core/types.js +38 -0
  19. package/dist/core/validation.cjs +185 -0
  20. package/{index.esm.js → dist/core/validation.js} +1 -4
  21. package/dist/index.cjs +43 -0
  22. package/dist/index.js +7 -0
  23. package/dist/nestjs/base.subscriber.cjs +287 -0
  24. package/dist/nestjs/base.subscriber.js +287 -0
  25. package/dist/nestjs/decorators.cjs +35 -0
  26. package/dist/nestjs/decorators.js +32 -0
  27. package/dist/nestjs/dlq.service.cjs +249 -0
  28. package/dist/nestjs/dlq.service.js +249 -0
  29. package/dist/nestjs/event-bus.module.cjs +152 -0
  30. package/dist/nestjs/event-bus.module.js +152 -0
  31. package/dist/nestjs/event-bus.service.cjs +243 -0
  32. package/dist/nestjs/event-bus.service.js +243 -0
  33. package/dist/nestjs/index.cjs +33 -0
  34. package/dist/nestjs/index.js +6 -0
  35. package/dist/nestjs/tokens.cjs +14 -0
  36. package/dist/nestjs/tokens.js +9 -0
  37. package/dist/testing/assertions.cjs +172 -0
  38. package/dist/testing/assertions.js +169 -0
  39. package/dist/testing/factories.cjs +122 -0
  40. package/dist/testing/factories.js +119 -0
  41. package/dist/testing/helpers.cjs +233 -0
  42. package/dist/testing/helpers.js +227 -0
  43. package/dist/testing/index.cjs +20 -0
  44. package/dist/testing/index.js +4 -0
  45. package/dist/testing/mock-event-bus.cjs +237 -0
  46. package/dist/testing/mock-event-bus.js +234 -0
  47. package/package.json +22 -22
  48. package/angular.d.ts +0 -1
  49. package/idempotency.esm.js +0 -701
  50. package/index.d.ts +0 -1
  51. package/nestjs.d.ts +0 -1
  52. package/nestjs.esm.js +0 -944
  53. package/testing.d.ts +0 -1
  54. package/testing.esm.js +0 -743
@@ -0,0 +1,282 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Error Classification - Determine retry behavior for errors
5
+ *
6
+ * Provides:
7
+ * - Retryable vs non-retryable error classification
8
+ * - Error categories (transient, permanent, poison)
9
+ * - Retry configuration per error type
10
+ * - Custom error classifiers
11
+ */
12
+ /**
13
+ * Default retry configurations by classification
14
+ */
15
+ const DEFAULT_RETRY_CONFIGS = {
16
+ transient: {
17
+ maxAttempts: 5,
18
+ initialDelayMs: 1000,
19
+ maxDelayMs: 60000,
20
+ backoffMultiplier: 2,
21
+ jitter: 0.1
22
+ },
23
+ permanent: {
24
+ maxAttempts: 0,
25
+ initialDelayMs: 0,
26
+ maxDelayMs: 0,
27
+ backoffMultiplier: 1,
28
+ jitter: 0
29
+ },
30
+ poison: {
31
+ maxAttempts: 0,
32
+ initialDelayMs: 0,
33
+ maxDelayMs: 0,
34
+ backoffMultiplier: 1,
35
+ jitter: 0
36
+ },
37
+ unknown: {
38
+ maxAttempts: 3,
39
+ initialDelayMs: 2000,
40
+ maxDelayMs: 30000,
41
+ backoffMultiplier: 2,
42
+ jitter: 0.2
43
+ }
44
+ };
45
+ /**
46
+ * Known transient error patterns
47
+ */
48
+ const TRANSIENT_ERROR_PATTERNS = [
49
+ // Network errors
50
+ /ECONNREFUSED/i, /ECONNRESET/i, /ETIMEDOUT/i, /ENETUNREACH/i, /EHOSTUNREACH/i, /ENOTFOUND/i, /socket hang up/i, /network error/i, /connection.*timeout/i, /request.*timeout/i,
51
+ // Database transient
52
+ /deadlock/i, /lock wait timeout/i, /too many connections/i, /connection pool exhausted/i, /temporarily unavailable/i,
53
+ // HTTP transient
54
+ /502 bad gateway/i, /503 service unavailable/i, /504 gateway timeout/i, /429 too many requests/i,
55
+ // Redis/Queue transient
56
+ /BUSY/i, /LOADING/i, /CLUSTERDOWN/i, /READONLY/i,
57
+ // Generic transient
58
+ /temporary failure/i, /try again/i, /retry/i, /throttl/i, /rate limit/i, /circuit breaker/i];
59
+ /**
60
+ * Known permanent error patterns
61
+ */
62
+ const PERMANENT_ERROR_PATTERNS = [
63
+ // Auth errors
64
+ /unauthorized/i, /forbidden/i, /access denied/i, /permission denied/i, /invalid token/i, /token expired/i,
65
+ // Business logic
66
+ /not found/i, /already exists/i, /duplicate/i, /conflict/i, /invalid state/i, /precondition failed/i,
67
+ // HTTP permanent
68
+ /400 bad request/i, /401 unauthorized/i, /403 forbidden/i, /404 not found/i, /409 conflict/i, /422 unprocessable/i];
69
+ /**
70
+ * Known poison error patterns (send to DLQ immediately)
71
+ */
72
+ const POISON_ERROR_PATTERNS = [
73
+ // Schema/Serialization
74
+ /invalid json/i, /json parse error/i, /unexpected token/i, /schema validation/i, /invalid event schema/i, /deserialization/i, /malformed/i,
75
+ // Data corruption
76
+ /data corruption/i, /checksum mismatch/i, /integrity error/i];
77
+ /**
78
+ * Error codes that indicate transient failures
79
+ */
80
+ const TRANSIENT_ERROR_CODES = new Set(['ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT', 'ENETUNREACH', 'EHOSTUNREACH', 'ENOTFOUND', 'EPIPE', 'EAI_AGAIN']);
81
+ /**
82
+ * HTTP status codes that indicate transient failures
83
+ */
84
+ const TRANSIENT_HTTP_STATUS = new Set([408, 429, 500, 502, 503, 504]);
85
+ /**
86
+ * HTTP status codes that indicate permanent failures
87
+ */
88
+ const PERMANENT_HTTP_STATUS = new Set([400, 401, 403, 404, 405, 409, 410, 422]);
89
+ /**
90
+ * Create an error classifier
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * const classifier = createErrorClassifier({
95
+ * customClassifiers: [
96
+ * (error) => {
97
+ * if (error instanceof MyCustomTransientError) return 'transient';
98
+ * return null; // Let default classification handle it
99
+ * }
100
+ * ],
101
+ * retryConfigs: {
102
+ * transient: { maxAttempts: 10 }, // Override max attempts
103
+ * },
104
+ * });
105
+ *
106
+ * const result = classifier.classify(error);
107
+ * if (result.sendToDlq) {
108
+ * await dlqService.send(event, error, result.reason);
109
+ * }
110
+ * ```
111
+ */
112
+ function createErrorClassifier(config = {}) {
113
+ const customClassifiers = config.customClassifiers ?? [];
114
+ const defaultClassification = config.defaultClassification ?? 'unknown';
115
+ // Merge retry configs
116
+ const retryConfigs = {
117
+ transient: {
118
+ ...DEFAULT_RETRY_CONFIGS.transient,
119
+ ...config.retryConfigs?.transient
120
+ },
121
+ permanent: {
122
+ ...DEFAULT_RETRY_CONFIGS.permanent,
123
+ ...config.retryConfigs?.permanent
124
+ },
125
+ poison: {
126
+ ...DEFAULT_RETRY_CONFIGS.poison,
127
+ ...config.retryConfigs?.poison
128
+ },
129
+ unknown: {
130
+ ...DEFAULT_RETRY_CONFIGS.unknown,
131
+ ...config.retryConfigs?.unknown
132
+ }
133
+ };
134
+ function extractErrorInfo(error) {
135
+ if (error instanceof Error) {
136
+ const errWithCode = error;
137
+ return {
138
+ message: error.message,
139
+ name: error.name,
140
+ code: errWithCode.code,
141
+ status: errWithCode.status ?? errWithCode.statusCode ?? errWithCode.response?.status
142
+ };
143
+ }
144
+ if (typeof error === 'object' && error !== null) {
145
+ const obj = error;
146
+ return {
147
+ message: String(obj['message'] ?? obj['error'] ?? ''),
148
+ code: obj['code'],
149
+ status: obj['status'] ?? obj['statusCode']
150
+ };
151
+ }
152
+ return {
153
+ message: String(error)
154
+ };
155
+ }
156
+ function classifyByPatterns(message) {
157
+ // Check poison patterns first (most specific)
158
+ for (const pattern of POISON_ERROR_PATTERNS) {
159
+ if (pattern.test(message)) {
160
+ return 'poison';
161
+ }
162
+ }
163
+ // Check permanent patterns
164
+ for (const pattern of PERMANENT_ERROR_PATTERNS) {
165
+ if (pattern.test(message)) {
166
+ return 'permanent';
167
+ }
168
+ }
169
+ // Check transient patterns
170
+ for (const pattern of TRANSIENT_ERROR_PATTERNS) {
171
+ if (pattern.test(message)) {
172
+ return 'transient';
173
+ }
174
+ }
175
+ return null;
176
+ }
177
+ function classify(error) {
178
+ // 1. Try custom classifiers first
179
+ for (const classifier of customClassifiers) {
180
+ const result = classifier(error);
181
+ if (result !== null) {
182
+ return {
183
+ classification: result,
184
+ retryConfig: retryConfigs[result],
185
+ sendToDlq: result === 'poison' || result === 'permanent',
186
+ reason: `Custom classifier: ${result}`
187
+ };
188
+ }
189
+ }
190
+ const {
191
+ message,
192
+ code,
193
+ status,
194
+ name
195
+ } = extractErrorInfo(error);
196
+ // 2. Check error code
197
+ if (code && TRANSIENT_ERROR_CODES.has(code)) {
198
+ return {
199
+ classification: 'transient',
200
+ retryConfig: retryConfigs.transient,
201
+ sendToDlq: false,
202
+ reason: `Error code: ${code}`
203
+ };
204
+ }
205
+ // 3. Check HTTP status
206
+ if (status !== undefined) {
207
+ if (TRANSIENT_HTTP_STATUS.has(status)) {
208
+ return {
209
+ classification: 'transient',
210
+ retryConfig: retryConfigs.transient,
211
+ sendToDlq: false,
212
+ reason: `HTTP status: ${status}`
213
+ };
214
+ }
215
+ if (PERMANENT_HTTP_STATUS.has(status)) {
216
+ return {
217
+ classification: 'permanent',
218
+ retryConfig: retryConfigs.permanent,
219
+ sendToDlq: true,
220
+ reason: `HTTP status: ${status}`
221
+ };
222
+ }
223
+ }
224
+ // 4. Check error patterns
225
+ const patternResult = classifyByPatterns(message) ?? classifyByPatterns(name ?? '');
226
+ if (patternResult) {
227
+ return {
228
+ classification: patternResult,
229
+ retryConfig: retryConfigs[patternResult],
230
+ sendToDlq: patternResult === 'poison' || patternResult === 'permanent',
231
+ reason: `Pattern match: ${message.slice(0, 50)}`
232
+ };
233
+ }
234
+ // 5. Use default classification
235
+ return {
236
+ classification: defaultClassification,
237
+ retryConfig: retryConfigs[defaultClassification],
238
+ sendToDlq: defaultClassification === 'poison' || defaultClassification === 'permanent',
239
+ reason: 'No matching pattern, using default'
240
+ };
241
+ }
242
+ function isRetryable(error) {
243
+ const result = classify(error);
244
+ return result.classification === 'transient' || result.classification === 'unknown';
245
+ }
246
+ function calculateDelay(attempt, retryConfig) {
247
+ // Exponential backoff: initialDelay * multiplier^attempt
248
+ const baseDelay = retryConfig.initialDelayMs * Math.pow(retryConfig.backoffMultiplier, attempt);
249
+ // Cap at maxDelay
250
+ const cappedDelay = Math.min(baseDelay, retryConfig.maxDelayMs);
251
+ // Add jitter to prevent thundering herd
252
+ const jitter = cappedDelay * retryConfig.jitter * Math.random();
253
+ return Math.round(cappedDelay + jitter);
254
+ }
255
+ return {
256
+ classify,
257
+ isRetryable,
258
+ calculateDelay
259
+ };
260
+ }
261
+ /**
262
+ * Pre-configured error classifier instance
263
+ */
264
+ const defaultErrorClassifier = createErrorClassifier();
265
+ /**
266
+ * Quick helper to check if error is retryable
267
+ */
268
+ function isRetryableError(error) {
269
+ return defaultErrorClassifier.isRetryable(error);
270
+ }
271
+ /**
272
+ * Quick helper to classify error
273
+ */
274
+ function classifyError(error) {
275
+ return defaultErrorClassifier.classify(error);
276
+ }
277
+
278
+ exports.DEFAULT_RETRY_CONFIGS = DEFAULT_RETRY_CONFIGS;
279
+ exports.classifyError = classifyError;
280
+ exports.createErrorClassifier = createErrorClassifier;
281
+ exports.defaultErrorClassifier = defaultErrorClassifier;
282
+ exports.isRetryableError = isRetryableError;
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Error Classification - Determine retry behavior for errors
3
+ *
4
+ * Provides:
5
+ * - Retryable vs non-retryable error classification
6
+ * - Error categories (transient, permanent, poison)
7
+ * - Retry configuration per error type
8
+ * - Custom error classifiers
9
+ */
10
+ /**
11
+ * Default retry configurations by classification
12
+ */
13
+ const DEFAULT_RETRY_CONFIGS = {
14
+ transient: {
15
+ maxAttempts: 5,
16
+ initialDelayMs: 1000,
17
+ maxDelayMs: 60000,
18
+ backoffMultiplier: 2,
19
+ jitter: 0.1
20
+ },
21
+ permanent: {
22
+ maxAttempts: 0,
23
+ initialDelayMs: 0,
24
+ maxDelayMs: 0,
25
+ backoffMultiplier: 1,
26
+ jitter: 0
27
+ },
28
+ poison: {
29
+ maxAttempts: 0,
30
+ initialDelayMs: 0,
31
+ maxDelayMs: 0,
32
+ backoffMultiplier: 1,
33
+ jitter: 0
34
+ },
35
+ unknown: {
36
+ maxAttempts: 3,
37
+ initialDelayMs: 2000,
38
+ maxDelayMs: 30000,
39
+ backoffMultiplier: 2,
40
+ jitter: 0.2
41
+ }
42
+ };
43
+ /**
44
+ * Known transient error patterns
45
+ */
46
+ const TRANSIENT_ERROR_PATTERNS = [
47
+ // Network errors
48
+ /ECONNREFUSED/i, /ECONNRESET/i, /ETIMEDOUT/i, /ENETUNREACH/i, /EHOSTUNREACH/i, /ENOTFOUND/i, /socket hang up/i, /network error/i, /connection.*timeout/i, /request.*timeout/i,
49
+ // Database transient
50
+ /deadlock/i, /lock wait timeout/i, /too many connections/i, /connection pool exhausted/i, /temporarily unavailable/i,
51
+ // HTTP transient
52
+ /502 bad gateway/i, /503 service unavailable/i, /504 gateway timeout/i, /429 too many requests/i,
53
+ // Redis/Queue transient
54
+ /BUSY/i, /LOADING/i, /CLUSTERDOWN/i, /READONLY/i,
55
+ // Generic transient
56
+ /temporary failure/i, /try again/i, /retry/i, /throttl/i, /rate limit/i, /circuit breaker/i];
57
+ /**
58
+ * Known permanent error patterns
59
+ */
60
+ const PERMANENT_ERROR_PATTERNS = [
61
+ // Auth errors
62
+ /unauthorized/i, /forbidden/i, /access denied/i, /permission denied/i, /invalid token/i, /token expired/i,
63
+ // Business logic
64
+ /not found/i, /already exists/i, /duplicate/i, /conflict/i, /invalid state/i, /precondition failed/i,
65
+ // HTTP permanent
66
+ /400 bad request/i, /401 unauthorized/i, /403 forbidden/i, /404 not found/i, /409 conflict/i, /422 unprocessable/i];
67
+ /**
68
+ * Known poison error patterns (send to DLQ immediately)
69
+ */
70
+ const POISON_ERROR_PATTERNS = [
71
+ // Schema/Serialization
72
+ /invalid json/i, /json parse error/i, /unexpected token/i, /schema validation/i, /invalid event schema/i, /deserialization/i, /malformed/i,
73
+ // Data corruption
74
+ /data corruption/i, /checksum mismatch/i, /integrity error/i];
75
+ /**
76
+ * Error codes that indicate transient failures
77
+ */
78
+ const TRANSIENT_ERROR_CODES = new Set(['ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT', 'ENETUNREACH', 'EHOSTUNREACH', 'ENOTFOUND', 'EPIPE', 'EAI_AGAIN']);
79
+ /**
80
+ * HTTP status codes that indicate transient failures
81
+ */
82
+ const TRANSIENT_HTTP_STATUS = new Set([408, 429, 500, 502, 503, 504]);
83
+ /**
84
+ * HTTP status codes that indicate permanent failures
85
+ */
86
+ const PERMANENT_HTTP_STATUS = new Set([400, 401, 403, 404, 405, 409, 410, 422]);
87
+ /**
88
+ * Create an error classifier
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * const classifier = createErrorClassifier({
93
+ * customClassifiers: [
94
+ * (error) => {
95
+ * if (error instanceof MyCustomTransientError) return 'transient';
96
+ * return null; // Let default classification handle it
97
+ * }
98
+ * ],
99
+ * retryConfigs: {
100
+ * transient: { maxAttempts: 10 }, // Override max attempts
101
+ * },
102
+ * });
103
+ *
104
+ * const result = classifier.classify(error);
105
+ * if (result.sendToDlq) {
106
+ * await dlqService.send(event, error, result.reason);
107
+ * }
108
+ * ```
109
+ */
110
+ function createErrorClassifier(config = {}) {
111
+ const customClassifiers = config.customClassifiers ?? [];
112
+ const defaultClassification = config.defaultClassification ?? 'unknown';
113
+ // Merge retry configs
114
+ const retryConfigs = {
115
+ transient: {
116
+ ...DEFAULT_RETRY_CONFIGS.transient,
117
+ ...config.retryConfigs?.transient
118
+ },
119
+ permanent: {
120
+ ...DEFAULT_RETRY_CONFIGS.permanent,
121
+ ...config.retryConfigs?.permanent
122
+ },
123
+ poison: {
124
+ ...DEFAULT_RETRY_CONFIGS.poison,
125
+ ...config.retryConfigs?.poison
126
+ },
127
+ unknown: {
128
+ ...DEFAULT_RETRY_CONFIGS.unknown,
129
+ ...config.retryConfigs?.unknown
130
+ }
131
+ };
132
+ function extractErrorInfo(error) {
133
+ if (error instanceof Error) {
134
+ const errWithCode = error;
135
+ return {
136
+ message: error.message,
137
+ name: error.name,
138
+ code: errWithCode.code,
139
+ status: errWithCode.status ?? errWithCode.statusCode ?? errWithCode.response?.status
140
+ };
141
+ }
142
+ if (typeof error === 'object' && error !== null) {
143
+ const obj = error;
144
+ return {
145
+ message: String(obj['message'] ?? obj['error'] ?? ''),
146
+ code: obj['code'],
147
+ status: obj['status'] ?? obj['statusCode']
148
+ };
149
+ }
150
+ return {
151
+ message: String(error)
152
+ };
153
+ }
154
+ function classifyByPatterns(message) {
155
+ // Check poison patterns first (most specific)
156
+ for (const pattern of POISON_ERROR_PATTERNS) {
157
+ if (pattern.test(message)) {
158
+ return 'poison';
159
+ }
160
+ }
161
+ // Check permanent patterns
162
+ for (const pattern of PERMANENT_ERROR_PATTERNS) {
163
+ if (pattern.test(message)) {
164
+ return 'permanent';
165
+ }
166
+ }
167
+ // Check transient patterns
168
+ for (const pattern of TRANSIENT_ERROR_PATTERNS) {
169
+ if (pattern.test(message)) {
170
+ return 'transient';
171
+ }
172
+ }
173
+ return null;
174
+ }
175
+ function classify(error) {
176
+ // 1. Try custom classifiers first
177
+ for (const classifier of customClassifiers) {
178
+ const result = classifier(error);
179
+ if (result !== null) {
180
+ return {
181
+ classification: result,
182
+ retryConfig: retryConfigs[result],
183
+ sendToDlq: result === 'poison' || result === 'permanent',
184
+ reason: `Custom classifier: ${result}`
185
+ };
186
+ }
187
+ }
188
+ const {
189
+ message,
190
+ code,
191
+ status,
192
+ name
193
+ } = extractErrorInfo(error);
194
+ // 2. Check error code
195
+ if (code && TRANSIENT_ERROR_CODES.has(code)) {
196
+ return {
197
+ classification: 'transient',
198
+ retryConfig: retryConfigs.transient,
199
+ sendToDlq: false,
200
+ reason: `Error code: ${code}`
201
+ };
202
+ }
203
+ // 3. Check HTTP status
204
+ if (status !== undefined) {
205
+ if (TRANSIENT_HTTP_STATUS.has(status)) {
206
+ return {
207
+ classification: 'transient',
208
+ retryConfig: retryConfigs.transient,
209
+ sendToDlq: false,
210
+ reason: `HTTP status: ${status}`
211
+ };
212
+ }
213
+ if (PERMANENT_HTTP_STATUS.has(status)) {
214
+ return {
215
+ classification: 'permanent',
216
+ retryConfig: retryConfigs.permanent,
217
+ sendToDlq: true,
218
+ reason: `HTTP status: ${status}`
219
+ };
220
+ }
221
+ }
222
+ // 4. Check error patterns
223
+ const patternResult = classifyByPatterns(message) ?? classifyByPatterns(name ?? '');
224
+ if (patternResult) {
225
+ return {
226
+ classification: patternResult,
227
+ retryConfig: retryConfigs[patternResult],
228
+ sendToDlq: patternResult === 'poison' || patternResult === 'permanent',
229
+ reason: `Pattern match: ${message.slice(0, 50)}`
230
+ };
231
+ }
232
+ // 5. Use default classification
233
+ return {
234
+ classification: defaultClassification,
235
+ retryConfig: retryConfigs[defaultClassification],
236
+ sendToDlq: defaultClassification === 'poison' || defaultClassification === 'permanent',
237
+ reason: 'No matching pattern, using default'
238
+ };
239
+ }
240
+ function isRetryable(error) {
241
+ const result = classify(error);
242
+ return result.classification === 'transient' || result.classification === 'unknown';
243
+ }
244
+ function calculateDelay(attempt, retryConfig) {
245
+ // Exponential backoff: initialDelay * multiplier^attempt
246
+ const baseDelay = retryConfig.initialDelayMs * Math.pow(retryConfig.backoffMultiplier, attempt);
247
+ // Cap at maxDelay
248
+ const cappedDelay = Math.min(baseDelay, retryConfig.maxDelayMs);
249
+ // Add jitter to prevent thundering herd
250
+ const jitter = cappedDelay * retryConfig.jitter * Math.random();
251
+ return Math.round(cappedDelay + jitter);
252
+ }
253
+ return {
254
+ classify,
255
+ isRetryable,
256
+ calculateDelay
257
+ };
258
+ }
259
+ /**
260
+ * Pre-configured error classifier instance
261
+ */
262
+ const defaultErrorClassifier = createErrorClassifier();
263
+ /**
264
+ * Quick helper to check if error is retryable
265
+ */
266
+ function isRetryableError(error) {
267
+ return defaultErrorClassifier.isRetryable(error);
268
+ }
269
+ /**
270
+ * Quick helper to classify error
271
+ */
272
+ function classifyError(error) {
273
+ return defaultErrorClassifier.classify(error);
274
+ }
275
+
276
+ export { DEFAULT_RETRY_CONFIGS, classifyError, createErrorClassifier, defaultErrorClassifier, isRetryableError };
@@ -0,0 +1,148 @@
1
+ 'use strict';
2
+
3
+ var types = require('./types.cjs');
4
+
5
+ /**
6
+ * Generate a UUID v7 (time-sortable)
7
+ *
8
+ * UUID v7 format: timestamp (48 bits) + version (4 bits) + random (12 bits) + variant (2 bits) + random (62 bits)
9
+ */
10
+ function generateEventId() {
11
+ // Get timestamp in milliseconds
12
+ const timestamp = Date.now();
13
+ // Convert to hex (12 characters for 48 bits)
14
+ const timestampHex = timestamp.toString(16).padStart(12, '0');
15
+ // Generate random bytes
16
+ const randomBytes = new Uint8Array(10);
17
+ crypto.getRandomValues(randomBytes);
18
+ // Convert to hex
19
+ const randomHex = Array.from(randomBytes).map(b => b.toString(16).padStart(2, '0')).join('');
20
+ // Construct UUID v7
21
+ // Format: xxxxxxxx-xxxx-7xxx-yxxx-xxxxxxxxxxxx
22
+ // Where x is timestamp/random and 7 is version, y is variant (8, 9, a, or b)
23
+ const uuid = [timestampHex.slice(0, 8),
24
+ // time_low
25
+ timestampHex.slice(8, 12),
26
+ // time_mid
27
+ '7' + randomHex.slice(0, 3),
28
+ // version (7) + random
29
+ (parseInt(randomHex.slice(3, 5), 16) & 0x3f | 0x80).toString(16).padStart(2, '0') + randomHex.slice(5, 7),
30
+ // variant + random
31
+ randomHex.slice(7, 19) // random
32
+ ].join('-');
33
+ return uuid;
34
+ }
35
+ /**
36
+ * Generate a correlation ID (also UUID v7 for traceability)
37
+ */
38
+ function generateCorrelationId() {
39
+ return generateEventId();
40
+ }
41
+ /**
42
+ * Create a single event
43
+ */
44
+ function createEvent(type, data, options) {
45
+ const id = options.id ?? generateEventId();
46
+ const correlationId = options.correlationId ?? generateCorrelationId();
47
+ const timestamp = options.timestamp ?? new Date().toISOString();
48
+ const actor = options.actor ?? {
49
+ id: 'system',
50
+ type: 'system'
51
+ };
52
+ const metadata = {
53
+ source: options.source,
54
+ environment: options.environment,
55
+ ...options.metadata
56
+ };
57
+ return {
58
+ id,
59
+ type,
60
+ version: options.version ?? types.DEFAULT_EVENT_VERSION,
61
+ timestamp,
62
+ correlationId,
63
+ causationId: options.causationId,
64
+ actor,
65
+ metadata,
66
+ data,
67
+ priority: options.priority,
68
+ aggregate: options.aggregate
69
+ };
70
+ }
71
+ /**
72
+ * Create an event factory with default configuration
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * const factory = createEventFactory({
77
+ * source: 'trade-service',
78
+ * environment: process.env.NODE_ENV || 'development',
79
+ * });
80
+ *
81
+ * const event = factory.create('TradeProposalCreated', {
82
+ * tradeId: '123',
83
+ * initiatorId: 'user-1',
84
+ * recipientId: 'user-2',
85
+ * }, {
86
+ * actor: { id: 'user-1', type: 'user' },
87
+ * priority: 'high',
88
+ * });
89
+ * ```
90
+ */
91
+ function createEventFactory(config) {
92
+ // Thread-local-like storage for correlation ID
93
+ let currentCorrelationId;
94
+ const systemActor = config.systemActor ?? {
95
+ id: 'system',
96
+ type: 'system',
97
+ name: config.source
98
+ };
99
+ const generateId = config.generateId ?? generateEventId;
100
+ const generateCorrelation = config.generateCorrelationId ?? generateCorrelationId;
101
+ return {
102
+ create(type, data, options = {}) {
103
+ const id = options.id ?? generateId();
104
+ const correlationId = options.correlationId ?? currentCorrelationId ?? generateCorrelation();
105
+ const timestamp = options.timestamp ?? new Date().toISOString();
106
+ const actor = options.actor ?? systemActor;
107
+ const metadata = {
108
+ source: config.source,
109
+ environment: config.environment,
110
+ ...options.metadata
111
+ };
112
+ return {
113
+ id,
114
+ type,
115
+ version: options.version ?? types.DEFAULT_EVENT_VERSION,
116
+ timestamp,
117
+ correlationId,
118
+ causationId: options.causationId,
119
+ actor,
120
+ metadata,
121
+ data,
122
+ priority: options.priority,
123
+ aggregate: options.aggregate
124
+ };
125
+ },
126
+ createFromCause(type, data, cause, options = {}) {
127
+ return this.create(type, data, {
128
+ ...options,
129
+ correlationId: cause.correlationId,
130
+ causationId: cause.id
131
+ });
132
+ },
133
+ getCorrelationId() {
134
+ return currentCorrelationId;
135
+ },
136
+ setCorrelationId(id) {
137
+ currentCorrelationId = id;
138
+ },
139
+ clearCorrelationId() {
140
+ currentCorrelationId = undefined;
141
+ }
142
+ };
143
+ }
144
+
145
+ exports.createEvent = createEvent;
146
+ exports.createEventFactory = createEventFactory;
147
+ exports.generateCorrelationId = generateCorrelationId;
148
+ exports.generateEventId = generateEventId;