andrud 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,456 @@
1
+ /**
2
+ * Object and array utilities
3
+ */
4
+
5
+ // Object utilities
6
+
7
+ /**
8
+ * Deep merge objects
9
+ */
10
+ export function merge<T extends object>(target: T, ...sources: Partial<T>[]): T {
11
+ for (const source of sources) {
12
+ for (const key of Object.keys(source) as Array<keyof T>) {
13
+ const targetValue = target[key];
14
+ const sourceValue = source[key];
15
+ if (
16
+ sourceValue &&
17
+ typeof sourceValue === 'object' &&
18
+ !Array.isArray(sourceValue) &&
19
+ targetValue &&
20
+ typeof targetValue === 'object' &&
21
+ !Array.isArray(targetValue)
22
+ ) {
23
+ (target as Record<string, unknown>)[key as string] = merge(
24
+ targetValue as object,
25
+ sourceValue as object
26
+ );
27
+ } else {
28
+ (target as Record<string, unknown>)[key as string] = sourceValue as unknown;
29
+ }
30
+ }
31
+ }
32
+ return target;
33
+ }
34
+
35
+ /**
36
+ * Omit keys from object
37
+ */
38
+ export function omit<T extends object, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {
39
+ const result = { ...obj };
40
+ keys.forEach((key) => delete result[key]);
41
+ return result;
42
+ }
43
+
44
+ /**
45
+ * Pick keys from object
46
+ */
47
+ export function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
48
+ const result = {} as Pick<T, K>;
49
+ keys.forEach((key) => {
50
+ result[key] = obj[key];
51
+ });
52
+ return result;
53
+ }
54
+
55
+ /**
56
+ * Invert object keys and values
57
+ */
58
+ export function invert<T extends Record<string, string | number>>(obj: T): Record<string, string> {
59
+ const result: Record<string, string> = {};
60
+ for (const [key, value] of Object.entries(obj)) {
61
+ result[value.toString()] = key;
62
+ }
63
+ return result;
64
+ }
65
+
66
+ /**
67
+ * Map values of object
68
+ */
69
+ export function mapValues<T extends object, U>(
70
+ obj: T,
71
+ fn: (value: T[keyof T], key: keyof T) => U
72
+ ): Record<keyof T, U> {
73
+ const result = {} as Record<keyof T, U>;
74
+ for (const [key, value] of Object.entries(obj)) {
75
+ result[key as keyof T] = fn(value as T[keyof T], key as keyof T);
76
+ }
77
+ return result;
78
+ }
79
+
80
+ /**
81
+ * Filter object keys
82
+ */
83
+ export function filterKeys<T extends object>(
84
+ obj: T,
85
+ fn: (key: keyof T) => boolean
86
+ ): Partial<T> {
87
+ const result: Partial<T> = {};
88
+ for (const [key, value] of Object.entries(obj)) {
89
+ if (fn(key as keyof T)) {
90
+ result[key as keyof T] = value as T[keyof T];
91
+ }
92
+ }
93
+ return result;
94
+ }
95
+
96
+ // Array utilities
97
+
98
+ /**
99
+ * Group array items by key
100
+ */
101
+ export function groupBy<T, K extends string | number>(
102
+ array: T[],
103
+ keyFn: (item: T) => K
104
+ ): Record<K, T[]> {
105
+ return array.reduce((result, item) => {
106
+ const key = keyFn(item);
107
+ if (!result[key]) {
108
+ result[key] = [];
109
+ }
110
+ result[key].push(item);
111
+ return result;
112
+ }, {} as Record<K, T[]>);
113
+ }
114
+
115
+ /**
116
+ * Get unique values
117
+ */
118
+ export function unique<T>(array: T[]): T[] {
119
+ return Array.from(new Set(array));
120
+ }
121
+
122
+ /**
123
+ * Get unique values by predicate
124
+ */
125
+ export function uniqueBy<T>(array: T[], predicate: (item: T) => unknown): T[] {
126
+ const seen = new Set();
127
+ return array.filter((item) => {
128
+ const key = predicate(item);
129
+ if (seen.has(key)) {
130
+ return false;
131
+ }
132
+ seen.add(key);
133
+ return true;
134
+ });
135
+ }
136
+
137
+ /**
138
+ * Split array into chunks
139
+ */
140
+ export function chunk<T>(array: T[], size: number): T[][] {
141
+ const chunks: T[][] = [];
142
+ for (let i = 0; i < array.length; i += size) {
143
+ chunks.push(array.slice(i, i + size));
144
+ }
145
+ return chunks;
146
+ }
147
+
148
+ /**
149
+ * Flatten array one level
150
+ */
151
+ export function flatten<T>(array: (T | T[])[]): T[] {
152
+ const result: T[] = [];
153
+ for (const item of array) {
154
+ if (Array.isArray(item)) {
155
+ result.push(...item);
156
+ } else {
157
+ result.push(item);
158
+ }
159
+ }
160
+ return result;
161
+ }
162
+
163
+ /**
164
+ * Deep flatten nested array
165
+ */
166
+ export function flattenDeep<T>(array: (T | T[])[]): T[] {
167
+ return array.reduce<T[]>((acc, item) =>
168
+ Array.isArray(item) ? acc.concat(flattenDeep(item)) : acc.concat(item), []);
169
+ }
170
+
171
+ /**
172
+ * Get difference between arrays
173
+ */
174
+ export function difference<T>(a: T[], b: T[]): T[] {
175
+ const bSet = new Set(b);
176
+ return a.filter((item) => !bSet.has(item));
177
+ }
178
+
179
+ /**
180
+ * Get intersection of arrays
181
+ */
182
+ export function intersection<T>(a: T[], b: T[]): T[] {
183
+ const bSet = new Set(b);
184
+ return a.filter((item) => bSet.has(item));
185
+ }
186
+
187
+ /**
188
+ * Get union of arrays
189
+ */
190
+ export function union<T>(...arrays: T[][]): T[] {
191
+ return unique(arrays.flat());
192
+ }
193
+
194
+ /**
195
+ * Sort array by key
196
+ */
197
+ export function sortBy<T>(array: T[], keyFn: (item: T) => unknown | unknown[]): T[] {
198
+ return [...array].sort((a, b) => {
199
+ const aKey = keyFn(a);
200
+ const bKey = keyFn(b);
201
+ if ((aKey as string) < (bKey as string)) return -1;
202
+ if ((aKey as string) > (bKey as string)) return 1;
203
+ return 0;
204
+ });
205
+ }
206
+
207
+ /**
208
+ * Partition array by predicate
209
+ */
210
+ export function partition<T>(array: T[], predicate: (item: T) => boolean): [T[], T[]] {
211
+ const truthy: T[] = [];
212
+ const falsy: T[] = [];
213
+ array.forEach((item) => {
214
+ if (predicate(item)) {
215
+ truthy.push(item);
216
+ } else {
217
+ falsy.push(item);
218
+ }
219
+ });
220
+ return [truthy, falsy];
221
+ }
222
+
223
+ /**
224
+ * Remove falsy values from array
225
+ */
226
+ export function compact<T>(array: (T | null | undefined | false | '' | 0)[]): T[] {
227
+ return array.filter(Boolean) as T[];
228
+ }
229
+
230
+ /**
231
+ * Zip arrays together
232
+ */
233
+ export function zip<T, U>(a: T[], b: U[]): Array<[T, U]> {
234
+ const length = Math.min(a.length, b.length);
235
+ const result: Array<[T, U]> = [];
236
+ for (let i = 0; i < length; i++) {
237
+ const aItem = a[i];
238
+ const bItem = b[i];
239
+ if (aItem !== undefined && bItem !== undefined) {
240
+ result.push([aItem, bItem]);
241
+ }
242
+ }
243
+ return result;
244
+ }
245
+
246
+ /**
247
+ * Unzip zipped arrays
248
+ */
249
+ export function unzip<T, U>(zipped: Array<[T, U]>): [T[], U[]] {
250
+ const a: T[] = [];
251
+ const b: U[] = [];
252
+ zipped.forEach(([first, second]) => {
253
+ a.push(first);
254
+ b.push(second);
255
+ });
256
+ return [a, b];
257
+ }
258
+
259
+ /**
260
+ * Generate range of numbers
261
+ */
262
+ export function range(start: number, end?: number, step: number = 1): number[] {
263
+ if (end === undefined) {
264
+ end = start;
265
+ start = 0;
266
+ }
267
+ const result: number[] = [];
268
+ for (let i = start; i < end; i += step) {
269
+ result.push(i);
270
+ }
271
+ return result;
272
+ }
273
+
274
+ /**
275
+ * Generate array of repeated values
276
+ */
277
+ export function times<T>(n: number, fn: (index: number) => T): T[] {
278
+ return Array.from({ length: n }, (_, i) => fn(i));
279
+ }
280
+
281
+ // Math utilities
282
+
283
+ /**
284
+ * Clamp number between min and max
285
+ */
286
+ export function clamp(value: number, min: number, max: number): number {
287
+ return Math.min(Math.max(value, min), max);
288
+ }
289
+
290
+ /**
291
+ * Generate random integer between min and max
292
+ */
293
+ export function randomInt(min: number, max: number): number {
294
+ return Math.floor(Math.random() * (max - min + 1)) + min;
295
+ }
296
+
297
+ /**
298
+ * Get random element from array
299
+ */
300
+ export function randomElement<T>(array: T[]): T | undefined {
301
+ if (array.length === 0) return undefined;
302
+ return array[Math.floor(Math.random() * array.length)] as T;
303
+ }
304
+
305
+ /**
306
+ * Shuffle array in place
307
+ */
308
+ export function shuffle<T>(array: T[]): T[] {
309
+ const result = [...array];
310
+ for (let i = result.length - 1; i > 0; i--) {
311
+ const j = Math.floor(Math.random() * (i + 1));
312
+ const temp = result[i];
313
+ const swap = result[j];
314
+ if (temp !== undefined && swap !== undefined) {
315
+ result[i] = swap;
316
+ result[j] = temp;
317
+ }
318
+ }
319
+ return result;
320
+ }
321
+
322
+ // Async utilities
323
+
324
+ /**
325
+ * Sleep for specified milliseconds
326
+ */
327
+ export function sleep(ms: number): Promise<void> {
328
+ return new Promise((resolve) => setTimeout(resolve, ms));
329
+ }
330
+
331
+ export interface RetryOptions {
332
+ retries?: number;
333
+ delay?: number;
334
+ backoff?: number;
335
+ onRetry?: (error: Error, attempt: number) => void;
336
+ }
337
+
338
+ /**
339
+ * Retry function on failure
340
+ */
341
+ export async function retry<T>(
342
+ fn: () => Promise<T>,
343
+ options: RetryOptions = {}
344
+ ): Promise<T> {
345
+ const {
346
+ retries = 3,
347
+ delay = 1000,
348
+ backoff = 2,
349
+ onRetry
350
+ } = options;
351
+
352
+ let lastError: Error;
353
+
354
+ for (let attempt = 0; attempt <= retries; attempt++) {
355
+ try {
356
+ return await fn();
357
+ } catch (error) {
358
+ lastError = error as Error;
359
+
360
+ if (attempt < retries) {
361
+ if (onRetry) {
362
+ onRetry(lastError, attempt + 1);
363
+ }
364
+ await sleep(delay * Math.pow(backoff, attempt));
365
+ }
366
+ }
367
+ }
368
+
369
+ throw lastError!;
370
+ }
371
+
372
+ // Function utilities
373
+
374
+ /**
375
+ * Memoize function results
376
+ */
377
+ export function memoize<T extends (...args: unknown[]) => unknown>(
378
+ fn: T
379
+ ): T & { clear: () => void } {
380
+ const cache = new Map<string, unknown>();
381
+ const memoized = ((...args: unknown[]) => {
382
+ const key = JSON.stringify(args);
383
+ if (cache.has(key)) {
384
+ return cache.get(key);
385
+ }
386
+ const result = fn(...args);
387
+ cache.set(key, result);
388
+ return result;
389
+ }) as T & { clear: () => void };
390
+
391
+ memoized.clear = () => cache.clear();
392
+ return memoized;
393
+ }
394
+
395
+ // Deep utilities
396
+
397
+ /**
398
+ * Deep clone an object
399
+ */
400
+ export function deepClone<T>(obj: T): T {
401
+ if (obj === null || typeof obj !== 'object') {
402
+ return obj;
403
+ }
404
+
405
+ if (Array.isArray(obj)) {
406
+ return obj.map((item) => deepClone(item)) as unknown as T;
407
+ }
408
+
409
+ if (obj instanceof Date) {
410
+ return new Date(obj.getTime()) as unknown as T;
411
+ }
412
+
413
+ if (obj instanceof RegExp) {
414
+ return new RegExp(obj.source, obj.flags) as unknown as T;
415
+ }
416
+
417
+ const cloned = {} as T;
418
+ for (const key of Object.keys(obj) as Array<keyof T>) {
419
+ cloned[key] = deepClone(obj[key]);
420
+ }
421
+ return cloned;
422
+ }
423
+
424
+ /**
425
+ * Deep equality check
426
+ */
427
+ export function deepEqual(a: unknown, b: unknown): boolean {
428
+ if (a === b) return true;
429
+
430
+ if (a === null || b === null) return false;
431
+ if (typeof a !== 'object' || typeof b !== 'object') return false;
432
+
433
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
434
+
435
+ const aObj = a as Record<string, unknown>;
436
+ const bObj = b as Record<string, unknown>;
437
+
438
+ const aKeys = Object.keys(aObj);
439
+ const bKeys = Object.keys(bObj);
440
+
441
+ if (aKeys.length !== bKeys.length) return false;
442
+
443
+ for (const key of aKeys) {
444
+ if (!bKeys.includes(key)) return false;
445
+ if (!deepEqual(aObj[key], bObj[key])) return false;
446
+ }
447
+
448
+ return true;
449
+ }
450
+
451
+ /**
452
+ * Deep merge objects
453
+ */
454
+ export function deepMerge<T extends object>(target: T, ...sources: Partial<T>[]): T {
455
+ return merge(target, ...sources);
456
+ }