@satoshibits/functional 1.0.2
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 +242 -0
- package/dist/array-utils.d.mts +317 -0
- package/dist/array-utils.d.mts.map +1 -0
- package/dist/array-utils.mjs +370 -0
- package/dist/array-utils.mjs.map +1 -0
- package/dist/composition.d.mts +603 -0
- package/dist/composition.d.mts.map +1 -0
- package/dist/composition.mjs +516 -0
- package/dist/composition.mjs.map +1 -0
- package/dist/object-utils.d.mts +267 -0
- package/dist/object-utils.d.mts.map +1 -0
- package/dist/object-utils.mjs +258 -0
- package/dist/object-utils.mjs.map +1 -0
- package/dist/option.d.mts +622 -0
- package/dist/option.d.mts.map +1 -0
- package/dist/option.mjs +637 -0
- package/dist/option.mjs.map +1 -0
- package/dist/performance.d.mts +265 -0
- package/dist/performance.d.mts.map +1 -0
- package/dist/performance.mjs +453 -0
- package/dist/performance.mjs.map +1 -0
- package/dist/pipeline.d.mts +431 -0
- package/dist/pipeline.d.mts.map +1 -0
- package/dist/pipeline.mjs +460 -0
- package/dist/pipeline.mjs.map +1 -0
- package/dist/predicates.d.mts +722 -0
- package/dist/predicates.d.mts.map +1 -0
- package/dist/predicates.mjs +802 -0
- package/dist/predicates.mjs.map +1 -0
- package/dist/reader-result.d.mts +422 -0
- package/dist/reader-result.d.mts.map +1 -0
- package/dist/reader-result.mjs +758 -0
- package/dist/reader-result.mjs.map +1 -0
- package/dist/result.d.mts +684 -0
- package/dist/result.d.mts.map +1 -0
- package/dist/result.mjs +814 -0
- package/dist/result.mjs.map +1 -0
- package/dist/types.d.mts +439 -0
- package/dist/types.d.mts.map +1 -0
- package/dist/types.mjs +191 -0
- package/dist/types.mjs.map +1 -0
- package/dist/validation.d.mts +622 -0
- package/dist/validation.d.mts.map +1 -0
- package/dist/validation.mjs +852 -0
- package/dist/validation.mjs.map +1 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# Functional Library
|
|
2
|
+
|
|
3
|
+
A comprehensive collection of functional programming utilities for TypeScript, designed to promote immutability, composability, and type safety.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This library provides a set of pure, composable functions that follow functional programming principles. All utilities are designed to be tree-shakeable, type-safe, and optimized for performance.
|
|
8
|
+
|
|
9
|
+
## Architecture Principles
|
|
10
|
+
|
|
11
|
+
- **Pure Functions**: All utilities are pure functions with no side effects
|
|
12
|
+
- **Immutability**: Functions never mutate input data, always returning new values
|
|
13
|
+
- **Composability**: Designed to work together through function composition
|
|
14
|
+
- **Type Safety**: Full TypeScript support with comprehensive type inference
|
|
15
|
+
- **Tree-Shaking**: No barrel exports - import directly from specific modules
|
|
16
|
+
- **Performance**: Optimized implementations with minimal overhead
|
|
17
|
+
|
|
18
|
+
## Modules
|
|
19
|
+
|
|
20
|
+
### `array-utils.mts`
|
|
21
|
+
|
|
22
|
+
Functional utilities for array manipulation and transformation.
|
|
23
|
+
|
|
24
|
+
**Functions:**
|
|
25
|
+
|
|
26
|
+
- `mapWithIndex` - Map with access to element index
|
|
27
|
+
- `filterMap` - Combined filter and map in a single pass
|
|
28
|
+
- `chunk` - Split array into chunks of specified size
|
|
29
|
+
- `groupBy` - Group elements by a key function
|
|
30
|
+
- `findSafe` - Type-safe array element finder returning Result type
|
|
31
|
+
- `partition` - Split array into two based on predicate
|
|
32
|
+
|
|
33
|
+
### `object-utils.mts`
|
|
34
|
+
|
|
35
|
+
Utilities for immutable object operations.
|
|
36
|
+
|
|
37
|
+
**Functions:**
|
|
38
|
+
|
|
39
|
+
- `mapValues` - Transform object values while preserving keys
|
|
40
|
+
- `pick` - Create new object with selected keys
|
|
41
|
+
- `omit` - Create new object without specified keys
|
|
42
|
+
- `merge` - Deep merge objects with type safety
|
|
43
|
+
|
|
44
|
+
### `composition.mts`
|
|
45
|
+
|
|
46
|
+
Core function composition utilities.
|
|
47
|
+
|
|
48
|
+
**Functions:**
|
|
49
|
+
|
|
50
|
+
- `pipe` - Left-to-right function composition
|
|
51
|
+
- `pipeAsync` - Async function pipeline
|
|
52
|
+
- `compose` - Right-to-left function composition
|
|
53
|
+
- `composeAsync` - Async function composition
|
|
54
|
+
- `flow` - Type-safe variadic pipe
|
|
55
|
+
- `flowAsync` - Type-safe variadic async pipe
|
|
56
|
+
- `tap` - Side effect injection
|
|
57
|
+
- `curry` - Function currying
|
|
58
|
+
- `partial` - Partial application
|
|
59
|
+
- `flip` - Flip function arguments
|
|
60
|
+
- `memoize` - Function memoization
|
|
61
|
+
- `constant` - Create constant function
|
|
62
|
+
- `identity` - Identity function
|
|
63
|
+
- `noop` - No-operation function
|
|
64
|
+
|
|
65
|
+
### `predicates.mts`
|
|
66
|
+
|
|
67
|
+
Predicate functions and logical combinators.
|
|
68
|
+
|
|
69
|
+
**Functions:**
|
|
70
|
+
|
|
71
|
+
- `and` - Logical AND combinator
|
|
72
|
+
- `or` - Logical OR combinator
|
|
73
|
+
- `not` - Logical NOT combinator
|
|
74
|
+
- `xor` - Logical XOR combinator
|
|
75
|
+
- `isNil` - Check for null or undefined
|
|
76
|
+
- `isNotNil` - Check for non-null/undefined
|
|
77
|
+
- `isEmpty` - Check for empty values
|
|
78
|
+
- `isNotEmpty` - Check for non-empty values
|
|
79
|
+
- `equals` - Deep equality check
|
|
80
|
+
- `oneOf` - Check if value is in array
|
|
81
|
+
- `inRange` - Check if number is in range
|
|
82
|
+
- `matches` - Partial object matching
|
|
83
|
+
- `hasProperty` - Property existence check
|
|
84
|
+
- `includes` - Substring/array element check
|
|
85
|
+
- `alwaysTrue` - Constant true predicate
|
|
86
|
+
- `alwaysFalse` - Constant false predicate
|
|
87
|
+
|
|
88
|
+
### `performance.mts`
|
|
89
|
+
|
|
90
|
+
Performance optimization utilities.
|
|
91
|
+
|
|
92
|
+
**Functions:**
|
|
93
|
+
|
|
94
|
+
- `debounce` - Delay function execution
|
|
95
|
+
- `throttle` - Rate limit function calls
|
|
96
|
+
- `batchAsync` - Batch async operations
|
|
97
|
+
- `performanceUtils.measure` - Measure function execution time
|
|
98
|
+
|
|
99
|
+
### `pipeline.mts`
|
|
100
|
+
|
|
101
|
+
Fluent pipeline API for chaining operations.
|
|
102
|
+
|
|
103
|
+
**Class:**
|
|
104
|
+
|
|
105
|
+
- `Pipeline` - Chainable transformation pipeline with methods:
|
|
106
|
+
- `map` - Transform value
|
|
107
|
+
- `flatMap` - Transform and flatten
|
|
108
|
+
- `filter` - Conditional transformation
|
|
109
|
+
- `tap` - Side effects
|
|
110
|
+
- `pipeAsync` - Async transformations
|
|
111
|
+
- `value` - Extract final value
|
|
112
|
+
|
|
113
|
+
### `result.mts`
|
|
114
|
+
|
|
115
|
+
Result type for explicit error handling without exceptions.
|
|
116
|
+
|
|
117
|
+
**Types & Functions:**
|
|
118
|
+
|
|
119
|
+
- `Result<T, E>` - Success or error union type
|
|
120
|
+
- `Result.ok` - Create success result
|
|
121
|
+
- `Result.err` - Create error result
|
|
122
|
+
- `Result.map` - Transform success value
|
|
123
|
+
- `Result.mapError` - Transform error value
|
|
124
|
+
- `Result.chain` - Monadic bind
|
|
125
|
+
- `Result.match` - Pattern matching
|
|
126
|
+
- `Result.isOk` - Type guard for success
|
|
127
|
+
- `Result.isErr` - Type guard for error
|
|
128
|
+
|
|
129
|
+
### `reader-result.mts`
|
|
130
|
+
|
|
131
|
+
Reader monad combined with Result type for dependency injection and error handling.
|
|
132
|
+
|
|
133
|
+
**Types & Functions:**
|
|
134
|
+
|
|
135
|
+
- `ReaderResult<D, E, A>` - Reader + Result monad
|
|
136
|
+
- `ReaderResult.of` - Create from value
|
|
137
|
+
- `ReaderResult.fromResult` - Lift Result
|
|
138
|
+
- `ReaderResult.ask` - Access dependencies
|
|
139
|
+
- `ReaderResult.chain` - Monadic composition
|
|
140
|
+
- `ReaderResult.map` - Transform success value
|
|
141
|
+
- `ReaderResult.run` - Execute with dependencies
|
|
142
|
+
|
|
143
|
+
### `validation.mts`
|
|
144
|
+
|
|
145
|
+
Validation utilities and error types.
|
|
146
|
+
|
|
147
|
+
**Types & Functions:**
|
|
148
|
+
|
|
149
|
+
- `ValidationError` - Structured validation error
|
|
150
|
+
- `createValidationError` - Error factory
|
|
151
|
+
- `combineValidationErrors` - Merge multiple errors
|
|
152
|
+
- `formatValidationError` - Error formatting
|
|
153
|
+
|
|
154
|
+
## Usage Patterns
|
|
155
|
+
|
|
156
|
+
### Import Strategy
|
|
157
|
+
|
|
158
|
+
Always import directly from specific modules for optimal tree-shaking:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
// ✅ Correct - Direct imports
|
|
162
|
+
import { pipe, compose } from "@/lib/functional/composition.mjs";
|
|
163
|
+
import { mapValues, pick } from "@/lib/functional/object-utils.mjs";
|
|
164
|
+
|
|
165
|
+
// ❌ Wrong - No barrel imports (index.mts was removed)
|
|
166
|
+
import { pipe } from "@/lib/functional";
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Composition Patterns
|
|
170
|
+
|
|
171
|
+
Functions are designed to work together through composition:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { chunk, filterMap } from "@/lib/functional/array-utils.mjs";
|
|
175
|
+
import { pipe } from "@/lib/functional/composition.mjs";
|
|
176
|
+
import { isNotNil } from "@/lib/functional/predicates.mjs";
|
|
177
|
+
|
|
178
|
+
// Combine utilities for complex transformations
|
|
179
|
+
const processData = pipe(
|
|
180
|
+
filterMap((x: unknown) => (isNotNil(x) ? x : undefined)),
|
|
181
|
+
chunk(10),
|
|
182
|
+
);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Error Handling
|
|
186
|
+
|
|
187
|
+
Use Result types for explicit error handling:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
import { findSafe } from "@/lib/functional/array-utils.mjs";
|
|
191
|
+
import { Result } from "@/lib/functional/result.mjs";
|
|
192
|
+
|
|
193
|
+
// Functions return Result types for safety
|
|
194
|
+
const result = findSafe((x: User) => x.id === targetId)(users);
|
|
195
|
+
|
|
196
|
+
if (result.success) {
|
|
197
|
+
console.log("Found user:", result.data);
|
|
198
|
+
} else {
|
|
199
|
+
console.log("User not found");
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Testing
|
|
204
|
+
|
|
205
|
+
All utilities have comprehensive test suites. Run tests with:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
# Run all functional library tests
|
|
209
|
+
pnpm test src/lib/functional
|
|
210
|
+
|
|
211
|
+
# Run specific module tests
|
|
212
|
+
pnpm test src/lib/functional/array-utils.test.ts
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Performance Considerations
|
|
216
|
+
|
|
217
|
+
- **Memory Efficiency**: Functions like `filterMap` avoid intermediate arrays
|
|
218
|
+
- **Lazy Evaluation**: Pipeline class enables lazy transformation chains
|
|
219
|
+
- **Memoization**: Use `memoize` for expensive pure computations
|
|
220
|
+
- **Batching**: `batchAsync` optimizes concurrent async operations
|
|
221
|
+
|
|
222
|
+
## Migration Guide
|
|
223
|
+
|
|
224
|
+
If migrating from the old structure:
|
|
225
|
+
|
|
226
|
+
1. Replace imports from `pipe.mts` with `composition.mts`
|
|
227
|
+
2. Import array/object utilities from their dedicated modules
|
|
228
|
+
3. Remove any imports from `index.mts` (barrel file removed)
|
|
229
|
+
4. Update type imports for Result and ValidationError
|
|
230
|
+
|
|
231
|
+
## Contributing
|
|
232
|
+
|
|
233
|
+
When adding new utilities:
|
|
234
|
+
|
|
235
|
+
1. Ensure functions are pure with no side effects
|
|
236
|
+
2. Add comprehensive JSDoc with `@example` blocks
|
|
237
|
+
3. Include proper `@since` tags with current date
|
|
238
|
+
4. Write thorough unit tests
|
|
239
|
+
5. Follow established naming conventions
|
|
240
|
+
6. Update this README with new functions
|
|
241
|
+
|
|
242
|
+
## License - MIT
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module array-utils
|
|
3
|
+
* @description Functional utilities for working with arrays in a type-safe, immutable manner.
|
|
4
|
+
* These functions are designed to be composed and follow functional programming principles.
|
|
5
|
+
* All operations return new arrays, preserving immutability.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { filterMap, chunk, groupBy } from './array-utils.mts';
|
|
10
|
+
*
|
|
11
|
+
* // filter and transform in one pass
|
|
12
|
+
* const numbers = filterMap((s: string) => {
|
|
13
|
+
* const n = parseInt(s);
|
|
14
|
+
* return isNaN(n) ? undefined : n;
|
|
15
|
+
* })(['1', 'a', '2', 'b', '3']);
|
|
16
|
+
* // => [1, 2, 3]
|
|
17
|
+
*
|
|
18
|
+
* // chunk into batches
|
|
19
|
+
* const batches = chunk(3)([1, 2, 3, 4, 5, 6, 7]);
|
|
20
|
+
* // => [[1, 2, 3], [4, 5, 6], [7]]
|
|
21
|
+
*
|
|
22
|
+
* // group by property
|
|
23
|
+
* const users = [
|
|
24
|
+
* { name: 'Alice', role: 'admin' },
|
|
25
|
+
* { name: 'Bob', role: 'user' },
|
|
26
|
+
* { name: 'Charlie', role: 'admin' }
|
|
27
|
+
* ];
|
|
28
|
+
* const byRole = groupBy((u: typeof users[0]) => u.role)(users);
|
|
29
|
+
* // => { admin: [Alice, Charlie], user: [Bob] }
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @category Utilities
|
|
33
|
+
* @since 2025-07-03
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* Map over an array with index.
|
|
37
|
+
* @description Transforms each element of an array using a function that receives both the element and its index.
|
|
38
|
+
* Useful when you need both the element and its position during transformation.
|
|
39
|
+
* Preserves the original array and returns a new array with transformed values.
|
|
40
|
+
*
|
|
41
|
+
* @template T - The type of elements in the input array
|
|
42
|
+
* @template U - The type of elements in the output array
|
|
43
|
+
* @param {function(T, number): U} fn - Transformation function that receives item and index
|
|
44
|
+
* @returns {function(T[]): U[]} A function that takes an array and returns the transformed array
|
|
45
|
+
*
|
|
46
|
+
* @category Transformation
|
|
47
|
+
* @example
|
|
48
|
+
* const indexed = mapWithIndex((item, i) => `${i}: ${item}`)(['a', 'b', 'c']);
|
|
49
|
+
* // => ['0: a', '1: b', '2: c']
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* // Creating a numbered list
|
|
53
|
+
* const items = ['First', 'Second', 'Third'];
|
|
54
|
+
* const numbered = mapWithIndex((item, i) => `${i + 1}. ${item}`)(items);
|
|
55
|
+
* // => ['1. First', '2. Second', '3. Third']
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* // Add index metadata to objects
|
|
59
|
+
* const data = [{ name: 'Alice' }, { name: 'Bob' }];
|
|
60
|
+
* const withIndex = mapWithIndex((item, i) => ({ ...item, index: i }))(data);
|
|
61
|
+
* // => [{ name: 'Alice', index: 0 }, { name: 'Bob', index: 1 }]
|
|
62
|
+
*
|
|
63
|
+
* @see map - Standard array map without index
|
|
64
|
+
* @see filterMap - Transform and filter in one pass
|
|
65
|
+
* @since 2025-07-03
|
|
66
|
+
*/
|
|
67
|
+
export declare const mapWithIndex: <T, U>(fn: (item: T, index: number) => U) => (arr: T[]) => U[];
|
|
68
|
+
/**
|
|
69
|
+
* Filter and map in a single pass, removing undefined values.
|
|
70
|
+
* More efficient than chaining filter and map when transformation might return undefined.
|
|
71
|
+
* Optimized to avoid creating intermediate arrays for better memory efficiency.
|
|
72
|
+
*
|
|
73
|
+
* @category Transformation
|
|
74
|
+
* @example
|
|
75
|
+
* const nums = filterMap((s: string) => {
|
|
76
|
+
* const n = parseInt(s);
|
|
77
|
+
* return isNaN(n) ? undefined : n;
|
|
78
|
+
* })(['1', 'a', '2', 'b', '3']);
|
|
79
|
+
* // => [1, 2, 3]
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* // Parse and validate in one pass
|
|
83
|
+
* const parseEmails = filterMap((str: string) => {
|
|
84
|
+
* const trimmed = str.trim();
|
|
85
|
+
* return trimmed.includes('@') ? trimmed : undefined;
|
|
86
|
+
* });
|
|
87
|
+
* parseEmails([' john@example.com', 'invalid', 'jane@test.com ']);
|
|
88
|
+
* // => ['john@example.com', 'jane@test.com']
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* // Extract and transform nested data
|
|
92
|
+
* const users = [
|
|
93
|
+
* { name: 'Alice', profile: { age: 25 } },
|
|
94
|
+
* { name: 'Bob', profile: null },
|
|
95
|
+
* { name: 'Charlie', profile: { age: 30 } }
|
|
96
|
+
* ];
|
|
97
|
+
* const ages = filterMap((u: typeof users[0]) =>
|
|
98
|
+
* u.profile ? { name: u.name, age: u.profile.age } : undefined
|
|
99
|
+
* )(users);
|
|
100
|
+
* // => [{ name: 'Alice', age: 25 }, { name: 'Charlie', age: 30 }]
|
|
101
|
+
*
|
|
102
|
+
* @see map - Transform without filtering
|
|
103
|
+
* @see filter - Filter without transformation
|
|
104
|
+
* @since 2025-07-03
|
|
105
|
+
*/
|
|
106
|
+
export declare const filterMap: <T, U>(fn: (item: T, index: number) => U | undefined) => (arr: T[]) => U[];
|
|
107
|
+
/**
|
|
108
|
+
* Chunk an array into smaller arrays of specified size.
|
|
109
|
+
* @description Splits an array into multiple sub-arrays of a specified maximum size.
|
|
110
|
+
* The last chunk may contain fewer elements if the array length is not evenly divisible by the chunk size.
|
|
111
|
+
* Useful for pagination, batch processing, or creating grid layouts.
|
|
112
|
+
*
|
|
113
|
+
* @template T - The type of elements in the array
|
|
114
|
+
* @param {number} size - The maximum size of each chunk (must be positive)
|
|
115
|
+
* @returns {function(T[]): T[][]} A function that takes an array and returns an array of chunks
|
|
116
|
+
*
|
|
117
|
+
* @category Grouping
|
|
118
|
+
* @example
|
|
119
|
+
* const chunks = chunk(2)([1, 2, 3, 4, 5]);
|
|
120
|
+
* // => [[1, 2], [3, 4], [5]]
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* // Batch API requests
|
|
124
|
+
* const userIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
|
125
|
+
* const batches = chunk(3)(userIds);
|
|
126
|
+
* // => [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* // Create rows for a grid
|
|
130
|
+
* const items = ['A', 'B', 'C', 'D', 'E', 'F'];
|
|
131
|
+
* const rows = chunk(3)(items);
|
|
132
|
+
* // => [['A', 'B', 'C'], ['D', 'E', 'F']]
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* // Process large dataset in batches
|
|
136
|
+
* const processInBatches = async <T>(items: T[], batchSize: number, processor: (batch: T[]) => Promise<void>) => {
|
|
137
|
+
* const batches = chunk(batchSize)(items);
|
|
138
|
+
* for (const batch of batches) {
|
|
139
|
+
* await processor(batch);
|
|
140
|
+
* }
|
|
141
|
+
* };
|
|
142
|
+
*
|
|
143
|
+
* @see groupBy - Group by a key function
|
|
144
|
+
* @see partition - Split into two arrays
|
|
145
|
+
* @since 2025-07-03
|
|
146
|
+
*/
|
|
147
|
+
export declare const chunk: <T>(size: number) => (arr: T[]) => T[][];
|
|
148
|
+
/**
|
|
149
|
+
* Group array elements by a key function.
|
|
150
|
+
* @description Creates an object where keys are the grouping values and values are arrays of elements.
|
|
151
|
+
* Each element is placed into exactly one group based on the key function result.
|
|
152
|
+
* The order of elements within each group is preserved from the original array.
|
|
153
|
+
*
|
|
154
|
+
* @template T - The type of elements in the array
|
|
155
|
+
* @template K - The type of the grouping key (must be string or number)
|
|
156
|
+
* @param {function(T): K} keyFn - Function that extracts the grouping key from each element
|
|
157
|
+
* @returns {function(T[]): Record<K, T[]>} A function that takes an array and returns grouped elements
|
|
158
|
+
*
|
|
159
|
+
* @category Grouping
|
|
160
|
+
* @example
|
|
161
|
+
* const users = [
|
|
162
|
+
* { name: 'Alice', age: 25 },
|
|
163
|
+
* { name: 'Bob', age: 30 },
|
|
164
|
+
* { name: 'Charlie', age: 25 }
|
|
165
|
+
* ];
|
|
166
|
+
* const byAge = groupBy((u: typeof users[0]) => u.age)(users);
|
|
167
|
+
* // => { 25: [{ name: 'Alice', age: 25 }, { name: 'Charlie', age: 25 }], 30: [{ name: 'Bob', age: 30 }] }
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* // Group by first letter
|
|
171
|
+
* const words = ['apple', 'banana', 'apricot', 'cherry', 'avocado'];
|
|
172
|
+
* const byFirstLetter = groupBy((word: string) => word[0])(words);
|
|
173
|
+
* // => { a: ['apple', 'apricot', 'avocado'], b: ['banana'], c: ['cherry'] }
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* // Group transactions by status
|
|
177
|
+
* const transactions = [
|
|
178
|
+
* { id: 1, status: 'pending', amount: 100 },
|
|
179
|
+
* { id: 2, status: 'completed', amount: 200 },
|
|
180
|
+
* { id: 3, status: 'pending', amount: 150 }
|
|
181
|
+
* ];
|
|
182
|
+
* const byStatus = groupBy((t: typeof transactions[0]) => t.status)(transactions);
|
|
183
|
+
* // => { pending: [{id: 1, ...}, {id: 3, ...}], completed: [{id: 2, ...}] }
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* // Group by computed property
|
|
187
|
+
* const scores = [65, 72, 88, 95, 42, 58, 90];
|
|
188
|
+
* const byGrade = groupBy((score: number) => {
|
|
189
|
+
* if (score >= 90) return 'A';
|
|
190
|
+
* if (score >= 80) return 'B';
|
|
191
|
+
* if (score >= 70) return 'C';
|
|
192
|
+
* if (score >= 60) return 'D';
|
|
193
|
+
* return 'F';
|
|
194
|
+
* })(scores);
|
|
195
|
+
* // => { A: [95, 90], B: [88], C: [72], D: [65], F: [42, 58] }
|
|
196
|
+
*
|
|
197
|
+
* @see chunk - Group into fixed-size arrays
|
|
198
|
+
* @see partition - Split into two groups
|
|
199
|
+
* @since 2025-07-03
|
|
200
|
+
*/
|
|
201
|
+
export declare const groupBy: <T, K extends string | number>(keyFn: (item: T) => K) => (arr: T[]) => Record<K, T[]>;
|
|
202
|
+
/**
|
|
203
|
+
* Find the first item that matches a predicate, returning a Result.
|
|
204
|
+
* @description Safe alternative to Array.find that explicitly handles the not-found case.
|
|
205
|
+
* Returns a discriminated union result that forces explicit handling of both success and failure cases.
|
|
206
|
+
* This prevents runtime errors from undefined values and makes the control flow explicit.
|
|
207
|
+
*
|
|
208
|
+
* @template T - The type of elements in the array
|
|
209
|
+
* @param {function(T): boolean} predicate - Function to test each element
|
|
210
|
+
* @returns {function(T[]): { success: true; data: T } | { success: false; error: string }} A function that searches the array and returns a Result
|
|
211
|
+
*
|
|
212
|
+
* @category Search
|
|
213
|
+
* @example
|
|
214
|
+
* const result = findSafe((n: number) => n > 3)([1, 2, 3, 4, 5]);
|
|
215
|
+
* // => { success: true, data: 4 }
|
|
216
|
+
*
|
|
217
|
+
* const notFound = findSafe((n: number) => n > 10)([1, 2, 3]);
|
|
218
|
+
* // => { success: false, error: 'Item not found' }
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* // Find user by email
|
|
222
|
+
* const users = [
|
|
223
|
+
* { id: 1, email: 'alice@example.com' },
|
|
224
|
+
* { id: 2, email: 'bob@example.com' }
|
|
225
|
+
* ];
|
|
226
|
+
* const findByEmail = (email: string) =>
|
|
227
|
+
* findSafe((u: typeof users[0]) => u.email === email)(users);
|
|
228
|
+
*
|
|
229
|
+
* const result = findByEmail('alice@example.com');
|
|
230
|
+
* if (result.success) {
|
|
231
|
+
* console.log('Found user:', result.data.id);
|
|
232
|
+
* } else {
|
|
233
|
+
* console.log('User not found');
|
|
234
|
+
* }
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* // Chain with other operations safely
|
|
238
|
+
* const processUser = (email: string) => {
|
|
239
|
+
* const result = findSafe((u: User) => u.email === email)(users);
|
|
240
|
+
* if (!result.success) {
|
|
241
|
+
* return { success: false, error: `No user with email ${email}` };
|
|
242
|
+
* }
|
|
243
|
+
* // process result.data safely
|
|
244
|
+
* return { success: true, data: processUserData(result.data) };
|
|
245
|
+
* };
|
|
246
|
+
*
|
|
247
|
+
* @see find - Native array find (returns undefined)
|
|
248
|
+
* @see filter - Get all matching items
|
|
249
|
+
* @since 2025-07-03
|
|
250
|
+
*/
|
|
251
|
+
export declare const findSafe: <T>(predicate: (item: T) => boolean) => (arr: T[]) => {
|
|
252
|
+
success: true;
|
|
253
|
+
data: T;
|
|
254
|
+
} | {
|
|
255
|
+
success: false;
|
|
256
|
+
error: string;
|
|
257
|
+
};
|
|
258
|
+
/**
|
|
259
|
+
* Partition an array into two arrays based on a predicate.
|
|
260
|
+
* @description Splits an array into two parts: elements that satisfy the predicate go into the first array,
|
|
261
|
+
* and elements that don't satisfy the predicate go into the second array.
|
|
262
|
+
* More efficient than running filter twice with opposite predicates.
|
|
263
|
+
* Preserves the relative order of elements in both resulting arrays.
|
|
264
|
+
*
|
|
265
|
+
* @template T - The type of elements in the array
|
|
266
|
+
* @param {function(T): boolean} predicate - Function to test each element
|
|
267
|
+
* @returns {function(T[]): [T[], T[]]} A function that takes an array and returns a tuple of [matching, non-matching] arrays
|
|
268
|
+
*
|
|
269
|
+
* @category Grouping
|
|
270
|
+
* @example
|
|
271
|
+
* const [evens, odds] = partition((n: number) => n % 2 === 0)([1, 2, 3, 4, 5]);
|
|
272
|
+
* // => evens: [2, 4], odds: [1, 3, 5]
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* // Separate valid and invalid data
|
|
276
|
+
* const data = [
|
|
277
|
+
* { id: 1, valid: true },
|
|
278
|
+
* { id: 2, valid: false },
|
|
279
|
+
* { id: 3, valid: true }
|
|
280
|
+
* ];
|
|
281
|
+
* const [valid, invalid] = partition((item: typeof data[0]) => item.valid)(data);
|
|
282
|
+
* // => valid: [{id: 1, valid: true}, {id: 3, valid: true}]
|
|
283
|
+
* // => invalid: [{id: 2, valid: false}]
|
|
284
|
+
*
|
|
285
|
+
* @example
|
|
286
|
+
* // Separate active and inactive users
|
|
287
|
+
* const users = [
|
|
288
|
+
* { name: 'Alice', lastLogin: new Date('2024-01-10') },
|
|
289
|
+
* { name: 'Bob', lastLogin: new Date('2023-12-01') },
|
|
290
|
+
* { name: 'Charlie', lastLogin: new Date('2024-01-14') }
|
|
291
|
+
* ];
|
|
292
|
+
* const thirtyDaysAgo = new Date();
|
|
293
|
+
* thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
|
294
|
+
*
|
|
295
|
+
* const [active, inactive] = partition(
|
|
296
|
+
* (u: typeof users[0]) => u.lastLogin > thirtyDaysAgo
|
|
297
|
+
* )(users);
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* // Partition by multiple criteria
|
|
301
|
+
* const products = [
|
|
302
|
+
* { name: 'Laptop', price: 1200, inStock: true },
|
|
303
|
+
* { name: 'Mouse', price: 25, inStock: false },
|
|
304
|
+
* { name: 'Keyboard', price: 80, inStock: true }
|
|
305
|
+
* ];
|
|
306
|
+
* const [available, unavailable] = partition(
|
|
307
|
+
* (p: typeof products[0]) => p.inStock && p.price < 1000
|
|
308
|
+
* )(products);
|
|
309
|
+
* // => available: [{ name: 'Keyboard', ... }]
|
|
310
|
+
* // => unavailable: [{ name: 'Laptop', ... }, { name: 'Mouse', ... }]
|
|
311
|
+
*
|
|
312
|
+
* @see filter - Get only matching items
|
|
313
|
+
* @see groupBy - Group into multiple categories
|
|
314
|
+
* @since 2025-07-03
|
|
315
|
+
*/
|
|
316
|
+
export declare const partition: <T>(predicate: (item: T) => boolean) => (arr: T[]) => [T[], T[]];
|
|
317
|
+
//# sourceMappingURL=array-utils.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"array-utils.d.mts","sourceRoot":"","sources":["../src/array-utils.mts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,YAAY,GACtB,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,CAAC,WAClC,CAAC,EAAE,KAAG,CAAC,EACA,CAAC;AAEhB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,eAAO,MAAM,SAAS,GACnB,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,CAAC,GAAG,SAAS,WAC9C,CAAC,EAAE,KAAG,CAAC,EAOL,CAAC;AAEX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,eAAO,MAAM,KAAK,GACf,CAAC,QAAS,MAAM,WACX,CAAC,EAAE,KAAG,CAAC,EAAE,EAMd,CAAC;AAEJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH,eAAO,MAAM,OAAO,GACjB,CAAC,EAAE,CAAC,SAAS,MAAM,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,WAC9C,CAAC,EAAE,KAAG,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAUxB,CAAC;AAEJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,eAAO,MAAM,QAAQ,GAClB,CAAC,aAAc,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,WAE7B,CAAC,EAAE,KACP;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAM9D,CAAC;AAEJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AACH,eAAO,MAAM,SAAS,GACnB,CAAC,aAAc,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,WAC9B,CAAC,EAAE,KAAG,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAWpB,CAAC"}
|