From 7b2d7e4b8a6ab0abac8bb1e61f61e8fdf7fcdafd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Miszczyszyn?= Date: Mon, 22 Mar 2021 10:20:41 +0100 Subject: [PATCH] feat: Refinements (#48) * wip * Tests * Getting close * Tests pass * Implementation almost ready * v0.6.1-0 * All migrated * Tests * v0.6.1-1 * Update stringify * v0.6.1-2 * Add sourcemaps * Cleanup * Docs wip * feat(refinements): Add nextValid and nextNotValid BREAKING CHANGE: `next` was renamed to `nextValid`. * chore: Update deps * docs: Fix link * docs: Update readme with new benchmarks --- README.md | 32 +- __tests__/bench.js | 22 +- __tests__/index.test-d.ts | 46 +-- __tests__/property-tests.spec.ts | 60 +-- __tests__/prototypePollution.spec.ts | 18 +- __tests__/refinements.spec.ts | 97 +++++ __tests__/refinements.test-d.ts | 63 +++ __tests__/types.test-d.ts | 107 ++++++ __tests__/unit-tests.spec.ts | 212 ++++++---- docs/docs/introduction.md | 10 +- docs/docs/modifiers/allowUnknownKeys.md | 29 -- docs/docs/modifiers/minArrayLength.md | 38 ++ docs/docs/modifiers/minLength.md | 50 --- docs/docs/modifiers/minStringLength.md | 16 + docs/docs/modifiers/nonEmpty.md | 29 -- docs/docs/modifiers/refinements.md | 107 ++++++ docs/docs/utilities/pipe.md | 2 +- docs/docs/utilities/typeOf.md | 2 +- docs/docs/validate.md | 2 +- docs/docs/validators/array.md | 6 +- docs/docs/validators/date.md | 4 + docs/docs/validators/number.md | 2 +- docs/docs/validators/object.md | 39 +- docs/docs/validators/oneOf.md | 2 +- docs/sidebars.js | 6 +- package.json | 36 +- rollup.config.js | 3 + src/index.ts | 7 +- src/modifiers/allowUnknownKeys.ts | 11 - src/modifiers/minArrayLength.ts | 14 + src/modifiers/minLength.ts | 34 -- src/modifiers/minStringLength.ts | 4 + src/modifiers/nil.ts | 11 +- src/modifiers/nonEmpty.ts | 3 - src/modifiers/nullable.ts | 27 +- src/modifiers/optional.ts | 27 +- src/refine.ts | 76 ++++ src/schema.ts | 11 +- src/stringify.ts | 10 +- src/types.ts | 63 +-- src/utils/either.ts | 11 +- src/validators/__validate.ts | 5 +- src/validators/array.ts | 83 ++-- src/validators/boolean.ts | 32 +- src/validators/date.ts | 36 +- src/validators/number.ts | 36 +- src/validators/object.ts | 155 ++++---- src/validators/oneOf.ts | 80 ++-- src/validators/string.ts | 36 +- src/validators/tuple.ts | 102 ++--- src/validators/unknown.ts | 25 +- tsconfig.json | 6 +- yarn.lock | 490 +++++++++++++----------- 53 files changed, 1371 insertions(+), 1064 deletions(-) create mode 100644 __tests__/refinements.spec.ts create mode 100644 __tests__/refinements.test-d.ts create mode 100644 __tests__/types.test-d.ts delete mode 100644 docs/docs/modifiers/allowUnknownKeys.md create mode 100644 docs/docs/modifiers/minArrayLength.md delete mode 100644 docs/docs/modifiers/minLength.md create mode 100644 docs/docs/modifiers/minStringLength.md delete mode 100644 docs/docs/modifiers/nonEmpty.md create mode 100644 docs/docs/modifiers/refinements.md delete mode 100644 src/modifiers/allowUnknownKeys.ts create mode 100644 src/modifiers/minArrayLength.ts delete mode 100644 src/modifiers/minLength.ts create mode 100644 src/modifiers/minStringLength.ts delete mode 100644 src/modifiers/nonEmpty.ts create mode 100644 src/refine.ts diff --git a/README.md b/README.md index b323246..21cd3ed 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,9 @@ `@typeofweb/schema` is a lightweight and extensible library for data validation with full TypeScript support! + [![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors-) + [![codecov](https://codecov.io/gh/typeofweb/schema/branch/main/graph/badge.svg?token=6DNCIHEEUO)](https://codecov.io/gh/typeofweb/schema) @@ -51,7 +53,7 @@ const personSchema = object({ name: string(), age: number(), email: optional(string()), -}); +})(); const mark = { name: 'Mark', @@ -71,24 +73,24 @@ Early benchmarks show some really promising performance of `@typeofweb/schema` w ``` Platform info: ============== - Darwin 20.2.0 x64 - Node.JS: 14.15.2 - V8: 8.4.371.19-node.17 + Darwin 20.3.0 x64 + Node.JS: 14.16.0 + V8: 8.4.371.19-node.18 Intel(R) Core(TM) i7-6920HQ CPU @ 2.90GHz × 8 ``` | library | relative speed | operations per second | avg. operation time | | --------------------- | -------------: | --------------------: | ------------------: | -| **@typeofweb/schema** | ref | **(2,812,709 rps)** | **(avg: 0.355μs)** | -| io-ts@2.2.13 | -27.82% | (2,030,076 rps) | (avg: 0.492μs) | -| mschema@0.5.6 | -79.15% | (586,537 rps) | (avg: 1μs) | -| validator.js@2.0.4 | -83.22% | (471,847 rps) | (avg: 2μs) | -| validate.js@0.13.1 | -91.62% | (235,741 rps) | (avg: 4μs) | -| validatorjs@3.22.1 | -94.08% | (166,599 rps) | (avg: 6μs) | -| joi@17.3.0 | -95.52% | (125,992 rps) | (avg: 7μs) | -| superstruct@0.13.3 | -97.17% | (79,536 rps) | (avg: 12μs) | -| yup@0.32.8 | -97.66% | (65,748 rps) | (avg: 15μs) | -| parambulator@1.5.2 | -99.17% | (23,308 rps) | (avg: 42μs) | -| zod@1.11.11 | -99.36% | (18,126 rps) | (avg: 55μs) | +| **@typeofweb/schema** | **ref** | **(1,934,098 rps)** | **(avg: 0.517μs)** | +| io-ts@2.2.13 | -7.21% | (1,794,594 rps) | (avg: 0.557μs) | +| mschema@0.5.6 | -69.8% | (584,151 rps) | (avg: 1μs) | +| validator.js@2.0.4 | -76.16% | (461,088 rps) | (avg: 2μs) | +| validate.js@0.13.1 | -89.02% | (212,408 rps) | (avg: 4μs) | +| validatorjs@3.22.1 | -92.2% | (150,791 rps) | (avg: 6μs) | +| joi@17.3.0 | -93.25% | (130,541 rps) | (avg: 7μs) | +| superstruct@0.13.3 | -96.99% | (58,197 rps) | (avg: 17μs) | +| yup@0.32.8 | -97.09% | (56,243 rps) | (avg: 17μs) | +| parambulator@1.5.2 | -98.99% | (19,492 rps) | (avg: 51μs) | +| zod@1.11.11 | -99.03% | (18,827 rps) | (avg: 53μs) | ### 👉 [schema.typeofweb.com](https://schema.typeofweb.com/) 👈 diff --git a/__tests__/bench.js b/__tests__/bench.js index 60ed439..d88d00e 100644 --- a/__tests__/bench.js +++ b/__tests__/bench.js @@ -1,24 +1,24 @@ 'use strict'; exports.__esModule = true; -const src_1 = require('../dist/index.common.js'); +const { object, minStringLength, string, number, validate } = require('../dist/index.common.js'); function run(i) { - const schema = src_1.object({ - name: src_1.minLength(4)(src_1.string()), - email: src_1.string(), - firstName: src_1.nonEmpty(src_1.string()), - phone: src_1.nonEmpty(src_1.string()), - age: src_1.oneOf([i]), - }); - const validator = src_1.validate(schema); + const schema = object({ + name: minStringLength(4)(string()), + email: string(), + firstName: minStringLength(0)(string()), + phone: string(), + // age: number(), + })(); + const validator = validate(schema); const obj = { name: 'John Doe', email: 'john.doe@company.space', firstName: 'John', phone: '123-4567', - age: i, + // age: i, }; return validator(obj); } -for (let i = 0; i < 2000000; ++i) { +for (let i = 0; i < 4000000; ++i) { run(i); } diff --git a/__tests__/index.test-d.ts b/__tests__/index.test-d.ts index 3c40a1b..459cbbd 100644 --- a/__tests__/index.test-d.ts +++ b/__tests__/index.test-d.ts @@ -12,8 +12,8 @@ import { nullable, optional, nil, - minLength, - nonEmpty, + minArrayLength, + minStringLength, unknown, } from '../src'; @@ -27,19 +27,19 @@ expectType(validate(validator2)('')); const validator3 = date(); expectType(validate(validator3)('')); -const validator4 = oneOf(['a', 'b']); +const validator4 = oneOf(['a', 'b'])(); expectType<'a' | 'b'>(validate(validator4)('a')); -const validator41 = validate(oneOf([number(), false]))(''); +const validator41 = validate(oneOf([number(), false])())(''); expectType(validator41); const validator5 = nullable(string()); expectType(validate(validator5)('')); -const validator6 = nullable(oneOf(['a'])); +const validator6 = nullable(oneOf(['a'])()); expectType<'a' | null>(validate(validator6)('')); -const validator7 = optional(nullable(oneOf(['a']))); +const validator7 = optional(nullable(oneOf(['a'])())); expectType<'a' | null | undefined>(validate(validator7)('')); const validator8 = optional(nullable(boolean())); @@ -49,33 +49,33 @@ const validator9 = nil(boolean()); expectType(validate(validator9)(false)); // nested -const nested1 = object({}); +const nested1 = object({})(); expectType<{}>(validate(nested1)({})); const nested2 = object({ a: string(), b: object({ c: number(), - }), -}); + })(), +})(); expectType<{ readonly a: string; readonly b: { readonly c: number } }>(validate(nested2)({})); const nested3 = array(); -expectType(validate(nested3)([])); +expectType(validate(nested3())([])); const nested4 = array(string()); -expectType(validate(nested4)([])); +expectType(validate(nested4())([])); const nested5 = array(string(), optional(number())); -expectType(validate(nested5)([])); +expectType(validate(nested5())([])); -const nested6 = optional(array(string(), number())); +const nested6 = optional(array(string(), number())()); expectType(validate(nested6)([])); const nested7 = optional( object({ - arr: optional(array(string(), number())), - }), + arr: optional(array(string(), number())()), + })(), ); expectType< | { @@ -94,14 +94,14 @@ expectType(validate(parsed2)(new Date(0))); const parsed3 = date(); expectType(validate(parsed3)('1970-01-01T00:00:00.000Z')); -// minLength and nonEmpty -const len1 = minLength(1)(array(string())); +// minArrayLength and minStringLength +const len1 = minArrayLength(1)(array(string())()); const resLen1 = validate(len1)([]); expectType(resLen1); expectType(resLen1[0]); expectType(resLen1[1]); -const len10 = minLength(10)(array(string())); +const len10 = minArrayLength(10)(array(string())()); const resLen10 = validate(len10)([]); expectType< readonly [ @@ -131,20 +131,20 @@ expectType(resLen10[9]); expectType(resLen10[10]); expectType(resLen10[11]); -const nonEmptyArr = validate(nonEmpty(array(string())))(''); +const nonEmptyArr = validate(minArrayLength(1)(array(string())()))(''); expectType(nonEmptyArr); -const nonEmptyStr1 = validate(nonEmpty(string()))(''); +const nonEmptyStr1 = validate(minStringLength(1)(string()))(''); expectType(nonEmptyStr1); -const nonEmptyStr2 = validate(minLength(10)(string()))(''); +const nonEmptyStr2 = validate(minStringLength(10)(string()))(''); expectType(nonEmptyStr2); const personSchema = object({ name: string(), age: number(), email: optional(string()), -}); +})(); expectType<{ readonly name: string; @@ -165,6 +165,6 @@ expectType<{ readonly a: number; readonly b: string; readonly c?: unknown }>( a: number(), b: string(), c: unknown(), - }), + })(), )('dsdas'), ); diff --git a/__tests__/property-tests.spec.ts b/__tests__/property-tests.spec.ts index 7cde76f..b08a0b1 100644 --- a/__tests__/property-tests.spec.ts +++ b/__tests__/property-tests.spec.ts @@ -1,6 +1,7 @@ import Fc from 'fast-check'; import { anyPass, complement, is, sort } from 'ramda'; +import type { SomeSchema } from '../src'; import { validate, number, @@ -14,13 +15,12 @@ import { optional, nil, ValidationError, - minLength, - nonEmpty, + minArrayLength, λ, pipe, unknown, tuple, - allowUnknownKeys, + minStringLength, } from '../src'; import { isISODateString } from '../src/utils/dateUtils'; @@ -52,7 +52,13 @@ const notThrows = (predicate: (...args: T) => unkn ...args: T ) => !throws(predicate)(...args); -const primitiveValidators = [number, string, date, boolean]; +// eslint-disable-next-line +const primitiveValidators: ((schema?: SomeSchema) => SomeSchema)[] = [ + number, + string, + date, + boolean, +]; describe('@typeofweb/schema', () => { describe('validators', () => { @@ -143,7 +149,7 @@ describe('@typeofweb/schema', () => { Fc.array(Fc.oneof(Fc.string(), Fc.double(), Fc.integer())).filter( (arr) => arr.length > 0, ), - (arr) => notThrows(validate(oneOf(arr)))(arr[0]), + (arr) => notThrows(validate(oneOf(arr)()))(arr[0]), ), )); @@ -154,9 +160,9 @@ describe('@typeofweb/schema', () => { (arr) => arr.length > 0, ), (arr) => - notThrows(validate(oneOf(shuffle([...arr, ...primitiveValidators.map((v) => v())]))))( - shuffle(arr)[0], - ), + notThrows( + validate(oneOf(shuffle([...arr, ...primitiveValidators.map((v) => v())]))()), + )(shuffle(arr)[0]), ), )); @@ -164,7 +170,7 @@ describe('@typeofweb/schema', () => { Fc.assert( Fc.property( Fc.set(Fc.oneof(Fc.string(), Fc.double(), Fc.integer())).filter((x) => x.length > 1), - ([el, ...arr]) => throws(validate(oneOf(arr)), ValidationError)(el), + ([el, ...arr]) => throws(validate(oneOf(arr)()), ValidationError)(el), ), )); }); @@ -186,9 +192,9 @@ describe('@typeofweb/schema', () => { object({ a: number(), b: string(), - c: array(number(), string(), boolean()), - d: object({ e: string() }), - }), + c: array(number(), string(), boolean())(), + d: object({ e: string() })(), + })(), ), ), ), @@ -203,9 +209,9 @@ describe('@typeofweb/schema', () => { object({ a: number(), b: string(), - c: array(string()), - d: object({ e: string() }), - }), + c: array(string())(), + d: object({ e: string() })(), + })(), ), ValidationError, ), @@ -222,7 +228,7 @@ describe('@typeofweb/schema', () => { it('should validate tuples', () => Fc.assert( Fc.property(Fc.string(), Fc.integer(), (...args) => - notThrows(validate(tuple([string(), number()])))(args), + notThrows(validate(tuple([string(), number()])()))(args), ), )); @@ -231,7 +237,7 @@ describe('@typeofweb/schema', () => { Fc.property( Fc.anything().filter(complement(is(String))), Fc.anything().filter(complement(anyPass([is(Number), isCoercibleToNum]))), - (...args) => throws(validate(tuple([string(), number()])))(args), + (...args) => throws(validate(tuple([string(), number()])()))(args), ), )); }); @@ -292,9 +298,9 @@ describe('@typeofweb/schema', () => { Fc.array(Fc.oneof(Fc.string(), Fc.integer(), Fc.date(), Fc.boolean())), (arr) => { const assertion = arr.length > 0 ? notThrows : throws; - return assertion(validate(nonEmpty(array(...primitiveValidators.map((v) => v())))))( - arr, - ); + return assertion( + validate(minArrayLength(1)(array(...primitiveValidators.map((v) => v()))())), + )(arr); }, ), )); @@ -303,7 +309,7 @@ describe('@typeofweb/schema', () => { Fc.assert( Fc.property(Fc.string(), (str) => { const assertion = str.length > 0 ? notThrows : throws; - return assertion(validate(nonEmpty(string())))(str); + return assertion(validate(minStringLength(1)(string())))(str); }), )); }); @@ -324,7 +330,7 @@ describe('@typeofweb/schema', () => { const assertion = arr.length >= length ? notThrows : throws; return assertion( - validate(minLength(length)(array(...primitiveValidators.map((v) => v())))), + λ(array(...primitiveValidators.map((v) => v())), minArrayLength(length), validate), )(arr); }, ), @@ -337,7 +343,7 @@ describe('@typeofweb/schema', () => { Fc.integer({ min, max }), (str, length) => { const assertion = str.length >= length ? notThrows : throws; - return assertion(validate(minLength(length)(string())))(str); + return assertion(validate(minStringLength(length)(string())))(str); }, ), )); @@ -345,7 +351,9 @@ describe('@typeofweb/schema', () => { describe('allowUnknownKeys', () => Fc.assert( - Fc.property(Fc.object(), (obj) => notThrows(validate(allowUnknownKeys(object({}))))(obj)), + Fc.property(Fc.object(), (obj) => + notThrows(validate(object({}, { allowUnknownKeys: true })()))(obj), + ), )); }); @@ -356,7 +364,7 @@ describe('@typeofweb/schema', () => { Fc.property(Fc.oneof(Fc.constant(null), Fc.constant(undefined), Fc.string()), (value) => { const assertion = typeof value === 'string' ? (value.length > 0 ? notThrows : throws) : notThrows; - const validator = λ(string, nonEmpty, nullable, optional); + const validator = λ(string, minStringLength(1), nullable, optional); return assertion(validate(validator))(value); }), )); @@ -366,7 +374,7 @@ describe('@typeofweb/schema', () => { Fc.property(Fc.oneof(Fc.constant(null), Fc.constant(undefined), Fc.string()), (value) => { const assertion = typeof value === 'string' ? (value.length > 0 ? notThrows : throws) : notThrows; - const validator = pipe(string(), nonEmpty, nullable, optional); + const validator = pipe(string(), minStringLength(1), nullable, optional); return assertion(validate(validator))(value); }), )); diff --git a/__tests__/prototypePollution.spec.ts b/__tests__/prototypePollution.spec.ts index 42d170a..9cf8cf5 100644 --- a/__tests__/prototypePollution.spec.ts +++ b/__tests__/prototypePollution.spec.ts @@ -8,8 +8,8 @@ describe('prototype pollution', () => { const schema = object({ __proto__: object({ polluted: string(), - }), - }); + })(), + })(); expect(() => validate(schema)({ __proto__: { polluted: true } })).not.toThrowError(); expect(obj.polluted).toBe(undefined); // @ts-ignore @@ -25,9 +25,9 @@ describe('prototype pollution', () => { prototype: optional( object({ polluted: optional(string()), - }), + })(), ), - }), + })(), ); expect(() => validate(schema)(Object)).toThrowError(); expect(obj.polluted).toBe(undefined); @@ -41,8 +41,8 @@ describe('prototype pollution', () => { expect(obj.polluted).toBe(undefined); const t = {}; const schema = object({ - constructor: optional(object({ polluted: optional(string()) })), - }); + constructor: optional(object({ polluted: optional(string()) })()), + })(); expect(() => validate(schema)(t)).not.toThrowError(); expect(typeof t.constructor).toBe('function'); expect(obj.polluted).toBe(undefined); @@ -58,9 +58,9 @@ describe('prototype pollution', () => { constructor: object({ prototype: object({ polluted: string(), - }), - }), - }); + })(), + })(), + })(); expect(() => validate(schema)({ constructor: { prototype: { polluted: 'yes' } } }), ).not.toThrowError(); diff --git a/__tests__/refinements.spec.ts b/__tests__/refinements.spec.ts new file mode 100644 index 0000000..2373cb0 --- /dev/null +++ b/__tests__/refinements.spec.ts @@ -0,0 +1,97 @@ +import { + array, + date, + minArrayLength, + minStringLength, + nullable, + number, + optional, + refine, + string, + validate, + λ, +} from '../src'; + +describe('refinements', () => { + const even = refine((value: number, t) => (value % 2 === 0 ? t.nextValid(value) : t.left(value))); + + const noDuplicateItems = refine((arr: ReadonlyArray, t) => { + const allUnique = arr.every((item, index) => index === arr.indexOf(item)); + return allUnique ? t.nextValid(arr) : t.left(arr); + }); + + const allowTimestamps = refine((value, t) => + typeof value === 'number' ? t.nextValid(new Date(value)) : t.nextValid(value), + ); + + const presentOrFuture = refine((value: Date, t) => + value.getTime() >= Date.now() ? t.nextValid(value) : t.left(value), + ); + + it('nullable', () => { + expect(λ(string, nullable, validate)(null)).toEqual(null); + expect(λ(string, nullable, validate)('siema')).toEqual('siema'); + expect(() => λ(string, nullable, validate)(123)).toThrow(); + }); + + it('optional', () => { + expect(λ(string, optional, validate)(undefined)).toEqual(undefined); + expect(λ(string, optional, validate)('siema')).toEqual('siema'); + expect(() => λ(string, optional, validate)(123)).toThrow(); + }); + + it('even', () => { + expect(λ(number, even, validate)(2)).toEqual(2); + expect(() => λ(number, even, validate)(1)).toThrow(); + expect(() => λ(number, even, validate)('dsadsadsa')).toThrow(); + }); + + it('noDuplicateItems', () => { + expect(λ(array(string()), noDuplicateItems, validate)(['siema'])).toEqual(['siema']); + expect(() => λ(array(string()), noDuplicateItems, validate)('siema')).toThrow(); + expect(() => λ(array(string()), noDuplicateItems, validate)(['a', 'b', 'a'])).toThrow(); + }); + + it('allowTimestamps', () => { + expect(() => λ(date, allowTimestamps, validate)('')).toThrow(); + expect(λ(date, allowTimestamps, validate)(new Date(123123123))).toEqual(new Date(123123123)); + expect(λ(date, allowTimestamps, validate)(123123123)).toEqual(new Date(123123123)); + }); + + it('presentOrFuture', () => { + const futureDate = new Date(); + futureDate.setMonth(futureDate.getMonth() + 10); + const pastDate = new Date(); + pastDate.setMonth(futureDate.getMonth() - 10); + + expect(λ(presentOrFuture, date, validate)(futureDate)).toEqual(futureDate); + expect(() => λ(presentOrFuture, date, validate)('')).toThrow(); + expect(() => λ(presentOrFuture, date, validate)(pastDate)).toThrow(); + }); + + it('', () => { + expect(λ(string, nullable, optional, validate)(null)).toEqual(null); + expect(λ(string, nullable, optional, validate)(undefined)).toEqual(undefined); + expect(λ(string, nullable, optional, validate)('adsadsad')).toEqual('adsadsad'); + expect(() => λ(string, nullable, optional, validate)(12312312)).toThrow(); + + expect(λ(minStringLength(2), nullable, validate)('ab')).toEqual('ab'); + expect(() => λ(minStringLength(2), nullable, validate)('a')).toThrow(); + expect(() => λ(minStringLength(2), nullable, validate)(1)).toThrow(); + expect(() => λ(minStringLength(2), nullable, validate)(11231231)).toThrow(); + + expect(λ(array(string()), minArrayLength(2), nullable, validate)(['a', 'b'])).toEqual([ + 'a', + 'b', + ]); + expect(() => λ(array(string()), minArrayLength(2), nullable, validate)([])).toThrow(); + expect(() => λ(array(string()), minArrayLength(2), nullable, validate)([1, 2, 3])).toThrow(); + expect(() => λ(array(string()), minArrayLength(2), nullable, validate)([1, 'b'])).toThrow(); + expect(() => λ(array(string()), minArrayLength(2), nullable, validate)([1, 2])).toThrow(); + + expect(λ(number, even, nullable, validate)(2)).toEqual(2); + expect(λ(number, even, nullable, validate)(null)).toEqual(null); + expect(() => λ(number, even, nullable, validate)(1)).toThrow(); + expect(() => λ(number, even, nullable, validate)('adsadsadsa')).toThrow(); + }); +}); diff --git a/__tests__/refinements.test-d.ts b/__tests__/refinements.test-d.ts new file mode 100644 index 0000000..2aba259 --- /dev/null +++ b/__tests__/refinements.test-d.ts @@ -0,0 +1,63 @@ +import { expectType } from 'tsd'; + +import { + string, + validate, + pipe, + array, + number, + date, + optional, + nullable, + minArrayLength, + minStringLength, +} from '../src'; +import { refine } from '../src/refine'; + +const nullR = pipe(string, nullable, validate)(''); +expectType(nullR); + +const optionalR = pipe(string, optional, validate)(''); +expectType(optionalR); + +const even = refine((value: number, t) => (value % 2 === 0 ? t.nextValid(value) : t.left(value))); +const evenR = pipe(number, even, validate)(''); +expectType(evenR); + +const noDuplicateItems = refine((arr: ReadonlyArray, t) => { + const allUnique = arr.every((item, index) => index === arr.indexOf(item)); + return allUnique ? t.nextValid(arr) : t.left(arr); +}); +const noDuplicateItemsR = pipe(array(string()), noDuplicateItems, validate)(''); +expect(noDuplicateItemsR); + +const noDuplicateItemsAnyR = pipe(array(number()), noDuplicateItems, validate)(''); +expect(noDuplicateItemsAnyR); + +const allowTimestamps = refine((value, t) => + typeof value === 'number' ? t.nextValid(new Date(value)) : t.nextValid(value), +); +const allowDateTimestamps = pipe(date, allowTimestamps); +const allowDateTimestampsR = pipe(allowDateTimestamps, validate)(''); +expectType(allowDateTimestampsR); + +const presentOrFuture = refine((value: Date, t) => + value.getTime() >= Date.now() ? t.nextValid(value) : t.left(value), +); +const allowDateTimestampsR2 = pipe(presentOrFuture, date, allowTimestamps, validate)(''); +expectType(allowDateTimestampsR2); + +const ref1 = pipe(string, nullable, optional, validate)(''); +expectType(ref1); + +const ref2 = pipe(minStringLength(2), nullable, validate)(''); +expectType(ref2); + +const ref3 = pipe(array(string()), minArrayLength(2), nullable, validate)(''); +expectType(ref3); + +const ref4 = pipe(number, even, nullable, validate)(1); +expectType(ref4); + +// @ts-expect-error +pipe(nullable, even, validate)(1); diff --git a/__tests__/types.test-d.ts b/__tests__/types.test-d.ts new file mode 100644 index 0000000..4b00253 --- /dev/null +++ b/__tests__/types.test-d.ts @@ -0,0 +1,107 @@ +import { expectType } from 'tsd'; + +import type { + If, + KeysOfType, + Optional, + Required, + TupleOf, + UndefinedToOptional, +} from '../src/types'; + +/** + * TupleOf + */ +declare const to1: TupleOf; +expectType(to1); + +declare const to2: TupleOf; +expectType< + readonly [string, string, string, string, string, string, string, string, string, string] +>(to2); + +// @ts-expect-error +declare const _to3: TupleOf; + +/** + * If + */ +declare const if1: If; +expectType(if1); + +declare const if2: If; +expectType(if2); + +declare const if3: If<{ readonly a: 1; readonly b: 2 }, { readonly a: number }, true>; +expectType(if3); + +declare const if4: If<{ readonly a: 1; readonly b: 2 }, { readonly c: number }, true>; +expectType(if4); + +/** + * KeysOfType + */ +declare const kot1: KeysOfType<{ readonly a: number; readonly b: string }, string>; +expectType<'b'>(kot1); + +declare const kot2: KeysOfType< + { readonly a: number; readonly b: string; readonly c: string | number }, + string +>; +expectType<'b' | 'c'>(kot2); + +declare const kot3: KeysOfType< + { readonly a: number; readonly b: number; readonly c: object }, + string +>; +expectType(kot3); + +/** + * Optional + */ +declare const opt1: Optional<{ readonly a: string; readonly b: number | undefined }>; +expectType<{ readonly b?: number | undefined }>(opt1); + +declare const opt2: Optional<{ readonly a: string; readonly b?: number | undefined }>; +expectType<{ readonly b?: number | undefined }>(opt2); + +declare const opt3: Optional<{ readonly a: string; readonly b?: number }>; +expectType<{ readonly b?: number | undefined }>(opt3); + +/** + * Required + */ +declare const req1: Required<{ readonly a: string; readonly b: number | undefined }>; +expectType<{ readonly a: string }>(req1); + +declare const req2: Required<{ readonly a: string | undefined; readonly b?: number | undefined }>; +expectType<{}>(req2); + +declare const req3: Required<{ readonly a: string; readonly b?: number }>; +expectType<{ readonly a: string }>(req3); + +/** + * UndefinedToOptional + */ +declare const uto1: UndefinedToOptional<{}>; +expectType<{}>(uto1); + +declare const uto2: UndefinedToOptional; +expectType(uto2); + +declare const uto3: UndefinedToOptional; +expectType(uto3); + +declare const uto4: UndefinedToOptional; +expectType(uto4); + +declare const uto5: UndefinedToOptional<{ + readonly a: string; + readonly b: number | undefined; + readonly c?: object; +}>; +expectType<{ + readonly a: string; + readonly b?: number | undefined; + readonly c?: object | undefined; +}>(uto5); diff --git a/__tests__/unit-tests.spec.ts b/__tests__/unit-tests.spec.ts index cabec7e..78df851 100644 --- a/__tests__/unit-tests.spec.ts +++ b/__tests__/unit-tests.spec.ts @@ -1,15 +1,17 @@ import type { SomeSchema } from '../src'; import { - allowUnknownKeys, + pipe, + refine, + minArrayLength, + λ, + minStringLength, tuple, unknown, array, boolean, date, isSchema, - minLength, nil, - nonEmpty, nullable, number, object, @@ -23,13 +25,13 @@ import { isISODateString } from '../src/utils/dateUtils'; describe('@typeofweb/schema unit tests', () => { const simpleValidators: ReadonlyArray<() => SomeSchema> = [boolean, date, number, string]; - const objectValidator = () => object({}); - const arrayValidator = () => array(string()); - const literalValidator = () => oneOf(['a']); + const objectValidator = object({}); + const arrayValidator = array(string()); + const literalValidator = oneOf(['a']); const allValidators = [...simpleValidators, objectValidator, arrayValidator, literalValidator]; const modifiers: ReadonlyArray< { bivarianceHack(schema: SomeSchema): SomeSchema }['bivarianceHack'] - > = [minLength(35), nil, nonEmpty, nullable, optional]; + > = [nil, nullable, optional]; describe('validation', () => { it('string validator should coerce Date to ISOString', () => { @@ -97,7 +99,7 @@ describe('@typeofweb/schema unit tests', () => { name: string(), age: number(), email: optional(string()), - }); + })(); expect( validate(personSchema)({ @@ -147,7 +149,7 @@ describe('@typeofweb/schema unit tests', () => { const user = object({ name: string(), age: optional(number()), - }); + })(); expect(() => validate(user)({ age: 23 })).toThrowErrorMatchingInlineSnapshot( `"Invalid type! Expected { name: string, age: (number | undefined) } but got {\\"age\\":23}!"`, ); @@ -157,7 +159,7 @@ describe('@typeofweb/schema unit tests', () => { const user = object({ name: string(), age: optional(number()), - }); + })(); expect(() => validate(user)({ name: '32', x: 23 })).toThrowErrorMatchingInlineSnapshot( `"Invalid type! Expected { name: string, age: (number | undefined) } but got {\\"name\\":\\"32\\",\\"x\\":23}!"`, ); @@ -175,17 +177,17 @@ describe('@typeofweb/schema unit tests', () => { }); it('should fail minLength for non-string values', () => { - expect(() => validate(minLength(10)(string()))({})).toThrowError(); + expect(() => validate(minStringLength(10)(string()))({})).toThrowError(); }); it('should handle more complex case', () => { const schema = object({ - name: minLength(4)(string()), + name: minStringLength(4)(string()), email: string(), - firstName: nonEmpty(string()), - phone: nonEmpty(string()), + firstName: minStringLength(1)(string()), + phone: minStringLength(1)(string()), age: number(), - }); + })(); const validator = validate(schema); const obj = { @@ -201,9 +203,9 @@ describe('@typeofweb/schema unit tests', () => { it('unknown() should allow missing keys in object', () => { const schema = object({ - name: minLength(4)(string()), + name: minStringLength(4)(string()), email: unknown(), - }); + })(); const validator = validate(schema); const obj = { @@ -216,7 +218,7 @@ describe('@typeofweb/schema unit tests', () => { it(`object() should work with objects with null prototype`, () => { const schema = object({ name: string(), - }); + })(); const validator = validate(schema); const obj = Object.create(null, { name: { value: 'halo', enumerable: true } }) as object; @@ -227,7 +229,7 @@ describe('@typeofweb/schema unit tests', () => { it(`object() should work with sealed and frozen objects`, () => { const schema = object({ name: string(), - }); + })(); const validator = validate(schema); const obj1 = Object.seal({ name: 'jeden' }); @@ -242,7 +244,7 @@ describe('@typeofweb/schema unit tests', () => { it(`object() should strip non-enumerable properties when validating`, () => { const schema = object({ name: string(), - }); + })(); const validator = validate(schema); const obj = Object.create(null, { @@ -257,7 +259,7 @@ describe('@typeofweb/schema unit tests', () => { it(`object() should not mutate existing objects`, () => { const schema = object({ name: string(), - }); + })(); const validator = validate(schema); const obj = Object.create(null, { @@ -278,11 +280,11 @@ describe('@typeofweb/schema unit tests', () => { it('tuple should validate given items in given order', () => { const schema = object({ - a: tuple([]), - b: tuple([1, 2, 3]), - c: tuple([number(), string()]), - d: nullable(tuple([string()])), - }); + a: tuple([])(), + b: tuple([1, 2, 3])(), + c: tuple([number(), string()])(), + d: nullable(tuple([string()])()), + })(); const validator = validate(schema); const obj = { @@ -296,10 +298,10 @@ describe('@typeofweb/schema unit tests', () => { }); it('tuple should throw on invalid values', () => { - const validator1 = validate(tuple([])); - const validator2 = validate(tuple([1, 2, 3])); - const validator3 = validate(tuple([number(), string()])); - const validator4 = validate(tuple([1, 2, 3, number(), string()])); + const validator1 = validate(tuple([])()); + const validator2 = validate(tuple([1, 2, 3])()); + const validator3 = validate(tuple([number(), string()])()); + const validator4 = validate(tuple([1, 2, 3, number(), string()])()); expect(() => validator1([1, 2, 3])).toThrow(); expect(() => validator2([1, 3, 2])).toThrow(); @@ -312,9 +314,9 @@ describe('@typeofweb/schema unit tests', () => { object({ a: number(), b: string(), - c: array(string()), - d: object({ e: string(), f: oneOf([string(), false]) }), - }), + c: array(string())(), + d: object({ e: string(), f: oneOf([string(), false])() })(), + })(), ); expect(() => @@ -324,16 +326,17 @@ describe('@typeofweb/schema unit tests', () => { ); }); - it('object should not throw on unknown keys when allowUnknownKeys modifier is used', () => { + it('object should not throw on unknown keys when allowUnknownKeys option is true', () => { const validator = validate( - allowUnknownKeys( - object({ + object( + { a: number(), b: string(), - c: array(string()), - d: object({ e: string(), f: oneOf([string(), false]) }), - }), - ), + c: array(string())(), + d: object({ e: string(), f: oneOf([string(), false])() })(), + }, + { allowUnknownKeys: true }, + )(), ); const obj = { @@ -365,14 +368,14 @@ describe('@typeofweb/schema unit tests', () => { }); it('should throw on when array was expected but not given', () => { - const validator = validate(array(string())); + const validator = validate(array(string())()); expect(() => validator(42)).toThrowErrorMatchingInlineSnapshot( `"Invalid type! Expected string[] but got 42!"`, ); }); it('should throw on invalid values in arrays', () => { - const validator = validate(array(object({ a: string() }))); + const validator = validate(array(object({ a: string() })())()); expect(() => validator([123])).toThrowErrorMatchingInlineSnapshot( `"Invalid type! Expected { a: string }[] but got [123]!"`, ); @@ -388,11 +391,11 @@ describe('@typeofweb/schema unit tests', () => { e: array( object({ f: string(), - }), - ), - }), - }), - }), + })(), + )(), + })(), + })(), + })(), ); expect(() => @@ -416,6 +419,47 @@ describe('@typeofweb/schema unit tests', () => { `"Invalid type! Expected { a: number, b: { c: string, d: { e: { f: string }[] } } } but got {\\"a\\":1,\\"b\\":{\\"c\\":\\"aaa\\",\\"d\\":{\\"e\\":[{\\"f\\":\\"bbb\\"},{\\"f\\":123}]}}}!"`, ); }); + + it('should only accept arrays with at least 0 elements when [false] is given', () => { + const primitiveValidators: readonly ((schema?: SomeSchema) => SomeSchema)[] = [ + number, + string, + date, + boolean, + ]; + expect( + λ(array(...primitiveValidators.map((v) => v())), minArrayLength(0), validate)([false]), + ); + }); + + it('should not accept arrays with at least 1 element when [] is given', () => { + const primitiveValidators: readonly ((schema?: SomeSchema) => SomeSchema)[] = [ + number, + string, + date, + boolean, + ]; + expect(() => + λ(array(...primitiveValidators.map((v) => v())), minArrayLength(1), validate)([]), + ).toThrow(); + }); + + it('should exit early if one of the validators in oneOf returns right', () => { + const spy = jest.fn(); + const shouldNotBeCalled = refine((value) => { + spy(value); + throw new Error(String(value)); + }); + const validator = pipe(oneOf([string(), nullable(number()), shouldNotBeCalled()]), validate); + expect(validator(null)).toEqual(null); + expect(spy).toHaveBeenCalledTimes(0); + }); + + it('nil should allow undefined or null', () => { + expect(validate(nil())(null)).toEqual(null); + expect(validate(nil())(undefined)).toEqual(undefined); + expect(() => validate(nil())('aaaa')).toThrow(); + }); }); describe('schemaToString', () => { @@ -424,53 +468,51 @@ describe('@typeofweb/schema unit tests', () => { expect(schemaToString(number())).toMatchInlineSnapshot(`"number"`); expect(schemaToString(boolean())).toMatchInlineSnapshot(`"boolean"`); expect(schemaToString(date())).toMatchInlineSnapshot(`"Date"`); - expect(schemaToString(unknown())).toMatchInlineSnapshot(`"(unknown | undefined | null)"`); + expect(schemaToString(unknown())).toMatchInlineSnapshot(`"unknown"`); }); it('should work for oneOf', () => { - expect(schemaToString(oneOf([1]))).toMatchInlineSnapshot(`"1"`); - expect(schemaToString(oneOf([string()]))).toMatchInlineSnapshot(`"string"`); - expect(schemaToString(oneOf([1, 2, 3]))).toMatchInlineSnapshot(`"(1 | 2 | 3)"`); - expect(schemaToString(oneOf(['a', 'b', 'c']))).toMatchInlineSnapshot( + expect(schemaToString(oneOf([1])())).toMatchInlineSnapshot(`"1"`); + expect(schemaToString(oneOf([string()])())).toMatchInlineSnapshot(`"string"`); + expect(schemaToString(oneOf([1, 2, 3])())).toMatchInlineSnapshot(`"(1 | 2 | 3)"`); + expect(schemaToString(oneOf(['a', 'b', 'c'])())).toMatchInlineSnapshot( `"(\\"a\\" | \\"b\\" | \\"c\\")"`, ); - expect(schemaToString(oneOf(['a', 'b', 12, 'c']))).toMatchInlineSnapshot( + expect(schemaToString(oneOf(['a', 'b', 12, 'c'])())).toMatchInlineSnapshot( `"(\\"a\\" | \\"b\\" | 12 | \\"c\\")"`, ); - expect(schemaToString(oneOf([1, 2, string(), 3, boolean()]))).toMatchInlineSnapshot( + expect(schemaToString(oneOf([1, 2, string(), 3, boolean()])())).toMatchInlineSnapshot( `"(1 | 2 | string | 3 | boolean)"`, ); }); it('should work for arrays', () => { - expect(schemaToString(array(number()))).toMatchInlineSnapshot(`"number[]"`); - expect(schemaToString(array(string()))).toMatchInlineSnapshot(`"string[]"`); - expect(schemaToString(array(string(), boolean()))).toMatchInlineSnapshot( + expect(schemaToString(array(number())())).toMatchInlineSnapshot(`"number[]"`); + expect(schemaToString(array(string())())).toMatchInlineSnapshot(`"string[]"`); + expect(schemaToString(array(string(), boolean())())).toMatchInlineSnapshot( `"(string | boolean)[]"`, ); - expect(schemaToString(array(unknown()))).toMatchInlineSnapshot( - `"(unknown | undefined | null)[]"`, - ); + expect(schemaToString(array(unknown())())).toMatchInlineSnapshot(`"unknown[]"`); }); it('should work for objects', () => { - expect(schemaToString(object({}))).toMatchInlineSnapshot(`"{}"`); - expect(schemaToString(object({ a: string() }))).toMatchInlineSnapshot(`"{ a: string }"`); + expect(schemaToString(object({})())).toMatchInlineSnapshot(`"{}"`); + expect(schemaToString(object({ a: string() })())).toMatchInlineSnapshot(`"{ a: string }"`); expect( - schemaToString(object({ a: string(), b: number(), 'no elo koleś': boolean() })), + schemaToString(object({ a: string(), b: number(), 'no elo koleś': boolean() })()), ).toMatchInlineSnapshot(`"{ a: string, b: number, \\"no elo koleś\\": boolean }"`); }); it('should work for tuples', () => { - expect(schemaToString(tuple(['a', string(), number()]))).toMatchInlineSnapshot( + expect(schemaToString(tuple(['a', string(), number()])())).toMatchInlineSnapshot( `"[\\"a\\", string, number]"`, ); - expect(schemaToString(tuple(['a']))).toMatchInlineSnapshot(`"[\\"a\\"]"`); - expect(schemaToString(tuple([number(), oneOf(['s', 'm', 'h'])]))).toMatchInlineSnapshot( + expect(schemaToString(tuple(['a'])())).toMatchInlineSnapshot(`"[\\"a\\"]"`); + expect(schemaToString(tuple([number(), oneOf(['s', 'm', 'h'])()])())).toMatchInlineSnapshot( `"[number, (\\"s\\" | \\"m\\" | \\"h\\")]"`, ); expect( - schemaToString(tuple([number(), tuple([string(), oneOf(['s', 'm', 'h'])])])), + schemaToString(tuple([number(), tuple([string(), oneOf(['s', 'm', 'h'])()])()])()), ).toMatchInlineSnapshot(`"[number, [string, (\\"s\\" | \\"m\\" | \\"h\\")]]"`); }); @@ -481,8 +523,8 @@ describe('@typeofweb/schema unit tests', () => { a: string(), b: number(), 'no elo koleś': boolean(), - c: object({ e: array(oneOf([array(string()), object({ xxx: number() })])) }), - }), + c: object({ e: array(oneOf([array(string())(), object({ xxx: number() })()])())() })(), + })(), ), ).toMatchInlineSnapshot( `"{ a: string, b: number, \\"no elo koleś\\": boolean, c: { e: (string[] | { xxx: number })[] } }"`, @@ -501,33 +543,33 @@ describe('@typeofweb/schema unit tests', () => { }); it('should work for oneOf with modifiers', () => { - expect(schemaToString(optional(oneOf([1])))).toMatchInlineSnapshot(`"(1 | undefined)"`); - expect(schemaToString(nullable(oneOf([string()])))).toMatchInlineSnapshot( + expect(schemaToString(optional(oneOf([1])()))).toMatchInlineSnapshot(`"(1 | undefined)"`); + expect(schemaToString(nullable(oneOf([string()])()))).toMatchInlineSnapshot( `"(string | null)"`, ); - expect(schemaToString(nullable(oneOf([1, 2, 3])))).toMatchInlineSnapshot( + expect(schemaToString(nullable(oneOf([1, 2, 3])()))).toMatchInlineSnapshot( `"((1 | 2 | 3) | null)"`, ); - expect(schemaToString(oneOf([1, 2, optional(string()), 3, boolean()]))).toMatchInlineSnapshot( - `"(1 | 2 | (string | undefined) | 3 | boolean)"`, - ); + expect( + schemaToString(oneOf([1, 2, optional(string()), 3, boolean()])()), + ).toMatchInlineSnapshot(`"(1 | 2 | (string | undefined) | 3 | boolean)"`); }); it('should work for arrays with modifiers', () => { - expect(schemaToString(array(number()))).toMatchInlineSnapshot(`"number[]"`); - expect(schemaToString(array(string()))).toMatchInlineSnapshot(`"string[]"`); - expect(schemaToString(array(string(), boolean()))).toMatchInlineSnapshot( + expect(schemaToString(array(number())())).toMatchInlineSnapshot(`"number[]"`); + expect(schemaToString(array(string())())).toMatchInlineSnapshot(`"string[]"`); + expect(schemaToString(array(string(), boolean())())).toMatchInlineSnapshot( `"(string | boolean)[]"`, ); }); it('should work for objects with modifiers', () => { - expect(schemaToString(optional(object({})))).toMatchInlineSnapshot(`"({} | undefined)"`); - expect(schemaToString(nullable(object({ a: string() })))).toMatchInlineSnapshot( + expect(schemaToString(optional(object({})()))).toMatchInlineSnapshot(`"({} | undefined)"`); + expect(schemaToString(nullable(object({ a: string() })()))).toMatchInlineSnapshot( `"({ a: string } | null)"`, ); expect( - schemaToString(nil(object({ a: string(), b: number(), 'no elo koleś': boolean() }))), + schemaToString(nil(object({ a: string(), b: number(), 'no elo koleś': boolean() })())), ).toMatchInlineSnapshot( `"({ a: string, b: number, \\"no elo koleś\\": boolean } | undefined | null)"`, ); @@ -541,9 +583,11 @@ describe('@typeofweb/schema unit tests', () => { b: number(), 'no elo koleś': boolean(), c: object({ - e: nullable(array(oneOf([optional(array(string())), object({ xxx: number() })]))), - }), - }), + e: nullable( + array(oneOf([optional(array(string())()), object({ xxx: number() })()])())(), + ), + })(), + })(), ), ).toMatchInlineSnapshot( `"{ a: (string | undefined), b: number, \\"no elo koleś\\": boolean, c: { e: (((string[] | undefined) | { xxx: number })[] | null) } }"`, diff --git a/docs/docs/introduction.md b/docs/docs/introduction.md index 0d5025c..fa2fd3b 100644 --- a/docs/docs/introduction.md +++ b/docs/docs/introduction.md @@ -21,7 +21,7 @@ const personSchema = object({ name: string(), age: number(), email: optional(string()), -}); +})(); const mark = { name: 'Mark', @@ -47,7 +47,7 @@ import { number, object, date, validate } from '@typeofweb/schema'; const userQuery = object({ dob: date(), query: number(), -}); +})(); const payload = { dob: '2001-04-16T00:00:00.000Z', @@ -69,10 +69,10 @@ const result = userQueryValidator(payload); ```ts import { string, object, array, validate } from '@typeofweb/schema'; -const validator = validate(array(object({ a: string() }))); +const validator = validate(array(object({ a: string() })())()); const result = validator([123]); -// throws ValidationError: Invalid type! Expected ≫{ a: ≫string≪ }[]≪ but got [123]! +// throws ValidationError: Invalid type! Expected { a: string }[] but got [123]! ``` ## Types generated from validators @@ -84,7 +84,7 @@ const personSchema = object({ name: string(), age: number(), email: optional(string()), -}); +})(); type Person = TypeOf; // type Person = { diff --git a/docs/docs/modifiers/allowUnknownKeys.md b/docs/docs/modifiers/allowUnknownKeys.md deleted file mode 100644 index bfb45d0..0000000 --- a/docs/docs/modifiers/allowUnknownKeys.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -id: allowUnknownKeys -title: allowUnknownKeys ---- - -The `allowUnknownKeys` modifier is used to not throw validation error on unspecifed fields in object. - -```ts -const computerSchema = object({ - cpuModel: string(), - gpuModel: string(), - RAM: number(), -}); - -const anotherComputerSchema = allowUnknownKeys(computerSchema); - -const computer = { - cpuModel: 'Intel', - gpuModel: 'Nvidia', - RAM: 8, - motherboard: 'MSI', -}; - -// It's fine -validate(anotherComputerSchema)(computer); - -// Throws ValidationError -validate(computerSchema)(computer); -``` diff --git a/docs/docs/modifiers/minArrayLength.md b/docs/docs/modifiers/minArrayLength.md new file mode 100644 index 0000000..65426eb --- /dev/null +++ b/docs/docs/modifiers/minArrayLength.md @@ -0,0 +1,38 @@ +--- +id: minArrayLength +title: minArrayLength +--- + +The `minArrayLength` modifier is used to contraint length of`array` values. It also changes the return validation type – tuples with given number of elements are returned instead of arrays: + +```ts +const len10Validator = λ(array(number()), minArrayLength(2), validate)([]); +const result = validate(len10Validator)([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); +// type of result is +// readonly [number, number, ...number[]] +``` + +As a result, it makes using such arrays/tuples more typesafe. Notice how the type of the `thirdElement` is different: + +```ts +// number +const firstElement = result[0]; +// number +const tenthElement = result[1]; +// number | undefined +const thirdElement = result[2]; +``` + +## Type instantiation is excessively deep and possibly infinite + +On rare occasions you may come across the following TypeScript error: _Type instantiation is excessively deep and possibly infinite.ts(2589)_. It's a limitation of the TypeScript compiler when used on very deeply nested conditional types such as the tuples generated by the `minArrayLength` modifier. + +Should this issue ever occur to you, please override the inferred generic parameter with `number`: + +```ts +// error in compile-time +const whoopsieDaisyValidator = λ(array(number()), minArrayLength(100000), validate)([]); + +// fallback to less-typesafe array: readonly number[] +const better = λ(array(number()), minArrayLength(100000), validate)([]); +``` diff --git a/docs/docs/modifiers/minLength.md b/docs/docs/modifiers/minLength.md deleted file mode 100644 index 784e388..0000000 --- a/docs/docs/modifiers/minLength.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -id: minLength -title: minLength ---- - -The `minLength` modifier is used to contraint length of `string` or `array` values. - -```ts -const atLeastTwoCharsValidator = validate(minLength(2)(string())); - -// Returns 'ok' -const ok = atLeastTwoCharsValidator('ok'); - -// Throws ValidationError -const notOk = atLeastTwoCharsValidator('?'); -``` - -The `minLength` modifier also changes the return validation type when used on arrays. In such case arrays become tuples with given number of elements: - -```ts -const len10Validator = validate(minLength(10)(array(number()))); -// type of result is -// readonly [number, number, number, number, number, number, number, number, number, number, ...number[]] -const result = validate(len10Validator)([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); -``` - -As a result, it makes using such arrays/tuples more typesafe. Notice how the type of the `eleventhElement` is different: - -```ts -// number -const firstElement = result[0]; -// number -const tenthElement = result[9]; -// number | undefined -const eleventhElement = result[10]; -``` - -## Type instantiation is excessively deep and possibly infinite - -On rare occasions you may come across the following TypeScript error: _Type instantiation is excessively deep and possibly infinite.ts(2589)_. It's a limitation of the TypeScript compiler when used on very deeply nested conditional types such as the tuples generated by the `minLength` modifier. - -Should this issue ever occur to you, please override the inferred generic parameter with `number`: - -```ts -// errors in compile-time -const whoopsieDaisyValidator = validate(minLength(100000)(array(number())))([]); - -// fallback to less-typesafe array: readonly number[] -const better = validate(minLength(100000)(array(number())))([]); -``` diff --git a/docs/docs/modifiers/minStringLength.md b/docs/docs/modifiers/minStringLength.md new file mode 100644 index 0000000..7ec2d75 --- /dev/null +++ b/docs/docs/modifiers/minStringLength.md @@ -0,0 +1,16 @@ +--- +id: minStringLength +title: minStringLength +--- + +The `minStringLength` modifier is used to contraint length of `string` or `array` values. + +```ts +const atLeastTwoCharsValidator = validate(minStringLength(2)(string())); + +// Returns 'ok' +const ok = atLeastTwoCharsValidator('ok'); + +// Throws ValidationError +const notOk = atLeastTwoCharsValidator('?'); +``` diff --git a/docs/docs/modifiers/nonEmpty.md b/docs/docs/modifiers/nonEmpty.md deleted file mode 100644 index 8a855c1..0000000 --- a/docs/docs/modifiers/nonEmpty.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -id: nonEmpty -title: nonEmpty ---- - -The `nonEmpty` modifier can be applied to `string` or `array` schemas in order to make sure they contain at least one character or one item respectively. - -```ts -const nonEmptyArrayValidator = validate(nonEmpty(array(string()))); - -// Returns ['a', 'b', 'c'] -const ok = nonEmptyArrayValidator(['a', 'b', 'c']); - -// Throws ValidationError -const notOk = nonEmptyArrayValidator([]); -``` - -The `nonEmpty` modifier also changes the return type of the validation – it's a variadic tuple instead of an array: - -```ts -// readonly [string, ...string[]] -const nonEmptyResult = validate(nonEmpty(array(string())))(['a']); -``` - -You can read more about it in the `minLength` section below. Technically speaking the implementation of the `nonEmpty` is as simple as: - -```ts -const nonEmpty = minLength(1); -``` diff --git a/docs/docs/modifiers/refinements.md b/docs/docs/modifiers/refinements.md new file mode 100644 index 0000000..a74d150 --- /dev/null +++ b/docs/docs/modifiers/refinements.md @@ -0,0 +1,107 @@ +--- +id: refinements +title: Custom modifiers (refinements) +--- + +`@typeofweb/schema` is almost infinitely extensible thanks to the `refine` function. It can be used to: + +- create custom validators +- modify or adjust validators which are built-in +- parse data before validating + +## `refine` + +`refine` takes two parameters both of which are functions. First of them is a "refinement function" which is where the implementation details go. Such a function is given `value: unknown` as its first parameter and `t: RefinementToolkit` as second: + +```ts +refine((value, t) => { + // … +}); +``` + +`value` is, well, the value passed to the validator, and `t` is an object which contains four functions: `right`, `left`, `nextValid`, and `nextNotValid`, all of which are described in detail below. + +Second parameter of `refine` is another function which is used for stringifying the validator for error purposes: + +```ts +refine( + (value, t) => { + // … + }, + () => 'XXX', +); +``` + +Such a validator would throw `Invalid type! Expected XXX but got …!` in case it's fed with invalid data. + +## `RefinementToolkit` + +As mentioned in the previous paragraph, `RefinementToolkit` consists of 3 functions: `right`, `left`, and `next`. They all play a different role and are equally useful when creating custom validators or modifiers: + +- `right(value)` – ends validation with a success and `value` +- `left(value)` – ends validation with an error +- `nextValid(value)` – continues validation if there are other validators after it; success otherwise +- `nextNotValid(value)` – continues validation if there are other validators after it; fail otherwise + +In most of the cases you'll want to use `left` and `nextValid`. + +Let's look at a few short examples of where each of these functions comes handy: + +```ts +export const optional = refine((value, t) => + value === undefined ? t.right(undefined) : t.nextNotValid(value), +); + +const presentOrFuture = refine((value: Date, t) => + value.getTime() >= Date.now() ? t.nextValid(value) : t.left(value), +); +``` + +`optional` is a built-in validator which either succeeds instantly if given value is equal to `undefined` (`t.right`) or passes it along to the next validator. In case there are no other validators in the pipeline, validation fails because the value is clearly not `undefined`. + +`presentOrFuture` is a validator meant to be used after the `date` validator. It's role is to determine if given date is present or in the future, and if it is so, it should pass the date to another validator in line (`t.nextValid`). However, if given date is in the past, we expect this validator to short circuit and finish with an error (`t.left`). + +`t.nextValid` and `t.left` are most commonly used. Use `t.right` sparingly only if you're certain no further validation will ever be required on given value. `t.nextNotValid` is meant for refinements which are supposed to extend other validators – such as optional, nullable or nil. + +## Custom validator + +As a matter of fact, all built-in validators are now implemented using the `refine` function. Let's look at the `number` implementation: + +```ts +export const number = refine( + (value, t) => { + const parsedValue = parseNumber(value); + if (typeof parsedValue !== 'number' || Number.isNaN(parsedValue)) { + return t.left(value); + } + return t.nextValid(parsedValue); + }, + () => typeToPrint('number'), +); + +function parseNumber(value: unknown) { + if (typeof value === 'string') { + if (value.trim() === '') { + return value; + } + return Number(value); + } + return value; +} +``` + +You can create your own validators in the same manner. + +## Modify validators + +## Parse data + +Another common scenario is allowing timestamps where dates are expected. We can create a refinement which takes a timestamp and converts it to a `Date` instance before further validation is executed: + +```ts +const allowTimestamps = refine((value, t) => + typeof value === 'number' ? t.nextValid(new Date(value)) : t.nextValid(value), +); + +λ(date, allowTimestamps, validate)(1231231231231); // new Date(1231231231231) Tue Jan 06 2009 09:40:31 GMT+0100 +``` diff --git a/docs/docs/utilities/pipe.md b/docs/docs/utilities/pipe.md index 298ac7f..ed6ee8b 100644 --- a/docs/docs/utilities/pipe.md +++ b/docs/docs/utilities/pipe.md @@ -23,5 +23,5 @@ const blogSchema = object({ description: λ(string, nil), href: λ(string, nil, nonEmpty), rssUrl: λ(string, nil, nonEmpty), -}); +})(); ``` diff --git a/docs/docs/utilities/typeOf.md b/docs/docs/utilities/typeOf.md index 04d36a2..a2b40c2 100644 --- a/docs/docs/utilities/typeOf.md +++ b/docs/docs/utilities/typeOf.md @@ -14,7 +14,7 @@ const blogSchema = object({ description: λ(string, nil), href: λ(string, nil, nonEmpty), rssUrl: λ(string, nil, nonEmpty), -}); +})(); type Blog = TypeOf; // type Blog = { diff --git a/docs/docs/validate.md b/docs/docs/validate.md index 52d6129..ce6955e 100644 --- a/docs/docs/validate.md +++ b/docs/docs/validate.md @@ -23,7 +23,7 @@ const queryValidator = validate( dateFrom: date(), dateTo: date(), resultsPerPage: number(), - }), + })(), ); const queryObject = queryValidator(parsedQuery); diff --git a/docs/docs/validators/array.md b/docs/docs/validators/array.md index afe412d..92f8f45 100644 --- a/docs/docs/validators/array.md +++ b/docs/docs/validators/array.md @@ -3,10 +3,10 @@ id: array title: array --- -Creates a schema that matches arrays containing values specified by the schemas passed as parameters. +Creates a schema that matches arrays containing values specified by the schemas passed as parameters: ```ts -const musicGenresSchema = array(string()); +const musicGenresSchema = array(string())(); const musicGenresValidator = validate(musicGenresSchema); // Returns ['classical', 'lofi', 'pop'] @@ -14,7 +14,7 @@ const musicGenres = musicGenresValidator(['classical', 'lofi', 'pop']); ``` ```ts -const primitiveValidator = validate(array(string(), number(), boolean())); +const primitiveValidator = validate(array(string(), number(), boolean())()); // Returns [false, 'string', 123, 42, ':)'] primitiveValidator([false, 'string', 123, 42, ':)']); ``` diff --git a/docs/docs/validators/date.md b/docs/docs/validators/date.md index 1795b24..9b701df 100644 --- a/docs/docs/validators/date.md +++ b/docs/docs/validators/date.md @@ -14,3 +14,7 @@ const annieBirthday = dateValidator( new Date('Tue Jan 12 2021 22:00:20 GMT+0100 (Central European Standard Time)'), ); ``` + +## Other date formats + +By default, other date formats or timestamps are not supported. However, you're encouraged to create a custom refinement for this purpose. You can find a working example in [Custom modifiers (refinements)](modifiers/refinements.md). diff --git a/docs/docs/validators/number.md b/docs/docs/validators/number.md index 02968e3..de765e9 100644 --- a/docs/docs/validators/number.md +++ b/docs/docs/validators/number.md @@ -3,7 +3,7 @@ id: number title: number --- -Creates a schema that matches numbers. +Creates a schema that matches numbers: ```ts const numberSchema = number(); diff --git a/docs/docs/validators/object.md b/docs/docs/validators/object.md index 0df9520..3e5b55b 100644 --- a/docs/docs/validators/object.md +++ b/docs/docs/validators/object.md @@ -12,7 +12,7 @@ const carSchema = object({ mass: number(), enginePower: number(), fuelCapacity: number(), -}); +})(); const carValidator = validate(carSchema); /* Returns { @@ -30,3 +30,40 @@ const matthewCar = carValidator({ fuelCapacity: 59, }); ``` + +## `allowUnknownKeys` + +The `allowUnknownKeys` option is used to not throw validation error on unspecifed fields in object (`false` by default): + +```ts +const computerSchema = object( + { + cpuModel: string(), + gpuModel: string(), + RAM: number(), + }, + { allowUnknownKeys: false }, +)(); + +const anotherComputerSchema = object( + { + cpuModel: string(), + gpuModel: string(), + RAM: number(), + }, + { allowUnknownKeys: true }, +)(); + +const computer = { + cpuModel: 'Intel', + gpuModel: 'Nvidia', + RAM: 8, + motherboard: 'MSI', +}; + +// Throws ValidationError +validate(computerSchema)(computer); + +// It's fine +validate(anotherComputerSchema)(computer); +``` diff --git a/docs/docs/validators/oneOf.md b/docs/docs/validators/oneOf.md index 617fdb6..c6c8a0e 100644 --- a/docs/docs/validators/oneOf.md +++ b/docs/docs/validators/oneOf.md @@ -6,7 +6,7 @@ title: oneOf Creates a schema that matches only specified values. ```ts -const fishSchema = oneOf(['trout', 'catfish']); +const fishSchema = oneOf(['trout', 'catfish'])(); const fishValidator = validate(fishSchema); // Returns 'trout' diff --git a/docs/sidebars.js b/docs/sidebars.js index 1762331..83264f2 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -19,9 +19,9 @@ module.exports = { 'modifiers/nullable', 'modifiers/optional', 'modifiers/nil', - 'modifiers/nonEmpty', - 'modifiers/minLength', - 'modifiers/allowUnknownKeys', + 'modifiers/minArrayLength', + 'modifiers/minStringLength', + 'modifiers/refinements', ], Utilities: ['utilities/pipe', 'utilities/typeOf'], }, diff --git a/package.json b/package.json index 92ec35c..6202bc3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@typeofweb/schema", - "version": "0.6.1", + "version": "0.6.1-2", "main": "dist/index.common.js", "module": "dist/index.esm.js", "browser": "dist/index.umd.js", @@ -35,36 +35,36 @@ "validations" ], "devDependencies": { - "@rollup/plugin-commonjs": "17.0.0", - "@rollup/plugin-node-resolve": "11.1.0", - "@rollup/plugin-typescript": "8.1.0", + "@rollup/plugin-commonjs": "17.1.0", + "@rollup/plugin-node-resolve": "11.2.0", + "@rollup/plugin-typescript": "8.2.0", "@tsconfig/node12": "1.0.7", "@tsconfig/node14": "1.0.0", - "@types/jest": "26.0.20", - "@types/ramda": "0.27.36", - "@typescript-eslint/eslint-plugin": "4.14.1", - "@typescript-eslint/parser": "4.14.1", - "all-contributors-cli": "6.19.0", - "eslint": "7.18.0", - "eslint-config-prettier": "7.2.0", + "@types/jest": "26.0.21", + "@types/ramda": "0.27.39", + "@typescript-eslint/eslint-plugin": "4.18.0", + "@typescript-eslint/parser": "4.18.0", + "all-contributors-cli": "6.20.0", + "eslint": "7.22.0", + "eslint-config-prettier": "8.1.0", "eslint-plugin-functional": "3.2.1", "eslint-plugin-import": "2.22.1", - "fast-check": "2.12.0", + "fast-check": "2.14.0", "husky": "4.3.8", "jest": "26.6.3", - "lint-staged": "10.5.3", + "lint-staged": "10.5.4", "prettier": "2.2.1", "ramda": "0.27.1", "rimraf": "3.0.2", - "rollup": "2.38.0", - "rollup-plugin-filesize": "9.1.0", - "rollup-plugin-license": "2.2.0", + "rollup": "2.42.2", + "rollup-plugin-filesize": "9.1.1", + "rollup-plugin-license": "2.3.0", "rollup-plugin-prettier": "2.1.0", "rollup-plugin-terser": "7.0.2", - "ts-jest": "26.4.4", + "ts-jest": "26.5.4", "tsd": "https://github.com/typeofweb/tsd#pkg", "tslib": "2.1.0", - "typescript": "4.1.3", + "typescript": "4.2.3", "weak-napi": "2.0.2" }, "scripts": { diff --git a/rollup.config.js b/rollup.config.js index 1f3a24b..aeb1191 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -20,6 +20,7 @@ const rollupConfig = [ format: 'es', dir: './', entryFileNames: pkg.module, + sourcemap: true, plugins: [ shouldCompress ? terser({ @@ -37,6 +38,7 @@ const rollupConfig = [ format: 'cjs', dir: './', entryFileNames: pkg.main, + sourcemap: true, plugins: [ shouldCompress ? terser({ @@ -52,6 +54,7 @@ const rollupConfig = [ { name: '@typeofweb/schema', entryFileNames: pkg.browser, + sourcemap: true, format: 'umd', dir: './', plugins: [ diff --git a/src/index.ts b/src/index.ts index fbed314..332223b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,9 +14,10 @@ export { tuple } from './validators/tuple'; export { unknown } from './validators/unknown'; export { validate } from './validators/__validate'; -export { minLength } from './modifiers/minLength'; export { nil } from './modifiers/nil'; -export { nonEmpty } from './modifiers/nonEmpty'; export { nullable } from './modifiers/nullable'; export { optional } from './modifiers/optional'; -export { allowUnknownKeys } from './modifiers/allowUnknownKeys'; +export { minArrayLength } from './modifiers/minArrayLength'; +export { minStringLength } from './modifiers/minStringLength'; + +export { refine } from './refine'; diff --git a/src/modifiers/allowUnknownKeys.ts b/src/modifiers/allowUnknownKeys.ts deleted file mode 100644 index 958f730..0000000 --- a/src/modifiers/allowUnknownKeys.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { MergeModifiers, Schema, SomeSchema } from '../types'; - -export const allowUnknownKeys = >(schema: S) => - ({ - ...schema, - __modifiers: { ...schema.__modifiers, allowUnknownKeys: true }, - } as Schema< - S['__type'], - MergeModifiers, - S['__values'] - >); diff --git a/src/modifiers/minArrayLength.ts b/src/modifiers/minArrayLength.ts new file mode 100644 index 0000000..4bc28fe --- /dev/null +++ b/src/modifiers/minArrayLength.ts @@ -0,0 +1,14 @@ +import { refine } from '../refine'; +import type { TupleOf } from '../types'; + +export const minArrayLength = (minLength: L) => + refine((value: readonly unknown[], t) => { + return value.length >= minLength + ? t.nextValid( + value as readonly [ + ...TupleOf, + ...(readonly typeof value[number][]) + ], + ) + : t.left(value); + }); diff --git a/src/modifiers/minLength.ts b/src/modifiers/minLength.ts deleted file mode 100644 index f4dbc90..0000000 --- a/src/modifiers/minLength.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ValidationError } from '../errors'; -import type { SomeSchema, Schema, TupleOf, MergeModifiers } from '../types'; -import { left } from '../utils/either'; - -export const minLength = (length: L) => < - S extends SomeSchema | SomeSchema ->( - schema: S, -) => { - return { - ...(schema as SomeSchema | SomeSchema), - __validate(value) { - if ((typeof value !== 'string' && !Array.isArray(value)) || value.length < length) { - return left(new ValidationError(this, value)); - } - return schema.__validate(value); - }, - __modifiers: { - ...schema.__modifiers, - minLength: length, - }, - } as Schema< - S['__type'] extends readonly unknown[] - ? readonly [...TupleOf, ...S['__type']] - : S['__type'], - MergeModifiers< - S['__modifiers'], - { - readonly minLength: L; - } - >, - S['__values'] - >; -}; diff --git a/src/modifiers/minStringLength.ts b/src/modifiers/minStringLength.ts new file mode 100644 index 0000000..589e2c7 --- /dev/null +++ b/src/modifiers/minStringLength.ts @@ -0,0 +1,4 @@ +import { refine } from '../refine'; + +export const minStringLength = (minLength: L) => + refine((value: string, t) => (value.length >= minLength ? t.nextValid(value) : t.left(value))); diff --git a/src/modifiers/nil.ts b/src/modifiers/nil.ts index dd3622d..2785a62 100644 --- a/src/modifiers/nil.ts +++ b/src/modifiers/nil.ts @@ -1,7 +1,6 @@ -import { nullable } from '../modifiers/nullable'; -import { optional } from '../modifiers/optional'; -import type { SomeSchema } from '../types'; +import { refine } from '../refine'; -export const nil = >(schema: S) => { - return nullable(optional(schema)); -}; +export const nil = refine( + (value, t) => (value === null || value === undefined ? t.right(value) : t.nextNotValid(value)), + () => `undefined | null`, +); diff --git a/src/modifiers/nonEmpty.ts b/src/modifiers/nonEmpty.ts deleted file mode 100644 index 23ecc38..0000000 --- a/src/modifiers/nonEmpty.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { minLength } from '../modifiers/minLength'; - -export const nonEmpty = minLength(1); diff --git a/src/modifiers/nullable.ts b/src/modifiers/nullable.ts index 3f6d47b..1d0ac09 100644 --- a/src/modifiers/nullable.ts +++ b/src/modifiers/nullable.ts @@ -1,22 +1,7 @@ -import type { SomeSchema, Schema, MergeModifiers } from '../types'; -import { right } from '../utils/either'; +import { refine } from '../refine'; +import { typeToPrint } from '../stringify'; -export const nullable = >(schema: S) => { - return { - ...schema, - __modifiers: { - ...schema.__modifiers, - nullable: true, - }, - __validate(value) { - if (value === null) { - return right(value); - } - return schema.__validate(value); - }, - } as Schema< - S['__type'], - MergeModifiers, - S['__values'] - >; -}; +export const nullable = refine( + (value, t) => (value === null ? t.right(null) : t.nextNotValid(value)), + () => typeToPrint('null'), +); diff --git a/src/modifiers/optional.ts b/src/modifiers/optional.ts index a972323..d816a04 100644 --- a/src/modifiers/optional.ts +++ b/src/modifiers/optional.ts @@ -1,22 +1,7 @@ -import type { MergeModifiers, Schema, SomeSchema } from '../types'; -import { right } from '../utils/either'; +import { refine } from '../refine'; +import { typeToPrint } from '../stringify'; -export const optional = >(schema: S) => { - return { - ...schema, - __modifiers: { - ...schema.__modifiers, - optional: true, - }, - __validate(value) { - if (value === undefined) { - return right(value); - } - return schema.__validate(value); - }, - } as Schema< - S['__type'], - MergeModifiers, - S['__values'] - >; -}; +export const optional = refine( + (value, t) => (value === undefined ? t.right(undefined) : t.nextNotValid(value)), + () => typeToPrint('undefined'), +); diff --git a/src/refine.ts b/src/refine.ts new file mode 100644 index 0000000..50bceb9 --- /dev/null +++ b/src/refine.ts @@ -0,0 +1,76 @@ +import { unionToPrint } from './stringify'; +import type { Either, If, Next, Pretty, SomeSchema } from './types'; +import { left, right, nextValid, nextNotValid } from './utils/either'; + +type Refinement = ( + this: SomeSchema, + value: Input, + t: RefinementToolkit, +) => Either | Next; + +const refinementToolkit = { + right: right, + left: left, + nextValid, + nextNotValid, +} as const; +type RefinementToolkit = typeof refinementToolkit; + +export const refine = ( + refinement: Refinement, + toString?: () => string, +) => >(schema?: S) => { + type HasExitEarlyResult = unknown extends ExitEarlyResult + ? false + : ExitEarlyResult extends never + ? false + : readonly unknown[] extends ExitEarlyResult + ? false + : true; + + type HasOutput = unknown extends Output + ? false + : Output extends never + ? false + : {} extends Output + ? true + : // : readonly unknown[] extends Output + // ? false + true; + + type Result = + | If + | If< + true, + HasOutput, + // if schema is an array schema + S['__type'] extends readonly (infer TypeOfSchemaElement)[] + ? // and Output is a *tuple* schema + Output extends readonly [...infer _] + ? // replace each tuple element in Output with the type of element from schema + { readonly [Index in keyof Output]: TypeOfSchemaElement } + : Output + : Output + > + | If + | If; + + return { + ...schema, + toString() { + return unionToPrint([schema?.toString()!, toString?.()!].filter(Boolean)); + }, + __validate(val) { + // eslint-disable-next-line functional/no-this-expression + const innerResult = refinement.call(this, val as Input, refinementToolkit); + + if (innerResult?._t === 'left' || innerResult?._t === 'right') { + return innerResult; + } + if (!schema) { + return innerResult; + } + return schema.__validate(innerResult.value); + }, + } as SomeSchema>; +}; diff --git a/src/schema.ts b/src/schema.ts index 748d7df..0edb327 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -1,12 +1,5 @@ -import type { SomeSchema, DefaultModifiers } from './types'; +import type { SomeSchema } from './types'; export const isSchema = (val: any): val is SomeSchema => { - return typeof val === 'object' && val !== null && '__modifiers' in val; -}; - -export const initialModifiers: DefaultModifiers = { - optional: false, - nullable: false, - allowUnknownKeys: false, - minLength: undefined, + return typeof val === 'object' && val !== null && '__validate' in val && 'toString' in val; }; diff --git a/src/stringify.ts b/src/stringify.ts index 922ff4d..31d9665 100644 --- a/src/stringify.ts +++ b/src/stringify.ts @@ -1,15 +1,14 @@ import type { SomeSchema } from './types'; export const schemaToString = (schema: SomeSchema): string => { - const modifiers = getModifiers(schema); - return unionToPrint([schema.toString(), ...modifiers]); + return schema.toString(); }; export const typeToPrint = (str: string) => str; export const objectToPrint = (str: string) => '{' + str + '}'; export const quote = (str: string) => (/\s/.test(str) ? `"${str}"` : str); -const unionToPrint = (arr: readonly string[]): string => { +export const unionToPrint = (arr: readonly string[]): string => { const str = arr.join(' | '); if (arr.length > 1) { @@ -18,11 +17,6 @@ const unionToPrint = (arr: readonly string[]): string => { return str; }; -const getModifiers = (v: SomeSchema): readonly string[] => { - const modifiers = [v.__modifiers.optional && 'undefined', v.__modifiers.nullable && 'null']; - return modifiers.filter((m): m is string => Boolean(m)); -}; - declare global { interface Array { includes(searchElement: unknown, fromIndex?: number): searchElement is T; diff --git a/src/types.ts b/src/types.ts index 175cda1..68fb618 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,53 +1,26 @@ import type { ValidationError } from './errors'; -export type TypeOf> = Pretty | TypeOfSchema>; +export type TypeOf> = Pretty; export type Primitives = string | number | boolean; -export type Json = Primitives | { readonly [prop in string | number]: Json } | readonly Json[]; -export interface Schema< - Type extends unknown, - Modifiers extends DefaultModifiers, - Values extends DefaultValues -> { +export interface Schema { readonly __type: Type; - readonly __values: Values; - readonly __modifiers: Modifiers; /** * @internal */ - readonly __parse?: (val: unknown) => Type; - /** - * @internal - */ - readonly __validate: (val: unknown) => Either; + readonly __validate: (val: unknown) => Either | Next; toString(): string; } -export type Either = - | { readonly _t: 'left'; readonly value: L } - | { readonly _t: 'right'; readonly value: R }; - -export type DefaultModifiers = { - readonly optional: boolean | undefined; - readonly nullable: boolean | undefined; - readonly allowUnknownKeys: boolean | undefined; - readonly minLength: number | undefined; -}; +type Left = { readonly _t: 'left'; readonly value: L }; +type Right = { readonly _t: 'right'; readonly value: R }; +export type Next = + | { readonly _t: 'nextNotValid'; readonly value: Output } + | { readonly _t: 'nextValid'; readonly value: Output }; -export type MergeModifiers< - M extends DefaultModifiers, - V extends Partial -> = Pretty< - { - readonly [K in keyof DefaultModifiers]: K extends keyof V ? V[K] : M[K]; - } ->; +export type Either = Left | Right; -export type SomeSchema = Schema; - -type DefaultValues = SomeSchema | Primitives | Functor | Primitives>; - -export type Functor = Record | readonly T[]; +export type SomeSchema = Schema; export type TupleOf< T, @@ -55,13 +28,7 @@ export type TupleOf< Acc extends readonly unknown[] = readonly [] > = Acc['length'] extends Length ? Acc : TupleOf; -type TypeOfModifiers> = - | If - | If; - -type TypeOfSchema> = S['__type']; - -type If = T extends Condition ? Y : N; +export type If = T extends Condition ? Y : N; export type Pretty = X extends Date ? X @@ -71,18 +38,18 @@ export type Pretty = X extends Date } : X; -type KeysOfType = { +export type KeysOfType = { readonly [key in keyof T]: SelectedType extends T[key] ? key : never; }[keyof T]; type PlainObject = { readonly [name: string]: any }; -type Optional = Partial>>; -type Required = Omit>; +export type Optional = Partial>>; +export type Required = Omit>; export type UndefinedToOptional = T extends PlainObject ? {} extends T ? {} : T extends Date | readonly unknown[] ? T - : Required & Optional + : Pretty & Optional> : T; diff --git a/src/utils/either.ts b/src/utils/either.ts index 82a6235..61b3853 100644 --- a/src/utils/either.ts +++ b/src/utils/either.ts @@ -1,4 +1,9 @@ -import type { Either } from '../types'; +import type { Either, Next } from '../types'; -export const left = (value: L) => ({ _t: 'left', value } as Either); -export const right = (value: R) => ({ _t: 'right', value } as Either); +export const left = (value: L): Either => ({ _t: 'left', value }); +export const right = (value: R): Either => ({ _t: 'right', value }); +export const nextValid = (value: Output): Next => ({ _t: 'nextValid', value }); +export const nextNotValid = (value: Output): Next => ({ + _t: 'nextNotValid', + value, +}); diff --git a/src/validators/__validate.ts b/src/validators/__validate.ts index f54fa18..3e8ecb4 100644 --- a/src/validators/__validate.ts +++ b/src/validators/__validate.ts @@ -2,10 +2,9 @@ import { ValidationError } from '../errors'; import type { SomeSchema, TypeOf } from '../types'; export const validate = >(schema: S) => (value: unknown) => { - const parsedValue: unknown = schema.__parse ? schema.__parse(value) : value; - const result = schema.__validate(parsedValue); + const result = schema.__validate(value); - if (result._t === 'right') { + if (result._t === 'right' || result._t === 'nextValid') { return result.value as TypeOf; } else { // throw result.value; diff --git a/src/validators/array.ts b/src/validators/array.ts index 3b42f45..3ef9d97 100644 --- a/src/validators/array.ts +++ b/src/validators/array.ts @@ -1,58 +1,43 @@ /* eslint-disable functional/no-loop-statement */ import { ValidationError } from '../errors'; -import { initialModifiers } from '../schema'; +import { refine } from '../refine'; import { schemaToString, typeToPrint } from '../stringify'; -import type { Schema, SomeSchema, TypeOf } from '../types'; -import { left, right } from '../utils/either'; +import type { SomeSchema, TypeOf } from '../types'; -export const array = []>(...arr: readonly [...U]) => { +export const array = []>(...validators: readonly [...U]) => { type TypeOfResult = readonly TypeOf[]; - return { - __modifiers: initialModifiers, - __type: {} as TypeOfResult, - __values: arr, - toString: toStringArray, - __validate: validateArray, - } as Schema; -}; - -function toStringArray< - U extends readonly SomeSchema[], - TypeOfResult extends readonly TypeOf[] ->(this: Schema) { - const str = this.__values.map((s) => schemaToString(s)).join(' | '); - return this.__values.length > 1 ? typeToPrint(`(${str})[]`) : typeToPrint(`${str}[]`); -} - -function validateArray< - U extends readonly SomeSchema[], - TypeOfResult extends readonly TypeOf[] ->(this: Schema, values: readonly unknown[]) { - if (!Array.isArray(values)) { - return left(new ValidationError(this, values)); - } - const validators = this.__values; + return refine( + function (values, t) { + if (!Array.isArray(values)) { + return t.left(values); + } - let isError = false; - const result = new Array(values.length); - valuesLoop: for (let i = 0; i < values.length; ++i) { - const value = values[i]! as unknown; - for (let k = 0; k < validators.length; ++k) { - const validator = validators[k]!; - const r = validator.__validate(value); - if (r._t === 'right') { - result[i] = r.value; - continue valuesLoop; + let isError = false; + const result = new Array(values.length); + valuesLoop: for (let i = 0; i < values.length; ++i) { + const value = values[i]! as unknown; + for (let k = 0; k < validators.length; ++k) { + const validator = validators[k]!; + const r = validator.__validate(value); + if (r._t === 'right' || r._t === 'nextValid') { + result[i] = r.value; + continue valuesLoop; + } + } + result[i] = new ValidationError(this, values); + isError = true; + continue; } - } - result[i] = new ValidationError(this, values); - isError = true; - continue; - } - if (isError) { - return left(result); - } - return right(result); -} + if (isError) { + return t.left(result as TypeOfResult); + } + return t.nextValid(result as TypeOfResult); + }, + () => { + const str = validators.map((s) => schemaToString(s)).join(' | '); + return validators.length > 1 ? typeToPrint(`(${str})[]`) : typeToPrint(`${str}[]`); + }, + ); +}; diff --git a/src/validators/boolean.ts b/src/validators/boolean.ts index faf75a8..b9931cb 100644 --- a/src/validators/boolean.ts +++ b/src/validators/boolean.ts @@ -1,24 +1,12 @@ -import { ValidationError } from '../errors'; -import { initialModifiers } from '../schema'; +import { refine } from '../refine'; import { typeToPrint } from '../stringify'; -import type { Schema } from '../types'; -import { left, right } from '../utils/either'; -export const boolean = () => { - return { - __modifiers: initialModifiers, - toString: toStringBoolean, - __validate: validateBoolean, - } as Schema; -}; - -function toStringBoolean() { - return typeToPrint('boolean'); -} - -function validateBoolean(this: Schema, value: unknown) { - if (typeof value !== 'boolean') { - return left(new ValidationError(this, value)); - } - return right(value); -} +export const boolean = refine( + (value, t) => { + if (typeof value !== 'boolean') { + return t.left(value); + } + return t.nextValid(value); + }, + () => typeToPrint('boolean'), +); diff --git a/src/validators/date.ts b/src/validators/date.ts index 156a4e5..e2a5208 100644 --- a/src/validators/date.ts +++ b/src/validators/date.ts @@ -1,33 +1,21 @@ -import { ValidationError } from '../errors'; -import { initialModifiers } from '../schema'; +import { refine } from '../refine'; import { typeToPrint } from '../stringify'; -import type { Schema } from '../types'; import { isDate, isISODateString } from '../utils/dateUtils'; -import { left, right } from '../utils/either'; -export const date = () => { - return { - __modifiers: initialModifiers, - toString: toStringDate, - __parse: parseDate, - __validate: validateDate, - } as Schema; -}; +export const date = refine( + (value, t) => { + const parsedValue = parseDate(value); + if (!isDate(parsedValue) || Number.isNaN(Number(parsedValue))) { + return t.left(parsedValue); + } + return t.nextValid(parsedValue); + }, + () => typeToPrint('Date'), +); -function toStringDate() { - return typeToPrint('Date'); -} - -function parseDate(this: Schema, value: unknown) { +function parseDate(value: unknown) { if (typeof value === 'string' && isISODateString(value)) { return new Date(value); } return value; } - -function validateDate(this: Schema, value: unknown) { - if (!isDate(value) || Number.isNaN(Number(value))) { - return left(new ValidationError(this, value)); - } - return right(value); -} diff --git a/src/validators/number.ts b/src/validators/number.ts index 4c627e9..4b2b924 100644 --- a/src/validators/number.ts +++ b/src/validators/number.ts @@ -1,23 +1,18 @@ -import { ValidationError } from '../errors'; -import { initialModifiers } from '../schema'; +import { refine } from '../refine'; import { typeToPrint } from '../stringify'; -import type { Schema } from '../types'; -import { left, right } from '../utils/either'; -export const number = () => { - return { - __modifiers: initialModifiers, - toString: toStringNumber, - __parse: parseNumber, - __validate: validateNumber, - } as Schema; -}; - -function toStringNumber() { - return typeToPrint('number'); -} +export const number = refine( + (value, t) => { + const parsedValue = parseNumber(value); + if (typeof parsedValue !== 'number' || Number.isNaN(parsedValue)) { + return t.left(parsedValue); + } + return t.nextValid(parsedValue); + }, + () => typeToPrint('number'), +); -function parseNumber(this: Schema, value: unknown) { +function parseNumber(value: unknown) { if (typeof value === 'string') { if (value.trim() === '') { return value; @@ -26,10 +21,3 @@ function parseNumber(this: Schema, value } return value; } - -function validateNumber(this: Schema, value: unknown) { - if (typeof value !== 'number' || Number.isNaN(value)) { - return left(new ValidationError(this, value)); - } - return right(value); -} diff --git a/src/validators/object.ts b/src/validators/object.ts index 38995f9..8f41c54 100644 --- a/src/validators/object.ts +++ b/src/validators/object.ts @@ -1,99 +1,88 @@ /* eslint-disable functional/no-loop-statement */ import { ValidationError } from '../errors'; -import { initialModifiers } from '../schema'; +import { refine } from '../refine'; import { schemaToString, objectToPrint, quote } from '../stringify'; -import type { Schema, SomeSchema, TypeOf, UndefinedToOptional } from '../types'; -import { left, right } from '../utils/either'; +import type { SomeSchema, TypeOf, UndefinedToOptional } from '../types'; -export const object = >>(obj: U) => { +export interface ObjectSchemaOptions { + readonly allowUnknownKeys?: boolean; +} + +export const object = >>( + schemasObject: U, + options?: ObjectSchemaOptions, +) => { type TypeOfResult = UndefinedToOptional< { readonly [K in keyof U]: TypeOf; } >; - return { - __modifiers: initialModifiers, - __type: {} as TypeOfResult, - __values: obj, - toString: toStringObject, - __validate: validateObject, - } as Schema; -}; + return refine( + function (obj, t) { + if (typeof obj !== 'object' || obj === null) { + return t.left(obj); + } + const object = obj as Record; -function toStringObject< - U extends Record>, - TypeOfResult = UndefinedToOptional< - { - readonly [K in keyof U]: TypeOf; - } - > ->(this: Schema) { - const entries = Object.entries(this.__values).map( - ([key, val]) => [key, schemaToString(val)] as const, - ); - if (entries.length === 0) { - return objectToPrint(''); - } - return objectToPrint(' ' + entries.map(([key, val]) => quote(key) + ': ' + val).join(', ') + ' '); -} + const validators = schemasObject as Record>; + const allowUnknownKeys = !!options?.allowUnknownKeys; -function validateObject< - U extends Record>, - TypeOfResult extends UndefinedToOptional< - { - readonly [K in keyof U]: TypeOf; - } - > ->(this: Schema, object: Record) { - if (typeof object !== 'object' || object === null) { - return left(new ValidationError(this, object)); - } - const validators = this.__values as Record>; - const allowUnknownKeys = !!this.__modifiers.allowUnknownKeys; + let isError = false; + const result = {} as Record; + for (const key in object) { + if (!Object.prototype.hasOwnProperty.call(object, key)) { + continue; + } + const value = object[key]; - let isError = false; - const result = {} as Record; - for (const key in object) { - if (!Object.prototype.hasOwnProperty.call(object, key)) { - continue; - } - const value = object[key]; - - const validator = validators[key]; - if (validator) { - const r = validator.__validate(value); - result[key] = r.value; - isError ||= r._t === 'left'; - continue; - } else { - if (allowUnknownKeys) { - result[key] = value; - continue; - } else { - isError = true; - result[key] = new ValidationError(this, object); - continue; + const validator = validators[key]; + if (validator) { + const r = validator.__validate(value); + result[key] = r.value; + isError ||= r._t === 'left'; + continue; + } else { + if (allowUnknownKeys) { + result[key] = value; + continue; + } else { + isError = true; + result[key] = new ValidationError(this, object); + continue; + } + } } - } - } - for (const key in validators) { - if (!Object.prototype.hasOwnProperty.call(validators, key)) { - continue; - } - if (key in result) { - continue; - } - const validator = validators[key]!; - const value = object[key]; - const r = validator.__validate(value); - result[key] = r.value; - isError ||= r._t === 'left'; - } + for (const key in validators) { + if (!Object.prototype.hasOwnProperty.call(validators, key)) { + continue; + } + if (key in result) { + continue; + } + const validator = validators[key]!; + const value = object[key]; + const r = validator.__validate(value); + result[key] = r.value; + isError ||= r._t === 'left'; + } - if (isError) { - return left(result); - } - return right(result); -} + if (isError) { + return t.left(result as TypeOfResult); + } + return t.nextValid(result as TypeOfResult); + }, + () => { + const entries = Object.entries(schemasObject).map( + ([key, val]) => [key, schemaToString(val)] as const, + ); + if (entries.length === 0) { + return objectToPrint(''); + } + return objectToPrint( + ' ' + entries.map(([key, val]) => quote(key) + ': ' + val).join(', ') + ' ', + ); + }, + ); +}; diff --git a/src/validators/oneOf.ts b/src/validators/oneOf.ts index 06db2be..0dda4c3 100644 --- a/src/validators/oneOf.ts +++ b/src/validators/oneOf.ts @@ -1,60 +1,44 @@ /* eslint-disable functional/no-loop-statement */ -import { ValidationError } from '../errors'; -import { initialModifiers, isSchema } from '../schema'; +import { refine } from '../refine'; +import { isSchema } from '../schema'; import { schemaToString } from '../stringify'; -import type { Primitives, Schema, SomeSchema, TypeOf } from '../types'; -import { left, right } from '../utils/either'; +import type { Primitives, SomeSchema, TypeOf } from '../types'; // `U extends (Primitives)[]` and `[...U]` is a trick to force TypeScript to narrow the type correctly // thanks to this, there's no need for "as const": oneOf(['a', 'b']) works as oneOf(['a', 'b'] as const) export const oneOf = )[]>( - values: readonly [...U], + validatorsOrLiterals: readonly [...U], ) => { type TypeOfResult = { readonly [Index in keyof U]: U[Index] extends SomeSchema ? TypeOf : U[Index]; }[number]; - return { - __values: values, - __type: {} as unknown, - __modifiers: initialModifiers, - toString: toStringOneOf, - __validate: validateOneOf, - } as Schema; -}; - -function toStringOneOf< - U extends readonly (Primitives | SomeSchema)[], - TypeOfResult extends { - readonly [Index in keyof U]: U[Index] extends SomeSchema ? TypeOf : U[Index]; - }[number] ->(this: Schema, _value: unknown) { - const str = this.__values - .map((s) => (isSchema(s) ? schemaToString(s) : JSON.stringify(s))) - .join(' | '); - - return this.__values.length > 1 ? `(${str})` : str; -} - -function validateOneOf< - U extends readonly (Primitives | SomeSchema)[], - TypeOfResult extends { - readonly [Index in keyof U]: U[Index] extends SomeSchema ? TypeOf : U[Index]; - }[number] ->(this: Schema, value: unknown) { - for (let i = 0; i < this.__values.length; ++i) { - const valueOrSchema = this.__values[i]; - if (isSchema(valueOrSchema)) { - const r = valueOrSchema.__validate(value); - if (r._t === 'right') { - return r; - } - continue; - } else { - if (value === valueOrSchema) { - return right(valueOrSchema as TypeOfResult); + return refine( + function (value, t) { + for (let i = 0; i < validatorsOrLiterals.length; ++i) { + const valueOrSchema = validatorsOrLiterals[i]; + if (isSchema(valueOrSchema)) { + const r = valueOrSchema.__validate(value); + if (r._t === 'right') { + return t.right(r.value as TypeOfResult); + } else if (r._t === 'nextValid') { + return t.nextValid(r.value as TypeOfResult); + } + continue; + } else { + if (value === valueOrSchema) { + return t.right(valueOrSchema as TypeOfResult); + } + } } - } - } - return left(new ValidationError(this, value)); -} + return t.left(value as TypeOfResult); + }, + () => { + const str = validatorsOrLiterals + .map((s) => (isSchema(s) ? schemaToString(s) : JSON.stringify(s))) + .join(' | '); + + return validatorsOrLiterals.length > 1 ? `(${str})` : str; + }, + ); +}; diff --git a/src/validators/string.ts b/src/validators/string.ts index 70f7361..7dbf093 100644 --- a/src/validators/string.ts +++ b/src/validators/string.ts @@ -1,33 +1,21 @@ -import { ValidationError } from '../errors'; -import { initialModifiers } from '../schema'; +import { refine } from '../refine'; import { typeToPrint } from '../stringify'; -import type { Schema } from '../types'; import { isDate } from '../utils/dateUtils'; -import { left, right } from '../utils/either'; -export const string = () => { - return { - __modifiers: initialModifiers, - toString: toStringString, - __parse: parseString, - __validate: validateString, - } as Schema; -}; +export const string = refine( + (value, t) => { + const parsedValue = parseString(value); + if (typeof parsedValue !== 'string') { + return t.left(parsedValue); + } + return t.nextValid(parsedValue); + }, + () => typeToPrint('string'), +); -function toStringString() { - return typeToPrint('string'); -} - -function parseString(this: Schema, value: unknown) { +function parseString(value: unknown) { if (isDate(value)) { return value.toISOString(); } return value; } - -function validateString(this: Schema, value: unknown) { - if (typeof value !== 'string') { - return left(new ValidationError(this, value)); - } - return right(value); -} diff --git a/src/validators/tuple.ts b/src/validators/tuple.ts index 09440ba..e971614 100644 --- a/src/validators/tuple.ts +++ b/src/validators/tuple.ts @@ -1,74 +1,56 @@ /* eslint-disable functional/no-loop-statement */ import { ValidationError } from '../errors'; -import { initialModifiers, isSchema } from '../schema'; +import { refine } from '../refine'; +import { isSchema } from '../schema'; import { schemaToString } from '../stringify'; -import type { SomeSchema, TypeOf, Schema, Primitives } from '../types'; -import { left, right } from '../utils/either'; +import type { SomeSchema, TypeOf, Primitives } from '../types'; export const tuple = )[]>( - values: readonly [...U], + validatorsOrLiterals: readonly [...U], ) => { type TypeOfResult = { readonly [Index in keyof U]: U[Index] extends SomeSchema ? TypeOf : U[Index]; }; - return { - __values: values, - __type: {} as TypeOfResult, - __modifiers: initialModifiers, - toString: toStringTuple, - __validate: validateTuple, - } as Schema; -}; - -function toStringTuple< - U extends readonly (Primitives | SomeSchema)[], - TypeOfResult extends { - readonly [Index in keyof U]: U[Index] extends SomeSchema ? TypeOf : U[Index]; - } ->(this: Schema, _value: unknown) { - return ( - '[' + - this.__values.map((s) => (isSchema(s) ? schemaToString(s) : JSON.stringify(s))).join(', ') + - ']' - ); -} - -function validateTuple< - U extends readonly (Primitives | SomeSchema)[], - TypeOfResult extends { - readonly [Index in keyof U]: U[Index] extends SomeSchema ? TypeOf : U[Index]; - } ->(this: Schema, values: readonly unknown[]) { - if (!Array.isArray(values) || values.length !== this.__values.length) { - return left(new ValidationError(this, values)); - } + return refine( + function (values, t) { + if (!Array.isArray(values) || values.length !== validatorsOrLiterals.length) { + return t.left(values); + } - let isError = false; - const result = new Array(values.length); - for (let i = 0; i < values.length; ++i) { - const valueOrSchema = this.__values[i]; - const value = values[i] as unknown; + let isError = false; + const result = new Array(values.length); + for (let i = 0; i < values.length; ++i) { + const valueOrSchema = validatorsOrLiterals[i]; + const value = values[i] as unknown; - if (isSchema(valueOrSchema)) { - const r = valueOrSchema.__validate(value); - result[i] = r.value as unknown; - isError ||= r._t === 'left'; - continue; - } else { - if (valueOrSchema === value) { - result[i] = value; - continue; - } else { - result[i] = new ValidationError(this, values); - isError = true; - continue; + if (isSchema(valueOrSchema)) { + const r = valueOrSchema.__validate(value); + result[i] = r.value as unknown; + isError ||= r._t === 'left'; + continue; + } else { + if (valueOrSchema === value) { + result[i] = value; + continue; + } else { + result[i] = new ValidationError(this, validatorsOrLiterals); + isError = true; + continue; + } + } } - } - } - if (isError) { - return left(result); - } - return right(result); -} + if (isError) { + return t.left((result as unknown) as TypeOfResult); + } + return t.nextValid((result as unknown) as TypeOfResult); + }, + () => + '[' + + validatorsOrLiterals + .map((s) => (isSchema(s) ? schemaToString(s) : JSON.stringify(s))) + .join(', ') + + ']', + ); +}; diff --git a/src/validators/unknown.ts b/src/validators/unknown.ts index a57b1cc..adf5530 100644 --- a/src/validators/unknown.ts +++ b/src/validators/unknown.ts @@ -1,20 +1,9 @@ -import { initialModifiers } from '../schema'; +import { refine } from '../refine'; import { typeToPrint } from '../stringify'; -import type { Schema } from '../types'; -import { right } from '../utils/either'; -const modifiers = { ...initialModifiers, nullable: true, optional: true }; -export const unknown = () => { - return { - __modifiers: modifiers, - toString: toStringUnknown, - __validate: validateUnknown, - } as Schema; -}; - -function toStringUnknown() { - return typeToPrint('unknown'); -} -function validateUnknown(this: Schema, value: unknown) { - return right(value); -} +export const unknown = refine( + (value, t) => { + return t.nextValid(value); + }, + () => typeToPrint('unknown'), +); diff --git a/tsconfig.json b/tsconfig.json index da099ff..fdc2b1a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,7 @@ "strict": true, "alwaysStrict": true, "noUnusedLocals": true, - "noUnusedParameters": true, + "noUnusedParameters": false, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "noUncheckedIndexedAccess": true, @@ -16,7 +16,9 @@ "resolveJsonModule": true, "allowUnreachableCode": true, "stripInternal": true, - "declarationMap": true + "declaration": true, + "declarationMap": true, + "sourceMap": true }, "include": ["src", "__tests__"] } diff --git a/yarn.lock b/yarn.lock index 196a6fb..64acee4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.11": +"@babel/code-frame@7.12.11", "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== @@ -232,13 +232,20 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/runtime@^7.10.3", "@babel/runtime@^7.12.5", "@babel/runtime@^7.7.6": +"@babel/runtime@^7.12.5", "@babel/runtime@^7.7.6": version "7.12.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e" integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg== dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.13.8": + version "7.13.8" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.8.tgz#cc886a85c072df1de23670dc1aa59fc116c4017c" + integrity sha512-CwQljpw6qSayc0fRG1soxHAKs1CnQMOChm4mlQP6My0kf9upVGizj/KhlTTgyUnETmHpcUXjaluNAkteRFuafg== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.10.4", "@babel/template@^7.12.7", "@babel/template@^7.3.3": version "7.12.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.7.tgz#c817233696018e39fbb6c491d2fb684e05ed43bc" @@ -285,10 +292,10 @@ exec-sh "^0.3.2" minimist "^1.2.0" -"@eslint/eslintrc@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.3.0.tgz#d736d6963d7003b6514e6324bec9c602ac340318" - integrity sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg== +"@eslint/eslintrc@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.0.tgz#99cc0a0584d72f1df38b900fb062ba995f395547" + integrity sha512-2ZPCc+uNbjV5ERJr+aKSPRwZgKd2z11x0EgLvb1PURmUrn9QNRXFqje0Ldq454PfAVyaJYyrDvvIKSFP4NnBog== dependencies: ajv "^6.12.4" debug "^4.1.1" @@ -297,7 +304,6 @@ ignore "^4.0.6" import-fresh "^3.2.1" js-yaml "^3.13.1" - lodash "^4.17.20" minimatch "^3.0.4" strip-json-comments "^3.1.1" @@ -529,15 +535,13 @@ unique-filename "^1.1.1" which "^2.0.2" -"@npmcli/installed-package-contents@^1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@npmcli/installed-package-contents/-/installed-package-contents-1.0.5.tgz#cc78565e55d9f14d46acf46a96f70934e516fa3d" - integrity sha512-aKIwguaaqb6ViwSOFytniGvLPb9SMCUm39TgM3SfUo7n0TxUMbwoXfpwyvQ4blm10lzbAwTsvjr7QZ85LvTi4A== +"@npmcli/installed-package-contents@^1.0.6": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz#ab7408c6147911b970a8abe261ce512232a3f4fa" + integrity sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw== dependencies: npm-bundled "^1.1.1" npm-normalize-package-bin "^1.0.1" - read-package-json-fast "^1.1.1" - readdir-scoped-modules "^1.1.0" "@npmcli/move-file@^1.0.1": version "1.0.1" @@ -546,34 +550,34 @@ dependencies: mkdirp "^1.0.4" -"@npmcli/node-gyp@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@npmcli/node-gyp/-/node-gyp-1.0.1.tgz#dedc4ea9b3c6ef207081ebcd82c053ef60edc478" - integrity sha512-pBqoKPWmuk9iaEcXlLBVRIA6I1kG9JiICU+sG0NuD6NAR461F+02elHJS4WkQxHW2W5rnsfvP/ClKwmsZ9RaaA== +"@npmcli/node-gyp@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@npmcli/node-gyp/-/node-gyp-1.0.2.tgz#3cdc1f30e9736dbc417373ed803b42b1a0a29ede" + integrity sha512-yrJUe6reVMpktcvagumoqD9r08fH1iRo01gn1u0zoCApa9lnZGEigVKUd2hzsCId4gdtkZZIVscLhNxMECKgRg== -"@npmcli/promise-spawn@^1.1.0", "@npmcli/promise-spawn@^1.2.0", "@npmcli/promise-spawn@^1.3.0": +"@npmcli/promise-spawn@^1.1.0", "@npmcli/promise-spawn@^1.2.0", "@npmcli/promise-spawn@^1.3.2": version "1.3.2" resolved "https://registry.yarnpkg.com/@npmcli/promise-spawn/-/promise-spawn-1.3.2.tgz#42d4e56a8e9274fba180dabc0aea6e38f29274f5" integrity sha512-QyAGYo/Fbj4MXeGdJcFzZ+FkDkomfRBrPM+9QYJSg+PxgAUL+LU3FneQk37rKR2/zjqkCV1BLHccX98wRXG3Sg== dependencies: infer-owner "^1.0.4" -"@npmcli/run-script@^1.3.0": - version "1.8.1" - resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-1.8.1.tgz#729c5ac7293f250b654504d263952703af6da39c" - integrity sha512-G8c86g9cQHyRINosIcpovzv0BkXQc3urhL1ORf3KTe4TS4UBsg2O4Z2feca/W3pfzdHEJzc83ETBW4aKbb3SaA== +"@npmcli/run-script@^1.8.2": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-1.8.3.tgz#07f440ed492400bb1114369bc37315eeaaae2bb3" + integrity sha512-ELPGWAVU/xyU+A+H3pEPj0QOvYwLTX71RArXcClFzeiyJ/b/McsZ+d0QxpznvfFtZzxGN/gz/1cvlqICR4/suQ== dependencies: - "@npmcli/node-gyp" "^1.0.0" - "@npmcli/promise-spawn" "^1.3.0" + "@npmcli/node-gyp" "^1.0.2" + "@npmcli/promise-spawn" "^1.3.2" infer-owner "^1.0.4" node-gyp "^7.1.0" puka "^1.0.1" - read-package-json-fast "^1.1.3" + read-package-json-fast "^2.0.1" -"@rollup/plugin-commonjs@17.0.0": - version "17.0.0" - resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-17.0.0.tgz#2ae2228354cf0fbba6cf9f06f30b2c66a974324c" - integrity sha512-/omBIJG1nHQc+bgkYDuLpb/V08QyutP9amOrJRUSlYJZP+b/68gM//D8sxJe3Yry2QnYIr3QjR3x4AlxJEN3GA== +"@rollup/plugin-commonjs@17.1.0": + version "17.1.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-17.1.0.tgz#757ec88737dffa8aa913eb392fade2e45aef2a2d" + integrity sha512-PoMdXCw0ZyvjpCMT5aV4nkL0QywxP29sODQsSGeDpr/oI49Qq9tRtAsb/LbYbDzFlOydVEqHmmZWFtXJEAX9ew== dependencies: "@rollup/pluginutils" "^3.1.0" commondir "^1.0.1" @@ -583,10 +587,10 @@ magic-string "^0.25.7" resolve "^1.17.0" -"@rollup/plugin-node-resolve@11.1.0": - version "11.1.0" - resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.1.0.tgz#fa0f888297b3aebcd6534e8aba4e6fe01997649a" - integrity sha512-ouBBppRdWJKCllDXGzJ7ZIkYbaq+5TmyP0smt1vdJCFfoZhLi31vhpmjLhyo8lreHf4RoeSNllaWrvSqHpHRog== +"@rollup/plugin-node-resolve@11.2.0": + version "11.2.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.0.tgz#a5ab88c35bb7622d115f44984dee305112b6f714" + integrity sha512-qHjNIKYt5pCcn+5RUBQxK8krhRvf1HnyVgUCcFFcweDS7fhkOLZeYh0mhHK6Ery8/bb9tvN/ubPzmfF0qjDCTA== dependencies: "@rollup/pluginutils" "^3.1.0" "@types/resolve" "1.17.1" @@ -595,10 +599,10 @@ is-module "^1.0.0" resolve "^1.19.0" -"@rollup/plugin-typescript@8.1.0": - version "8.1.0" - resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-8.1.0.tgz#b7bbbbb4fd1324834f37844efd48b3844d912742" - integrity sha512-pyQlcGQYRsONUDwXK3ckGPHjPzmjlq4sinzr7emW8ZMb2oZjg9WLcdcP8wyHSvBjvHrLzMayyPy079RROqb4vw== +"@rollup/plugin-typescript@8.2.0": + version "8.2.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-8.2.0.tgz#3e2059cbcae916785d8d7bf07816210c829f817c" + integrity sha512-5DyVsb7L+ehLfNPu/nat8Gq3uJGzku4bMFPt90XahtgiSBf7z9YKPLqFUJKMT41W/mJ98SVGDPOhzikGrr/Lhg== dependencies: "@rollup/pluginutils" "^3.1.0" resolve "^1.17.0" @@ -722,10 +726,10 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@26.0.20", "@types/jest@26.x": - version "26.0.20" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.20.tgz#cd2f2702ecf69e86b586e1f5223a60e454056307" - integrity sha512-9zi2Y+5USJRxd0FsahERhBwlcvFh6D2GLQnY2FH2BzK8J9s9omvNHIbvABwIluXa0fD8XVKMLTO0aOEuUfACAA== +"@types/jest@26.0.21": + version "26.0.21" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.21.tgz#3a73c2731e7e4f0fbaea56ce7ff8c79cf812bd24" + integrity sha512-ab9TyM/69yg7eew9eOwKMUmvIZAKEGZYlq/dhe5/0IMUd/QLJv5ldRMdddSn+u22N13FP3s5jYyktxuBwY0kDA== dependencies: jest-diff "^26.0.0" pretty-format "^26.0.0" @@ -765,10 +769,10 @@ resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.1.6.tgz#f4b1efa784e8db479cdb8b14403e2144b1e9ff03" integrity sha512-6gOkRe7OIioWAXfnO/2lFiv+SJichKVSys1mSsgyrYHSEjk8Ctv4tSR/Odvnu+HWlH2C8j53dahU03XmQdd5fA== -"@types/ramda@0.27.36": - version "0.27.36" - resolved "https://registry.yarnpkg.com/@types/ramda/-/ramda-0.27.36.tgz#be90a316235835eb5fa9673cb3cd84e931da30e7" - integrity sha512-E0eLH6AdViBD1qkoTZlUpRSY0+xvJMVBqfGp7eOwXqftZ7X8gmTKnxzVDPsAuJPEeU600vD2Df3gfTZTeZ6fTA== +"@types/ramda@0.27.39": + version "0.27.39" + resolved "https://registry.yarnpkg.com/@types/ramda/-/ramda-0.27.39.tgz#7541d9d745a2003c8f635897dff8c65c12be9327" + integrity sha512-od24Hng0uS1NcMSPo6ZzKXN2WJxjbF7IBzQhRCtSDyIShdEJTAWXgQlyDg998aylNrXbVvQxkFJmuaLHQcfczg== dependencies: ts-toolbelt "^6.15.1" @@ -796,13 +800,13 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@4.14.1": - version "4.14.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.14.1.tgz#22dd301ce228aaab3416b14ead10b1db3e7d3180" - integrity sha512-5JriGbYhtqMS1kRcZTQxndz1lKMwwEXKbwZbkUZNnp6MJX0+OVXnG0kOlBZP4LUAxEyzu3cs+EXd/97MJXsGfw== +"@typescript-eslint/eslint-plugin@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.18.0.tgz#50fbce93211b5b690895d20ebec6fe8db48af1f6" + integrity sha512-Lzkc/2+7EoH7+NjIWLS2lVuKKqbEmJhtXe3rmfA8cyiKnZm3IfLf51irnBcmow8Q/AptVV0XBZmBJKuUJTe6cQ== dependencies: - "@typescript-eslint/experimental-utils" "4.14.1" - "@typescript-eslint/scope-manager" "4.14.1" + "@typescript-eslint/experimental-utils" "4.18.0" + "@typescript-eslint/scope-manager" "4.18.0" debug "^4.1.1" functional-red-black-tree "^1.0.1" lodash "^4.17.15" @@ -810,15 +814,15 @@ semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@4.14.1": - version "4.14.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.14.1.tgz#a5c945cb24dabb96747180e1cfc8487f8066f471" - integrity sha512-2CuHWOJwvpw0LofbyG5gvYjEyoJeSvVH2PnfUQSn0KQr4v8Dql2pr43ohmx4fdPQ/eVoTSFjTi/bsGEXl/zUUQ== +"@typescript-eslint/experimental-utils@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.18.0.tgz#ed6c955b940334132b17100d2917449b99a91314" + integrity sha512-92h723Kblt9JcT2RRY3QS2xefFKar4ZQFVs3GityOKWQYgtajxt/tuXIzL7sVCUlM1hgreiV5gkGYyBpdOwO6A== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/scope-manager" "4.14.1" - "@typescript-eslint/types" "4.14.1" - "@typescript-eslint/typescript-estree" "4.14.1" + "@typescript-eslint/scope-manager" "4.18.0" + "@typescript-eslint/types" "4.18.0" + "@typescript-eslint/typescript-estree" "4.18.0" eslint-scope "^5.0.0" eslint-utils "^2.0.0" @@ -834,14 +838,14 @@ eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@4.14.1": - version "4.14.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.14.1.tgz#3bd6c24710cd557d8446625284bcc9c6d52817c6" - integrity sha512-mL3+gU18g9JPsHZuKMZ8Z0Ss9YP1S5xYZ7n68Z98GnPq02pYNQuRXL85b9GYhl6jpdvUc45Km7hAl71vybjUmw== +"@typescript-eslint/parser@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.18.0.tgz#a211edb14a69fc5177054bec04c95b185b4dde21" + integrity sha512-W3z5S0ZbecwX3PhJEAnq4mnjK5JJXvXUDBYIYGoweCyWyuvAKfGHvzmpUzgB5L4cRBb+cTu9U/ro66dx7dIimA== dependencies: - "@typescript-eslint/scope-manager" "4.14.1" - "@typescript-eslint/types" "4.14.1" - "@typescript-eslint/typescript-estree" "4.14.1" + "@typescript-eslint/scope-manager" "4.18.0" + "@typescript-eslint/types" "4.18.0" + "@typescript-eslint/typescript-estree" "4.18.0" debug "^4.1.1" "@typescript-eslint/scope-manager@4.13.0": @@ -852,23 +856,23 @@ "@typescript-eslint/types" "4.13.0" "@typescript-eslint/visitor-keys" "4.13.0" -"@typescript-eslint/scope-manager@4.14.1": - version "4.14.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.14.1.tgz#8444534254c6f370e9aa974f035ced7fe713ce02" - integrity sha512-F4bjJcSqXqHnC9JGUlnqSa3fC2YH5zTtmACS1Hk+WX/nFB0guuynVK5ev35D4XZbdKjulXBAQMyRr216kmxghw== +"@typescript-eslint/scope-manager@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.18.0.tgz#d75b55234c35d2ff6ac945758d6d9e53be84a427" + integrity sha512-olX4yN6rvHR2eyFOcb6E4vmhDPsfdMyfQ3qR+oQNkAv8emKKlfxTWUXU5Mqxs2Fwe3Pf1BoPvrwZtwngxDzYzQ== dependencies: - "@typescript-eslint/types" "4.14.1" - "@typescript-eslint/visitor-keys" "4.14.1" + "@typescript-eslint/types" "4.18.0" + "@typescript-eslint/visitor-keys" "4.18.0" "@typescript-eslint/types@4.13.0": version "4.13.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.13.0.tgz#6a7c6015a59a08fbd70daa8c83dfff86250502f8" integrity sha512-/+aPaq163oX+ObOG00M0t9tKkOgdv9lq0IQv/y4SqGkAXmhFmCfgsELV7kOCTb2vVU5VOmVwXBXJTDr353C1rQ== -"@typescript-eslint/types@4.14.1": - version "4.14.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.14.1.tgz#b3d2eb91dafd0fd8b3fce7c61512ac66bd0364aa" - integrity sha512-SkhzHdI/AllAgQSxXM89XwS1Tkic7csPdndUuTKabEwRcEfR8uQ/iPA3Dgio1rqsV3jtqZhY0QQni8rLswJM2w== +"@typescript-eslint/types@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.18.0.tgz#bebe323f81f2a7e2e320fac9415e60856267584a" + integrity sha512-/BRociARpj5E+9yQ7cwCF/SNOWwXJ3qhjurMuK2hIFUbr9vTuDeu476Zpu+ptxY2kSxUHDGLLKy+qGq2sOg37A== "@typescript-eslint/typescript-estree@4.13.0": version "4.13.0" @@ -884,17 +888,16 @@ semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/typescript-estree@4.14.1": - version "4.14.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.14.1.tgz#20d3b8c8e3cdc8f764bdd5e5b0606dd83da6075b" - integrity sha512-M8+7MbzKC1PvJIA8kR2sSBnex8bsR5auatLCnVlNTJczmJgqRn8M+sAlQfkEq7M4IY3WmaNJ+LJjPVRrREVSHQ== +"@typescript-eslint/typescript-estree@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.18.0.tgz#756d3e61da8c16ab99185532c44872f4cd5538cb" + integrity sha512-wt4xvF6vvJI7epz+rEqxmoNQ4ZADArGQO9gDU+cM0U5fdVv7N+IAuVoVAoZSOZxzGHBfvE3XQMLdy+scsqFfeg== dependencies: - "@typescript-eslint/types" "4.14.1" - "@typescript-eslint/visitor-keys" "4.14.1" + "@typescript-eslint/types" "4.18.0" + "@typescript-eslint/visitor-keys" "4.18.0" debug "^4.1.1" globby "^11.0.1" is-glob "^4.0.1" - lodash "^4.17.15" semver "^7.3.2" tsutils "^3.17.1" @@ -906,12 +909,12 @@ "@typescript-eslint/types" "4.13.0" eslint-visitor-keys "^2.0.0" -"@typescript-eslint/visitor-keys@4.14.1": - version "4.14.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.14.1.tgz#e93c2ff27f47ee477a929b970ca89d60a117da91" - integrity sha512-TAblbDXOI7bd0C/9PE1G+AFo7R5uc+ty1ArDoxmrC1ah61Hn6shURKy7gLdRb1qKJmjHkqu5Oq+e4Kt0jwf1IA== +"@typescript-eslint/visitor-keys@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.18.0.tgz#4e6fe2a175ee33418318a029610845a81e2ff7b6" + integrity sha512-Q9t90JCvfYaN0OfFUgaLqByOfz8yPeTAdotn/XYNm5q9eHax90gzdb+RJ6E9T5s97Kv/UHWKERTmqA0jTKAEHw== dependencies: - "@typescript-eslint/types" "4.14.1" + "@typescript-eslint/types" "4.18.0" eslint-visitor-keys "^2.0.0" abab@^2.0.3: @@ -991,10 +994,10 @@ ajv@^7.0.2: require-from-string "^2.0.2" uri-js "^4.2.2" -all-contributors-cli@6.19.0: - version "6.19.0" - resolved "https://registry.yarnpkg.com/all-contributors-cli/-/all-contributors-cli-6.19.0.tgz#7e4550973afede2476b62bd159fee6d72a1ad802" - integrity sha512-QJN4iLeTeYpTZJES8XFTzQ+itA1qSyBbxLapJLtwrnY+kipyRhCX49fS/s/qftQQym9XLATMZUpUeEeJSox1sw== +all-contributors-cli@6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/all-contributors-cli/-/all-contributors-cli-6.20.0.tgz#9bc98dda38cb29cfe8afc8a78c004e14af25d2f6" + integrity sha512-trEQlL1s1u8FSWSwY2w9uL4GCG7Fo9HIW5rm5LtlE0SQHSolfXQBzJib07Qes5j52/t72wjuE6sEKkuRrwiuuQ== dependencies: "@babel/runtime" "^7.7.6" async "^3.0.1" @@ -1161,11 +1164,6 @@ arrify@^1.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= -asap@^2.0.0: - version "2.0.6" - resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" - integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= - asn1@~0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" @@ -1313,6 +1311,20 @@ boxen@^4.2.0: type-fest "^0.8.1" widest-line "^3.1.0" +boxen@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.0.0.tgz#64fe9b16066af815f51057adcc800c3730120854" + integrity sha512-5bvsqw+hhgUi3oYGK0Vf4WpIkyemp60WBInn7+WNfoISzAqk/HX4L7WNROq38E6UR/y3YADpv6pEm4BfkeEAdA== + dependencies: + ansi-align "^3.0.0" + camelcase "^6.2.0" + chalk "^4.1.0" + cli-boxes "^2.2.1" + string-width "^4.2.0" + type-fest "^0.20.2" + widest-line "^3.1.0" + wrap-ansi "^7.0.0" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -1463,7 +1475,7 @@ camelcase@^5.0.0, camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.0.0: +camelcase@^6.0.0, camelcase@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== @@ -1545,7 +1557,7 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -cli-boxes@^2.2.0: +cli-boxes@^2.2.0, cli-boxes@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== @@ -1802,11 +1814,6 @@ debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: dependencies: ms "2.0.0" -debuglog@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" - integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= - decamelize-keys@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" @@ -1911,14 +1918,6 @@ detect-newline@^3.0.0: resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== -dezalgo@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" - integrity sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY= - dependencies: - asap "^2.0.0" - wrappy "1" - didyoumean@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.1.tgz#e92edfdada6537d484d73c0172fd1eba0c4976ff" @@ -1980,7 +1979,7 @@ duplexer@0.1.1: resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E= -duplexer@^0.1.1: +duplexer@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== @@ -2039,6 +2038,11 @@ err-code@^1.0.0: resolved "https://registry.yarnpkg.com/err-code/-/err-code-1.1.2.tgz#06e0116d3028f6aef4806849eb0ea6a748ae6960" integrity sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA= +err-code@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" + integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== + error-ex@^1.2.0, error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -2105,10 +2109,10 @@ escodegen@^1.14.1: optionalDependencies: source-map "~0.6.1" -eslint-config-prettier@7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-7.2.0.tgz#f4a4bd2832e810e8cc7c1411ec85b3e85c0c53f9" - integrity sha512-rV4Qu0C3nfJKPOAhFujFxB7RMP+URFyQqqOZW9DMRD7ZDTFyjaIlETU3xzHELt++4ugC0+Jm084HQYkkJe+Ivg== +eslint-config-prettier@8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.1.0.tgz#4ef1eaf97afe5176e6a75ddfb57c335121abc5a6" + integrity sha512-oKMhGv3ihGbCIimCAjqkdzx2Q+jthoqnXSP+d86M9tptwugycmTFdVR4IpLgq2c4SHifbwO90z2fQ8/Aio73yw== eslint-formatter-pretty@^4.0.0: version "4.0.0" @@ -2199,13 +2203,13 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== -eslint@7.18.0: - version "7.18.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.18.0.tgz#7fdcd2f3715a41fe6295a16234bd69aed2c75e67" - integrity sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ== +eslint@7.22.0: + version "7.22.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.22.0.tgz#07ecc61052fec63661a2cab6bd507127c07adc6f" + integrity sha512-3VawOtjSJUQiiqac8MQc+w457iGLfuNGLFn8JmF051tTKbh5/x/0vlcEj8OgDCaw7Ysa2Jn8paGshV7x2abKXg== dependencies: - "@babel/code-frame" "^7.0.0" - "@eslint/eslintrc" "^0.3.0" + "@babel/code-frame" "7.12.11" + "@eslint/eslintrc" "^0.4.0" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" @@ -2216,12 +2220,12 @@ eslint@7.18.0: eslint-utils "^2.1.0" eslint-visitor-keys "^2.0.0" espree "^7.3.1" - esquery "^1.2.0" + esquery "^1.4.0" esutils "^2.0.2" - file-entry-cache "^6.0.0" + file-entry-cache "^6.0.1" functional-red-black-tree "^1.0.1" glob-parent "^5.0.0" - globals "^12.1.0" + globals "^13.6.0" ignore "^4.0.6" import-fresh "^3.0.0" imurmurhash "^0.1.4" @@ -2229,7 +2233,7 @@ eslint@7.18.0: js-yaml "^3.13.1" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" - lodash "^4.17.20" + lodash "^4.17.21" minimatch "^3.0.4" natural-compare "^1.4.0" optionator "^0.9.1" @@ -2256,10 +2260,10 @@ esprima@^4.0.0, esprima@^4.0.1: resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" - integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== dependencies: estraverse "^5.1.0" @@ -2411,10 +2415,10 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -fast-check@2.12.0: - version "2.12.0" - resolved "https://registry.yarnpkg.com/fast-check/-/fast-check-2.12.0.tgz#533b969f0e4546bbefb99cbff32a5d14981a227a" - integrity sha512-6wZK9r2zymqtj/7k9LB4TTJGiyoGtN7NamXAKOWa4svZlFOdE3p7kbC46Au474E6R6votoQrM5aAjRnYnx0BNg== +fast-check@2.14.0: + version "2.14.0" + resolved "https://registry.yarnpkg.com/fast-check/-/fast-check-2.14.0.tgz#13e891977a7cc1ba87aa3883c75053990c02fb21" + integrity sha512-4hm0ioyEVCwgjBLBqriwAFYu3/yc7pNH9fBfvzi6JRWRs4R3mwZwrr/d4MHbY0fTBJ0Uakg4TaMAAQ8Cnt2+KQ== dependencies: pure-rand "^4.1.1" @@ -2466,10 +2470,10 @@ figures@^3.0.0, figures@^3.2.0: dependencies: escape-string-regexp "^1.0.5" -file-entry-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.0.tgz#7921a89c391c6d93efec2169ac6bf300c527ea0a" - integrity sha512-fqoO76jZ3ZnYrXLDRxBR1YvOvc0k844kcOg40bgsPrE25LAb/PDqTY+ho64Xh2c8ZXgIKldchCFHczG2UVRcWA== +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== dependencies: flat-cache "^3.0.4" @@ -2581,10 +2585,10 @@ fsevents@^2.1.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.1.tgz#b209ab14c61012636c8863507edf7fb68cc54e9f" integrity sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw== -fsevents@~2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" - integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== +fsevents@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== function-bind@^1.1.1: version "1.1.1" @@ -2715,6 +2719,13 @@ globals@^12.1.0: dependencies: type-fest "^0.8.1" +globals@^13.6.0: + version "13.7.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.7.0.tgz#aed3bcefd80ad3ec0f0be2cf0c895110c0591795" + integrity sha512-Aipsz6ZKRxa/xQkZhNg0qIWXT6x6rD46f6x/PCnBomlttdIyAPak4YD9jTmKpZ72uROSMU87qJtcgpgHaVchiA== + dependencies: + type-fest "^0.20.2" + globby@^11.0.1: version "11.0.2" resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.2.tgz#1af538b766a3b540ebfb58a32b2e2d5897321d83" @@ -2754,13 +2765,12 @@ growly@^1.3.0: resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= -gzip-size@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" - integrity sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA== +gzip-size@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" + integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q== dependencies: - duplexer "^0.1.1" - pify "^4.0.1" + duplexer "^0.1.2" har-schema@^2.0.0: version "2.0.0" @@ -3954,10 +3964,10 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= -lint-staged@10.5.3: - version "10.5.3" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.5.3.tgz#c682838b3eadd4c864d1022da05daa0912fb1da5" - integrity sha512-TanwFfuqUBLufxCc3RUtFEkFraSPNR3WzWcGF39R3f2J7S9+iF9W0KTVLfSy09lYGmZS5NDCxjNvhGMSJyFCWg== +lint-staged@10.5.4: + version "10.5.4" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.5.4.tgz#cd153b5f0987d2371fc1d2847a409a2fe705b665" + integrity sha512-EechC3DdFic/TdOPgj/RB3FicqE6932LTHCUm0Y2fsD9KGlLB+RwJl2q1IYBIvEsKzDOgn0D4gll+YxG5RsrKg== dependencies: chalk "^4.1.0" cli-truncate "^2.1.0" @@ -4036,11 +4046,6 @@ lodash.isnil@4.0.0: resolved "https://registry.yarnpkg.com/lodash.isnil/-/lodash.isnil-4.0.0.tgz#49e28cd559013458c814c5479d3c663a21bfaa6c" integrity sha1-SeKM1VkBNFjIFMVHnTxmOiG/qmw= -lodash.memoize@4.x: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= - lodash.omitby@4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.omitby/-/lodash.omitby-4.6.0.tgz#5c15ff4754ad555016b53c041311e8f079204791" @@ -4051,10 +4056,10 @@ lodash.sortby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= -lodash@4.17.19: - version "4.17.19" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" - integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== +lodash@4.17.21, lodash@4.x, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== lodash@^4.11.2, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20: version "4.17.20" @@ -4341,10 +4346,10 @@ mkdirp@1.0.4, mkdirp@1.x, mkdirp@^1.0.3, mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -moment@2.27.0: - version "2.27.0" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.27.0.tgz#8bff4e3e26a236220dfe3e36de756b6ebaa0105d" - integrity sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ== +moment@2.29.1: + version "2.29.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" + integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== ms@2.0.0: version "2.0.0" @@ -4780,20 +4785,20 @@ package-json@^6.3.0: registry-url "^5.0.0" semver "^6.2.0" -package-name-regex@1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/package-name-regex/-/package-name-regex-1.0.8.tgz#4b8388cace6cc415f462ab4a269583d9a2d9f131" - integrity sha512-g3vB2J62dLqf4m50VM4tJUC4sixw3JB+Igd0cF3P/gJhAvmvsmFEV2eWZTeLbwfkKEWTf3+gwQ2C6JFFRxWHEQ== +package-name-regex@1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/package-name-regex/-/package-name-regex-1.0.9.tgz#d7d33ff3f2ef43a28fbb02aa4fcf48fc1ab26588" + integrity sha512-+U2oQCfEz2IlGqws8gmfKzdMDbSd6+RZp6UIFdKo+GAw3+o+kfnsgXkWtJ1JMoKhpP2kEvuYyTy1lXOEQEe0ZA== -pacote@^11.1.10: - version "11.2.0" - resolved "https://registry.yarnpkg.com/pacote/-/pacote-11.2.0.tgz#c95be8ef4db76a5436738f9ad493d0669dfaf9df" - integrity sha512-O5jAQu7bP8AxMiJXMQuePaN8ue8Ngj4UVtwU29HlnRF6EYa5+HPZtGaLGvEk/Y5uNcqbT0AjwnPoaZS33FvY6A== +pacote@^11.2.7: + version "11.2.7" + resolved "https://registry.yarnpkg.com/pacote/-/pacote-11.2.7.tgz#a44a9d40d4feac524e9ce78d77e2b390181d8afc" + integrity sha512-ogxPor11v/rnU9ukwLlI2dPx22q9iob1+yZyqSwerKsOvBMhU9e+SJHtxY4y2N0MRH4/5jGsGiRLsZeJWyM4dQ== dependencies: "@npmcli/git" "^2.0.1" - "@npmcli/installed-package-contents" "^1.0.5" + "@npmcli/installed-package-contents" "^1.0.6" "@npmcli/promise-spawn" "^1.2.0" - "@npmcli/run-script" "^1.3.0" + "@npmcli/run-script" "^1.8.2" cacache "^15.0.5" chownr "^2.0.0" fs-minipass "^2.1.0" @@ -4804,10 +4809,10 @@ pacote@^11.1.10: npm-packlist "^2.1.4" npm-pick-manifest "^6.0.0" npm-registry-fetch "^9.0.0" - promise-retry "^1.1.1" - read-package-json-fast "^1.1.3" + promise-retry "^2.0.1" + read-package-json-fast "^2.0.1" rimraf "^3.0.2" - ssri "^8.0.0" + ssri "^8.0.1" tar "^6.1.0" parent-module@^1.0.0: @@ -4906,11 +4911,6 @@ pify@^2.0.0: resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" - integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== - pify@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f" @@ -5016,6 +5016,14 @@ promise-retry@^1.1.1: err-code "^1.0.0" retry "^0.10.0" +promise-retry@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" + integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== + dependencies: + err-code "^2.0.2" + retry "^0.12.0" + prompts@^2.0.1: version "2.4.0" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.0.tgz#4aa5de0723a231d1ee9121c40fdf663df73f61d7" @@ -5096,10 +5104,10 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== -read-package-json-fast@^1.1.1, read-package-json-fast@^1.1.3: - version "1.2.1" - resolved "https://registry.yarnpkg.com/read-package-json-fast/-/read-package-json-fast-1.2.1.tgz#e8518d6f37c99eb3afc26704c5cbb50d7ead82dd" - integrity sha512-OFbpwnHcv74Oa5YN5WvbOBfLw6yPmPcwvyJJw/tj9cWFBF7juQUDLDSZiOjEcgzfweWeeROOmbPpNN1qm4hcRg== +read-package-json-fast@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/read-package-json-fast/-/read-package-json-fast-2.0.2.tgz#2dcb24d9e8dd50fb322042c8c35a954e6cc7ac9e" + integrity sha512-5fyFUyO9B799foVk4n6ylcoAktG/FbE3jwRKxvwaeSrIunaoMc0u81dzXxjeAFKOce7O5KncdfwpGvvs6r5PsQ== dependencies: json-parse-even-better-errors "^2.3.0" npm-normalize-package-bin "^1.0.1" @@ -5153,16 +5161,6 @@ readable-stream@^2.0.6: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readdir-scoped-modules@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309" - integrity sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw== - dependencies: - debuglog "^1.0.1" - dezalgo "^1.0.0" - graceful-fs "^4.1.2" - once "^1.3.0" - redent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" @@ -5330,6 +5328,11 @@ retry@^0.10.0: resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q= +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= + reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -5342,32 +5345,32 @@ rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" -rollup-plugin-filesize@9.1.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/rollup-plugin-filesize/-/rollup-plugin-filesize-9.1.0.tgz#e6032a2975d1c0c8f2c30ecaa73a12bf39406461" - integrity sha512-3umx+e2AzYH4lJaBtyeWk2kC7JafzZhy5AJwj2amudWWgqIFpI/QH7s2+9LWBwIvjK5ty6K6eM3i6dw7qF4GrQ== +rollup-plugin-filesize@9.1.1: + version "9.1.1" + resolved "https://registry.yarnpkg.com/rollup-plugin-filesize/-/rollup-plugin-filesize-9.1.1.tgz#31a6b02b27ce08082ef0970cfe4c451714ff91c4" + integrity sha512-x0r2A85TCEdRwF3rm+bcN4eAmbER8tt+YVf88gBQ6sLyH4oGcnNLPQqAUX+v7mIvHC/y59QwZvo6vxaC2ias6Q== dependencies: - "@babel/runtime" "^7.10.3" - boxen "^4.2.0" + "@babel/runtime" "^7.13.8" + boxen "^5.0.0" brotli-size "4.0.0" colors "^1.4.0" filesize "^6.1.0" - gzip-size "^5.1.1" - pacote "^11.1.10" - terser "^5.5.1" + gzip-size "^6.0.0" + pacote "^11.2.7" + terser "^5.6.0" -rollup-plugin-license@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/rollup-plugin-license/-/rollup-plugin-license-2.2.0.tgz#0d19139bbe44dda500fbf15530af07c91949e348" - integrity sha512-xXb1vviEwlJMX+VGUSsglcMA/Rh9d2QzEm94awy4FlnsPqGrXoTYYGOR3UXR6gYIxiJFkr7qmkKF/NXfre/y8g== +rollup-plugin-license@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-license/-/rollup-plugin-license-2.3.0.tgz#fc8711c5e0f5850bda2908751c718f45b0315bee" + integrity sha512-oi8pL59fVTwXCkLUsZ8dVGVJjO7Hcc5UT0chJvKd0MktPgeYHSadkaicAYUemdYHHpjb0D3DyvedZAEPt+2r8w== dependencies: commenting "1.1.0" glob "7.1.6" - lodash "4.17.19" + lodash "4.17.21" magic-string "0.25.7" mkdirp "1.0.4" - moment "2.27.0" - package-name-regex "1.0.8" + moment "2.29.1" + package-name-regex "1.0.9" spdx-expression-validate "2.0.0" spdx-satisfies "5.0.0" @@ -5393,12 +5396,12 @@ rollup-plugin-terser@7.0.2: serialize-javascript "^4.0.0" terser "^5.0.0" -rollup@2.38.0: - version "2.38.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.38.0.tgz#57942d5a10826cb12ed1f19c261f774efa502d2d" - integrity sha512-ay9zDiNitZK/LNE/EM2+v5CZ7drkB2xyDljvb1fQJCGnq43ZWRkhxN145oV8GmoW1YNi4sA/1Jdkr2LfawJoXw== +rollup@2.42.2: + version "2.42.2" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.42.2.tgz#4ea90d60539f47c5739df6ccce60bf47e2011992" + integrity sha512-o34Ar4rf01ky4EV1RFlTPd+tXICDz13a2o1PARLPFBxighJoPsxvliJTrULqjmIVpZP+JPm499ZPkvnPzRxUYA== optionalDependencies: - fsevents "~2.1.2" + fsevents "~2.3.1" rsvp@^4.8.4: version "4.8.5" @@ -5778,6 +5781,13 @@ ssri@^8.0.0: dependencies: minipass "^3.1.1" +ssri@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== + dependencies: + minipass "^3.1.1" + stack-utils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz#cd5f030126ff116b78ccb3c027fe302713b61277" @@ -6005,7 +6015,7 @@ terminal-link@^2.0.0: ansi-escapes "^4.2.1" supports-hyperlinks "^2.0.0" -terser@^5.0.0, terser@^5.5.1: +terser@^5.0.0: version "5.5.1" resolved "https://registry.yarnpkg.com/terser/-/terser-5.5.1.tgz#540caa25139d6f496fdea056e414284886fb2289" integrity sha512-6VGWZNVP2KTUcltUQJ25TtNjx/XgdDsBDKGt8nN0MpydU36LmbPPcMBd2kmtZNNGVVDLg44k7GKeHHj+4zPIBQ== @@ -6014,6 +6024,15 @@ terser@^5.0.0, terser@^5.5.1: source-map "~0.7.2" source-map-support "~0.5.19" +terser@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.6.0.tgz#138cdf21c5e3100b1b3ddfddf720962f88badcd2" + integrity sha512-vyqLMoqadC1uR0vywqOZzriDYzgEkNJFK4q9GeyOBHIbiECHiWLKcWfbQWAUaPfxkjDhapSlZB9f7fkMrvkVjA== + dependencies: + commander "^2.20.0" + source-map "~0.7.2" + source-map-support "~0.5.19" + test-exclude@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" @@ -6121,18 +6140,17 @@ trim-newlines@^3.0.0: resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.0.tgz#79726304a6a898aa8373427298d54c2ee8b1cb30" integrity sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA== -ts-jest@26.4.4: - version "26.4.4" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.4.4.tgz#61f13fb21ab400853c532270e52cc0ed7e502c49" - integrity sha512-3lFWKbLxJm34QxyVNNCgXX1u4o/RV0myvA2y2Bxm46iGIjKlaY0own9gIckbjZJPn+WaJEnfPPJ20HHGpoq4yg== +ts-jest@26.5.4: + version "26.5.4" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.5.4.tgz#207f4c114812a9c6d5746dd4d1cdf899eafc9686" + integrity sha512-I5Qsddo+VTm94SukBJ4cPimOoFZsYTeElR2xy6H2TOVs+NsvgYglW8KuQgKoApOKuaU/Ix/vrF9ebFZlb5D2Pg== dependencies: - "@types/jest" "26.x" bs-logger "0.x" buffer-from "1.x" fast-json-stable-stringify "2.x" jest-util "^26.1.0" json5 "2.x" - lodash.memoize "4.x" + lodash "4.x" make-error "1.x" mkdirp "1.x" semver "7.x" @@ -6222,6 +6240,11 @@ type-fest@^0.13.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + type-fest@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" @@ -6239,10 +6262,10 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7" - integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== +typescript@4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3" + integrity sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw== union-value@^1.0.0: version "1.0.1" @@ -6492,6 +6515,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"