Skip to content

Commit

Permalink
Add Schema old behaviour back as SchemaLoose
Browse files Browse the repository at this point in the history
  • Loading branch information
iamandrewluca committed Sep 27, 2024
1 parent afb3c0a commit 2f3f1fe
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 0 deletions.
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ 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 {SchemaLoose} from './source/schema-loose';
export type {LiteralToPrimitive} from './source/literal-to-primitive';
export type {LiteralToPrimitiveDeep} from './source/literal-to-primitive-deep';
export type {
Expand Down
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ Click the type names for complete docs.
- [`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.
- [`SchemaLoose`](source/schema-loose.d.ts) - Similar to `Schema` but stops at arrays.
- [`Exact`](source/exact.d.ts) - Create a type that does not allow extra properties.
- [`OptionalKeysOf`](source/optional-keys-of.d.ts) - Extract all optional keys from the given type.
- [`KeysOfUnion`](source/keys-of-union.d.ts) - Create a union of all keys from a given type, even those exclusive to specific union members.
Expand Down
71 changes: 71 additions & 0 deletions source/schema-loose.d.ts
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;
};
123 changes: 123 additions & 0 deletions test-d/schema-loose.ts
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);

0 comments on commit 2f3f1fe

Please sign in to comment.