@dbx-tools/shared-core 0.1.18 → 0.1.20

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/src/object.ts CHANGED
@@ -1,9 +1,810 @@
1
1
  /**
2
- * Small value guards, coercions, object-shape types, and structural
3
- * deep-equality: narrow parsed JSON to a record, coerce loose truthy/falsy
4
- * strings to a boolean, describe object shapes (`NameLike`, `NonFunctionKeys`),
5
- * and compare values with {@link deepEqual}. Dependency-free and browser-safe.
2
+ * Dependency-free object + iterable utilities.
3
+ *
4
+ * Value guards / coercions / shape types: {@link isRecord} narrows parsed JSON
5
+ * to a record, {@link toBoolean} coerces loose truthy/falsy values, {@link
6
+ * deepEqual} compares structurally, and {@link NameLike}/{@link NonFunctionKeys}
7
+ * describe object shapes.
8
+ *
9
+ * Iterable helpers: {@link generator} flattens mixed arguments; {@link sequence}
10
+ * wraps source(s) in a lazy, `Array`-compatible {@link Sequence}. Every
11
+ * transform/terminal is a standalone function operating on plain {@link
12
+ * Container}s (see {@link map}, {@link filter}, {@link group}, ...); the {@link
13
+ * Sequence} methods are thin forwarders over them so the same logic backs both
14
+ * the free-function and the fluent/chained styles.
15
+ */
16
+
17
+ /** Lazy sequence over iterable source(s). See {@link sequence}. */
18
+ export type Sequence<T> = SequenceImpl<T>;
19
+
20
+
21
+ type SequenceSource<T> = Iterable<T> | ReadonlyMap<unknown, T> | OneOrMany<T> | null | undefined;
22
+
23
+ /**
24
+ * A non-scalar {@link Iterable} - one to treat as a collection of elements
25
+ * rather than a scalar. {@link isContainer} narrows to this, excluding strings,
26
+ * `String`/`RegExp` objects, and functions. {@link Collection} is the eagerly-
27
+ * sized subset. The element defaults to `unknown` so any `Collection` is
28
+ * assignable to a bare `Container`.
29
+ */
30
+ export type Container<T = unknown> = Iterable<T>;
31
+
32
+ /**
33
+ * A built-in, eagerly-sized {@link Container}: an {@link Array}, {@link Set}, or
34
+ * {@link Map} (whose *values* are `T` - a Map iterates `[key, value]` entries,
35
+ * so its element type differs, but its value type is `T`). All share a cheap
36
+ * emptiness check ({@link isEmpty}).
37
+ */
38
+ export type Collection<T> = ReadonlyArray<T> | ReadonlySet<T> | ReadonlyMap<unknown, T>;
39
+
40
+ export type OneOrMany<T> = [T, ...T[]];
41
+
42
+ /** Narrow a readonly array to a non-empty {@link OneOrMany} tuple. */
43
+ export function isOneOrMany<T = unknown>(value: readonly T[]): value is OneOrMany<T> {
44
+ return value.length > 0;
45
+ }
46
+
47
+ /** A source accepted by a variadic op: a {@link Container} of `T`, or nothing. */
48
+ type Source<T> = Container<T> | null | undefined;
49
+
50
+ /**
51
+ * Element type of a {@link group} bucket array: when the predicate `P` is a type
52
+ * guard (`value is S`), the bucket is narrowed to `S & T`; otherwise it stays `T`.
53
+ */
54
+ type GroupValue<T, P> = P extends (value: any, ...rest: any[]) => value is infer S ? S & T : T;
55
+
56
+ /** A map of group name -> predicate, as accepted by {@link group}. */
57
+ type GroupPredicates<T> = Record<string, (value: T, index: number) => boolean>;
58
+
59
+
60
+
61
+
62
+ /**
63
+ * Type guard for a {@link Collection}: an {@link Array}, {@link Set}, or
64
+ * {@link Map}. Narrows `value` so its element/value type is treated as `T`.
65
+ *
66
+ * @typeParam T - Element (or Map value) type asserted for the collection.
67
+ * @param value - Value to test.
68
+ * @returns `true` (narrowing `value` to {@link Collection}<`T`>) for a
69
+ * built-in array/set/map.
70
+ */
71
+ export function isCollection<T = unknown>(value: unknown): value is Collection<T> {
72
+ return Array.isArray(value) || value instanceof Set || value instanceof Map;
73
+ }
74
+
75
+ /**
76
+ * `true` when a {@link Collection} has no elements. Uses `length` for arrays
77
+ * and `size` for {@link Set}/{@link Map}.
78
+ *
79
+ * @param collection - The array, set, or map to test.
80
+ */
81
+ export function isEmpty(
82
+ collection: Collection<unknown> | Record<string, unknown>,
83
+ options?: { recursive?: boolean },
84
+ ): boolean {
85
+
86
+ function visit(value: unknown, seen?: Set<unknown>): boolean {
87
+ if (value == null) return true;
88
+ else if (typeof value === "object") {
89
+ if (seen?.has(value)) return true;
90
+ seen?.add(value);
91
+ if (Array.isArray(value)) {
92
+ return value.length === 0 || (seen ? value.every((item) => visit(item, seen)) : false);
93
+ }
94
+ if (value instanceof Set) {
95
+ return (
96
+ value.size === 0 || (seen ? [...value].every((item) => visit(item, seen)) : false)
97
+ );
98
+ }
99
+ if (value instanceof Map) {
100
+ return (
101
+ value.size === 0 ||
102
+ (seen ? [...value.values()].every((item) => visit(item, seen)) : false)
103
+ );
104
+ }
105
+ const keys = Object.keys(value);
106
+ if (keys.length === 0) return true;
107
+ else if (seen) {
108
+ return keys.every((key) => visit((value as Record<string, unknown>)[key], seen));
109
+ } else {
110
+ return false
111
+ }
112
+ } else {
113
+ return false;
114
+ }
115
+ }
116
+ return visit(collection, options?.recursive ? new Set() : undefined);
117
+ }
118
+
119
+
120
+ /**
121
+ * Normalizes a source to an iterable of its `T` values, so Maps are treated
122
+ * uniformly with arrays/sets: a {@link Map} yields its *values* (matching
123
+ * {@link Collection}'s value-typed `T`), any other iterable yields itself. This
124
+ * is what {@link sequence} consumes, so a `Map` contributes values everywhere
125
+ * rather than `[key, value]` entries.
126
+ */
127
+ export function values<T>(source: Iterable<T> | ReadonlyMap<unknown, T>): Iterable<T> {
128
+ return source instanceof Map ? source.values() : (source as Iterable<T>);
129
+ }
130
+
131
+ /**
132
+ * Type guard for a {@link Container} - an iterable to be treated as a collection
133
+ * rather than a scalar.
134
+ *
135
+ * Deliberately excludes values that are technically iterable but should be
136
+ * treated as scalars here - strings, `String`/`RegExp` objects, and functions -
137
+ * so a lone string is never spread character-by-character.
138
+ *
139
+ * @typeParam T - Element type asserted for the iterable.
140
+ * @param value - Value to test.
141
+ * @returns `true` (narrowing `value` to {@link Container}<`T`>) for a non-string
142
+ * iterable.
143
+ */
144
+ export function isContainer<T = unknown>(value: unknown): value is Container<T> {
145
+ return (
146
+ value != null &&
147
+ typeof value !== "string" &&
148
+ !(value instanceof String) &&
149
+ !(value instanceof RegExp) &&
150
+ typeof value !== "function" &&
151
+ typeof (value as { [Symbol.iterator]?: unknown })[Symbol.iterator] === "function"
152
+ );
153
+ }
154
+
155
+
156
+ function sequenceSources<T>(...sources: SequenceSource<T>[]): Iterable<T>[] {
157
+ const sourceIterables: Iterable<T>[] = [];
158
+ for (const source of sources) {
159
+ if (source == null || (isCollection(source) && isEmpty(source))) continue;
160
+ sourceIterables.push(values(source));
161
+ }
162
+ return sourceIterables;
163
+ }
164
+
165
+ /**
166
+ * Flattens nested arrays for {@link flat}. Non-array values are wrapped as a
167
+ * single-element iterable; depth decrements per array level.
168
+ */
169
+ function flattenValue(value: unknown, depth: number): Iterable<unknown> {
170
+ if (depth > 0 && Array.isArray(value)) {
171
+ const nextDepth = Number.isFinite(depth) ? depth - 1 : depth;
172
+ return {
173
+ *[Symbol.iterator]() {
174
+ for (const item of value) yield* flattenValue(item, nextDepth);
175
+ },
176
+ };
177
+ }
178
+ return [value];
179
+ }
180
+
181
+ /** Wrap a per-element transform (each element -> an iterable) as a lazy {@link Sequence}. */
182
+ function derive<T, U>(
183
+ source: Iterable<T>,
184
+ fn: (value: T, index: number) => Iterable<U>,
185
+ ): Sequence<U> {
186
+ return sequence({
187
+ *[Symbol.iterator]() {
188
+ let index = 0;
189
+ for (const value of source) yield* fn(value, index++);
190
+ },
191
+ });
192
+ }
193
+
194
+ /** Like {@link derive}, but yields one value per source element (no wrapper iterable). */
195
+ function deriveOne<T, U>(source: Iterable<T>, fn: (value: T, index: number) => U): Sequence<U> {
196
+ return sequence({
197
+ *[Symbol.iterator]() {
198
+ let index = 0;
199
+ for (const value of source) yield fn(value, index++);
200
+ },
201
+ });
202
+ }
203
+
204
+ /**
205
+ * Same semantics as `Array.prototype.map`, over a single {@link Container}.
206
+ *
207
+ * @param source - The container to map (nullish yields an empty sequence).
208
+ * @param callback - Called per element with its index; its result is emitted.
209
+ */
210
+ export function map<T, U>(
211
+ source: Source<T>,
212
+ callback: (value: T, index: number) => U,
213
+ ): Sequence<U> {
214
+ return deriveOne(sequence(source), callback);
215
+ }
216
+
217
+ /**
218
+ * Same semantics as `Array.prototype.filter`, over a single {@link Container}.
219
+ * A type-guard predicate narrows the resulting element type.
220
+ *
221
+ * @param source - The container to filter (nullish yields an empty sequence).
222
+ * @param predicate - Keeps elements for which it returns `true`.
223
+ */
224
+ export function filter<T, S extends T>(
225
+ source: Source<T>,
226
+ predicate: (value: T, index: number) => value is S,
227
+ ): Sequence<S>;
228
+ export function filter<T>(
229
+ source: Source<T>,
230
+ predicate: (value: T, index: number) => boolean,
231
+ ): Sequence<T>;
232
+ export function filter<T>(
233
+ source: Source<T>,
234
+ predicate: (value: T, index: number) => boolean,
235
+ ): Sequence<T> {
236
+ return derive(sequence(source), function* (value, index) {
237
+ if (predicate(value, index)) yield value;
238
+ });
239
+ }
240
+
241
+ /**
242
+ * Concatenates the sources and yields only elements that are not `null` or
243
+ * `undefined`, narrowing the element type to {@link NonNullable}<`T`>.
244
+ *
245
+ * @param sources - Containers to concatenate (nullish sources are skipped).
246
+ */
247
+ export function nonNull<T>(...sources: readonly Source<T>[]): Sequence<NonNullable<T>> {
248
+ return filter(sequence(...sources), (value): value is NonNullable<T> => value != null);
249
+ }
250
+
251
+ /**
252
+ * Same semantics as `Array.prototype.flatMap`, over a single {@link Container}.
253
+ *
254
+ * @param source - The container to map (nullish yields an empty sequence).
255
+ * @param callback - Returns a value or array of values, flattened one level.
256
+ */
257
+ export function flatMap<T, U>(
258
+ source: Source<T>,
259
+ callback: (value: T, index: number) => U | ReadonlyArray<U>,
260
+ ): Sequence<U> {
261
+ return derive(sequence(source), function* (value, index) {
262
+ const result = callback(value, index);
263
+ if (Array.isArray(result)) yield* result;
264
+ else yield result as U;
265
+ });
266
+ }
267
+
268
+ /**
269
+ * Same semantics as `Array.prototype.flat` (arrays only). `depth` leads so the
270
+ * sources can stay variadic; use `depth < 1` for a no-op passthrough.
271
+ *
272
+ * @param depth - How many array levels to flatten.
273
+ * @param sources - Containers to concatenate, then flatten (nullish skipped).
274
+ */
275
+ export function flat<T>(depth: number, ...sources: readonly Source<T>[]): Sequence<T> {
276
+ const src = sequence(...sources);
277
+ if (depth < 1) return src;
278
+ return derive(src, (value) => flattenValue(value, depth)) as Sequence<T>;
279
+ }
280
+
281
+ /**
282
+ * Concatenates the sources, then lazily yields values in encounter order,
283
+ * skipping a value only when an equal one was already yielded (`Set` /
284
+ * SameValueZero). Uniqueness is checked per element as it is consumed.
285
+ *
286
+ * @param sources - Containers to concatenate (nullish sources are skipped).
6
287
  */
288
+ export function distinct<T>(...sources: readonly Source<T>[]): Sequence<T> {
289
+ const src = sequence(...sources);
290
+ return sequence({
291
+ *[Symbol.iterator]() {
292
+ const seen = new Set<T>();
293
+ for (const value of src) {
294
+ if (seen.has(value)) continue;
295
+ seen.add(value);
296
+ yield value;
297
+ }
298
+ },
299
+ });
300
+ }
301
+
302
+ /**
303
+ * Yields `source`, then each appended `item` in order (arrays spread one level),
304
+ * mirroring `Array.prototype.concat`. `items` are scalar values/arrays, not
305
+ * containers, so `source` stays a single leading argument.
306
+ *
307
+ * @param source - The leading container (nullish yields just the items).
308
+ * @param items - Values (or arrays of values) appended after the source.
309
+ */
310
+ export function concat<T>(
311
+ source: Source<T>,
312
+ ...items: readonly (T | ReadonlyArray<T>)[]
313
+ ): Sequence<T> {
314
+ const src = sequence(source);
315
+ if (items.length === 0) return src;
316
+ return sequence({
317
+ *[Symbol.iterator]() {
318
+ yield* src;
319
+ for (const item of items) {
320
+ if (Array.isArray(item)) yield* item;
321
+ else yield item as T;
322
+ }
323
+ },
324
+ });
325
+ }
326
+
327
+ /**
328
+ * Yields at most `count` elements from the front of the concatenated sources.
329
+ * `count` leads so the sources can stay variadic.
330
+ *
331
+ * @param count - Maximum number of elements to yield (`<= 0` yields none).
332
+ * @param sources - Containers to concatenate (nullish sources are skipped).
333
+ */
334
+ export function take<T>(count: number, ...sources: readonly Source<T>[]): Sequence<T> {
335
+ if (count <= 0) return emptySequence as Sequence<T>;
336
+ const src = sequence(...sources);
337
+ return sequence({
338
+ *[Symbol.iterator]() {
339
+ let taken = 0;
340
+ for (const value of src) {
341
+ yield value;
342
+ if (++taken >= count) return;
343
+ }
344
+ },
345
+ });
346
+ }
347
+
348
+ /**
349
+ * Splits the concatenated sources into one array per named predicate. Consumes
350
+ * the input once, routing each element to the FIRST predicate it satisfies (so
351
+ * groups are disjoint); elements matching no predicate are dropped. Type-guard
352
+ * predicates narrow their group's element type (see {@link GroupValue}).
353
+ * `predicates` leads so the sources can stay variadic.
354
+ *
355
+ * @typeParam G - The map of group name -> predicate.
356
+ * @param predicates - Named predicates; evaluated in declaration order.
357
+ * @param sources - Containers to concatenate (nullish sources are skipped).
358
+ * @returns An object with the same keys, each an array of its group's elements.
359
+ *
360
+ * @example
361
+ * const { strings, fns } = group({ strings: isString, fns: isFunction }, xs);
362
+ */
363
+ export function group<T, G extends GroupPredicates<T>>(
364
+ predicates: G,
365
+ ...sources: readonly Source<T>[]
366
+ ): { [K in keyof G]: GroupValue<T, G[K]>[] } {
367
+ const keys = Object.keys(predicates) as (keyof G)[];
368
+ const buckets = new Map<keyof G, T[]>(keys.map((key) => [key, []]));
369
+ let index = 0;
370
+ for (const value of sequence(...sources)) {
371
+ const i = index++;
372
+ for (const key of keys) {
373
+ if (predicates[key]!(value, i)) {
374
+ buckets.get(key)!.push(value);
375
+ break;
376
+ }
377
+ }
378
+ }
379
+ const result = {} as { [K in keyof G]: GroupValue<T, G[K]>[] };
380
+ for (const key of keys) {
381
+ result[key] = buckets.get(key)! as GroupValue<T, G[typeof key]>[];
382
+ }
383
+ return result;
384
+ }
385
+
386
+ /**
387
+ * Same semantics as `Array.prototype.find`, over a single {@link Container}.
388
+ * Consumes elements until a match. A type guard narrows the return type.
389
+ */
390
+ export function find<T, S extends T>(
391
+ source: Source<T>,
392
+ predicate: (value: T, index: number) => value is S,
393
+ ): S | undefined;
394
+ export function find<T>(
395
+ source: Source<T>,
396
+ predicate: (value: T, index: number) => boolean,
397
+ ): T | undefined;
398
+ export function find<T>(
399
+ source: Source<T>,
400
+ predicate: (value: T, index: number) => boolean,
401
+ ): T | undefined {
402
+ let index = 0;
403
+ for (const value of sequence(source)) {
404
+ if (predicate(value, index++)) return value;
405
+ }
406
+ return undefined;
407
+ }
408
+
409
+ /**
410
+ * Same semantics as `Array.prototype.findLast`, over a single {@link Container}.
411
+ * Consumes the full source. A type guard narrows the return type.
412
+ */
413
+ export function findLast<T, S extends T>(
414
+ source: Source<T>,
415
+ predicate: (value: T, index: number) => value is S,
416
+ ): S | undefined;
417
+ export function findLast<T>(
418
+ source: Source<T>,
419
+ predicate: (value: T, index: number) => boolean,
420
+ ): T | undefined;
421
+ export function findLast<T>(
422
+ source: Source<T>,
423
+ predicate: (value: T, index: number) => boolean,
424
+ ): T | undefined {
425
+ let index = 0;
426
+ let match: T | undefined;
427
+ for (const value of sequence(source)) {
428
+ if (predicate(value, index++)) match = value;
429
+ }
430
+ return match;
431
+ }
432
+
433
+ /**
434
+ * Same semantics as `Array.prototype.findIndex`, over a single {@link Container}.
435
+ * Consumes elements until a match.
436
+ */
437
+ export function findIndex<T>(
438
+ source: Source<T>,
439
+ predicate: (value: T, index: number) => boolean,
440
+ ): number {
441
+ let index = 0;
442
+ for (const value of sequence(source)) {
443
+ if (predicate(value, index)) return index;
444
+ index++;
445
+ }
446
+ return -1;
447
+ }
448
+
449
+ /**
450
+ * Same semantics as `Array.prototype.findLastIndex`, over a single
451
+ * {@link Container}. Consumes the full source.
452
+ */
453
+ export function findLastIndex<T>(
454
+ source: Source<T>,
455
+ predicate: (value: T, index: number) => boolean,
456
+ ): number {
457
+ let index = 0;
458
+ let match = -1;
459
+ for (const value of sequence(source)) {
460
+ if (predicate(value, index)) match = index;
461
+ index++;
462
+ }
463
+ return match;
464
+ }
465
+
466
+ /**
467
+ * Same semantics as `Array.prototype.some`, over a single {@link Container}.
468
+ * Short-circuits on the first match.
469
+ */
470
+ export function some<T>(
471
+ source: Source<T>,
472
+ predicate: (value: T, index: number) => boolean,
473
+ ): boolean {
474
+ let index = 0;
475
+ for (const value of sequence(source)) if (predicate(value, index++)) return true;
476
+ return false;
477
+ }
478
+
479
+ /**
480
+ * Same semantics as `Array.prototype.every`, over a single {@link Container}.
481
+ * Short-circuits on the first failure.
482
+ */
483
+ export function every<T, S extends T>(
484
+ source: Source<T>,
485
+ predicate: (value: T, index: number) => value is S,
486
+ ): boolean;
487
+ export function every<T>(
488
+ source: Source<T>,
489
+ predicate: (value: T, index: number) => boolean,
490
+ ): boolean;
491
+ export function every<T>(
492
+ source: Source<T>,
493
+ predicate: (value: T, index: number) => boolean,
494
+ ): boolean {
495
+ let index = 0;
496
+ for (const value of sequence(source)) if (!predicate(value, index++)) return false;
497
+ return true;
498
+ }
499
+
500
+ /**
501
+ * Same semantics as `Array.prototype.forEach`, over a single {@link Container}.
502
+ * Consumes the source.
503
+ */
504
+ export function forEach<T>(source: Source<T>, callback: (value: T, index: number) => void): void {
505
+ let index = 0;
506
+ for (const value of sequence(source)) callback(value, index++);
507
+ }
508
+
509
+ /**
510
+ * Same semantics as `Array.prototype.at` over the concatenated sources.
511
+ * Non-negative indices scan lazily; negative indices materialize first. `index`
512
+ * leads so the sources can stay variadic.
513
+ *
514
+ * @param index - Zero-based position; negative counts from the end.
515
+ * @param sources - Containers to concatenate (nullish sources are skipped).
516
+ */
517
+ export function at<T>(index: number, ...sources: readonly Source<T>[]): T | undefined {
518
+ const src = sequence(...sources);
519
+ if (index < 0) return toArray(src).at(index);
520
+ let i = 0;
521
+ for (const value of src) {
522
+ if (i++ === index) return value;
523
+ }
524
+ return undefined;
525
+ }
526
+
527
+ export function toOneOrMany<T>(input: (T | OneOrMany<T>)): OneOrMany<T> {
528
+ return Array.isArray(input) ? input : [input];
529
+ }
530
+
531
+ /**
532
+ * Materializes the concatenated sources into a new array. Consumes single-pass
533
+ * sources.
534
+ *
535
+ * @param sources - Containers to concatenate (nullish sources are skipped).
536
+ */
537
+ export function toArray<T>(...sources: readonly Source<T>[]): readonly T[] {
538
+ return [...sequence(...sources)];
539
+ }
540
+
541
+ /**
542
+ * Lazy iterable sequence with `Array`-compatible transforms and terminal
543
+ * methods. Single-pass by default; call {@link SequenceImpl.cache} to retain
544
+ * pulled values for re-iteration. Built for generators and other sources where
545
+ * a second pass is not guaranteed.
546
+ *
547
+ * The methods forward to the standalone functions of the same name - see each
548
+ * method's `@see` - so the free-function and chained styles share one impl.
549
+ */
550
+ class SequenceImpl<T> {
551
+ private iterator?: Iterator<T>;
552
+ private exhausted: boolean;
553
+
554
+ constructor(
555
+ private readonly source: Iterable<T>,
556
+ private readonly buffer: T[] | undefined,
557
+ state: { readonly exhausted?: boolean } = {},
558
+ ) {
559
+ this.exhausted = state.exhausted ?? false;
560
+ }
561
+
562
+ private get caching(): boolean {
563
+ return this.buffer !== undefined;
564
+ }
565
+
566
+ /** Advance the underlying source once, creating the iterator on first use. */
567
+ private pull(): IteratorResult<T> {
568
+ this.iterator ??= this.source[Symbol.iterator]();
569
+ return this.iterator.next();
570
+ }
571
+
572
+ *[Symbol.iterator](): Iterator<T> {
573
+ if (!this.caching) {
574
+ if (this.exhausted) return;
575
+ for (let next = this.pull(); !next.done; next = this.pull()) {
576
+ yield next.value;
577
+ }
578
+ this.exhausted = true;
579
+ } else {
580
+ // Cached: iterate like a list. Always replay the buffer from the start,
581
+ // extending it from the source on demand until exhausted. A broken loop
582
+ // leaves the buffer intact, so the next iteration starts over from the
583
+ // beginning.
584
+ const buffer = this.buffer!;
585
+ let index = 0;
586
+ for (; ;) {
587
+ if (index < buffer.length) {
588
+ yield buffer[index++]!;
589
+ continue;
590
+ }
591
+ if (this.exhausted) return;
592
+ const next = this.pull();
593
+ if (next.done) {
594
+ this.exhausted = true;
595
+ return;
596
+ }
597
+ buffer.push(next.value);
598
+ yield next.value;
599
+ index++;
600
+ }
601
+ }
602
+ }
603
+
604
+ /** @see {@link map} */
605
+ map<U>(callback: (value: T, index: number) => U): Sequence<U> {
606
+ return map(this, callback);
607
+ }
608
+
609
+ /** @see {@link filter} */
610
+ filter<S extends T>(predicate: (value: T, index: number) => value is S): Sequence<S>;
611
+ filter(predicate: (value: T, index: number) => boolean): Sequence<T>;
612
+ filter(predicate: (value: T, index: number) => boolean): Sequence<T> {
613
+ return filter(this, predicate);
614
+ }
615
+
616
+ /** @see {@link nonNull} */
617
+ nonNull(): Sequence<NonNullable<T>> {
618
+ return nonNull(this);
619
+ }
620
+
621
+ /** @see {@link flatMap} */
622
+ flatMap<U>(callback: (value: T, index: number) => U | ReadonlyArray<U>): Sequence<U> {
623
+ return flatMap(this, callback);
624
+ }
625
+
626
+ /** @see {@link flat} */
627
+ flat(depth = 1): Sequence<T> {
628
+ return flat(depth, this);
629
+ }
630
+
631
+ /** @see {@link distinct} */
632
+ distinct(): Sequence<T> {
633
+ return distinct(this);
634
+ }
635
+
636
+ /** @see {@link concat} */
637
+ concat(...items: readonly (T | ReadonlyArray<T>)[]): Sequence<T> {
638
+ return concat(this, ...items);
639
+ }
640
+
641
+ /**
642
+ * Lazily yields this sequence followed by each iterable `source` in order.
643
+ * Like {@link concat}, but for iterable sources (generators, other sequences,
644
+ * `Set`, `Map`, etc.) rather than scalar values or arrays. A {@link Map}
645
+ * source contributes its values.
646
+ *
647
+ * @see {@link sequence}
648
+ */
649
+ join(
650
+ ...sources: readonly SequenceSource<T>[]
651
+ ): Sequence<T> {
652
+ const sourceIterables = sequenceSources(this, ...sources);
653
+ return sequenceSources.length === 0 ? this : sequence(...sourceIterables);
654
+ }
655
+
656
+ /** @see {@link take} */
657
+ take(count: number): Sequence<T> {
658
+ return take(count, this);
659
+ }
660
+
661
+ /** @see {@link group} */
662
+ group<G extends GroupPredicates<T>>(predicates: G): { [K in keyof G]: GroupValue<T, G[K]>[] } {
663
+ return group(predicates, this);
664
+ }
665
+
666
+ /**
667
+ * Returns a cached, re-iterable view of this sequence. An already-caching
668
+ * sequence returns itself; otherwise this one-pass sequence is wrapped in a
669
+ * new instance that retains pulled values. Iterate the returned instance, not
670
+ * the original, to avoid competing for the same single-pass source.
671
+ */
672
+ cache(): Sequence<T> {
673
+ return this.caching ? this : new SequenceImpl(this, []);
674
+ }
675
+
676
+ /** @see {@link find} */
677
+ find<S extends T>(predicate: (value: T, index: number) => value is S): S | undefined;
678
+ find(predicate: (value: T, index: number) => boolean): T | undefined;
679
+ find(predicate: (value: T, index: number) => boolean): T | undefined {
680
+ return find(this, predicate);
681
+ }
682
+
683
+ /** @see {@link findLast} */
684
+ findLast<S extends T>(predicate: (value: T, index: number) => value is S): S | undefined;
685
+ findLast(predicate: (value: T, index: number) => boolean): T | undefined;
686
+ findLast(predicate: (value: T, index: number) => boolean): T | undefined {
687
+ return findLast(this, predicate);
688
+ }
689
+
690
+ /** @see {@link findIndex} */
691
+ findIndex(predicate: (value: T, index: number) => boolean): number {
692
+ return findIndex(this, predicate);
693
+ }
694
+
695
+ /** @see {@link findLastIndex} */
696
+ findLastIndex(predicate: (value: T, index: number) => boolean): number {
697
+ return findLastIndex(this, predicate);
698
+ }
699
+
700
+ /** @see {@link some} */
701
+ some(predicate: (value: T, index: number) => boolean): boolean {
702
+ return some(this, predicate);
703
+ }
704
+
705
+ /** @see {@link every} (the `S extends T` overload narrows at compile time only). */
706
+ every<S extends T>(predicate: (value: T, index: number) => value is S): this is Sequence<S>;
707
+ every(predicate: (value: T, index: number) => boolean): boolean;
708
+ every(predicate: (value: T, index: number) => boolean): boolean {
709
+ return every(this, predicate);
710
+ }
711
+
712
+ /** @see {@link forEach} */
713
+ forEach(callback: (value: T, index: number) => void): void {
714
+ forEach(this, callback);
715
+ }
716
+
717
+ /** @see {@link at} */
718
+ at(index: number): T | undefined {
719
+ return at(index, this);
720
+ }
721
+
722
+ /**
723
+ * Materialize the sequence into a new array. Consumes a single-pass source; a
724
+ * cached, exhausted sequence copies its buffer directly.
725
+ *
726
+ * @see {@link toArray}
727
+ */
728
+ toArray(): readonly T[] {
729
+ if (this.caching && this.exhausted) return [...this.buffer!];
730
+ return toArray(this);
731
+ }
732
+ }
733
+
734
+ /** Shared empty sequence singleton, reusable for any element type. */
735
+ const emptySequence: Sequence<never> = new SequenceImpl([], undefined, {
736
+ exhausted: true,
737
+ });
738
+
739
+ /**
740
+ * Wrap one or more iterable `sources` in a single lazy {@link Sequence},
741
+ * iterated in order. `null`/`undefined` sources are skipped; when nothing
742
+ * remains (every source omitted, `null`, `undefined`, or an empty array /
743
+ * `Set` / `Map`), {@link emptySequence} is returned. The result is single-pass
744
+ * - call `.cache()` to make it re-iterable.
745
+ *
746
+ * A {@link Map} source contributes its values (see {@link values}), consistent
747
+ * with {@link Collection}'s value-typed `T`.
748
+ *
749
+ * @typeParam T - Element type of the sequence.
750
+ * @param sources - Iterables to concatenate, in order (`Map` sources use values).
751
+ */
752
+ export function sequence<T>(
753
+ ...sources: readonly SequenceSource<T>[]
754
+ ): Sequence<T> {
755
+ // Skip nullish sources and known-empty collections; normalize the rest to
756
+ // their values so a Map contributes values rather than [key, value] entries.
757
+ const sourceIterables = sequenceSources(...sources);
758
+ if (sourceIterables.length === 0) return emptySequence as Sequence<T>;
759
+ // Reuse an existing sequence as-is rather than re-wrapping it.
760
+ if (sourceIterables.length === 1) {
761
+ const only = sourceIterables[0]!;
762
+ return only instanceof SequenceImpl ? (only as Sequence<T>) : new SequenceImpl(only, undefined);
763
+ }
764
+ return new SequenceImpl(
765
+ {
766
+ *[Symbol.iterator]() {
767
+ for (const source of sourceIterables) yield* source;
768
+ },
769
+ },
770
+ undefined,
771
+ );
772
+ }
773
+
774
+
775
+
776
+ /**
777
+ * Flattens a mix of single items and iterables into one lazy {@link Generator}.
778
+ *
779
+ * Arguments are emitted in order: `null`/`undefined` are skipped, non-string
780
+ * iterables (per {@link isContainer}) are yielded element-by-element, and
781
+ * anything else (including strings) is yielded as a single item.
782
+ *
783
+ * @typeParam T - Element type produced by the generator.
784
+ * @param items - Items and/or iterables to flatten, in order.
785
+ * @returns A generator over the flattened elements.
786
+ */
787
+ export function* generator<T>(
788
+ ...items: readonly (T | Iterable<T> | null | undefined)[]
789
+ ): Generator<T> {
790
+ for (const item of items) {
791
+ if (item === null || item === undefined) {
792
+ continue;
793
+ } else if (isContainer(item)) {
794
+ yield* item;
795
+ } else {
796
+ yield item;
797
+ }
798
+ }
799
+ }
800
+
801
+
802
+
803
+
804
+
805
+ // ---------------------------------------------------------------------------
806
+ // Object value guards, coercions, and structural equality
807
+ // ---------------------------------------------------------------------------
7
808
 
8
809
  /** Minimal shape for objects that expose an optional `name` (e.g. AppKit plugins). */
9
810
  export interface NameLike {