{"_id":"@fishka/assertions","_rev":"2-0ce4f358396801d21b2c962f13a0d545","name":"@fishka/assertions","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.0":{"name":"@fishka/assertions","version":"1.0.0","keywords":["assert","validate","check"],"license":"MIT","_id":"@fishka/assertions@1.0.0","maintainers":[{"name":"fishka","email":"fishka@ramigo.org"}],"dist":{"shasum":"39c710f693ace46d1be878c5c4f72152531d5859","tarball":"https://registry.npmjs.org/@fishka/assertions/-/assertions-1.0.0.tgz","fileCount":75,"integrity":"sha512-M9d1qcCCY9m48+hKWoRtqkbpzfursd3K//VvjUy7ezGoAr7TEOhebQ6t4VxJVK9fh0NBFic0i04EyUXsl3nKRg==","signatures":[{"sig":"MEUCIHH6lFAbtGjiIU6jj4WRV3JPm/7CED6/WwWrSTRY00cpAiEAncwNioiDrT5eMiAGZFT/+4hD31iAv9gMTa4DZkNnsdo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":144885},"main":"./dist/cjs/index.js","types":"./dist/esm/index.d.ts","module":"./dist/esm/index.js","engines":{"node":">=10"},"exports":{".":{"types":"./dist/esm/index.d.ts","import":"./dist/esm/index.js","require":"./dist/cjs/index.js"}},"gitHead":"584c88e7a4008b586d49bdce6acff10449e5f436","private":false,"scripts":{"lint":"eslint .","test":"jest --runInBand","build":"npm run build:esm && npm run build:cjs","clean":"rimraf ./dist","format":"prettier --write \"./**/*.{ts,js,md,json}\"","release":"npm run clean && npm run build && npm run lint && npm run test && npm publish","prebuild":"npm run clean","build:cjs":"tsc -p tsconfig.cjs.json","build:esm":"tsc -p tsconfig.esm.json"},"_npmUser":{"name":"fishka","email":"fishka@ramigo.org"},"repository":{"url":"https://gitea.com/fishka/assertions.git","type":"git"},"_npmVersion":"11.7.0","description":"Assertions and Validations for TypeScript","directories":{},"sideEffects":false,"_nodeVersion":"20.19.2","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.39.2","rimraf":"^6.1.2","ts-jest":"^29.4.6","prettier":"^3.7.4","ts-loader":"^9.5.4","typescript":"^5.9.3","@typescript-eslint/parser":"^8.52.0","eslint-plugin-unused-imports":"^4.3.0","@typescript-eslint/eslint-plugin":"^8.52.0"},"_npmOperationalInternal":{"tmp":"tmp/assertions_1.0.0_1767666843422_0.2840632528682525","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@fishka/assertions","description":"Assertions and Validations for TypeScript","version":"1.0.1","license":"MIT","main":"./dist/cjs/index.js","module":"./dist/esm/index.js","types":"./dist/esm/index.d.ts","private":false,"sideEffects":false,"scripts":{"prebuild":"npm run clean","build:esm":"tsc -p tsconfig.esm.json","build:cjs":"tsc -p tsconfig.cjs.json","build":"npm run build:esm && npm run build:cjs","clean":"rimraf ./dist","lint":"eslint .","format":"prettier --write \"./**/*.{ts,js,md,json}\"","test":"jest --runInBand","release":"npm run clean && npm run build && npm run lint && npm run test && npm publish"},"devDependencies":{"@typescript-eslint/eslint-plugin":"^8.52.0","@typescript-eslint/parser":"^8.52.0","eslint":"^9.39.2","eslint-plugin-unused-imports":"^4.3.0","prettier":"^3.7.4","rimraf":"^6.1.2","ts-jest":"^29.4.6","ts-loader":"^9.5.4","typescript":"^5.9.3"},"engines":{"node":">=10"},"keywords":["assert","validate","check"],"exports":{".":{"types":"./dist/esm/index.d.ts","import":"./dist/esm/index.js","require":"./dist/cjs/index.js"}},"repository":{"type":"git","url":"https://gitea.com/fishka/assertions.git"},"publishConfig":{"access":"public"},"gitHead":"8dc62f571eee5fe31a6d9e0bddcd3012bcdce144","_id":"@fishka/assertions@1.0.1","_nodeVersion":"20.19.2","_npmVersion":"11.7.0","dist":{"integrity":"sha512-2tVShUUOvqJqJxJvXcJE9o1VylVnNZXJdSnDcG+P+7PEmWi2/DNuh/BaAKFVCWmB08xNFkNpg1Wwf8xWROE+fw==","shasum":"b144e317308c955047d2ddb4cd3efd822667dfe2","tarball":"https://registry.npmjs.org/@fishka/assertions/-/assertions-1.0.1.tgz","fileCount":84,"unpackedSize":192006,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCdp18d3pzNydrYWKyWwD/vrGd+Q10rP1FtmuVDDFKAuwIgfCO+2ZNUIIRFJe/AqiwnvV3CbR2Yo1vFuK2pKgA4PQU="}]},"_npmUser":{"name":"fishka","email":"fishka@ramigo.org"},"directories":{},"maintainers":[{"name":"fishka","email":"fishka@ramigo.org"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/assertions_1.0.1_1767926351850_0.1892238694361439"},"_hasShrinkwrap":false}},"time":{"created":"2026-01-06T02:34:03.333Z","modified":"2026-01-09T02:39:12.184Z","1.0.0":"2026-01-06T02:34:03.593Z","1.0.1":"2026-01-09T02:39:12.007Z"},"license":"MIT","keywords":["assert","validate","check"],"repository":{"type":"git","url":"https://gitea.com/fishka/assertions.git"},"description":"Assertions and Validations for TypeScript","maintainers":[{"name":"fishka","email":"fishka@ramigo.org"}],"readme":"# @fishka/assertions\n\nType-safe runtime assertions and validations for TypeScript with compile-time verification.\n\n## Features\n\n- **Type-safe** - Full TypeScript support with `asserts` type guards\n- **Compile-time checked** - ObjectAssertion ensures all fields are covered\n- **Two patterns** - Assertions (throw errors) or Validations (return error strings)\n- **Zero dependencies** - Lightweight and self-contained\n- **Composable** - Build complex validators from simple building blocks\n- **Rich constraints** - Array length, uniqueness, string patterns, cross-field validation\n- **Tree-shakable** - Import only what you need\n\n## Installation\n\n```bash\nnpm install @fishka/assertions\n```\n\nor\n\n```bash\nyarn add @fishka/assertions\n```\n\n## Quick Start\n\n```typescript\nimport { assertString, assertNumber, assertObject } from '@fishka/assertions';\n\n// Basic type assertions\nassertString('hello'); // ✓ passes\nassertNumber(42); // ✓ passes\nassertString(123); // ✗ throws: \"Not a string <number:123>\"\n\n// Object validation with compile-time type safety\ninterface User {\n  name: string;\n  age: number;\n}\n\nconst userAssertion: ObjectAssertion<User> = {\n  name: assertString,\n  age: assertNumber,\n};\n\nconst data: unknown = { name: 'Alice', age: 30 };\nassertObject(data, userAssertion);\n// TypeScript now knows `data` is User type\nconsole.log(data.name); // ✓ type-safe\n```\n\n## Core Concepts\n\n### Assertions vs Validations\n\n**Assertions** throw errors and use TypeScript's `asserts` type guards:\n\n```typescript\nimport { assertString, assertObject } from '@fishka/assertions';\n\nfunction processUser(data: unknown) {\n  assertObject(data, userAssertion); // throws if invalid\n  // TypeScript now knows data is User\n  console.log(data.name.toUpperCase());\n}\n```\n\n**Validations** return error messages without throwing:\n\n```typescript\nimport { validateObject } from '@fishka/assertions';\n\nfunction validateUserInput(input: unknown) {\n  const error = validateObject(input, userAssertion);\n  if (error) {\n    return { success: false, error };\n  }\n  return { success: true, data: input }; // TypeScript knows input is User\n}\n```\n\nUse **assertions** for internal consistency checks. Use **validations** for user input or when you need to handle errors gracefully.\n\n## Common Use Cases\n\n### 1. Primitive Type Assertions\n\n```typescript\nimport { assertString, assertNumber, assertBoolean, assertDate, assertNonNullable } from '@fishka/assertions';\n\nassertString('hello'); // ✓\nassertNumber(42); // ✓\nassertBoolean(true); // ✓\nassertDate(new Date()); // ✓\nassertNonNullable('value'); // ✓\nassertNonNullable(null); // ✗ throws: \"Value is null\"\n```\n\n### 2. Format Assertions\n\n```typescript\nimport { assertUuid, assertEmail, assertHexString } from '@fishka/assertions';\n\nassertUuid('550e8400-e29b-41d4-a716-446655440000'); // ✓\nassertEmail('user@example.com'); // ✓\nassertHexString('a1b2c3'); // ✓\n```\n\n### 3. Object Validation\n\n#### Basic Object\n\n```typescript\nimport { assertObject, type ObjectAssertion } from '@fishka/assertions';\n\ninterface User {\n  name: string;\n  age: number;\n  email: string;\n}\n\nconst userAssertion: ObjectAssertion<User> = {\n  name: assertString,\n  age: assertNumber,\n  email: assertEmail,\n};\n\nassertObject({ name: 'Alice', age: 30, email: 'alice@example.com' }, userAssertion);\n```\n\n#### Optional Fields\n\n```typescript\nimport { undefinedOr, nullOr } from '@fishka/assertions';\n\ninterface Profile {\n  name: string;\n  nickname?: string; // optional\n  bio: string | null; // nullable\n}\n\nconst profileAssertion: ObjectAssertion<Profile> = {\n  name: assertString,\n  nickname: undefinedOr(assertString), // allows undefined\n  bio: nullOr(assertString), // allows null\n};\n```\n\n#### Nested Objects\n\n```typescript\ninterface Address {\n  street: string;\n  city: string;\n}\n\ninterface User {\n  name: string;\n  address: Address;\n}\n\nconst addressAssertion: ObjectAssertion<Address> = {\n  street: assertString,\n  city: assertString,\n};\n\nconst userAssertion: ObjectAssertion<User> = {\n  name: assertString,\n  address: addressAssertion, // nested object\n};\n```\n\n#### Cross-Field Validation\n\n```typescript\nconst userAssertion: ObjectAssertion<User> = {\n  name: assertString,\n  age: assertNumber,\n  email: assertEmail,\n  $o: user => {\n    // Custom validation after all fields pass\n    assertTruthy(user.age >= 18, 'User must be 18 or older');\n  },\n};\n```\n\n#### Strict Mode (No Extra Fields)\n\n```typescript\nassertObject(\n  { name: 'Alice', age: 30, unknownField: 'oops' },\n  userAssertion,\n  undefined,\n  { failOnUnknownFields: true }, // ✗ throws: \"property can't be checked: unknownField\"\n);\n```\n\n### 4. Array Validation\n\n#### Basic Arrays\n\n```typescript\nimport { assertArray, arrayAssertion } from '@fishka/assertions';\n\nassertArray(['a', 'b', 'c'], assertString); // ✓\nassertArray([1, 2, 3], assertNumber); // ✓\n\n// Or create reusable array assertion\nconst stringArrayAssertion = arrayAssertion(assertString);\nassertArray(['x', 'y'], stringArrayAssertion);\n```\n\n#### Array Constraints\n\n```typescript\n// Length constraints\nassertArray([1, 2, 3], assertNumber, { minLength: 2, maxLength: 5 }); // ✓\nassertArray([1], assertNumber, { minLength: 2 }); // ✗ throws\n\n// Uniqueness check\nassertArray(\n  ['a', 'b', 'c'],\n  assertString,\n  { uniqueByIdentity: v => v }, // ✓\n);\n\nassertArray(\n  ['a', 'a', 'b'],\n  assertString,\n  { uniqueByIdentity: v => v }, // ✗ throws: \"array contains non-unique elements\"\n);\n```\n\n#### Arrays in Objects\n\n```typescript\nimport { undefinedOr } from '@fishka/assertions';\n\ninterface User {\n  name: string;\n  tags?: string[];\n}\n\nconst userAssertion: ObjectAssertion<User> = {\n  name: assertString,\n  tags: undefinedOr(arrayAssertion(assertString)),\n};\n```\n\n### 5. Record (Dictionary) Validation\n\n```typescript\nimport { assertRecord, recordAssertion } from '@fishka/assertions';\n\n// Record<string, number>\nassertRecord({ a: 1, b: 2, c: 3 }, assertNumber); // ✓\n\n// With key validation\nassertRecord(\n  { 'user@example.com': 'Alice' },\n  assertString,\n  { keyAssertion: assertEmail }, // validates keys are emails\n);\n\n// Ensure key matches field in value\nassertRecord(\n  {\n    user123: { id: 'user123', name: 'Alice' },\n    user456: { id: 'user456', name: 'Bob' },\n  },\n  objectAssertion({ id: assertString, name: assertString }),\n  { keyField: 'id' }, // ensures record key === value.id\n);\n```\n\n### 6. Custom Assertions\n\n#### Type-checked Custom Assertions\n\n```typescript\nimport { $a } from '@fishka/assertions';\n\n// Create custom assertion with type checking\nconst assertPositive = $a<number>(value => value > 0, 'Must be positive');\n\nassertPositive(42); // ✓\nassertPositive(-5); // ✗ throws: \"Check is failed: Must be positive\"\n```\n\n#### Untyped Custom Assertions\n\n```typescript\nimport { $u } from '@fishka/assertions';\n\nconst assertValidDate = $u(value => value instanceof Date && !isNaN(value.getTime()), 'Must be a valid Date');\n\nassertValidDate(new Date()); // ✓\nassertValidDate(new Date('invalid')); // ✗ throws\n```\n\n#### String Constraints\n\n```typescript\nimport { stringAssertion } from '@fishka/assertions';\n\nconst passwordAssertion = stringAssertion({\n  minLength: 8,\n  maxLength: 64,\n});\n\npasswordAssertion('short'); // ✗ throws: \"length is too small: 5 < 8\"\npasswordAssertion('validpassword'); // ✓\n```\n\n### 7. User Input Validation (Non-throwing)\n\n```typescript\nimport { validateObject } from '@fishka/assertions';\n\nfunction handleFormSubmit(formData: unknown) {\n  const error = validateObject(formData, userAssertion);\n\n  if (error) {\n    // Show error to user\n    console.error('Validation failed:', error);\n    return { success: false, error };\n  }\n\n  // formData is now typed as User\n  saveUser(formData);\n  return { success: true };\n}\n```\n\n### 8. API Validation\n\n#### Option 1: API Request Validation (Express.js Server)\n\nValidate incoming HTTP requests on the server side:\n\n```typescript\nimport express from 'express';\nimport { validateObject, assertObject, type ObjectAssertion } from '@fishka/assertions';\n\ninterface CreateUserRequest {\n  name: string;\n  email: string;\n  age: number;\n  tags?: string[];\n}\n\nconst createUserRequestAssertion: ObjectAssertion<CreateUserRequest> = {\n  name: assertString,\n  email: assertEmail,\n  age: assertNumber,\n  tags: undefinedOr(arrayAssertion(assertString)),\n};\n\nconst app = express();\napp.use(express.json());\n\n// POST /users - Create new user\napp.post('/users', (req, res) => {\n  // Validate request body\n  const error = validateObject(req.body, createUserRequestAssertion);\n  if (error) {\n    return res.status(400).json({ success: false, error: `Invalid request: ${error}` });\n  }\n  // req.body is now typed as CreateUserRequest\n  const user = createUser(req.body);\n  res.status(201).json({ success: true, data: user });\n});\n\n// GET /users/:id - Get user by ID\napp.get('/users/:id', (req, res) => {\n  const userId = req.params.id;\n  // Validate UUID format\n  assertUuid(userId, 'userId');\n  const user = getUserById(userId);\n  res.json({ success: true, data: user });\n});\n```\n\n#### Option 2: API Response Validation (Client-side)\n\nValidate API responses on the client to ensure type safety:\n\n```typescript\ninterface ApiResponse<T> {\n  success: boolean;\n  data: T;\n  error?: string;\n}\n\ninterface User {\n  id: string;\n  name: string;\n  email: string;\n  createdAt: string;\n}\n\ninterface UsersListResponse {\n  users: User[];\n  meta: {\n    total: number;\n    page: number;\n    pageSize: number;\n  };\n}\n\n// Define response assertion\nconst userAssertion: ObjectAssertion<User> = {\n  id: assertUuid,\n  name: assertString,\n  email: assertEmail,\n  createdAt: assertString,\n};\n\nconst usersListResponseAssertion: ObjectAssertion<UsersListResponse> = {\n  users: arrayAssertion(userAssertion),\n  meta: {\n    total: assertNumber,\n    page: assertNumber,\n    pageSize: assertNumber,\n  },\n};\n\n// Client function to fetch users\nasync function fetchUsers(page: number = 1): Promise<UsersListResponse> {\n  const response = await fetch(`/api/users?page=${page}`);\n  const data: unknown = await response.json();\n\n  // Validate response structure\n  assertObject(data, usersListResponseAssertion);\n\n  // Now data is fully typed as UsersListResponse!\n  return data;\n}\n\n// Usage\nconst { users, meta } = await fetchUsers(1);\nconsole.log(`Loaded ${users.length} of ${meta.total} users`);\n```\n\n#### Bonus: Middleware Pattern for Express\n\nCreate reusable validation middleware:\n\n```typescript\nimport { Request, Response, NextFunction } from 'express';\nimport { validateObject, type ObjectAssertion } from '@fishka/assertions';\n\n// Generic validation middleware\nfunction validateBody<T>(assertion: ObjectAssertion<T>) {\n  return (req: Request, res: Response, next: NextFunction) => {\n    const error = validateObject(req.body, assertion);\n\n    if (error) {\n      return res.status(400).json({\n        success: false,\n        error: `Validation failed: ${error}`,\n      });\n    }\n\n    // Body is valid, continue to route handler\n    next();\n  };\n}\n\n// Use the middleware\napp.post('/users', validateBody(createUserRequestAssertion), (req, res) => {\n  // req.body is typed and validated\n  const user = createUser(req.body);\n  res.status(201).json({ success: true, data: user });\n});\n```\n\n## API Reference\n\n### Primitive Assertions\n\n```typescript\nassertString(value: unknown, context?: string): asserts value is string\nassertNumber(value: unknown, context?: string): asserts value is number\nassertBoolean(value: unknown, context?: string): asserts value is boolean\nassertDate(value: unknown, context?: string): asserts value is Date\nassertNonNullable<T>(value: T, context?: string): asserts value is NonNullable<T>\n```\n\n### Format Assertions\n\n```typescript\nassertUuid(value: unknown, context?: string): asserts value is string\nassertHexString(value: unknown, context?: string): asserts value is string\nassertEmail(value: unknown, context?: string): asserts value is string\n```\n\n### Structural Assertions\n\n```typescript\nassertObject<T>(\n  value: unknown,\n  assertion: ObjectAssertion<T>,\n  context?: string,\n  constraints?: { failOnUnknownFields?: boolean; allowedUnknownFieldNames?: string[] }\n): asserts value is T\n\nassertArray<T>(\n  value: unknown,\n  elementAssertion: Assertion<T>,\n  constraints?: { minLength?: number; maxLength?: number; uniqueByIdentity?: (el: T) => string },\n  context?: string\n): asserts value is Array<T>\n\nassertRecord<T>(\n  value: unknown,\n  valueAssertion: Assertion<T>,\n  constraints?: { keyAssertion?: (key: string) => void; keyField?: string },\n  context?: string\n): asserts value is Record<string, T>\n```\n\n### Factory Functions\n\n```typescript\narrayAssertion<T>(elementAssertion: Assertion<T>, constraints?): ValueAssertion<Array<T>>\nrecordAssertion<T>(valueAssertion: Assertion<T>, constraints?): ValueAssertion<Record<string, T>>\nobjectAssertion<T>(objAssertion: ObjectAssertion<T>): ValueAssertion<T>\n\n$a<T>(checkFn: (value: T) => boolean, errorMsg?: string): ValueAssertion<T>\n$u(checkFn: (value: unknown) => boolean, errorMsg?: string): ValueAssertion<unknown>\n\nundefinedOr<T>(assertion: Assertion<T>): Assertion<T | undefined>\nnullOr<T>(assertion: Assertion<T>): Assertion<T | null>\nvalueOr<T>(expectedValue: T, orAssertion: Assertion<T>): Assertion<T>\n\nstringAssertion(constraints: { minLength?: number; maxLength?: number }): ValueAssertion<string>\n```\n\n### Validation Functions (Non-throwing)\n\n```typescript\nvalidateObject<T>(value: unknown, assertion: ObjectAssertion<T>, context?, constraints?): string | undefined\nvalidateArray<T>(value: unknown, elementAssertion: Assertion<T>, constraints?, context?): string | undefined\nvalidateRecord<T>(value: unknown, valueAssertion: Assertion<T>, constraints?, context?): string | undefined\n```\n\n### Type Predicates (Checks)\n\n```typescript\nisString(value: unknown): value is string\nisNumber(value: unknown): value is number\nisBoolean(value: unknown): value is boolean\nisDate(value: unknown): value is Date\nisUuid(value: unknown): value is string\nisHexString(value: unknown): value is string\nisEmail(value: unknown, constraints?): value is string\nisNonNullable<T>(value: T): value is NonNullable<T>\n```\n\n### Core Functions\n\n```typescript\nassertTruthy(value: unknown, error?: string | (() => string)): asserts value\nfail(error?: string | (() => string)): never  // throws assertion error\n```\n\n## Advanced Features\n\n### Error Context Propagation\n\nErrors automatically include the full path to the failing field:\n\n```typescript\nconst data = {\n  user: {\n    profile: {\n      age: 'twenty', // wrong type\n    },\n  },\n};\n\nassertObject(data, userAssertion, 'apiResponse');\n// throws: \"apiResponse.user.profile.age: Not a number <string:twenty>\"\n```\n\n### TypeScript Compile-Time Safety\n\nObjectAssertion is compile-time verified - TypeScript ensures all fields are covered:\n\n```typescript\ninterface User {\n  name: string;\n  age: number;\n  email: string;\n}\n\nconst userAssertion: ObjectAssertion<User> = {\n  name: assertString,\n  age: assertNumber,\n  // ✗ TypeScript error: Property 'email' is missing\n};\n```\n\n### Custom Error Factory\n\nOverride the default error factory for all assertions:\n\n```typescript\nimport { setDefaultAssertionErrorFactory } from '@fishka/assertions';\n\nsetDefaultAssertionErrorFactory((message: string) => {\n  return new CustomError(message);\n});\n```\n\n## Common Patterns\n\n### Validating Configuration Files\n\n```typescript\ninterface AppConfig {\n  port: number;\n  host: string;\n  database: {\n    url: string;\n    poolSize: number;\n  };\n  features: string[];\n}\n\nconst configAssertion: ObjectAssertion<AppConfig> = {\n  port: assertNumber,\n  host: assertString,\n  database: {\n    url: assertString,\n    poolSize: assertNumber,\n  },\n  features: arrayAssertion(assertString),\n};\n\nconst config: unknown = JSON.parse(configFileContents);\nassertObject(config, configAssertion);\n// config is now fully typed and validated\n```\n\n### Form Validation\n\n```typescript\nfunction validateLoginForm(formData: FormData): { error?: string; data?: LoginData } {\n  const input = {\n    email: formData.get('email'),\n    password: formData.get('password'),\n  };\n\n  const error = validateObject(input, loginAssertion);\n  if (error) {\n    return { error };\n  }\n\n  return { data: input };\n}\n```\n\n### Runtime Type Narrowing\n\n```typescript\nfunction processValue(value: unknown) {\n  if (isString(value)) {\n    // TypeScript knows value is string\n    console.log(value.toUpperCase());\n  } else if (isNumber(value)) {\n    // TypeScript knows value is number\n    console.log(value.toFixed(2));\n  }\n}\n```\n\n## Why @fishka/assertions?\n\n- **Type Safety**: Leverages TypeScript's type system for compile-time and runtime safety\n- **Developer Experience**: Clear error messages with full context path\n- **Zero Dependencies**: No external dependencies, minimal bundle size\n- **Flexible**: Choose between throwing assertions or returning error messages\n- **Composable**: Build complex validators from simple building blocks\n- **Battle-tested**: Comprehensive test suite with 111+ tests\n\n## License\n\nMIT\n\n## Contributing\n\nIssues and pull requests are welcome at https://gitea.com/fishka/assertions\n","readmeFilename":"README.md"}