ts-fortress 4.2.0 → 4.3.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 (38) hide show
  1. package/dist/entry-point.mjs +2 -1
  2. package/dist/entry-point.mjs.map +1 -1
  3. package/dist/index.mjs +2 -1
  4. package/dist/index.mjs.map +1 -1
  5. package/dist/record/index.d.mts +1 -0
  6. package/dist/record/index.d.mts.map +1 -1
  7. package/dist/record/index.mjs +1 -0
  8. package/dist/record/index.mjs.map +1 -1
  9. package/dist/record/omit.mjs +2 -2
  10. package/dist/record/omit.mjs.map +1 -1
  11. package/dist/record/optional.d.mts +1 -0
  12. package/dist/record/optional.d.mts.map +1 -1
  13. package/dist/record/optional.mjs.map +1 -1
  14. package/dist/record/partial.mjs +2 -2
  15. package/dist/record/partial.mjs.map +1 -1
  16. package/dist/record/pick.mjs +2 -2
  17. package/dist/record/pick.mjs.map +1 -1
  18. package/dist/record/required.d.mts +22 -0
  19. package/dist/record/required.d.mts.map +1 -0
  20. package/dist/record/required.mjs +33 -0
  21. package/dist/record/required.mjs.map +1 -0
  22. package/dist/utils/index.mjs +1 -1
  23. package/dist/utils/to-union-string.d.mts +1 -0
  24. package/dist/utils/to-union-string.d.mts.map +1 -1
  25. package/dist/utils/to-union-string.mjs +4 -3
  26. package/dist/utils/to-union-string.mjs.map +1 -1
  27. package/package.json +1 -1
  28. package/src/compose/intersection.test.mts +10 -11
  29. package/src/compose/merge-records.test.mts +2 -3
  30. package/src/compose/union.test.mts +2 -3
  31. package/src/record/index.mts +1 -0
  32. package/src/record/omit.mts +2 -2
  33. package/src/record/optional.mts +5 -0
  34. package/src/record/partial.mts +3 -3
  35. package/src/record/pick.mts +2 -2
  36. package/src/record/required.mts +246 -0
  37. package/src/record/required.test.mts +600 -0
  38. package/src/utils/to-union-string.mts +5 -2
@@ -0,0 +1,246 @@
1
+ import { expectType, Obj } from 'ts-data-forge';
2
+ import { type RecordType, type Type } from '../type.mjs';
3
+ import { toUnionKeyString } from '../utils/index.mjs';
4
+ import {
5
+ isOptionalProperty,
6
+ type OptionalPropertyType,
7
+ type RequiredPropertyType,
8
+ } from './optional.mjs';
9
+ import { record } from './record.mjs';
10
+
11
+ /**
12
+ * Creates a Required type. If keysToBeRequired is set, only those keys are
13
+ * made required, otherwise, all properties are made required.
14
+ */
15
+ export const required = <
16
+ const R extends ReadonlyRecord<string, Type<unknown>>,
17
+ const KeysToBeRequired extends NonEmptyArray<keyof R & string>,
18
+ >(
19
+ recordType: RecordType<R>,
20
+ options?: PartialReadonly<{
21
+ keysToBeRequired: KeysToBeRequired;
22
+ typeName: string;
23
+
24
+ /** @default true */
25
+ allowExcessProperties: boolean;
26
+ }>,
27
+ ): RequiredType<R, KeysToBeRequired> => {
28
+ const typeNameFilled: string =
29
+ options?.typeName ??
30
+ (options?.keysToBeRequired === undefined
31
+ ? `Required<${recordType.typeName}>`
32
+ : `PartiallyRequired<${recordType.typeName}, ${toUnionKeyString(options.keysToBeRequired)}>`);
33
+
34
+ const keysToBeRequired: ReadonlySet<string> = new Set(
35
+ options?.keysToBeRequired ?? Object.keys(recordType.shape),
36
+ );
37
+
38
+ const requiredShape =
39
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
40
+ Object.fromEntries(
41
+ Object.entries(recordType.shape).map(
42
+ ([k, v]) => [k, keysToBeRequired.has(k) ? makeRequired(v) : v] as const,
43
+ ),
44
+ ) satisfies ReadonlyRecord<string, Type<unknown>> as RequiredTypeShape<
45
+ R,
46
+ KeysToBeRequired
47
+ >;
48
+
49
+ return record(requiredShape, {
50
+ typeName: typeNameFilled,
51
+ allowExcessProperties:
52
+ options?.allowExcessProperties ?? recordType.allowExcessProperties,
53
+ });
54
+ };
55
+
56
+ /**
57
+ * Makes an optional property required by removing the optional flag.
58
+ */
59
+ const makeRequired = <T extends Type<unknown>>(
60
+ t: T,
61
+ ): RequiredPropertyType<T> =>
62
+ isOptionalProperty(t)
63
+ ? // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
64
+ (Obj.omit(t, ['optional']) as RequiredPropertyType<T>)
65
+ : t;
66
+
67
+ type RequiredTypeShape<
68
+ R extends ReadonlyRecord<string, Type<unknown>>,
69
+ KeysToBeRequired extends NonEmptyArray<keyof R & string> | undefined,
70
+ > =
71
+ TypeEq<KeysToBeRequired, undefined> extends true
72
+ ? FullyRequiredType<R>
73
+ : PartiallyRequiredType<R, ArrayElement<KeysToBeRequired>>;
74
+
75
+ type FullyRequiredType<R extends ReadonlyRecord<string, Type<unknown>>> = {
76
+ readonly [P in keyof R]: RequiredPropertyType<R[P]>;
77
+ };
78
+
79
+ type PartiallyRequiredType<
80
+ R extends ReadonlyRecord<string, Type<unknown>>,
81
+ K extends keyof R,
82
+ > = {
83
+ readonly [P in keyof R]: P extends K ? RequiredPropertyType<R[P]> : R[P];
84
+ };
85
+
86
+ export type RequiredType<
87
+ R extends ReadonlyRecord<string, Type<unknown>>,
88
+ KeysToBeRequired extends NonEmptyArray<keyof R & string> | undefined,
89
+ > = RecordType<RequiredTypeShape<R, KeysToBeRequired>>;
90
+
91
+ expectType<
92
+ RequiredTypeShape<
93
+ {
94
+ a: OptionalPropertyType<Type<0>>;
95
+ b: Type<1>;
96
+ c: OptionalPropertyType<Type<2>>;
97
+ },
98
+ ['a']
99
+ >,
100
+ Readonly<{
101
+ a: Type<0>;
102
+ b: Type<1>;
103
+ c: OptionalPropertyType<Type<2>>;
104
+ }>
105
+ >('=');
106
+
107
+ expectType<
108
+ RecordType<
109
+ Readonly<{
110
+ a: Type<0>;
111
+ b: Type<1>;
112
+ c: Type<2>;
113
+ }>
114
+ >,
115
+ Type<
116
+ Readonly<{
117
+ a: 0;
118
+ b: 1;
119
+ c: 2;
120
+ }>
121
+ > &
122
+ Readonly<{
123
+ shape: Readonly<{
124
+ a: Type<0>;
125
+ b: Type<1>;
126
+ c: Type<2>;
127
+ }>;
128
+ allowExcessProperties: boolean;
129
+ }>
130
+ >('=');
131
+
132
+ expectType<
133
+ RecordType<
134
+ Readonly<{
135
+ a: Type<0>;
136
+ b: Type<1>;
137
+ c: OptionalPropertyType<Type<2>>;
138
+ }>
139
+ >,
140
+ Type<
141
+ Readonly<{
142
+ a: 0;
143
+ b: 1;
144
+ c?: 2;
145
+ }>
146
+ > &
147
+ Readonly<{
148
+ shape: Readonly<{
149
+ a: Type<0>;
150
+ b: Type<1>;
151
+ c: OptionalPropertyType<Type<2>>;
152
+ }>;
153
+ allowExcessProperties: boolean;
154
+ }>
155
+ >('=');
156
+
157
+ expectType<
158
+ RequiredType<
159
+ {
160
+ a: OptionalPropertyType<Type<0>>;
161
+ b: Type<1>;
162
+ c: OptionalPropertyType<Type<2>>;
163
+ },
164
+ ['a']
165
+ >,
166
+ Type<
167
+ Readonly<{
168
+ a: 0;
169
+ b: 1;
170
+ c?: 2;
171
+ }>
172
+ > &
173
+ Readonly<{
174
+ shape: Readonly<{
175
+ a: Type<0>;
176
+ b: Type<1>;
177
+ c: OptionalPropertyType<Type<2>>;
178
+ }>;
179
+ allowExcessProperties: boolean;
180
+ }>
181
+ >('=');
182
+
183
+ expectType<
184
+ RequiredType<
185
+ {
186
+ a: OptionalPropertyType<Type<0>>;
187
+ b: OptionalPropertyType<Type<1>>;
188
+ c: Type<2>;
189
+ },
190
+ ['a', 'b']
191
+ >,
192
+ Type<
193
+ Readonly<{
194
+ a: 0;
195
+ b: 1;
196
+ c: 2;
197
+ }>
198
+ > &
199
+ Readonly<{
200
+ shape: Readonly<{
201
+ a: Type<0>;
202
+ b: Type<1>;
203
+ c: Type<2>;
204
+ }>;
205
+ allowExcessProperties: boolean;
206
+ }>
207
+ >('=');
208
+
209
+ expectType<
210
+ RequiredType<
211
+ {
212
+ a: OptionalPropertyType<Type<0>>;
213
+ b: OptionalPropertyType<Type<1>>;
214
+ c: OptionalPropertyType<Type<2>>;
215
+ },
216
+ undefined
217
+ >,
218
+ Type<
219
+ Readonly<{
220
+ a: 0;
221
+ b: 1;
222
+ c: 2;
223
+ }>
224
+ > &
225
+ Readonly<{
226
+ shape: Readonly<{
227
+ a: Type<0>;
228
+ b: Type<1>;
229
+ c: Type<2>;
230
+ }>;
231
+ allowExcessProperties: boolean;
232
+ }>
233
+ >('=');
234
+
235
+ expectType<
236
+ RequiredType<
237
+ {
238
+ a: OptionalPropertyType<Type<0>>;
239
+ b: Type<1>;
240
+ c: OptionalPropertyType<Type<2>>;
241
+ },
242
+ // @ts-expect-error key "d" doesn't exist
243
+ ['a', 'd']
244
+ >,
245
+ 0
246
+ >('!=');