@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.
Files changed (46) hide show
  1. package/README.md +242 -0
  2. package/dist/array-utils.d.mts +317 -0
  3. package/dist/array-utils.d.mts.map +1 -0
  4. package/dist/array-utils.mjs +370 -0
  5. package/dist/array-utils.mjs.map +1 -0
  6. package/dist/composition.d.mts +603 -0
  7. package/dist/composition.d.mts.map +1 -0
  8. package/dist/composition.mjs +516 -0
  9. package/dist/composition.mjs.map +1 -0
  10. package/dist/object-utils.d.mts +267 -0
  11. package/dist/object-utils.d.mts.map +1 -0
  12. package/dist/object-utils.mjs +258 -0
  13. package/dist/object-utils.mjs.map +1 -0
  14. package/dist/option.d.mts +622 -0
  15. package/dist/option.d.mts.map +1 -0
  16. package/dist/option.mjs +637 -0
  17. package/dist/option.mjs.map +1 -0
  18. package/dist/performance.d.mts +265 -0
  19. package/dist/performance.d.mts.map +1 -0
  20. package/dist/performance.mjs +453 -0
  21. package/dist/performance.mjs.map +1 -0
  22. package/dist/pipeline.d.mts +431 -0
  23. package/dist/pipeline.d.mts.map +1 -0
  24. package/dist/pipeline.mjs +460 -0
  25. package/dist/pipeline.mjs.map +1 -0
  26. package/dist/predicates.d.mts +722 -0
  27. package/dist/predicates.d.mts.map +1 -0
  28. package/dist/predicates.mjs +802 -0
  29. package/dist/predicates.mjs.map +1 -0
  30. package/dist/reader-result.d.mts +422 -0
  31. package/dist/reader-result.d.mts.map +1 -0
  32. package/dist/reader-result.mjs +758 -0
  33. package/dist/reader-result.mjs.map +1 -0
  34. package/dist/result.d.mts +684 -0
  35. package/dist/result.d.mts.map +1 -0
  36. package/dist/result.mjs +814 -0
  37. package/dist/result.mjs.map +1 -0
  38. package/dist/types.d.mts +439 -0
  39. package/dist/types.d.mts.map +1 -0
  40. package/dist/types.mjs +191 -0
  41. package/dist/types.mjs.map +1 -0
  42. package/dist/validation.d.mts +622 -0
  43. package/dist/validation.d.mts.map +1 -0
  44. package/dist/validation.mjs +852 -0
  45. package/dist/validation.mjs.map +1 -0
  46. package/package.json +46 -0
@@ -0,0 +1,370 @@
1
+ "use strict";
2
+ /**
3
+ * @module array-utils
4
+ * @description Functional utilities for working with arrays in a type-safe, immutable manner.
5
+ * These functions are designed to be composed and follow functional programming principles.
6
+ * All operations return new arrays, preserving immutability.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { filterMap, chunk, groupBy } from './array-utils.mts';
11
+ *
12
+ * // filter and transform in one pass
13
+ * const numbers = filterMap((s: string) => {
14
+ * const n = parseInt(s);
15
+ * return isNaN(n) ? undefined : n;
16
+ * })(['1', 'a', '2', 'b', '3']);
17
+ * // => [1, 2, 3]
18
+ *
19
+ * // chunk into batches
20
+ * const batches = chunk(3)([1, 2, 3, 4, 5, 6, 7]);
21
+ * // => [[1, 2, 3], [4, 5, 6], [7]]
22
+ *
23
+ * // group by property
24
+ * const users = [
25
+ * { name: 'Alice', role: 'admin' },
26
+ * { name: 'Bob', role: 'user' },
27
+ * { name: 'Charlie', role: 'admin' }
28
+ * ];
29
+ * const byRole = groupBy((u: typeof users[0]) => u.role)(users);
30
+ * // => { admin: [Alice, Charlie], user: [Bob] }
31
+ * ```
32
+ *
33
+ * @category Utilities
34
+ * @since 2025-07-03
35
+ */
36
+ /**
37
+ * Map over an array with index.
38
+ * @description Transforms each element of an array using a function that receives both the element and its index.
39
+ * Useful when you need both the element and its position during transformation.
40
+ * Preserves the original array and returns a new array with transformed values.
41
+ *
42
+ * @template T - The type of elements in the input array
43
+ * @template U - The type of elements in the output array
44
+ * @param {function(T, number): U} fn - Transformation function that receives item and index
45
+ * @returns {function(T[]): U[]} A function that takes an array and returns the transformed array
46
+ *
47
+ * @category Transformation
48
+ * @example
49
+ * const indexed = mapWithIndex((item, i) => `${i}: ${item}`)(['a', 'b', 'c']);
50
+ * // => ['0: a', '1: b', '2: c']
51
+ *
52
+ * @example
53
+ * // Creating a numbered list
54
+ * const items = ['First', 'Second', 'Third'];
55
+ * const numbered = mapWithIndex((item, i) => `${i + 1}. ${item}`)(items);
56
+ * // => ['1. First', '2. Second', '3. Third']
57
+ *
58
+ * @example
59
+ * // Add index metadata to objects
60
+ * const data = [{ name: 'Alice' }, { name: 'Bob' }];
61
+ * const withIndex = mapWithIndex((item, i) => ({ ...item, index: i }))(data);
62
+ * // => [{ name: 'Alice', index: 0 }, { name: 'Bob', index: 1 }]
63
+ *
64
+ * @see map - Standard array map without index
65
+ * @see filterMap - Transform and filter in one pass
66
+ * @since 2025-07-03
67
+ */
68
+ export var mapWithIndex = function (fn) {
69
+ return function (arr) {
70
+ return arr.map(fn);
71
+ };
72
+ };
73
+ /**
74
+ * Filter and map in a single pass, removing undefined values.
75
+ * More efficient than chaining filter and map when transformation might return undefined.
76
+ * Optimized to avoid creating intermediate arrays for better memory efficiency.
77
+ *
78
+ * @category Transformation
79
+ * @example
80
+ * const nums = filterMap((s: string) => {
81
+ * const n = parseInt(s);
82
+ * return isNaN(n) ? undefined : n;
83
+ * })(['1', 'a', '2', 'b', '3']);
84
+ * // => [1, 2, 3]
85
+ *
86
+ * @example
87
+ * // Parse and validate in one pass
88
+ * const parseEmails = filterMap((str: string) => {
89
+ * const trimmed = str.trim();
90
+ * return trimmed.includes('@') ? trimmed : undefined;
91
+ * });
92
+ * parseEmails([' john@example.com', 'invalid', 'jane@test.com ']);
93
+ * // => ['john@example.com', 'jane@test.com']
94
+ *
95
+ * @example
96
+ * // Extract and transform nested data
97
+ * const users = [
98
+ * { name: 'Alice', profile: { age: 25 } },
99
+ * { name: 'Bob', profile: null },
100
+ * { name: 'Charlie', profile: { age: 30 } }
101
+ * ];
102
+ * const ages = filterMap((u: typeof users[0]) =>
103
+ * u.profile ? { name: u.name, age: u.profile.age } : undefined
104
+ * )(users);
105
+ * // => [{ name: 'Alice', age: 25 }, { name: 'Charlie', age: 30 }]
106
+ *
107
+ * @see map - Transform without filtering
108
+ * @see filter - Filter without transformation
109
+ * @since 2025-07-03
110
+ */
111
+ export var filterMap = function (fn) {
112
+ return function (arr) {
113
+ return arr.reduce(function (acc, item, index) {
114
+ var result = fn(item, index);
115
+ if (result !== undefined) {
116
+ acc.push(result);
117
+ }
118
+ return acc;
119
+ }, []);
120
+ };
121
+ };
122
+ /**
123
+ * Chunk an array into smaller arrays of specified size.
124
+ * @description Splits an array into multiple sub-arrays of a specified maximum size.
125
+ * The last chunk may contain fewer elements if the array length is not evenly divisible by the chunk size.
126
+ * Useful for pagination, batch processing, or creating grid layouts.
127
+ *
128
+ * @template T - The type of elements in the array
129
+ * @param {number} size - The maximum size of each chunk (must be positive)
130
+ * @returns {function(T[]): T[][]} A function that takes an array and returns an array of chunks
131
+ *
132
+ * @category Grouping
133
+ * @example
134
+ * const chunks = chunk(2)([1, 2, 3, 4, 5]);
135
+ * // => [[1, 2], [3, 4], [5]]
136
+ *
137
+ * @example
138
+ * // Batch API requests
139
+ * const userIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
140
+ * const batches = chunk(3)(userIds);
141
+ * // => [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
142
+ *
143
+ * @example
144
+ * // Create rows for a grid
145
+ * const items = ['A', 'B', 'C', 'D', 'E', 'F'];
146
+ * const rows = chunk(3)(items);
147
+ * // => [['A', 'B', 'C'], ['D', 'E', 'F']]
148
+ *
149
+ * @example
150
+ * // Process large dataset in batches
151
+ * const processInBatches = async <T>(items: T[], batchSize: number, processor: (batch: T[]) => Promise<void>) => {
152
+ * const batches = chunk(batchSize)(items);
153
+ * for (const batch of batches) {
154
+ * await processor(batch);
155
+ * }
156
+ * };
157
+ *
158
+ * @see groupBy - Group by a key function
159
+ * @see partition - Split into two arrays
160
+ * @since 2025-07-03
161
+ */
162
+ export var chunk = function (size) {
163
+ return function (arr) {
164
+ var chunks = [];
165
+ for (var i = 0; i < arr.length; i += size) {
166
+ chunks.push(arr.slice(i, i + size));
167
+ }
168
+ return chunks;
169
+ };
170
+ };
171
+ /**
172
+ * Group array elements by a key function.
173
+ * @description Creates an object where keys are the grouping values and values are arrays of elements.
174
+ * Each element is placed into exactly one group based on the key function result.
175
+ * The order of elements within each group is preserved from the original array.
176
+ *
177
+ * @template T - The type of elements in the array
178
+ * @template K - The type of the grouping key (must be string or number)
179
+ * @param {function(T): K} keyFn - Function that extracts the grouping key from each element
180
+ * @returns {function(T[]): Record<K, T[]>} A function that takes an array and returns grouped elements
181
+ *
182
+ * @category Grouping
183
+ * @example
184
+ * const users = [
185
+ * { name: 'Alice', age: 25 },
186
+ * { name: 'Bob', age: 30 },
187
+ * { name: 'Charlie', age: 25 }
188
+ * ];
189
+ * const byAge = groupBy((u: typeof users[0]) => u.age)(users);
190
+ * // => { 25: [{ name: 'Alice', age: 25 }, { name: 'Charlie', age: 25 }], 30: [{ name: 'Bob', age: 30 }] }
191
+ *
192
+ * @example
193
+ * // Group by first letter
194
+ * const words = ['apple', 'banana', 'apricot', 'cherry', 'avocado'];
195
+ * const byFirstLetter = groupBy((word: string) => word[0])(words);
196
+ * // => { a: ['apple', 'apricot', 'avocado'], b: ['banana'], c: ['cherry'] }
197
+ *
198
+ * @example
199
+ * // Group transactions by status
200
+ * const transactions = [
201
+ * { id: 1, status: 'pending', amount: 100 },
202
+ * { id: 2, status: 'completed', amount: 200 },
203
+ * { id: 3, status: 'pending', amount: 150 }
204
+ * ];
205
+ * const byStatus = groupBy((t: typeof transactions[0]) => t.status)(transactions);
206
+ * // => { pending: [{id: 1, ...}, {id: 3, ...}], completed: [{id: 2, ...}] }
207
+ *
208
+ * @example
209
+ * // Group by computed property
210
+ * const scores = [65, 72, 88, 95, 42, 58, 90];
211
+ * const byGrade = groupBy((score: number) => {
212
+ * if (score >= 90) return 'A';
213
+ * if (score >= 80) return 'B';
214
+ * if (score >= 70) return 'C';
215
+ * if (score >= 60) return 'D';
216
+ * return 'F';
217
+ * })(scores);
218
+ * // => { A: [95, 90], B: [88], C: [72], D: [65], F: [42, 58] }
219
+ *
220
+ * @see chunk - Group into fixed-size arrays
221
+ * @see partition - Split into two groups
222
+ * @since 2025-07-03
223
+ */
224
+ export var groupBy = function (keyFn) {
225
+ return function (arr) {
226
+ var groups = {};
227
+ for (var _i = 0, arr_1 = arr; _i < arr_1.length; _i++) {
228
+ var item = arr_1[_i];
229
+ var key = keyFn(item);
230
+ if (!groups[key]) {
231
+ groups[key] = [];
232
+ }
233
+ groups[key].push(item);
234
+ }
235
+ return groups;
236
+ };
237
+ };
238
+ /**
239
+ * Find the first item that matches a predicate, returning a Result.
240
+ * @description Safe alternative to Array.find that explicitly handles the not-found case.
241
+ * Returns a discriminated union result that forces explicit handling of both success and failure cases.
242
+ * This prevents runtime errors from undefined values and makes the control flow explicit.
243
+ *
244
+ * @template T - The type of elements in the array
245
+ * @param {function(T): boolean} predicate - Function to test each element
246
+ * @returns {function(T[]): { success: true; data: T } | { success: false; error: string }} A function that searches the array and returns a Result
247
+ *
248
+ * @category Search
249
+ * @example
250
+ * const result = findSafe((n: number) => n > 3)([1, 2, 3, 4, 5]);
251
+ * // => { success: true, data: 4 }
252
+ *
253
+ * const notFound = findSafe((n: number) => n > 10)([1, 2, 3]);
254
+ * // => { success: false, error: 'Item not found' }
255
+ *
256
+ * @example
257
+ * // Find user by email
258
+ * const users = [
259
+ * { id: 1, email: 'alice@example.com' },
260
+ * { id: 2, email: 'bob@example.com' }
261
+ * ];
262
+ * const findByEmail = (email: string) =>
263
+ * findSafe((u: typeof users[0]) => u.email === email)(users);
264
+ *
265
+ * const result = findByEmail('alice@example.com');
266
+ * if (result.success) {
267
+ * console.log('Found user:', result.data.id);
268
+ * } else {
269
+ * console.log('User not found');
270
+ * }
271
+ *
272
+ * @example
273
+ * // Chain with other operations safely
274
+ * const processUser = (email: string) => {
275
+ * const result = findSafe((u: User) => u.email === email)(users);
276
+ * if (!result.success) {
277
+ * return { success: false, error: `No user with email ${email}` };
278
+ * }
279
+ * // process result.data safely
280
+ * return { success: true, data: processUserData(result.data) };
281
+ * };
282
+ *
283
+ * @see find - Native array find (returns undefined)
284
+ * @see filter - Get all matching items
285
+ * @since 2025-07-03
286
+ */
287
+ export var findSafe = function (predicate) {
288
+ return function (arr) {
289
+ var found = arr.find(predicate);
290
+ if (found !== undefined) {
291
+ return { success: true, data: found };
292
+ }
293
+ return { success: false, error: "Item not found" };
294
+ };
295
+ };
296
+ /**
297
+ * Partition an array into two arrays based on a predicate.
298
+ * @description Splits an array into two parts: elements that satisfy the predicate go into the first array,
299
+ * and elements that don't satisfy the predicate go into the second array.
300
+ * More efficient than running filter twice with opposite predicates.
301
+ * Preserves the relative order of elements in both resulting arrays.
302
+ *
303
+ * @template T - The type of elements in the array
304
+ * @param {function(T): boolean} predicate - Function to test each element
305
+ * @returns {function(T[]): [T[], T[]]} A function that takes an array and returns a tuple of [matching, non-matching] arrays
306
+ *
307
+ * @category Grouping
308
+ * @example
309
+ * const [evens, odds] = partition((n: number) => n % 2 === 0)([1, 2, 3, 4, 5]);
310
+ * // => evens: [2, 4], odds: [1, 3, 5]
311
+ *
312
+ * @example
313
+ * // Separate valid and invalid data
314
+ * const data = [
315
+ * { id: 1, valid: true },
316
+ * { id: 2, valid: false },
317
+ * { id: 3, valid: true }
318
+ * ];
319
+ * const [valid, invalid] = partition((item: typeof data[0]) => item.valid)(data);
320
+ * // => valid: [{id: 1, valid: true}, {id: 3, valid: true}]
321
+ * // => invalid: [{id: 2, valid: false}]
322
+ *
323
+ * @example
324
+ * // Separate active and inactive users
325
+ * const users = [
326
+ * { name: 'Alice', lastLogin: new Date('2024-01-10') },
327
+ * { name: 'Bob', lastLogin: new Date('2023-12-01') },
328
+ * { name: 'Charlie', lastLogin: new Date('2024-01-14') }
329
+ * ];
330
+ * const thirtyDaysAgo = new Date();
331
+ * thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
332
+ *
333
+ * const [active, inactive] = partition(
334
+ * (u: typeof users[0]) => u.lastLogin > thirtyDaysAgo
335
+ * )(users);
336
+ *
337
+ * @example
338
+ * // Partition by multiple criteria
339
+ * const products = [
340
+ * { name: 'Laptop', price: 1200, inStock: true },
341
+ * { name: 'Mouse', price: 25, inStock: false },
342
+ * { name: 'Keyboard', price: 80, inStock: true }
343
+ * ];
344
+ * const [available, unavailable] = partition(
345
+ * (p: typeof products[0]) => p.inStock && p.price < 1000
346
+ * )(products);
347
+ * // => available: [{ name: 'Keyboard', ... }]
348
+ * // => unavailable: [{ name: 'Laptop', ... }, { name: 'Mouse', ... }]
349
+ *
350
+ * @see filter - Get only matching items
351
+ * @see groupBy - Group into multiple categories
352
+ * @since 2025-07-03
353
+ */
354
+ export var partition = function (predicate) {
355
+ return function (arr) {
356
+ var left = [];
357
+ var right = [];
358
+ for (var _i = 0, arr_2 = arr; _i < arr_2.length; _i++) {
359
+ var item = arr_2[_i];
360
+ if (predicate(item)) {
361
+ left.push(item);
362
+ }
363
+ else {
364
+ right.push(item);
365
+ }
366
+ }
367
+ return [left, right];
368
+ };
369
+ };
370
+ //# sourceMappingURL=array-utils.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"array-utils.mjs","sourceRoot":"","sources":["../src/array-utils.mts"],"names":[],"mappings":"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"}