Skip to content

Commit 912e0be

Browse files
authored
feat: More intuitive Object generics, faster types (#1540)
* WIP * WIP * WIP * SAVEPOINT * WIP * SAVEPOINT * SAVEPOINT * convert tests * GH actions ci * test
1 parent eeacc92 commit 912e0be

35 files changed

+1508
-1088
lines changed

.eslintrc

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"jest/no-focused-tests": "error",
2626
"jest/no-identical-title": "error",
2727
"jest/prefer-to-have-length": "warn",
28-
"jest/valid-expect": "off"
28+
"jest/valid-expect": "off",
29+
"@typescript-eslint/no-empty-function": "off"
2930
}
3031
}
3132
]

.github/workflows/ci.yml

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
name: Test
2+
on:
3+
push:
4+
branches: [master, next]
5+
pull_request:
6+
branches: [master, next]
7+
jobs:
8+
build:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v2
12+
- name: Setup Node
13+
uses: actions/setup-node@v2
14+
with:
15+
node-version: 'lts/*'
16+
cache: 'yarn'
17+
- run: yarn install --frozen-lockfile
18+
- run: yarn test

jest-sync.config.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@
77
"setupFilesAfterEnv": ["./test-setup.js"],
88
"roots": ["test"],
99
"testRegex": "\\.(t|j)s$",
10-
"testPathIgnorePatterns": ["helpers\\.js", "\\.eslintrc\\.js", "types\\.ts"]
10+
"testPathIgnorePatterns": ["helpers\\.ts", "\\.eslintrc\\.js", "types\\.ts"]
1111
}

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
],
6161
"testRegex": "\\.(j|t)s$",
6262
"testPathIgnorePatterns": [
63-
"helpers\\.js",
63+
"helpers\\.ts",
6464
"\\.eslintrc\\.js",
6565
"types\\.ts"
6666
]
@@ -73,6 +73,7 @@
7373
"@babel/core": "^7.15.8",
7474
"@babel/plugin-proposal-logical-assignment-operators": "^7.14.5",
7575
"@babel/preset-typescript": "^7.15.0",
76+
"@types/jest": "^27.0.3",
7677
"@typescript-eslint/eslint-plugin": "^4.33.0",
7778
"@typescript-eslint/parser": "^4.33.0",
7879
"babel-eslint": "^10.1.0",

src/Condition.ts

+14-15
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
import isSchema from './util/isSchema';
22
import Reference from './Reference';
3-
import { SchemaLike } from './types';
3+
import type { ISchema } from './util/types';
44

5-
export interface ConditionBuilder<T extends SchemaLike> {
6-
(this: T, value: any, schema: T): SchemaLike;
7-
(v1: any, v2: any, schema: T): SchemaLike;
8-
(v1: any, v2: any, v3: any, schema: T): SchemaLike;
9-
(v1: any, v2: any, v3: any, v4: any, schema: T): SchemaLike;
5+
export interface ConditionBuilder<T extends ISchema<any, any>> {
6+
(this: T, value: any, schema: T): ISchema<any, any> | void;
7+
(v1: any, v2: any, schema: T): ISchema<any, any> | void;
8+
(v1: any, v2: any, v3: any, schema: T): ISchema<any, any> | void;
9+
(v1: any, v2: any, v3: any, v4: any, schema: T): ISchema<any, any> | void;
1010
}
1111

12-
export type ConditionConfig<T extends SchemaLike> = {
12+
export type ConditionConfig<T extends ISchema<any>> = {
1313
is: any | ((...values: any[]) => boolean);
14-
then?: SchemaLike | ((schema: T) => SchemaLike);
15-
otherwise?: SchemaLike | ((schema: T) => SchemaLike);
14+
then?: (schema: T) => ISchema<any>;
15+
otherwise?: (schema: T) => ISchema<any>;
1616
};
1717

18-
export type ConditionOptions<T extends SchemaLike> =
18+
export type ConditionOptions<T extends ISchema<any, any>> =
1919
| ConditionBuilder<T>
2020
| ConditionConfig<T>;
2121

@@ -25,7 +25,7 @@ export type ResolveOptions<TContext = any> = {
2525
context?: TContext;
2626
};
2727

28-
class Condition<T extends SchemaLike = SchemaLike> {
28+
class Condition<T extends ISchema<any, any> = ISchema<any, any>> {
2929
fn: ConditionBuilder<T>;
3030

3131
constructor(public refs: Reference[], options: ConditionOptions<T>) {
@@ -52,18 +52,17 @@ class Condition<T extends SchemaLike = SchemaLike> {
5252
: (...values: any[]) => values.every((value) => value === is);
5353

5454
this.fn = function (...args: any[]) {
55-
let options = args.pop();
55+
let _opts = args.pop();
5656
let schema = args.pop();
5757
let branch = check(...args) ? then : otherwise;
5858

59-
if (!branch) return undefined;
60-
if (typeof branch === 'function') return branch(schema);
61-
return schema.concat(branch.resolve(options));
59+
return branch?.(schema) ?? schema;
6260
};
6361
}
6462

6563
resolve(base: T, options: ResolveOptions) {
6664
let values = this.refs.map((ref) =>
65+
// TODO: ? operator here?
6766
ref.getValue(options?.value, options?.parent, options?.context),
6867
);
6968

src/Lazy.ts

+47-45
Original file line numberDiff line numberDiff line change
@@ -1,128 +1,130 @@
11
import isSchema from './util/isSchema';
2-
import type { Callback, ValidateOptions } from './types';
2+
import type { AnyObject, Callback, ValidateOptions } from './types';
33
import type { ResolveOptions } from './Condition';
44

55
import type {
6-
AnySchema,
76
CastOptions,
8-
ConfigOf,
97
SchemaFieldDescription,
108
SchemaLazyDescription,
119
} from './schema';
12-
import { Config, TypedSchema, TypeOf } from './util/types';
13-
14-
export type LazyBuilder<T extends AnySchema = any> = (
10+
import { Flags, ISchema } from './util/types';
11+
import { BaseSchema } from '.';
12+
13+
export type LazyBuilder<
14+
T,
15+
TContext = AnyObject,
16+
TDefault = any,
17+
TFlags extends Flags = any,
18+
> = (
1519
value: any,
1620
options: ResolveOptions,
17-
) => T;
18-
19-
export function create<T extends AnySchema>(builder: LazyBuilder<T>) {
20-
return new Lazy(builder);
21+
) => ISchema<T, TContext, TFlags, TDefault>;
22+
23+
export function create<
24+
T,
25+
TContext = AnyObject,
26+
TFlags extends Flags = any,
27+
TDefault = any,
28+
>(builder: LazyBuilder<T, TContext, TDefault, TFlags>) {
29+
return new Lazy<T, TContext, TDefault, TFlags>(builder);
2130
}
2231

23-
export type LazyReturnValue<T> = T extends Lazy<infer TSchema>
24-
? TSchema
25-
: never;
26-
27-
export type LazyType<T> = LazyReturnValue<T> extends TypedSchema
28-
? TypeOf<LazyReturnValue<T>>
29-
: never;
30-
3132
export interface LazySpec {
3233
meta: Record<string, unknown> | undefined;
3334
}
3435

35-
class Lazy<T extends AnySchema, TConfig extends Config = ConfigOf<T>>
36-
implements TypedSchema {
36+
class Lazy<T, TContext = AnyObject, TDefault = any, TFlags extends Flags = any>
37+
implements ISchema<T, TContext, TFlags, TDefault>
38+
{
3739
type = 'lazy' as const;
3840

3941
__isYupSchema__ = true;
4042

41-
readonly __type!: T['__type'];
42-
readonly __outputType!: T['__outputType'];
43+
declare readonly __outputType: T;
44+
declare readonly __context: TContext;
45+
declare readonly __flags: TFlags;
46+
declare readonly __default: TDefault;
4347

4448
spec: LazySpec;
4549

46-
constructor(private builder: LazyBuilder<T>) {
50+
constructor(private builder: LazyBuilder<T, TContext, TDefault, TFlags>) {
4751
this.spec = { meta: undefined };
4852
}
4953

50-
clone(): Lazy<T, TConfig> {
51-
const next = new Lazy(this.builder);
54+
clone(): Lazy<T, TContext, TDefault, TFlags> {
55+
const next = create(this.builder);
5256
next.spec = { ...this.spec };
5357
return next;
5458
}
5559

5660
private _resolve = (
5761
value: any,
58-
options: ResolveOptions<TConfig['context']> = {},
59-
): T => {
60-
let schema = this.builder(value, options);
62+
options: ResolveOptions<TContext> = {},
63+
): BaseSchema<T, TContext, TDefault, TFlags> => {
64+
let schema = this.builder(value, options) as BaseSchema<
65+
T,
66+
TContext,
67+
TDefault,
68+
TFlags
69+
>;
6170

6271
if (!isSchema(schema))
6372
throw new TypeError('lazy() functions must return a valid schema');
6473

6574
return schema.resolve(options);
6675
};
6776

68-
resolve(options: ResolveOptions<TConfig['context']>) {
77+
resolve(options: ResolveOptions<TContext>) {
6978
return this._resolve(options.value, options);
7079
}
7180

72-
cast(value: any, options?: CastOptions<TConfig['context']>): T['__type'] {
81+
cast(value: any, options?: CastOptions<TContext>): T {
7382
return this._resolve(value, options).cast(value, options);
7483
}
7584

7685
validate(
7786
value: any,
7887
options?: ValidateOptions,
7988
maybeCb?: Callback,
80-
): T['__outputType'] {
89+
): Promise<T> {
8190
// @ts-expect-error missing public callback on type
8291
return this._resolve(value, options).validate(value, options, maybeCb);
8392
}
8493

85-
validateSync(
86-
value: any,
87-
options?: ValidateOptions<TConfig['context']>,
88-
): T['__outputType'] {
94+
validateSync(value: any, options?: ValidateOptions<TContext>): T {
8995
return this._resolve(value, options).validateSync(value, options);
9096
}
9197

92-
validateAt(
93-
path: string,
94-
value: any,
95-
options?: ValidateOptions<TConfig['context']>,
96-
) {
98+
validateAt(path: string, value: any, options?: ValidateOptions<TContext>) {
9799
return this._resolve(value, options).validateAt(path, value, options);
98100
}
99101

100102
validateSyncAt(
101103
path: string,
102104
value: any,
103-
options?: ValidateOptions<TConfig['context']>,
105+
options?: ValidateOptions<TContext>,
104106
) {
105107
return this._resolve(value, options).validateSyncAt(path, value, options);
106108
}
107109

108-
isValid(value: any, options?: ValidateOptions<TConfig['context']>) {
110+
isValid(value: any, options?: ValidateOptions<TContext>) {
109111
return this._resolve(value, options).isValid(value, options);
110112
}
111113

112-
isValidSync(value: any, options?: ValidateOptions<TConfig['context']>) {
114+
isValidSync(value: any, options?: ValidateOptions<TContext>) {
113115
return this._resolve(value, options).isValidSync(value, options);
114116
}
115117

116118
describe(
117-
options?: ResolveOptions<TConfig['context']>,
119+
options?: ResolveOptions<TContext>,
118120
): SchemaLazyDescription | SchemaFieldDescription {
119121
return options
120122
? this.resolve(options).describe(options)
121123
: { type: 'lazy', meta: this.spec.meta, label: undefined };
122124
}
123125

124126
meta(): Record<string, unknown> | undefined;
125-
meta(obj: Record<string, unknown>): Lazy<T, TConfig>;
127+
meta(obj: Record<string, unknown>): Lazy<T, TContext, TDefault, TFlags>;
126128
meta(...args: [Record<string, unknown>?]) {
127129
if (args.length === 0) return this.spec.meta;
128130

0 commit comments

Comments
 (0)