{"id":19257547,"url":"https://github.com/ericvera/betterbe","last_synced_at":"2026-01-31T21:48:36.162Z","repository":{"id":237584922,"uuid":"794839390","full_name":"ericvera/betterbe","owner":"ericvera","description":null,"archived":false,"fork":false,"pushed_at":"2025-08-12T20:13:25.000Z","size":4210,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-08-12T22:11:18.976Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ericvera.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-05-02T04:05:49.000Z","updated_at":"2025-08-12T20:13:28.000Z","dependencies_parsed_at":"2024-05-06T06:31:36.165Z","dependency_job_id":"56f5a984-a697-4660-938a-bd84a05fefbd","html_url":"https://github.com/ericvera/betterbe","commit_stats":null,"previous_names":["ericvera/ohno"],"tags_count":24,"template":false,"template_full_name":"ericvera/ts-lib-template","purl":"pkg:github/ericvera/betterbe","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericvera%2Fbetterbe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericvera%2Fbetterbe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericvera%2Fbetterbe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericvera%2Fbetterbe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ericvera","download_url":"https://codeload.github.com/ericvera/betterbe/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericvera%2Fbetterbe/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28956868,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-31T18:30:42.805Z","status":"ssl_error","status_checked_at":"2026-01-31T18:30:19.593Z","response_time":128,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-09T19:10:36.730Z","updated_at":"2026-01-31T21:48:36.147Z","avatar_url":"https://github.com/ericvera.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# betterbe\n\n**A minimally flexible data validator**\n\n[![github license](https://img.shields.io/github/license/ericvera/betterbe.svg?style=flat-square)](https://github.com/ericvera/betterbe/blob/master/LICENSE)\n[![npm version](https://img.shields.io/npm/v/betterbe.svg?style=flat-square)](https://npmjs.org/package/betterbe)\n\n## Features\n\n- Props are required by default\n- Strict by default (no type coercion)\n- No unknown properties allowed\n- Lightweight with no dependencies\n- TypeScript-first design\n\n## Installation\n\n```bash\n# npm\nnpm install betterbe\n\n# yarn\nyarn add betterbe\n```\n\n## Basic Example\n\n```ts\nimport { boolean, number, object, record, string } from 'betterbe'\n\nconst validateUid = string({\n  minLength: 10,\n  maxLength: 12,\n  alphabet: '0123456789',\n})\n\nconst validateMessage = object({\n  from: object({\n    uid: validateUid,\n  }),\n  message: string({ minLength: 1, maxLength: 280 }),\n  utcTime: number({ integer: true }),\n  urgent: boolean({ required: false }),\n  metadata: record(string({ pattern: /^[a-z_]+$/ }), string(), {\n    required: false,\n  }),\n})\n\n// This is not expected to throw (valid input)\nvalidateMessage.validate({\n  from: { uid: '1234567890' },\n  message: 'Hello, World!',\n  utcTime: 1630000000,\n  metadata: { user_agent: 'Mozilla/5.0', client_version: '1.0.0' },\n})\n\n// This is expected to throw as character `-` is not valid in the uid alphabet\nvalidateMessage.validate({\n  from: { uid: '1234567-90' },\n  message: 'Hello, World!',\n  utcTime: 1630000000,\n})\n```\n\n## API Reference\n\n### String Validator\n\n```ts\nimport { string } from 'betterbe'\n\n// Basic usage\nconst validateName = string()\n\n// With options\nconst validateUsername = string({\n  minLength: 3,\n  maxLength: 20,\n  pattern: /^[a-zA-Z0-9_]+$/,\n  required: true,\n})\n\n// With alphabet restriction\nconst validateDigitCode = string({\n  alphabet: '0123456789',\n  minLength: 6,\n  maxLength: 6,\n})\n\n// With oneOf (enumeration)\nconst validateColor = string({\n  oneOf: ['red', 'green', 'blue'],\n})\n\n// With custom test function\nconst validateEmail = string({\n  test: (value) =\u003e {\n    if (!value.includes('@')) {\n      throw new Error('Invalid email format')\n    }\n  },\n})\n```\n\nOptions:\n\n- `minLength`: Minimum string length\n- `maxLength`: Maximum string length\n- `pattern`: RegExp pattern the string must match\n- `alphabet`: String of allowed characters\n- `required`: Whether the value is required (default: `true`)\n- `test`: Custom validation function\n- `oneOf`: Array of allowed string values (cannot be used with other string options)\n\n### Number Validator\n\n```ts\nimport { number } from 'betterbe'\n\n// Basic usage\nconst validateAge = number()\n\n// With options\nconst validatePositiveInteger = number({\n  min: 1,\n  integer: true,\n})\n\n// With range\nconst validatePercentage = number({\n  min: 0,\n  max: 100,\n})\n\n// Optional number\nconst validateOptionalCount = number({\n  required: false,\n})\n\n// Example: NaN validation\ntry {\n  validateAge.validate(NaN)\n} catch (error) {\n  console.log(error.message) // \"is not a number\"\n}\n```\n\n**Note**: The number validator automatically rejects `NaN` values and will throw a validation error with the message \"is not a number\".\n\nOptions:\n\n- `min`: Minimum value\n- `max`: Maximum value\n- `integer`: Whether the number must be an integer\n- `required`: Whether the value is required (default: `true`)\n\n### Boolean Validator\n\n```ts\nimport { boolean } from 'betterbe'\n\n// Basic usage\nconst validateIsActive = boolean()\n\n// Optional boolean\nconst validateOptionalFlag = boolean({\n  required: false,\n})\n```\n\nOptions:\n\n- `required`: Whether the value is required (default: `true`)\n\n### Array Validator\n\n```ts\nimport { array, string, number } from 'betterbe'\n\n// Array of strings\nconst validateTags = array(string())\n\n// Array of numbers with length constraints\nconst validateScores = array(number(), {\n  minLength: 1,\n  maxLength: 10,\n})\n\n// Array with unique values\nconst validateUniqueIds = array(string(), {\n  unique: true,\n})\n\n// Optional array\nconst validateOptionalItems = array(string(), {\n  required: false,\n})\n\n// With custom test function\nconst validateSortedNumbers = array(number(), {\n  test: (values) =\u003e {\n    for (let i = 1; i \u003c values.length; i++) {\n      if (values[i] \u003c values[i - 1]) {\n        throw new Error('Array must be sorted in ascending order')\n      }\n    }\n  },\n})\n```\n\nOptions:\n\n- `minLength`: Minimum array length\n- `maxLength`: Maximum array length\n- `required`: Whether the value is required (default: `true`)\n- `unique`: Whether array values must be unique (default: `false`)\n- `test`: Custom validation function\n\n### Object Validator\n\n```ts\nimport { object, string, number, boolean } from 'betterbe'\n\n// Basic usage\nconst validateUser = object({\n  name: string(),\n  age: number(),\n  isActive: boolean(),\n})\n\n// Nested objects\nconst validatePost = object({\n  title: string(),\n  content: string(),\n  author: object({\n    id: string(),\n    name: string(),\n  }),\n  published: boolean(),\n})\n\n// Optional object\nconst validateOptionalMetadata = object(\n  {\n    tags: array(string()),\n  },\n  {\n    required: false,\n  },\n)\n\n// With custom test function\nconst validateCredentials = object(\n  {\n    username: string(),\n    password: string(),\n  },\n  {\n    test: (value) =\u003e {\n      if (value.username === value.password) {\n        throw new Error('Username and password cannot be the same')\n      }\n    },\n  },\n)\n```\n\nOptions:\n\n- `required`: Whether the value is required (default: `true`)\n- `test`: Custom validation function\n\n### Record Validator\n\n```ts\nimport { record, string, number, boolean } from 'betterbe'\n\n// Basic usage - validates objects with dynamic keys\nconst validateScores = record(\n  string(), // all keys must be strings\n  number(), // all values must be numbers\n)\n\n// With key validation - only specific keys allowed\nconst validatePermissions = record(\n  string({ oneOf: ['read', 'write', 'admin'] }),\n  boolean(),\n)\n\n// With key pattern validation\nconst validateUserData = record(\n  string({ pattern: /^user_[0-9]+$/ }), // keys like \"user_123\"\n  object({\n    name: string(),\n    age: number(),\n  }),\n)\n\n// Nested records\nconst validateConfiguration = record(\n  string({ pattern: /^[a-z]+$/ }), // lowercase keys only\n  record(string({ oneOf: ['value', 'enabled', 'config'] }), string()),\n)\n\n// Optional record\nconst validateOptionalSettings = record(string(), string(), { required: false })\n\n// With custom test function\nconst validateApiKeys = record(\n  string({ pattern: /^api_/ }),\n  string({ minLength: 32 }),\n  {\n    test: (value) =\u003e {\n      if (Object.keys(value).length === 0) {\n        throw new Error('At least one API key is required')\n      }\n    },\n  },\n)\n```\n\nOptions:\n\n- `required`: Whether the value is required (default: `true`)\n- `test`: Custom validation function\n\n## Error Handling\n\nThe library throws `ValidationError` instances when validation fails. These errors contain:\n\n- `type`: The type of validation that failed (e.g., 'required', 'minLength', 'pattern')\n- `message`: A human-readable error message\n- `meta`: Additional metadata about the validation that failed\n\n### Error Metadata\n\nThe `meta` object includes context information:\n\n- `context`: `'key'` or `'value'` - indicates whether the error is for key or value validation\n- `originalKey`: The key that failed validation (in record validators)\n- `propertyName`: The property name that failed validation (in object validators)\n- `arrayIndex`: The array index where validation failed (in array validators)\n\n### Examples\n\n```ts\nimport { string, record, array, object } from 'betterbe'\n\n// Basic validation error\nconst validateUsername = string({ minLength: 3 })\n\ntry {\n  validateUsername.validate('ab')\n} catch (error) {\n  console.log(error.type) // 'minLength'\n  console.log(error.message) // \"is shorter than expected length 3\"\n  console.log(error.meta) // { minLength: 3, context: 'value' }\n}\n\n// Record key validation error\nconst validateScores = record(string({ pattern: /^[a-z]+$/ }), number())\n\ntry {\n  validateScores.validate({ 'Invalid-Key': 100 })\n} catch (error) {\n  console.log(error.type) // 'pattern'\n  console.log(error.message) // \"key 'Invalid-Key' does not match pattern\"\n  console.log(error.meta) // { pattern: /^[a-z]+$/, context: 'key', originalKey: 'Invalid-Key' }\n}\n\n// Array item validation error\nconst validateNumbers = array(number({ min: 0 }))\n\ntry {\n  validateNumbers.validate([1, -5, 3])\n} catch (error) {\n  console.log(error.type) // 'min'\n  console.log(error.message) // \"'[1]' is less than minimum 0\"\n  console.log(error.meta) // { min: 0, context: 'value', arrayIndex: 1 }\n}\n\n// Object property validation error\nconst validateUser = object({\n  name: string({ maxLength: 10 }),\n  age: number(),\n})\n\ntry {\n  validateUser.validate({ name: 'ThisNameIsTooLong', age: 25 })\n} catch (error) {\n  console.log(error.type) // 'maxLength'\n  console.log(error.message) // \"'name' is longer than expected length 10\"\n  console.log(error.meta) // { maxLength: 10, context: 'value', propertyName: 'name' }\n}\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fericvera%2Fbetterbe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fericvera%2Fbetterbe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fericvera%2Fbetterbe/lists"}