diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..a416e5b --- /dev/null +++ b/src/index.ts @@ -0,0 +1,3 @@ +export * from 'remeda'; + +export * from './matches'; diff --git a/src/matches.test.ts b/src/matches.test.ts new file mode 100644 index 0000000..0c38351 --- /dev/null +++ b/src/matches.test.ts @@ -0,0 +1,22 @@ +import { test, expect } from 'vitest'; +import { matches } from './matches'; + +test('basic', () => { + expect(matches(1, 1)).toBeTruthy(); + expect(matches(1, 2)).not.toBeTruthy(); + expect(matches({ a: 1, b: 2 }, { a: 1 })).toBeTruthy(); + expect(matches({ a: 2, b: 2 }, { a: 1 })).not.toBeTruthy(); + expect( + matches([1, 1, { a: 3 }], { 2: { a: (x: number) => x > 2 } }) + ).toBeTruthy(); + + expect(matches({ a: 4, b: 5, c: 6 }, { a: 4, c: 6 })).toEqual(true); +}); + +test('data last', () => { + const partial = matches(1); + + expect(partial(1)).toBeTruthy(); + expect(partial(2)).toBeFalsy(); + expect(matches({ a: 4, c: 6 })({ a: 4, b: 5, c: 6 })).toEqual(true); +}); diff --git a/src/matches.ts b/src/matches.ts new file mode 100644 index 0000000..e68058d --- /dev/null +++ b/src/matches.ts @@ -0,0 +1,58 @@ +import { purry } from 'remeda'; + +function _matches(target: any, condition: any) { + if (typeof condition === 'function') return condition(target); + + if (typeof condition === 'object') { + return Object.entries(condition).every(([key, value]) => + matches(target[key], value) + ); + } + + return target === condition; +} + +type Matcher = ((dataInput: I) => boolean) | any; + +/** + * Compares the input with the matcher and returns `true` if they match. + * The matcher can be a function (which will be fed the input and is expected + * to return a boolean), or a value. If the value is an object, the matching + * will be recursive. + * @param input the input data + * @param matcher matching function or value + * @signature + * R.matches(inputData, matcher) + * @example + * R.matches( 'potato', 'potato'); // => true + * R.matches( 'potato', 'turnip'); // => false + * R.matches( 'potato', vegetable => vegetable === 'potato'); // => true + * R.matches({ a: 1, b :2 }, { a: 1 } ); // => true + * R.matches({ 'a': 4, 'b': 5, 'c': 6 }, { 'a': 4, 'c': 6 }) // => true + * @data_first + * @category Object + */ +export function matches(data: I, matcher: Matcher): boolean; + +/** + * Compares the input with the matcher and returns `true` if they match. + * The matcher can be a function (which will be fed the input and is expected + * to return a boolean), or a value. If the value is an object, the matching + * will be recursive. + * @param input the input data + * @param matcher matching function or value + * @signature + * R.matches(matcher)(inputData) + * @example + * R.pipe({a:1,b:2}, R.matches({ a: 1 }) ) // => true + * R.pipe({a:1,b:2}, R.matches({ b: (val) => val < 5 }) ) // => true + * R.pipe({a:1,b:2}, R.matches({ c: 3 }) ) // => false + * R.pipe( { 'a': 4, 'b': 5, 'c': 6 }, R.matches({ 'a': 4, 'c': 6 })) // => true + * @data_last + * @category Object + */ +export function matches(matcher: Matcher): (dataIn: any) => boolean; + +export function matches() { + return purry(_matches, arguments); +}