Schema: Add recurseIntoArrays option (#960)

Co-authored-by: Grigoris Christainas <grigorisxristainas@gmail.com>
This commit is contained in:
Andrei Luca 2024-11-27 12:36:20 +02:00 committed by Sindre Sorhus
parent d7b692bb04
commit fbbb8ba53b
3 changed files with 125 additions and 8 deletions

2
index.d.ts vendored
View File

@ -66,7 +66,7 @@ export type {SimplifyDeep} from './source/simplify-deep';
export type {Jsonify} from './source/jsonify';
export type {Jsonifiable} from './source/jsonifiable';
export type {StructuredCloneable} from './source/structured-cloneable';
export type {Schema} from './source/schema';
export type {Schema, SchemaOptions} from './source/schema';
export type {LiteralToPrimitive} from './source/literal-to-primitive';
export type {LiteralToPrimitiveDeep} from './source/literal-to-primitive-deep';
export type {

57
source/schema.d.ts vendored
View File

@ -19,6 +19,7 @@ interface User {
created: Date;
active: boolean;
passwordHash: string;
attributes: ['Foo', 'Bar']
}
type UserMask = Schema<User, 'mask' | 'hide' | 'show'>;
@ -32,12 +33,13 @@ const userMaskSettings: UserMask = {
created: 'show',
active: 'show',
passwordHash: 'hide',
attributes: ['mask', 'show']
}
```
@category Object
*/
export type Schema<ObjectType, ValueType> = ObjectType extends string
export type Schema<ObjectType, ValueType, Options extends SchemaOptions = {}> = ObjectType extends string
? ValueType
: ObjectType extends Map<unknown, unknown>
? ValueType
@ -48,7 +50,9 @@ export type Schema<ObjectType, ValueType> = ObjectType extends string
: ObjectType extends ReadonlySet<unknown>
? ValueType
: ObjectType extends Array<infer U>
? Array<Schema<U, ValueType>>
? Options['recurseIntoArrays'] extends false | undefined
? ValueType
: Array<Schema<U, ValueType>>
: ObjectType extends (...arguments_: unknown[]) => unknown
? ValueType
: ObjectType extends Date
@ -58,14 +62,53 @@ export type Schema<ObjectType, ValueType> = ObjectType extends string
: ObjectType extends RegExp
? ValueType
: ObjectType extends object
? SchemaObject<ObjectType, ValueType>
? SchemaObject<ObjectType, ValueType, Options>
: ValueType;
/**
Same as `Schema`, but accepts only `object`s as inputs. Internal helper for `Schema`.
*/
type SchemaObject<ObjectType extends object, K> = {
[KeyType in keyof ObjectType]: ObjectType[KeyType] extends readonly unknown[] | unknown[]
? Schema<ObjectType[KeyType], K>
: Schema<ObjectType[KeyType], K> | K;
type SchemaObject<
ObjectType extends object,
K,
Options extends SchemaOptions,
> = {
[KeyType in keyof ObjectType]: ObjectType[KeyType] extends
| readonly unknown[]
| unknown[]
? Options['recurseIntoArrays'] extends false | undefined
? K
: Schema<ObjectType[KeyType], K, Options>
: Schema<ObjectType[KeyType], K, Options> | K;
};
/**
@see Schema
*/
export type SchemaOptions = {
/**
By default, this affects elements in array and tuple types. You can change this by passing `{recurseIntoArrays: false}` as the third type argument:
- If `recurseIntoArrays` is set to `true` (default), array elements will be recursively processed as well.
- If `recurseIntoArrays` is set to `false`, arrays will not be recursively processed, and the entire array will be replaced with the given value type.
@example
```
type UserMask = Schema<User, 'mask' | 'hide' | 'show', {recurseIntoArrays: false}>;
const userMaskSettings: UserMask = {
id: 'show',
name: {
firstname: 'show',
lastname: 'mask',
},
created: 'show',
active: 'show',
passwordHash: 'hide',
attributes: 'hide'
}
```
@default true
*/
readonly recurseIntoArrays?: boolean | undefined;
};

View File

@ -126,3 +126,77 @@ expectType<ComplexOption>(complexBarSchema.readonlySet);
expectType<readonly ComplexOption[]>(complexBarSchema.readonlyArray);
expectType<readonly [ComplexOption]>(complexBarSchema.readonlyTuple);
expectType<ComplexOption>(complexBarSchema.regExp);
// With Options and `recurseIntoArrays` set to `false`
type FooSchemaWithOptionsNoRecurse = Schema<typeof foo, FooOption, {recurseIntoArrays: false | undefined}>;
const fooSchemaWithOptionsNoRecurse: FooSchemaWithOptionsNoRecurse = {
baz: 'A',
bar: {
function: 'A',
object: {key: 'A'},
string: 'A',
number: 'A',
boolean: 'A',
symbol: 'A',
map: 'A',
set: 'A',
array: 'A',
tuple: 'A',
objectArray: 'A',
readonlyMap: 'A',
readonlySet: 'A',
readonlyArray: 'A' as const,
readonlyTuple: 'A' as const,
regExp: 'A',
},
};
expectNotAssignable<FooSchemaWithOptionsNoRecurse>(foo);
expectNotAssignable<FooSchemaWithOptionsNoRecurse>({key: 'value'});
expectNotAssignable<FooSchemaWithOptionsNoRecurse>(new Date());
expectType<FooOption>(fooSchemaWithOptionsNoRecurse.baz);
const barSchemaWithOptionsNoRecurse = fooSchemaWithOptionsNoRecurse.bar as Schema<typeof foo['bar'], FooOption, {recurseIntoArrays: false | undefined}>;
expectType<FooOption>(barSchemaWithOptionsNoRecurse.function);
expectType<FooOption | {key: FooOption}>(barSchemaWithOptionsNoRecurse.object);
expectType<FooOption>(barSchemaWithOptionsNoRecurse.string);
expectType<FooOption>(barSchemaWithOptionsNoRecurse.number);
expectType<FooOption>(barSchemaWithOptionsNoRecurse.boolean);
expectType<FooOption>(barSchemaWithOptionsNoRecurse.symbol);
expectType<FooOption>(barSchemaWithOptionsNoRecurse.map);
expectType<FooOption>(barSchemaWithOptionsNoRecurse.set);
expectType<FooOption>(barSchemaWithOptionsNoRecurse.array);
expectType<FooOption>(barSchemaWithOptionsNoRecurse.tuple);
expectType<FooOption>(barSchemaWithOptionsNoRecurse.objectArray);
expectType<FooOption>(barSchemaWithOptionsNoRecurse.readonlyMap);
expectType<FooOption>(barSchemaWithOptionsNoRecurse.readonlySet);
expectType<FooOption>(barSchemaWithOptionsNoRecurse.readonlyArray);
expectType<FooOption>(barSchemaWithOptionsNoRecurse.readonlyTuple);
expectType<FooOption>(barSchemaWithOptionsNoRecurse.regExp);
// With Options and `recurseIntoArrays` set to `true`
type FooSchemaWithOptionsRecurse = Schema<typeof foo, FooOption, {recurseIntoArrays: true}>;
expectNotAssignable<FooSchemaWithOptionsRecurse>(foo);
expectNotAssignable<FooSchemaWithOptionsRecurse>({key: 'value'});
expectNotAssignable<FooSchemaWithOptionsRecurse>(new Date());
expectType<FooOption>(fooSchema.baz);
const barSchemaWithOptionsRecurse = fooSchema.bar as Schema<typeof foo['bar'], FooOption, {recurseIntoArrays: true}>;
expectType<FooOption>(barSchemaWithOptionsRecurse.function);
expectType<FooOption | {key: FooOption}>(barSchemaWithOptionsRecurse.object);
expectType<FooOption>(barSchemaWithOptionsRecurse.string);
expectType<FooOption>(barSchemaWithOptionsRecurse.number);
expectType<FooOption>(barSchemaWithOptionsRecurse.boolean);
expectType<FooOption>(barSchemaWithOptionsRecurse.symbol);
expectType<FooOption>(barSchemaWithOptionsRecurse.map);
expectType<FooOption>(barSchemaWithOptionsRecurse.set);
expectType<FooOption[]>(barSchemaWithOptionsRecurse.array);
expectType<FooOption[]>(barSchemaWithOptionsRecurse.tuple);
expectType<Array<{key: FooOption}>>(barSchemaWithOptionsRecurse.objectArray);
expectType<FooOption>(barSchemaWithOptionsRecurse.readonlyMap);
expectType<FooOption>(barSchemaWithOptionsRecurse.readonlySet);
expectType<readonly FooOption[]>(barSchemaWithOptionsRecurse.readonlyArray);
expectType<readonly [FooOption]>(barSchemaWithOptionsRecurse.readonlyTuple);
expectType<FooOption>(barSchemaWithOptionsRecurse.regExp);