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.
- package/dist/entry-point.mjs +1 -0
- package/dist/entry-point.mjs.map +1 -1
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -1
- package/dist/record/keyof.d.mts +4 -3
- package/dist/record/keyof.d.mts.map +1 -1
- package/dist/record/keyof.mjs +5 -1
- package/dist/record/keyof.mjs.map +1 -1
- package/dist/record/omit.d.mts +5 -4
- package/dist/record/omit.d.mts.map +1 -1
- package/dist/record/omit.mjs +8 -39
- package/dist/record/omit.mjs.map +1 -1
- package/dist/record/partial.d.mts +13 -4
- package/dist/record/partial.d.mts.map +1 -1
- package/dist/record/partial.mjs +11 -118
- package/dist/record/partial.mjs.map +1 -1
- package/dist/record/pick.d.mts +5 -4
- package/dist/record/pick.d.mts.map +1 -1
- package/dist/record/pick.mjs +8 -39
- package/dist/record/pick.mjs.map +1 -1
- package/dist/record/record.d.mts +4 -24
- package/dist/record/record.d.mts.map +1 -1
- package/dist/record/record.mjs +9 -7
- package/dist/record/record.mjs.map +1 -1
- package/dist/type.d.mts +25 -1
- package/dist/type.d.mts.map +1 -1
- package/dist/type.mjs +1 -1
- package/dist/utils/index.d.mts +1 -0
- package/dist/utils/index.d.mts.map +1 -1
- package/dist/utils/index.mjs +1 -0
- package/dist/utils/index.mjs.map +1 -1
- package/dist/utils/to-union-string.d.mts +2 -0
- package/dist/utils/to-union-string.d.mts.map +1 -0
- package/dist/utils/to-union-string.mjs +4 -0
- package/dist/utils/to-union-string.mjs.map +1 -0
- package/package.json +1 -1
- package/src/record/keyof.mts +34 -12
- package/src/record/omit.mts +37 -70
- package/src/record/omit.test.mts +8 -1
- package/src/record/partial.mts +165 -156
- package/src/record/partial.test.mts +44 -4
- package/src/record/pick.mts +37 -73
- package/src/record/pick.test.mts +8 -1
- package/src/record/record-strict-composition.test.mts +16 -16
- package/src/record/record.mts +17 -48
- package/src/record/record.test.mts +9 -1
- package/src/type.mts +54 -2
- package/src/utils/index.mts +1 -0
- package/src/utils/to-union-string.mts +2 -0
package/dist/utils/index.d.mts
CHANGED
|
@@ -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"}
|
package/dist/utils/index.mjs
CHANGED
|
@@ -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
|
package/dist/utils/index.mjs.map
CHANGED
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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
package/src/record/keyof.mts
CHANGED
|
@@ -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
|
|
4
|
+
import { type RecordType, type Type } from '../type.mjs';
|
|
5
5
|
|
|
6
|
-
export const keyof = <const R extends Type<
|
|
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(
|
|
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
|
-
|
|
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<
|
|
41
|
+
expectType<KeyofType<{}>, Type<undefined>>('=');
|
|
27
42
|
|
|
28
43
|
expectType<
|
|
29
|
-
KeyofType<
|
|
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<
|
|
35
|
-
Type<'
|
|
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'>>('=');
|
package/src/record/omit.mts
CHANGED
|
@@ -1,98 +1,65 @@
|
|
|
1
|
-
import { expectType,
|
|
2
|
-
import { type
|
|
3
|
-
import {
|
|
4
|
-
|
|
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<
|
|
13
|
-
const KeysToOmit extends readonly (keyof
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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<
|
|
36
|
+
Omit<{ a: Type<0>; b: Type<1>; c: Type<2> }, 'a' | 'b'>,
|
|
74
37
|
{
|
|
75
|
-
c: Type<
|
|
38
|
+
c: Type<2>;
|
|
76
39
|
}
|
|
77
40
|
>('=');
|
|
78
41
|
|
|
79
42
|
expectType<
|
|
80
|
-
|
|
81
|
-
Type<{
|
|
82
|
-
|
|
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<
|
|
88
|
-
Type<{
|
|
89
|
-
|
|
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
|
-
|
|
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
|
>,
|
package/src/record/omit.test.mts
CHANGED
|
@@ -14,7 +14,14 @@ describe('omit', () => {
|
|
|
14
14
|
|
|
15
15
|
const ym = omit(ymd, ['date']);
|
|
16
16
|
|
|
17
|
-
expectType<
|
|
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
|
|
package/src/record/partial.mts
CHANGED
|
@@ -1,190 +1,199 @@
|
|
|
1
|
-
import { expectType
|
|
2
|
-
import { type
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
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<
|
|
16
|
-
const KeysToBeOptional extends NonEmptyArray<keyof
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
)
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
|
100
|
-
R extends Type<
|
|
101
|
-
KeysToBeOptional extends NonEmptyArray<keyof
|
|
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
|
-
?
|
|
105
|
-
:
|
|
106
|
-
|
|
107
|
-
type
|
|
108
|
-
R
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
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
|
>,
|