-
-
Notifications
You must be signed in to change notification settings - Fork 537
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Schema old behaviour back as SchemaLoose
- Loading branch information
1 parent
afb3c0a
commit 2f3f1fe
Showing
4 changed files
with
196 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
/** | ||
Create a deep version of another object type where property values are recursively replaced into a given value type. | ||
Use-cases: | ||
- Form validation: Define how each field should be validated. | ||
- Form settings: Define configuration for input fields. | ||
- Parsing: Define types that specify special behavior for specific fields. | ||
@example | ||
``` | ||
import type {SchemaLoose} from 'type-fest'; | ||
interface User { | ||
id: string; | ||
name: { | ||
firstname: string; | ||
lastname: string; | ||
}; | ||
created: Date; | ||
active: boolean; | ||
passwordHash: string; | ||
} | ||
type UserMask = SchemaLoose<User, 'mask' | 'hide' | 'show'>; | ||
const userMaskSettings: UserMask = { | ||
id: 'show', | ||
name: { | ||
firstname: 'show', | ||
lastname: 'mask', | ||
}, | ||
created: 'show', | ||
active: 'show', | ||
passwordHash: 'hide', | ||
} | ||
``` | ||
@category Object | ||
*/ | ||
export type SchemaLoose<ObjectType, ValueType> = ObjectType extends string | ||
? ValueType | ||
: ObjectType extends Map<unknown, unknown> | ||
? ValueType | ||
: ObjectType extends Set<unknown> | ||
? ValueType | ||
: ObjectType extends ReadonlyMap<unknown, unknown> | ||
? ValueType | ||
: ObjectType extends ReadonlySet<unknown> | ||
? ValueType | ||
: ObjectType extends readonly unknown[] | ||
? ValueType | ||
: ObjectType extends unknown[] | ||
? ValueType | ||
: ObjectType extends (...arguments_: unknown[]) => unknown | ||
? ValueType | ||
: ObjectType extends Date | ||
? ValueType | ||
: ObjectType extends Function | ||
? ValueType | ||
: ObjectType extends RegExp | ||
? ValueType | ||
: ObjectType extends object | ||
? SchemaLooseObject<ObjectType, ValueType> | ||
: ValueType; | ||
|
||
/** | ||
Same as `SchemaLoose`, but accepts only `object`s as inputs. Internal helper for `SchemaLoose`. | ||
*/ | ||
type SchemaLooseObject<ObjectType extends object, K> = { | ||
[KeyType in keyof ObjectType]: SchemaLoose<ObjectType[KeyType], K> | K; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import {expectNotAssignable, expectType} from 'tsd'; | ||
import type {SchemaLoose} from '../index'; | ||
|
||
const foo = { | ||
baz: 'fred', | ||
bar: { | ||
function: (_: string): void => undefined, | ||
object: {key: 'value'}, | ||
string: 'waldo', | ||
number: 1, | ||
boolean: false, | ||
symbol: Symbol('test'), | ||
map: new Map<string, string>(), | ||
set: new Set<string>(), | ||
array: ['foo'], | ||
tuple: ['foo'] as ['foo'], | ||
readonlyMap: new Map<string, string>() as ReadonlyMap<string, string>, | ||
readonlySet: new Set<string>() as ReadonlySet<string>, | ||
readonlyArray: ['foo'] as readonly string[], | ||
readonlyTuple: ['foo'] as const, | ||
regExp: /.*/g, | ||
}, | ||
}; | ||
|
||
type FooOption = 'A' | 'B'; | ||
type FooSchema = SchemaLoose<typeof foo, FooOption>; | ||
|
||
const fooSchema: FooSchema = { | ||
baz: 'A', | ||
bar: { | ||
function: 'A', | ||
object: {key: 'A'}, | ||
string: 'A', | ||
number: 'A', | ||
boolean: 'A', | ||
symbol: 'A', | ||
map: 'A', | ||
set: 'A', | ||
array: 'A', | ||
tuple: 'A', | ||
readonlyMap: 'A', | ||
readonlySet: 'A', | ||
readonlyArray: 'A', | ||
readonlyTuple: 'A', | ||
regExp: 'A', | ||
}, | ||
}; | ||
|
||
expectNotAssignable<FooSchema>(foo); | ||
expectNotAssignable<FooSchema>({key: 'value'}); | ||
expectNotAssignable<FooSchema>(new Date()); | ||
expectType<FooOption>(fooSchema.baz); | ||
|
||
const barSchema = fooSchema.bar as SchemaLoose<typeof foo['bar'], FooOption>; | ||
expectType<FooOption>(barSchema.function); | ||
expectType<FooOption | {key: FooOption}>(barSchema.object); | ||
expectType<FooOption>(barSchema.string); | ||
expectType<FooOption>(barSchema.number); | ||
expectType<FooOption>(barSchema.boolean); | ||
expectType<FooOption>(barSchema.symbol); | ||
expectType<FooOption>(barSchema.map); | ||
expectType<FooOption>(barSchema.set); | ||
expectType<FooOption>(barSchema.array); | ||
expectType<FooOption>(barSchema.tuple); | ||
expectType<FooOption>(barSchema.readonlyMap); | ||
expectType<FooOption>(barSchema.readonlySet); | ||
expectType<FooOption>(barSchema.readonlyArray); | ||
expectType<FooOption>(barSchema.readonlyTuple); | ||
expectType<FooOption>(barSchema.regExp); | ||
|
||
type ComplexOption = { | ||
type: 'readonly' | 'required' | 'optional'; | ||
validation(value: unknown): boolean; | ||
}; | ||
type ComplexSchema = SchemaLoose<typeof foo, ComplexOption>; | ||
|
||
const createComplexOption = (type: ComplexOption['type']): ComplexOption => ({ | ||
type, | ||
validation(value) { | ||
return value !== undefined; | ||
}, | ||
}); | ||
|
||
const complexFoo: ComplexSchema = { | ||
baz: createComplexOption('optional'), | ||
bar: { | ||
function: createComplexOption('required'), | ||
object: createComplexOption('readonly'), | ||
string: createComplexOption('readonly'), | ||
number: createComplexOption('readonly'), | ||
boolean: createComplexOption('readonly'), | ||
symbol: createComplexOption('readonly'), | ||
map: createComplexOption('readonly'), | ||
set: createComplexOption('readonly'), | ||
array: createComplexOption('readonly'), | ||
tuple: createComplexOption('readonly'), | ||
readonlyMap: createComplexOption('readonly'), | ||
readonlySet: createComplexOption('readonly'), | ||
readonlyArray: createComplexOption('readonly'), | ||
readonlyTuple: createComplexOption('readonly'), | ||
regExp: createComplexOption('readonly'), | ||
}, | ||
}; | ||
|
||
expectNotAssignable<ComplexSchema>(foo); | ||
expectType<ComplexOption>(complexFoo.baz); | ||
|
||
const complexBarSchema = complexFoo.bar as SchemaLoose<typeof foo['bar'], ComplexOption>; | ||
expectType<ComplexOption>(complexBarSchema.function); | ||
expectType<ComplexOption | {key: ComplexOption}>(complexBarSchema.object); | ||
expectType<ComplexOption>(complexBarSchema.string); | ||
expectType<ComplexOption>(complexBarSchema.number); | ||
expectType<ComplexOption>(complexBarSchema.boolean); | ||
expectType<ComplexOption>(complexBarSchema.symbol); | ||
expectType<ComplexOption>(complexBarSchema.map); | ||
expectType<ComplexOption>(complexBarSchema.set); | ||
expectType<ComplexOption>(complexBarSchema.array); | ||
expectType<ComplexOption>(complexBarSchema.tuple); | ||
expectType<ComplexOption>(complexBarSchema.readonlyMap); | ||
expectType<ComplexOption>(complexBarSchema.readonlySet); | ||
expectType<ComplexOption>(complexBarSchema.readonlyArray); | ||
expectType<ComplexOption>(complexBarSchema.readonlyTuple); | ||
expectType<ComplexOption>(complexBarSchema.regExp); |