@gotza02/seq-thinking 1.1.4 → 1.1.6
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 +31 -27
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp-server.js +1 -1
- package/package.json +8 -2
- package/agents_test.log +0 -15
- package/data/agents/1770106504306-dljh9ef.json +0 -68
- package/data/agents/1770106504310-4oarrst.json +0 -58
- package/data/agents/1770106540588-pvitt55.json +0 -68
- package/data/agents/1770106540595-z2ya871.json +0 -58
- package/data/agents/1770106710890-0e2naq1.json +0 -68
- package/data/agents/1770106710893-r076yxx.json +0 -58
- package/data/agents/1770109212161-4ybd0i7.json +0 -68
- package/data/agents/1770109212166-gkhya8h.json +0 -58
- package/data/agents/1770117726716-lrnm415.json +0 -68
- package/data/agents/1770117726719-w6hsf3v.json +0 -58
- package/data/sessions/1770100622009-5afiuyv.json +0 -499
- package/data/sessions/1770106504312-75zk750.json +0 -107
- package/data/sessions/1770106540597-z8e8soo.json +0 -150
- package/data/sessions/1770106710894-0kxgy5x.json +0 -150
- package/data/sessions/1770109212169-zpddeb9.json +0 -150
- package/data/sessions/1770117726720-frcwj99.json +0 -150
- package/real_world_test.log +0 -200
- package/real_world_test_dynamic.log +0 -184
- package/real_world_test_real.log +0 -184
- package/src/__tests__/agents.test.ts +0 -858
- package/src/__tests__/mcp-server.test.ts +0 -380
- package/src/__tests__/sequential-thinking.test.ts +0 -687
- package/src/__tests__/swarm-coordinator.test.ts +0 -903
- package/src/__tests__/types.test.ts +0 -839
- package/src/__tests__/utils.test.ts +0 -322
- package/src/agents/base-agent.ts +0 -288
- package/src/agents/critic-agent.ts +0 -582
- package/src/agents/index.ts +0 -11
- package/src/agents/meta-reasoning-agent.ts +0 -314
- package/src/agents/reasoner-agent.ts +0 -312
- package/src/agents/synthesizer-agent.ts +0 -641
- package/src/index.ts +0 -118
- package/src/mcp-server.ts +0 -391
- package/src/real_world_test.ts +0 -89
- package/src/sequential-thinking.ts +0 -614
- package/src/swarm-coordinator.ts +0 -772
- package/src/types/index.ts +0 -915
- package/src/utils/index.ts +0 -1004
- package/src/utils/llm-adapter.ts +0 -110
- package/src/utils/logger.ts +0 -56
- package/src/utils/persistence.ts +0 -109
- package/test_output.log +0 -0
- package/tsconfig.json +0 -21
package/src/utils/index.ts
DELETED
|
@@ -1,1004 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Utility Functions for MCP Sequential Thinking System
|
|
3
|
-
* @module utils
|
|
4
|
-
* @version 1.0.0
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
8
|
-
|
|
9
|
-
// ============================================================================
|
|
10
|
-
// ID Generation
|
|
11
|
-
// ============================================================================
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Generate a unique ID based on timestamp and random string
|
|
15
|
-
* @returns Unique ID string
|
|
16
|
-
*/
|
|
17
|
-
export function generateId(): string {
|
|
18
|
-
const timestamp = Date.now().toString();
|
|
19
|
-
const random = Math.random().toString(36).substring(2, 9).padEnd(7, '0');
|
|
20
|
-
return `${timestamp}-${random}`;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Generate a UUID v4
|
|
25
|
-
* @returns UUID string
|
|
26
|
-
*/
|
|
27
|
-
export function generateUUID(): string {
|
|
28
|
-
return uuidv4();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Generate a short 8-character ID
|
|
33
|
-
* @returns Short ID string
|
|
34
|
-
*/
|
|
35
|
-
export function generateShortId(): string {
|
|
36
|
-
return Math.random().toString(36).substring(2, 10);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// ============================================================================
|
|
40
|
-
// Numeric Utilities
|
|
41
|
-
// ============================================================================
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Clamp a value between min and max
|
|
45
|
-
* @param value - Value to clamp
|
|
46
|
-
* @param min - Minimum value
|
|
47
|
-
* @param max - Maximum value
|
|
48
|
-
* @returns Clamped value
|
|
49
|
-
*/
|
|
50
|
-
export function clamp(value: number, min: number, max: number): number {
|
|
51
|
-
return Math.min(max, Math.max(min, value));
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Clamp a confidence score to [0, 1]
|
|
56
|
-
* @param confidence - Confidence score
|
|
57
|
-
* @returns Clamped confidence
|
|
58
|
-
*/
|
|
59
|
-
export function clampConfidence(confidence: number): number {
|
|
60
|
-
return clamp(confidence, 0, 1);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Linear interpolation between two values
|
|
65
|
-
* @param a - Start value
|
|
66
|
-
* @param b - End value
|
|
67
|
-
* @param t - Interpolation factor (0-1)
|
|
68
|
-
* @returns Interpolated value
|
|
69
|
-
*/
|
|
70
|
-
export function lerp(a: number, b: number, t: number): number {
|
|
71
|
-
return a + (b - a) * clamp(t, 0, 1);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Round a number to specified decimal places
|
|
76
|
-
* @param value - Value to round
|
|
77
|
-
* @param decimals - Number of decimal places
|
|
78
|
-
* @returns Rounded value
|
|
79
|
-
*/
|
|
80
|
-
export function roundTo(value: number, decimals: number): number {
|
|
81
|
-
const factor = Math.pow(10, decimals);
|
|
82
|
-
return Math.round(value * factor) / factor;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// ============================================================================
|
|
86
|
-
// Similarity Calculation
|
|
87
|
-
// ============================================================================
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Extract words from text
|
|
91
|
-
* @param text - Input text
|
|
92
|
-
* @returns Array of words
|
|
93
|
-
*/
|
|
94
|
-
export function extractWords(text: string): string[] {
|
|
95
|
-
return text.toLowerCase().match(/\b\w+\b/g) || [];
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Calculate Jaccard similarity between two sets
|
|
100
|
-
* @param setA - First set
|
|
101
|
-
* @param setB - Second set
|
|
102
|
-
* @returns Jaccard similarity (0-1)
|
|
103
|
-
*/
|
|
104
|
-
export function calculateJaccardSimilarity<T>(setA: Set<T>, setB: Set<T>): number {
|
|
105
|
-
const intersection = new Set([...setA].filter(x => setB.has(x)));
|
|
106
|
-
const union = new Set([...setA, ...setB]);
|
|
107
|
-
return union.size === 0 ? 0 : intersection.size / union.size;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Calculate Levenshtein distance between two strings
|
|
112
|
-
* @param a - First string
|
|
113
|
-
* @param b - Second string
|
|
114
|
-
* @returns Levenshtein distance
|
|
115
|
-
*/
|
|
116
|
-
export function levenshteinDistance(a: string, b: string): number {
|
|
117
|
-
const matrix: number[][] = [];
|
|
118
|
-
|
|
119
|
-
for (let i = 0; i <= b.length; i++) {
|
|
120
|
-
matrix[i] = [i];
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
for (let j = 0; j <= a.length; j++) {
|
|
124
|
-
matrix[0][j] = j;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
for (let i = 1; i <= b.length; i++) {
|
|
128
|
-
for (let j = 1; j <= a.length; j++) {
|
|
129
|
-
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
130
|
-
matrix[i][j] = matrix[i - 1][j - 1];
|
|
131
|
-
} else {
|
|
132
|
-
matrix[i][j] = Math.min(
|
|
133
|
-
matrix[i - 1][j - 1] + 1, // substitution
|
|
134
|
-
matrix[i][j - 1] + 1, // insertion
|
|
135
|
-
matrix[i - 1][j] + 1 // deletion
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return matrix[b.length][a.length];
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Calculate text similarity using Levenshtein distance
|
|
146
|
-
* @param a - First text
|
|
147
|
-
* @param b - Second text
|
|
148
|
-
* @returns Similarity score (0-1)
|
|
149
|
-
*/
|
|
150
|
-
export function calculateSimilarity(a: string, b: string): number {
|
|
151
|
-
if (a === b) return 1;
|
|
152
|
-
if (a.length === 0 || b.length === 0) return 0;
|
|
153
|
-
|
|
154
|
-
// Optimization: return 0 if no common characters
|
|
155
|
-
const setA = new Set(a.split(''));
|
|
156
|
-
const hasCommon = b.split('').some(char => setA.has(char));
|
|
157
|
-
if (!hasCommon) return 0;
|
|
158
|
-
|
|
159
|
-
const distance = levenshteinDistance(a, b);
|
|
160
|
-
const longestLength = Math.max(a.length, b.length);
|
|
161
|
-
|
|
162
|
-
return (longestLength - distance) / longestLength;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Calculate cosine similarity between two vectors
|
|
167
|
-
* @param a - First vector
|
|
168
|
-
* @param b - Second vector
|
|
169
|
-
* @returns Cosine similarity (-1 to 1)
|
|
170
|
-
*/
|
|
171
|
-
export function calculateCosineSimilarity(a: number[], b: number[]): number {
|
|
172
|
-
if (a.length !== b.length) {
|
|
173
|
-
throw new Error('Vectors must have same length');
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
let dotProduct = 0;
|
|
177
|
-
let normA = 0;
|
|
178
|
-
let normB = 0;
|
|
179
|
-
|
|
180
|
-
for (let i = 0; i < a.length; i++) {
|
|
181
|
-
dotProduct += a[i] * b[i];
|
|
182
|
-
normA += a[i] * a[i];
|
|
183
|
-
normB += b[i] * b[i];
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (normA === 0 || normB === 0) {
|
|
187
|
-
return 0;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Extract n-grams from text
|
|
195
|
-
* @param text - Input text
|
|
196
|
-
* @param n - N-gram size
|
|
197
|
-
* @returns Array of n-grams
|
|
198
|
-
*/
|
|
199
|
-
export function extractNGrams(text: string, n: number): string[] {
|
|
200
|
-
const words = extractWords(text);
|
|
201
|
-
const ngrams: string[] = [];
|
|
202
|
-
|
|
203
|
-
for (let i = 0; i <= words.length - n; i++) {
|
|
204
|
-
ngrams.push(words.slice(i, i + n).join(' '));
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return ngrams;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Calculate n-gram similarity between two texts
|
|
212
|
-
* @param a - First text
|
|
213
|
-
* @param b - Second text
|
|
214
|
-
* @param n - N-gram size (default: 2)
|
|
215
|
-
* @returns Similarity score (0-1)
|
|
216
|
-
*/
|
|
217
|
-
export function calculateNGramSimilarity(a: string, b: string, n = 2): number {
|
|
218
|
-
const ngramsA = new Set(extractNGrams(a, n));
|
|
219
|
-
const ngramsB = new Set(extractNGrams(b, n));
|
|
220
|
-
return calculateJaccardSimilarity(ngramsA, ngramsB);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// ============================================================================
|
|
224
|
-
// Statistical Utilities
|
|
225
|
-
// ============================================================================
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Calculate mean of an array of numbers
|
|
229
|
-
* @param values - Array of numbers
|
|
230
|
-
* @returns Mean value
|
|
231
|
-
*/
|
|
232
|
-
export function calculateMean(values: number[]): number {
|
|
233
|
-
if (values.length === 0) return NaN;
|
|
234
|
-
return values.reduce((sum, v) => sum + v, 0) / values.length;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Calculate weighted average
|
|
239
|
-
* @param values - Array of values
|
|
240
|
-
* @param weights - Array of weights
|
|
241
|
-
* @returns Weighted average
|
|
242
|
-
*/
|
|
243
|
-
export function calculateWeightedAverage(values: number[], weights: number[]): number {
|
|
244
|
-
if (values.length !== weights.length) {
|
|
245
|
-
throw new Error('Values and weights must have same length');
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (values.length === 0) return NaN;
|
|
249
|
-
|
|
250
|
-
const totalWeight = weights.reduce((sum, w) => sum + w, 0);
|
|
251
|
-
if (totalWeight === 0) return 0;
|
|
252
|
-
|
|
253
|
-
const weightedSum = values.reduce((sum, v, i) => sum + v * weights[i], 0);
|
|
254
|
-
return weightedSum / totalWeight;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Calculate median of an array of numbers
|
|
259
|
-
* @param values - Array of numbers
|
|
260
|
-
* @returns Median value
|
|
261
|
-
*/
|
|
262
|
-
export function calculateMedian(values: number[]): number {
|
|
263
|
-
if (values.length === 0) return 0;
|
|
264
|
-
|
|
265
|
-
const sorted = [...values].sort((a, b) => a - b);
|
|
266
|
-
const mid = Math.floor(sorted.length / 2);
|
|
267
|
-
|
|
268
|
-
if (sorted.length % 2 === 0) {
|
|
269
|
-
return (sorted[mid - 1] + sorted[mid]) / 2;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return sorted[mid];
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Calculate standard deviation
|
|
277
|
-
* @param values - Array of numbers
|
|
278
|
-
* @returns Standard deviation
|
|
279
|
-
*/
|
|
280
|
-
export function calculateStandardDeviation(values: number[]): number {
|
|
281
|
-
if (values.length === 0) return 0;
|
|
282
|
-
|
|
283
|
-
const mean = calculateMean(values);
|
|
284
|
-
const squaredDiffs = values.map(v => Math.pow(v - mean, 2));
|
|
285
|
-
const variance = calculateMean(squaredDiffs);
|
|
286
|
-
|
|
287
|
-
return Math.sqrt(variance);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Calculate variance
|
|
292
|
-
* @param values - Array of numbers
|
|
293
|
-
* @returns Variance
|
|
294
|
-
*/
|
|
295
|
-
export function calculateVariance(values: number[]): number {
|
|
296
|
-
if (values.length === 0) return 0;
|
|
297
|
-
|
|
298
|
-
const mean = calculateMean(values);
|
|
299
|
-
const squaredDiffs = values.map(v => Math.pow(v - mean, 2));
|
|
300
|
-
|
|
301
|
-
return calculateMean(squaredDiffs);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* Find minimum value
|
|
306
|
-
* @param values - Array of numbers
|
|
307
|
-
* @returns Minimum value
|
|
308
|
-
*/
|
|
309
|
-
export function findMin(values: number[]): number {
|
|
310
|
-
if (values.length === 0) return 0;
|
|
311
|
-
return Math.min(...values);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Find maximum value
|
|
316
|
-
* @param values - Array of numbers
|
|
317
|
-
* @returns Maximum value
|
|
318
|
-
*/
|
|
319
|
-
export function findMax(values: number[]): number {
|
|
320
|
-
if (values.length === 0) return 0;
|
|
321
|
-
return Math.max(...values);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Normalize values to [0, 1] range
|
|
326
|
-
* @param values - Array of numbers
|
|
327
|
-
* @returns Normalized values
|
|
328
|
-
*/
|
|
329
|
-
export function normalizeValues(values: number[]): number[] {
|
|
330
|
-
if (values.length === 0) return [];
|
|
331
|
-
|
|
332
|
-
const min = findMin(values);
|
|
333
|
-
const max = findMax(values);
|
|
334
|
-
const range = max - min;
|
|
335
|
-
|
|
336
|
-
if (range === 0) return values.map(() => 0.5);
|
|
337
|
-
|
|
338
|
-
return values.map(v => (v - min) / range);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* Calculate percentile
|
|
343
|
-
* @param values - Array of numbers
|
|
344
|
-
* @param percentile - Percentile (0-100)
|
|
345
|
-
* @returns Percentile value
|
|
346
|
-
*/
|
|
347
|
-
export function calculatePercentile(values: number[], percentile: number): number {
|
|
348
|
-
if (values.length === 0) return 0;
|
|
349
|
-
|
|
350
|
-
const sorted = [...values].sort((a, b) => a - b);
|
|
351
|
-
const index = (percentile / 100) * (sorted.length - 1);
|
|
352
|
-
const lower = Math.floor(index);
|
|
353
|
-
const upper = Math.ceil(index);
|
|
354
|
-
const weight = index - lower;
|
|
355
|
-
|
|
356
|
-
return sorted[lower] * (1 - weight) + sorted[upper] * weight;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// ============================================================================
|
|
360
|
-
// Time Formatting
|
|
361
|
-
// ============================================================================
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* Format duration in milliseconds to human-readable string
|
|
365
|
-
* @param ms - Duration in milliseconds
|
|
366
|
-
* @returns Formatted string
|
|
367
|
-
*/
|
|
368
|
-
export function formatDuration(ms: number): string {
|
|
369
|
-
const isNegative = ms < 0;
|
|
370
|
-
const absMs = Math.abs(Math.floor(ms));
|
|
371
|
-
const sign = isNegative ? '-' : '';
|
|
372
|
-
|
|
373
|
-
if (absMs < 1000) {
|
|
374
|
-
return `${sign}${absMs}ms`;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
const seconds = Math.floor(absMs / 1000);
|
|
378
|
-
if (seconds < 60) {
|
|
379
|
-
return `${sign}${seconds}s`;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
const minutes = Math.floor(seconds / 60);
|
|
383
|
-
if (minutes < 60) {
|
|
384
|
-
return `${sign}${minutes}m`;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
const hours = Math.floor(minutes / 60);
|
|
388
|
-
return `${sign}${hours}h`;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
/**
|
|
392
|
-
* Format timestamp to ISO string
|
|
393
|
-
* @param timestamp - Timestamp (Date or number)
|
|
394
|
-
* @returns ISO string
|
|
395
|
-
*/
|
|
396
|
-
export function formatTimestamp(timestamp: Date | number): string {
|
|
397
|
-
const date = timestamp instanceof Date ? timestamp : new Date(timestamp);
|
|
398
|
-
return date.toISOString();
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* Format relative time (e.g., "2m ago")
|
|
403
|
-
* @param timestamp - Timestamp (Date or number)
|
|
404
|
-
* @returns Relative time string
|
|
405
|
-
*/
|
|
406
|
-
export function formatRelativeTime(timestamp: Date | number): string {
|
|
407
|
-
const date = timestamp instanceof Date ? timestamp : new Date(timestamp);
|
|
408
|
-
const now = new Date();
|
|
409
|
-
const diffMs = now.getTime() - date.getTime();
|
|
410
|
-
|
|
411
|
-
const seconds = Math.floor(diffMs / 1000);
|
|
412
|
-
const minutes = Math.floor(seconds / 60);
|
|
413
|
-
const hours = Math.floor(minutes / 60);
|
|
414
|
-
const days = Math.floor(hours / 24);
|
|
415
|
-
|
|
416
|
-
if (days > 0) return `${days}d ago`;
|
|
417
|
-
if (hours > 0) return `${hours}h ago`;
|
|
418
|
-
if (minutes > 0) return `${minutes}m ago`;
|
|
419
|
-
return `${seconds}s ago`;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// ============================================================================
|
|
423
|
-
// Object Utilities
|
|
424
|
-
// ============================================================================
|
|
425
|
-
|
|
426
|
-
/**
|
|
427
|
-
* Deep clone an object
|
|
428
|
-
* @param obj - Object to clone
|
|
429
|
-
* @returns Cloned object
|
|
430
|
-
*/
|
|
431
|
-
export function deepClone<T>(obj: T): T {
|
|
432
|
-
if (obj === null || typeof obj !== 'object') return obj;
|
|
433
|
-
if (obj instanceof Date) return new Date(obj.getTime()) as unknown as T;
|
|
434
|
-
if (Array.isArray(obj)) return obj.map(deepClone) as unknown as T;
|
|
435
|
-
|
|
436
|
-
const cloned = {} as T;
|
|
437
|
-
for (const key in obj) {
|
|
438
|
-
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
439
|
-
cloned[key] = deepClone(obj[key]);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
return cloned;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
/**
|
|
447
|
-
* Deep clone with Date support
|
|
448
|
-
* @param obj - Object to clone
|
|
449
|
-
* @returns Cloned object with Dates preserved
|
|
450
|
-
*/
|
|
451
|
-
export function deepCloneWithDates<T>(obj: T): T {
|
|
452
|
-
return deepClone(obj);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
/**
|
|
456
|
-
* Deep merge two objects
|
|
457
|
-
* @param target - Target object
|
|
458
|
-
* @param source - Source object
|
|
459
|
-
* @returns Merged object
|
|
460
|
-
*/
|
|
461
|
-
export function deepMerge<T extends Record<string, unknown>>(
|
|
462
|
-
target: T,
|
|
463
|
-
source: Partial<T>
|
|
464
|
-
): T {
|
|
465
|
-
const result = { ...target };
|
|
466
|
-
|
|
467
|
-
for (const key in source) {
|
|
468
|
-
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
469
|
-
const sourceValue = source[key];
|
|
470
|
-
const targetValue = result[key];
|
|
471
|
-
|
|
472
|
-
if (
|
|
473
|
-
typeof sourceValue === 'object' &&
|
|
474
|
-
sourceValue !== null &&
|
|
475
|
-
!Array.isArray(sourceValue) &&
|
|
476
|
-
typeof targetValue === 'object' &&
|
|
477
|
-
targetValue !== null &&
|
|
478
|
-
!Array.isArray(targetValue)
|
|
479
|
-
) {
|
|
480
|
-
result[key] = deepMerge(
|
|
481
|
-
targetValue as Record<string, unknown>,
|
|
482
|
-
sourceValue as Record<string, unknown>
|
|
483
|
-
) as T[Extract<keyof T, string>];
|
|
484
|
-
} else {
|
|
485
|
-
result[key] = sourceValue as T[Extract<keyof T, string>];
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
return result;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
/**
|
|
494
|
-
* Deep equality check
|
|
495
|
-
* @param a - First object
|
|
496
|
-
* @param b - Second object
|
|
497
|
-
* @returns True if equal
|
|
498
|
-
*/
|
|
499
|
-
export function deepEqual(a: unknown, b: unknown): boolean {
|
|
500
|
-
if (a === b) return true;
|
|
501
|
-
if (a === null || b === null) return false;
|
|
502
|
-
if (typeof a !== typeof b) return false;
|
|
503
|
-
if (typeof a !== 'object') return false;
|
|
504
|
-
|
|
505
|
-
if (a instanceof Date && b instanceof Date) {
|
|
506
|
-
return a.getTime() === b.getTime();
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
510
|
-
if (a.length !== b.length) return false;
|
|
511
|
-
return a.every((item, i) => deepEqual(item, b[i]));
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
const aObj = a as Record<string, unknown>;
|
|
515
|
-
const bObj = b as Record<string, unknown>;
|
|
516
|
-
const aKeys = Object.keys(aObj);
|
|
517
|
-
const bKeys = Object.keys(bObj);
|
|
518
|
-
|
|
519
|
-
if (aKeys.length !== bKeys.length) return false;
|
|
520
|
-
|
|
521
|
-
return aKeys.every(key => deepEqual(aObj[key], bObj[key]));
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
/**
|
|
525
|
-
* Pick specific keys from an object
|
|
526
|
-
* @param obj - Source object
|
|
527
|
-
* @param keys - Keys to pick
|
|
528
|
-
* @returns Object with picked keys
|
|
529
|
-
*/
|
|
530
|
-
export function pick<T extends Record<string, unknown>, K extends keyof T>(
|
|
531
|
-
obj: T,
|
|
532
|
-
keys: K[]
|
|
533
|
-
): Pick<T, K> {
|
|
534
|
-
const result = {} as Pick<T, K>;
|
|
535
|
-
|
|
536
|
-
for (const key of keys) {
|
|
537
|
-
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
538
|
-
result[key] = obj[key];
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
return result;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
/**
|
|
546
|
-
* Omit specific keys from an object
|
|
547
|
-
* @param obj - Source object
|
|
548
|
-
* @param keys - Keys to omit
|
|
549
|
-
* @returns Object without omitted keys
|
|
550
|
-
*/
|
|
551
|
-
export function omit<T extends Record<string, unknown>, K extends keyof T>(
|
|
552
|
-
obj: T,
|
|
553
|
-
keys: K[]
|
|
554
|
-
): Omit<T, K> {
|
|
555
|
-
const result = { ...obj };
|
|
556
|
-
|
|
557
|
-
for (const key of keys) {
|
|
558
|
-
delete result[key];
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
return result as Omit<T, K>;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
// ============================================================================
|
|
565
|
-
// Validation Utilities
|
|
566
|
-
// ============================================================================
|
|
567
|
-
|
|
568
|
-
/**
|
|
569
|
-
* Validate UUID format
|
|
570
|
-
* @param uuid - String to validate
|
|
571
|
-
* @returns True if valid UUID
|
|
572
|
-
*/
|
|
573
|
-
export function validateUUID(uuid: string): boolean {
|
|
574
|
-
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
575
|
-
return uuidRegex.test(uuid);
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
/**
|
|
579
|
-
* Check if value is a non-empty string
|
|
580
|
-
* @param value - Value to check
|
|
581
|
-
* @returns True if non-empty string
|
|
582
|
-
*/
|
|
583
|
-
export function isNonEmptyString(value: unknown): value is string {
|
|
584
|
-
return typeof value === 'string' && value.length > 0;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
/**
|
|
588
|
-
* Check if value is a positive number
|
|
589
|
-
* @param value - Value to check
|
|
590
|
-
* @returns True if positive number
|
|
591
|
-
*/
|
|
592
|
-
export function isPositiveNumber(value: unknown): value is number {
|
|
593
|
-
return typeof value === 'number' && !isNaN(value) && value > 0;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
/**
|
|
597
|
-
* Check if value is in range
|
|
598
|
-
* @param value - Value to check
|
|
599
|
-
* @param min - Minimum value
|
|
600
|
-
* @param max - Maximum value
|
|
601
|
-
* @returns True if in range
|
|
602
|
-
*/
|
|
603
|
-
export function isInRange(value: number, min: number, max: number): boolean {
|
|
604
|
-
return value >= min && value <= max;
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
/**
|
|
608
|
-
* Check if value is a non-empty array
|
|
609
|
-
* @param value - Value to check
|
|
610
|
-
* @returns True if non-empty array
|
|
611
|
-
*/
|
|
612
|
-
export function isNonEmptyArray<T>(value: unknown): value is T[] {
|
|
613
|
-
return Array.isArray(value) && value.length > 0;
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
/**
|
|
617
|
-
* Check if value is a plain object
|
|
618
|
-
* @param value - Value to check
|
|
619
|
-
* @returns True if plain object
|
|
620
|
-
*/
|
|
621
|
-
export function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
622
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
// ============================================================================
|
|
626
|
-
// String Utilities
|
|
627
|
-
// ============================================================================
|
|
628
|
-
|
|
629
|
-
/**
|
|
630
|
-
* Sanitize input string (remove special characters)
|
|
631
|
-
* @param input - Input string
|
|
632
|
-
* @returns Sanitized string
|
|
633
|
-
*/
|
|
634
|
-
export function sanitizeString(input: string): string {
|
|
635
|
-
return input.replace(/[<>\"']/g, '');
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
/**
|
|
639
|
-
* Truncate string with ellipsis
|
|
640
|
-
* @param str - String to truncate
|
|
641
|
-
* @param maxLength - Maximum length
|
|
642
|
-
* @returns Truncated string
|
|
643
|
-
*/
|
|
644
|
-
export function truncateString(str: string, maxLength: number): string {
|
|
645
|
-
if (str.length <= maxLength) return str;
|
|
646
|
-
return str.substring(0, maxLength - 3) + '...';
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
/**
|
|
650
|
-
* Convert string to camelCase
|
|
651
|
-
* @param str - Input string
|
|
652
|
-
* @returns camelCase string
|
|
653
|
-
*/
|
|
654
|
-
export function toCamelCase(str: string): string {
|
|
655
|
-
return str
|
|
656
|
-
.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) =>
|
|
657
|
-
index === 0 ? word.toLowerCase() : word.toUpperCase()
|
|
658
|
-
)
|
|
659
|
-
.replace(/\s+/g, '');
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
/**
|
|
663
|
-
* Convert string to PascalCase
|
|
664
|
-
* @param str - Input string
|
|
665
|
-
* @returns PascalCase string
|
|
666
|
-
*/
|
|
667
|
-
export function toPascalCase(str: string): string {
|
|
668
|
-
return str
|
|
669
|
-
.replace(/(?:^\w|[A-Z]|\b\w)/g, word => word.toUpperCase())
|
|
670
|
-
.replace(/\s+/g, '');
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
/**
|
|
674
|
-
* Convert string to snake_case
|
|
675
|
-
* @param str - Input string
|
|
676
|
-
* @returns snake_case string
|
|
677
|
-
*/
|
|
678
|
-
export function toSnakeCase(str: string): string {
|
|
679
|
-
return str
|
|
680
|
-
.replace(/\W+/g, ' ')
|
|
681
|
-
.split(/ |\B(?=[A-Z])/)
|
|
682
|
-
.map(word => word.toLowerCase())
|
|
683
|
-
.join('_');
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
/**
|
|
687
|
-
* Convert string to kebab-case
|
|
688
|
-
* @param str - Input string
|
|
689
|
-
* @returns kebab-case string
|
|
690
|
-
*/
|
|
691
|
-
export function toKebabCase(str: string): string {
|
|
692
|
-
return str
|
|
693
|
-
.replace(/\W+/g, ' ')
|
|
694
|
-
.split(/ |\B(?=[A-Z])/)
|
|
695
|
-
.map(word => word.toLowerCase())
|
|
696
|
-
.join('-');
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
/**
|
|
700
|
-
* Escape regex special characters
|
|
701
|
-
* @param str - Input string
|
|
702
|
-
* @returns Escaped string
|
|
703
|
-
*/
|
|
704
|
-
export function escapeRegExp(str: string): string {
|
|
705
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
// ============================================================================
|
|
709
|
-
// Array Utilities
|
|
710
|
-
// ============================================================================
|
|
711
|
-
|
|
712
|
-
/**
|
|
713
|
-
* Remove duplicates from array
|
|
714
|
-
* @param arr - Input array
|
|
715
|
-
* @returns Array without duplicates
|
|
716
|
-
*/
|
|
717
|
-
export function unique<T>(arr: T[]): T[] {
|
|
718
|
-
return [...new Set(arr)];
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
/**
|
|
722
|
-
* Remove duplicates by key function
|
|
723
|
-
* @param arr - Input array
|
|
724
|
-
* @param keyFn - Key function
|
|
725
|
-
* @returns Array without duplicates
|
|
726
|
-
*/
|
|
727
|
-
export function uniqueBy<T>(arr: T[], keyFn: (item: T) => unknown): T[] {
|
|
728
|
-
const seen = new Set<unknown>();
|
|
729
|
-
return arr.filter(item => {
|
|
730
|
-
const key = keyFn(item);
|
|
731
|
-
if (seen.has(key)) return false;
|
|
732
|
-
seen.add(key);
|
|
733
|
-
return true;
|
|
734
|
-
});
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
/**
|
|
738
|
-
* Group array by key function
|
|
739
|
-
* @param arr - Input array
|
|
740
|
-
* @param keyFn - Key function
|
|
741
|
-
* @returns Grouped object
|
|
742
|
-
*/
|
|
743
|
-
export function groupBy<T>(arr: T[], keyFn: (item: T) => string): Record<string, T[]> {
|
|
744
|
-
return arr.reduce((groups, item) => {
|
|
745
|
-
const key = keyFn(item);
|
|
746
|
-
if (!groups[key]) groups[key] = [];
|
|
747
|
-
groups[key].push(item);
|
|
748
|
-
return groups;
|
|
749
|
-
}, {} as Record<string, T[]>);
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
/**
|
|
753
|
-
* Partition array by predicate
|
|
754
|
-
* @param arr - Input array
|
|
755
|
-
* @param predicate - Predicate function
|
|
756
|
-
* @returns [passing, failing] arrays
|
|
757
|
-
*/
|
|
758
|
-
export function partition<T>(arr: T[], predicate: (item: T) => boolean): [T[], T[]] {
|
|
759
|
-
const passing: T[] = [];
|
|
760
|
-
const failing: T[] = [];
|
|
761
|
-
|
|
762
|
-
for (const item of arr) {
|
|
763
|
-
if (predicate(item)) {
|
|
764
|
-
passing.push(item);
|
|
765
|
-
} else {
|
|
766
|
-
failing.push(item);
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
return [passing, failing];
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
/**
|
|
774
|
-
* Shuffle array (Fisher-Yates algorithm)
|
|
775
|
-
* @param arr - Input array
|
|
776
|
-
* @returns Shuffled array
|
|
777
|
-
*/
|
|
778
|
-
export function shuffle<T>(arr: T[]): T[] {
|
|
779
|
-
const result = [...arr];
|
|
780
|
-
|
|
781
|
-
for (let i = result.length - 1; i > 0; i--) {
|
|
782
|
-
const j = Math.floor(Math.random() * (i + 1));
|
|
783
|
-
[result[i], result[j]] = [result[j], result[i]];
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
return result;
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
/**
|
|
790
|
-
* Sample random element from array
|
|
791
|
-
* @param arr - Input array
|
|
792
|
-
* @returns Random element
|
|
793
|
-
*/
|
|
794
|
-
export function sample<T>(arr: T[]): T | undefined {
|
|
795
|
-
if (arr.length === 0) return undefined;
|
|
796
|
-
return arr[Math.floor(Math.random() * arr.length)];
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
/**
|
|
800
|
-
* Split array into chunks
|
|
801
|
-
* @param arr - Input array
|
|
802
|
-
* @param size - Chunk size
|
|
803
|
-
* @returns Array of chunks
|
|
804
|
-
*/
|
|
805
|
-
export function chunk<T>(arr: T[], size: number): T[][] {
|
|
806
|
-
const result: T[][] = [];
|
|
807
|
-
|
|
808
|
-
for (let i = 0; i < arr.length; i += size) {
|
|
809
|
-
result.push(arr.slice(i, i + size));
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
return result;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
/**
|
|
816
|
-
* Flatten array one level
|
|
817
|
-
* @param arr - Input array
|
|
818
|
-
* @returns Flattened array
|
|
819
|
-
*/
|
|
820
|
-
export function flatten<T>(arr: T[][]): T[] {
|
|
821
|
-
return arr.reduce((flat, item) => flat.concat(item), []);
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
/**
|
|
825
|
-
* Deep flatten array
|
|
826
|
-
* @param arr - Input array
|
|
827
|
-
* @returns Deeply flattened array
|
|
828
|
-
*/
|
|
829
|
-
export function deepFlatten<T>(arr: unknown[]): T[] {
|
|
830
|
-
const result: T[] = [];
|
|
831
|
-
|
|
832
|
-
for (const item of arr) {
|
|
833
|
-
if (Array.isArray(item)) {
|
|
834
|
-
result.push(...deepFlatten<T>(item));
|
|
835
|
-
} else {
|
|
836
|
-
result.push(item as T);
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
return result;
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
// ============================================================================
|
|
844
|
-
// Async Utilities
|
|
845
|
-
// ============================================================================
|
|
846
|
-
|
|
847
|
-
/**
|
|
848
|
-
* Sleep for specified milliseconds
|
|
849
|
-
* @param ms - Milliseconds to sleep
|
|
850
|
-
* @returns Promise that resolves after ms
|
|
851
|
-
*/
|
|
852
|
-
export function sleep(ms: number): Promise<void> {
|
|
853
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
/**
|
|
857
|
-
* Create a timeout promise
|
|
858
|
-
* @param ms - Timeout in milliseconds
|
|
859
|
-
* @param message - Error message
|
|
860
|
-
* @returns Promise that rejects after ms
|
|
861
|
-
*/
|
|
862
|
-
export function createTimeoutPromise(ms: number, message = 'Operation timed out'): Promise<never> {
|
|
863
|
-
return new Promise((_, reject) => {
|
|
864
|
-
setTimeout(() => reject(new Error(message)), ms);
|
|
865
|
-
});
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
/**
|
|
869
|
-
* Wrap promise with timeout
|
|
870
|
-
* @param promise - Promise to wrap
|
|
871
|
-
* @param ms - Timeout in milliseconds
|
|
872
|
-
* @returns Promise with timeout
|
|
873
|
-
*/
|
|
874
|
-
export async function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
|
|
875
|
-
return Promise.race([promise, createTimeoutPromise(ms)]);
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
/**
|
|
879
|
-
* Retry a function with exponential backoff
|
|
880
|
-
* @param fn - Function to retry
|
|
881
|
-
* @param maxRetries - Maximum retries
|
|
882
|
-
* @param delayMs - Initial delay
|
|
883
|
-
* @returns Function result
|
|
884
|
-
*/
|
|
885
|
-
export async function retry<T>(
|
|
886
|
-
fn: () => Promise<T>,
|
|
887
|
-
maxRetries = 3,
|
|
888
|
-
delayMs = 1000
|
|
889
|
-
): Promise<T> {
|
|
890
|
-
let lastError: Error | undefined;
|
|
891
|
-
|
|
892
|
-
for (let i = 0; i < maxRetries; i++) {
|
|
893
|
-
try {
|
|
894
|
-
return await fn();
|
|
895
|
-
} catch (error) {
|
|
896
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
897
|
-
|
|
898
|
-
if (i < maxRetries - 1) {
|
|
899
|
-
await sleep(delayMs * Math.pow(2, i));
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
throw lastError || new Error('Retry failed');
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
/**
|
|
908
|
-
* Debounce a function
|
|
909
|
-
* @param fn - Function to debounce
|
|
910
|
-
* @param ms - Debounce delay
|
|
911
|
-
* @returns Debounced function
|
|
912
|
-
*/
|
|
913
|
-
export function debounce<T extends (...args: unknown[]) => unknown>(
|
|
914
|
-
fn: T,
|
|
915
|
-
ms: number
|
|
916
|
-
): (...args: Parameters<T>) => void {
|
|
917
|
-
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
918
|
-
|
|
919
|
-
return (...args: Parameters<T>) => {
|
|
920
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
921
|
-
timeoutId = setTimeout(() => fn(...args), ms);
|
|
922
|
-
};
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
/**
|
|
926
|
-
* Throttle a function
|
|
927
|
-
* @param fn - Function to throttle
|
|
928
|
-
* @param ms - Throttle interval
|
|
929
|
-
* @returns Throttled function
|
|
930
|
-
*/
|
|
931
|
-
export function throttle<T extends (...args: unknown[]) => unknown>(
|
|
932
|
-
fn: T,
|
|
933
|
-
ms: number
|
|
934
|
-
): (...args: Parameters<T>) => void {
|
|
935
|
-
let lastTime = 0;
|
|
936
|
-
|
|
937
|
-
return (...args: Parameters<T>) => {
|
|
938
|
-
const now = Date.now();
|
|
939
|
-
|
|
940
|
-
if (now - lastTime >= ms) {
|
|
941
|
-
lastTime = now;
|
|
942
|
-
fn(...args);
|
|
943
|
-
}
|
|
944
|
-
};
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
// ============================================================================
|
|
948
|
-
// Error Handling
|
|
949
|
-
// ============================================================================
|
|
950
|
-
|
|
951
|
-
/**
|
|
952
|
-
* Try-catch wrapper for synchronous functions
|
|
953
|
-
* @param fn - Function to wrap
|
|
954
|
-
* @returns Result object
|
|
955
|
-
*/
|
|
956
|
-
export function tryCatch<T, E = Error>(fn: () => T): Result<T, E> {
|
|
957
|
-
try {
|
|
958
|
-
return { success: true, data: fn() };
|
|
959
|
-
} catch (error) {
|
|
960
|
-
return { success: false, error: error as E };
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
/**
|
|
965
|
-
* Try-catch wrapper for async functions
|
|
966
|
-
* @param fn - Async function to wrap
|
|
967
|
-
* @returns Result object
|
|
968
|
-
*/
|
|
969
|
-
export async function tryCatchAsync<T, E = Error>(
|
|
970
|
-
fn: () => Promise<T>
|
|
971
|
-
): Promise<Result<T, E>> {
|
|
972
|
-
try {
|
|
973
|
-
return { success: true, data: await fn() };
|
|
974
|
-
} catch (error) {
|
|
975
|
-
return { success: false, error: error as E };
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
/**
|
|
980
|
-
* Create enhanced error with context
|
|
981
|
-
* @param message - Error message
|
|
982
|
-
* @param context - Error context
|
|
983
|
-
* @returns Enhanced error
|
|
984
|
-
*/
|
|
985
|
-
export function createError(message: string, context?: Record<string, unknown>): Error {
|
|
986
|
-
const error = new Error(message);
|
|
987
|
-
if (context) {
|
|
988
|
-
(error as Error & { context: Record<string, unknown> }).context = context;
|
|
989
|
-
}
|
|
990
|
-
return error;
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
// ============================================================================
|
|
994
|
-
// Type Guards
|
|
995
|
-
// ============================================================================
|
|
996
|
-
|
|
997
|
-
/** Result type */
|
|
998
|
-
export type Result<T, E = Error> = {
|
|
999
|
-
success: true;
|
|
1000
|
-
data: T;
|
|
1001
|
-
} | {
|
|
1002
|
-
success: false;
|
|
1003
|
-
error: E;
|
|
1004
|
-
};
|