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,38 @@
1
+ /// <reference types="ts-type-forge" />
2
+
3
+ type SmallUint = SmallInt<'>=0'>;
4
+ type PositiveSmallInt = SmallInt<'>0'>;
5
+
6
+ /**
7
+ * Represents the type of keys that can be used in a standard JavaScript Map.
8
+ */
9
+ type MapSetKeyType = Primitive;
10
+
11
+ // https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/length
12
+ // https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/length
13
+ // Max array length : 2^32 - 1
14
+ // Max string length : 2^53 - 1
15
+
16
+ declare namespace SizeType {
17
+ type Arr = Uint32;
18
+ type TypedArray = SafeUint;
19
+ type Str = SafeUint;
20
+
21
+ type ArrSearchResult = Arr | -1;
22
+ type TypedArraySearchResult = TypedArray | -1;
23
+ type StrSearchResult = Str | -1;
24
+
25
+ type ArgArr = WithSmallInt<NormalizeBrandUnion<NegativeInt32 | Arr>>;
26
+ type ArgTypedArray = WithSmallInt<SafeInt>;
27
+ type ArgStr = WithSmallInt<SafeInt>;
28
+
29
+ type ArgArrPositive = WithSmallInt<IntersectBrand<PositiveNumber, Arr>>;
30
+ type ArgTypedArrayPositive = WithSmallInt<
31
+ IntersectBrand<PositiveNumber, TypedArray>
32
+ >;
33
+ type ArgStrPositive = WithSmallInt<IntersectBrand<PositiveNumber, Str>>;
34
+
35
+ type ArgArrNonNegative = WithSmallInt<Arr>;
36
+ type ArgTypedArrayNonNegative = WithSmallInt<TypedArray>;
37
+ type ArgStrNonNegative = WithSmallInt<Str>;
38
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Type guard function that checks if an object has a specific key as its own property.
3
+ *
4
+ * This function uses `Object.hasOwn()` to check if the given object has the specified key
5
+ * as its own property (not inherited). It acts as a type guard that narrows the type of the
6
+ * object to guarantee the key exists, enabling type-safe property access.
7
+ *
8
+ * **Type Narrowing Behavior:**
9
+ * - When the guard returns `true`, TypeScript narrows the object type to include the checked key
10
+ * - For union types, only union members that contain the key are preserved
11
+ * - The key's value type is preserved from the original object type when possible
12
+ *
13
+ * @template R - The type of the input object, must extend UnknownRecord
14
+ * @template K - The type of the key to check for, must extend PropertyKey (string | number | symbol)
15
+ * @param obj - The object to check for the presence of the key
16
+ * @param key - The key to check for in the object
17
+ * @returns `true` if the object has the specified key as its own property, `false` otherwise.
18
+ * When `true`, TypeScript narrows the object type to guarantee the key exists.
19
+ *
20
+ * @example
21
+ * Basic usage with known object structure:
22
+ * ```typescript
23
+ * const obj = { a: 1, b: 'hello' };
24
+ *
25
+ * if (hasKey(obj, 'a')) {
26
+ * // obj is narrowed to guarantee 'a' exists
27
+ * console.log(obj.a); // TypeScript knows 'a' exists and is type number
28
+ * // No need for optional chaining or undefined checks
29
+ * }
30
+ *
31
+ * if (hasKey(obj, 'c')) {
32
+ * // This block won't execute at runtime
33
+ * console.log(obj.c); // But TypeScript would know 'c' exists if it did
34
+ * }
35
+ * ```
36
+ *
37
+ * @example
38
+ * Working with dynamic objects and unknown keys:
39
+ * ```typescript
40
+ * const dynamicObj: Record<string, unknown> = { x: 10, y: 20 };
41
+ * const userInput: string = getUserInput();
42
+ *
43
+ * if (hasKey(dynamicObj, userInput)) {
44
+ * // Safe to access the dynamic key
45
+ * const value = dynamicObj[userInput]; // Type: unknown
46
+ * console.log(`Value for ${userInput}:`, value);
47
+ * } else {
48
+ * console.log(`Key '${userInput}' not found`);
49
+ * }
50
+ * ```
51
+ *
52
+ * @example
53
+ * Type narrowing with union types:
54
+ * ```typescript
55
+ * type UserPreferences =
56
+ * | { theme: 'dark'; notifications: boolean }
57
+ * | { theme: 'light' }
58
+ * | { autoSave: true; interval: number };
59
+ *
60
+ * const preferences: UserPreferences = getPreferences();
61
+ *
62
+ * if (hasKey(preferences, 'theme')) {
63
+ * // preferences is narrowed to the first two union members
64
+ * console.log(preferences.theme); // 'dark' | 'light'
65
+ * }
66
+ *
67
+ * if (hasKey(preferences, 'autoSave')) {
68
+ * // preferences is narrowed to the third union member
69
+ * console.log(preferences.interval); // number (we know this exists)
70
+ * }
71
+ * ```
72
+ *
73
+ * @example
74
+ * Combining with other type guards for progressive narrowing:
75
+ * ```typescript
76
+ * const data: unknown = parseApiResponse();
77
+ *
78
+ * if (isRecord(data) && hasKey(data, 'user')) {
79
+ * // data is now Record<string, unknown> with guaranteed 'user' key
80
+ * const user = data.user;
81
+ *
82
+ * if (isRecord(user) && hasKey(user, 'name')) {
83
+ * // Safely access nested properties
84
+ * console.log('User name:', user.name);
85
+ * }
86
+ * }
87
+ * ```
88
+ *
89
+ * @see {@link keyIsIn} - Similar function that narrows the key type instead of the object type
90
+ */
91
+ export const hasKey = <
92
+ const R extends UnknownRecord,
93
+ const K extends PropertyKey,
94
+ >(
95
+ obj: R,
96
+ key: K,
97
+ ): obj is HasKeyReturnType<R, K> => Object.hasOwn(obj, key);
98
+
99
+ /**
100
+ * @internal
101
+ * When R is a union type (including the case with only one element), if any element in the union
102
+ * contains K as a key, returns a type that narrows the union to only those elements that contain K as a key.
103
+ * If none of the elements in the union contain K as a key, returns `ReadonlyRecord<K, unknown>`.
104
+ * The result is made readonly.
105
+ */
106
+ export type HasKeyReturnType<
107
+ R extends UnknownRecord,
108
+ K extends PropertyKey,
109
+ > = R extends R // union distribution
110
+ ? K extends keyof R
111
+ ? string extends keyof R
112
+ ? ReadonlyRecord<K, R[keyof R]> & R
113
+ : number extends keyof R
114
+ ? ReadonlyRecord<K, R[keyof R]> & R
115
+ : symbol extends keyof R
116
+ ? ReadonlyRecord<K, R[keyof R]> & R
117
+ : R
118
+ : never // omit union member that does not have key K
119
+ : never; // dummy case for union distribution
@@ -0,0 +1,219 @@
1
+ import { expectType } from '../expect-type.mjs';
2
+ import { hasKey, type HasKeyReturnType } from './has-key.mjs';
3
+
4
+ {
5
+ expectType<
6
+ HasKeyReturnType<Readonly<{ a: 0 }> | Readonly<{ b: 1 }>, 'a'>,
7
+ Readonly<{ a: 0 }>
8
+ >('=');
9
+
10
+ expectType<
11
+ HasKeyReturnType<Readonly<{ a: 0 }> | Readonly<{ b: 1 }>, 'b'>,
12
+ Readonly<{ b: 1 }>
13
+ >('=');
14
+
15
+ expectType<
16
+ HasKeyReturnType<Readonly<{ a: 0 }> | Readonly<{ b: 1 }>, 'd'>,
17
+ never
18
+ >('=');
19
+
20
+ expectType<
21
+ HasKeyReturnType<
22
+ | Readonly<{ a: 0 }>
23
+ | Readonly<{ a: 1; b: 1 }>
24
+ | Readonly<{ b: 2 }>
25
+ | Readonly<{ c: 3 }>,
26
+ 'a'
27
+ >,
28
+ Readonly<{ a: 0 }> | Readonly<{ a: 1; b: 1 }>
29
+ >('=');
30
+
31
+ expectType<
32
+ HasKeyReturnType<
33
+ | Readonly<{ a: 0 }>
34
+ | Readonly<{ a: 1; b: 1 }>
35
+ | Readonly<{ b: 2 }>
36
+ | Readonly<{ c: 3 }>,
37
+ 'b'
38
+ >,
39
+ Readonly<{ a: 1; b: 1 }> | Readonly<{ b: 2 }>
40
+ >('=');
41
+
42
+ expectType<
43
+ HasKeyReturnType<
44
+ | ReadonlyRecord<string, number>
45
+ | Readonly<{ a: 0 }>
46
+ | Readonly<{ a: 1; b: 1 }>
47
+ | Readonly<{ b: 2 }>,
48
+ 'a'
49
+ >,
50
+ | Readonly<{ a: 0 }>
51
+ | Readonly<{ a: 1; b: 1 }>
52
+ | (ReadonlyRecord<'a', number> & ReadonlyRecord<string, number>)
53
+ >('=');
54
+
55
+ expectType<
56
+ HasKeyReturnType<ReadonlyRecord<string, unknown>, 'a'>,
57
+ ReadonlyRecord<'a', unknown> & ReadonlyRecord<string, unknown>
58
+ >('=');
59
+ }
60
+
61
+ {
62
+ type R = Readonly<{ a: 0 }> | Readonly<{ b: 1 }>;
63
+
64
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
65
+ const obj: R = { a: 0 } as R;
66
+
67
+ if (hasKey(obj, 'a')) {
68
+ expectType<typeof obj.a, 0>('=');
69
+ expectType<typeof obj, Readonly<{ a: 0 }>>('=');
70
+ }
71
+
72
+ if (hasKey(obj, 'c')) {
73
+ expectType<typeof obj, never>('=');
74
+ }
75
+ }
76
+
77
+ {
78
+ type R =
79
+ | Readonly<{ a: 0 }>
80
+ | Readonly<{ a: 1; b: 1 }>
81
+ | Readonly<{ b: 2 }>
82
+ | Readonly<{ c: 3 }>;
83
+
84
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
85
+ const obj: R = { a: 0 } as R;
86
+
87
+ if (hasKey(obj, 'a') && hasKey(obj, 'b')) {
88
+ expectType<typeof obj.a, 1>('=');
89
+ expectType<typeof obj.b, 1>('=');
90
+ expectType<typeof obj, Readonly<{ a: 1; b: 1 }>>('=');
91
+ }
92
+ }
93
+
94
+ {
95
+ type R =
96
+ | ReadonlyRecord<string, number>
97
+ | Readonly<{ a: 0 }>
98
+ | Readonly<{ a: 1; b: 1 }>
99
+ | Readonly<{ b: 2 }>;
100
+
101
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
102
+ const obj: R = { a: 0 } as R;
103
+
104
+ expectType<
105
+ R,
106
+ | ReadonlyRecord<string, number>
107
+ | Readonly<{ a: 0 }>
108
+ | Readonly<{ a: 1; b: 1 }>
109
+ | Readonly<{ b: 2 }>
110
+ >('=');
111
+
112
+ if (hasKey(obj, 'a')) {
113
+ expectType<typeof obj.a, number>('=');
114
+
115
+ expectType<
116
+ typeof obj,
117
+ | Readonly<{ a: 0 }>
118
+ | Readonly<{ a: 1; b: 1 }>
119
+ | (ReadonlyRecord<'a', number> & ReadonlyRecord<string, number>)
120
+ >('=');
121
+ }
122
+
123
+ if (hasKey(obj, 'a') && hasKey(obj, 'b')) {
124
+ expectType<typeof obj.a, number>('=');
125
+ expectType<typeof obj.b, number>('=');
126
+ expectType<
127
+ typeof obj,
128
+ | Readonly<{ a: 1; b: 1 }>
129
+ | (ReadonlyRecord<'a', number> &
130
+ ReadonlyRecord<'b', number> &
131
+ ReadonlyRecord<string, number>)
132
+ >('=');
133
+ }
134
+ }
135
+
136
+ {
137
+ const o: ReadonlyRecord<string, unknown> = { a: 0, b: 1 };
138
+
139
+ if (hasKey(o, 'a')) {
140
+ expectType<typeof o.a, unknown>('=');
141
+ expectType<
142
+ typeof o,
143
+ ReadonlyRecord<'a', unknown> & ReadonlyRecord<string, unknown>
144
+ >('=');
145
+ }
146
+
147
+ if (hasKey(o, 'c')) {
148
+ expectType<typeof o.c, unknown>('=');
149
+ expectType<
150
+ typeof o,
151
+ ReadonlyRecord<'c', unknown> & ReadonlyRecord<string, unknown>
152
+ >('=');
153
+ }
154
+
155
+ if (hasKey(o, 'a') && hasKey(o, 'b')) {
156
+ expectType<typeof o.a, unknown>('=');
157
+ expectType<typeof o.b, unknown>('=');
158
+ expectType<
159
+ typeof o,
160
+ ReadonlyRecord<'a', unknown> &
161
+ ReadonlyRecord<'b', unknown> &
162
+ ReadonlyRecord<string, unknown>
163
+ >('=');
164
+ }
165
+
166
+ {
167
+ /**
168
+ * @note Simply using `R & Record<K, R[K]>` as the return type of hasKey may seem good enough
169
+ * since it works as intended for cases like `obj: Record<string, unknown>`.
170
+ * However, for finite-sized types like `obj: { a: 0 } | { b: 1 }`,
171
+ * filtering with `hasKey(obj, 'a')` causes `obj.a` to widen to `unknown` instead of `0`,
172
+ * which doesn't work well.
173
+ */
174
+
175
+ const hasOwnNaive = <R extends UnknownRecord, K extends string>(
176
+ obj: R,
177
+ key: K,
178
+ ): obj is R & Record<K, R[K]> => hasKey(obj, key);
179
+
180
+ {
181
+ type O =
182
+ | Readonly<{ a: 0 }>
183
+ | Readonly<{ a: 1; b: 1 }>
184
+ | Readonly<{ b: 2 }>
185
+ | Record<string, number>;
186
+
187
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
188
+ const o2 = { b: 2 } as O;
189
+
190
+ if (hasOwnNaive(o2, 'a')) {
191
+ expectType<typeof o2.a, unknown>('=');
192
+ }
193
+
194
+ // eslint-disable-next-line no-restricted-syntax
195
+ if ('a' in o2) {
196
+ expectType<typeof o2.a, number>('=');
197
+ }
198
+ }
199
+
200
+ {
201
+ type O =
202
+ | Readonly<{ a: 0 }>
203
+ | Readonly<{ a: 1; b: 1 }>
204
+ | Readonly<{ b: 2 }>;
205
+
206
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
207
+ const o2 = { b: 2 } as O;
208
+
209
+ if (hasOwnNaive(o2, 'a')) {
210
+ expectType<typeof o2.a, unknown>('=');
211
+ }
212
+
213
+ // eslint-disable-next-line no-restricted-syntax
214
+ if ('a' in o2) {
215
+ expectType<typeof o2.a, 0 | 1>('=');
216
+ }
217
+ }
218
+ }
219
+ }
@@ -0,0 +1,7 @@
1
+ export * from './has-key.mjs';
2
+ export * from './is-non-empty-string.mjs';
3
+ export * from './is-non-null-object.mjs';
4
+ export * from './is-primitive.mjs';
5
+ export * from './is-record.mjs';
6
+ export * from './is-type.mjs';
7
+ export * from './key-is-in.mjs';
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Type guard that checks if a value is a non-empty string.
3
+ *
4
+ * This function performs both a type check (ensuring the value is a string) and a length check
5
+ * (ensuring the string is not empty). It acts as a type guard that narrows the input type to
6
+ * exclude `null`, `undefined`, and empty strings.
7
+ *
8
+ * **Type Narrowing Behavior:**
9
+ * - Eliminates `null` and `undefined` from the type
10
+ * - Excludes empty string (`""`) from the type
11
+ * - Preserves literal string types when possible
12
+ * - Whitespace-only strings are considered non-empty
13
+ *
14
+ * @template S - The input type, which can include `string`, `null`, or `undefined`
15
+ * @param s - The value to check
16
+ * @returns `true` if `s` is a string with length > 0, `false` otherwise.
17
+ * When `true`, TypeScript narrows the type to exclude empty strings and nullish values.
18
+ *
19
+ * @example
20
+ * Basic usage with different string types:
21
+ * ```typescript
22
+ * isNonEmptyString("hello"); // true
23
+ * isNonEmptyString(" "); // true (whitespace is considered non-empty)
24
+ * isNonEmptyString("\t\n"); // true (whitespace characters are non-empty)
25
+ * isNonEmptyString(""); // false
26
+ * isNonEmptyString(null); // false
27
+ * isNonEmptyString(undefined); // false
28
+ * ```
29
+ *
30
+ * @example
31
+ * Type guard usage with nullable strings:
32
+ * ```typescript
33
+ * const userInput: string | null | undefined = getUserInput();
34
+ *
35
+ * if (isNonEmptyString(userInput)) {
36
+ * // userInput is now typed as non-empty string
37
+ * console.log(userInput.charAt(0)); // Safe to access string methods
38
+ * console.log(userInput.toUpperCase()); // No need for null checks
39
+ * const length = userInput.length; // Guaranteed to be > 0
40
+ * } else {
41
+ * console.log('No valid input provided');
42
+ * }
43
+ * ```
44
+ *
45
+ * @example
46
+ * Working with literal string types:
47
+ * ```typescript
48
+ * type Status = "active" | "inactive" | "" | null;
49
+ * const status: Status = getStatus();
50
+ *
51
+ * if (isNonEmptyString(status)) {
52
+ * // status is now typed as "active" | "inactive"
53
+ * console.log(`Status is: ${status}`);
54
+ * }
55
+ * ```
56
+ *
57
+ * @example
58
+ * Form validation patterns:
59
+ * ```typescript
60
+ * interface FormData {
61
+ * name?: string;
62
+ * email?: string;
63
+ * phone?: string;
64
+ * }
65
+ *
66
+ * function validateForm(data: FormData): string[] {
67
+ * const errors: string[] = [];
68
+ *
69
+ * if (!isNonEmptyString(data.name)) {
70
+ * errors.push('Name is required');
71
+ * }
72
+ *
73
+ * if (!isNonEmptyString(data.email)) {
74
+ * errors.push('Email is required');
75
+ * } else if (!data.email.includes('@')) {
76
+ * // Safe to access string methods after guard
77
+ * errors.push('Invalid email format');
78
+ * }
79
+ *
80
+ * return errors;
81
+ * }
82
+ * ```
83
+ *
84
+ * @example
85
+ * Filtering arrays of potentially empty strings:
86
+ * ```typescript
87
+ * const responses: (string | null | undefined)[] = [
88
+ * "hello",
89
+ * "",
90
+ * "world",
91
+ * null,
92
+ * undefined,
93
+ * " "
94
+ * ];
95
+ *
96
+ * const validResponses = responses.filter(isNonEmptyString);
97
+ * // validResponses is now string[] containing ["hello", "world", " "]
98
+ *
99
+ * validResponses.forEach(response => {
100
+ * // Each response is guaranteed to be a non-empty string
101
+ * console.log(response.trim().toUpperCase());
102
+ * });
103
+ * ```
104
+ */
105
+ export const isNonEmptyString = <S extends string | null | undefined>(
106
+ s: S,
107
+ ): s is RelaxedExclude<NonNullable<S>, ''> =>
108
+ typeof s === 'string' && s.length > 0;
@@ -0,0 +1,91 @@
1
+ import { expectType } from '../expect-type.mjs';
2
+ import { isNonEmptyString } from './is-non-empty-string.mjs';
3
+
4
+ describe('isNonEmptyString', () => {
5
+ test('should return true for non-empty strings', () => {
6
+ expect(isNonEmptyString('hello')).toBe(true);
7
+ expect(isNonEmptyString('a')).toBe(true);
8
+ expect(isNonEmptyString(' ')).toBe(true); // Space is not empty
9
+ expect(isNonEmptyString(' multiple spaces ')).toBe(true);
10
+ expect(isNonEmptyString('123')).toBe(true);
11
+ expect(isNonEmptyString('special!@#$%')).toBe(true);
12
+ });
13
+
14
+ test('should return false for empty string', () => {
15
+ expect(isNonEmptyString('')).toBe(false);
16
+ });
17
+
18
+ test('should return false for non-string values', () => {
19
+ expect(isNonEmptyString(null)).toBe(false);
20
+ expect(isNonEmptyString(undefined)).toBe(false);
21
+ // @ts-expect-error Testing non-string types
22
+ expect(isNonEmptyString(42)).toBe(false);
23
+ // @ts-expect-error Testing non-string types
24
+ expect(isNonEmptyString(0)).toBe(false);
25
+ // @ts-expect-error Testing non-string types
26
+ expect(isNonEmptyString(true)).toBe(false);
27
+ // @ts-expect-error Testing non-string types
28
+ expect(isNonEmptyString(false)).toBe(false);
29
+ // @ts-expect-error Testing non-string types
30
+ expect(isNonEmptyString({})).toBe(false);
31
+ // @ts-expect-error Testing non-string types
32
+ expect(isNonEmptyString([])).toBe(false);
33
+ // @ts-expect-error Testing non-string types
34
+ expect(isNonEmptyString(['string'])).toBe(false);
35
+ // @ts-expect-error Testing non-string types
36
+ expect(isNonEmptyString(() => 'string')).toBe(false);
37
+ });
38
+
39
+ test('should act as a type guard', () => {
40
+ const value: unknown = 'test';
41
+
42
+ // @ts-expect-error Testing non-string types
43
+ if (isNonEmptyString(value)) {
44
+ expectType<typeof value, string>('=');
45
+ // TypeScript knows it's a string
46
+ expect(value.length).toBeGreaterThan(0);
47
+ expect(value.charAt(0)).toBe('t');
48
+ }
49
+ });
50
+
51
+ test('should narrow string | undefined | null types', () => {
52
+ const maybeString: string | undefined | null = 'hello';
53
+
54
+ if (isNonEmptyString(maybeString)) {
55
+ expectType<typeof maybeString, string>('=');
56
+ expect(maybeString.toUpperCase()).toBe('HELLO');
57
+ }
58
+ });
59
+
60
+ test('should work in filter operations', () => {
61
+ const mixed: unknown[] = [
62
+ 'valid',
63
+ '',
64
+ null,
65
+ 'another',
66
+ 42,
67
+ undefined,
68
+ 'third',
69
+ false,
70
+ ];
71
+
72
+ // @ts-expect-error Testing non-string types
73
+ const nonEmptyStrings = mixed.filter(isNonEmptyString);
74
+ expect(nonEmptyStrings).toStrictEqual(['valid', 'another', 'third']);
75
+ });
76
+
77
+ test('should handle string edge cases', () => {
78
+ expect(isNonEmptyString('\n')).toBe(true); // Newline
79
+ expect(isNonEmptyString('\t')).toBe(true); // Tab
80
+ expect(isNonEmptyString('\r')).toBe(true); // Carriage return
81
+ expect(isNonEmptyString('\0')).toBe(true); // Null character
82
+ expect(isNonEmptyString('🎉')).toBe(true); // Emoji
83
+ expect(isNonEmptyString('你好')).toBe(true); // Unicode characters
84
+ });
85
+
86
+ test('should not accept String objects', () => {
87
+ // @ts-expect-error Testing non-string types
88
+ // eslint-disable-next-line no-new-wrappers
89
+ expect(isNonEmptyString(new String('hello') as unknown)).toBe(false);
90
+ });
91
+ });
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Type guard that checks if a value is a non-null object.
3
+ *
4
+ * This function checks if a value has type `"object"` according to the `typeof` operator
5
+ * and is not `null`. This includes all object types such as plain objects, arrays, dates,
6
+ * regular expressions, and other object instances, but excludes functions.
7
+ *
8
+ * **Type Narrowing Behavior:**
9
+ * - Narrows `unknown` to `object`
10
+ * - Excludes `null`, `undefined`, and all primitive types
11
+ * - Excludes functions (they have `typeof` === `"function"`, not `"object"`)
12
+ * - Includes arrays, dates, regex, and other object instances
13
+ *
14
+ * **Note:** This function returns `true` for arrays. If you need to check for plain objects
15
+ * specifically (excluding arrays), use `isRecord()` instead.
16
+ *
17
+ * @param u - The value to check
18
+ * @returns `true` if `u` is an object and not `null`, `false` otherwise.
19
+ * When `true`, TypeScript narrows the type to `object`.
20
+ *
21
+ * @example
22
+ * Basic usage with different value types:
23
+ * ```typescript
24
+ * isNonNullObject({}); // true (plain object)
25
+ * isNonNullObject([]); // true (arrays are objects)
26
+ * isNonNullObject(new Date()); // true (Date instance)
27
+ * isNonNullObject(/regex/); // true (RegExp instance)
28
+ * isNonNullObject(new Map()); // true (Map instance)
29
+ * isNonNullObject(null); // false (null is not considered object here)
30
+ * isNonNullObject(undefined); // false (primitive)
31
+ * isNonNullObject("string"); // false (primitive)
32
+ * isNonNullObject(42); // false (primitive)
33
+ * isNonNullObject(true); // false (primitive)
34
+ * isNonNullObject(() => {}); // false (functions are not objects in this context)
35
+ * ```
36
+ *
37
+ * @example
38
+ * Type guard usage with unknown values:
39
+ * ```typescript
40
+ * const value: unknown = parseJsonData();
41
+ *
42
+ * if (isNonNullObject(value)) {
43
+ * // value is now typed as object
44
+ * console.log('Value is an object');
45
+ *
46
+ * // You can now safely use object-specific operations
47
+ * console.log(Object.keys(value)); // Safe to call Object.keys
48
+ * console.log(value.toString()); // Safe to call methods
49
+ *
50
+ * // But you may need additional checks for specific object types
51
+ * if (Array.isArray(value)) {
52
+ * console.log('It\'s an array with length:', value.length);
53
+ * }
54
+ * } else {
55
+ * console.log('Value is not an object');
56
+ * }
57
+ * ```
58
+ *
59
+ * @example
60
+ * Filtering arrays to find objects:
61
+ * ```typescript
62
+ * const mixedArray: unknown[] = [
63
+ * { name: 'John' },
64
+ * 'string',
65
+ * [1, 2, 3],
66
+ * 42,
67
+ * null,
68
+ * new Date(),
69
+ * () => 'function'
70
+ * ];
71
+ *
72
+ * const objects = mixedArray.filter(isNonNullObject);
73
+ * // objects contains: [{ name: 'John' }, [1, 2, 3], Date instance]
74
+ * // Note: includes both plain objects and arrays
75
+ *
76
+ * objects.forEach(obj => {
77
+ * // Each obj is guaranteed to be an object
78
+ * console.log('Object type:', obj.constructor.name);
79
+ * });
80
+ * ```
81
+ *
82
+ * @example
83
+ * Progressive type narrowing with other guards:
84
+ * ```typescript
85
+ * const apiResponse: unknown = await fetchData();
86
+ *
87
+ * if (isNonNullObject(apiResponse)) {
88
+ * // apiResponse is now object
89
+ *
90
+ * if (isRecord(apiResponse)) {
91
+ * // Further narrowed to UnknownRecord (plain object, not array)
92
+ *
93
+ * if (hasKey(apiResponse, 'status')) {
94
+ * console.log('API status:', apiResponse.status);
95
+ * }
96
+ * } else if (Array.isArray(apiResponse)) {
97
+ * console.log('Response is an array with length:', apiResponse.length);
98
+ * }
99
+ * }
100
+ * ```
101
+ *
102
+ * @see {@link isRecord} - For checking plain objects specifically (excludes arrays)
103
+ */
104
+ // eslint-disable-next-line @typescript-eslint/no-restricted-types
105
+ export const isNonNullObject = (u: unknown): u is object =>
106
+ typeof u === 'object' && u !== null;