Skip to content

Commit fbbb8ba

Browse files
unrevised6419grigorischristainas
authored andcommitted
Schema: Add recurseIntoArrays option (#960)
Co-authored-by: Grigoris Christainas <[email protected]>
1 parent d7b692b commit fbbb8ba

File tree

3 files changed

+125
-8
lines changed

3 files changed

+125
-8
lines changed

index.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export type {SimplifyDeep} from './source/simplify-deep';
6666
export type {Jsonify} from './source/jsonify';
6767
export type {Jsonifiable} from './source/jsonifiable';
6868
export type {StructuredCloneable} from './source/structured-cloneable';
69-
export type {Schema} from './source/schema';
69+
export type {Schema, SchemaOptions} from './source/schema';
7070
export type {LiteralToPrimitive} from './source/literal-to-primitive';
7171
export type {LiteralToPrimitiveDeep} from './source/literal-to-primitive-deep';
7272
export type {

source/schema.d.ts

+50-7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ interface User {
1919
created: Date;
2020
active: boolean;
2121
passwordHash: string;
22+
attributes: ['Foo', 'Bar']
2223
}
2324
2425
type UserMask = Schema<User, 'mask' | 'hide' | 'show'>;
@@ -32,12 +33,13 @@ const userMaskSettings: UserMask = {
3233
created: 'show',
3334
active: 'show',
3435
passwordHash: 'hide',
36+
attributes: ['mask', 'show']
3537
}
3638
```
3739
3840
@category Object
3941
*/
40-
export type Schema<ObjectType, ValueType> = ObjectType extends string
42+
export type Schema<ObjectType, ValueType, Options extends SchemaOptions = {}> = ObjectType extends string
4143
? ValueType
4244
: ObjectType extends Map<unknown, unknown>
4345
? ValueType
@@ -48,7 +50,9 @@ export type Schema<ObjectType, ValueType> = ObjectType extends string
4850
: ObjectType extends ReadonlySet<unknown>
4951
? ValueType
5052
: ObjectType extends Array<infer U>
51-
? Array<Schema<U, ValueType>>
53+
? Options['recurseIntoArrays'] extends false | undefined
54+
? ValueType
55+
: Array<Schema<U, ValueType>>
5256
: ObjectType extends (...arguments_: unknown[]) => unknown
5357
? ValueType
5458
: ObjectType extends Date
@@ -58,14 +62,53 @@ export type Schema<ObjectType, ValueType> = ObjectType extends string
5862
: ObjectType extends RegExp
5963
? ValueType
6064
: ObjectType extends object
61-
? SchemaObject<ObjectType, ValueType>
65+
? SchemaObject<ObjectType, ValueType, Options>
6266
: ValueType;
6367

6468
/**
6569
Same as `Schema`, but accepts only `object`s as inputs. Internal helper for `Schema`.
6670
*/
67-
type SchemaObject<ObjectType extends object, K> = {
68-
[KeyType in keyof ObjectType]: ObjectType[KeyType] extends readonly unknown[] | unknown[]
69-
? Schema<ObjectType[KeyType], K>
70-
: Schema<ObjectType[KeyType], K> | K;
71+
type SchemaObject<
72+
ObjectType extends object,
73+
K,
74+
Options extends SchemaOptions,
75+
> = {
76+
[KeyType in keyof ObjectType]: ObjectType[KeyType] extends
77+
| readonly unknown[]
78+
| unknown[]
79+
? Options['recurseIntoArrays'] extends false | undefined
80+
? K
81+
: Schema<ObjectType[KeyType], K, Options>
82+
: Schema<ObjectType[KeyType], K, Options> | K;
83+
};
84+
85+
/**
86+
@see Schema
87+
*/
88+
export type SchemaOptions = {
89+
/**
90+
By default, this affects elements in array and tuple types. You can change this by passing `{recurseIntoArrays: false}` as the third type argument:
91+
- If `recurseIntoArrays` is set to `true` (default), array elements will be recursively processed as well.
92+
- If `recurseIntoArrays` is set to `false`, arrays will not be recursively processed, and the entire array will be replaced with the given value type.
93+
94+
@example
95+
```
96+
type UserMask = Schema<User, 'mask' | 'hide' | 'show', {recurseIntoArrays: false}>;
97+
98+
const userMaskSettings: UserMask = {
99+
id: 'show',
100+
name: {
101+
firstname: 'show',
102+
lastname: 'mask',
103+
},
104+
created: 'show',
105+
active: 'show',
106+
passwordHash: 'hide',
107+
attributes: 'hide'
108+
}
109+
```
110+
111+
@default true
112+
*/
113+
readonly recurseIntoArrays?: boolean | undefined;
71114
};

test-d/schema.ts

+74
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,77 @@ expectType<ComplexOption>(complexBarSchema.readonlySet);
126126
expectType<readonly ComplexOption[]>(complexBarSchema.readonlyArray);
127127
expectType<readonly [ComplexOption]>(complexBarSchema.readonlyTuple);
128128
expectType<ComplexOption>(complexBarSchema.regExp);
129+
130+
// With Options and `recurseIntoArrays` set to `false`
131+
type FooSchemaWithOptionsNoRecurse = Schema<typeof foo, FooOption, {recurseIntoArrays: false | undefined}>;
132+
133+
const fooSchemaWithOptionsNoRecurse: FooSchemaWithOptionsNoRecurse = {
134+
baz: 'A',
135+
bar: {
136+
function: 'A',
137+
object: {key: 'A'},
138+
string: 'A',
139+
number: 'A',
140+
boolean: 'A',
141+
symbol: 'A',
142+
map: 'A',
143+
set: 'A',
144+
array: 'A',
145+
tuple: 'A',
146+
objectArray: 'A',
147+
readonlyMap: 'A',
148+
readonlySet: 'A',
149+
readonlyArray: 'A' as const,
150+
readonlyTuple: 'A' as const,
151+
regExp: 'A',
152+
},
153+
};
154+
155+
expectNotAssignable<FooSchemaWithOptionsNoRecurse>(foo);
156+
expectNotAssignable<FooSchemaWithOptionsNoRecurse>({key: 'value'});
157+
expectNotAssignable<FooSchemaWithOptionsNoRecurse>(new Date());
158+
expectType<FooOption>(fooSchemaWithOptionsNoRecurse.baz);
159+
160+
const barSchemaWithOptionsNoRecurse = fooSchemaWithOptionsNoRecurse.bar as Schema<typeof foo['bar'], FooOption, {recurseIntoArrays: false | undefined}>;
161+
expectType<FooOption>(barSchemaWithOptionsNoRecurse.function);
162+
expectType<FooOption | {key: FooOption}>(barSchemaWithOptionsNoRecurse.object);
163+
expectType<FooOption>(barSchemaWithOptionsNoRecurse.string);
164+
expectType<FooOption>(barSchemaWithOptionsNoRecurse.number);
165+
expectType<FooOption>(barSchemaWithOptionsNoRecurse.boolean);
166+
expectType<FooOption>(barSchemaWithOptionsNoRecurse.symbol);
167+
expectType<FooOption>(barSchemaWithOptionsNoRecurse.map);
168+
expectType<FooOption>(barSchemaWithOptionsNoRecurse.set);
169+
expectType<FooOption>(barSchemaWithOptionsNoRecurse.array);
170+
expectType<FooOption>(barSchemaWithOptionsNoRecurse.tuple);
171+
expectType<FooOption>(barSchemaWithOptionsNoRecurse.objectArray);
172+
expectType<FooOption>(barSchemaWithOptionsNoRecurse.readonlyMap);
173+
expectType<FooOption>(barSchemaWithOptionsNoRecurse.readonlySet);
174+
expectType<FooOption>(barSchemaWithOptionsNoRecurse.readonlyArray);
175+
expectType<FooOption>(barSchemaWithOptionsNoRecurse.readonlyTuple);
176+
expectType<FooOption>(barSchemaWithOptionsNoRecurse.regExp);
177+
178+
// With Options and `recurseIntoArrays` set to `true`
179+
type FooSchemaWithOptionsRecurse = Schema<typeof foo, FooOption, {recurseIntoArrays: true}>;
180+
181+
expectNotAssignable<FooSchemaWithOptionsRecurse>(foo);
182+
expectNotAssignable<FooSchemaWithOptionsRecurse>({key: 'value'});
183+
expectNotAssignable<FooSchemaWithOptionsRecurse>(new Date());
184+
expectType<FooOption>(fooSchema.baz);
185+
186+
const barSchemaWithOptionsRecurse = fooSchema.bar as Schema<typeof foo['bar'], FooOption, {recurseIntoArrays: true}>;
187+
expectType<FooOption>(barSchemaWithOptionsRecurse.function);
188+
expectType<FooOption | {key: FooOption}>(barSchemaWithOptionsRecurse.object);
189+
expectType<FooOption>(barSchemaWithOptionsRecurse.string);
190+
expectType<FooOption>(barSchemaWithOptionsRecurse.number);
191+
expectType<FooOption>(barSchemaWithOptionsRecurse.boolean);
192+
expectType<FooOption>(barSchemaWithOptionsRecurse.symbol);
193+
expectType<FooOption>(barSchemaWithOptionsRecurse.map);
194+
expectType<FooOption>(barSchemaWithOptionsRecurse.set);
195+
expectType<FooOption[]>(barSchemaWithOptionsRecurse.array);
196+
expectType<FooOption[]>(barSchemaWithOptionsRecurse.tuple);
197+
expectType<Array<{key: FooOption}>>(barSchemaWithOptionsRecurse.objectArray);
198+
expectType<FooOption>(barSchemaWithOptionsRecurse.readonlyMap);
199+
expectType<FooOption>(barSchemaWithOptionsRecurse.readonlySet);
200+
expectType<readonly FooOption[]>(barSchemaWithOptionsRecurse.readonlyArray);
201+
expectType<readonly [FooOption]>(barSchemaWithOptionsRecurse.readonlyTuple);
202+
expectType<FooOption>(barSchemaWithOptionsRecurse.regExp);

0 commit comments

Comments
 (0)