@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
|
@@ -0,0 +1,802 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module predicates
|
|
4
|
+
* @description Functional utilities for composing and manipulating predicate functions.
|
|
5
|
+
* Predicates are functions that return boolean values and are fundamental
|
|
6
|
+
* for filtering, validation, and conditional logic. This module provides
|
|
7
|
+
* combinators for building complex predicates from simple ones, along with
|
|
8
|
+
* common predicate patterns for everyday use.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { and, or, not, isNotNil, inRange, hasProperty } from './predicates.mts';
|
|
13
|
+
*
|
|
14
|
+
* // compose predicates with logical operators
|
|
15
|
+
* const isPositiveEven = and(
|
|
16
|
+
* (n: number) => n > 0,
|
|
17
|
+
* (n: number) => n % 2 === 0
|
|
18
|
+
* );
|
|
19
|
+
*
|
|
20
|
+
* // create reusable validation functions
|
|
21
|
+
* const isValidUser = and(
|
|
22
|
+
* hasProperty('email'),
|
|
23
|
+
* hasProperty('name'),
|
|
24
|
+
* user => isNotNil(user.email) && user.email.includes('@')
|
|
25
|
+
* );
|
|
26
|
+
*
|
|
27
|
+
* // filter collections
|
|
28
|
+
* const validUsers = users.filter(isValidUser);
|
|
29
|
+
* const adults = people.filter(person => inRange(18, 120)(person.age));
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @category Core
|
|
33
|
+
* @since 2025-07-03
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* Logical AND combinator for predicates.
|
|
37
|
+
* @description Returns true only if all predicates return true. Short-circuits
|
|
38
|
+
* on the first false result for efficiency. Accepts any number of predicates
|
|
39
|
+
* and combines them into a single predicate function.
|
|
40
|
+
*
|
|
41
|
+
* @template T - The type of value being tested
|
|
42
|
+
* @param {Array<(value: T) => boolean>} predicates - Functions to combine with AND logic
|
|
43
|
+
* @returns {(value: T) => boolean} A predicate that returns true if all predicates pass
|
|
44
|
+
*
|
|
45
|
+
* @category Combinators
|
|
46
|
+
* @example
|
|
47
|
+
* // Basic number validation
|
|
48
|
+
* const isPositive = (n: number) => n > 0;
|
|
49
|
+
* const isEven = (n: number) => n % 2 === 0;
|
|
50
|
+
* const isPositiveEven = and(isPositive, isEven);
|
|
51
|
+
*
|
|
52
|
+
* isPositiveEven(4); // => true
|
|
53
|
+
* isPositiveEven(-2); // => false (not positive)
|
|
54
|
+
* isPositiveEven(3); // => false (not even)
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* // Validating user data
|
|
58
|
+
* const hasName = (user: { name?: string }) => !!user.name;
|
|
59
|
+
* const hasEmail = (user: { email?: string }) => !!user.email;
|
|
60
|
+
* const isAdult = (user: { age?: number }) => (user.age ?? 0) >= 18;
|
|
61
|
+
*
|
|
62
|
+
* const isValidAdultUser = and(hasName, hasEmail, isAdult);
|
|
63
|
+
* isValidAdultUser({ name: 'John', email: 'john@example.com', age: 25 }); // => true
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* // Form validation with multiple rules
|
|
67
|
+
* const isValidPassword = and(
|
|
68
|
+
* (pwd: string) => pwd.length >= 8,
|
|
69
|
+
* (pwd: string) => /[A-Z]/.test(pwd),
|
|
70
|
+
* (pwd: string) => /[0-9]/.test(pwd),
|
|
71
|
+
* (pwd: string) => /[!@#$%^&*]/.test(pwd)
|
|
72
|
+
* );
|
|
73
|
+
*
|
|
74
|
+
* isValidPassword('Pass123!'); // => true
|
|
75
|
+
* isValidPassword('weak'); // => false
|
|
76
|
+
*
|
|
77
|
+
* @see or - Logical OR combinator
|
|
78
|
+
* @see not - Logical NOT combinator
|
|
79
|
+
* @since 2025-07-03
|
|
80
|
+
*/
|
|
81
|
+
export var and = function () {
|
|
82
|
+
var predicates = [];
|
|
83
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
84
|
+
predicates[_i] = arguments[_i];
|
|
85
|
+
}
|
|
86
|
+
return function (value) {
|
|
87
|
+
return predicates.every(function (predicate) { return predicate(value); });
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Logical OR combinator for predicates.
|
|
92
|
+
* @description Returns true if at least one predicate returns true. Short-circuits
|
|
93
|
+
* on the first true result for efficiency. Accepts any number of predicates
|
|
94
|
+
* and combines them into a single predicate function.
|
|
95
|
+
*
|
|
96
|
+
* @template T - The type of value being tested
|
|
97
|
+
* @param {Array<(value: T) => boolean>} predicates - Functions to combine with OR logic
|
|
98
|
+
* @returns {(value: T) => boolean} A predicate that returns true if any predicate passes
|
|
99
|
+
*
|
|
100
|
+
* @category Combinators
|
|
101
|
+
* @example
|
|
102
|
+
* // Role-based access control
|
|
103
|
+
* const isAdmin = (user: { role: string }) => user.role === 'admin';
|
|
104
|
+
* const isModerator = (user: { role: string }) => user.role === 'moderator';
|
|
105
|
+
* const hasPrivileges = or(isAdmin, isModerator);
|
|
106
|
+
*
|
|
107
|
+
* hasPrivileges({ role: 'admin' }); // => true
|
|
108
|
+
* hasPrivileges({ role: 'moderator' }); // => true
|
|
109
|
+
* hasPrivileges({ role: 'user' }); // => false
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* // Multiple payment methods
|
|
113
|
+
* const hasCreditCard = (payment: { type: string }) => payment.type === 'credit';
|
|
114
|
+
* const hasPayPal = (payment: { type: string }) => payment.type === 'paypal';
|
|
115
|
+
* const hasCrypto = (payment: { type: string }) => payment.type === 'crypto';
|
|
116
|
+
*
|
|
117
|
+
* const acceptsPayment = or(hasCreditCard, hasPayPal, hasCrypto);
|
|
118
|
+
* acceptsPayment({ type: 'paypal' }); // => true
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* // Flexible search criteria
|
|
122
|
+
* const searchTerm = 'john';
|
|
123
|
+
* const matchesSearch = or(
|
|
124
|
+
* (user: User) => user.name.toLowerCase().includes(searchTerm),
|
|
125
|
+
* (user: User) => user.email.toLowerCase().includes(searchTerm),
|
|
126
|
+
* (user: User) => user.username.toLowerCase().includes(searchTerm)
|
|
127
|
+
* );
|
|
128
|
+
*
|
|
129
|
+
* const searchResults = users.filter(matchesSearch);
|
|
130
|
+
*
|
|
131
|
+
* @see and - Logical AND combinator
|
|
132
|
+
* @see not - Logical NOT combinator
|
|
133
|
+
* @since 2025-07-03
|
|
134
|
+
*/
|
|
135
|
+
export var or = function () {
|
|
136
|
+
var predicates = [];
|
|
137
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
138
|
+
predicates[_i] = arguments[_i];
|
|
139
|
+
}
|
|
140
|
+
return function (value) {
|
|
141
|
+
return predicates.some(function (predicate) { return predicate(value); });
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* Logical NOT combinator for predicates.
|
|
146
|
+
* @description Inverts the result of a predicate, turning true to false and
|
|
147
|
+
* false to true. Useful for creating the opposite of existing predicates
|
|
148
|
+
* without duplicating logic.
|
|
149
|
+
*
|
|
150
|
+
* @template T - The type of value being tested
|
|
151
|
+
* @param {(value: T) => boolean} predicate - The predicate to invert
|
|
152
|
+
* @returns {(value: T) => boolean} A predicate that returns the opposite result
|
|
153
|
+
*
|
|
154
|
+
* @category Combinators
|
|
155
|
+
* @example
|
|
156
|
+
* // Basic negation
|
|
157
|
+
* const isPositive = (n: number) => n > 0;
|
|
158
|
+
* const isNegativeOrZero = not(isPositive);
|
|
159
|
+
*
|
|
160
|
+
* isNegativeOrZero(-5); // => true
|
|
161
|
+
* isNegativeOrZero(0); // => true
|
|
162
|
+
* isNegativeOrZero(5); // => false
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* // Filtering out specific items
|
|
166
|
+
* const isError = (log: { level: string }) => log.level === 'error';
|
|
167
|
+
* const nonErrorLogs = logs.filter(not(isError));
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* // Excluding items from selection
|
|
171
|
+
* const isBlacklisted = oneOf(blacklistedIds);
|
|
172
|
+
* const allowedItems = items.filter(not(item => isBlacklisted(item.id)));
|
|
173
|
+
*
|
|
174
|
+
* @see and - Logical AND combinator
|
|
175
|
+
* @see or - Logical OR combinator
|
|
176
|
+
* @since 2025-07-03
|
|
177
|
+
*/
|
|
178
|
+
export var not = function (predicate) {
|
|
179
|
+
return function (value) {
|
|
180
|
+
return !predicate(value);
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
/**
|
|
184
|
+
* Exclusive OR (XOR) combinator for predicates.
|
|
185
|
+
* @description Returns true if exactly one predicate returns true, but not both.
|
|
186
|
+
* Useful for ensuring mutually exclusive conditions or validating that
|
|
187
|
+
* exactly one option is selected from a pair.
|
|
188
|
+
*
|
|
189
|
+
* @template T - The type of value being tested
|
|
190
|
+
* @param {(value: T) => boolean} predicate1 - First predicate to test
|
|
191
|
+
* @param {(value: T) => boolean} predicate2 - Second predicate to test
|
|
192
|
+
* @returns {(value: T) => boolean} A predicate that returns true if exactly one input predicate passes
|
|
193
|
+
*
|
|
194
|
+
* @category Combinators
|
|
195
|
+
* @example
|
|
196
|
+
* // Authentication method validation
|
|
197
|
+
* const hasUsername = (auth: { username?: string }) => !!auth.username;
|
|
198
|
+
* const hasEmail = (auth: { email?: string }) => !!auth.email;
|
|
199
|
+
* const hasExactlyOneIdentifier = xor(hasUsername, hasEmail);
|
|
200
|
+
*
|
|
201
|
+
* hasExactlyOneIdentifier({ username: 'john' }); // => true
|
|
202
|
+
* hasExactlyOneIdentifier({ email: 'john@example.com' }); // => true
|
|
203
|
+
* hasExactlyOneIdentifier({ username: 'john', email: 'j@e.com' }); // => false
|
|
204
|
+
* hasExactlyOneIdentifier({}); // => false
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* // Toggle state validation
|
|
208
|
+
* const isManualMode = (config: Config) => config.mode === 'manual';
|
|
209
|
+
* const hasAutoSettings = (config: Config) => !!config.autoSettings;
|
|
210
|
+
* const isValidConfig = xor(isManualMode, hasAutoSettings);
|
|
211
|
+
* // Ensures either manual mode OR auto settings, but not both
|
|
212
|
+
*
|
|
213
|
+
* @see and - Logical AND combinator
|
|
214
|
+
* @see or - Logical OR combinator
|
|
215
|
+
* @since 2025-07-03
|
|
216
|
+
*/
|
|
217
|
+
export var xor = function (predicate1, predicate2) {
|
|
218
|
+
return function (value) {
|
|
219
|
+
var p1 = predicate1(value);
|
|
220
|
+
var p2 = predicate2(value);
|
|
221
|
+
return (p1 && !p2) || (!p1 && p2);
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
/**
|
|
225
|
+
* Creates a predicate that checks if a value is not null or undefined.
|
|
226
|
+
* @description Type guard function that narrows types by excluding null and undefined.
|
|
227
|
+
* Particularly useful for filtering arrays and conditional type narrowing.
|
|
228
|
+
* Unlike truthiness checks, this explicitly checks for null/undefined only.
|
|
229
|
+
*
|
|
230
|
+
* @template T - The type of the non-null value
|
|
231
|
+
* @param {T | null | undefined} value - The value to check
|
|
232
|
+
* @returns {value is T} True if the value is not null or undefined
|
|
233
|
+
*
|
|
234
|
+
* @category Type Guards
|
|
235
|
+
* @example
|
|
236
|
+
* // Array filtering with type narrowing
|
|
237
|
+
* const values: (string | null | undefined)[] = ['a', null, 'b', undefined, 'c'];
|
|
238
|
+
* const nonNullValues = values.filter(isNotNil);
|
|
239
|
+
* // => ['a', 'b', 'c'] with type string[]
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* // Type guard in conditional
|
|
243
|
+
* function processValue(value: string | null) {
|
|
244
|
+
* if (isNotNil(value)) {
|
|
245
|
+
* // TypeScript knows value is string here
|
|
246
|
+
* return value.toUpperCase();
|
|
247
|
+
* }
|
|
248
|
+
* return 'DEFAULT';
|
|
249
|
+
* }
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* // Optional chaining alternative
|
|
253
|
+
* const users: (User | null)[] = await fetchUsers();
|
|
254
|
+
* const activeUsers = users
|
|
255
|
+
* .filter(isNotNil)
|
|
256
|
+
* .filter(user => user.status === 'active');
|
|
257
|
+
*
|
|
258
|
+
* @see isNil - Check if value is null or undefined
|
|
259
|
+
* @see isEmpty - Check if value is empty
|
|
260
|
+
* @since 2025-07-03
|
|
261
|
+
*/
|
|
262
|
+
export var isNotNil = function (value) {
|
|
263
|
+
return value !== null && value !== undefined;
|
|
264
|
+
};
|
|
265
|
+
/**
|
|
266
|
+
* Creates a predicate that checks if a value is null or undefined.
|
|
267
|
+
* @description Type guard function that identifies null and undefined values.
|
|
268
|
+
* Useful for validation, error handling, and conditional logic where
|
|
269
|
+
* null/undefined values need special treatment.
|
|
270
|
+
*
|
|
271
|
+
* @template T - The type of the value when not null/undefined
|
|
272
|
+
* @param {T | null | undefined} value - The value to check
|
|
273
|
+
* @returns {value is null | undefined} True if the value is null or undefined
|
|
274
|
+
*
|
|
275
|
+
* @category Type Guards
|
|
276
|
+
* @example
|
|
277
|
+
* // Filtering null/undefined values
|
|
278
|
+
* const values = [1, null, 2, undefined, 3];
|
|
279
|
+
* const nilValues = values.filter(isNil);
|
|
280
|
+
* // => [null, undefined]
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* // Early return pattern
|
|
284
|
+
* function processUser(user: User | null) {
|
|
285
|
+
* if (isNil(user)) {
|
|
286
|
+
* return { error: 'User not found' };
|
|
287
|
+
* }
|
|
288
|
+
* // Process user...
|
|
289
|
+
* }
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* // Default value handling
|
|
293
|
+
* function getConfig(key: string): string {
|
|
294
|
+
* const value = configMap.get(key);
|
|
295
|
+
* if (isNil(value)) {
|
|
296
|
+
* return getDefaultConfig(key);
|
|
297
|
+
* }
|
|
298
|
+
* return value;
|
|
299
|
+
* }
|
|
300
|
+
*
|
|
301
|
+
* @see isNotNil - Check if value is not null or undefined
|
|
302
|
+
* @see isEmpty - Check if value is empty
|
|
303
|
+
* @since 2025-07-03
|
|
304
|
+
*/
|
|
305
|
+
export var isNil = function (value) {
|
|
306
|
+
return value === null || value === undefined;
|
|
307
|
+
};
|
|
308
|
+
/**
|
|
309
|
+
* Checks if a value is empty (null, undefined, empty string, empty array, or empty object).
|
|
310
|
+
* @description Comprehensive emptiness check that handles multiple data types.
|
|
311
|
+
* Returns true for null, undefined, empty strings, arrays with no elements,
|
|
312
|
+
* and objects with no own properties. Does not consider whitespace-only strings as empty.
|
|
313
|
+
*
|
|
314
|
+
* @param {unknown} value - The value to check for emptiness
|
|
315
|
+
* @returns {boolean} True if the value is considered empty
|
|
316
|
+
*
|
|
317
|
+
* @category Value Checks
|
|
318
|
+
* @example
|
|
319
|
+
* // Basic emptiness checks
|
|
320
|
+
* isEmpty(null); // => true
|
|
321
|
+
* isEmpty(undefined); // => true
|
|
322
|
+
* isEmpty(''); // => true
|
|
323
|
+
* isEmpty([]); // => true
|
|
324
|
+
* isEmpty({}); // => true
|
|
325
|
+
* isEmpty('hello'); // => false
|
|
326
|
+
* isEmpty([1, 2, 3]); // => false
|
|
327
|
+
* isEmpty({ a: 1 }); // => false
|
|
328
|
+
*
|
|
329
|
+
* @example
|
|
330
|
+
* // Form validation
|
|
331
|
+
* const formData = { name: '', email: 'test@example.com', bio: null };
|
|
332
|
+
* const emptyFields = Object.entries(formData)
|
|
333
|
+
* .filter(([_, value]) => isEmpty(value))
|
|
334
|
+
* .map(([key]) => key);
|
|
335
|
+
* // => ['name', 'bio']
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* // API response validation
|
|
339
|
+
* function handleResponse(data: unknown) {
|
|
340
|
+
* if (isEmpty(data)) {
|
|
341
|
+
* throw new Error('Empty response received');
|
|
342
|
+
* }
|
|
343
|
+
* return processData(data);
|
|
344
|
+
* }
|
|
345
|
+
*
|
|
346
|
+
* @see isNotEmpty - Check if value has content
|
|
347
|
+
* @see isNil - Check if value is null or undefined
|
|
348
|
+
* @since 2025-07-03
|
|
349
|
+
*/
|
|
350
|
+
export var isEmpty = function (value) {
|
|
351
|
+
if (value === null || value === undefined)
|
|
352
|
+
return true;
|
|
353
|
+
if (typeof value === 'string')
|
|
354
|
+
return value.length === 0;
|
|
355
|
+
if (Array.isArray(value))
|
|
356
|
+
return value.length === 0;
|
|
357
|
+
if (typeof value === 'object')
|
|
358
|
+
return Object.keys(value).length === 0;
|
|
359
|
+
return false;
|
|
360
|
+
};
|
|
361
|
+
/**
|
|
362
|
+
* Opposite of isEmpty - checks if a value has content.
|
|
363
|
+
* @description Returns true if a value is not empty. A value is considered
|
|
364
|
+
* to have content if it's not null, undefined, an empty string, an empty array,
|
|
365
|
+
* or an empty object.
|
|
366
|
+
*
|
|
367
|
+
* @param {unknown} value - The value to check for content
|
|
368
|
+
* @returns {boolean} True if the value has content
|
|
369
|
+
*
|
|
370
|
+
* @category Value Checks
|
|
371
|
+
* @example
|
|
372
|
+
* // Form field validation
|
|
373
|
+
* const requiredFields = ['name', 'email'];
|
|
374
|
+
* const formData = { name: 'John', email: '', phone: '123' };
|
|
375
|
+
*
|
|
376
|
+
* const filledRequired = requiredFields.every(field =>
|
|
377
|
+
* isNotEmpty(formData[field as keyof typeof formData])
|
|
378
|
+
* );
|
|
379
|
+
* // => false (email is empty)
|
|
380
|
+
*
|
|
381
|
+
* @example
|
|
382
|
+
* // Filter out empty values
|
|
383
|
+
* const config = {
|
|
384
|
+
* apiKey: 'abc123',
|
|
385
|
+
* endpoint: '',
|
|
386
|
+
* timeout: 5000,
|
|
387
|
+
* headers: {}
|
|
388
|
+
* };
|
|
389
|
+
*
|
|
390
|
+
* const validConfig = Object.fromEntries(
|
|
391
|
+
* Object.entries(config).filter(([_, value]) => isNotEmpty(value))
|
|
392
|
+
* );
|
|
393
|
+
* // => { apiKey: 'abc123', timeout: 5000 }
|
|
394
|
+
*
|
|
395
|
+
* @see isEmpty - Check if value is empty
|
|
396
|
+
* @see isNotNil - Check if value is not null or undefined
|
|
397
|
+
* @since 2025-07-03
|
|
398
|
+
*/
|
|
399
|
+
export var isNotEmpty = function (value) { return !isEmpty(value); };
|
|
400
|
+
/**
|
|
401
|
+
* Creates a predicate that checks if a value equals a specific value.
|
|
402
|
+
* @description Uses strict equality (===) to compare values. Creates a reusable
|
|
403
|
+
* predicate function that can be used for filtering, finding, or validation.
|
|
404
|
+
*
|
|
405
|
+
* @template T - The type of values being compared
|
|
406
|
+
* @param {T} target - The value to compare against
|
|
407
|
+
* @returns {(value: T) => boolean} A predicate that returns true if the value equals the target
|
|
408
|
+
*
|
|
409
|
+
* @category Comparison
|
|
410
|
+
* @example
|
|
411
|
+
* // Basic filtering
|
|
412
|
+
* const isJohn = equals('John');
|
|
413
|
+
* ['John', 'Jane', 'John', 'Jack'].filter(isJohn);
|
|
414
|
+
* // => ['John', 'John']
|
|
415
|
+
*
|
|
416
|
+
* @example
|
|
417
|
+
* // Status checking
|
|
418
|
+
* const isActive = equals('active');
|
|
419
|
+
* const activeUsers = users.filter(user => isActive(user.status));
|
|
420
|
+
*
|
|
421
|
+
* @example
|
|
422
|
+
* // Finding specific items
|
|
423
|
+
* const isTargetId = equals(targetUserId);
|
|
424
|
+
* const targetUser = users.find(user => isTargetId(user.id));
|
|
425
|
+
*
|
|
426
|
+
* @see oneOf - Check if value is one of multiple values
|
|
427
|
+
* @since 2025-07-03
|
|
428
|
+
*/
|
|
429
|
+
export var equals = function (target) {
|
|
430
|
+
return function (value) {
|
|
431
|
+
return value === target;
|
|
432
|
+
};
|
|
433
|
+
};
|
|
434
|
+
/**
|
|
435
|
+
* Creates a predicate that checks if a value is one of the specified values.
|
|
436
|
+
* @description Uses Array.includes internally to check membership. Useful for
|
|
437
|
+
* creating allowlists, checking against enums, or validating against a set
|
|
438
|
+
* of acceptable values.
|
|
439
|
+
*
|
|
440
|
+
* @template T - The type of values in the options array
|
|
441
|
+
* @param {T[]} options - Array of acceptable values
|
|
442
|
+
* @returns {(value: T) => boolean} A predicate that returns true if the value is in the options
|
|
443
|
+
*
|
|
444
|
+
* @category Comparison
|
|
445
|
+
* @example
|
|
446
|
+
* // Day type checking
|
|
447
|
+
* const isWeekend = oneOf(['Saturday', 'Sunday']);
|
|
448
|
+
* isWeekend('Saturday'); // => true
|
|
449
|
+
* isWeekend('Monday'); // => false
|
|
450
|
+
*
|
|
451
|
+
* @example
|
|
452
|
+
* // Permission checking
|
|
453
|
+
* const canEdit = oneOf(['admin', 'editor', 'author']);
|
|
454
|
+
* const editableContent = content.filter(item => canEdit(item.userRole));
|
|
455
|
+
*
|
|
456
|
+
* @example
|
|
457
|
+
* // Enum validation
|
|
458
|
+
* enum Status { Active = 'active', Inactive = 'inactive', Pending = 'pending' }
|
|
459
|
+
* const isValidStatus = oneOf(Object.values(Status));
|
|
460
|
+
*
|
|
461
|
+
* @see equals - Check if value equals a specific value
|
|
462
|
+
* @see includes - Check if array includes a value
|
|
463
|
+
* @since 2025-07-03
|
|
464
|
+
*/
|
|
465
|
+
export var oneOf = function (options) {
|
|
466
|
+
return function (value) {
|
|
467
|
+
return options.includes(value);
|
|
468
|
+
};
|
|
469
|
+
};
|
|
470
|
+
/**
|
|
471
|
+
* Creates a predicate that checks if a number is within a range (inclusive).
|
|
472
|
+
* @description Both minimum and maximum values are included in the range.
|
|
473
|
+
* Useful for validating numeric values against acceptable bounds.
|
|
474
|
+
*
|
|
475
|
+
* @param {number} min - The minimum value (inclusive)
|
|
476
|
+
* @param {number} max - The maximum value (inclusive)
|
|
477
|
+
* @returns {(value: number) => boolean} A predicate that returns true if the value is within range
|
|
478
|
+
*
|
|
479
|
+
* @category Numeric
|
|
480
|
+
* @example
|
|
481
|
+
* // Age validation
|
|
482
|
+
* const isValidAge = inRange(18, 65);
|
|
483
|
+
* isValidAge(17); // => false
|
|
484
|
+
* isValidAge(18); // => true
|
|
485
|
+
* isValidAge(30); // => true
|
|
486
|
+
* isValidAge(65); // => true
|
|
487
|
+
* isValidAge(66); // => false
|
|
488
|
+
*
|
|
489
|
+
* @example
|
|
490
|
+
* // Score validation
|
|
491
|
+
* const isPassingGrade = inRange(60, 100);
|
|
492
|
+
* const passingStudents = students.filter(s => isPassingGrade(s.score));
|
|
493
|
+
*
|
|
494
|
+
* @example
|
|
495
|
+
* // Temperature monitoring
|
|
496
|
+
* const isNormalTemp = inRange(36.0, 37.5);
|
|
497
|
+
* const alerts = readings
|
|
498
|
+
* .filter(not(r => isNormalTemp(r.temperature)))
|
|
499
|
+
* .map(r => ({ time: r.time, temp: r.temperature }));
|
|
500
|
+
*
|
|
501
|
+
* @since 2025-07-03
|
|
502
|
+
*/
|
|
503
|
+
export var inRange = function (min, max) {
|
|
504
|
+
return function (value) {
|
|
505
|
+
return value >= min && value <= max;
|
|
506
|
+
};
|
|
507
|
+
};
|
|
508
|
+
/**
|
|
509
|
+
* Creates a predicate that checks if a string matches a regular expression.
|
|
510
|
+
* @description Uses RegExp.test() to check if the pattern matches the string.
|
|
511
|
+
* The regular expression can include flags for case-insensitive matching,
|
|
512
|
+
* multiline mode, etc.
|
|
513
|
+
*
|
|
514
|
+
* @param {RegExp} pattern - The regular expression to test against
|
|
515
|
+
* @returns {(value: string) => boolean} A predicate that returns true if the string matches
|
|
516
|
+
*
|
|
517
|
+
* @category String
|
|
518
|
+
* @example
|
|
519
|
+
* // Email validation
|
|
520
|
+
* const isEmail = matches(/^[^\s@]+@[^\s@]+\.[^\s@]+$/);
|
|
521
|
+
* isEmail('test@example.com'); // => true
|
|
522
|
+
* isEmail('invalid-email'); // => false
|
|
523
|
+
*
|
|
524
|
+
* @example
|
|
525
|
+
* // Phone number validation
|
|
526
|
+
* const isPhoneNumber = matches(/^\+?[\d\s-()]+$/);
|
|
527
|
+
* const validPhones = contacts
|
|
528
|
+
* .map(c => c.phone)
|
|
529
|
+
* .filter(isPhoneNumber);
|
|
530
|
+
*
|
|
531
|
+
* @example
|
|
532
|
+
* // URL validation with flags
|
|
533
|
+
* const isHttpUrl = matches(/^https?:\/\//i);
|
|
534
|
+
* const secureUrls = urls.filter(matches(/^https:\/\//));
|
|
535
|
+
*
|
|
536
|
+
* @since 2025-07-03
|
|
537
|
+
*/
|
|
538
|
+
export var matches = function (pattern) {
|
|
539
|
+
return function (value) {
|
|
540
|
+
return pattern.test(value);
|
|
541
|
+
};
|
|
542
|
+
};
|
|
543
|
+
/**
|
|
544
|
+
* Creates a predicate that checks if an object has a specific property.
|
|
545
|
+
* @description Type guard that checks for property existence using the 'in' operator.
|
|
546
|
+
* The returned predicate narrows the type to include the checked property.
|
|
547
|
+
* Works with string, number, and symbol keys.
|
|
548
|
+
*
|
|
549
|
+
* @template K - The type of the property key
|
|
550
|
+
* @param {K} key - The property key to check for
|
|
551
|
+
* @returns {<T extends object>(obj: T) => obj is T & Record<K, unknown>} A type guard predicate
|
|
552
|
+
*
|
|
553
|
+
* @category Object
|
|
554
|
+
* @example
|
|
555
|
+
* // Type-safe property filtering
|
|
556
|
+
* const hasEmail = hasProperty('email');
|
|
557
|
+
* const users = [
|
|
558
|
+
* { name: 'John', email: 'john@example.com' },
|
|
559
|
+
* { name: 'Jane' }
|
|
560
|
+
* ];
|
|
561
|
+
* const usersWithEmail = users.filter(hasEmail);
|
|
562
|
+
* // => [{ name: 'John', email: 'john@example.com' }]
|
|
563
|
+
* // TypeScript knows these have email property
|
|
564
|
+
*
|
|
565
|
+
* @example
|
|
566
|
+
* // Feature detection
|
|
567
|
+
* const supportsWebGL = hasProperty('WebGLRenderingContext');
|
|
568
|
+
* if (supportsWebGL(window)) {
|
|
569
|
+
* // Initialize WebGL...
|
|
570
|
+
* }
|
|
571
|
+
*
|
|
572
|
+
* @example
|
|
573
|
+
* // Optional property handling
|
|
574
|
+
* interface User {
|
|
575
|
+
* id: string;
|
|
576
|
+
* name: string;
|
|
577
|
+
* avatar?: string;
|
|
578
|
+
* }
|
|
579
|
+
*
|
|
580
|
+
* const hasAvatar = hasProperty('avatar');
|
|
581
|
+
* const usersWithAvatars = users.filter(hasAvatar);
|
|
582
|
+
* // Now TypeScript knows avatar exists on these users
|
|
583
|
+
*
|
|
584
|
+
* @since 2025-07-03
|
|
585
|
+
*/
|
|
586
|
+
export var hasProperty = function (key) {
|
|
587
|
+
return function (obj) {
|
|
588
|
+
return key in obj;
|
|
589
|
+
};
|
|
590
|
+
};
|
|
591
|
+
/**
|
|
592
|
+
* Creates a predicate that checks if an array includes a specific value.
|
|
593
|
+
* @description Creates a predicate function that tests array membership.
|
|
594
|
+
* Uses Array.includes internally, so it uses SameValueZero equality.
|
|
595
|
+
*
|
|
596
|
+
* @template T - The type of elements in the array
|
|
597
|
+
* @param {T} target - The value to search for in arrays
|
|
598
|
+
* @returns {(array: T[]) => boolean} A predicate that returns true if the array includes the target
|
|
599
|
+
*
|
|
600
|
+
* @category Array
|
|
601
|
+
* @example
|
|
602
|
+
* // Language filtering
|
|
603
|
+
* const hasFavorite = includes('JavaScript');
|
|
604
|
+
* const jsDevs = developers.filter(dev => hasFavorite(dev.languages));
|
|
605
|
+
*
|
|
606
|
+
* @example
|
|
607
|
+
* // Tag filtering
|
|
608
|
+
* const hasUrgentTag = includes('urgent');
|
|
609
|
+
* const urgentTasks = tasks.filter(task => hasUrgentTag(task.tags));
|
|
610
|
+
*
|
|
611
|
+
* @example
|
|
612
|
+
* // Permission checking
|
|
613
|
+
* const hasAdminPermission = includes('admin');
|
|
614
|
+
* const adminActions = actions.filter(action =>
|
|
615
|
+
* hasAdminPermission(action.requiredPermissions)
|
|
616
|
+
* );
|
|
617
|
+
*
|
|
618
|
+
* @see oneOf - Check if value is one of multiple values
|
|
619
|
+
* @since 2025-07-03
|
|
620
|
+
*/
|
|
621
|
+
export var includes = function (target) {
|
|
622
|
+
return function (array) {
|
|
623
|
+
return array.includes(target);
|
|
624
|
+
};
|
|
625
|
+
};
|
|
626
|
+
/**
|
|
627
|
+
* Creates a predicate that always returns true.
|
|
628
|
+
* @description Useful as a default predicate, for conditional filtering,
|
|
629
|
+
* or as a placeholder during development. The parameter is ignored.
|
|
630
|
+
*
|
|
631
|
+
* @template T - The type of the ignored parameter
|
|
632
|
+
* @param {T} _ - Value is ignored
|
|
633
|
+
* @returns {boolean} Always returns true
|
|
634
|
+
*
|
|
635
|
+
* @category Constants
|
|
636
|
+
* @example
|
|
637
|
+
* // Admin bypass for filters
|
|
638
|
+
* const filters = {
|
|
639
|
+
* status: user.role === 'admin' ? alwaysTrue : equals('published')
|
|
640
|
+
* };
|
|
641
|
+
*
|
|
642
|
+
* @example
|
|
643
|
+
* // Conditional filtering
|
|
644
|
+
* const nameFilter = searchTerm
|
|
645
|
+
* ? (user: User) => user.name.includes(searchTerm)
|
|
646
|
+
* : alwaysTrue;
|
|
647
|
+
*
|
|
648
|
+
* @example
|
|
649
|
+
* // Feature toggle
|
|
650
|
+
* const canAccessFeature = FEATURE_ENABLED ? hasPermission('feature') : alwaysTrue;
|
|
651
|
+
*
|
|
652
|
+
* @see alwaysFalse - Predicate that always returns false
|
|
653
|
+
* @since 2025-07-03
|
|
654
|
+
*/
|
|
655
|
+
export var alwaysTrue = function (_) { return true; };
|
|
656
|
+
/**
|
|
657
|
+
* Creates a predicate that always returns false.
|
|
658
|
+
* @description Useful for disabling features, creating empty filter results,
|
|
659
|
+
* or as a placeholder during development. The parameter is ignored.
|
|
660
|
+
*
|
|
661
|
+
* @template T - The type of the ignored parameter
|
|
662
|
+
* @param {T} _ - Value is ignored
|
|
663
|
+
* @returns {boolean} Always returns false
|
|
664
|
+
*
|
|
665
|
+
* @category Constants
|
|
666
|
+
* @example
|
|
667
|
+
* // Conditional record display
|
|
668
|
+
* const filters = {
|
|
669
|
+
* deleted: showDeleted ? alwaysTrue : alwaysFalse
|
|
670
|
+
* };
|
|
671
|
+
*
|
|
672
|
+
* @example
|
|
673
|
+
* // Feature flags
|
|
674
|
+
* const canAccessBeta = BETA_ENABLED ? hasRole('beta') : alwaysFalse;
|
|
675
|
+
*
|
|
676
|
+
* @example
|
|
677
|
+
* // Maintenance mode
|
|
678
|
+
* const canPerformAction = MAINTENANCE_MODE ? alwaysFalse : hasPermission('action');
|
|
679
|
+
*
|
|
680
|
+
* @see alwaysTrue - Predicate that always returns true
|
|
681
|
+
* @since 2025-07-03
|
|
682
|
+
*/
|
|
683
|
+
export var alwaysFalse = function (_) { return false; };
|
|
684
|
+
/**
|
|
685
|
+
* Higher-order predicate utilities for complex compositions.
|
|
686
|
+
* @description Advanced utilities for creating and transforming predicates.
|
|
687
|
+
* These functions provide powerful patterns for building complex predicates
|
|
688
|
+
* from simpler ones.
|
|
689
|
+
*
|
|
690
|
+
* @category Advanced
|
|
691
|
+
* @since 2025-07-03
|
|
692
|
+
*/
|
|
693
|
+
export var predicateUtils = {
|
|
694
|
+
/**
|
|
695
|
+
* Creates a predicate based on a property value.
|
|
696
|
+
* @description Checks if an object's property equals a specific value.
|
|
697
|
+
* Uses strict equality (===) for comparison.
|
|
698
|
+
*
|
|
699
|
+
* @template T - The type of the object
|
|
700
|
+
* @template K - The type of the property key
|
|
701
|
+
* @param {K} key - The property key to check
|
|
702
|
+
* @param {T[K]} value - The value to compare against
|
|
703
|
+
* @returns {(obj: T) => boolean} A predicate that checks the property value
|
|
704
|
+
*
|
|
705
|
+
* @example
|
|
706
|
+
* // Role-based filtering
|
|
707
|
+
* const isAdminRole = predicateUtils.propEquals('role', 'admin');
|
|
708
|
+
* const admins = users.filter(isAdminRole);
|
|
709
|
+
*
|
|
710
|
+
* @example
|
|
711
|
+
* // Status checking
|
|
712
|
+
* const isPublished = predicateUtils.propEquals('status', 'published');
|
|
713
|
+
* const publishedPosts = posts.filter(isPublished);
|
|
714
|
+
*
|
|
715
|
+
* @since 2025-07-03
|
|
716
|
+
*/
|
|
717
|
+
propEquals: function (key, value) {
|
|
718
|
+
return function (obj) {
|
|
719
|
+
return obj[key] === value;
|
|
720
|
+
};
|
|
721
|
+
},
|
|
722
|
+
/**
|
|
723
|
+
* Creates a predicate that checks multiple properties.
|
|
724
|
+
* @description Checks if an object matches all properties in a partial object.
|
|
725
|
+
* Only checks properties that exist in the partial object.
|
|
726
|
+
*
|
|
727
|
+
* @template T - The type of the object being checked
|
|
728
|
+
* @param {Partial<T>} partial - Object with properties to match
|
|
729
|
+
* @returns {(obj: T) => boolean} A predicate that checks all properties match
|
|
730
|
+
*
|
|
731
|
+
* @example
|
|
732
|
+
* // Finding specific users
|
|
733
|
+
* const isJohnDoe = predicateUtils.propsMatch({
|
|
734
|
+
* firstName: 'John',
|
|
735
|
+
* lastName: 'Doe'
|
|
736
|
+
* });
|
|
737
|
+
*
|
|
738
|
+
* const johnDoe = users.find(isJohnDoe);
|
|
739
|
+
*
|
|
740
|
+
* @example
|
|
741
|
+
* // Filtering by multiple criteria
|
|
742
|
+
* const isTargetProduct = predicateUtils.propsMatch({
|
|
743
|
+
* category: 'electronics',
|
|
744
|
+
* inStock: true,
|
|
745
|
+
* featured: true
|
|
746
|
+
* });
|
|
747
|
+
*
|
|
748
|
+
* const featuredElectronics = products.filter(isTargetProduct);
|
|
749
|
+
*
|
|
750
|
+
* @since 2025-07-03
|
|
751
|
+
*/
|
|
752
|
+
propsMatch: function (partial) {
|
|
753
|
+
return function (obj) {
|
|
754
|
+
return Object.entries(partial).every(function (_a) {
|
|
755
|
+
var key = _a[0], value = _a[1];
|
|
756
|
+
return obj[key] === value;
|
|
757
|
+
});
|
|
758
|
+
};
|
|
759
|
+
},
|
|
760
|
+
/**
|
|
761
|
+
* Creates a predicate that applies a transformation before testing.
|
|
762
|
+
* @description Contramap allows you to adapt a predicate for one type to work
|
|
763
|
+
* with another type by providing a transformation function. This is the
|
|
764
|
+
* contravariant functor operation for predicates.
|
|
765
|
+
*
|
|
766
|
+
* @template A - The input type
|
|
767
|
+
* @template B - The type the predicate expects
|
|
768
|
+
* @param {(a: A) => B} transform - Function to transform A to B
|
|
769
|
+
* @param {(b: B) => boolean} predicate - Predicate that operates on type B
|
|
770
|
+
* @returns {(value: A) => boolean} A predicate that operates on type A
|
|
771
|
+
*
|
|
772
|
+
* @example
|
|
773
|
+
* // Extract property before testing
|
|
774
|
+
* const hasLongName = predicateUtils.contramap(
|
|
775
|
+
* (user: { name: string }) => user.name,
|
|
776
|
+
* (name: string) => name.length > 10
|
|
777
|
+
* );
|
|
778
|
+
*
|
|
779
|
+
* @example
|
|
780
|
+
* // Case-insensitive comparison
|
|
781
|
+
* const isCaseInsensitiveMatch = (target: string) =>
|
|
782
|
+
* predicateUtils.contramap(
|
|
783
|
+
* (s: string) => s.toLowerCase(),
|
|
784
|
+
* equals(target.toLowerCase())
|
|
785
|
+
* );
|
|
786
|
+
*
|
|
787
|
+
* @example
|
|
788
|
+
* // Date comparison
|
|
789
|
+
* const isAfter2020 = predicateUtils.contramap(
|
|
790
|
+
* (date: Date) => date.getFullYear(),
|
|
791
|
+
* (year: number) => year > 2020
|
|
792
|
+
* );
|
|
793
|
+
*
|
|
794
|
+
* @since 2025-07-03
|
|
795
|
+
*/
|
|
796
|
+
contramap: function (transform, predicate) {
|
|
797
|
+
return function (value) {
|
|
798
|
+
return predicate(transform(value));
|
|
799
|
+
};
|
|
800
|
+
},
|
|
801
|
+
};
|
|
802
|
+
//# sourceMappingURL=predicates.mjs.map
|