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.
- package/LICENSE +201 -0
- package/README.md +534 -0
- package/package.json +101 -0
- package/src/array/array-utils-creation.test.mts +443 -0
- package/src/array/array-utils-modification.test.mts +197 -0
- package/src/array/array-utils-overload-type-error.test.mts +149 -0
- package/src/array/array-utils-reducing-value.test.mts +425 -0
- package/src/array/array-utils-search.test.mts +169 -0
- package/src/array/array-utils-set-op.test.mts +335 -0
- package/src/array/array-utils-slice-clamped.test.mts +113 -0
- package/src/array/array-utils-slicing.test.mts +316 -0
- package/src/array/array-utils-transformation.test.mts +790 -0
- package/src/array/array-utils-validation.test.mts +492 -0
- package/src/array/array-utils.mts +4000 -0
- package/src/array/array.test.mts +146 -0
- package/src/array/index.mts +2 -0
- package/src/array/tuple-utils.mts +519 -0
- package/src/array/tuple-utils.test.mts +518 -0
- package/src/collections/imap-mapped.mts +801 -0
- package/src/collections/imap-mapped.test.mts +860 -0
- package/src/collections/imap.mts +651 -0
- package/src/collections/imap.test.mts +932 -0
- package/src/collections/index.mts +6 -0
- package/src/collections/iset-mapped.mts +889 -0
- package/src/collections/iset-mapped.test.mts +1187 -0
- package/src/collections/iset.mts +682 -0
- package/src/collections/iset.test.mts +1084 -0
- package/src/collections/queue.mts +390 -0
- package/src/collections/queue.test.mts +282 -0
- package/src/collections/stack.mts +423 -0
- package/src/collections/stack.test.mts +225 -0
- package/src/expect-type.mts +206 -0
- package/src/functional/index.mts +4 -0
- package/src/functional/match.mts +300 -0
- package/src/functional/match.test.mts +177 -0
- package/src/functional/optional.mts +733 -0
- package/src/functional/optional.test.mts +619 -0
- package/src/functional/pipe.mts +212 -0
- package/src/functional/pipe.test.mts +85 -0
- package/src/functional/result.mts +1134 -0
- package/src/functional/result.test.mts +777 -0
- package/src/globals.d.mts +38 -0
- package/src/guard/has-key.mts +119 -0
- package/src/guard/has-key.test.mts +219 -0
- package/src/guard/index.mts +7 -0
- package/src/guard/is-non-empty-string.mts +108 -0
- package/src/guard/is-non-empty-string.test.mts +91 -0
- package/src/guard/is-non-null-object.mts +106 -0
- package/src/guard/is-non-null-object.test.mts +90 -0
- package/src/guard/is-primitive.mts +165 -0
- package/src/guard/is-primitive.test.mts +102 -0
- package/src/guard/is-record.mts +153 -0
- package/src/guard/is-record.test.mts +112 -0
- package/src/guard/is-type.mts +450 -0
- package/src/guard/is-type.test.mts +496 -0
- package/src/guard/key-is-in.mts +163 -0
- package/src/guard/key-is-in.test.mts +19 -0
- package/src/index.mts +10 -0
- package/src/iterator/index.mts +1 -0
- package/src/iterator/range.mts +120 -0
- package/src/iterator/range.test.mts +33 -0
- package/src/json/index.mts +1 -0
- package/src/json/json.mts +711 -0
- package/src/json/json.test.mts +628 -0
- package/src/number/branded-types/finite-number.mts +354 -0
- package/src/number/branded-types/finite-number.test.mts +135 -0
- package/src/number/branded-types/index.mts +26 -0
- package/src/number/branded-types/int.mts +278 -0
- package/src/number/branded-types/int.test.mts +140 -0
- package/src/number/branded-types/int16.mts +192 -0
- package/src/number/branded-types/int16.test.mts +170 -0
- package/src/number/branded-types/int32.mts +193 -0
- package/src/number/branded-types/int32.test.mts +170 -0
- package/src/number/branded-types/non-negative-finite-number.mts +223 -0
- package/src/number/branded-types/non-negative-finite-number.test.mts +188 -0
- package/src/number/branded-types/non-negative-int16.mts +187 -0
- package/src/number/branded-types/non-negative-int16.test.mts +201 -0
- package/src/number/branded-types/non-negative-int32.mts +187 -0
- package/src/number/branded-types/non-negative-int32.test.mts +204 -0
- package/src/number/branded-types/non-zero-finite-number.mts +229 -0
- package/src/number/branded-types/non-zero-finite-number.test.mts +198 -0
- package/src/number/branded-types/non-zero-int.mts +167 -0
- package/src/number/branded-types/non-zero-int.test.mts +177 -0
- package/src/number/branded-types/non-zero-int16.mts +196 -0
- package/src/number/branded-types/non-zero-int16.test.mts +195 -0
- package/src/number/branded-types/non-zero-int32.mts +196 -0
- package/src/number/branded-types/non-zero-int32.test.mts +197 -0
- package/src/number/branded-types/non-zero-safe-int.mts +196 -0
- package/src/number/branded-types/non-zero-safe-int.test.mts +232 -0
- package/src/number/branded-types/non-zero-uint16.mts +189 -0
- package/src/number/branded-types/non-zero-uint16.test.mts +199 -0
- package/src/number/branded-types/non-zero-uint32.mts +189 -0
- package/src/number/branded-types/non-zero-uint32.test.mts +199 -0
- package/src/number/branded-types/positive-finite-number.mts +241 -0
- package/src/number/branded-types/positive-finite-number.test.mts +204 -0
- package/src/number/branded-types/positive-int.mts +304 -0
- package/src/number/branded-types/positive-int.test.mts +176 -0
- package/src/number/branded-types/positive-int16.mts +188 -0
- package/src/number/branded-types/positive-int16.test.mts +197 -0
- package/src/number/branded-types/positive-int32.mts +188 -0
- package/src/number/branded-types/positive-int32.test.mts +197 -0
- package/src/number/branded-types/positive-safe-int.mts +187 -0
- package/src/number/branded-types/positive-safe-int.test.mts +210 -0
- package/src/number/branded-types/positive-uint16.mts +188 -0
- package/src/number/branded-types/positive-uint16.test.mts +203 -0
- package/src/number/branded-types/positive-uint32.mts +188 -0
- package/src/number/branded-types/positive-uint32.test.mts +203 -0
- package/src/number/branded-types/safe-int.mts +291 -0
- package/src/number/branded-types/safe-int.test.mts +170 -0
- package/src/number/branded-types/safe-uint.mts +187 -0
- package/src/number/branded-types/safe-uint.test.mts +176 -0
- package/src/number/branded-types/uint.mts +179 -0
- package/src/number/branded-types/uint.test.mts +158 -0
- package/src/number/branded-types/uint16.mts +186 -0
- package/src/number/branded-types/uint16.test.mts +170 -0
- package/src/number/branded-types/uint32.mts +218 -0
- package/src/number/branded-types/uint32.test.mts +170 -0
- package/src/number/enum/index.mts +2 -0
- package/src/number/enum/int8.mts +344 -0
- package/src/number/enum/int8.test.mts +180 -0
- package/src/number/enum/uint8.mts +293 -0
- package/src/number/enum/uint8.test.mts +164 -0
- package/src/number/index.mts +4 -0
- package/src/number/num.mts +604 -0
- package/src/number/num.test.mts +242 -0
- package/src/number/refined-number-utils.mts +566 -0
- package/src/object/index.mts +1 -0
- package/src/object/object.mts +447 -0
- package/src/object/object.test.mts +124 -0
- package/src/others/cast-mutable.mts +113 -0
- package/src/others/cast-readonly.mts +192 -0
- package/src/others/cast-readonly.test.mts +89 -0
- package/src/others/if-then.mts +98 -0
- package/src/others/if-then.test.mts +75 -0
- package/src/others/index.mts +7 -0
- package/src/others/map-nullable.mts +172 -0
- package/src/others/map-nullable.test.mts +297 -0
- package/src/others/memoize-function.mts +196 -0
- package/src/others/memoize-function.test.mts +168 -0
- package/src/others/tuple.mts +160 -0
- package/src/others/tuple.test.mts +11 -0
- package/src/others/unknown-to-string.mts +215 -0
- 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';
|