@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.
Files changed (49) hide show
  1. package/README.md +31 -27
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/mcp-server.js +1 -1
  5. package/package.json +8 -2
  6. package/agents_test.log +0 -15
  7. package/data/agents/1770106504306-dljh9ef.json +0 -68
  8. package/data/agents/1770106504310-4oarrst.json +0 -58
  9. package/data/agents/1770106540588-pvitt55.json +0 -68
  10. package/data/agents/1770106540595-z2ya871.json +0 -58
  11. package/data/agents/1770106710890-0e2naq1.json +0 -68
  12. package/data/agents/1770106710893-r076yxx.json +0 -58
  13. package/data/agents/1770109212161-4ybd0i7.json +0 -68
  14. package/data/agents/1770109212166-gkhya8h.json +0 -58
  15. package/data/agents/1770117726716-lrnm415.json +0 -68
  16. package/data/agents/1770117726719-w6hsf3v.json +0 -58
  17. package/data/sessions/1770100622009-5afiuyv.json +0 -499
  18. package/data/sessions/1770106504312-75zk750.json +0 -107
  19. package/data/sessions/1770106540597-z8e8soo.json +0 -150
  20. package/data/sessions/1770106710894-0kxgy5x.json +0 -150
  21. package/data/sessions/1770109212169-zpddeb9.json +0 -150
  22. package/data/sessions/1770117726720-frcwj99.json +0 -150
  23. package/real_world_test.log +0 -200
  24. package/real_world_test_dynamic.log +0 -184
  25. package/real_world_test_real.log +0 -184
  26. package/src/__tests__/agents.test.ts +0 -858
  27. package/src/__tests__/mcp-server.test.ts +0 -380
  28. package/src/__tests__/sequential-thinking.test.ts +0 -687
  29. package/src/__tests__/swarm-coordinator.test.ts +0 -903
  30. package/src/__tests__/types.test.ts +0 -839
  31. package/src/__tests__/utils.test.ts +0 -322
  32. package/src/agents/base-agent.ts +0 -288
  33. package/src/agents/critic-agent.ts +0 -582
  34. package/src/agents/index.ts +0 -11
  35. package/src/agents/meta-reasoning-agent.ts +0 -314
  36. package/src/agents/reasoner-agent.ts +0 -312
  37. package/src/agents/synthesizer-agent.ts +0 -641
  38. package/src/index.ts +0 -118
  39. package/src/mcp-server.ts +0 -391
  40. package/src/real_world_test.ts +0 -89
  41. package/src/sequential-thinking.ts +0 -614
  42. package/src/swarm-coordinator.ts +0 -772
  43. package/src/types/index.ts +0 -915
  44. package/src/utils/index.ts +0 -1004
  45. package/src/utils/llm-adapter.ts +0 -110
  46. package/src/utils/logger.ts +0 -56
  47. package/src/utils/persistence.ts +0 -109
  48. package/test_output.log +0 -0
  49. package/tsconfig.json +0 -21
@@ -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
- };