@asaidimu/predicates
v1.0.0
Published
A list of predicate functions.
Downloads
1
Readme
@asaidimu/predicates
A comprehensive collection of robust, reusable predicate functions for TypeScript applications.
Table of Contents
- Overview & Features
- Installation & Setup
- Usage Documentation
- Project Architecture
- Development & Contributing
- Additional Information
Overview & Features
@asaidimu/predicates is a utility library providing a standardized interface for a wide range of predicate (validation) functions. Each predicate is a pure function designed to check a specific condition on a given piece of data, optionally focusing on a field within that data, and taking additional arguments for flexible validation rules.
This library promotes reusability, composability, and type safety in your validation logic. Instead of writing ad-hoc checks throughout your codebase, you can leverage these pre-built, well-tested predicates to ensure data integrity, simplify conditional logic, and enhance code readability. It's particularly useful for form validation, API input validation, or any scenario requiring clear, declarative data assertions.
Key Features
- Standardized API: All predicates conform to the
PredicateorAsyncPredicatetype, ensuring a consistent invocation pattern. - Targeted Validation: Predicates can validate a root
dataobject or a specificfieldwithin it. - Extensible Arguments: Many predicates accept dynamic arguments, allowing for flexible rules (e.g.,
minLength(5),matchesRegex(/pattern/)). - Categorized Predicates: Functions are logically grouped into separate modules for easy discovery and granular imports:
- Basic: Fundamental type and presence checks (
isString,isNumber,isEmpty,isTruthy, etc.). - String: String-specific validations (
minLength,matchesRegex,isAlphanumeric,hasUppercase, etc.). - Number: Numeric validations (
minValue,inRange,isInteger,isEven,isPrime, etc.). - Collection: Array/collection validations (
arrayMinLength,contains,isUnique,everyElement, etc.). - Temporal: Date and time validations (
isBefore,isToday,isLeapYear,inDateRange, etc.). - Object: Object structure and content validations (
hasProperty,propertyCount,isDeepEqual,noNullValues, etc.). - Presence: Checks for existence (
isPresent,isAbsent,isNotEmpty).
- Basic: Fundamental type and presence checks (
- Comprehensive Coverage: A growing set of over 60 predicates covering various data types and common validation scenarios.
- TypeScript Support: Fully typed for enhanced developer experience and compile-time safety.
- Lightweight & Tree-Shakable: Focuses purely on predicate functions, avoiding heavy dependencies. Each predicate category is an independent module, allowing bundlers to tree-shake unused functions.
Installation & Setup
Prerequisites
- Node.js (LTS version recommended)
- Bun (or npm/Yarn as package manager)
- TypeScript (for development or if consuming types in a TypeScript project)
Installation Steps
Install the package using your preferred package manager:
# With Bun (recommended for this project's development)
bun install @asaidimu/predicates
# With npm
npm install @asaidimu/predicates
# With Yarn
yarn add @asaidimu/predicatesConfiguration
This library consists of pure functions and does not require any specific configuration files or environment variables. You simply import and use the predicates. TypeScript users will automatically benefit from the provided type definitions.
Verification
To quickly verify the installation, you can create a small TypeScript file (e.g., test-predicates.ts):
// test-predicates.ts
import { isString, isNumber } from '@asaidimu/predicates/basic';
import { minLength } from '@asaidimu/predicates/string';
import { isPositive } from '@asaidimu/predicates/number';
const data = {
username: 'john_doe',
age: 30,
email: '[email protected]'
};
console.log('Is username a string?', isString({ data: data, field: 'username' }));
console.log('Is username at least 5 chars?', minLength({ data: data, field: 'username', arguments: 5 }));
console.log('Is age a number?', isNumber({ data: data, field: 'age' }));
console.log('Is age positive?', isPositive({ data: data, field: 'age' }));
// Expected Output:
// Is username a string? true
// Is username at least 5 chars? true
// Is age a number? true
// Is age positive? trueRun this file:
# With Bun
bun run test-predicates.ts
# With ts-node (install globally: npm install -g ts-node)
ts-node test-predicates.tsUsage Documentation
Each predicate function in this library adheres to a common interface, making them intuitive and consistent to use.
Predicate Structure
The core Predicate type defines the common signature for all synchronous predicate functions:
export type Predicate<T, K=unknown> = (params: {
data: T;
field?: keyof T;
arguments: PredicateParameters<K>;
}) => boolean;
export type PredicateParameters<K = any> = K;data: The entire data object or the direct value you want to validate.field?: (Optional) If validating a property within an object, specify the key of that property. Whenfieldis provided, the predicate operates ondata[field]. If omitted, thedataitself is validated.arguments: (Optional, but often required) Specific parameters for the predicate's logic (e.g., a minimum value, a regex pattern, a comparison date). Its typeKvaries per predicate.
The library also defines AsyncPredicate for future asynchronous checks, but currently, all provided predicates are synchronous.
You can import predicates directly from the main package entry point, which re-exports all individual predicates, or from their specific category modules for more granular control over imports and bundle size.
// Recommended import for individual predicates:
import { isString, isNumber } from '@asaidimu/predicates/basic';
import { minLength } from '@asaidimu/predicates/string';
// Or import all predicates as a single map (less tree-shakeable for specific needs):
import { Predicates } from '@asaidimu/predicates';
console.log(Predicates.isString({ data: "hello" }));Basic Predicates
Located in src/predicates/basic.ts, these predicates offer fundamental type and existence checks.
import { isString, isNumber, isUndefined, isEmpty, isObject, isArray, isBoolean, isTruthy } from '@asaidimu/predicates/basic';
const userData = {
name: 'Alice',
age: 28,
email: undefined,
tags: ['typescript', 'javascript'],
address: null,
preferences: {},
isActive: true,
count: 0,
};
// Check specific fields
console.log('Is userData.name a string?', isString({ data: userData, field: 'name' })); // true
console.log('Is userData.age a number?', isNumber({ data: userData, field: 'age' })); // true
console.log('Is userData.email undefined?', isUndefined({ data: userData, field: 'email' })); // true
console.log('Is userData.address an object?', isObject({ data: userData, field: 'address' })); // false (isObject excludes null)
console.log('Is userData.tags an array?', isArray({ data: userData, field: 'tags' })); // true
console.log('Is userData.preferences an empty object?', isEmpty({ data: userData, field: 'preferences' })); // true
console.log('Is userData.isActive a boolean?', isBoolean({ data: userData, field: 'isActive' })); // true
console.log('Is userData.count truthy?', isTruthy({ data: userData, field: 'count' })); // false (0 is falsy)
// Check root data
console.log('Is 123 a number?', isNumber({ data: 123 })); // true
console.log('Is "hello" empty?', isEmpty({ data: 'hello' })); // false
console.log('Is [] empty?', isEmpty({ data: [] })); // true
console.log('Is null empty?', isEmpty({ data: null })); // trueString Predicates
Located in src/predicates/string.ts, these are for validating string values.
import { minLength, maxLength, matchesRegex, isAlphanumeric, hasDigit, includesSubstring } from '@asaidimu/predicates/string';
const userRegistration = {
username: 'dev_user1',
password: 'SecurePassword123!',
bio: 'A short bio.',
tagline: 'Hello world',
};
console.log('Username has min length 5?', minLength({ data: userRegistration, field: 'username', arguments: 5 })); // true
console.log('Bio has max length 10?', maxLength({ data: userRegistration, field: 'bio', arguments: 10 })); // false
console.log('Password matches strong regex?', matchesRegex({
data: userRegistration,
field: 'password',
arguments: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()])[A-Za-z\d!@#$%^&*()]{8,}$/
})); // true (if 8+ chars, with lower, upper, digit, special)
console.log('Username is alphanumeric?', isAlphanumeric({ data: userRegistration, field: 'username' })); // false (contains '_')
console.log('Password has a digit?', hasDigit({ data: userRegistration, field: 'password' })); // true
console.log('Tagline includes "world"?', includesSubstring({ data: userRegistration, field: 'tagline', arguments: 'world' })); // trueNumber Predicates
Located in src/predicates/number.ts, for validating numeric values.
import { minValue, inRange, isInteger, isPositive, isMultipleOf, isPrime, isPerfectSquare, isEven } from '@asaidimu/predicates/number';
const product = {
price: 99.99,
stock: 150,
discount: 10,
primeCandidate: 17,
squareCandidate: 25,
};
console.log('Price is at least 0?', minValue({ data: product, field: 'price', arguments: 0 })); // true
console.log('Stock is between 100 and 200?', inRange({ data: product, field: 'stock', arguments: { min: 100, max: 200 } })); // true
console.log('Discount is an integer?', isInteger({ data: product, field: 'discount' })); // true
console.log('Price is positive?', isPositive({ data: product, field: 'price' })); // true
console.log('Stock is a multiple of 10?', isMultipleOf({ data: product, field: 'stock', arguments: 10 })); // true
console.log('Is primeCandidate a prime number?', isPrime({ data: product, field: 'primeCandidate' })); // true
console.log('Is squareCandidate a perfect square?', isPerfectSquare({ data: product, field: 'squareCandidate' })); // true
console.log('Is discount an even number?', isEven({ data: product, field: 'discount' })); // trueCollection Predicates
Located in src/predicates/collection.ts, for validating arrays and other collections.
import { arrayMinLength, contains, isUnique, everyElement, isArrayOfType, isContiguous, isSorted } from '@asaidimu/predicates/collection';
import { isNumber, isObject, isString } from '@asaidimu/predicates/basic'; // For inner type checks
const order = {
items: [
{ id: 1, quantity: 2 },
{ id: 2, quantity: 1 },
{ id: 3, quantity: 5 }
],
tags: ['electronics', 'gadgets', 'electronics'], // Note: 'electronics' is duplicated
categories: ['tech', 'gadgets', 'wearables'],
numbers: [1, 2, 3, 4],
unsortedNumbers: [5, 1, 3],
alphabetical: ['apple', 'banana', 'cherry'],
};
console.log('Order has at least 1 item?', arrayMinLength({ data: order, field: 'items', arguments: 1 })); // true
console.log('Tags contain "gadgets"?', contains({ data: order, field: 'tags', arguments: 'gadgets' })); // true
console.log('Categories are unique?', isUnique({ data: order, field: 'categories' })); // true
console.log('Tags are unique?', isUnique({ data: order, field: 'tags' })); // false (electronics is duplicated)
console.log('All order items are objects?', everyElement({ data: order, field: 'items', arguments: isObject })); // true
console.log('All numbers are actual numbers?', isArrayOfType({ data: order, field: 'numbers', arguments: isNumber })); // true
console.log('Is numbers array contiguous?', isContiguous({ data: order, field: 'numbers' })); // true
console.log('Is unsortedNumbers sorted (ascending)?', isSorted({ data: order, field: 'unsortedNumbers' })); // false
console.log('Is alphabetical sorted (ascending)?', isSorted({ data: order, field: 'alphabetical' })); // trueTemporal Predicates
Located in src/predicates/temporal.ts, for date and time validations.
import { isBefore, inDateRange, isToday, isFuture, isWeekend, isLeapYear, isMonth } from '@asaidimu/predicates/temporal';
const now = new Date();
const nextYear = new Date(now.getFullYear() + 1, 0, 1);
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 15);
const event = {
startDate: new Date('2024-07-20T10:00:00Z'), // Saturday
endDate: new Date('2024-07-25T17:00:00Z'),
deliveryDate: nextYear,
birthDate: new Date('1999-02-29T00:00:00Z'), // A leap year date
checkDate: lastMonth,
};
const comparisonDate = new Date('2024-07-22T12:00:00Z'); // Monday
const rangeStart = new Date('2024-07-15T00:00:00Z');
const rangeEnd = new Date('2024-07-30T23:59:59Z');
console.log('Start date is before comparison date?', isBefore({ data: event, field: 'startDate', arguments: comparisonDate })); // true
console.log('End date is within range?', inDateRange({ data: event, field: 'endDate', arguments: { start: rangeStart, end: rangeEnd } })); // true
console.log('Delivery date is today?', isToday({ data: event, field: 'deliveryDate' })); // false
console.log('Delivery date is in the future?', isFuture({ data: event, field: 'deliveryDate' })); // true
console.log('Is start date on a weekend?', isWeekend({ data: event, field: 'startDate' })); // true (if July 20, 2024 is a Saturday)
console.log('Is birthDate in a leap year?', isLeapYear({ data: event, field: 'birthDate' })); // true
console.log(`Is checkDate in month ${lastMonth.getMonth()}?`, isMonth({ data: event, field: 'checkDate', arguments: lastMonth.getMonth() })); // trueObject Predicates
Located in src/predicates/object.ts, for validating object structure and content.
import { hasProperty, hasAllProperties, isEmptyObject, propertyValueSatisfies, isDeepEqual, hasNestedProperty, hasOnlyKeys } from '@asaidimu/predicates/object';
import { isString, isBoolean, isNumber } from '@asaidimu/predicates/basic';
const userProfile = {
id: 'user123',
name: 'Jane Doe',
settings: {
theme: 'dark',
notifications: true,
level: 5
},
address: null,
contact: {
email: '[email protected]'
}
};
console.log('Profile has "id" property?', hasProperty({ data: userProfile, arguments: 'id' })); // true
console.log('Profile has "id" and "name" properties?', hasAllProperties({ data: userProfile, arguments: ['id', 'name'] })); // true
console.log('Settings object is empty?', isEmptyObject({ data: userProfile, field: 'settings' })); // false
console.log('Settings theme is a string?', propertyValueSatisfies({ data: userProfile, field: 'settings', arguments: { key: 'theme', predicate: isString } })); // true
console.log('Settings notifications is boolean?', propertyValueSatisfies({ data: userProfile, field: 'settings', arguments: { key: 'notifications', predicate: isBoolean } })); // true
console.log('Has nested property contact.email?', hasNestedProperty({ data: userProfile, arguments: 'contact.email' })); // true
const anotherProfile = {
id: 'user123',
name: 'Jane Doe',
settings: {
theme: 'dark',
notifications: true,
level: 5
},
address: null,
contact: {
email: '[email protected]'
}
};
console.log('Profiles are deeply equal?', isDeepEqual({ data: userProfile, arguments: anotherProfile })); // true
const simpleObj = { a: 1, b: 2 };
console.log('Simple object has only keys "a", "b"?', hasOnlyKeys({ data: simpleObj, arguments: ['a', 'b'] })); // true
console.log('Simple object has only keys "a", "c"?', hasOnlyKeys({ data: simpleObj, arguments: ['a', 'c'] })); // false (missing b, extra c)Presence Predicates
Located in src/predicates/presence.ts, for checking if a value is present, absent, or non-empty based on null, undefined, empty strings, arrays, or objects.
import { isPresent, isAbsent, isNotEmpty } from '@asaidimu/predicates/presence';
const item = {
name: 'Book',
author: null,
description: undefined,
tags: [],
price: 25.00,
details: {},
category: 'Fiction'
};
console.log('Item name is present?', isPresent({ data: item, field: 'name' })); // true
console.log('Item author is absent?', isAbsent({ data: item, field: 'author' })); // true
console.log('Item description is absent?', isAbsent({ data: item, field: 'description' })); // true
console.log('Item tags is not empty?', isNotEmpty({ data: item, field: 'tags' })); // false (empty array)
console.log('Item details is not empty?', isNotEmpty({ data: item, field: 'details' })); // false (empty object)
console.log('Item price is present?', isPresent({ data: item, field: 'price' })); // true
console.log('Item category is not empty?', isNotEmpty({ data: item, field: 'category' })); // trueComposing Predicates
While this library provides individual predicates, it's designed to be easily composed with logical operators (AND, OR, NOT) to build complex validation rules.
import { minLength, maxLength, hasUppercase, hasLowercase, hasDigit, hasSpecialCharacter } from '@asaidimu/predicates/string';
const isStrongPassword = (password: string) => {
// Direct usage, assuming `password` is the `data` itself.
return (
minLength({ data: password, arguments: 8 }) &&
maxLength({ data: password, arguments: 30 }) &&
hasUppercase({ data: password }) &&
hasLowercase({ data: password }) &&
hasDigit({ data: password }) &&
hasSpecialCharacter({ data: password })
);
};
console.log('Is "P@sswOrd123!" a strong password?', isStrongPassword('P@sswOrd123!')); // true
console.log('Is "weak123" a strong password?', isStrongPassword('weak123')); // false
console.log('Is "JustTooLongPasswordWithoutAnySpecialCharacter12345" a strong password?', isStrongPassword('JustTooLongPasswordWithoutAnySpecialCharacter12345')); // false (too long)
// Example with data object and field
const validateUserPassword = (user: { password?: string }) => {
return user.password !== undefined && isStrongPassword(user.password);
}
console.log('User password is strong?', validateUserPassword({ password: 'ValidPassword!1' })); // true
console.log('User password is strong?', validateUserPassword({ password: 'weak' })); // falseProject Architecture
Core Components
src/types.ts: This pivotal file defines thePredicateandAsyncPredicateinterfaces. These are the contracts that all predicate functions must adhere to, ensuring a consistent API across the library. It also introducesPredicateParametersfor flexible arguments andPredicateMapfor organizing predicates into categories.src/predicates/*.ts: Each file in this directory represents a distinct category of predicate functions (e.g.,string.tsfor string validations,temporal.tsfor date/time checks). Each category file exports individual predicate functions and aPredicateMapobject (e.g.,StringPredicates,TemporalPredicates), which aggregates all predicates within that category.index.ts: This is the primary public entry point for the library. It re-exports all individual predicate functions from their respective categories for direct, tree-shakeable imports. It also exports a consolidatedPredicatesobject, which is aPredicateMapmerging all categorized predicates into a single accessible object.tsup.config.ts: This configuration file usestsup, a TypeScript bundler, to compile the source code. It dynamically discovers and sets up entry points for each predicate module (src/predicates/*),index.ts, andsrc/types.ts, ensuring that each module is built into CJS, ESM, and type declaration (.d.ts) files.dist.package.json: This file is a specializedpackage.jsonthat is copied to thedistdirectory during thepostbuildstep. It configures themain,types, andexportsfields to correctly point to the compiled output, enabling proper module resolution for consumers of the published package.
Data Flow
The library's data flow is straightforward:
- A consumer imports specific predicate functions (e.g.,
isString,minLength) or the aggregatedPredicatesobject. - The consumer calls a predicate function, passing a
paramsobject containing thedatato validate, an optionalfieldkey (to target a property withindata), and optionalargumentsspecific to the predicate's logic. - The predicate function applies its internal logic to the relevant part of the
data(either thedataitself ordata[field]) and returns aboolean(orPromise<boolean>for future async predicates). - The consumer then uses this boolean result to drive conditional logic, display validation messages, or control program flow in their application.
Extension Points
The modular design of this library makes it highly extensible:
- Adding New Predicates: To add a new predicate, simply create a new file under
src/predicates/(for a new category) or add to an existing category file. Ensure it conforms to thePredicateorAsyncPredicateinterface, add comprehensive JSDoc, and export it. Updatetsup.config.tsandindex.tsif it's a new category or needs specific exposure. - Custom Predicates: Users can easily define their own custom predicates that conform to the
Predicatetype. This allows them to integrate seamlessly with other validation logic that might utilize this library's conventions, without requiring any changes to the library itself.
Development & Contributing
We welcome contributions to @asaidimu/predicates!
Development Setup
- Clone the repository:
git clone https://github.com/asaidimu/predicates.git cd predicates - Install dependencies:
This project uses
bunas its package manager, which is recommended for development.
If you preferbun installnpmoryarn, ensure all dependencies are installed using:npm install # or yarn install
Scripts
The package.json defines several useful scripts for development:
bun run ci: Installs project dependencies. This script is primarily used in CI/CD pipelines to ensure a clean and consistent build environment.bun run clean: Removes thedistdirectory, which contains compiled output. Useful for ensuring a fresh build.bun run cleanbun run prebuild: Executes thecleanscript, then runs a custom TypeScript script (./scripts/prebuild.ts). This script is responsible for preparingdist.package.jsonby potentially copying relevant metadata from the mainpackage.jsonbefore the library build.bun run build: Compiles the TypeScript source code into JavaScript (CommonJS and ES Modules formats) and generates TypeScript declaration files (.d.ts) usingtsup.bun run buildbun run postbuild: Runs after thebuildcommand. It copiesREADME.md,LICENSE.md, and the prepareddist.package.jsoninto thedistdirectory, making the compiled package ready for distribution and publication to npm.
Testing
While no explicit test files are included in this snapshot, a robust library like @asaidimu/predicates would typically include a comprehensive suite of unit tests for each predicate function. These tests ensure that each predicate behaves as expected across various valid and invalid inputs, covering edge cases and argument handling.
To run tests (if tests were implemented using a framework like Jest or Vitest):
bun test # or npm test / yarn testContributing Guidelines
We appreciate your interest in contributing! To ensure a smooth process, please follow these guidelines:
- Fork the repository: Start by forking the
asaidimu/predicatesrepository to your GitHub account. - Create a feature branch:
Choose a descriptive name for your branch (e.g.,git checkout -b feature/your-feature-namefeat/add-email-predicate,fix/numeric-range-bug). - Implement your changes:
- Ensure your code adheres to existing coding styles and conventions.
- Add or update predicates following the
PredicateorAsyncPredicateinterface. - Include comprehensive JSDoc comments for all new or modified predicates, mirroring the existing documentation style and providing clear examples.
- Write or update unit tests for any new functionality or bug fixes to maintain code quality and prevent regressions.
- Commit your changes: Use clear and concise commit messages. This project uses Semantic Release for automated versioning and changelog generation, so your commit messages should follow Conventional Commits specifications (e.g.,
feat(string): add isEmail predicate,fix(number): correct isPrime logic). - Push to your branch:
git push origin feature/your-feature-name - Open a Pull Request: Submit a pull request to the
mainbranch of the original repository. Provide a clear description of your changes, explaining the problem solved or the feature added, and link to any relevant issues.
Issue Reporting
If you encounter any bugs, have a feature request, or wish to propose an improvement, please open an issue on the GitHub Issues page. Provide as much detail as possible, including steps to reproduce bugs or a clear description of new features.
Additional Information
Troubleshooting
- TypeScript Errors: If you encounter TypeScript compilation errors, ensure your
tsconfig.jsonincludeslib: ["ESNext", "DOM"]andmoduleResolution: "bundler", as specified in this project's configuration. Also, verify that yourtypescriptand@typescript-eslint/parserversions indevDependenciesare compatible. - Installation Issues: If
bun install(ornpm install/yarn install) fails, try clearing your package manager cache (bun cache clean,npm cache clean --force,yarn cache clean) and reinstalling. Network issues or corrupted caches can sometimes cause installation problems. - Module Resolution: If you're having trouble importing modules (e.g.,
@core/types), ensure yourtsconfig.jsonhas the correctbaseUrlandpathsconfiguration ("baseUrl": ".", "paths": { "@core/*": ["src/*"] }).
Changelog / Roadmap
For detailed changes in each version, please refer to the CHANGELOG.md file.
Future enhancements for @asaidimu/predicates may include:
- Expanded Predicate Library: Adding more specialized predicate functions across existing and potentially new categories (e.g., network, security, geographical).
- Asynchronous Predicates: Implementing
AsyncPredicatefunctions for checks requiring I/O operations (e.g.,isUsernameUniquewhich might query a database or API). - Predicate Composition Utilities: Providing higher-order functions or utilities for easily combining predicates with logical AND (
and), OR (or), and NOT (not) operators, potentially with short-circuiting or custom error handling. - Performance Optimizations: Continuously reviewing and optimizing predicate implementations for better performance.
- Documentation Improvements: Enhancing examples, adding more in-depth usage guides, and potentially generating API documentation.
License
This project is licensed under the MIT License. See the LICENSE.md file for the full text.
Acknowledgments
Developed by Saidimu. Special thanks to all contributors and the open-source community for their invaluable inspiration, tools, and support that make projects like this possible.
