@aleph-ai/tinyaleph 1.3.0 → 1.4.1
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 +423 -12
- package/backends/cryptographic/index.js +455 -2
- package/core/beacon.js +735 -0
- package/core/crt-homology.js +1004 -0
- package/core/enochian-vocabulary.js +910 -0
- package/core/enochian.js +744 -0
- package/core/errors.js +587 -0
- package/core/hilbert.js +651 -1
- package/core/index.js +86 -1
- package/core/lambda.js +284 -33
- package/core/logger.js +350 -0
- package/core/prime.js +136 -1
- package/core/quaternion-semantics.js +623 -0
- package/core/reduction.js +391 -1
- package/core/rformer-crt.js +892 -0
- package/core/topology.js +655 -0
- package/docs/README.md +54 -0
- package/docs/design/PYTHON_PORT_DESIGN.md +1400 -0
- package/docs/reference/07-topology.md +257 -0
- package/docs/reference/08-observer.md +421 -0
- package/docs/reference/09-crt-homology.md +369 -0
- package/modular.js +231 -3
- package/package.json +1 -1
package/core/errors.js
ADDED
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized Error Handling for TinyAleph
|
|
3
|
+
*
|
|
4
|
+
* Provides a unified error handling system with:
|
|
5
|
+
* - Error categorization and classification
|
|
6
|
+
* - Automatic retry logic for transient errors
|
|
7
|
+
* - Error aggregation and reporting
|
|
8
|
+
* - User-friendly error messages
|
|
9
|
+
* - Async error boundary wrappers
|
|
10
|
+
*
|
|
11
|
+
* Browser-compatible: No Node.js-specific dependencies.
|
|
12
|
+
* Extracted from apps/sentient/lib/error-handler.js for library reuse.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// Simple EventEmitter that works in both browser and Node.js
|
|
16
|
+
class SimpleEventEmitter {
|
|
17
|
+
constructor() {
|
|
18
|
+
this._events = new Map();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
on(event, listener) {
|
|
22
|
+
if (!this._events.has(event)) {
|
|
23
|
+
this._events.set(event, []);
|
|
24
|
+
}
|
|
25
|
+
this._events.get(event).push(listener);
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
off(event, listener) {
|
|
30
|
+
if (!this._events.has(event)) return this;
|
|
31
|
+
const listeners = this._events.get(event);
|
|
32
|
+
const idx = listeners.indexOf(listener);
|
|
33
|
+
if (idx !== -1) listeners.splice(idx, 1);
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
emit(event, ...args) {
|
|
38
|
+
if (!this._events.has(event)) return false;
|
|
39
|
+
for (const listener of this._events.get(event)) {
|
|
40
|
+
listener(...args);
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
removeAllListeners(event) {
|
|
46
|
+
if (event) {
|
|
47
|
+
this._events.delete(event);
|
|
48
|
+
} else {
|
|
49
|
+
this._events.clear();
|
|
50
|
+
}
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ============================================================================
|
|
56
|
+
// LOG LEVELS
|
|
57
|
+
// ============================================================================
|
|
58
|
+
|
|
59
|
+
const LogLevel = {
|
|
60
|
+
TRACE: 0,
|
|
61
|
+
DEBUG: 1,
|
|
62
|
+
INFO: 2,
|
|
63
|
+
WARN: 3,
|
|
64
|
+
ERROR: 4,
|
|
65
|
+
FATAL: 5,
|
|
66
|
+
SILENT: 6
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const LogLevelNames = Object.fromEntries(
|
|
70
|
+
Object.entries(LogLevel).map(([k, v]) => [v, k])
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// ============================================================================
|
|
74
|
+
// ERROR CATEGORIES
|
|
75
|
+
// ============================================================================
|
|
76
|
+
|
|
77
|
+
const ErrorCategory = {
|
|
78
|
+
NETWORK: 'network', // Network/transport errors
|
|
79
|
+
AUTHENTICATION: 'auth', // Auth/credential errors
|
|
80
|
+
VALIDATION: 'validation', // Input validation errors
|
|
81
|
+
RESOURCE: 'resource', // Resource not found/unavailable
|
|
82
|
+
PERMISSION: 'permission', // Permission denied
|
|
83
|
+
TIMEOUT: 'timeout', // Operation timeouts
|
|
84
|
+
RATE_LIMIT: 'rate_limit', // Rate limiting
|
|
85
|
+
INTERNAL: 'internal', // Internal/unexpected errors
|
|
86
|
+
EXTERNAL: 'external', // External service errors
|
|
87
|
+
USER: 'user', // User-caused errors
|
|
88
|
+
CONFIGURATION: 'config', // Configuration errors
|
|
89
|
+
LLM: 'llm', // LLM-specific errors
|
|
90
|
+
MEMORY: 'memory', // Memory/state errors
|
|
91
|
+
OSCILLATOR: 'oscillator' // Oscillator/dynamics errors
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// ============================================================================
|
|
95
|
+
// CUSTOM ERROR CLASSES
|
|
96
|
+
// ============================================================================
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Base error class with category and metadata
|
|
100
|
+
*/
|
|
101
|
+
class AlephError extends Error {
|
|
102
|
+
constructor(message, options = {}) {
|
|
103
|
+
super(message);
|
|
104
|
+
this.name = 'AlephError';
|
|
105
|
+
this.category = options.category || ErrorCategory.INTERNAL;
|
|
106
|
+
this.code = options.code || 'UNKNOWN_ERROR';
|
|
107
|
+
this.retryable = options.retryable ?? false;
|
|
108
|
+
this.metadata = options.metadata || {};
|
|
109
|
+
this.originalError = options.cause || null;
|
|
110
|
+
this.timestamp = Date.now();
|
|
111
|
+
this.userMessage = options.userMessage || this.getDefaultUserMessage();
|
|
112
|
+
|
|
113
|
+
// Capture stack trace
|
|
114
|
+
if (Error.captureStackTrace) {
|
|
115
|
+
Error.captureStackTrace(this, AlephError);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
getDefaultUserMessage() {
|
|
120
|
+
switch (this.category) {
|
|
121
|
+
case ErrorCategory.NETWORK:
|
|
122
|
+
return 'Network connection error. Please check your connection.';
|
|
123
|
+
case ErrorCategory.AUTHENTICATION:
|
|
124
|
+
return 'Authentication failed. Please check your credentials.';
|
|
125
|
+
case ErrorCategory.RATE_LIMIT:
|
|
126
|
+
return 'Too many requests. Please wait a moment.';
|
|
127
|
+
case ErrorCategory.TIMEOUT:
|
|
128
|
+
return 'Operation timed out. Please try again.';
|
|
129
|
+
case ErrorCategory.LLM:
|
|
130
|
+
return 'AI service error. Please try again later.';
|
|
131
|
+
default:
|
|
132
|
+
return 'An unexpected error occurred.';
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
toJSON() {
|
|
137
|
+
return {
|
|
138
|
+
name: this.name,
|
|
139
|
+
message: this.message,
|
|
140
|
+
category: this.category,
|
|
141
|
+
code: this.code,
|
|
142
|
+
retryable: this.retryable,
|
|
143
|
+
metadata: this.metadata,
|
|
144
|
+
userMessage: this.userMessage,
|
|
145
|
+
timestamp: this.timestamp,
|
|
146
|
+
stack: this.stack
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Network-related errors
|
|
153
|
+
*/
|
|
154
|
+
class NetworkError extends AlephError {
|
|
155
|
+
constructor(message, options = {}) {
|
|
156
|
+
super(message, {
|
|
157
|
+
...options,
|
|
158
|
+
category: ErrorCategory.NETWORK,
|
|
159
|
+
retryable: options.retryable ?? true
|
|
160
|
+
});
|
|
161
|
+
this.name = 'NetworkError';
|
|
162
|
+
this.statusCode = options.statusCode;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* LLM-related errors
|
|
168
|
+
*/
|
|
169
|
+
class LLMError extends AlephError {
|
|
170
|
+
constructor(message, options = {}) {
|
|
171
|
+
super(message, {
|
|
172
|
+
...options,
|
|
173
|
+
category: ErrorCategory.LLM
|
|
174
|
+
});
|
|
175
|
+
this.name = 'LLMError';
|
|
176
|
+
this.provider = options.provider;
|
|
177
|
+
this.model = options.model;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Validation errors
|
|
183
|
+
*/
|
|
184
|
+
class ValidationError extends AlephError {
|
|
185
|
+
constructor(message, options = {}) {
|
|
186
|
+
super(message, {
|
|
187
|
+
...options,
|
|
188
|
+
category: ErrorCategory.VALIDATION,
|
|
189
|
+
retryable: false
|
|
190
|
+
});
|
|
191
|
+
this.name = 'ValidationError';
|
|
192
|
+
this.fields = options.fields || [];
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Timeout errors
|
|
198
|
+
*/
|
|
199
|
+
class TimeoutError extends AlephError {
|
|
200
|
+
constructor(message, options = {}) {
|
|
201
|
+
super(message, {
|
|
202
|
+
...options,
|
|
203
|
+
category: ErrorCategory.TIMEOUT,
|
|
204
|
+
retryable: true
|
|
205
|
+
});
|
|
206
|
+
this.name = 'TimeoutError';
|
|
207
|
+
this.timeout = options.timeout;
|
|
208
|
+
this.operation = options.operation;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ============================================================================
|
|
213
|
+
// ERROR HANDLER
|
|
214
|
+
// ============================================================================
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Centralized Error Handler
|
|
218
|
+
*/
|
|
219
|
+
class ErrorHandler extends SimpleEventEmitter {
|
|
220
|
+
constructor(options = {}) {
|
|
221
|
+
super();
|
|
222
|
+
|
|
223
|
+
this.logger = options.logger || null;
|
|
224
|
+
|
|
225
|
+
// Error aggregation
|
|
226
|
+
this.errors = [];
|
|
227
|
+
this.maxErrors = options.maxErrors || 1000;
|
|
228
|
+
|
|
229
|
+
// Error rate tracking
|
|
230
|
+
this.errorRates = new Map(); // category -> count in window
|
|
231
|
+
this.rateWindow = options.rateWindow || 60000; // 1 minute
|
|
232
|
+
|
|
233
|
+
// User message handlers
|
|
234
|
+
this.userMessageHandlers = new Map();
|
|
235
|
+
|
|
236
|
+
// Recovery handlers
|
|
237
|
+
this.recoveryHandlers = new Map();
|
|
238
|
+
|
|
239
|
+
// Setup default handlers
|
|
240
|
+
this.setupDefaultHandlers();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Set the logger instance
|
|
245
|
+
* @param {Object} logger - Logger instance with error(), warn(), info() methods
|
|
246
|
+
*/
|
|
247
|
+
setLogger(logger) {
|
|
248
|
+
this.logger = logger;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
setupDefaultHandlers() {
|
|
252
|
+
// Register recovery handlers for retryable errors
|
|
253
|
+
this.registerRecoveryHandler(ErrorCategory.NETWORK, async (error, context) => {
|
|
254
|
+
// Default: wait and retry
|
|
255
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
256
|
+
return { retry: true };
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
this.registerRecoveryHandler(ErrorCategory.RATE_LIMIT, async (error, context) => {
|
|
260
|
+
// Wait for rate limit reset
|
|
261
|
+
const waitTime = error.metadata?.retryAfter || 5000;
|
|
262
|
+
await new Promise(r => setTimeout(r, waitTime));
|
|
263
|
+
return { retry: true };
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Handle an error
|
|
269
|
+
* @param {Error} error - Error to handle
|
|
270
|
+
* @param {Object} context - Error context
|
|
271
|
+
* @returns {Object} Handler result
|
|
272
|
+
*/
|
|
273
|
+
async handle(error, context = {}) {
|
|
274
|
+
// Normalize to AlephError
|
|
275
|
+
const alephError = this.normalize(error);
|
|
276
|
+
|
|
277
|
+
// Log the error
|
|
278
|
+
if (this.logger) {
|
|
279
|
+
this.logger.error(alephError.message, {
|
|
280
|
+
category: alephError.category,
|
|
281
|
+
code: alephError.code,
|
|
282
|
+
retryable: alephError.retryable,
|
|
283
|
+
metadata: alephError.metadata,
|
|
284
|
+
stack: alephError.stack
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Record error
|
|
289
|
+
this.recordError(alephError);
|
|
290
|
+
|
|
291
|
+
// Update error rate
|
|
292
|
+
this.updateErrorRate(alephError.category);
|
|
293
|
+
|
|
294
|
+
// Emit event
|
|
295
|
+
this.emit('error', alephError, context);
|
|
296
|
+
|
|
297
|
+
// Try recovery if retryable
|
|
298
|
+
if (alephError.retryable && context.canRetry !== false) {
|
|
299
|
+
const recovery = await this.attemptRecovery(alephError, context);
|
|
300
|
+
if (recovery.retry) {
|
|
301
|
+
return { handled: true, retry: true, error: alephError };
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
handled: true,
|
|
307
|
+
retry: false,
|
|
308
|
+
error: alephError,
|
|
309
|
+
userMessage: alephError.userMessage
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Normalize any error to AlephError
|
|
315
|
+
* @param {Error} error - Error to normalize
|
|
316
|
+
* @returns {AlephError}
|
|
317
|
+
*/
|
|
318
|
+
normalize(error) {
|
|
319
|
+
if (error instanceof AlephError) {
|
|
320
|
+
return error;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Classify based on error properties
|
|
324
|
+
let category = ErrorCategory.INTERNAL;
|
|
325
|
+
let retryable = false;
|
|
326
|
+
let code = 'UNKNOWN_ERROR';
|
|
327
|
+
|
|
328
|
+
// Check for network errors
|
|
329
|
+
if (error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED' ||
|
|
330
|
+
error.code === 'ECONNRESET' || error.message.includes('network')) {
|
|
331
|
+
category = ErrorCategory.NETWORK;
|
|
332
|
+
retryable = true;
|
|
333
|
+
code = error.code || 'NETWORK_ERROR';
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Check for timeout errors
|
|
337
|
+
if (error.name === 'TimeoutError' || error.code === 'ETIMEDOUT' ||
|
|
338
|
+
error.message.includes('timeout')) {
|
|
339
|
+
category = ErrorCategory.TIMEOUT;
|
|
340
|
+
retryable = true;
|
|
341
|
+
code = 'TIMEOUT';
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Check for rate limit errors
|
|
345
|
+
if (error.status === 429 || error.message.includes('rate limit')) {
|
|
346
|
+
category = ErrorCategory.RATE_LIMIT;
|
|
347
|
+
retryable = true;
|
|
348
|
+
code = 'RATE_LIMITED';
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Check for auth errors
|
|
352
|
+
if (error.status === 401 || error.status === 403 ||
|
|
353
|
+
error.message.includes('unauthorized') || error.message.includes('forbidden')) {
|
|
354
|
+
category = ErrorCategory.AUTHENTICATION;
|
|
355
|
+
code = 'AUTH_FAILED';
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return new AlephError(error.message, {
|
|
359
|
+
category,
|
|
360
|
+
code,
|
|
361
|
+
retryable,
|
|
362
|
+
cause: error,
|
|
363
|
+
metadata: { originalName: error.name, originalCode: error.code }
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Record error for aggregation
|
|
369
|
+
* @param {AlephError} error - Error to record
|
|
370
|
+
*/
|
|
371
|
+
recordError(error) {
|
|
372
|
+
this.errors.push({
|
|
373
|
+
...error.toJSON(),
|
|
374
|
+
handledAt: Date.now()
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
if (this.errors.length > this.maxErrors) {
|
|
378
|
+
this.errors.shift();
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Update error rate tracking
|
|
384
|
+
* @param {string} category - Error category
|
|
385
|
+
*/
|
|
386
|
+
updateErrorRate(category) {
|
|
387
|
+
const now = Date.now();
|
|
388
|
+
const key = `${category}:${Math.floor(now / this.rateWindow)}`;
|
|
389
|
+
|
|
390
|
+
this.errorRates.set(key, (this.errorRates.get(key) || 0) + 1);
|
|
391
|
+
|
|
392
|
+
// Clean old windows
|
|
393
|
+
for (const [k] of this.errorRates) {
|
|
394
|
+
const windowTime = parseInt(k.split(':')[1]) * this.rateWindow;
|
|
395
|
+
if (now - windowTime > this.rateWindow * 2) {
|
|
396
|
+
this.errorRates.delete(k);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Get current error rate for a category
|
|
403
|
+
* @param {string} category - Error category
|
|
404
|
+
* @returns {number}
|
|
405
|
+
*/
|
|
406
|
+
getErrorRate(category) {
|
|
407
|
+
const now = Date.now();
|
|
408
|
+
const currentWindow = Math.floor(now / this.rateWindow);
|
|
409
|
+
const key = `${category}:${currentWindow}`;
|
|
410
|
+
return this.errorRates.get(key) || 0;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Register a recovery handler for a category
|
|
415
|
+
* @param {string} category - Error category
|
|
416
|
+
* @param {Function} handler - Recovery handler
|
|
417
|
+
*/
|
|
418
|
+
registerRecoveryHandler(category, handler) {
|
|
419
|
+
this.recoveryHandlers.set(category, handler);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Attempt recovery for an error
|
|
424
|
+
* @param {AlephError} error - Error to recover from
|
|
425
|
+
* @param {Object} context - Error context
|
|
426
|
+
* @returns {Promise<Object>}
|
|
427
|
+
*/
|
|
428
|
+
async attemptRecovery(error, context) {
|
|
429
|
+
const handler = this.recoveryHandlers.get(error.category);
|
|
430
|
+
|
|
431
|
+
if (!handler) {
|
|
432
|
+
return { retry: false };
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
try {
|
|
436
|
+
return await handler(error, context);
|
|
437
|
+
} catch (recoveryError) {
|
|
438
|
+
if (this.logger) {
|
|
439
|
+
this.logger.warn('Recovery failed', {
|
|
440
|
+
originalError: error.code,
|
|
441
|
+
recoveryError: recoveryError.message
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
return { retry: false };
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Get error statistics
|
|
450
|
+
* @returns {Object}
|
|
451
|
+
*/
|
|
452
|
+
getStats() {
|
|
453
|
+
const categories = {};
|
|
454
|
+
for (const error of this.errors) {
|
|
455
|
+
categories[error.category] = (categories[error.category] || 0) + 1;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return {
|
|
459
|
+
totalErrors: this.errors.length,
|
|
460
|
+
byCategory: categories,
|
|
461
|
+
recentErrors: this.errors.slice(-10),
|
|
462
|
+
errorRates: Object.fromEntries(this.errorRates)
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// ============================================================================
|
|
468
|
+
// ASYNC WRAPPERS
|
|
469
|
+
// ============================================================================
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Wrap an async function with error handling
|
|
473
|
+
* @param {Function} fn - Async function to wrap
|
|
474
|
+
* @param {ErrorHandler} handler - Error handler
|
|
475
|
+
* @param {Object} options - Options
|
|
476
|
+
* @returns {Function}
|
|
477
|
+
*/
|
|
478
|
+
function withErrorHandling(fn, handler, options = {}) {
|
|
479
|
+
const maxRetries = options.maxRetries || 3;
|
|
480
|
+
const context = options.context || {};
|
|
481
|
+
|
|
482
|
+
return async function(...args) {
|
|
483
|
+
let lastError;
|
|
484
|
+
|
|
485
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
486
|
+
try {
|
|
487
|
+
return await fn.apply(this, args);
|
|
488
|
+
} catch (error) {
|
|
489
|
+
lastError = error;
|
|
490
|
+
|
|
491
|
+
const result = await handler.handle(error, {
|
|
492
|
+
...context,
|
|
493
|
+
attempt,
|
|
494
|
+
maxRetries,
|
|
495
|
+
canRetry: attempt < maxRetries
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
if (!result.retry) {
|
|
499
|
+
throw handler.normalize(error);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Apply backoff
|
|
503
|
+
const backoff = options.backoff || ((a) => Math.pow(2, a) * 100);
|
|
504
|
+
await new Promise(r => setTimeout(r, backoff(attempt)));
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
throw handler.normalize(lastError);
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Create an async error boundary
|
|
514
|
+
* @param {Function} fn - Async function
|
|
515
|
+
* @param {Object} options - Options
|
|
516
|
+
* @returns {Promise}
|
|
517
|
+
*/
|
|
518
|
+
async function errorBoundary(fn, options = {}) {
|
|
519
|
+
const handler = options.handler;
|
|
520
|
+
const fallback = options.fallback;
|
|
521
|
+
|
|
522
|
+
if (!handler) {
|
|
523
|
+
throw new Error('errorBoundary requires an error handler');
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
try {
|
|
527
|
+
return await fn();
|
|
528
|
+
} catch (error) {
|
|
529
|
+
const result = await handler.handle(error, options.context);
|
|
530
|
+
|
|
531
|
+
if (fallback !== undefined) {
|
|
532
|
+
return typeof fallback === 'function' ? fallback(error) : fallback;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
throw result.error;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Wrap a promise with timeout
|
|
541
|
+
* @param {Promise} promise - Promise to wrap
|
|
542
|
+
* @param {number} timeout - Timeout in ms
|
|
543
|
+
* @param {string} operation - Operation name
|
|
544
|
+
* @returns {Promise}
|
|
545
|
+
*/
|
|
546
|
+
function withTimeout(promise, timeout, operation = 'operation') {
|
|
547
|
+
return Promise.race([
|
|
548
|
+
promise,
|
|
549
|
+
new Promise((_, reject) => {
|
|
550
|
+
setTimeout(() => {
|
|
551
|
+
reject(new TimeoutError(`${operation} timed out after ${timeout}ms`, {
|
|
552
|
+
timeout,
|
|
553
|
+
operation
|
|
554
|
+
}));
|
|
555
|
+
}, timeout);
|
|
556
|
+
})
|
|
557
|
+
]);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// ============================================================================
|
|
561
|
+
// EXPORTS
|
|
562
|
+
// ============================================================================
|
|
563
|
+
|
|
564
|
+
module.exports = {
|
|
565
|
+
// Levels and categories
|
|
566
|
+
LogLevel,
|
|
567
|
+
LogLevelNames,
|
|
568
|
+
ErrorCategory,
|
|
569
|
+
|
|
570
|
+
// Error classes
|
|
571
|
+
AlephError,
|
|
572
|
+
NetworkError,
|
|
573
|
+
LLMError,
|
|
574
|
+
ValidationError,
|
|
575
|
+
TimeoutError,
|
|
576
|
+
|
|
577
|
+
// Event emitter (browser-compatible)
|
|
578
|
+
SimpleEventEmitter,
|
|
579
|
+
|
|
580
|
+
// Error handler
|
|
581
|
+
ErrorHandler,
|
|
582
|
+
|
|
583
|
+
// Utilities
|
|
584
|
+
withErrorHandling,
|
|
585
|
+
errorBoundary,
|
|
586
|
+
withTimeout
|
|
587
|
+
};
|