@wistia/utils 0.0.0-stage → 1.0.0

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/llms-full.txt ADDED
@@ -0,0 +1,1195 @@
1
+ # @wistia/utils — full reference
2
+
3
+ Runtime type guards, utility types, and utility functions for TypeScript.
4
+ Every guard is a `value is T` predicate that narrows `unknown` values;
5
+ every type is a small composable building block; utilities are
6
+ general-purpose helper functions. Side-effect free, tree-shakeable, ESM
7
+ and CJS builds.
8
+
9
+ ## Install
10
+
11
+ ```sh
12
+ npm install @wistia/utils
13
+ yarn add @wistia/utils
14
+ pnpm add @wistia/utils
15
+ ```
16
+
17
+ ## Import
18
+
19
+ ```ts
20
+ import {
21
+ isString,
22
+ isNotNil,
23
+ isNonEmptyArray,
24
+ type Nilable,
25
+ type NonEmptyArray,
26
+ } from '@wistia/utils';
27
+ ```
28
+
29
+ ## Guards
30
+
31
+ <!-- AUTOGEN:LLMS-FULL-GUARDS:START -->
32
+ ### `hasKey`
33
+
34
+ ```ts
35
+ hasKey<Key extends string, Value>(value: unknown, key: Key): value is Record<Key, Value>
36
+ ```
37
+
38
+ ```ts
39
+ hasKey({ a: 1 }, 'a'); // true
40
+ hasKey({ a: 1 }, 'b'); // false
41
+ ```
42
+
43
+ ### `isArray`
44
+
45
+ ```ts
46
+ isArray(value: unknown): value is unknown[]
47
+ ```
48
+
49
+ ```ts
50
+ isArray([1, 2]); // true
51
+ isArray('abc'); // false
52
+ ```
53
+
54
+ ### `isAsyncFunction`
55
+
56
+ ```ts
57
+ isAsyncFunction(value: unknown): value is (...args: unknown[]) => Promise<unknown>
58
+ ```
59
+
60
+ Detection relies on `constructor.name === 'AsyncFunction'` and is a
61
+ heuristic: async functions transpiled to regular functions return false,
62
+ async generator functions return false, and a forged `constructor`
63
+ property can fool the check. Bound async functions return true, since
64
+ `bind` preserves the prototype chain. Synchronous functions that return
65
+ promises also return false.
66
+
67
+ ```ts
68
+ isAsyncFunction(async () => {}); // true
69
+ isAsyncFunction(() => {}); // false
70
+ ```
71
+
72
+ ### `isBigInt`
73
+
74
+ ```ts
75
+ isBigInt(value: unknown): value is bigint
76
+ ```
77
+
78
+ ```ts
79
+ isBigInt(10n); // true
80
+ isBigInt(10); // false
81
+ ```
82
+
83
+ ### `isBoolean`
84
+
85
+ ```ts
86
+ isBoolean(value: unknown): value is boolean
87
+ ```
88
+
89
+ ```ts
90
+ isBoolean(true); // true
91
+ isBoolean(1); // false
92
+ ```
93
+
94
+ ### `isDate`
95
+
96
+ ```ts
97
+ isDate(value: unknown): value is Date
98
+ ```
99
+
100
+ Matches `Date` instances only. Does not parse date strings or
101
+ accept numeric timestamps.
102
+
103
+ ```ts
104
+ isDate(new Date()); // true
105
+ isDate('2024-01-01'); // false
106
+ ```
107
+
108
+ ### `isEmptyArray`
109
+
110
+ ```ts
111
+ isEmptyArray(value: unknown): value is never[]
112
+ ```
113
+
114
+ ```ts
115
+ isEmptyArray([]); // true
116
+ isEmptyArray([1]); // false
117
+ ```
118
+
119
+ ### `isEmptyRecord`
120
+
121
+ ```ts
122
+ isEmptyRecord(value: unknown): value is EmptyObject
123
+ ```
124
+
125
+ ```ts
126
+ isEmptyRecord({}); // true
127
+ isEmptyRecord({ a: 1 }); // false
128
+ ```
129
+
130
+ ### `isEmptyString`
131
+
132
+ ```ts
133
+ isEmptyString(value: unknown): value is ''
134
+ ```
135
+
136
+ ```ts
137
+ isEmptyString(''); // true
138
+ isEmptyString('a'); // false
139
+ ```
140
+
141
+ ### `isError`
142
+
143
+ ```ts
144
+ isError(value: unknown): value is Error
145
+ ```
146
+
147
+ Matches subclasses of `Error` too (e.g. `TypeError`, custom errors).
148
+
149
+ ```ts
150
+ isError(new Error('boom')); // true
151
+ isError('boom'); // false
152
+ ```
153
+
154
+ ### `isFalsy`
155
+
156
+ ```ts
157
+ isFalsy(value: unknown): value is Falsy
158
+ ```
159
+
160
+ Returns true for `false`, `0`, `-0`, `0n`, `''`, `null`, `undefined`,
161
+ and `NaN`.
162
+
163
+ ```ts
164
+ isFalsy(0); // true
165
+ isFalsy(1); // false
166
+ ```
167
+
168
+ ### `isFiniteNumber`
169
+
170
+ ```ts
171
+ isFiniteNumber(value: unknown): value is number
172
+ ```
173
+
174
+ ```ts
175
+ isFiniteNumber(42); // true
176
+ isFiniteNumber(Infinity); // false
177
+ ```
178
+
179
+ ### `isFunction`
180
+
181
+ ```ts
182
+ isFunction(value: unknown): value is (...args: unknown[]) => unknown
183
+ ```
184
+
185
+ ```ts
186
+ isFunction(() => {}); // true
187
+ isFunction({}); // false
188
+ ```
189
+
190
+ ### `isHtmlButtonElement`
191
+
192
+ ```ts
193
+ isHtmlButtonElement(value: unknown): value is HTMLButtonElement
194
+ ```
195
+
196
+ ```ts
197
+ isHtmlButtonElement(document.createElement('button')); // true
198
+ isHtmlButtonElement(document.createElement('div')); // false
199
+ ```
200
+
201
+ ### `isHtmlElement`
202
+
203
+ ```ts
204
+ isHtmlElement(value: unknown): value is HTMLElement
205
+ ```
206
+
207
+ ```ts
208
+ isHtmlElement(document.body); // true
209
+ isHtmlElement('div'); // false
210
+ ```
211
+
212
+ ### `isHtmlInputElement`
213
+
214
+ ```ts
215
+ isHtmlInputElement(value: unknown): value is HTMLInputElement
216
+ ```
217
+
218
+ ```ts
219
+ isHtmlInputElement(document.createElement('input')); // true
220
+ isHtmlInputElement(document.createElement('div')); // false
221
+ ```
222
+
223
+ ### `isHtmlVideoElement`
224
+
225
+ ```ts
226
+ isHtmlVideoElement(value: unknown): value is HTMLVideoElement
227
+ ```
228
+
229
+ ```ts
230
+ isHtmlVideoElement(document.createElement('video')); // true
231
+ isHtmlVideoElement(document.createElement('div')); // false
232
+ ```
233
+
234
+ ### `isInteger`
235
+
236
+ ```ts
237
+ isInteger(value: unknown): value is number
238
+ ```
239
+
240
+ Uses `Number.isInteger`, so non-finite values (`NaN`, `Infinity`)
241
+ return false.
242
+
243
+ ```ts
244
+ isInteger(5); // true
245
+ isInteger(5.5); // false
246
+ ```
247
+
248
+ ### `isIterable`
249
+
250
+ ```ts
251
+ isIterable(value: unknown): value is Iterable<unknown>
252
+ ```
253
+
254
+ Strings are iterable and return true. Plain objects without
255
+ `Symbol.iterator` return false.
256
+
257
+ ```ts
258
+ isIterable([1, 2, 3]); // true
259
+ isIterable({}); // false
260
+ ```
261
+
262
+ ### `isMap`
263
+
264
+ ```ts
265
+ isMap(value: unknown): value is Map<unknown, unknown>
266
+ ```
267
+
268
+ WeakMap instances return false.
269
+
270
+ ```ts
271
+ isMap(new Map()); // true
272
+ isMap({}); // false
273
+ ```
274
+
275
+ ### `isMouseEvent`
276
+
277
+ ```ts
278
+ isMouseEvent(value: unknown): value is MouseEvent
279
+ ```
280
+
281
+ ```ts
282
+ isMouseEvent(new MouseEvent('click')); // true
283
+ isMouseEvent(new Event('change')); // false
284
+ ```
285
+
286
+ ### `isNaN`
287
+
288
+ ```ts
289
+ isNaN(value: unknown): value is number
290
+ ```
291
+
292
+ Uses `Number.isNaN` (not the coercive global `isNaN`), so only the
293
+ literal `NaN` value returns true.
294
+
295
+ ```ts
296
+ isNaN(Number.NaN); // true
297
+ isNaN(0); // false
298
+ ```
299
+
300
+ ### `isNil`
301
+
302
+ ```ts
303
+ isNil(value: unknown): value is Nil
304
+ ```
305
+
306
+ ```ts
307
+ isNil(null); // true
308
+ isNil(0); // false
309
+ ```
310
+
311
+ ### `isNonBlankString`
312
+
313
+ ```ts
314
+ isNonBlankString(value: unknown): value is string
315
+ ```
316
+
317
+ ```ts
318
+ isNonBlankString('a'); // true
319
+ isNonBlankString(' '); // false
320
+ ```
321
+
322
+ ### `isNonEmptyArray`
323
+
324
+ ```ts
325
+ isNonEmptyArray<T>(value: Nilable<T[] | UnknownRecord>): value is NonEmptyArray<T>
326
+ ```
327
+
328
+ ```ts
329
+ isNonEmptyArray([1]); // true
330
+ isNonEmptyArray([]); // false
331
+ ```
332
+
333
+ ### `isNonEmptyRecord`
334
+
335
+ ```ts
336
+ isNonEmptyRecord(value: unknown): value is Record<string, unknown>
337
+ ```
338
+
339
+ ```ts
340
+ isNonEmptyRecord({ a: 1 }); // true
341
+ isNonEmptyRecord({}); // false
342
+ ```
343
+
344
+ ### `isNonEmptyString`
345
+
346
+ ```ts
347
+ isNonEmptyString(value: unknown): value is string
348
+ ```
349
+
350
+ ```ts
351
+ isNonEmptyString('a'); // true
352
+ isNonEmptyString(''); // false
353
+ ```
354
+
355
+ ### `isNonNaNNumber`
356
+
357
+ ```ts
358
+ isNonNaNNumber(value: unknown): value is number
359
+ ```
360
+
361
+ ```ts
362
+ isNonNaNNumber(42); // true
363
+ isNonNaNNumber(Number.NaN); // false
364
+ ```
365
+
366
+ ### `isNotArray`
367
+
368
+ ```ts
369
+ isNotArray(value: unknown): value is Exclude<unknown, unknown[]>
370
+ ```
371
+
372
+ ```ts
373
+ isNotArray('x'); // true
374
+ isNotArray([]); // false
375
+ ```
376
+
377
+ ### `isNotBoolean`
378
+
379
+ ```ts
380
+ isNotBoolean(value: unknown): value is Exclude<unknown, boolean>
381
+ ```
382
+
383
+ ```ts
384
+ isNotBoolean(1); // true
385
+ isNotBoolean(true); // false
386
+ ```
387
+
388
+ ### `isNotFunction`
389
+
390
+ ```ts
391
+ isNotFunction(value: unknown): value is Exclude<unknown, (...args: unknown[]) => unknown>
392
+ ```
393
+
394
+ ```ts
395
+ isNotFunction(1); // true
396
+ isNotFunction(() => {}); // false
397
+ ```
398
+
399
+ ### `isNotNil`
400
+
401
+ ```ts
402
+ isNotNil<T>(value: Nil | T): value is T
403
+ ```
404
+
405
+ Common in `.filter(isNotNil)` to drop `null`/`undefined` from arrays
406
+ with narrowing.
407
+
408
+ ```ts
409
+ isNotNil(0); // true
410
+ isNotNil(null); // false
411
+
412
+ const items: (string | null)[] = ['a', null, 'b'];
413
+ const clean: string[] = items.filter(isNotNil); // ['a', 'b']
414
+ ```
415
+
416
+ ### `isNotNull`
417
+
418
+ ```ts
419
+ isNotNull<T>(value: unknown): value is Exclude<T, null>
420
+ ```
421
+
422
+ ```ts
423
+ isNotNull(undefined); // true
424
+ isNotNull(null); // false
425
+ ```
426
+
427
+ ### `isNotNumber`
428
+
429
+ ```ts
430
+ isNotNumber(value: unknown): value is Exclude<unknown, number>
431
+ ```
432
+
433
+ ```ts
434
+ isNotNumber('1'); // true
435
+ isNotNumber(1); // false
436
+ ```
437
+
438
+ ### `isNotRecord`
439
+
440
+ ```ts
441
+ isNotRecord(value: unknown): value is Exclude<unknown, Record<string, unknown>>
442
+ ```
443
+
444
+ ```ts
445
+ isNotRecord([]); // true
446
+ isNotRecord({}); // false
447
+ ```
448
+
449
+ ### `isNotString`
450
+
451
+ ```ts
452
+ isNotString(value: unknown): value is Exclude<unknown, string>
453
+ ```
454
+
455
+ ```ts
456
+ isNotString(1); // true
457
+ isNotString('a'); // false
458
+ ```
459
+
460
+ ### `isNotUndefined`
461
+
462
+ ```ts
463
+ isNotUndefined<T>(value: Undefinable<T>): value is Exclude<T, undefined>
464
+ ```
465
+
466
+ ```ts
467
+ isNotUndefined(null); // true
468
+ isNotUndefined(undefined); // false
469
+ ```
470
+
471
+ ### `isNotVoid`
472
+
473
+ ```ts
474
+ isNotVoid(value: unknown): value is Exclude<unknown, void>
475
+ ```
476
+
477
+ ```ts
478
+ isNotVoid(0); // true
479
+ isNotVoid(undefined); // false
480
+ ```
481
+
482
+ ### `isNull`
483
+
484
+ ```ts
485
+ isNull(value: unknown): value is null
486
+ ```
487
+
488
+ ```ts
489
+ isNull(null); // true
490
+ isNull(undefined); // false
491
+ ```
492
+
493
+ ### `isNumber`
494
+
495
+ ```ts
496
+ isNumber(value: unknown): value is number
497
+ ```
498
+
499
+ `NaN` is a `number`, so `isNumber(NaN)` is true. Use `isInteger` or
500
+ combine with `!isNaN(value)` if you need a finite number.
501
+
502
+ ```ts
503
+ isNumber(1); // true
504
+ isNumber('1'); // false
505
+ ```
506
+
507
+ ### `isPlainObject`
508
+
509
+ ```ts
510
+ isPlainObject(value: unknown): value is Record<string, unknown>
511
+ ```
512
+
513
+ ```ts
514
+ isPlainObject({}); // true
515
+ isPlainObject(new Date()); // false
516
+ ```
517
+
518
+ ### `isPromise`
519
+
520
+ ```ts
521
+ isPromise(value: unknown): value is Promise<unknown>
522
+ ```
523
+
524
+ The full `Promise` method surface is required so the narrowed type is
525
+ accurate: `then`, `catch`, and `finally` must all be functions. Thenables
526
+ that only implement a subset are rejected.
527
+
528
+ ```ts
529
+ isPromise(Promise.resolve()); // true
530
+ isPromise({ then: () => 1 }); // false
531
+ ```
532
+
533
+ ### `isRecord`
534
+
535
+ ```ts
536
+ isRecord(value: unknown): value is Record<string, unknown>
537
+ ```
538
+
539
+ ```ts
540
+ isRecord({}); // true
541
+ isRecord([]); // false
542
+ ```
543
+
544
+ ### `isRegExp`
545
+
546
+ ```ts
547
+ isRegExp(value: unknown): value is RegExp
548
+ ```
549
+
550
+ ```ts
551
+ isRegExp(/abc/); // true
552
+ isRegExp('/abc/'); // false
553
+ ```
554
+
555
+ ### `isSet`
556
+
557
+ ```ts
558
+ isSet(value: unknown): value is Set<unknown>
559
+ ```
560
+
561
+ WeakSet instances return false.
562
+
563
+ ```ts
564
+ isSet(new Set()); // true
565
+ isSet([]); // false
566
+ ```
567
+
568
+ ### `isString`
569
+
570
+ ```ts
571
+ isString(value: unknown): value is string
572
+ ```
573
+
574
+ ```ts
575
+ isString('hi'); // true
576
+ isString(42); // false
577
+ ```
578
+
579
+ ### `isSvgElement`
580
+
581
+ ```ts
582
+ isSvgElement(value: unknown): value is SVGElement
583
+ ```
584
+
585
+ ```ts
586
+ isSvgElement(document.createElementNS('http://www.w3.org/2000/svg', 'svg')); // true
587
+ isSvgElement(document.createElement('div')); // false
588
+ ```
589
+
590
+ ### `isSymbol`
591
+
592
+ ```ts
593
+ isSymbol(value: unknown): value is symbol
594
+ ```
595
+
596
+ ```ts
597
+ isSymbol(Symbol('a')); // true
598
+ isSymbol('symbol'); // false
599
+ ```
600
+
601
+ ### `isTextNode`
602
+
603
+ ```ts
604
+ isTextNode(value: unknown): value is Text
605
+ ```
606
+
607
+ ```ts
608
+ isTextNode(document.createTextNode('hi')); // true
609
+ isTextNode(document.createElement('div')); // false
610
+ ```
611
+
612
+ ### `isTruthy`
613
+
614
+ ```ts
615
+ isTruthy(value: unknown): boolean
616
+ ```
617
+
618
+ The complement of `isFalsy`. Does not provide narrowing on its own;
619
+ use `isNotNil` or a specific positive guard when narrowing matters.
620
+
621
+ ```ts
622
+ isTruthy(1); // true
623
+ isTruthy(0); // false
624
+ ```
625
+
626
+ ### `isUndefined`
627
+
628
+ ```ts
629
+ isUndefined(value: unknown): value is undefined
630
+ ```
631
+
632
+ ```ts
633
+ isUndefined(undefined); // true
634
+ isUndefined(null); // false
635
+ ```
636
+
637
+ ### `isVoid`
638
+
639
+ ```ts
640
+ isVoid(value: unknown): value is void
641
+ ```
642
+
643
+ ```ts
644
+ isVoid(undefined); // true
645
+ isVoid(0); // false
646
+ ```
647
+
648
+ ### `isWeakMap`
649
+
650
+ ```ts
651
+ isWeakMap(value: unknown): value is WeakMap<WeakKey, unknown>
652
+ ```
653
+
654
+ Map instances return false. Narrows to `WeakMap<WeakKey, unknown>`, since
655
+ weak collections accept non-registered symbols as keys (ES2023).
656
+
657
+ ```ts
658
+ isWeakMap(new WeakMap()); // true
659
+ isWeakMap(new Map()); // false
660
+ ```
661
+
662
+ ### `isWeakSet`
663
+
664
+ ```ts
665
+ isWeakSet(value: unknown): value is WeakSet<WeakKey>
666
+ ```
667
+
668
+ Set instances return false. Narrows to `WeakSet<WeakKey>`, since weak
669
+ collections accept non-registered symbols as members (ES2023).
670
+
671
+ ```ts
672
+ isWeakSet(new WeakSet()); // true
673
+ isWeakSet(new Set()); // false
674
+ ```
675
+
676
+ ### `readonlyArrayIncludes`
677
+
678
+ ```ts
679
+ readonlyArrayIncludes<T>(arr: readonly T[], elem: unknown): elem is T
680
+ ```
681
+
682
+ ```ts
683
+ const arr = ['a', 'b'] as const;
684
+ readonlyArrayIncludes(arr, 'a'); // true
685
+ readonlyArrayIncludes(arr, 'z'); // false
686
+ ```
687
+ <!-- AUTOGEN:LLMS-FULL-GUARDS:END -->
688
+
689
+ ## Types
690
+
691
+ <!-- AUTOGEN:LLMS-FULL-TYPES:START -->
692
+ ### `Arrayable<T>`
693
+
694
+ ```ts
695
+ type Arrayable<T> = T | T[];
696
+ type Tags = Arrayable<string>; // string | string[]
697
+ ```
698
+
699
+ ### `Falsy`
700
+
701
+ ```ts
702
+ type Falsy = typeof Number.NaN | '' | 0n | false | null | undefined;
703
+ type X = Falsy; // number | '' | 0n | false | null | undefined
704
+ ```
705
+
706
+ ### `NestedNonNullable<T, P extends Paths<T> & string>`
707
+
708
+ ```ts
709
+ type NestedNonNullable<T, P extends Paths<T> & string> = NonNullable<Get<T, P>>;
710
+ type User = { profile?: { name?: string } };
711
+ type Name = NestedNonNullable<User, 'profile.name'>; // string
712
+ ```
713
+
714
+ This type recursively traverses the object along the given path and applies `NonNullable` to the resulting type.
715
+ It may fail for excessively deep property paths or in the presence of circular references. In such cases, consider
716
+ using nested `NonNullable` definitions to avoid errors such as:
717
+ - TS2589: Type instantiation is excessively deep and possibly infinite.
718
+ - TS2615: Type of property 'XYZ' circularly references itself in mapped type.
719
+
720
+ ### `Nil`
721
+
722
+ ```ts
723
+ type Nil = null | undefined;
724
+ type X = Nil; // null | undefined
725
+ ```
726
+
727
+ ### `Nilable<A>`
728
+
729
+ ```ts
730
+ type Nilable<A> = A | Nil;
731
+ type Maybe = Nilable<string>; // string | null | undefined
732
+ ```
733
+
734
+ ### `NilableArray<T>`
735
+
736
+ ```ts
737
+ type NilableArray<T> = Nilable<Nilable<T>[]>;
738
+ type Items = NilableArray<string>; // (string | null | undefined)[] | null | undefined
739
+ ```
740
+
741
+ ### `NonEmptyArray<T>`
742
+
743
+ ```ts
744
+ type NonEmptyArray<T> = [T, ...T[]];
745
+ type Ids = NonEmptyArray<number>; // [number, ...number[]]
746
+ ```
747
+
748
+ ### `NotNilable<A>`
749
+
750
+ ```ts
751
+ type NotNilable<A> = Exclude<A, Nil>;
752
+ type X = NotNilable<string | null | undefined>; // string
753
+ ```
754
+
755
+ ### `Nullable<A>`
756
+
757
+ ```ts
758
+ type Nullable<A> = A | null;
759
+ type X = Nullable<string>; // string | null
760
+ ```
761
+
762
+ ### `NullableProperties<T extends UnknownRecord>`
763
+
764
+ ```ts
765
+ type NullableProperties<T extends UnknownRecord> = { [Key in keyof T]: Nullable<T[Key]> };
766
+ type User = { name: string; age: number };
767
+ type X = NullableProperties<User>; // { name: string | null; age: number | null }
768
+ ```
769
+
770
+ ### `Truthy<T>`
771
+
772
+ ```ts
773
+ type Truthy<T> = Exclude<T, Falsy>;
774
+ type X = Truthy<string | 0 | null>; // string
775
+ ```
776
+
777
+ ### `Undefinable<A>`
778
+
779
+ ```ts
780
+ type Undefinable<A> = A | undefined;
781
+ type X = Undefinable<string>; // string | undefined
782
+ ```
783
+ <!-- AUTOGEN:LLMS-FULL-TYPES:END -->
784
+
785
+ ## Utilities
786
+
787
+ <!-- AUTOGEN:LLMS-FULL-UTILITIES:START -->
788
+ ### `asyncNoOpFn`
789
+
790
+ ```ts
791
+ asyncNoOpFn(): Promise<void>
792
+ ```
793
+
794
+ ```ts
795
+ await asyncNoOpFn(); // undefined
796
+ ```
797
+
798
+ ### `buildTimeDuration`
799
+
800
+ ```ts
801
+ buildTimeDuration(numberOfMilliseconds: number): TimeDuration
802
+ ```
803
+
804
+ ```ts
805
+ buildTimeDuration(3723000); // { hours: 1, minutes: 2, seconds: 3 }
806
+ ```
807
+
808
+ ### `coerceToBoolean`
809
+
810
+ ```ts
811
+ coerceToBoolean(value: unknown): boolean
812
+ ```
813
+
814
+ ```ts
815
+ coerceToBoolean('true'); // true
816
+ coerceToBoolean('false'); // false
817
+ coerceToBoolean(0); // false
818
+ ```
819
+
820
+ ### `dateOnlyISOString`
821
+
822
+ ```ts
823
+ dateOnlyISOString(date: unknown, { timeZone = defaultTimeZone }: DateOnlyStringOptions = {}): string
824
+ ```
825
+
826
+ ```ts
827
+ dateOnlyISOString(new Date(2011, 11, 15)); // '2011-12-15'
828
+ ```
829
+
830
+ ### `dateOnlyString`
831
+
832
+ ```ts
833
+ dateOnlyString(
834
+ date: unknown,
835
+ { timeZone = defaultTimeZone, omitYear = false }: DateOnlyStringOptions = {},
836
+ ): string
837
+ ```
838
+
839
+ ```ts
840
+ dateOnlyString(new Date(2011, 11, 15)); // 'Dec 15, 2011'
841
+ ```
842
+
843
+ ### `dateOnlyStringForSentence`
844
+
845
+ ```ts
846
+ dateOnlyStringForSentence(
847
+ date: unknown,
848
+ { timeZone = defaultTimeZone }: DateOnlyStringOptions = {},
849
+ ): string
850
+ ```
851
+
852
+ ```ts
853
+ dateOnlyStringForSentence(new Date(2011, 11, 15)); // 'December 15, 2011'
854
+ ```
855
+
856
+ ### `dateOnlyStringNumeric`
857
+
858
+ ```ts
859
+ dateOnlyStringNumeric(
860
+ date: unknown,
861
+ { timeZone = defaultTimeZone }: DateOnlyStringOptions = {},
862
+ ): string
863
+ ```
864
+
865
+ ```ts
866
+ dateOnlyStringNumeric(new Date(2011, 11, 15)); // '12/15/2011'
867
+ ```
868
+
869
+ ### `dateTimeRounded`
870
+
871
+ ```ts
872
+ dateTimeRounded(dateTime: Date, toISOString = true): string | Date | null
873
+ ```
874
+
875
+ ```ts
876
+ dateTimeRounded(new Date(2015, 10, 7, 0, 10)); // '2015-11-07T00:30:00.000Z' (in UTC)
877
+ dateTimeRounded(new Date(2015, 10, 7, 0, 40), false); // new Date(2015, 10, 7, 1, 0)
878
+ ```
879
+
880
+ ### `dateTimeString`
881
+
882
+ ```ts
883
+ dateTimeString(
884
+ date: Date | number | string | null | undefined,
885
+ { timeZone = defaultTimeZone, omitYear = false }: DateOnlyStringOptions = {},
886
+ ): string
887
+ ```
888
+
889
+ ```ts
890
+ dateTimeString(new Date(2011, 11, 15, 7, 30)); // 'Dec 15, 2011, 7:30 AM'
891
+ ```
892
+
893
+ ### `dateTimeStringForSentence`
894
+
895
+ ```ts
896
+ dateTimeStringForSentence(
897
+ date: unknown,
898
+ { timeZone = defaultTimeZone }: DateOnlyStringOptions = {},
899
+ ): string
900
+ ```
901
+
902
+ ```ts
903
+ dateTimeStringForSentence(new Date(2011, 11, 15, 7, 30)); // 'December 15, 2011, 7:30 AM'
904
+ ```
905
+
906
+ ### `dateTimeToDate`
907
+
908
+ ```ts
909
+ dateTimeToDate(dateTime: Partial<WistiaDateTimeObject> | null | undefined): Date | null
910
+ ```
911
+
912
+ ```ts
913
+ dateTimeToDate({ year: 2015, month: 10, dayOfMonth: 7, hours: 4, minutes: 15 });
914
+ // new Date(2015, 10, 7, 4, 15)
915
+ ```
916
+
917
+ ### `dateTimeToISO`
918
+
919
+ ```ts
920
+ dateTimeToISO(dateTime: Partial<WistiaDateTimeObject>): string | null
921
+ ```
922
+
923
+ ```ts
924
+ dateTimeToISO({ year: 2015, month: 10, dayOfMonth: 7, hours: 4, minutes: 15 });
925
+ // '2015-11-07T04:15:00.000Z' (in UTC)
926
+ ```
927
+
928
+ ### `dateToDateTime`
929
+
930
+ ```ts
931
+ dateToDateTime(date: Date | null | undefined): WistiaDateTimeObject | null
932
+ ```
933
+
934
+ ```ts
935
+ dateToDateTime(new Date(2011, 11, 15, 3, 10));
936
+ // { year: 2011, month: 11, dayOfMonth: 15, hours: 3, minutes: 10 }
937
+ ```
938
+
939
+ ### `dateUTCOffset`
940
+
941
+ ```ts
942
+ dateUTCOffset(date: Date): string
943
+ ```
944
+
945
+ ```ts
946
+ dateUTCOffset(new Date(2011, 11, 15)); // '+00:00' (when run in UTC)
947
+ ```
948
+
949
+ ### `dayOfWeekString`
950
+
951
+ ```ts
952
+ dayOfWeekString(
953
+ date: Date | null,
954
+ { timeZone = defaultTimeZone }: DateOnlyStringOptions = {},
955
+ ): string
956
+ ```
957
+
958
+ ```ts
959
+ dayOfWeekString(new Date(2011, 11, 15)); // 'Thursday'
960
+ ```
961
+
962
+ ### `deepMerge`
963
+
964
+ ```ts
965
+ deepMerge<T extends Record<string, unknown>[]>(...objects: T): DeepMergeResult<T>
966
+ ```
967
+
968
+ ```ts
969
+ deepMerge({ a: { b: 1 }, c: [1] }, { a: { d: 2 }, c: [2] });
970
+ // { a: { b: 1, d: 2 }, c: [1, 2] }
971
+ ```
972
+
973
+ ### `getObjectEntries`
974
+
975
+ ```ts
976
+ getObjectEntries<T extends Record<string, unknown>>(obj: T): [keyof T, T[keyof T]][]
977
+ ```
978
+
979
+ ```ts
980
+ getObjectEntries({ a: 1, b: 2 }); // [['a', 1], ['b', 2]]
981
+ ```
982
+
983
+ ### `getObjectKeys`
984
+
985
+ ```ts
986
+ getObjectKeys<T extends Record<string, unknown>>(obj: T): (keyof T)[]
987
+ ```
988
+
989
+ ```ts
990
+ getObjectKeys({ a: 1, b: 2 }); // ['a', 'b']
991
+ ```
992
+
993
+ ### `getObjectValues`
994
+
995
+ ```ts
996
+ getObjectValues<T extends Record<string, unknown>>(obj: T): T[keyof T][]
997
+ ```
998
+
999
+ ```ts
1000
+ getObjectValues({ a: 1, b: 2 }); // [1, 2]
1001
+ ```
1002
+
1003
+ ### `isEmail`
1004
+
1005
+ ```ts
1006
+ isEmail(value: string): boolean
1007
+ ```
1008
+
1009
+ ```ts
1010
+ isEmail('user@example.com'); // true
1011
+ isEmail('user@example'); // false
1012
+ ```
1013
+
1014
+ ### `isUrl`
1015
+
1016
+ ```ts
1017
+ isUrl(value: unknown): boolean
1018
+ ```
1019
+
1020
+ ```ts
1021
+ isUrl('https://wistia.com'); // true
1022
+ isUrl('not a url'); // false
1023
+ ```
1024
+
1025
+ ### `mediaDurationString`
1026
+
1027
+ ```ts
1028
+ mediaDurationString(numberOfMilliseconds: number): string
1029
+ ```
1030
+
1031
+ ```ts
1032
+ mediaDurationString(234230); // '3:54'
1033
+ mediaDurationString(23423000); // '6:30:23'
1034
+ ```
1035
+
1036
+ ### `millisecondsToDurationISOString`
1037
+
1038
+ ```ts
1039
+ millisecondsToDurationISOString(numberOfMilliseconds: number): string
1040
+ ```
1041
+
1042
+ ```ts
1043
+ millisecondsToDurationISOString(33000); // 'PT33S'
1044
+ millisecondsToDurationISOString(3605000); // 'PT1H5S'
1045
+ ```
1046
+
1047
+ ### `monthDayStringNumeric`
1048
+
1049
+ ```ts
1050
+ monthDayStringNumeric(
1051
+ date: unknown,
1052
+ { timeZone = defaultTimeZone }: DateOnlyStringOptions = {},
1053
+ ): string
1054
+ ```
1055
+
1056
+ ```ts
1057
+ monthDayStringNumeric(new Date(2011, 11, 15)); // '12/15'
1058
+ ```
1059
+
1060
+ ### `noOpFn`
1061
+
1062
+ ```ts
1063
+ noOpFn(): void
1064
+ ```
1065
+
1066
+ ```ts
1067
+ noOpFn(); // undefined
1068
+ ```
1069
+
1070
+ ### `noOpFnReturningFalse`
1071
+
1072
+ ```ts
1073
+ noOpFnReturningFalse(): false
1074
+ ```
1075
+
1076
+ ```ts
1077
+ noOpFnReturningFalse(); // false
1078
+ ```
1079
+
1080
+ ### `noOpFnReturningNull`
1081
+
1082
+ ```ts
1083
+ noOpFnReturningNull(): null
1084
+ ```
1085
+
1086
+ ```ts
1087
+ noOpFnReturningNull(); // null
1088
+ ```
1089
+
1090
+ ### `parseDateString`
1091
+
1092
+ ```ts
1093
+ parseDateString(str: string): Date | null
1094
+ ```
1095
+
1096
+ ```ts
1097
+ parseDateString('Jan 5, 2025'); // Date for 2025-01-05 at local midnight
1098
+ parseDateString('garbage'); // null
1099
+ ```
1100
+
1101
+ ### `sessionDurationString`
1102
+
1103
+ ```ts
1104
+ sessionDurationString(numberOfMilliseconds: number): string
1105
+ ```
1106
+
1107
+ ```ts
1108
+ sessionDurationString(2342300); // '0:39:02'
1109
+ ```
1110
+
1111
+ ### `stripExtension`
1112
+
1113
+ ```ts
1114
+ stripExtension(str: string): string
1115
+ ```
1116
+
1117
+ ```ts
1118
+ stripExtension('config.yml'); // 'config'
1119
+ stripExtension('example.test.ts'); // 'example.test'
1120
+ ```
1121
+
1122
+ ### `timeAgoString`
1123
+
1124
+ ```ts
1125
+ timeAgoString(
1126
+ date: Date,
1127
+ { nowAnchor = new Date(), includeTime = true }: TimeAgoOptions = {},
1128
+ ): string
1129
+ ```
1130
+
1131
+ ```ts
1132
+ timeAgoString(new Date(Date.now() - 30_000)); // '< 1 minute ago'
1133
+ ```
1134
+
1135
+ ### `timeOnlyString`
1136
+
1137
+ ```ts
1138
+ timeOnlyString(date: unknown, { timeZone = defaultTimeZone }: DateOnlyStringOptions = {}): string
1139
+ ```
1140
+
1141
+ ```ts
1142
+ timeOnlyString(new Date(2011, 11, 15, 7, 30)); // '7:30 AM'
1143
+ ```
1144
+
1145
+ ### `tupleIncludes`
1146
+
1147
+ ```ts
1148
+ tupleIncludes<TTuple extends readonly TItem[], TItem>(
1149
+ tuple: TTuple,
1150
+ item: TItem,
1151
+ ): item is TTuple[number]
1152
+ ```
1153
+
1154
+ ```ts
1155
+ const tuple = ['x', 'y'] as const;
1156
+ tupleIncludes(tuple, 'x'); // true
1157
+ tupleIncludes(tuple, 'z'); // false
1158
+ ```
1159
+ <!-- AUTOGEN:LLMS-FULL-UTILITIES:END -->
1160
+
1161
+ ## Patterns
1162
+
1163
+ Filter out nullish values from a list, with narrowing:
1164
+
1165
+ ```ts
1166
+ import { isNotNil } from '@wistia/utils';
1167
+
1168
+ const raw: (string | null | undefined)[] = ['a', null, 'b', undefined];
1169
+ const clean: string[] = raw.filter(isNotNil); // ['a', 'b']
1170
+ ```
1171
+
1172
+ Narrow a parameter before using it:
1173
+
1174
+ ```ts
1175
+ import { isString, isNonEmptyArray } from '@wistia/utils';
1176
+
1177
+ function describe(value: unknown): string {
1178
+ if (isString(value)) return value.toUpperCase();
1179
+ if (isNonEmptyArray<unknown>(value)) return `${value.length} items`;
1180
+ return 'other';
1181
+ }
1182
+ ```
1183
+
1184
+ Safely access an unknown object key:
1185
+
1186
+ ```ts
1187
+ import { hasKey } from '@wistia/utils';
1188
+
1189
+ function readName(value: unknown): string | undefined {
1190
+ if (hasKey<'name', string>(value, 'name') && typeof value.name === 'string') {
1191
+ return value.name;
1192
+ }
1193
+ return undefined;
1194
+ }
1195
+ ```