Skip to content

Commit a82353f

Browse files
committed
feat: Make Array generic consistent with others
BREAKING CHANGE: types only, `ArraySchema` initial generic is the array type not the type of the array element. `array<T>()` is still the inner type.
1 parent 94b73c4 commit a82353f

File tree

3 files changed

+44
-42
lines changed

3 files changed

+44
-42
lines changed

README.md

+7-5
Original file line numberDiff line numberDiff line change
@@ -166,15 +166,17 @@ import {
166166
- [`date.min(limit: Date | string | Ref, message?: string | function): Schema`](#dateminlimit-date--string--ref-message-string--function-schema)
167167
- [`date.max(limit: Date | string | Ref, message?: string | function): Schema`](#datemaxlimit-date--string--ref-message-string--function-schema)
168168
- [array](#array)
169-
- [`array.of(type: Schema): Schema`](#arrayoftype-schema-schema)
170-
- [`array.length(length: number | Ref, message?: string | function): Schema`](#arraylengthlength-number--ref-message-string--function-schema)
171-
- [`array.min(limit: number | Ref, message?: string | function): Schema`](#arrayminlimit-number--ref-message-string--function-schema)
172-
- [`array.max(limit: number | Ref, message?: string | function): Schema`](#arraymaxlimit-number--ref-message-string--function-schema)
173-
- [`array.ensure(): Schema`](#arrayensure-schema)
169+
- [`array.of(type: Schema): this`](#arrayoftype-schema-this)
170+
- [`array.json(): this`](#arrayjson-this)
171+
- [`array.length(length: number | Ref, message?: string | function): this`](#arraylengthlength-number--ref-message-string--function-this)
172+
- [`array.min(limit: number | Ref, message?: string | function): this`](#arrayminlimit-number--ref-message-string--function-this)
173+
- [`array.max(limit: number | Ref, message?: string | function): this`](#arraymaxlimit-number--ref-message-string--function-this)
174+
- [`array.ensure(): this`](#arrayensure-this)
174175
- [`array.compact(rejector: (value) => boolean): Schema`](#arraycompactrejector-value--boolean-schema)
175176
- [object](#object)
176177
- [Object schema defaults](#object-schema-defaults)
177178
- [`object.shape(fields: object, noSortEdges?: Array<[string, string]>): Schema`](#objectshapefields-object-nosortedges-arraystring-string-schema)
179+
- [`object.json(): this`](#objectjson-this)
178180
- [`object.concat(schemaB: ObjectSchema): ObjectSchema`](#objectconcatschemab-objectschema-objectschema)
179181
- [`object.pick(keys: string[]): Schema`](#objectpickkeys-string-schema)
180182
- [`object.omit(keys: string[]): Schema`](#objectomitkeys-string-schema)

src/array.ts

+33-36
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type {
99
Callback,
1010
Message,
1111
Maybe,
12+
Optionals,
1213
} from './types';
1314
import ValidationError from './ValidationError';
1415
import type Reference from './Reference';
@@ -27,6 +28,8 @@ import Schema, { SchemaInnerTypeDescription, SchemaSpec } from './schema';
2728
import { ResolveOptions } from './Condition';
2829
import parseJson from 'parse-json';
2930

31+
type InnerType<T> = T extends Array<infer I> ? I : never;
32+
3033
export type RejectorFn = (
3134
value: any,
3235
index: number,
@@ -36,19 +39,18 @@ export type RejectorFn = (
3639
export function create<C extends AnyObject = AnyObject, T = any>(
3740
type?: ISchema<T, C>,
3841
) {
39-
return new ArraySchema<T, C>(type as any);
42+
return new ArraySchema<T[] | undefined, C>(type as any);
4043
}
4144

4245
export default class ArraySchema<
43-
T,
46+
TIn extends any[] | null | undefined,
4447
TContext,
4548
TDefault = undefined,
4649
TFlags extends Flags = '',
47-
TIn extends any[] | null | undefined = T[] | undefined,
4850
> extends Schema<TIn, TContext, TDefault, TFlags> {
49-
innerType?: ISchema<T, TContext>;
51+
readonly innerType?: ISchema<InnerType<TIn>, TContext>;
5052

51-
constructor(type?: ISchema<T, TContext>) {
53+
constructor(type?: ISchema<InnerType<TIn>, TContext>) {
5254
super({
5355
type: 'array',
5456
check(v: any): v is NonNullable<TIn> {
@@ -60,15 +62,13 @@ export default class ArraySchema<
6062
this.innerType = type;
6163
}
6264

63-
private get _subType() {
64-
return this.innerType;
65-
}
66-
6765
protected _cast(_value: any, _opts: InternalOptions<TContext>) {
6866
const value = super._cast(_value, _opts);
6967

70-
//should ignore nulls here
71-
if (!this._typeCheck(value) || !this.innerType) return value;
68+
// should ignore nulls here
69+
if (!this._typeCheck(value) || !this.innerType) {
70+
return value;
71+
}
7272

7373
let isChanged = false;
7474
const castArray = value.map((v, idx) => {
@@ -151,6 +151,7 @@ export default class ArraySchema<
151151

152152
clone(spec?: SchemaSpec<any>) {
153153
const next = super.clone(spec);
154+
// @ts-expect-error readonly
154155
next.innerType = this.innerType;
155156
return next;
156157
}
@@ -160,22 +161,23 @@ export default class ArraySchema<
160161
return this.transform(parseJson);
161162
}
162163

163-
concat<IT, IC, ID, IF extends Flags, IIn extends Maybe<IT[]>>(
164-
schema: ArraySchema<IT, IC, ID, IF, IIn>,
164+
concat<IIn extends Maybe<any[]>, IC, ID, IF extends Flags>(
165+
schema: ArraySchema<IIn, IC, ID, IF>,
165166
): ArraySchema<
166-
Concat<T, IT>,
167+
Concat<TIn, IIn>,
167168
TContext & IC,
168169
Extract<IF, 'd'> extends never ? TDefault : ID,
169-
TFlags | IF,
170-
Concat<TIn, IIn>
170+
TFlags | IF
171171
>;
172172
concat(schema: this): this;
173173
concat(schema: any): any {
174174
let next = super.concat(schema) as this;
175175

176+
// @ts-expect-error readonly
176177
next.innerType = this.innerType;
177178

178179
if (schema.innerType)
180+
// @ts-expect-error readonly
179181
next.innerType = next.innerType
180182
? // @ts-expect-error Lazy doesn't have concat and will break
181183
next.innerType.concat(schema.innerType)
@@ -184,7 +186,9 @@ export default class ArraySchema<
184186
return next;
185187
}
186188

187-
of<U>(schema: ISchema<U, TContext>): ArraySchema<U, TContext, TFlags> {
189+
of<U>(
190+
schema: ISchema<U, TContext>,
191+
): ArraySchema<U[] | Optionals<TIn>, TContext, TFlags> {
188192
// FIXME: this should return a new instance of array without the default to be
189193
let next = this.clone();
190194

@@ -194,8 +198,8 @@ export default class ArraySchema<
194198
printValue(schema),
195199
);
196200

197-
// FIXME(ts):
198-
next.innerType = schema as any;
201+
// @ts-expect-error readonly
202+
next.innerType = schema;
199203

200204
return next as any;
201205
}
@@ -243,8 +247,6 @@ export default class ArraySchema<
243247
});
244248
}
245249

246-
// ArraySchema<T, TContext, T[], SetFlag<TFlags, 'd'>, NonNullable<TIn>>
247-
248250
ensure() {
249251
return this.default<TIn>(() => [] as any).transform(
250252
(val: TIn, original: any) => {
@@ -285,35 +287,30 @@ export default class ArraySchema<
285287
create.prototype = ArraySchema.prototype;
286288

287289
export default interface ArraySchema<
288-
T,
290+
TIn extends any[] | null | undefined,
289291
TContext,
290292
TDefault = undefined,
291293
TFlags extends Flags = '',
292-
TIn extends any[] | null | undefined = T[] | undefined,
293294
> extends Schema<TIn, TContext, TDefault, TFlags> {
294295
default<D extends Maybe<TIn>>(
295296
def: Thunk<D>,
296-
): ArraySchema<T, TContext, D, ToggleDefault<TFlags, D>, TIn>;
297+
): ArraySchema<TIn, TContext, D, ToggleDefault<TFlags, D>>;
297298

298-
defined(
299-
msg?: Message,
300-
): ArraySchema<T, TContext, TDefault, TFlags, Defined<TIn>>;
301-
optional(): ArraySchema<T, TContext, TDefault, TFlags, TIn | undefined>;
299+
defined(msg?: Message): ArraySchema<Defined<TIn>, TContext, TDefault, TFlags>;
300+
optional(): ArraySchema<TIn | undefined, TContext, TDefault, TFlags>;
302301

303302
required(
304303
msg?: Message,
305-
): ArraySchema<T, TContext, TDefault, TFlags, NonNullable<TIn>>;
306-
notRequired(): ArraySchema<T, TContext, TDefault, TFlags, Maybe<TIn>>;
304+
): ArraySchema<NonNullable<TIn>, TContext, TDefault, TFlags>;
305+
notRequired(): ArraySchema<Maybe<TIn>, TContext, TDefault, TFlags>;
307306

308-
nullable(
309-
msg?: Message,
310-
): ArraySchema<T, TContext, TDefault, TFlags, TIn | null>;
311-
nonNullable(): ArraySchema<T, TContext, TDefault, TFlags, NotNull<TIn>>;
307+
nullable(msg?: Message): ArraySchema<TIn | null, TContext, TDefault, TFlags>;
308+
nonNullable(): ArraySchema<NotNull<TIn>, TContext, TDefault, TFlags>;
312309

313310
strip(
314311
enabled: false,
315-
): ArraySchema<T, TContext, TDefault, UnsetFlag<TFlags, 's'>>;
312+
): ArraySchema<TIn, TContext, TDefault, UnsetFlag<TFlags, 's'>>;
316313
strip(
317314
enabled?: true,
318-
): ArraySchema<T, TContext, TDefault, SetFlag<TFlags, 's'>>;
315+
): ArraySchema<TIn, TContext, TDefault, SetFlag<TFlags, 's'>>;
319316
}

test/types/types.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ date: {
392392

393393
const blDefined = bool().default(false);
394394

395-
// $ExpectType boolean
395+
// $ExpectType false
396396
blDefined.getDefault();
397397

398398
// $ExpectType false | undefined
@@ -502,6 +502,9 @@ Array: {
502502
// $ExpectType (string | undefined)[] | undefined
503503
array(string()).cast(null);
504504

505+
// $ExpectType (string | undefined)[] | null
506+
array().defined().nullable().of(string()).cast(null);
507+
505508
// $ExpectType string[] | undefined
506509
array(string().required()).validateSync(null);
507510

0 commit comments

Comments
 (0)