11/**
2- * 🔒 Type-safe array helpers that do not mutate
2+ * Array types, type guards, operations, transformations, accessors, and
3+ * mutations
34 *
4- * Array types, guards, operations, transformations, accessors, and (rare)
5- * mutations.
5+ * ### Example
6+ *
7+ * ```ts
8+ * // Types - compile-time guarantee of at least one element
9+ * const _valid: NonEmptyReadonlyArray<number> = [1, 2, 3];
10+ * // ts-expect-error - empty array is not a valid NonEmptyReadonlyArray
11+ * const _invalid: NonEmptyReadonlyArray<number> = [];
12+ *
13+ * // Type guards
14+ * const arr: ReadonlyArray<number> = [1, 2, 3];
15+ * if (isNonEmptyReadonlyArray(arr)) {
16+ * firstInArray(arr);
17+ * }
18+ *
19+ * // Operations
20+ * const appended = appendToArray([1, 2, 3], 4); // [1, 2, 3, 4]
21+ * const prepended = prependToArray([2, 3], 1); // [1, 2, 3]
22+ *
23+ * // Transformations
24+ * const readonly: ReadonlyArray<number> = [1, 2, 3];
25+ * const mapped = mapArray(readonly, (x) => x * 2); // [2, 4, 6]
26+ * const filtered = filterArray(readonly, (x) => x > 1); // [2, 3]
27+ * const deduped = dedupeArray([1, 2, 1, 3, 2]); // [1, 2, 3]
28+ * const [evens, odds] = partitionArray(
29+ * [1, 2, 3, 4, 5],
30+ * (x) => x % 2 === 0,
31+ * ); // [[2, 4], [1, 3, 5]]
32+ *
33+ * // Accessors
34+ * const first = firstInArray(["a", "b", "c"]); // "a"
35+ * const last = lastInArray(["a", "b", "c"]); // "c"
36+ *
37+ * // Mutations
38+ * const mutable: NonEmptyArray<number> = [1, 2, 3];
39+ * shiftArray(mutable); // 1 (guaranteed to exist)
40+ * mutable; // [2, 3]
41+ * ```
642 *
743 * Functions are intentionally data-first to be prepared for the upcoming
844 * JavaScript pipe operator.
4379 * **Note**: Feel free to use Array instance methods (mutation) if you think
4480 * it's better (performance, local scope, etc.).
4581 *
46- * ### Example
47- *
48- * ```ts
49- * // Types - compile-time guarantee of at least one element
50- * const _valid: NonEmptyReadonlyArray<number> = [1, 2, 3];
51- * // ts-expect-error - empty array is not a valid NonEmptyReadonlyArray
52- * const _invalid: NonEmptyReadonlyArray<number> = [];
53- *
54- * // Guards
55- * const arr: ReadonlyArray<number> = [1, 2, 3];
56- * if (isNonEmptyReadonlyArray(arr)) {
57- * firstInArray(arr);
58- * }
59- *
60- * // Operations
61- * const appended = appendToArray([1, 2, 3], 4); // [1, 2, 3, 4]
62- * const prepended = prependToArray([2, 3], 1); // [1, 2, 3]
63- *
64- * // Transformations
65- * const readonly: ReadonlyArray<number> = [1, 2, 3];
66- * const mapped = mapArray(readonly, (x) => x * 2); // [2, 4, 6]
67- * const filtered = filterArray(readonly, (x) => x > 1); // [2, 3]
68- * const deduped = dedupeArray([1, 2, 1, 3, 2]); // [1, 2, 3]
69- *
70- * // Accessors
71- * const first = firstInArray(["a", "b", "c"]); // "a"
72- * const last = lastInArray(["a", "b", "c"]); // "c"
73- *
74- * // Mutations
75- * const mutable: NonEmptyArray<number> = [1, 2, 3];
76- * shiftArray(mutable); // 1 (guaranteed to exist)
77- * mutable; // [2, 3]
78- * ```
79- *
8082 * @module
8183 */
8284
85+ import { PredicateWithIndex , RefinementWithIndex } from "./Types.js" ;
86+
8387/**
8488 * An array with at least one element.
8589 *
@@ -175,8 +179,7 @@ export const prependToArray = <T>(
175179/**
176180 * Maps an array using a mapper function.
177181 *
178- * Accepts both mutable and readonly arrays. Does not mutate the original array.
179- * Preserves non-empty type.
182+ * Accepts both mutable and readonly arrays. Preserves non-empty type.
180183 *
181184 * ### Example
182185 *
@@ -202,22 +205,49 @@ export function mapArray<T, U>(
202205}
203206
204207/**
205- * Filters an array using a predicate function, returning a new readonly array.
208+ * Filters an array using a predicate or refinement function, returning a new
209+ * readonly array.
206210 *
207- * Accepts both mutable and readonly arrays. Does not mutate the original array.
211+ * Accepts both mutable and readonly arrays. When used with a refinement
212+ * function (with `value is Type` syntax), TypeScript will narrow the result
213+ * type to the narrowed type, making it useful for filtering with Evolu Types
214+ * like `PositiveInt.is`.
208215 *
209- * ### Example
216+ * ### Examples
217+ *
218+ * #### With predicate
210219 *
211220 * ```ts
212221 * filterArray([1, 2, 3, 4, 5], (x) => x % 2 === 0); // [2, 4]
213222 * ```
214223 *
224+ * #### With refinement
225+ *
226+ * ```ts
227+ * const mixed: ReadonlyArray<NonEmptyString | PositiveInt> = [
228+ * NonEmptyString.orThrow("hello"),
229+ * PositiveInt.orThrow(42),
230+ * ];
231+ * const positiveInts = filterArray(mixed, PositiveInt.is);
232+ * // positiveInts: ReadonlyArray<PositiveInt> (narrowed type)
233+ * ```
234+ *
215235 * @category Transformations
216236 */
217- export const filterArray = < T > (
237+ export function filterArray < T , S extends T > (
238+ array : ReadonlyArray < T > ,
239+ refinement : RefinementWithIndex < T , S > ,
240+ ) : ReadonlyArray < S > ;
241+ export function filterArray < T > (
242+ array : ReadonlyArray < T > ,
243+ predicate : PredicateWithIndex < T > ,
244+ ) : ReadonlyArray < T > ;
245+ export function filterArray < T > (
218246 array : ReadonlyArray < T > ,
219- predicate : ( item : T , index : number ) => boolean ,
220- ) : ReadonlyArray < T > => array . filter ( predicate ) as ReadonlyArray < T > ;
247+ predicate : PredicateWithIndex < T > ,
248+ ) : ReadonlyArray < T > {
249+ return array . filter ( predicate ) as ReadonlyArray < T > ;
250+ }
221251
222252/**
223253 * Returns a new readonly array with duplicate items removed. If `by` is
@@ -272,6 +302,71 @@ export function dedupeArray<T>(
272302 } ) as ReadonlyArray < T > ;
273303}
274304
305+ /**
306+ * Partitions an array into two arrays based on a predicate or refinement
307+ * function.
308+ *
309+ * Returns a tuple where the first array contains elements that satisfy the
310+ * predicate, and the second array contains elements that do not. Accepts both
311+ * mutable and readonly arrays.
312+ *
313+ * When used with a refinement function (with `value is Type` syntax),
314+ * TypeScript will narrow the first array to the narrowed type, making it useful
315+ * for filtering with Evolu Types like `PositiveInt.is`.
316+ *
317+ * ### Examples
318+ *
319+ * #### With predicate
320+ *
321+ * ```ts
322+ * const [evens, odds] = partitionArray(
323+ * [1, 2, 3, 4, 5],
324+ * (x) => x % 2 === 0,
325+ * );
326+ * evens; // [2, 4]
327+ * odds; // [1, 3, 5]
328+ * ```
329+ *
330+ * #### With refinement
331+ *
332+ * ```ts
333+ * const mixed: ReadonlyArray<NonEmptyString | PositiveInt> = [
334+ * NonEmptyString.orThrow("hello"),
335+ * PositiveInt.orThrow(42),
336+ * ];
337+ * const [positiveInts, strings] = partitionArray(mixed, PositiveInt.is);
338+ * // positiveInts: ReadonlyArray<PositiveInt> (narrowed type)
339+ * // strings: ReadonlyArray<NonEmptyString> (Exclude<T, PositiveInt>)
340+ * ```
341+ *
342+ * @category Transformations
343+ */
344+ export function partitionArray < T , S extends T > (
345+ array : ReadonlyArray < T > ,
346+ refinement : RefinementWithIndex < T , S > ,
347+ ) : readonly [ ReadonlyArray < S > , ReadonlyArray < Exclude < T , S > > ] ;
348+ export function partitionArray < T > (
349+ array : ReadonlyArray < T > ,
350+ predicate : PredicateWithIndex < T > ,
351+ ) : readonly [ ReadonlyArray < T > , ReadonlyArray < T > ] ;
352+ export function partitionArray < T > (
353+ array : ReadonlyArray < T > ,
354+ predicate : PredicateWithIndex < T > ,
355+ ) : readonly [ ReadonlyArray < T > , ReadonlyArray < T > ] {
356+ const trueArray : Array < T > = [ ] ;
357+ const falseArray : Array < T > = [ ] ;
358+
359+ for ( let i = 0 ; i < array . length ; i ++ ) {
360+ if ( predicate ( array [ i ] , i ) ) {
361+ trueArray . push ( array [ i ] ) ;
362+ } else {
363+ falseArray . push ( array [ i ] ) ;
364+ }
365+ }
366+
367+ return [ trueArray as ReadonlyArray < T > , falseArray as ReadonlyArray < T > ] ;
368+ }
369+
275370/**
276371 * Returns the first element of a non-empty array.
277372 *
0 commit comments