supaclaw 1.0.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.
@@ -0,0 +1,301 @@
1
+ "use strict";
2
+ /**
3
+ * Error handling and retry logic for OpenClaw Memory
4
+ *
5
+ * Provides:
6
+ * - Custom error types
7
+ * - Retry logic with exponential backoff
8
+ * - Circuit breaker pattern
9
+ * - Error recovery strategies
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.CircuitBreaker = exports.RateLimitError = exports.ValidationError = exports.EmbeddingError = exports.DatabaseError = exports.OpenClawError = void 0;
13
+ exports.retry = retry;
14
+ exports.wrapDatabaseOperation = wrapDatabaseOperation;
15
+ exports.wrapEmbeddingOperation = wrapEmbeddingOperation;
16
+ exports.validateInput = validateInput;
17
+ exports.safeJsonParse = safeJsonParse;
18
+ exports.withTimeout = withTimeout;
19
+ exports.gracefulFallback = gracefulFallback;
20
+ exports.batchWithErrorHandling = batchWithErrorHandling;
21
+ // Custom error types
22
+ class OpenClawError extends Error {
23
+ code;
24
+ details;
25
+ constructor(message, code, details) {
26
+ super(message);
27
+ this.code = code;
28
+ this.details = details;
29
+ this.name = 'OpenClawError';
30
+ }
31
+ }
32
+ exports.OpenClawError = OpenClawError;
33
+ class DatabaseError extends OpenClawError {
34
+ constructor(message, details) {
35
+ super(message, 'DATABASE_ERROR', details);
36
+ this.name = 'DatabaseError';
37
+ }
38
+ }
39
+ exports.DatabaseError = DatabaseError;
40
+ class EmbeddingError extends OpenClawError {
41
+ constructor(message, details) {
42
+ super(message, 'EMBEDDING_ERROR', details);
43
+ this.name = 'EmbeddingError';
44
+ }
45
+ }
46
+ exports.EmbeddingError = EmbeddingError;
47
+ class ValidationError extends OpenClawError {
48
+ constructor(message, details) {
49
+ super(message, 'VALIDATION_ERROR', details);
50
+ this.name = 'ValidationError';
51
+ }
52
+ }
53
+ exports.ValidationError = ValidationError;
54
+ class RateLimitError extends OpenClawError {
55
+ constructor(message, details) {
56
+ super(message, 'RATE_LIMIT_ERROR', details);
57
+ this.name = 'RateLimitError';
58
+ }
59
+ }
60
+ exports.RateLimitError = RateLimitError;
61
+ const DEFAULT_RETRY_OPTIONS = {
62
+ maxAttempts: 3,
63
+ initialDelayMs: 1000,
64
+ maxDelayMs: 10000,
65
+ backoffMultiplier: 2,
66
+ shouldRetry: (error) => {
67
+ // Retry on network errors, rate limits, and transient database errors
68
+ if (error instanceof RateLimitError)
69
+ return true;
70
+ if (error instanceof DatabaseError) {
71
+ // Don't retry validation/constraint errors
72
+ const msg = error.message.toLowerCase();
73
+ return !msg.includes('constraint') && !msg.includes('invalid');
74
+ }
75
+ // Retry on network errors
76
+ if (error.message.includes('ECONNREFUSED'))
77
+ return true;
78
+ if (error.message.includes('ETIMEDOUT'))
79
+ return true;
80
+ if (error.message.includes('ENOTFOUND'))
81
+ return true;
82
+ return false;
83
+ },
84
+ onRetry: () => { }, // No-op by default
85
+ };
86
+ /**
87
+ * Retry a function with exponential backoff
88
+ */
89
+ async function retry(fn, options) {
90
+ const opts = { ...DEFAULT_RETRY_OPTIONS, ...options };
91
+ let lastError;
92
+ for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {
93
+ try {
94
+ return await fn();
95
+ }
96
+ catch (error) {
97
+ lastError = error;
98
+ // Don't retry if we've exhausted attempts or error is non-retryable
99
+ if (attempt >= opts.maxAttempts || !opts.shouldRetry(lastError)) {
100
+ throw lastError;
101
+ }
102
+ // Calculate delay with exponential backoff
103
+ const delay = Math.min(opts.initialDelayMs * Math.pow(opts.backoffMultiplier, attempt - 1), opts.maxDelayMs);
104
+ opts.onRetry(attempt, lastError);
105
+ // Wait before retrying
106
+ await new Promise(resolve => setTimeout(resolve, delay));
107
+ }
108
+ }
109
+ throw lastError;
110
+ }
111
+ /**
112
+ * Circuit breaker pattern
113
+ * Prevents cascading failures by failing fast when error rate is high
114
+ */
115
+ class CircuitBreaker {
116
+ failureThreshold;
117
+ recoveryTimeMs;
118
+ successThreshold;
119
+ failures = 0;
120
+ successes = 0;
121
+ lastFailureTime = 0;
122
+ state = 'closed';
123
+ constructor(failureThreshold = 5, recoveryTimeMs = 60000, // 1 minute
124
+ successThreshold = 2) {
125
+ this.failureThreshold = failureThreshold;
126
+ this.recoveryTimeMs = recoveryTimeMs;
127
+ this.successThreshold = successThreshold;
128
+ }
129
+ async execute(fn) {
130
+ // If circuit is open, check if recovery time has passed
131
+ if (this.state === 'open') {
132
+ const now = Date.now();
133
+ if (now - this.lastFailureTime > this.recoveryTimeMs) {
134
+ this.state = 'half-open';
135
+ this.successes = 0;
136
+ }
137
+ else {
138
+ throw new OpenClawError('Circuit breaker is open', 'CIRCUIT_BREAKER_OPEN', { failures: this.failures, lastFailureTime: this.lastFailureTime });
139
+ }
140
+ }
141
+ try {
142
+ const result = await fn();
143
+ this.onSuccess();
144
+ return result;
145
+ }
146
+ catch (error) {
147
+ this.onFailure();
148
+ throw error;
149
+ }
150
+ }
151
+ onSuccess() {
152
+ this.failures = 0;
153
+ if (this.state === 'half-open') {
154
+ this.successes++;
155
+ if (this.successes >= this.successThreshold) {
156
+ this.state = 'closed';
157
+ this.successes = 0;
158
+ }
159
+ }
160
+ }
161
+ onFailure() {
162
+ this.failures++;
163
+ this.lastFailureTime = Date.now();
164
+ if (this.failures >= this.failureThreshold) {
165
+ this.state = 'open';
166
+ }
167
+ }
168
+ reset() {
169
+ this.failures = 0;
170
+ this.successes = 0;
171
+ this.lastFailureTime = 0;
172
+ this.state = 'closed';
173
+ }
174
+ getState() {
175
+ return {
176
+ state: this.state,
177
+ failures: this.failures,
178
+ successes: this.successes,
179
+ lastFailureTime: this.lastFailureTime,
180
+ };
181
+ }
182
+ }
183
+ exports.CircuitBreaker = CircuitBreaker;
184
+ /**
185
+ * Wrap database operations with error handling
186
+ */
187
+ function wrapDatabaseOperation(operation, errorContext) {
188
+ return retry(async () => {
189
+ try {
190
+ return await operation();
191
+ }
192
+ catch (error) {
193
+ throw new DatabaseError(`${errorContext}: ${error.message}`, error);
194
+ }
195
+ }, {
196
+ maxAttempts: 3,
197
+ initialDelayMs: 500,
198
+ onRetry: (attempt, error) => {
199
+ console.warn(`Retrying ${errorContext} (attempt ${attempt}):`, error.message);
200
+ },
201
+ });
202
+ }
203
+ /**
204
+ * Wrap embedding operations with error handling
205
+ */
206
+ function wrapEmbeddingOperation(operation, errorContext) {
207
+ return retry(async () => {
208
+ try {
209
+ return await operation();
210
+ }
211
+ catch (error) {
212
+ const err = error;
213
+ // Check for rate limit errors
214
+ if (err.message.includes('rate_limit') || err.message.includes('429')) {
215
+ throw new RateLimitError(`${errorContext}: Rate limit exceeded`, error);
216
+ }
217
+ throw new EmbeddingError(`${errorContext}: ${err.message}`, error);
218
+ }
219
+ }, {
220
+ maxAttempts: 3,
221
+ initialDelayMs: 2000, // Longer delay for API rate limits
222
+ backoffMultiplier: 3, // More aggressive backoff
223
+ onRetry: (attempt, error) => {
224
+ console.warn(`Retrying ${errorContext} (attempt ${attempt}):`, error.message);
225
+ },
226
+ });
227
+ }
228
+ /**
229
+ * Validate inputs
230
+ */
231
+ function validateInput(condition, message, details) {
232
+ if (!condition) {
233
+ throw new ValidationError(message, details);
234
+ }
235
+ }
236
+ /**
237
+ * Safe JSON parse
238
+ */
239
+ function safeJsonParse(json, fallback) {
240
+ try {
241
+ return JSON.parse(json);
242
+ }
243
+ catch {
244
+ return fallback;
245
+ }
246
+ }
247
+ /**
248
+ * Safe async operation with timeout
249
+ */
250
+ async function withTimeout(promise, timeoutMs, errorMessage = 'Operation timed out') {
251
+ return Promise.race([
252
+ promise,
253
+ new Promise((_, reject) => setTimeout(() => reject(new OpenClawError(errorMessage, 'TIMEOUT')), timeoutMs)),
254
+ ]);
255
+ }
256
+ /**
257
+ * Graceful degradation helper
258
+ */
259
+ async function gracefulFallback(primary, fallback, errorContext) {
260
+ try {
261
+ return await primary();
262
+ }
263
+ catch (error) {
264
+ console.warn(`${errorContext} failed, using fallback:`, error.message);
265
+ return await fallback();
266
+ }
267
+ }
268
+ /**
269
+ * Batch operation with error handling
270
+ * Continues processing even if some items fail
271
+ */
272
+ async function batchWithErrorHandling(items, operation, options) {
273
+ const opts = {
274
+ continueOnError: true,
275
+ onError: () => { },
276
+ ...options,
277
+ };
278
+ const results = await Promise.allSettled(items.map(async (item) => ({
279
+ item,
280
+ result: await operation(item),
281
+ })));
282
+ return results.map((result, index) => {
283
+ const item = items[index];
284
+ if (result.status === 'fulfilled') {
285
+ return {
286
+ success: true,
287
+ result: result.value.result,
288
+ item,
289
+ };
290
+ }
291
+ else {
292
+ const error = result.reason;
293
+ opts.onError(item, error);
294
+ return {
295
+ success: false,
296
+ error,
297
+ item,
298
+ };
299
+ }
300
+ });
301
+ }