ts-data-forge 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.
Files changed (143) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +534 -0
  3. package/package.json +101 -0
  4. package/src/array/array-utils-creation.test.mts +443 -0
  5. package/src/array/array-utils-modification.test.mts +197 -0
  6. package/src/array/array-utils-overload-type-error.test.mts +149 -0
  7. package/src/array/array-utils-reducing-value.test.mts +425 -0
  8. package/src/array/array-utils-search.test.mts +169 -0
  9. package/src/array/array-utils-set-op.test.mts +335 -0
  10. package/src/array/array-utils-slice-clamped.test.mts +113 -0
  11. package/src/array/array-utils-slicing.test.mts +316 -0
  12. package/src/array/array-utils-transformation.test.mts +790 -0
  13. package/src/array/array-utils-validation.test.mts +492 -0
  14. package/src/array/array-utils.mts +4000 -0
  15. package/src/array/array.test.mts +146 -0
  16. package/src/array/index.mts +2 -0
  17. package/src/array/tuple-utils.mts +519 -0
  18. package/src/array/tuple-utils.test.mts +518 -0
  19. package/src/collections/imap-mapped.mts +801 -0
  20. package/src/collections/imap-mapped.test.mts +860 -0
  21. package/src/collections/imap.mts +651 -0
  22. package/src/collections/imap.test.mts +932 -0
  23. package/src/collections/index.mts +6 -0
  24. package/src/collections/iset-mapped.mts +889 -0
  25. package/src/collections/iset-mapped.test.mts +1187 -0
  26. package/src/collections/iset.mts +682 -0
  27. package/src/collections/iset.test.mts +1084 -0
  28. package/src/collections/queue.mts +390 -0
  29. package/src/collections/queue.test.mts +282 -0
  30. package/src/collections/stack.mts +423 -0
  31. package/src/collections/stack.test.mts +225 -0
  32. package/src/expect-type.mts +206 -0
  33. package/src/functional/index.mts +4 -0
  34. package/src/functional/match.mts +300 -0
  35. package/src/functional/match.test.mts +177 -0
  36. package/src/functional/optional.mts +733 -0
  37. package/src/functional/optional.test.mts +619 -0
  38. package/src/functional/pipe.mts +212 -0
  39. package/src/functional/pipe.test.mts +85 -0
  40. package/src/functional/result.mts +1134 -0
  41. package/src/functional/result.test.mts +777 -0
  42. package/src/globals.d.mts +38 -0
  43. package/src/guard/has-key.mts +119 -0
  44. package/src/guard/has-key.test.mts +219 -0
  45. package/src/guard/index.mts +7 -0
  46. package/src/guard/is-non-empty-string.mts +108 -0
  47. package/src/guard/is-non-empty-string.test.mts +91 -0
  48. package/src/guard/is-non-null-object.mts +106 -0
  49. package/src/guard/is-non-null-object.test.mts +90 -0
  50. package/src/guard/is-primitive.mts +165 -0
  51. package/src/guard/is-primitive.test.mts +102 -0
  52. package/src/guard/is-record.mts +153 -0
  53. package/src/guard/is-record.test.mts +112 -0
  54. package/src/guard/is-type.mts +450 -0
  55. package/src/guard/is-type.test.mts +496 -0
  56. package/src/guard/key-is-in.mts +163 -0
  57. package/src/guard/key-is-in.test.mts +19 -0
  58. package/src/index.mts +10 -0
  59. package/src/iterator/index.mts +1 -0
  60. package/src/iterator/range.mts +120 -0
  61. package/src/iterator/range.test.mts +33 -0
  62. package/src/json/index.mts +1 -0
  63. package/src/json/json.mts +711 -0
  64. package/src/json/json.test.mts +628 -0
  65. package/src/number/branded-types/finite-number.mts +354 -0
  66. package/src/number/branded-types/finite-number.test.mts +135 -0
  67. package/src/number/branded-types/index.mts +26 -0
  68. package/src/number/branded-types/int.mts +278 -0
  69. package/src/number/branded-types/int.test.mts +140 -0
  70. package/src/number/branded-types/int16.mts +192 -0
  71. package/src/number/branded-types/int16.test.mts +170 -0
  72. package/src/number/branded-types/int32.mts +193 -0
  73. package/src/number/branded-types/int32.test.mts +170 -0
  74. package/src/number/branded-types/non-negative-finite-number.mts +223 -0
  75. package/src/number/branded-types/non-negative-finite-number.test.mts +188 -0
  76. package/src/number/branded-types/non-negative-int16.mts +187 -0
  77. package/src/number/branded-types/non-negative-int16.test.mts +201 -0
  78. package/src/number/branded-types/non-negative-int32.mts +187 -0
  79. package/src/number/branded-types/non-negative-int32.test.mts +204 -0
  80. package/src/number/branded-types/non-zero-finite-number.mts +229 -0
  81. package/src/number/branded-types/non-zero-finite-number.test.mts +198 -0
  82. package/src/number/branded-types/non-zero-int.mts +167 -0
  83. package/src/number/branded-types/non-zero-int.test.mts +177 -0
  84. package/src/number/branded-types/non-zero-int16.mts +196 -0
  85. package/src/number/branded-types/non-zero-int16.test.mts +195 -0
  86. package/src/number/branded-types/non-zero-int32.mts +196 -0
  87. package/src/number/branded-types/non-zero-int32.test.mts +197 -0
  88. package/src/number/branded-types/non-zero-safe-int.mts +196 -0
  89. package/src/number/branded-types/non-zero-safe-int.test.mts +232 -0
  90. package/src/number/branded-types/non-zero-uint16.mts +189 -0
  91. package/src/number/branded-types/non-zero-uint16.test.mts +199 -0
  92. package/src/number/branded-types/non-zero-uint32.mts +189 -0
  93. package/src/number/branded-types/non-zero-uint32.test.mts +199 -0
  94. package/src/number/branded-types/positive-finite-number.mts +241 -0
  95. package/src/number/branded-types/positive-finite-number.test.mts +204 -0
  96. package/src/number/branded-types/positive-int.mts +304 -0
  97. package/src/number/branded-types/positive-int.test.mts +176 -0
  98. package/src/number/branded-types/positive-int16.mts +188 -0
  99. package/src/number/branded-types/positive-int16.test.mts +197 -0
  100. package/src/number/branded-types/positive-int32.mts +188 -0
  101. package/src/number/branded-types/positive-int32.test.mts +197 -0
  102. package/src/number/branded-types/positive-safe-int.mts +187 -0
  103. package/src/number/branded-types/positive-safe-int.test.mts +210 -0
  104. package/src/number/branded-types/positive-uint16.mts +188 -0
  105. package/src/number/branded-types/positive-uint16.test.mts +203 -0
  106. package/src/number/branded-types/positive-uint32.mts +188 -0
  107. package/src/number/branded-types/positive-uint32.test.mts +203 -0
  108. package/src/number/branded-types/safe-int.mts +291 -0
  109. package/src/number/branded-types/safe-int.test.mts +170 -0
  110. package/src/number/branded-types/safe-uint.mts +187 -0
  111. package/src/number/branded-types/safe-uint.test.mts +176 -0
  112. package/src/number/branded-types/uint.mts +179 -0
  113. package/src/number/branded-types/uint.test.mts +158 -0
  114. package/src/number/branded-types/uint16.mts +186 -0
  115. package/src/number/branded-types/uint16.test.mts +170 -0
  116. package/src/number/branded-types/uint32.mts +218 -0
  117. package/src/number/branded-types/uint32.test.mts +170 -0
  118. package/src/number/enum/index.mts +2 -0
  119. package/src/number/enum/int8.mts +344 -0
  120. package/src/number/enum/int8.test.mts +180 -0
  121. package/src/number/enum/uint8.mts +293 -0
  122. package/src/number/enum/uint8.test.mts +164 -0
  123. package/src/number/index.mts +4 -0
  124. package/src/number/num.mts +604 -0
  125. package/src/number/num.test.mts +242 -0
  126. package/src/number/refined-number-utils.mts +566 -0
  127. package/src/object/index.mts +1 -0
  128. package/src/object/object.mts +447 -0
  129. package/src/object/object.test.mts +124 -0
  130. package/src/others/cast-mutable.mts +113 -0
  131. package/src/others/cast-readonly.mts +192 -0
  132. package/src/others/cast-readonly.test.mts +89 -0
  133. package/src/others/if-then.mts +98 -0
  134. package/src/others/if-then.test.mts +75 -0
  135. package/src/others/index.mts +7 -0
  136. package/src/others/map-nullable.mts +172 -0
  137. package/src/others/map-nullable.test.mts +297 -0
  138. package/src/others/memoize-function.mts +196 -0
  139. package/src/others/memoize-function.test.mts +168 -0
  140. package/src/others/tuple.mts +160 -0
  141. package/src/others/tuple.test.mts +11 -0
  142. package/src/others/unknown-to-string.mts +215 -0
  143. package/src/others/unknown-to-string.test.mts +114 -0
@@ -0,0 +1,496 @@
1
+ import { expectType } from '../expect-type.mjs';
2
+ import {
3
+ isBigint,
4
+ isBoolean,
5
+ isNonNullish,
6
+ isNotBigint,
7
+ isNotBoolean,
8
+ isNotNull,
9
+ isNotNumber,
10
+ isNotString,
11
+ isNotSymbol,
12
+ isNotUndefined,
13
+ isNull,
14
+ isNullish,
15
+ isNumber,
16
+ isString,
17
+ isSymbol,
18
+ isUndefined,
19
+ } from './is-type.mjs';
20
+
21
+ describe('isUndefined', () => {
22
+ test('should return true for undefined', () => {
23
+ expect(isUndefined(undefined)).toBe(true);
24
+ });
25
+
26
+ test('should return false for non-undefined values', () => {
27
+ expect(isUndefined(null)).toBe(false);
28
+ expect(isUndefined(0)).toBe(false);
29
+ expect(isUndefined('')).toBe(false);
30
+ expect(isUndefined(false)).toBe(false);
31
+ expect(isUndefined({})).toBe(false);
32
+ });
33
+
34
+ test('should act as a type guard', () => {
35
+ const value: string | undefined = undefined;
36
+ if (isUndefined(value)) {
37
+ expectType<typeof value, undefined>('=');
38
+ }
39
+ });
40
+ });
41
+
42
+ describe('isNotUndefined', () => {
43
+ test('should return false for undefined', () => {
44
+ expect(isNotUndefined(undefined)).toBe(false);
45
+ });
46
+
47
+ test('should return true for non-undefined values', () => {
48
+ expect(isNotUndefined(null)).toBe(true);
49
+ expect(isNotUndefined(0)).toBe(true);
50
+ expect(isNotUndefined('')).toBe(true);
51
+ expect(isNotUndefined(false)).toBe(true);
52
+ });
53
+
54
+ test('should narrow types correctly', () => {
55
+ const value: string | undefined = 'test';
56
+ if (isNotUndefined(value)) {
57
+ expectType<typeof value, string>('=');
58
+ }
59
+ });
60
+ });
61
+
62
+ describe('isNull', () => {
63
+ test('should return true for null', () => {
64
+ expect(isNull(null)).toBe(true);
65
+ });
66
+
67
+ test('should return false for non-null values', () => {
68
+ expect(isNull(undefined)).toBe(false);
69
+ expect(isNull(0)).toBe(false);
70
+ expect(isNull('')).toBe(false);
71
+ expect(isNull(false)).toBe(false);
72
+ expect(isNull({})).toBe(false);
73
+ });
74
+
75
+ test('should act as a type guard', () => {
76
+ const value: string | null = null;
77
+ if (isNull(value)) {
78
+ expectType<typeof value, null>('=');
79
+ }
80
+ });
81
+ });
82
+
83
+ describe('isNotNull', () => {
84
+ test('should return false for null', () => {
85
+ expect(isNotNull(null)).toBe(false);
86
+ });
87
+
88
+ test('should return true for non-null values', () => {
89
+ expect(isNotNull(undefined)).toBe(true);
90
+ expect(isNotNull(0)).toBe(true);
91
+ expect(isNotNull('')).toBe(true);
92
+ });
93
+
94
+ test('should narrow types correctly', () => {
95
+ const value: string | null = 'test';
96
+ if (isNotNull(value)) {
97
+ expectType<typeof value, string>('=');
98
+ }
99
+ });
100
+ });
101
+
102
+ describe('isString', () => {
103
+ test('should return true for strings', () => {
104
+ expect(isString('')).toBe(true);
105
+ expect(isString('hello')).toBe(true);
106
+ expect(isString('123')).toBe(true);
107
+ expect(isString(`template`)).toBe(true);
108
+ });
109
+
110
+ test('should return false for non-strings', () => {
111
+ expect(isString(123)).toBe(false);
112
+ expect(isString(true)).toBe(false);
113
+ expect(isString(null)).toBe(false);
114
+ expect(isString(undefined)).toBe(false);
115
+ // eslint-disable-next-line no-new-wrappers
116
+ expect(isString(new String('hello'))).toBe(false);
117
+ });
118
+
119
+ test('should act as a type guard', () => {
120
+ const value: unknown = 'test';
121
+ if (isString(value)) {
122
+ expectType<typeof value, string>('=');
123
+ expect(value.length).toBe(4);
124
+ }
125
+ });
126
+ });
127
+
128
+ describe('isNumber', () => {
129
+ test('should return true for numbers', () => {
130
+ expect(isNumber(0)).toBe(true);
131
+ expect(isNumber(42)).toBe(true);
132
+ expect(isNumber(-3.14)).toBe(true);
133
+ expect(isNumber(Number.NaN)).toBe(true);
134
+ expect(isNumber(Number.POSITIVE_INFINITY)).toBe(true);
135
+ expect(isNumber(Number.NEGATIVE_INFINITY)).toBe(true);
136
+ });
137
+
138
+ test('should return false for non-numbers', () => {
139
+ expect(isNumber('123')).toBe(false);
140
+ expect(isNumber(true)).toBe(false);
141
+ expect(isNumber(null)).toBe(false);
142
+ expect(isNumber(BigInt(123))).toBe(false);
143
+ // eslint-disable-next-line no-new-wrappers
144
+ expect(isNumber(new Number(42))).toBe(false);
145
+ });
146
+
147
+ test('should act as a type guard', () => {
148
+ const value: unknown = 42;
149
+ if (isNumber(value)) {
150
+ expectType<typeof value, number>('=');
151
+ expect(value + 1).toBe(43);
152
+ }
153
+ });
154
+ });
155
+
156
+ describe('isBigint', () => {
157
+ test('should return true for bigints', () => {
158
+ expect(isBigint(BigInt(0))).toBe(true);
159
+ expect(isBigint(123n)).toBe(true);
160
+ expect(isBigint(-456n)).toBe(true);
161
+ });
162
+
163
+ test('should return false for non-bigints', () => {
164
+ expect(isBigint(123)).toBe(false);
165
+ expect(isBigint('123')).toBe(false);
166
+ expect(isBigint(true)).toBe(false);
167
+ expect(isBigint(null)).toBe(false);
168
+ });
169
+
170
+ test('should act as a type guard', () => {
171
+ const value: unknown = 123n;
172
+ if (isBigint(value)) {
173
+ expectType<typeof value, bigint>('=');
174
+ expect(value + 1n).toBe(124n);
175
+ }
176
+ });
177
+ });
178
+
179
+ describe('isBoolean', () => {
180
+ test('should return true for booleans', () => {
181
+ expect(isBoolean(true)).toBe(true);
182
+ expect(isBoolean(false)).toBe(true);
183
+ });
184
+
185
+ test('should return false for non-booleans', () => {
186
+ expect(isBoolean(1)).toBe(false);
187
+ expect(isBoolean(0)).toBe(false);
188
+ expect(isBoolean('true')).toBe(false);
189
+ expect(isBoolean(null)).toBe(false);
190
+ // eslint-disable-next-line no-new-wrappers
191
+ expect(isBoolean(new Boolean(true))).toBe(false);
192
+ });
193
+
194
+ test('should act as a type guard', () => {
195
+ const value: unknown = true;
196
+ if (isBoolean(value)) {
197
+ expectType<typeof value, boolean>('=');
198
+ expect(!value).toBe(false);
199
+ }
200
+ });
201
+ });
202
+
203
+ describe('isSymbol', () => {
204
+ test('should return true for symbols', () => {
205
+ expect(isSymbol(Symbol())).toBe(true);
206
+ expect(isSymbol(Symbol('test'))).toBe(true);
207
+ expect(isSymbol(Symbol.iterator)).toBe(true);
208
+ });
209
+
210
+ test('should return false for non-symbols', () => {
211
+ expect(isSymbol('symbol')).toBe(false);
212
+ expect(isSymbol(123)).toBe(false);
213
+ expect(isSymbol(null)).toBe(false);
214
+ });
215
+
216
+ test('should act as a type guard', () => {
217
+ const value: unknown = Symbol('test');
218
+ if (isSymbol(value)) {
219
+ expectType<typeof value, symbol>('=');
220
+ expect(value.toString()).toContain('Symbol');
221
+ }
222
+ });
223
+ });
224
+
225
+ describe('isNotBoolean', () => {
226
+ test('should return false for boolean values', () => {
227
+ expect(isNotBoolean(true)).toBe(false);
228
+ expect(isNotBoolean(false)).toBe(false);
229
+ });
230
+
231
+ test('should return true for non-boolean values', () => {
232
+ expect(isNotBoolean(0)).toBe(true);
233
+ expect(isNotBoolean(1)).toBe(true);
234
+ expect(isNotBoolean('true')).toBe(true);
235
+ expect(isNotBoolean('false')).toBe(true);
236
+ expect(isNotBoolean(null)).toBe(true);
237
+ expect(isNotBoolean(undefined)).toBe(true);
238
+ expect(isNotBoolean({})).toBe(true);
239
+ expect(isNotBoolean([])).toBe(true);
240
+ });
241
+
242
+ test('should act as a type guard', () => {
243
+ const value: string | number | boolean = 'test';
244
+ if (isNotBoolean(value)) {
245
+ expectType<typeof value, string | number>('<=');
246
+ // Should not have boolean methods
247
+ expect(typeof value === 'string' || typeof value === 'number').toBe(true);
248
+ }
249
+ });
250
+ });
251
+
252
+ describe('isNotNumber', () => {
253
+ test('should return false for number values', () => {
254
+ expect(isNotNumber(0)).toBe(false);
255
+ expect(isNotNumber(42)).toBe(false);
256
+ expect(isNotNumber(-3.14)).toBe(false);
257
+ expect(isNotNumber(Number.NaN)).toBe(false);
258
+ expect(isNotNumber(Number.POSITIVE_INFINITY)).toBe(false);
259
+ expect(isNotNumber(Number.NEGATIVE_INFINITY)).toBe(false);
260
+ });
261
+
262
+ test('should return true for non-number values', () => {
263
+ expect(isNotNumber('123')).toBe(true);
264
+ expect(isNotNumber(true)).toBe(true);
265
+ expect(isNotNumber(false)).toBe(true);
266
+ expect(isNotNumber(null)).toBe(true);
267
+ expect(isNotNumber(undefined)).toBe(true);
268
+ expect(isNotNumber(BigInt(123))).toBe(true);
269
+ expect(isNotNumber({})).toBe(true);
270
+ expect(isNotNumber([])).toBe(true);
271
+ expect(isNotNumber(Symbol('test'))).toBe(true);
272
+ });
273
+
274
+ test('should act as a type guard', () => {
275
+ const value: string | number | boolean = 'test';
276
+ if (isNotNumber(value)) {
277
+ expectType<typeof value, string | boolean>('<=');
278
+ expect(typeof value === 'string' || typeof value === 'boolean').toBe(
279
+ true,
280
+ );
281
+ }
282
+ });
283
+ });
284
+
285
+ describe('isNotBigint', () => {
286
+ test('should return false for bigint values', () => {
287
+ expect(isNotBigint(BigInt(0))).toBe(false);
288
+ expect(isNotBigint(123n)).toBe(false);
289
+ expect(isNotBigint(-456n)).toBe(false);
290
+ });
291
+
292
+ test('should return true for non-bigint values', () => {
293
+ expect(isNotBigint(123)).toBe(true);
294
+ expect(isNotBigint('123')).toBe(true);
295
+ expect(isNotBigint(true)).toBe(true);
296
+ expect(isNotBigint(false)).toBe(true);
297
+ expect(isNotBigint(null)).toBe(true);
298
+ expect(isNotBigint(undefined)).toBe(true);
299
+ expect(isNotBigint({})).toBe(true);
300
+ expect(isNotBigint(Symbol('test'))).toBe(true);
301
+ });
302
+
303
+ test('should act as a type guard', () => {
304
+ const value: number | bigint = 123;
305
+ if (isNotBigint(value)) {
306
+ expectType<typeof value, number>('<=');
307
+ expect(typeof value).toBe('number');
308
+ }
309
+ });
310
+ });
311
+
312
+ describe('isNotString', () => {
313
+ test('should return false for string values', () => {
314
+ expect(isNotString('')).toBe(false);
315
+ expect(isNotString('hello')).toBe(false);
316
+ expect(isNotString('123')).toBe(false);
317
+ expect(isNotString(`template`)).toBe(false);
318
+ });
319
+
320
+ test('should return true for non-string values', () => {
321
+ expect(isNotString(123)).toBe(true);
322
+ expect(isNotString(true)).toBe(true);
323
+ expect(isNotString(false)).toBe(true);
324
+ expect(isNotString(null)).toBe(true);
325
+ expect(isNotString(undefined)).toBe(true);
326
+ expect(isNotString({})).toBe(true);
327
+ expect(isNotString([])).toBe(true);
328
+ expect(isNotString(Symbol('test'))).toBe(true);
329
+ expect(isNotString(BigInt(123))).toBe(true);
330
+ });
331
+
332
+ test('should act as a type guard', () => {
333
+ const value: string | number | boolean = 42;
334
+ if (isNotString(value)) {
335
+ expectType<typeof value, number | boolean>('<=');
336
+ expect(typeof value === 'number' || typeof value === 'boolean').toBe(
337
+ true,
338
+ );
339
+ }
340
+ });
341
+ });
342
+
343
+ describe('isNotSymbol', () => {
344
+ test('should return false for symbol values', () => {
345
+ expect(isNotSymbol(Symbol())).toBe(false);
346
+ expect(isNotSymbol(Symbol('test'))).toBe(false);
347
+ expect(isNotSymbol(Symbol.iterator)).toBe(false);
348
+ });
349
+
350
+ test('should return true for non-symbol values', () => {
351
+ expect(isNotSymbol('symbol')).toBe(true);
352
+ expect(isNotSymbol(123)).toBe(true);
353
+ expect(isNotSymbol(true)).toBe(true);
354
+ expect(isNotSymbol(false)).toBe(true);
355
+ expect(isNotSymbol(null)).toBe(true);
356
+ expect(isNotSymbol(undefined)).toBe(true);
357
+ expect(isNotSymbol({})).toBe(true);
358
+ expect(isNotSymbol([])).toBe(true);
359
+ expect(isNotSymbol(BigInt(123))).toBe(true);
360
+ });
361
+
362
+ test('should act as a type guard', () => {
363
+ const value: string | number | symbol = 'test';
364
+ if (isNotSymbol(value)) {
365
+ expectType<typeof value, string | number>('<=');
366
+ expect(typeof value === 'string' || typeof value === 'number').toBe(true);
367
+ }
368
+ });
369
+ });
370
+
371
+ describe('isNullish', () => {
372
+ test('should return true for null and undefined', () => {
373
+ expect(isNullish(null)).toBe(true);
374
+ expect(isNullish(undefined)).toBe(true);
375
+ });
376
+
377
+ test('should return false for non-nullish values', () => {
378
+ expect(isNullish(0)).toBe(false);
379
+ expect(isNullish(false)).toBe(false);
380
+ expect(isNullish('')).toBe(false);
381
+ expect(isNullish('null')).toBe(false);
382
+ expect(isNullish('undefined')).toBe(false);
383
+ expect(isNullish({})).toBe(false);
384
+ expect(isNullish([])).toBe(false);
385
+ expect(isNullish(Number.NaN)).toBe(false);
386
+ });
387
+
388
+ test('should act as a type guard', () => {
389
+ const value: string | null | undefined = null;
390
+ if (isNullish(value)) {
391
+ expectType<typeof value, null | undefined>('<=');
392
+ // Value is guaranteed to be null or undefined in this branch
393
+ expect(true).toBe(true);
394
+ }
395
+ });
396
+
397
+ test('should handle edge cases', () => {
398
+ // Test that it uses loose equality (==)
399
+ expect(isNullish(null)).toBe(true);
400
+ expect(isNullish(undefined)).toBe(true);
401
+ });
402
+ });
403
+
404
+ describe('isNonNullish', () => {
405
+ test('should return false for null and undefined', () => {
406
+ expect(isNonNullish(null)).toBe(false);
407
+ expect(isNonNullish(undefined)).toBe(false);
408
+ });
409
+
410
+ test('should return true for non-nullish values', () => {
411
+ expect(isNonNullish(0)).toBe(true);
412
+ expect(isNonNullish(false)).toBe(true);
413
+ expect(isNonNullish('')).toBe(true);
414
+ expect(isNonNullish('null')).toBe(true);
415
+ expect(isNonNullish('undefined')).toBe(true);
416
+ expect(isNonNullish({})).toBe(true);
417
+ expect(isNonNullish([])).toBe(true);
418
+ expect(isNonNullish(Number.NaN)).toBe(true);
419
+ expect(isNonNullish(Symbol('test'))).toBe(true);
420
+ expect(isNonNullish(BigInt(123))).toBe(true);
421
+ });
422
+
423
+ test('should act as a type guard', () => {
424
+ const value: string | null | undefined = 'test';
425
+ if (isNonNullish(value)) {
426
+ expectType<typeof value, string>('<=');
427
+ expect(value.length).toBe(4);
428
+ }
429
+ });
430
+
431
+ test('should work with array filtering', () => {
432
+ const items: (string | null | undefined)[] = [
433
+ 'hello',
434
+ null,
435
+ 'world',
436
+ undefined,
437
+ 'test',
438
+ ];
439
+
440
+ const definedItems = items.filter(isNonNullish);
441
+ expectType<typeof definedItems, string[]>('<=');
442
+
443
+ expect(definedItems).toHaveLength(3);
444
+ expect(definedItems).toStrictEqual(['hello', 'world', 'test']);
445
+ });
446
+
447
+ test('should handle complex union types', () => {
448
+ type ComplexType = string | number | boolean | null | undefined;
449
+ const value: ComplexType = 42;
450
+
451
+ if (isNonNullish(value)) {
452
+ expectType<typeof value, string | number | boolean>('<=');
453
+ // Value is guaranteed to be non-nullish in this branch
454
+ expect(true).toBe(true);
455
+ }
456
+ });
457
+ });
458
+
459
+ describe('type guard behavior in complex scenarios', () => {
460
+ test('should work with nested conditions', () => {
461
+ const value: string | number | boolean | null | undefined = 'test';
462
+
463
+ if (isNonNullish(value)) {
464
+ if (isNotBoolean(value)) {
465
+ if (isNotNumber(value)) {
466
+ expectType<typeof value, string>('<=');
467
+ expect(typeof value).toBe('string');
468
+ }
469
+ }
470
+ }
471
+ });
472
+
473
+ test('should work with array operations', () => {
474
+ const mixed: (string | number | boolean | null | undefined)[] = [
475
+ 'hello',
476
+ 42,
477
+ true,
478
+ null,
479
+ 'world',
480
+ undefined,
481
+ false,
482
+ 123,
483
+ ];
484
+
485
+ const nonNullish = mixed.filter(isNonNullish);
486
+ expectType<typeof nonNullish, (string | number | boolean)[]>('<=');
487
+
488
+ const nonBooleans = nonNullish.filter(isNotBoolean);
489
+ expectType<typeof nonBooleans, (string | number)[]>('<=');
490
+
491
+ const strings = nonBooleans.filter(isNotNumber);
492
+ expectType<typeof strings, string[]>('<=');
493
+
494
+ expect(strings).toStrictEqual(['hello', 'world']);
495
+ });
496
+ });
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Type guard that checks if a key exists as an own property in an object.
3
+ *
4
+ * This function is similar to `hasKey()` but with reversed parameter order and different
5
+ * type narrowing behavior. While `hasKey()` narrows the object type, `keyIsIn()` narrows
6
+ * the key type to be a valid key of the given object.
7
+ *
8
+ * **Type Narrowing Behavior:**
9
+ * - Narrows the key type to be a key that exists in the object (`K & keyof R`)
10
+ * - Useful when you have a dynamic key and want to ensure it's valid for a specific object
11
+ * - The object type remains unchanged
12
+ *
13
+ * **Implementation:** Uses `Object.hasOwn()` to check for own properties (not inherited).
14
+ *
15
+ * @template K - The type of the key to check, must extend PropertyKey (string | number | symbol)
16
+ * @template R - The type of the record (object), must extend UnknownRecord
17
+ * @param key - The key to check for
18
+ * @param obj - The object to check within
19
+ * @returns `true` if `key` is an own property of `obj`, `false` otherwise.
20
+ * When `true`, TypeScript narrows the key type to be a valid key of the object.
21
+ *
22
+ * @example
23
+ * Basic usage with known object structure:
24
+ * ```typescript
25
+ * const obj = { a: 1, b: 2, c: 3 };
26
+ * const userInput: string = getUserInput(); // Could be any string
27
+ *
28
+ * if (keyIsIn(userInput, obj)) {
29
+ * // userInput is now narrowed to 'a' | 'b' | 'c'
30
+ * const value = obj[userInput]; // Type-safe access, value is number
31
+ * console.log(`Value for ${userInput}:`, value);
32
+ * } else {
33
+ * console.log(`Key '${userInput}' not found in object`);
34
+ * }
35
+ * ```
36
+ *
37
+ * @example
38
+ * Dynamic key validation for safe property access:
39
+ * ```typescript
40
+ * const config = {
41
+ * apiUrl: 'https://api.example.com',
42
+ * timeout: 5000,
43
+ * retries: 3
44
+ * } as const;
45
+ *
46
+ * function getConfigValue(key: string): unknown {
47
+ * if (keyIsIn(key, config)) {
48
+ * // key is now narrowed to 'apiUrl' | 'timeout' | 'retries'
49
+ * return config[key]; // Safe access with proper typing
50
+ * }
51
+ *
52
+ * throw new Error(`Invalid config key: ${key}`);
53
+ * }
54
+ *
55
+ * // Usage
56
+ * const apiUrl = getConfigValue('apiUrl'); // Returns string
57
+ * const timeout = getConfigValue('timeout'); // Returns number
58
+ * // getConfigValue('invalid') would throw an error
59
+ * ```
60
+ *
61
+ * @example
62
+ * Form field validation:
63
+ * ```typescript
64
+ * interface FormData {
65
+ * name: string;
66
+ * email: string;
67
+ * age: number;
68
+ * }
69
+ *
70
+ * const formData: FormData = getFormData();
71
+ * const requiredFields: readonly string[] = ['name', 'email'] as const;
72
+ *
73
+ * function validateRequiredFields(data: FormData): string[] {
74
+ * const errors: string[] = [];
75
+ *
76
+ * for (const field of requiredFields) {
77
+ * if (keyIsIn(field, data)) {
78
+ * // field is now narrowed to keyof FormData
79
+ * const value = data[field];
80
+ *
81
+ * if (typeof value === 'string' && value.trim() === '') {
82
+ * errors.push(`${field} is required`);
83
+ * }
84
+ * }
85
+ * }
86
+ *
87
+ * return errors;
88
+ * }
89
+ * ```
90
+ *
91
+ * @example
92
+ * Safe object property iteration:
93
+ * ```typescript
94
+ * const userPreferences = {
95
+ * theme: 'dark',
96
+ * language: 'en',
97
+ * notifications: true
98
+ * };
99
+ *
100
+ * const settingsToUpdate: string[] = getSettingsFromUser();
101
+ *
102
+ * function updatePreferences(updates: Record<string, unknown>) {
103
+ * const validUpdates: Partial<typeof userPreferences> = {};
104
+ *
105
+ * for (const [key, value] of Object.entries(updates)) {
106
+ * if (keyIsIn(key, userPreferences)) {
107
+ * // key is now narrowed to valid preference keys
108
+ * validUpdates[key] = value as typeof userPreferences[typeof key];
109
+ * } else {
110
+ * console.warn(`Unknown preference key: ${key}`);
111
+ * }
112
+ * }
113
+ *
114
+ * return { ...userPreferences, ...validUpdates };
115
+ * }
116
+ * ```
117
+ *
118
+ * @example
119
+ * Comparison with hasKey() - different narrowing behavior:
120
+ * ```typescript
121
+ * const obj = { x: 10, y: 20 };
122
+ * const key: string = 'x';
123
+ *
124
+ * // Using keyIsIn - narrows the key type
125
+ * if (keyIsIn(key, obj)) {
126
+ * // key is now 'x' | 'y'
127
+ * const value = obj[key]; // Safe access
128
+ * }
129
+ *
130
+ * // Using hasKey - narrows the object type
131
+ * if (hasKey(obj, key)) {
132
+ * // obj type is narrowed to guarantee the key exists
133
+ * const value = obj.x; // Direct access
134
+ * }
135
+ * ```
136
+ *
137
+ * @example
138
+ * Working with union types:
139
+ * ```typescript
140
+ * type Config =
141
+ * | { type: 'database'; host: string; port: number }
142
+ * | { type: 'file'; path: string }
143
+ * | { type: 'memory'; maxSize: number };
144
+ *
145
+ * function getConfigProperty(config: Config, propName: string): unknown {
146
+ * if (keyIsIn(propName, config)) {
147
+ * // propName is narrowed to valid keys for the specific config type
148
+ * return config[propName];
149
+ * }
150
+ *
151
+ * return undefined;
152
+ * }
153
+ * ```
154
+ *
155
+ * @see {@link hasKey} - Similar function that narrows the object type instead of the key type
156
+ */
157
+ export const keyIsIn = <
158
+ const K extends PropertyKey,
159
+ const R extends UnknownRecord,
160
+ >(
161
+ key: K,
162
+ obj: R,
163
+ ): key is K & keyof typeof obj => Object.hasOwn(obj, key);
@@ -0,0 +1,19 @@
1
+ import { keyIsIn } from './key-is-in.mjs';
2
+
3
+ const f = <Key extends string, V, KeySub extends Key>(
4
+ key: Key,
5
+ obj: ReadonlyRecord<KeySub, V>,
6
+ ): V | undefined => (keyIsIn(key, obj) ? obj[key] : undefined);
7
+
8
+ describe('keyIsIn', () => {
9
+ f('a' as 'a' | 'b' | 'c', { a: 0, b: 1 });
10
+
11
+ test('', () => {
12
+ expect(
13
+ keyIsIn('a', {
14
+ a: 0,
15
+ b: 1,
16
+ }),
17
+ ).toBe(true);
18
+ });
19
+ });
package/src/index.mts ADDED
@@ -0,0 +1,10 @@
1
+ export * from './array/index.mjs';
2
+ export * from './collections/index.mjs';
3
+ export * from './expect-type.mjs';
4
+ export * from './functional/index.mjs';
5
+ export * from './guard/index.mjs';
6
+ export * from './iterator/index.mjs';
7
+ export * from './json/index.mjs';
8
+ export * from './number/index.mjs';
9
+ export * from './object/index.mjs';
10
+ export * from './others/index.mjs';
@@ -0,0 +1 @@
1
+ export * from './range.mjs';