From 3aabfbc5e7a22b72fe09df3ef4563c3a4a0f2146 Mon Sep 17 00:00:00 2001 From: Tellios Date: Thu, 27 Jan 2022 11:56:50 +0100 Subject: [PATCH] Add `Schema` type (#321) Co-authored-by: Sindre Sorhus --- index.d.ts | 1 + readme.md | 2 + source/schema.d.ts | 72 ++++++++++++++++++++++++++ test-d/schema.ts | 123 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 198 insertions(+) create mode 100644 source/schema.d.ts create mode 100644 test-d/schema.ts diff --git a/index.d.ts b/index.d.ts index c840619b..ee8bb19c 100644 --- a/index.d.ts +++ b/index.d.ts @@ -38,6 +38,7 @@ export {SetReturnType} from './source/set-return-type'; export {Asyncify} from './source/asyncify'; export {Simplify} from './source/simplify'; export {Jsonify} from './source/jsonify'; +export {Schema} from './source/schema'; export {LiteralToPrimitive} from './source/literal-to-primitive'; export { PositiveInfinity, diff --git a/readme.md b/readme.md index 7ee170c9..ec8cd642 100644 --- a/readme.md +++ b/readme.md @@ -120,6 +120,7 @@ Click the type names for complete docs. - [`Simplify`](source/simplify.d.ts) - Useful to flatten the type output to improve type hints shown in editors. And also to transform an interface into a type to aide with assignability. - [`Get`](source/get.d.ts) - Get a deeply-nested property from an object using a key path, like [Lodash's `.get()`](https://lodash.com/docs/latest#get) function. - [`StringKeyOf`](source/string-key-of.d.ts) - Get keys of the given type as strings. +- [`Schema`](source/schema.d.ts) - Create a deep version of another object type where property values are recursively replaced into a given value type. ### JSON @@ -203,6 +204,7 @@ Click the type names for complete docs. *If you know one of our types by a different name, add it here for discovery.* - `PartialBy` - See [`SetOptional`](https://github.com/sindresorhus/type-fest/blob/main/source/set-optional.d.ts) +- `RecordDeep`- See [`Schema`](https://github.com/sindresorhus/type-fest/blob/main/source/schema.d.ts) ## Tips diff --git a/source/schema.d.ts b/source/schema.d.ts new file mode 100644 index 00000000..e9763101 --- /dev/null +++ b/source/schema.d.ts @@ -0,0 +1,72 @@ +/** +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 {Schema} from 'type-fest'; + +interface User { + id: string; + name: { + firstname: string; + lastname: string; + }; + created: Date; + active: boolean; + passwordHash: string; +} + +type UserMask = Schema; + +const userMaskSettings: UserMask = { + id: 'show', + name: { + firstname: 'show', + lastname: 'mask', + }, + phoneNumbers: 'mask', + created: 'show', + active: 'show', + passwordHash: 'hide', +} +``` + +@category Object +*/ +export type Schema = ObjectType extends string + ? ValueType + : ObjectType extends Map + ? ValueType + : ObjectType extends Set + ? ValueType + : ObjectType extends ReadonlyMap + ? ValueType + : ObjectType extends ReadonlySet + ? 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 + ? SchemaObject + : ValueType; + +/** +Same as `Schema`, but accepts only `object`s as inputs. Internal helper for `Schema`. +*/ +type SchemaObject = { + [KeyType in keyof ObjectType]: Schema | K; +}; diff --git a/test-d/schema.ts b/test-d/schema.ts new file mode 100644 index 00000000..d3d7a8d8 --- /dev/null +++ b/test-d/schema.ts @@ -0,0 +1,123 @@ +import {expectType, expectError} from 'tsd'; +import {Schema} 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(), + set: new Set(), + array: ['foo'], + tuple: ['foo'] as ['foo'], + readonlyMap: new Map() as ReadonlyMap, + readonlySet: new Set() as ReadonlySet, + readonlyArray: ['foo'] as readonly string[], + readonlyTuple: ['foo'] as const, + regExp: /.*/g, + }, +}; + +type FooOption = 'A' | 'B'; +type FooSchema = Schema; + +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', + }, +}; + +expectError(foo); +expectError({key: 'value'}); +expectError(new Date()); +expectType(fooSchema.baz); + +const barSchema = fooSchema.bar as Schema; +expectType(barSchema.function); +expectType(barSchema.object); +expectType(barSchema.string); +expectType(barSchema.number); +expectType(barSchema.boolean); +expectType(barSchema.symbol); +expectType(barSchema.map); +expectType(barSchema.set); +expectType(barSchema.array); +expectType(barSchema.tuple); +expectType(barSchema.readonlyMap); +expectType(barSchema.readonlySet); +expectType(barSchema.readonlyArray); +expectType(barSchema.readonlyTuple); +expectType(barSchema.regExp); + +interface ComplexOption { + type: 'readonly' | 'required' | 'optional'; + validation(value: unknown): boolean; +} +type ComplexSchema = Schema; + +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'), + }, +}; + +expectError(foo); +expectType(complexFoo.baz); + +const complexBarSchema = complexFoo.bar as Schema; +expectType(complexBarSchema.function); +expectType(complexBarSchema.object); +expectType(complexBarSchema.string); +expectType(complexBarSchema.number); +expectType(complexBarSchema.boolean); +expectType(complexBarSchema.symbol); +expectType(complexBarSchema.map); +expectType(complexBarSchema.set); +expectType(complexBarSchema.array); +expectType(complexBarSchema.tuple); +expectType(complexBarSchema.readonlyMap); +expectType(complexBarSchema.readonlySet); +expectType(complexBarSchema.readonlyArray); +expectType(complexBarSchema.readonlyTuple); +expectType(complexBarSchema.regExp);