From ad7af34eb811541253150b7ff0b58a6bd7200796 Mon Sep 17 00:00:00 2001 From: Parbez Date: Thu, 7 Jul 2022 10:41:12 +0530 Subject: [PATCH] feat(arrays): add unique (#141) --- .all-contributorsrc | 154 ++++++++---------- .github/workflows/continuous-integration.yml | 27 +++ README.md | 32 ++-- package.json | 3 + scripts/no-bundle-test.mjs | 16 ++ ...ngthConstraints.ts => ArrayConstraints.ts} | 31 ++-- src/constraints/type-exports.ts | 2 +- src/constraints/util/isUnique.ts | 8 + src/validators/ArrayValidator.ts | 9 +- tests/lib/configs.test.ts | 8 +- tests/validators/array.test.ts | 33 +++- tests/validators/date.test.ts | 20 +-- tsup.config.ts | 3 +- yarn.lock | 26 +++ 14 files changed, 242 insertions(+), 130 deletions(-) create mode 100644 scripts/no-bundle-test.mjs rename src/constraints/{ArrayLengthConstraints.ts => ArrayConstraints.ts} (85%) create mode 100644 src/constraints/util/isUnique.ts diff --git a/.all-contributorsrc b/.all-contributorsrc index 6698bf7b..d50e185e 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1,88 +1,70 @@ { - "projectName": "shapeshift", - "projectOwner": "sapphiredev", - "repoType": "github", - "repoHost": "https://github.com", - "files": [ - "README.md" - ], - "imageSize": 100, - "commit": true, - "commitConvention": "angular", - "contributors": [ - { - "login": "kyranet", - "name": "Antonio Román", - "avatar_url": "https://avatars.githubusercontent.com/u/24852502?v=4", - "profile": "https://github.com/kyranet", - "contributions": [ - "code", - "doc", - "ideas" - ] - }, - { - "login": "vladfrangu", - "name": "Vlad Frangu", - "avatar_url": "https://avatars.githubusercontent.com/u/17960496?v=4", - "profile": "https://github.com/vladfrangu", - "contributions": [ - "code", - "doc", - "ideas" - ] - }, - { - "login": "favna", - "name": "Jeroen Claassens", - "avatar_url": "https://avatars.githubusercontent.com/u/4019718?v=4", - "profile": "https://favware.tech/", - "contributions": [ - "doc", - "maintenance", - "infra" - ] - }, - { - "login": "renovate[bot]", - "name": "renovate[bot]", - "avatar_url": "https://avatars.githubusercontent.com/in/2740?v=4", - "profile": "https://github.com/apps/renovate", - "contributions": [ - "maintenance" - ] - }, - { - "login": "renovate-bot", - "name": "WhiteSource Renovate", - "avatar_url": "https://avatars.githubusercontent.com/u/25180681?v=4", - "profile": "https://renovate.whitesourcesoftware.com/", - "contributions": [ - "maintenance" - ] - }, - { - "login": "Khasms", - "name": "John", - "avatar_url": "https://avatars.githubusercontent.com/u/36800359?v=4", - "profile": "https://github.com/Khasms", - "contributions": [ - "code" - ] - }, - { - "login": "imranbarbhuiya", - "name": "Parbez", - "avatar_url": "https://avatars.githubusercontent.com/u/74945038?v=4", - "profile": "https://github.com/imranbarbhuiya", - "contributions": [ - "code", - "test", - "bug", - "doc" - ] - } - ], - "contributorsPerLine": 7, - "skipCi": true + "projectName": "shapeshift", + "projectOwner": "sapphiredev", + "repoType": "github", + "repoHost": "https://github.com", + "files": ["README.md"], + "imageSize": 100, + "commit": true, + "commitConvention": "angular", + "contributors": [ + { + "login": "kyranet", + "name": "Antonio Román", + "avatar_url": "https://avatars.githubusercontent.com/u/24852502?v=4", + "profile": "https://github.com/kyranet", + "contributions": ["code", "doc", "ideas"] + }, + { + "login": "vladfrangu", + "name": "Vlad Frangu", + "avatar_url": "https://avatars.githubusercontent.com/u/17960496?v=4", + "profile": "https://github.com/vladfrangu", + "contributions": ["code", "doc", "ideas"] + }, + { + "login": "favna", + "name": "Jeroen Claassens", + "avatar_url": "https://avatars.githubusercontent.com/u/4019718?v=4", + "profile": "https://favware.tech/", + "contributions": ["doc", "maintenance", "infra"] + }, + { + "login": "renovate[bot]", + "name": "renovate[bot]", + "avatar_url": "https://avatars.githubusercontent.com/in/2740?v=4", + "profile": "https://github.com/apps/renovate", + "contributions": ["maintenance"] + }, + { + "login": "renovate-bot", + "name": "WhiteSource Renovate", + "avatar_url": "https://avatars.githubusercontent.com/u/25180681?v=4", + "profile": "https://renovate.whitesourcesoftware.com/", + "contributions": ["maintenance"] + }, + { + "login": "Khasms", + "name": "John", + "avatar_url": "https://avatars.githubusercontent.com/u/36800359?v=4", + "profile": "https://github.com/Khasms", + "contributions": ["code"] + }, + { + "login": "imranbarbhuiya", + "name": "Parbez", + "avatar_url": "https://avatars.githubusercontent.com/u/74945038?v=4", + "profile": "https://github.com/imranbarbhuiya", + "contributions": ["code", "test", "bug", "doc"] + }, + { + "login": "allcontributors[bot]", + "name": "allcontributors[bot]", + "avatar_url": "https://avatars.githubusercontent.com/in/23186?v=4", + "profile": "https://github.com/apps/allcontributors", + "contributions": ["doc"] + } + ], + "contributorsPerLine": 7, + "skipCi": true } diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 02a279f7..e4bcecf7 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -71,6 +71,33 @@ jobs: run: yarn --immutable - name: Typecheck And Build Code run: yarn typecheck && yarn build + - name: Upload bundle to artifacts + uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3 + with: + name: bundle + path: dist/ + if-no-files-found: error + + NoModulesTesting: + name: No Modules Testing + runs-on: ubuntu-latest + needs: Building + steps: + - name: Checkout Project + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3 + - name: Download generated typings artifact + uses: actions/download-artifact@fb598a63ae348fa914e94cd0ff38f362e927b741 # tag=v3 + with: + name: bundle + path: dist/ + - name: Use Node.js v18 + uses: actions/setup-node@eeb10cff27034e7acf239c5d29f62154018672fd # tag=v3 + with: + node-version: 18 + cache: yarn + registry-url: https://registry.npmjs.org/ + - name: Run test script + run: node scripts/no-bundle-test.mjs Upload_Coverage_Report: name: Upload coverage report to codecov diff --git a/README.md b/README.md index 3fa8ada8..4db3653d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ # @sapphire/shapeshift -**ShapeShift** +**Shapeshift** Blazing fast input validation and transformation ⚡ @@ -54,7 +54,7 @@ Blazing fast input validation and transformation ⚡ - [BaseValidator: methods and properties](#basevalidator-methods-and-properties) - [Enabling and disabling validation](#enabling-and-disabling-validation) - [Buy us some doughnuts](#buy-us-some-doughnuts) - - [Contributors ✨](#contributors-%E2%9C%A8) + - [Contributors](#contributors) ## Description @@ -62,7 +62,7 @@ Blazing fast input validation and transformation ⚡ A very fast and lightweight input validation and transformation library for JavaScript. -> **Note**: ShapeShift requires Node.js v14.0.0 or higher to work. +> **Note**: Shapeshift requires Node.js v14.0.0 or higher to work. ## Features @@ -154,7 +154,7 @@ s.literal(new Date(1639278160000)); // s.date.equal(1639278160000); [Back to top][toc] -ShapeShift includes a handful of string-specific validations: +Shapeshift includes a handful of string-specific validations: ```typescript s.string.lengthLessThan(5); @@ -176,7 +176,7 @@ s.string.ipv6; [Back to top][toc] -ShapeShift includes a handful of number-specific validations: +Shapeshift includes a handful of number-specific validations: ```typescript s.number.greaterThan(5); // > 5 @@ -216,7 +216,7 @@ s.number.ceil; // Transforms the number to the result of `Math.ceil` [Back to top][toc] -ShapeShift includes a handful of number-specific validations: +Shapeshift includes a handful of number-specific validations: ```typescript s.bigint.greaterThan(5n); // > 5n @@ -245,7 +245,7 @@ s.bigint.uintN(5); // Clamps to a bigint to an unsigned bigint with 5 digits, se [Back to top][toc] -ShapeShift includes a few boolean-specific validations: +Shapeshift includes a few boolean-specific validations: ```typescript s.boolean.true; // value must be true @@ -267,7 +267,7 @@ const stringArray = s.array(s.string); const stringArray = s.string.array; ``` -ShapeShift includes a handful of array-specific validations: +Shapeshift includes a handful of array-specific validations: ```typescript s.string.array.lengthLessThan(5); // Must have less than 5 elements @@ -279,6 +279,7 @@ s.string.array.lengthNotEqual(5); // Must not have exactly 5 elements s.string.array.lengthRange(0, 4); // Must have at least 0 elements and less than 4 elements (in math, that is [0, 4)) s.string.array.lengthRangeInclusive(0, 4); // Must have at least 0 elements and at most 4 elements (in math, that is [0, 4]) s.string.array.lengthRangeExclusive(0, 4); // Must have more than 0 element and less than 4 elements (in math, that is (0, 4)) +s.string.array.unique; // All elements must be unique. Deep equality is used to check for uniqueness. ``` > **Note**: All `.length` methods define tuple types with the given amount of elements. For example, `s.string.array.lengthGreaterThanOrEqual(2)`'s inferred type is `[string, string, ...string[]]` @@ -303,7 +304,7 @@ dish.parse(['Iberian ham', 10, new Date()]); [Back to top][toc] -ShapeShift includes a built-in method for composing OR types: +Shapeshift includes a built-in method for composing OR types: ```typescript const stringOrNumber = s.union(s.string, s.number); @@ -393,7 +394,7 @@ s.function([s.string]); // (arg0: string) => unknown s.function([s.string, s.number], s.string); // (arg0: string, arg1: number) => string ``` -> **Note**: ShapeShift will transform the given function into one with validation on arguments and output. You can access the `.raw` property of the function to get the unchecked function. +> **Note**: Shapeshift will transform the given function into one with validation on arguments and output. You can access the `.raw` property of the function to get the unchecked function. --- @@ -416,7 +417,7 @@ const bigInt64Array = s.bigInt64Array; const bigUint64Array = s.bigUint64Array; ``` -ShapeShift includes a handful of validations specific to typed arrays. +Shapeshift includes a handful of validations specific to typed arrays. ```typescript s.typedArray().lengthLessThan(5); // Length must be less than 5 @@ -633,7 +634,7 @@ const user = s.object({ [Back to top][toc] -By default, ShapeShift will not include keys that are not defined by the schema during parsing: +By default, Shapeshift will not include keys that are not defined by the schema during parsing: ```typescript const person = s.object({ @@ -683,7 +684,7 @@ You can use the `.passthrough` getter to make the validator add the unrecognized [Back to top][toc] -All validations in ShapeShift contain certain methods. +All validations in Shapeshift contain certain methods. `.run(data: unknown): Result`: given a validation, you can call this method to check whether or not the input is valid. If it is, a `Result` with `success: true` and a deep-cloned value will be returned with the given constraints and transformations. Otherwise, a `Result` with `success: false` and an error is returned. @@ -803,7 +804,7 @@ We accept donations through Open Collective, Ko-fi, Paypal, Patreon and GitHub S | Patreon | [Click Here](https://sapphirejs.dev/patreon) | | PayPal | [Click Here](https://sapphirejs.dev/paypal) | -## Contributors ✨ +## Contributors [Back to top][toc] @@ -822,6 +823,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
John

💻
Parbez

💻 ⚠️ 🐛 📖 + +
allcontributors[bot]

📖 + diff --git a/package.json b/package.json index 6310500e..0572e5c0 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@sapphire/eslint-config": "^4.3.7", "@sapphire/prettier-config": "^1.4.3", "@sapphire/ts-config": "^3.3.4", + "@types/lodash.uniqwith": "^4.5.7", "@types/node": "^18.0.0", "@typescript-eslint/eslint-plugin": "^5.30.3", "@typescript-eslint/parser": "^5.30.3", @@ -49,8 +50,10 @@ "eslint": "^8.19.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.2.1", + "fast-deep-equal": "^3.1.3", "husky": "^8.0.1", "lint-staged": "^13.0.3", + "lodash.uniqwith": "^4.5.0", "pinst": "^3.0.0", "prettier": "^2.7.1", "pretty-quick": "^3.1.3", diff --git a/scripts/no-bundle-test.mjs b/scripts/no-bundle-test.mjs new file mode 100644 index 00000000..4e7d89b7 --- /dev/null +++ b/scripts/no-bundle-test.mjs @@ -0,0 +1,16 @@ +/** + * A very simple file that can run without having a node_modules directory + * This way we can validate from a NodeJS environment that the bundle will + * work completely standalone + */ + +import assert from 'node:assert/strict'; +import { ExpectedConstraintError, s } from '../dist/index.mjs'; + +const uniqSchema = s.array(s.string).unique; +const goodData = ['a', 'b', 'c']; +const badData = ['a', 'b', 'a']; + +assert.deepStrictEqual(uniqSchema.parse(goodData), goodData); + +assert.throws(() => uniqSchema.parse(badData), ExpectedConstraintError); diff --git a/src/constraints/ArrayLengthConstraints.ts b/src/constraints/ArrayConstraints.ts similarity index 85% rename from src/constraints/ArrayLengthConstraints.ts rename to src/constraints/ArrayConstraints.ts index 2108b24c..ef953d5f 100644 --- a/src/constraints/ArrayLengthConstraints.ts +++ b/src/constraints/ArrayConstraints.ts @@ -1,18 +1,21 @@ import { ExpectedConstraintError } from '../lib/errors/ExpectedConstraintError'; import { Result } from '../lib/Result'; import type { IConstraint } from './base/IConstraint'; +import { isUnique } from './util/isUnique'; import { Comparator, equal, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, notEqual } from './util/operators'; -export type ArrayConstraintName = `s.array(T).length${ - | 'LessThan' - | 'LessThanOrEqual' - | 'GreaterThan' - | 'GreaterThanOrEqual' - | 'Equal' - | 'NotEqual' - | 'Range' - | 'RangeInclusive' - | 'RangeExclusive'}`; +export type ArrayConstraintName = `s.array(T).${ + | 'unique' + | `length${ + | 'LessThan' + | 'LessThanOrEqual' + | 'GreaterThan' + | 'GreaterThanOrEqual' + | 'Equal' + | 'NotEqual' + | 'Range' + | 'RangeInclusive' + | 'RangeExclusive'}`}`; function arrayLengthComparator(comparator: Comparator, name: ArrayConstraintName, expected: string, length: number): IConstraint { return { @@ -86,3 +89,11 @@ export function arrayLengthRangeExclusive(startAfter: number, endBefore: numb } }; } + +export const uniqueArray: IConstraint = { + run(input: unknown[]) { + return isUnique(input) // + ? Result.ok(input) + : Result.err(new ExpectedConstraintError('s.array(T).unique', 'Array values are not unique', input, 'Expected all values to be unique')); + } +}; diff --git a/src/constraints/type-exports.ts b/src/constraints/type-exports.ts index 9e0df16b..abf482f6 100644 --- a/src/constraints/type-exports.ts +++ b/src/constraints/type-exports.ts @@ -9,7 +9,7 @@ export type { arrayLengthRange, arrayLengthRangeExclusive, arrayLengthRangeInclusive -} from './ArrayLengthConstraints'; +} from './ArrayConstraints'; export type { IConstraint } from './base/IConstraint'; export type { BigIntConstraintName, diff --git a/src/constraints/util/isUnique.ts b/src/constraints/util/isUnique.ts new file mode 100644 index 00000000..16f2bd8c --- /dev/null +++ b/src/constraints/util/isUnique.ts @@ -0,0 +1,8 @@ +import fastDeepEqual from 'fast-deep-equal/es6'; +import uniqWith from 'lodash.uniqwith'; + +export function isUnique(input: unknown[]) { + if (input.length < 2) return true; + const uniqueArray = uniqWith(input, fastDeepEqual); + return uniqueArray.length === input.length; +} diff --git a/src/validators/ArrayValidator.ts b/src/validators/ArrayValidator.ts index c305c978..79938313 100644 --- a/src/validators/ArrayValidator.ts +++ b/src/validators/ArrayValidator.ts @@ -7,8 +7,9 @@ import { arrayLengthNotEqual, arrayLengthRange, arrayLengthRangeExclusive, - arrayLengthRangeInclusive -} from '../constraints/ArrayLengthConstraints'; + arrayLengthRangeInclusive, + uniqueArray +} from '../constraints/ArrayConstraints'; import type { IConstraint } from '../constraints/base/IConstraint'; import type { BaseError } from '../lib/errors/BaseError'; import { CombinedPropertyError } from '../lib/errors/CombinedPropertyError'; @@ -70,6 +71,10 @@ export class ArrayValidator extends BaseVali return this.addConstraint(arrayLengthRangeExclusive(startAfter, endBefore) as IConstraint) as any; } + public get unique(): this { + return this.addConstraint(uniqueArray as IConstraint); + } + protected override clone(): this { return Reflect.construct(this.constructor, [this.validator, this.constraints]); } diff --git a/tests/lib/configs.test.ts b/tests/lib/configs.test.ts index 942a376d..7b059a22 100644 --- a/tests/lib/configs.test.ts +++ b/tests/lib/configs.test.ts @@ -31,19 +31,19 @@ describe('Validation enabled and disabled configurations', () => { setGlobalValidationEnabled(true); }); - test.each(predicateAndValues)('GIVEN globally disabled %s predicate THEN returns the input', (_, inputPredicate, input) => { + test.each(predicateAndValues)('GIVEN globally disabled %j predicate THEN returns the input', (_, inputPredicate, input) => { expect(inputPredicate.parse(input)).toStrictEqual(input); }); }); describe('Validator level configurations', () => { - test.each(predicateAndValues)('GIVEN disabled %s predicate THEN returns the input', (_, inputPredicate, input) => { + test.each(predicateAndValues)('GIVEN disabled %j predicate THEN returns the input', (_, inputPredicate, input) => { const predicate = inputPredicate.setValidationEnabled(false); expect(predicate.parse(input)).toStrictEqual(input); }); - test.each(predicateAndValues)('GIVEN function to disable %s predicate THEN returns the input', (_, inputPredicate, input) => { + test.each(predicateAndValues)('GIVEN function to disable %j predicate THEN returns the input', (_, inputPredicate, input) => { const predicate = inputPredicate.setValidationEnabled(() => false); expect(predicate.parse(input)).toStrictEqual(input); @@ -66,7 +66,7 @@ describe('Validation enabled and disabled configurations', () => { }); test.each(predicateAndValues)( - 'GIVEN enabled %s predicate while the global option is set to false THEN it should throw validation errors', + 'GIVEN enabled %j predicate while the global option is set to false THEN it should throw validation errors', (_, inputPredicate, input) => { const predicate = inputPredicate.setValidationEnabled(true); diff --git a/tests/validators/array.test.ts b/tests/validators/array.test.ts index 860ec6d8..401d61a5 100644 --- a/tests/validators/array.test.ts +++ b/tests/validators/array.test.ts @@ -1,4 +1,4 @@ -import { CombinedPropertyError, ExpectedConstraintError, s, ValidationError } from '../../src'; +import { ArrayValidator, CombinedPropertyError, ExpectedConstraintError, s, ValidationError } from '../../src'; import { expectClonedValidator, expectError } from '../common/macros/comparators'; describe('ArrayValidator', () => { @@ -12,7 +12,7 @@ describe('ArrayValidator', () => { expectError(() => predicate.parse('Hello there'), new ValidationError('s.array(T)', 'Expected an array', 'Hello there')); }); - test.each([123, true, {}, [], null])('GIVEN an array with value %s other than string THEN throws CombinedPropertyError', (input) => { + test.each([123, true, {}, [], null])('GIVEN an array with value %j other than string THEN throws CombinedPropertyError', (input) => { expectError( () => predicate.parse([input]), new CombinedPropertyError([ @@ -192,6 +192,35 @@ describe('ArrayValidator', () => { }); }); + describe('Unique', () => { + const validInputPredicate: [unknown, ArrayValidator][] = [ + [['Hello'], s.string.array.unique], + [['Hello', 'There'], s.string.array.unique], + [[{ name: 'Hello' }, { name: 'Hi' }], s.object({ name: s.string }).array.unique], + [[['Hello'], ['Hi']], s.string.array.array.unique], + [[[{ name: 'Hello' }], [{ name: 'Hi' }]], s.object({ name: s.string }).array.array.unique] + ]; + + const invalidInputPredicate: [unknown, ArrayValidator][] = [ + [['Hello', 'Hello'], s.string.array.unique], + [[1, 2, 4, 2, 1], s.number.array.unique], + [[{ name: 'Hello' }, { name: 'Hello' }], s.object({ name: s.string }).array.unique], + [[['Hello'], ['Hello']], s.string.array.array.unique], + [[[{ name: 'Hello' }], [{ name: 'Hello' }]], s.object({ name: s.string }).array.array.unique] + ]; + + test.each(validInputPredicate)('GIVEN %j THEN return the given value', (value, p) => { + expect(p.parse(value)).toEqual(value); + }); + + test.each(invalidInputPredicate)('GIVEN %j THEN throws ExpectedConstraintError', (value, p) => { + expectError( + () => p.parse(value), + new ExpectedConstraintError('s.array(T).unique', 'Array values are not unique', value, 'Expected all values to be unique') + ); + }); + }); + test('GIVEN clone THEN returns similar instance', () => { const arrayPredicate = s.string.array; diff --git a/tests/validators/date.test.ts b/tests/validators/date.test.ts index deeeec50..10ddf20e 100644 --- a/tests/validators/date.test.ts +++ b/tests/validators/date.test.ts @@ -21,11 +21,11 @@ describe('DateValidator', () => { describe('lessThan', () => { const ltPredicate = s.date.lessThan(date); - test.each(datesInPast)('GIVEN %s THEN returns given value', (value) => { + test.each(datesInPast)('GIVEN %j THEN returns given value', (value) => { expect(ltPredicate.parse(value)).toBe(value); }); - test.each(datesInFuture)('GIVEN %s THEN throws ConstraintError', (value) => { + test.each(datesInFuture)('GIVEN %j THEN throws ConstraintError', (value) => { expectError( () => ltPredicate.parse(value), new ExpectedConstraintError('s.date.lessThan', 'Invalid Date value', value, 'expected < 2022-02-01T00:00:00.000Z') @@ -36,11 +36,11 @@ describe('DateValidator', () => { describe('lessThanOrEqual', () => { const lePredicate = s.date.lessThanOrEqual(date); - test.each([...datesInPast, date])('GIVEN %s THEN returns given value', (value) => { + test.each([...datesInPast, date])('GIVEN %j THEN returns given value', (value) => { expect(lePredicate.parse(value)).toBe(value); }); - test.each(datesInFuture)('GIVEN %s THEN throws ConstraintError', (value) => { + test.each(datesInFuture)('GIVEN %j THEN throws ConstraintError', (value) => { expectError( () => lePredicate.parse(value), new ExpectedConstraintError('s.date.lessThanOrEqual', 'Invalid Date value', value, 'expected <= 2022-02-01T00:00:00.000Z') @@ -51,11 +51,11 @@ describe('DateValidator', () => { describe('greaterThan', () => { const gtPredicate = s.date.greaterThan(date); - test.each(datesInFuture)('GIVEN %s THEN returns given value', (value) => { + test.each(datesInFuture)('GIVEN %j THEN returns given value', (value) => { expect(gtPredicate.parse(value)).toBe(value); }); - test.each(datesInPast)('GIVEN %s THEN throws ConstraintError', (value) => { + test.each(datesInPast)('GIVEN %j THEN throws ConstraintError', (value) => { expectError( () => gtPredicate.parse(value), new ExpectedConstraintError('s.date.greaterThan', 'Invalid Date value', value, 'expected > 2022-02-01T00:00:00.000Z') @@ -66,11 +66,11 @@ describe('DateValidator', () => { describe('greaterThanOrEqual', () => { const gePredicate = s.date.greaterThanOrEqual(date); - test.each([date, ...datesInFuture])('GIVEN %s THEN returns given value', (value) => { + test.each([date, ...datesInFuture])('GIVEN %j THEN returns given value', (value) => { expect(gePredicate.parse(value)).toBe(value); }); - test.each(datesInPast)('GIVEN %s THEN throws ConstraintError', (value) => { + test.each(datesInPast)('GIVEN %j THEN throws ConstraintError', (value) => { expectError( () => gePredicate.parse(value), new ExpectedConstraintError('s.date.greaterThanOrEqual', 'Invalid Date value', value, 'expected >= 2022-02-01T00:00:00.000Z') @@ -85,7 +85,7 @@ describe('DateValidator', () => { expect(eqPredicate.parse(date)).toBe(date); }); - test.each([...datesInPast, ...datesInFuture])('GIVEN %s THEN throws ConstraintError', (value) => { + test.each([...datesInPast, ...datesInFuture])('GIVEN %j THEN throws ConstraintError', (value) => { expectError( () => eqPredicate.parse(value), new ExpectedConstraintError('s.date.equal', 'Invalid Date value', value, 'expected === 2022-02-01T00:00:00.000Z') @@ -102,7 +102,7 @@ describe('DateValidator', () => { describe('notEqual', () => { const nePredicate = s.date.notEqual(date); - test.each([...datesInPast, ...datesInFuture])('GIVEN %s THEN returns given value', (value) => { + test.each([...datesInPast, ...datesInFuture])('GIVEN %j THEN returns given value', (value) => { expect(nePredicate.parse(value)).toBe(value); }); diff --git a/tsup.config.ts b/tsup.config.ts index e4a975e1..34d85d6c 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -6,7 +6,8 @@ export default defineConfig({ entry: ['src/index.ts'], format: ['esm', 'cjs', 'iife'], minify: false, - skipNodeModulesBundle: true, + skipNodeModulesBundle: false, + bundle: true, sourcemap: true, target: 'es2020', tsconfig: 'src/tsconfig.json', diff --git a/yarn.lock b/yarn.lock index 2fe33ab2..9349887e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -496,6 +496,7 @@ __metadata: "@sapphire/eslint-config": ^4.3.7 "@sapphire/prettier-config": ^1.4.3 "@sapphire/ts-config": ^3.3.4 + "@types/lodash.uniqwith": ^4.5.7 "@types/node": ^18.0.0 "@typescript-eslint/eslint-plugin": ^5.30.3 "@typescript-eslint/parser": ^5.30.3 @@ -504,8 +505,10 @@ __metadata: eslint: ^8.19.0 eslint-config-prettier: ^8.5.0 eslint-plugin-prettier: ^4.2.1 + fast-deep-equal: ^3.1.3 husky: ^8.0.1 lint-staged: ^13.0.3 + lodash.uniqwith: ^4.5.0 pinst: ^3.0.0 prettier: ^2.7.1 pretty-quick: ^3.1.3 @@ -606,6 +609,22 @@ __metadata: languageName: node linkType: hard +"@types/lodash.uniqwith@npm:^4.5.7": + version: 4.5.7 + resolution: "@types/lodash.uniqwith@npm:4.5.7" + dependencies: + "@types/lodash": "*" + checksum: feea705d2162d906bfba7043500bb2f149ca6032bb49a24b8f60f96b8ba0a41cd4aff7e82057fe5e9e25909f31ba81b35b34dbda549e5b8ba667db9560285446 + languageName: node + linkType: hard + +"@types/lodash@npm:*": + version: 4.14.182 + resolution: "@types/lodash@npm:4.14.182" + checksum: 7dd137aa9dbabd632408bd37009d984655164fa1ecc3f2b6eb94afe35bf0a5852cbab6183148d883e9c73a958b7fec9a9bcf7c8e45d41195add6a18c34958209 + languageName: node + linkType: hard + "@types/minimatch@npm:^3.0.3": version: 3.0.5 resolution: "@types/minimatch@npm:3.0.5" @@ -3545,6 +3564,13 @@ __metadata: languageName: node linkType: hard +"lodash.uniqwith@npm:^4.5.0": + version: 4.5.0 + resolution: "lodash.uniqwith@npm:4.5.0" + checksum: d49a4565ed64efd86674127d321622673c29cde3e060baebc0f30372f22886c61b2ead44709db8c890053db1b9660e8ed689689812c1a485eb5703caa94d1150 + languageName: node + linkType: hard + "lodash@npm:^4.17.12, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20": version: 4.17.21 resolution: "lodash@npm:4.17.21"