ts-fortress 3.2.0 → 4.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 (49) hide show
  1. package/dist/entry-point.mjs +1 -0
  2. package/dist/entry-point.mjs.map +1 -1
  3. package/dist/index.mjs +2 -0
  4. package/dist/index.mjs.map +1 -1
  5. package/dist/record/keyof.d.mts +4 -3
  6. package/dist/record/keyof.d.mts.map +1 -1
  7. package/dist/record/keyof.mjs +5 -1
  8. package/dist/record/keyof.mjs.map +1 -1
  9. package/dist/record/omit.d.mts +5 -4
  10. package/dist/record/omit.d.mts.map +1 -1
  11. package/dist/record/omit.mjs +8 -39
  12. package/dist/record/omit.mjs.map +1 -1
  13. package/dist/record/partial.d.mts +13 -4
  14. package/dist/record/partial.d.mts.map +1 -1
  15. package/dist/record/partial.mjs +11 -118
  16. package/dist/record/partial.mjs.map +1 -1
  17. package/dist/record/pick.d.mts +5 -4
  18. package/dist/record/pick.d.mts.map +1 -1
  19. package/dist/record/pick.mjs +8 -39
  20. package/dist/record/pick.mjs.map +1 -1
  21. package/dist/record/record.d.mts +4 -24
  22. package/dist/record/record.d.mts.map +1 -1
  23. package/dist/record/record.mjs +9 -7
  24. package/dist/record/record.mjs.map +1 -1
  25. package/dist/type.d.mts +25 -1
  26. package/dist/type.d.mts.map +1 -1
  27. package/dist/type.mjs +1 -1
  28. package/dist/utils/index.d.mts +1 -0
  29. package/dist/utils/index.d.mts.map +1 -1
  30. package/dist/utils/index.mjs +1 -0
  31. package/dist/utils/index.mjs.map +1 -1
  32. package/dist/utils/to-union-string.d.mts +2 -0
  33. package/dist/utils/to-union-string.d.mts.map +1 -0
  34. package/dist/utils/to-union-string.mjs +4 -0
  35. package/dist/utils/to-union-string.mjs.map +1 -0
  36. package/package.json +1 -1
  37. package/src/record/keyof.mts +34 -12
  38. package/src/record/omit.mts +37 -70
  39. package/src/record/omit.test.mts +8 -1
  40. package/src/record/partial.mts +165 -156
  41. package/src/record/partial.test.mts +44 -4
  42. package/src/record/pick.mts +37 -73
  43. package/src/record/pick.test.mts +8 -1
  44. package/src/record/record-strict-composition.test.mts +16 -16
  45. package/src/record/record.mts +17 -48
  46. package/src/record/record.test.mts +9 -1
  47. package/src/type.mts +54 -2
  48. package/src/utils/index.mts +1 -0
  49. package/src/utils/to-union-string.mts +2 -0
@@ -3,5 +3,6 @@ export * from './create-cast-fn.mjs';
3
3
  export * from './create-is-fn.mjs';
4
4
  export * from './create-primitive-type.mjs';
5
5
  export * from './create-type.mjs';
6
+ export * from './to-union-string.mjs';
6
7
  export * from './validation-error.mjs';
7
8
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/utils/index.mts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,mBAAmB,CAAC;AAClC,cAAc,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/utils/index.mts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC"}
@@ -3,5 +3,6 @@ export { createCastFn } from './create-cast-fn.mjs';
3
3
  export { createIsFn } from './create-is-fn.mjs';
4
4
  export { createPrimitiveType } from './create-primitive-type.mjs';
5
5
  export { createType } from './create-type.mjs';
6
+ export { toUnionString } from './to-union-string.mjs';
6
7
  export { createPrimitiveValidationError, prependIndexToValidationErrors, prependPathToValidationErrors, validationErrorToMessage, validationErrorsToMessages } from './validation-error.mjs';
7
8
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;"}
1
+ {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;"}
@@ -0,0 +1,2 @@
1
+ export declare const toUnionString: (members: readonly string[]) => string;
2
+ //# sourceMappingURL=to-union-string.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"to-union-string.d.mts","sourceRoot":"","sources":["../../src/utils/to-union-string.mts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,GAAI,SAAS,SAAS,MAAM,EAAE,KAAG,MACjB,CAAC"}
@@ -0,0 +1,4 @@
1
+ const toUnionString = (members) => members.map((k) => `"${k}"`).join(' | ');
2
+
3
+ export { toUnionString };
4
+ //# sourceMappingURL=to-union-string.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"to-union-string.mjs","sources":["../../src/utils/to-union-string.mts"],"sourcesContent":[null],"names":[],"mappings":"AAAO,MAAM,aAAa,GAAG,CAAC,OAA0B,KACtD,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAA,CAAA,EAAI,CAAC,CAAA,CAAA,CAAG,CAAC,CAAC,IAAI,CAAC,KAAK;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-fortress",
3
- "version": "3.2.0",
3
+ "version": "4.0.0",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "typescript"
@@ -1,10 +1,10 @@
1
1
  import { Arr, expectType, pipe } from 'ts-data-forge';
2
2
  import { enumType } from '../enum/index.mjs';
3
3
  import { undefinedType } from '../primitives/index.mjs';
4
- import { type Type, type TypeOf } from '../type.mjs';
4
+ import { type RecordType, type Type } from '../type.mjs';
5
5
 
6
- export const keyof = <const R extends Type<UnknownRecord>>(
7
- recordType: R,
6
+ export const keyof = <const R extends ReadonlyRecord<string, Type<unknown>>>(
7
+ recordType: RecordType<R>,
8
8
  options?: Partial<
9
9
  Readonly<{
10
10
  typeName: string;
@@ -12,25 +12,47 @@ export const keyof = <const R extends Type<UnknownRecord>>(
12
12
  >,
13
13
  ): KeyofType<R> =>
14
14
  // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
15
- pipe(Object.keys(recordType.defaultValue)).map((keys) =>
15
+ pipe(getKeys(recordType)).map((keys) =>
16
16
  Arr.isNonEmpty(keys)
17
- ? enumType(keys, {
17
+ ? (enumType(keys, {
18
18
  typeName: options?.typeName ?? `keyof ${recordType.typeName}`,
19
- })
20
- : undefinedType,
19
+ }) satisfies KeyofTypeSub<R>)
20
+ : (undefinedType satisfies Type<undefined>),
21
21
  ).value as KeyofType<R>;
22
22
 
23
- type KeyofType<R extends Type<UnknownRecord>> = Type<ToString<keyof TypeOf<R>>>;
23
+ const getKeys = <const R extends ReadonlyRecord<string, Type<unknown>>>(
24
+ recordType: RecordType<R>,
25
+ ): readonly ToString<keyof R>[] =>
26
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
27
+ Object.keys(recordType.shape) satisfies string[] as ToString<keyof R>[];
28
+
29
+ type KeyofTypeSub<R extends ReadonlyRecord<string, Type<unknown>>> = Type<
30
+ ToString<keyof R>
31
+ >;
32
+
33
+ type KeyofType<R extends ReadonlyRecord<string, Type<unknown>>> =
34
+ IsNever<keyof R> extends true ? Type<undefined> : KeyofTypeSub<R>;
35
+
36
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
37
+ expectType<keyof {}, never>('=');
38
+ expectType<ToString<keyof { 1: 1; 2: 2; 3: 3 }>, '1' | '2' | '3'>('=');
24
39
 
25
40
  // eslint-disable-next-line @typescript-eslint/no-empty-object-type
26
- expectType<KeyofType<Type<{}>>, Type<never>>('=');
41
+ expectType<KeyofType<{}>, Type<undefined>>('=');
27
42
 
28
43
  expectType<
29
- KeyofType<Type<{ a: Type<0>; b: Type<0>; c: Type<0> }>>,
44
+ KeyofType<{ a: Type<0>; b: Type<1>; c: Type<2> }>,
30
45
  Type<'a' | 'b' | 'c'>
31
46
  >('=');
32
47
 
33
48
  expectType<
34
- KeyofType<Type<{ 1: Type<0>; 2: Type<0>; 3: Type<0> }>>,
35
- Type<'1' | '2' | '3'>
49
+ KeyofType<{ x: Type<string>; y: Type<number>; z: Type<boolean> }>,
50
+ Type<'x' | 'y' | 'z'>
36
51
  >('=');
52
+
53
+ expectType<
54
+ KeyofType<{ same: Type<string>; value: Type<string> }>,
55
+ Type<'same' | 'value'>
56
+ >('=');
57
+
58
+ expectType<KeyofType<{ never: Type<never> }>, Type<'never'>>('=');
@@ -1,98 +1,65 @@
1
- import { expectType, isRecord, Obj, Result } from 'ts-data-forge';
2
- import { type Type, type TypeOf } from '../type.mjs';
3
- import {
4
- createAssertFn,
5
- createCastFn,
6
- createIsFn,
7
- createPrimitiveValidationError,
8
- } from '../utils/index.mjs';
1
+ import { expectType, Obj } from 'ts-data-forge';
2
+ import { type RecordType, type Type } from '../type.mjs';
3
+ import { toUnionString } from '../utils/index.mjs';
4
+ import { record } from './record.mjs';
9
5
 
10
6
  /** Creates a record type with keys omitted. */
11
7
  export const omit = <
12
- const R extends Type<UnknownRecord>,
13
- const KeysToOmit extends readonly (keyof TypeOf<R> & string)[],
8
+ const R extends ReadonlyRecord<string, Type<unknown>>,
9
+ const KeysToOmit extends readonly (keyof R & string)[],
14
10
  >(
15
- recordType: R,
11
+ recordType: RecordType<R>,
16
12
  keysToOmit: KeysToOmit,
17
13
  options?: Partial<
18
14
  Readonly<{
19
15
  typeName: string;
16
+
17
+ /** @default true */
18
+ allowExcessProperties: boolean;
20
19
  }>
21
20
  >,
22
- ): OmittedType<R, KeysToOmit> => {
23
- type V = Omit<TypeOf<R>, ArrayElement<KeysToOmit>>;
24
-
25
- const typeNameFilled: string =
26
- options?.typeName ??
27
- `Omit<${recordType.typeName}, ${keysToOmit.join('|')}>`;
28
-
29
- const defaultValue: V = Obj.omit(recordType.defaultValue, keysToOmit);
30
-
31
- const validate: Type<V>['validate'] = (a) => {
32
- if (!isRecord(a)) {
33
- return Result.err([
34
- createPrimitiveValidationError({
35
- actualValue: a,
36
- expectedType: 'record',
37
- typeName: typeNameFilled,
38
- }),
39
- ]);
40
- }
41
-
42
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
43
- const objectFilled = {
44
- ...Obj.pick(recordType.fill(a), keysToOmit),
45
- ...a,
46
- } as TypeOf<R>;
47
-
48
- const baseValidationResult = recordType.validate(objectFilled);
49
-
50
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
51
- return Result.map(baseValidationResult, () => a as V);
52
- };
21
+ ): OmittedType<R, KeysToOmit> =>
22
+ record(Obj.omit(recordType.shape, keysToOmit), {
23
+ typeName:
24
+ options?.typeName ??
25
+ `Omit<${recordType.typeName}, ${toUnionString(keysToOmit)}>`,
26
+ allowExcessProperties:
27
+ options?.allowExcessProperties ?? recordType.allowExcessProperties,
28
+ });
53
29
 
54
- const fill: Type<V>['fill'] = (a) => Obj.omit(recordType.fill(a), keysToOmit);
55
-
56
- return {
57
- typeName: typeNameFilled,
58
- defaultValue,
59
- fill,
60
- validate,
61
- is: createIsFn(validate),
62
- assertIs: createAssertFn(validate),
63
- cast: createCastFn(validate),
64
- };
65
- };
66
-
67
- type OmittedType<
68
- R extends Type<UnknownRecord>,
69
- KeysToOmit extends readonly (keyof TypeOf<R>)[],
70
- > = Type<Omit<TypeOf<R>, ArrayElement<KeysToOmit>>>;
30
+ export type OmittedType<
31
+ R extends ReadonlyRecord<string, Type<unknown>>,
32
+ KeysToOmit extends readonly (keyof R)[],
33
+ > = RecordType<Omit<R, ArrayElement<KeysToOmit>>>;
71
34
 
72
35
  expectType<
73
- Omit<TypeOf<Type<{ a: Type<0>; b: Type<0>; c: Type<0> }>>, 'a' | 'b'>,
36
+ Omit<{ a: Type<0>; b: Type<1>; c: Type<2> }, 'a' | 'b'>,
74
37
  {
75
- c: Type<0>;
38
+ c: Type<2>;
76
39
  }
77
40
  >('=');
78
41
 
79
42
  expectType<
80
- Type<Omit<TypeOf<Type<{ a: Type<0>; b: Type<0>; c: Type<0> }>>, 'a' | 'b'>>,
81
- Type<{
82
- c: Type<0>;
83
- }>
43
+ RecordType<Omit<{ a: Type<0>; b: Type<1>; c: Type<2> }, 'a' | 'b'>>,
44
+ Type<Readonly<{ c: 2 }>> &
45
+ Readonly<{
46
+ shape: { c: Type<2> };
47
+ allowExcessProperties: boolean;
48
+ }>
84
49
  >('=');
85
50
 
86
51
  expectType<
87
- OmittedType<Type<{ a: Type<0>; b: Type<0>; c: Type<0> }>, ['a', 'b']>,
88
- Type<{
89
- c: Type<0>;
90
- }>
52
+ OmittedType<{ a: Type<0>; b: Type<1>; c: Type<2> }, ['a', 'b']>,
53
+ Type<Readonly<{ c: 2 }>> &
54
+ Readonly<{
55
+ shape: { c: Type<2> };
56
+ allowExcessProperties: boolean;
57
+ }>
91
58
  >('=');
92
59
 
93
60
  expectType<
94
61
  OmittedType<
95
- Type<{ a: Type<0>; b: Type<0>; c: Type<0> }>,
62
+ { a: Type<0>; b: Type<1>; c: Type<2> },
96
63
  // @ts-expect-error key "d" doesn't exist
97
64
  ['a', 'd']
98
65
  >,
@@ -14,7 +14,14 @@ describe('omit', () => {
14
14
 
15
15
  const ym = omit(ymd, ['date']);
16
16
 
17
- expectType<typeof ym, Type<Readonly<{ year: number; month: number }>>>('=');
17
+ expectType<
18
+ typeof ym,
19
+ Type<Readonly<{ year: number; month: number }>> &
20
+ Readonly<{
21
+ shape: Readonly<{ year: Type<number>; month: Type<number> }>;
22
+ allowExcessProperties: boolean;
23
+ }>
24
+ >('=');
18
25
 
19
26
  type Ym = TypeOf<typeof ym>;
20
27
 
@@ -1,190 +1,199 @@
1
- import { expectType, ISet, isRecord, Obj, Result } from 'ts-data-forge';
2
- import { type Type, type TypeOf } from '../type.mjs';
3
- import {
4
- createAssertFn,
5
- createCastFn,
6
- createIsFn,
7
- createPrimitiveValidationError,
8
- } from '../utils/index.mjs';
1
+ import { expectType } from 'ts-data-forge';
2
+ import { type RecordType, type Type } from '../type.mjs';
3
+ import { toUnionString } from '../utils/index.mjs';
4
+ import { optional, type OptionalPropertyType } from './optional.mjs';
5
+ import { record } from './record.mjs';
9
6
 
10
7
  /**
11
8
  * Creates a Partial type. If keysToBeOptional is set, only those keys are
12
9
  * optional, otherwise, all properties are optional.
13
10
  */
14
11
  export const partial = <
15
- const R extends Type<UnknownRecord>,
16
- const KeysToBeOptional extends NonEmptyArray<keyof TypeOf<R> & string>,
12
+ const R extends ReadonlyRecord<string, Type<unknown>>,
13
+ const KeysToBeOptional extends NonEmptyArray<keyof R & string>,
17
14
  >(
18
- recordType: R,
15
+ recordType: RecordType<R>,
19
16
  options?: Partial<
20
17
  Readonly<{
21
18
  keysToBeOptional: KeysToBeOptional;
22
19
  typeName: string;
20
+
21
+ /** @default true */
22
+ allowExcessProperties: boolean;
23
23
  }>
24
24
  >,
25
25
  ): PartialType<R, KeysToBeOptional> => {
26
- type V = PartialTypeValue<R, KeysToBeOptional>;
27
-
28
26
  const typeNameFilled: string =
29
27
  options?.typeName ??
30
28
  (options?.keysToBeOptional === undefined
31
29
  ? `Partial<${recordType.typeName}>`
32
- : `PartiallyPartial<${recordType.typeName}, ${options.keysToBeOptional.join('|')}>`);
33
-
34
- const keysToBeOptional =
35
- options?.keysToBeOptional ?? Object.keys(recordType.defaultValue);
36
-
37
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
38
- const defaultValue: V = Obj.omit(
39
- recordType.defaultValue,
40
- keysToBeOptional,
41
- ) as V;
42
-
43
- const validate: Type<V>['validate'] = (a) => {
44
- if (!isRecord(a)) {
45
- return Result.err([
46
- createPrimitiveValidationError({
47
- actualValue: a,
48
- expectedType: 'record',
49
- typeName: typeNameFilled,
50
- }),
51
- ]);
52
- }
53
-
54
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
55
- const objectFilled = {
56
- // fill optional properties to skip check by recordType.validate
57
- ...Obj.pick(recordType.defaultValue, keysToBeOptional),
58
- // overwrite with `a`
59
- ...a,
60
- } as TypeOf<R>;
61
-
62
- const validationResult = recordType.validate(objectFilled);
63
-
64
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
65
- return Result.map(validationResult, () => a as V);
66
- };
67
-
68
- const fill: Type<V>['fill'] = (a) => {
69
- if (!isRecord(a)) {
70
- return defaultValue;
71
- }
30
+ : `PartiallyPartial<${recordType.typeName}, ${toUnionString(options.keysToBeOptional)}>`);
72
31
 
73
- const inputKeys = ISet.create(Object.keys(a));
32
+ const keysToBeOptional: ReadonlySet<string> = new Set(
33
+ options?.keysToBeOptional ?? Object.keys(recordType.shape),
34
+ );
74
35
 
36
+ const partialShape =
75
37
  // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
76
- return Obj.omit(
77
- // First, create a recursively filled object with all properties required.
78
- recordType.fill(a),
79
- // Then, Omit optional properties not present in `a`.
80
- keysToBeOptional.filter(
81
- (k) =>
82
- // avoid removing properties from `a`
83
- !inputKeys.has(k),
38
+ Object.fromEntries(
39
+ Object.entries(recordType.shape).map(
40
+ ([k, v]) => [k, keysToBeOptional.has(k) ? optional(v) : v] as const,
84
41
  ),
85
- ) as unknown as V;
86
- };
42
+ ) satisfies ReadonlyRecord<string, Type<unknown>> as PartialTypeShape<
43
+ R,
44
+ KeysToBeOptional
45
+ >;
87
46
 
88
- return {
47
+ return record(partialShape, {
89
48
  typeName: typeNameFilled,
90
- defaultValue,
91
- fill,
92
- validate,
93
- is: createIsFn(validate),
94
- assertIs: createAssertFn(validate),
95
- cast: createCastFn(validate),
96
- };
49
+ allowExcessProperties:
50
+ options?.allowExcessProperties ?? recordType.allowExcessProperties,
51
+ });
97
52
  };
98
53
 
99
- type PartialTypeValue<
100
- R extends Type<UnknownRecord>,
101
- KeysToBeOptional extends NonEmptyArray<keyof TypeOf<R> & string> | undefined,
54
+ type PartialTypeShape<
55
+ R extends ReadonlyRecord<string, Type<unknown>>,
56
+ KeysToBeOptional extends NonEmptyArray<keyof R & string> | undefined,
102
57
  > =
103
58
  TypeEq<KeysToBeOptional, undefined> extends true
104
- ? Partial<TypeOf<R>>
105
- : PartiallyPartial<TypeOf<R>, ArrayElement<KeysToBeOptional>>;
106
-
107
- type PartialType<
108
- R extends Type<UnknownRecord>,
109
- KeysToBeOptional extends NonEmptyArray<keyof TypeOf<R> & string> | undefined,
110
- > = Type<PartialTypeValue<R, KeysToBeOptional>>;
111
-
112
- // expectType<TsFortressInternal.SetOptional<Type<0>>, OptionalType<0>>('=');
113
-
114
- // expectType<
115
- // TsFortressInternal.SetOptionalForAllKey<{
116
- // a: Type<0>;
117
- // }>,
118
- // Readonly<{
119
- // a: OptionalType<0>;
120
- // }>
121
- // >('=');
122
-
123
- // expectType<
124
- // TsFortressInternal.SetOptionalForAllKey<{
125
- // a: Type<0>;
126
- // b: OptionalType<0>;
127
- // }>,
128
- // Readonly<{
129
- // a: OptionalType<0>;
130
- // b: OptionalType<0>;
131
- // }>
132
- // >('=');
133
-
134
- // expectType<
135
- // TsFortressInternal.SetOptionalForAllKey<
136
- // RecordType<{
137
- // a: OptionalType<0>;
138
- // b: OptionalType<0>;
139
- // c: Type<0>;
140
- // }>['source']
141
- // >,
142
- // Readonly<{
143
- // a: OptionalType<0>;
144
- // b: OptionalType<0>;
145
- // c: OptionalType<0>;
146
- // }>
147
- // >('=');
148
-
149
- // expectType<
150
- // PartialType<
151
- // RecordType<{
152
- // a: OptionalType<0>;
153
- // b: OptionalType<0>;
154
- // c: Type<0>;
155
- // }>,
156
- // undefined
157
- // >,
158
- // RecordType<
159
- // Readonly<{
160
- // a: OptionalType<0>;
161
- // b: OptionalType<0>;
162
- // c: OptionalType<0>;
163
- // }>
164
- // >
165
- // >('=');
166
-
167
- // expectType<
168
- // PartialType<
169
- // Type<{
170
- // a: OptionalType<0>;
171
- // b: Type<0>;
172
- // c: Type<0>;
173
- // }>,
174
- // ['b']
175
- // >,
176
- // RecordType<
177
- // Readonly<{
178
- // a: OptionalType<0>;
179
- // b: OptionalType<0>;
180
- // c: Type<0>;
181
- // }>
182
- // >
183
- // >('=');
59
+ ? FullyOptionalType<R>
60
+ : PartiallyOptionalType<R, ArrayElement<KeysToBeOptional>>;
61
+
62
+ type FullyOptionalType<R extends ReadonlyRecord<string, Type<unknown>>> = {
63
+ readonly [P in keyof R]: OptionalPropertyType<R[P]>;
64
+ };
65
+
66
+ type PartiallyOptionalType<
67
+ R extends ReadonlyRecord<string, Type<unknown>>,
68
+ K extends keyof R,
69
+ > = {
70
+ readonly [P in keyof R]: P extends K ? OptionalPropertyType<R[P]> : R[P];
71
+ };
72
+
73
+ export type PartialType<
74
+ R extends ReadonlyRecord<string, Type<unknown>>,
75
+ KeysToBeOptional extends NonEmptyArray<keyof R & string> | undefined,
76
+ > = RecordType<PartialTypeShape<R, KeysToBeOptional>>;
77
+
78
+ expectType<
79
+ PartialTypeShape<{ a: Type<0>; b: Type<1>; c: Type<2> }, ['a']>,
80
+ Readonly<{
81
+ a: OptionalPropertyType<Type<0>>;
82
+ b: Type<1>;
83
+ c: Type<2>;
84
+ }>
85
+ >('=');
86
+
87
+ expectType<
88
+ RecordType<
89
+ Readonly<{
90
+ a: Type<0>;
91
+ b: Type<1>;
92
+ c: Type<2>;
93
+ }>
94
+ >,
95
+ Type<
96
+ Readonly<{
97
+ a: 0;
98
+ b: 1;
99
+ c: 2;
100
+ }>
101
+ > &
102
+ Readonly<{
103
+ shape: Readonly<{
104
+ a: Type<0>;
105
+ b: Type<1>;
106
+ c: Type<2>;
107
+ }>;
108
+ allowExcessProperties: boolean;
109
+ }>
110
+ >('~=');
111
+
112
+ expectType<
113
+ RecordType<
114
+ Readonly<{
115
+ a: OptionalPropertyType<Type<0>>;
116
+ b: Type<1>;
117
+ c: Type<2>;
118
+ }>
119
+ >,
120
+ Type<
121
+ Readonly<{
122
+ a?: 0;
123
+ b: 1;
124
+ c: 2;
125
+ }>
126
+ > &
127
+ Readonly<{
128
+ shape: Readonly<{
129
+ a: OptionalPropertyType<Type<0>>;
130
+ b: Type<1>;
131
+ c: Type<2>;
132
+ }>;
133
+ allowExcessProperties: boolean;
134
+ }>
135
+ >('=');
136
+
137
+ expectType<
138
+ PartialType<{ a: Type<0>; b: Type<1>; c: Type<2> }, ['a']>,
139
+ Type<
140
+ Readonly<{
141
+ a?: 0;
142
+ b: 1;
143
+ c: 2;
144
+ }>
145
+ > &
146
+ Readonly<{
147
+ shape: Readonly<{
148
+ a: OptionalPropertyType<Type<0>>;
149
+ b: Type<1>;
150
+ c: Type<2>;
151
+ }>;
152
+ allowExcessProperties: boolean;
153
+ }>
154
+ >('=');
155
+
156
+ expectType<
157
+ PartialType<{ a: Type<0>; b: Type<1>; c: Type<2> }, ['a', 'b']>,
158
+ Type<
159
+ Readonly<{
160
+ a?: 0;
161
+ b?: 1;
162
+ c: 2;
163
+ }>
164
+ > &
165
+ Readonly<{
166
+ shape: Readonly<{
167
+ a: OptionalPropertyType<Type<0>>;
168
+ b: OptionalPropertyType<Type<1>>;
169
+ c: Type<2>;
170
+ }>;
171
+ allowExcessProperties: boolean;
172
+ }>
173
+ >('=');
174
+
175
+ expectType<
176
+ PartialType<{ a: Type<0>; b: Type<1>; c: Type<2> }, undefined>,
177
+ Type<
178
+ Readonly<{
179
+ a?: 0;
180
+ b?: 1;
181
+ c?: 2;
182
+ }>
183
+ > &
184
+ Readonly<{
185
+ shape: Readonly<{
186
+ a: OptionalPropertyType<Type<0>>;
187
+ b: OptionalPropertyType<Type<1>>;
188
+ c: OptionalPropertyType<Type<2>>;
189
+ }>;
190
+ allowExcessProperties: boolean;
191
+ }>
192
+ >('=');
184
193
 
185
194
  expectType<
186
195
  PartialType<
187
- Type<{ a: Type<0>; b: Type<0>; c: Type<0> }>,
196
+ { a: Type<0>; b: Type<1>; c: Type<2> },
188
197
  // @ts-expect-error key "d" doesn't exist
189
198
  ['a', 'd']
190
199
  >,