{"id":25454056,"url":"https://github.com/mjancarik/esmj-schema","last_synced_at":"2026-03-13T23:16:13.026Z","repository":{"id":277592175,"uuid":"932928620","full_name":"mjancarik/esmj-schema","owner":"mjancarik","description":"Tiny library for simple schema runtime validation system for JavaScript/TypeScript.","archived":false,"fork":false,"pushed_at":"2026-02-12T16:24:42.000Z","size":132,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-13T00:36:13.188Z","etag":null,"topics":["runtime-validation","schema","schema-validation","typescript"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/mjancarik.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2025-02-14T19:29:59.000Z","updated_at":"2026-02-12T16:30:16.000Z","dependencies_parsed_at":"2025-02-14T19:46:18.743Z","dependency_job_id":"f44fe9db-6cf0-4c54-92b4-5eae728c7b22","html_url":"https://github.com/mjancarik/esmj-schema","commit_stats":null,"previous_names":["mjancarik/esmj-schema"],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/mjancarik/esmj-schema","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mjancarik%2Fesmj-schema","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mjancarik%2Fesmj-schema/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mjancarik%2Fesmj-schema/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mjancarik%2Fesmj-schema/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mjancarik","download_url":"https://codeload.github.com/mjancarik/esmj-schema/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mjancarik%2Fesmj-schema/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30479122,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-13T20:45:58.186Z","status":"ssl_error","status_checked_at":"2026-03-13T20:45:20.133Z","response_time":60,"last_error":"SSL_read: 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":["runtime-validation","schema","schema-validation","typescript"],"created_at":"2025-02-17T23:59:21.058Z","updated_at":"2026-03-13T23:16:13.019Z","avatar_url":"https://github.com/mjancarik.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Schema\n\nThis small library provides a simple schema validation system for JavaScript/TypeScript. The library has basic types with opportunities for extending.\n\n## Table of Contents\n\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Why Use @esmj/schema?](#why-use-esmjschema)\n- [Comparison with Similar Libraries](#comparison-with-similar-libraries)\n- [Usage](#usage)\n  - [Basic Usage](#basic-usage)\n- [Modular Extensions](#modular-extensions)\n  - [String Extensions](#string-extensions-esmjschemastring)\n  - [Number Extensions](#number-extensions-esmjschemanumber)\n  - [Array Extensions](#array-extensions-esmjschemaarray)\n  - [Full Extensions](#full-extensions-esmjschemafull)\n- [API Reference Summary](#api-reference-summary)\n- [Schema Types](#schema-types)\n  - [s.coerce](#scoerce)\n  - [s.cast](#scast)\n- [Schema Methods](#schema-methods)\n  - [parse](#parsevalue-parseoptions)\n  - [safeParse](#safeparsevalue-parseoptions)\n  - [Error Collection with abortEarly](#error-collection-with-abortearly-option)\n- [Extending Schemas](#extending-schemas)\n- [More Examples](#more-examples)\n- [Examples Folder](#examples-folder)\n- [Migration Guide](#migration-guide)\n  - [From Zod](#from-zod)\n  - [From Yup](#from-yup)\n- [License](#license)\n\n## Installation\n\n```sh\nnpm install @esmj/schema\n```\n\n## Quick Start\n\nGet started with `@esmj/schema` in seconds:\n\n```typescript\nimport { s } from '@esmj/schema';\n\n// Define a schema\nconst userSchema = s.object({\n  name: s.string(),\n  age: s.number(),\n  email: s.string().optional()\n});\n\n// Parse data\nconst user = userSchema.parse({\n  name: 'John Doe',\n  age: 30\n});\n\nconsole.log(user);\n// { name: 'John Doe', age: 30 }\n\n// Safe parse with error handling\nconst result = userSchema.safeParse({\n  name: 'Jane',\n  age: 'invalid'\n});\n\nif (result.success) {\n  console.log(result.data);\n} else {\n  console.error(result.error.message);\n}\n```\n\n**With Extensions:**\n\n```typescript\nimport { s } from '@esmj/schema/full';\n\nconst schema = s.object({\n  username: s.string().trim().toLowerCase().min(3).max(20),\n  age: s.number().int().positive().min(18),\n  tags: s.array(s.string()).min(1).unique()\n});\n\nconst result = schema.parse({\n  username: '  JohnDoe  ',\n  age: 25,\n  tags: ['developer', 'typescript']\n});\n// { username: 'johndoe', age: 25, tags: ['developer', 'typescript'] }\n```\n\n## Why Use `@esmj/schema`?\n\n`@esmj/schema` is a lightweight and flexible schema validation library designed for developers who need a simple yet powerful way to validate and transform data. Here are some reasons to choose this package:\n\n1. **TypeScript First**: Built with TypeScript in mind, it provides strong type inference—even for deeply nested and complex schemas.\n2. **Extensibility**: Easily extend the library with custom logic, refinements, and preprocessors using the `extend` function.\n3. **Rich Features**: Includes advanced features like preprocessing, transformations, piping, refinements, and robust error collection (`abortEarly`), which are not always available in similar libraries.\n4. **Actionable Error Handling**: Collect all validation errors at once for better debugging and user experience, with clear and consistent error structures.\n5. **Lightweight**: No dependencies and a small footprint make it ideal for projects where performance and simplicity are key.\n6. **Customizable**: Offers fine-grained control over validation, error handling, and schema composition.\n7. **Performance**: Optimized for speed, making it one of the fastest schema validation libraries available.\n8. **Modular**: Import only what you need with separate string, number, and array extension modules to minimize bundle size.\n\n### Performance Highlights\n\n- **Schema Creation**: Create schemas at up to 4 370 618 ops/s (0.23 μs latency) with @sinclair/typebox, or 736 810 ops/s (1.36 μs latency) with @esmj/schema. Superstruct and @esmj/schema are also among the fastest for schema creation.\n- **Parsing**: Parse data at up to 4 627 714 ops/s (0.22 μs latency) with @zod/mini (note: @zod/mini was observed to consume 200% CPU, while other libraries used only 100% CPU), or 3 142 587 ops/s (0.32 μs latency) with @esmj/schema. ArkType and effect/Schema also show strong parsing throughput.\n- **Error Handling**: Efficiently manage errors at up to 2 428 049 ops/s (0.41 μs latency) with @esmj/schema, or 1 386 616 ops/s (0.72 μs latency) with @zod/mini.\n\nThese performance metrics make `@esmj/schema` an excellent choice for both frontend and backend applications where speed and efficiency are critical.\n\n## Comparison with Similar Libraries\n\nWhen choosing a schema validation library, bundle size can be an important factor, especially for frontend applications where minimizing JavaScript size is critical. Here's how `@esmj/schema` compares to other popular libraries:\n\n| Library           | Bundle Size (minified + gzipped) |\n|-------------------|---------------------------------|\n| `@esmj/schema`    | `~1.6 KB`                       |\n| Superstruct       | ~3.2 KB                         |\n| @sinclair/typebox | ~11.7 KB                        |\n| Yup               | ~12.2 KB                        |\n| Zod@3             | ~13 KB                          |\n| @zod/mini         | ~20.5 KB                        |\n| Joi               | ~40.4 KB                        |\n| Zod@4             | ~40.8 KB                        |\n| ArkType           | ~41.8 KB                        |\n| Effect/Schema     | ~115.5 KB                       |\n\n### Performance Comparison\n\n*All benchmarks were measured on Node.js v24.1.0.*\n\n#### Schema Creation Performance\n\n| Library           | Throughput average (ops/s)      | Latency average (μs)      |\n|-------------------|-------------------------------:|-------------------------:|\n| @esmj/schema      | 736 810.12 ± 3.03%            | 1.36 ± 3.24%            |\n| Zod@3             | 112 575.50 ± 0.86%            | 8.88 ± 0.87%            |\n| @zod/mini         | 23 456.07 ± 1.26%             | 42.64 ± 1.28%           |\n| Yup               | 75 051.06 ± 4.38%             | 13.36 ± 4.41%           |\n| Superstruct       | 509 401.06 ± 0.80%            | 1.96 ± 0.80%            |\n| Joi               | 42 455.28 ± 1.27%             | 23.56 ± 1.30%           |\n| `@sinclair/typebox` | `4 370 618.49 ± 1.23%`      | `0.23 ± 1.23%`          |\n| ArkType           | 16 282.69 ± 4.14%             | 61.61 ± 4.38%           |\n| effect/Schema     | 24 919.15 ± 4.31%             | 40.31 ± 4.78%           |\n\n#### Parsing Performance\n\n| Library           | Throughput average (ops/s)      | Latency average (μs)      |\n|-------------------|-------------------------------:|-------------------------:|\n| @esmj/schema      | 3 142 587.31 ± 0.97%          | 0.32 ± 0.99%            |\n| zod@3             | 1 018 777.24 ± 0.64%          | 0.98 ± 0.65%            |\n| `@zod/mini`       | `4 627 714.90 ± 2.23%`        | `0.22 ± 2.36%`          |\n| Yup               | 108 361.49 ± 0.50%            | 9.23 ± 0.51%            |\n| Superstruct       | 252 904.42 ± 2.20%            | 3.96 ± 2.44%            |\n| Joi               | 346 094.49 ± 0.65%            | 2.89 ± 0.65%            |\n| @sinclair/typebox | 228 711.62 ± 2.03%            | 4.38 ± 2.23%            |\n| ArkType           | 1 677 066.00 ± 0.58%          | 0.60 ± 0.59%            |\n| effect/Schema     | 1 060 056.14 ± 0.61%          | 0.94 ± 0.61%            |\n\n#### Error Handling Performance\n\n| Library           | Throughput average (ops/s)      | Latency average (μs)      |\n|-------------------|-------------------------------:|-------------------------:|\n| `@esmj/schema`    | `2 428 049.34 ± 0.54%`        | `0.41 ± 0.53%`          |\n| zod@3             | 641 504.22 ± 3.67%            | 1.57 ± 4.38%            |\n| @zod/mini         | 1 386 616.61 ± 0.60%          | 0.72 ± 0.60%            |\n| Yup               | 98 904.30 ± 0.61%             | 10.11 ± 0.61%           |\n| Superstruct       | 122 782.09 ± 1.03%            | 8.15 ± 1.03%            |\n| Joi               | 271 301.11 ± 1.58%            | 3.69 ± 1.59%            |\n| @sinclair/typebox | 228 734.49 ± 0.55%            | 4.37 ± 0.56%            |\n| ArkType           | 258 685.33 ± 1.23%            | 3.87 ± 1.23%            |\n| effect/Schema     | 165 753.69 ± 0.99%            | 6.03 ± 1.00%            |\n\n**Note:** During the performance tests, `@zod/mini` was observed to consume 200% CPU, while other libraries used only 100% CPU. This may affect the interpretation of the results, especially in multi-threaded environments.\n\n## Usage\n\n### Basic Usage\n\n```typescript\nimport { s, type Infer} from '@esmj/schema';\n\nconst schema = s.object({\n  username: s.string().optional().refine((val) =\u003e val.length \u003c= 255, {\n    message: \"Username can't be more than 255 characters\",\n  }),\n  password: s.string().default('unknown'),\n  birthday: s.preprocess((value) =\u003e new Date(value), s.date()),\n  account: s.string().default('0').transform((value) =\u003e Number.parseInt(value)).pipe(s.number()),\n  money: s.number(),\n  address: s.object({\n    street: s.string(),\n    city: s.string().optional(),\n  }).default({ street: 'unknown' }),\n  records: s.array(s.object({ name: s.string() })).default([]),\n});\n\ntype schemaType = Infer\u003ctypeof schema\u003e;\n\nconst result = schema.parse({\n  username: 'john_doe',\n  birthday: '2000-01-01T23:59:59.000Z',\n  address: { city: 'New York' },\n  money: 100,\n});\n\nconsole.log(result);\n// {\n//   username: 'john_doe',\n//   password: 'unknown',\n//   birthday: Date('2000-01-01T23:59:59.000Z'),\n//   account: 0,\n//   money: 100,\n//   address: {\n//     street: 'unknown',\n//     city: 'New York',\n//   },\n//   records: [],\n// }\n```\n\n## Modular Extensions\n\n`@esmj/schema` provides modular extensions that can be imported individually or all together, allowing you to include only the validation helpers you need.\n\n### Import Options\n\n```typescript\n// Minimal version (core only, ~1.5 KB)\nimport { s } from '@esmj/schema';\n\n// Full version (all extensions included, ~4 KB)\nimport { s } from '@esmj/schema/full';\n\n// String extensions only\nimport { s } from '@esmj/schema/string';\n\n// Number extensions only\nimport { s } from '@esmj/schema/number';\n\n// Array extensions only\nimport { s } from '@esmj/schema/array';\n\n// Mix and match (side-effect imports)\nimport '@esmj/schema/string';\nimport '@esmj/schema/number';\nimport { s } from '@esmj/schema';\n```\n\n### Bundle Size Impact\n\n- **Core only** (`@esmj/schema`): ~1.5 KB gzipped\n- **String extensions** (`@esmj/schema/string`): +~0.8 KB\n- **Number extensions** (`@esmj/schema/number`): +~0.6 KB\n- **Array extensions** (`@esmj/schema/array`): +~0.5 KB\n- **Full** (`@esmj/schema/full`): ~4 KB gzipped (all extensions)\n\n**Recommendation:** Import only the extensions you need to minimize bundle size.\n\n### String Extensions (`@esmj/schema/string`)\n\nString extensions provide common validation and transformation methods for string schemas.\n\n```typescript\nimport { s } from '@esmj/schema/string';\n\nconst userSchema = s.object({\n  username: s.string()\n    .trim()              // Remove whitespace\n    .toLowerCase()        // Convert to lowercase\n    .min(3)              // Minimum 3 characters\n    .max(20)             // Maximum 20 characters\n    .startsWith('user_'), // Must start with 'user_'\n  \n  email: s.string()\n    .trim()\n    .toLowerCase()\n    .includes('@')        // Must contain '@'\n});\n\nuserSchema.parse({\n  username: '  USER_John  ',\n  email: '  John@Example.com  '\n});\n// ✓ { username: 'user_john', email: 'john@example.com' }\n```\n\n**Available String Methods:**\n\n- **Length validations**: `min(length)`, `max(length)`, `length(exact)`, `nonEmpty()`\n- **Pattern validations**: `startsWith(prefix)`, `endsWith(suffix)`, `includes(substring)`\n- **Transformations**: `trim()`, `toLowerCase()`, `toUpperCase()`, `padStart(length, char)`, `padEnd(length, char)`, `replace(search, replace)`\n\n### Number Extensions (`@esmj/schema/number`)\n\nNumber extensions provide validation methods for number schemas including range checks and type validations.\n\n```typescript\nimport { s } from '@esmj/schema/number';\n\nconst productSchema = s.object({\n  price: s.number()\n    .positive()           // Must be positive\n    .min(0.01)           // Minimum value\n    .max(999999.99),     // Maximum value\n  \n  quantity: s.number()\n    .int()               // Must be integer\n    .positive()\n    .min(1)\n    .max(1000),\n  \n  discount: s.number()\n    .min(0)\n    .max(100)\n    .multipleOf(5)       // Must be multiple of 5\n});\n\nproductSchema.parse({\n  price: 29.99,\n  quantity: 5,\n  discount: 10\n});\n// ✓ { price: 29.99, quantity: 5, discount: 10 }\n```\n\n**Available Number Methods:**\n\n- **Range validations**: `min(value)`, `max(value)`, `positive()`, `negative()`\n- **Type validations**: `int()`, `float()`, `multipleOf(value)`, `finite()`\n\n### Array Extensions (`@esmj/schema/array`)\n\nArray extensions provide validation and transformation methods for array schemas.\n\n```typescript\nimport { s } from '@esmj/schema/array';\n\nconst tagsSchema = s.object({\n  tags: s.array(s.string())\n    .min(1)              // At least 1 item\n    .max(5)              // At most 5 items\n    .unique()            // All items must be unique\n});\n\ntagsSchema.parse({\n  tags: ['javascript', 'typescript', 'node']\n});\n// ✓ { tags: ['javascript', 'typescript', 'node'] }\n```\n\n**Available Array Methods:**\n\n- **Size validations**: `min(length)`, `max(length)`, `length(exact)`, `nonEmpty()`\n- **Content validations**: `unique()`\n- **Transformations**: `sort()`, `reverse()`\n\n### Full Extensions (`@esmj/schema/full`)\n\nThe full version includes all string, number, and array extensions in a single import.\n\n```typescript\nimport { s } from '@esmj/schema/full';\n\nconst productSchema = s.object({\n  // String extensions\n  name: s.string()\n    .trim()\n    .min(3)\n    .max(100),\n  \n  sku: s.string()\n    .toUpperCase()\n    .length(8)\n    .startsWith('PROD'),\n  \n  // Number extensions\n  price: s.number()\n    .positive()\n    .min(0.01)\n    .max(999999.99),\n  \n  stock: s.number()\n    .int()\n    .min(0),\n  \n  // Array extensions\n  categories: s.array(s.string())\n    .min(1)\n    .max(5)\n    .unique(),\n  \n  dimensions: s.array(s.number().positive())\n    .length(3) // [length, width, height]\n});\n```\n\n**Custom Error Messages:**\n\nAll extension methods support custom error messages:\n\n```typescript\nconst schema = s.object({\n  username: s.string().min(3, {\n    message: 'Username is too short! Please use at least 3 characters.'\n  }),\n  age: s.number().positive({\n    message: 'Age must be a positive number.'\n  }),\n  tags: s.array(s.string()).unique({\n    message: 'Duplicate tags are not allowed.'\n  })\n});\n```\n\n## API Reference Summary\n\n### Core Types\n\n- `s.string()` - String validation\n- `s.number()` - Number validation\n- `s.boolean()` - Boolean validation\n- `s.date()` - Date validation\n- `s.object(def)` - Object validation\n- `s.array(def)` - Array validation\n- `s.literal(value)` - Literal value validation\n- `s.enum(values)` - Enum validation\n- `s.union(schemas)` - Union validation\n- `s.any()` - Any type\n- `s.null()` - Null type\n- `s.undefined()` - Undefined type\n- `s.unknown()` - Unknown type\n\n### Modifiers\n\n- `.optional()` - Makes field optional\n- `.nullable()` - Makes field nullable\n- `.nullish()` - Makes field optional and nullable\n- `.default(value)` - Sets default value\n- `.catch(value)` - Returns fallback value on any parse failure\n\n### Coerce\n\n- `s.coerce.string()` - Coerce any value to string, then validate\n- `s.coerce.number()` - Coerce any value to number, then validate (fails for NaN)\n- `s.coerce.boolean()` - Coerce any value to boolean, then validate\n- `s.coerce.date()` - Coerce any value to Date, then validate (fails for invalid dates)\n\n### Cast\n\nSemantic casting that understands common string representations and rejects ambiguous inputs:\n\n- `s.cast.boolean()` - Cast to boolean; understands `'true'/'false'`, `'yes'/'no'`, `'on'/'off'`, `'1'/'0'` (case-insensitive); rejects `null`/`undefined`/unrecognised strings\n- `s.cast.number()` - Cast to number; trims whitespace from strings, accepts booleans (`true`→1, `false`→0); rejects `null`/`undefined`/empty strings\n- `s.cast.string()` - Cast to string; accepts strings, finite numbers, and booleans; rejects `null`/`undefined`/objects/`NaN`/`Infinity`\n- `s.cast.date()` - Cast to Date; accepts ISO strings, finite timestamps, and existing Dates; rejects `null`/`undefined`/booleans/empty strings\n- `s.cast.json(schema)` - Parse a JSON string and validate the result against a schema; non-string inputs pass through directly; malformed JSON returns a proper validation failure\n\n### Transformations\n\n- `.transform(fn)` - Transform value\n- `s.preprocess(fn, schema)` - Preprocess before validation\n- `.pipe(schema)` - Pipe to another schema\n- `.refine(fn, opts)` - Custom validation\n\n### String Extensions\n\nAvailable when importing from `@esmj/schema/string` or `@esmj/schema/full`:\n\n**Length Validations:**\n- `.min(n)` - Minimum length\n- `.max(n)` - Maximum length\n- `.length(n)` - Exact length\n- `.nonEmpty()` - Non-empty string\n\n**Pattern Validations:**\n- `.startsWith(prefix)` - Must start with prefix\n- `.endsWith(suffix)` - Must end with suffix\n- `.includes(substring)` - Must contain substring\n\n**Transformations:**\n- `.trim()` - Remove whitespace\n- `.toLowerCase()` - Convert to lowercase\n- `.toUpperCase()` - Convert to uppercase\n- `.padStart(length, char)` - Pad start\n- `.padEnd(length, char)` - Pad end\n- `.replace(search, replace)` - Replace text\n\n### Number Extensions\n\nAvailable when importing from `@esmj/schema/number` or `@esmj/schema/full`:\n\n**Range Validations:**\n- `.min(n)` - Minimum value\n- `.max(n)` - Maximum value\n- `.positive()` - Must be positive\n- `.negative()` - Must be negative\n\n**Type Validations:**\n- `.int()` - Must be integer\n- `.float()` - Must be float (non-integer)\n- `.multipleOf(n)` - Must be multiple of n\n- `.finite()` - Must be finite\n\n### Array Extensions\n\nAvailable when importing from `@esmj/schema/array` or `@esmj/schema/full`:\n\n**Size Validations:**\n- `.min(n)` - Minimum length\n- `.max(n)` - Maximum length\n- `.length(n)` - Exact length\n- `.nonEmpty()` - Non-empty array\n\n**Content Validations:**\n- `.unique()` - All items must be unique\n\n**Transformations:**\n- `.sort()` - Sort array\n- `.reverse()` - Reverse array\n\n### Schema Types\n\n#### `s.string(options?)`\n\nCreates a string schema. You can optionally pass `options` to customize error messages.\n\n- **`message`**: Can be either a constant string or a function `(value) =\u003e string`.\n\n```typescript\nconst stringSchema = s.string({\n  message: 'This is a constant error message.',\n});\n\nconst stringSchemaFunc = s.string({\n  message: (value) =\u003e `Custom error: \"${value}\" is not a valid string.`,\n});\n```\n\n#### `s.number(options?)`\n\nCreates a number schema. You can optionally pass `options` to customize error messages.\n\n- **`message`**: Can be either a constant string or a function `(value) =\u003e string`.\n\n```typescript\nconst numberSchema = s.number({\n  message: 'This is a constant error message.',\n});\n\nconst numberSchemaFunc = s.number({\n  message: (value) =\u003e `Custom error: \"${value}\" is not a valid number.`,\n});\n```\n\n#### `s.boolean(options?)`\n\nCreates a boolean schema. You can optionally pass `options` to customize error messages.\n\n- **`message`**: Can be either a constant string or a function `(value) =\u003e string`.\n\n```typescript\nconst booleanSchema = s.boolean({\n  message: 'This is a constant error message.',\n});\n\nconst booleanSchemaFunc = s.boolean({\n  message: (value) =\u003e `Custom error: \"${value}\" is not a valid boolean.`,\n});\n```\n\n#### `s.date(options?)`\n\nCreates a date schema. You can optionally pass `options` to customize error messages.\n\n- **`message`**: Can be either a constant string or a function `(value) =\u003e string`.\n\n```typescript\nconst dateSchema = s.date({\n  message: 'This is a constant error message.',\n});\n\nconst dateSchemaFunc = s.date({\n  message: (value) =\u003e `Custom error: \"${value}\" is not a valid date.`,\n});\n```\n\n#### `s.object(definition, options?)`\n\nCreates an object schema with the given definition. You can optionally pass `options` to customize error messages.\n\n- **`message`**: Can be either a constant string or a function `(value) =\u003e string`.\n\n```typescript\nconst objectSchema = s.object(\n  {\n    key: s.string(),\n    value: s.number(),\n  },\n  {\n    message: 'This is a constant error message.',\n  },\n);\n\nconst objectSchemaFunc = s.object(\n  {\n    key: s.string(),\n    value: s.number(),\n  },\n  {\n    message: (value) =\u003e `Custom error: \"${JSON.stringify(value)}\" is not a valid object.`,\n  },\n);\n```\n\n#### `s.array(definition, options?)`\n\nCreates an array schema with the given item definition. You can optionally pass `options` to customize error messages.\n\n- **`message`**: Can be either a constant string or a function `(value) =\u003e string`.\n\n```typescript\nconst arraySchema = s.array(s.string(), {\n  message: 'This is a constant error message.',\n});\n\nconst arraySchemaFunc = s.array(s.string(), {\n  message: (value) =\u003e `Custom error: \"${JSON.stringify(value)}\" is not a valid array.`,\n});\n```\n\n#### `s.enum(values, options?)`\n\nCreates an enum schema that validates against a predefined set of string values. You can optionally pass `options` to customize error messages.\n\n- **`message`**: Can be either a constant string or a function `(value) =\u003e string`.\n\n```typescript\nconst enumSchema = s.enum(['admin', 'user', 'guest'], {\n  message: 'This is a constant error message.',\n});\n\nconst enumSchemaFunc = s.enum(['admin', 'user', 'guest'], {\n  message: (value) =\u003e `Custom error: \"${value}\" is not a valid enum value.`,\n});\n```\n\n#### `s.literal(value, options?)`\n\nCreates a literal schema that validates against an exact value. The value can be a string, number, or boolean. This is useful for discriminated unions, API response types, and strict value validation. You can optionally pass `options` to customize error messages.\n\n- **`message`**: Can be either a constant string or a function `(value) =\u003e string`.\n\n```typescript\n// String literal\nconst adminSchema = s.literal('admin');\nadminSchema.parse('admin'); // ✅ 'admin'\nadminSchema.parse('user');  // ❌ throws error\n\n// Number literal\nconst statusCode = s.literal(200);\nstatusCode.parse(200); // ✅ 200\nstatusCode.parse(404); // ❌ throws error\n\n// Boolean literal\nconst enabled = s.literal(true);\nenabled.parse(true);  // ✅ true\nenabled.parse(false); // ❌ throws error\n\n// Custom error message\nconst typeSchema = s.literal('success', {\n  message: 'Response type must be \"success\"',\n});\n\n// Custom error function\nconst versionSchema = s.literal(1, {\n  message: (value) =\u003e `API version must be 1, received ${value}`,\n});\n\n// Discriminated unions with literal\nconst responseSchema = s.union([\n  s.object({\n    type: s.literal('success'),\n    data: s.string(),\n  }),\n  s.object({\n    type: s.literal('error'),\n    error: s.string(),\n  }),\n]);\n\n// Using multiple literals in union (similar to enum but with type inference)\nconst roleSchema = s.union([\n  s.literal('admin'),\n  s.literal('user'),\n  s.literal('guest'),\n]);\n```\n\n**Common Use Cases:**\n\n- **Discriminated Unions**: Use literal types to distinguish between different object shapes\n- **API Response Types**: Validate exact status codes or response types\n- **Configuration Flags**: Validate boolean flags or specific string values\n- **Type Guards**: Create strict type validation for specific values\n\n#### `s.union(definitions, options?)`\n\nCreates a schema that validates against multiple schemas (a union of schemas). The value must match at least one of the provided schemas. You can optionally pass `options` to customize error messages.\n\n- **`message`**: Can be either a constant string or a function `(value) =\u003e string`.\n\n```typescript\nconst schema = s.union([\n  s.string(),\n  s.number(),\n  s.boolean(),\n], {\n  message: 'This is a constant error message.',\n});\n\nconst schemaFunc = s.union([\n  s.string(),\n  s.number(),\n  s.boolean(),\n], {\n  message: (value) =\u003e `Custom error: \"${value}\" does not match any of the union schemas.`,\n});\n```\n\n#### `s.any()`\n\nCreates a schema that accepts any value.\n\n```typescript\nconst anySchema = s.any();\n```\n\n#### `s.preprocess(callback, schema)`\n\nCreates a schema that preprocesses the input value using the provided callback before validating it with the given schema.\n\n```typescript\nconst preprocessSchema = s.preprocess((value) =\u003e new Date(value), s.date());\n```\n\n#### `s.coerce`\n\nThe `coerce` namespace applies a native JS constructor to the input **before** validation.\nUnlike `s.preprocess`, you don't need to write the conversion yourself, and coerce methods\nprovide clear, specific error messages when coercion produces an invalid result.\n\n| Method | Coercion applied | Fails when |\n|---|---|---|\n| `s.coerce.string(options?)` | `String(v)` | Never — `String()` always succeeds |\n| `s.coerce.number(options?)` | `Number(v)` | Result is `NaN` (e.g. `'bad'`, `undefined`) |\n| `s.coerce.boolean(options?)` | `Boolean(v)` | Never — `Boolean()` always succeeds |\n| `s.coerce.date(options?)` | `new Date(v)` | Result is an invalid Date (e.g. `'garbage'`) |\n\n\u003e **Note:** `Boolean('false')` is `true` because `'false'` is a non-empty string. This matches JavaScript semantics.\n\n```typescript\ns.coerce.number().parse('42');         // 42\ns.coerce.number().parse(true);         // 1\ns.coerce.number().parse('bad');        // throws: Cannot coerce \"NaN\" to a valid number.\n\ns.coerce.string().parse(123);          // '123'\ns.coerce.string().parse(null);         // 'null'\n\ns.coerce.boolean().parse(0);           // false\ns.coerce.boolean().parse('false');     // true — non-empty string!\n\ns.coerce.date().parse('2024-01-01');   // Date object\ns.coerce.date().parse('garbage');      // throws: Cannot coerce \"Invalid Date\" to a valid date.\n\n// All schema methods chain normally after coerce:\ns.coerce.number().refine((v) =\u003e v \u003e 0, { message: 'Must be positive' }).parse('5'); // 5\n\n// Custom error message:\ns.coerce.number({ message: 'Expected a numeric value' }).parse('bad'); // throws: Expected a numeric value\n```\n\n#### `s.cast`\n\nProgrammer-friendly semantic casting. Unlike `s.coerce` (raw JS constructors), `s.cast` understands\ncommon string representations and rejects ambiguous inputs like `null`, `undefined`, and empty strings.\n\n| Method | Accepted inputs | Rejects |\n|---|---|---|\n| `s.cast.string(options?)` | strings, finite numbers, booleans | `null`, `undefined`, objects, `NaN`, `Infinity` |\n| `s.cast.number(options?)` | numbers (incl. booleans `true`/`false`→1/0), trimmed numeric strings | `null`, `undefined`, empty strings, non-numeric strings |\n| `s.cast.boolean(options?)` | booleans, `1`/`0`, `'true'/'false'`, `'yes'/'no'`, `'on'/'off'`, `'1'/'0'` | `null`, `undefined`, unrecognised strings, other numbers |\n| `s.cast.date(options?)` | `Date` objects, ISO strings, finite integer timestamps | `null`, `undefined`, booleans, empty strings, invalid date strings |\n| `s.cast.json(schema, options?)` | JSON strings (parsed), any non-string value (pass-through) | malformed JSON strings |\n\n**Key differences from `s.coerce`:**\n\n| Input | `s.coerce.boolean()` | `s.cast.boolean()` |\n|---|---|---|\n| `'false'` | `true` (non-empty string!) | `false` |\n| `'yes'` / `'no'` | `true` / `true` | `true` / `false` |\n| `null` | `false` | throws |\n\n| Input | `s.coerce.number()` | `s.cast.number()` |\n|---|---|---|\n| `null` | `0` | throws |\n| `''` | `0` | throws |\n\n| Input | `s.coerce.string()` | `s.cast.string()` |\n|---|---|---|\n| `null` | `'null'` | throws |\n| `undefined` | `'undefined'` | throws |\n\n```typescript\n// boolean\ns.cast.boolean().parse('false');     // false — unlike coerce!\ns.cast.boolean().parse('yes');       // true\ns.cast.boolean().parse('on');        // true\ns.cast.boolean().parse('OFF');       // false (case-insensitive)\ns.cast.boolean().parse(1);           // true\ns.cast.boolean().parse(0);           // false\ns.cast.boolean().parse('hello');     // throws: Cannot cast \"hello\" to boolean...\ns.cast.boolean().parse(null);        // throws\n\n// number\ns.cast.number().parse('42');         // 42\ns.cast.number().parse(' 3.14 ');     // 3.14 — trims whitespace\ns.cast.number().parse(true);         // 1\ns.cast.number().parse(false);        // 0\ns.cast.number().parse(null);         // throws: Cannot cast \"null\" to a number...\ns.cast.number().parse('');           // throws\n\n// string\ns.cast.string().parse(123);          // '123'\ns.cast.string().parse(true);         // 'true'\ns.cast.string().parse(false);        // 'false'\ns.cast.string().parse(null);         // throws: Cannot cast \"null\" to string...\ns.cast.string().parse(NaN);          // throws\n\n// date\ns.cast.date().parse('2024-01-01');   // Date object\ns.cast.date().parse(1704067200000);  // Date object\ns.cast.date().parse(null);           // throws: Cannot cast \"null\" to a valid date.\ns.cast.date().parse(true);           // throws\n\n// All schema methods chain normally:\ns.cast.number().refine((v) =\u003e v \u003e 0, { message: 'Must be positive' }).parse('5'); // 5\n\n// Custom error message:\ns.cast.boolean({ message: 'Must be a boolean flag' }).parse('maybe'); // throws: Must be a boolean flag\n\n// json\ns.cast.json(s.object({ name: s.string() })).parse('{\"name\":\"Alice\"}'); // { name: 'Alice' }\ns.cast.json(s.array(s.number())).parse('[1,2,3]');                     // [1, 2, 3]\ns.cast.json(s.object({ name: s.string() })).parse({ name: 'Alice' }); // { name: 'Alice' } — pass-through\ns.cast.json(s.number()).safeParse('not json');                         // { success: false, error: ... }\ns.cast.json(s.number(), { message: 'Invalid JSON' }).parse('bad');     // throws: Invalid JSON\n```\n\n### Schema Methods\n\n#### `parse(value, parseOptions?)`\n\nParses the given value according to the schema.\n\n```typescript\nconst result = stringSchema.parse('hello');\n```\n\n#### `safeParse(value, parseOptions?)`\n\nSafely parses the given value according to the schema, returning a success or error result.\n\n```typescript\nconst result = stringSchema.safeParse('hello'); \n// { success: true, data: 'hello' }\n\nconst errorResult = stringSchema.safeParse(123); \n// { success: false, error: { message: 'The value \"123\" must be type of string but is type of \"number\".' } }\n\n// Collect all errors (not just the first)\nconst allErrorsResult = stringSchema.safeParse(123, { abortEarly: false });\nconsole.log(allErrorsResult.errors); // Array of all errors\n```\n\n**Note:** The `error` returned by `safeParse` is not a native `Error` instance. Instead, it is a plain object with the following structure:\n\n```typescript\ntype ErrorStructure = {\n  message: string;\n  cause?: {\n    key?: string;\n  };\n};\n```\n\nThis allows for easier serialization and debugging but may require additional handling if you expect a native `Error` instance.\n\n#### `optional()`\n\nMakes the schema optional.\n\n```typescript\nconst optionalSchema = stringSchema.optional();\n```\n\n#### `nullable()`\n\nMakes the schema nullable.\n\n```typescript\nconst nullableSchema = stringSchema.nullable();\n```\n\n#### `nullish()`\n\nMakes the schema nullish (nullable and optional).\n\n```typescript\nconst nullishSchema = stringSchema.nullish();\n```\n\n#### `default(defaultValue)`\n\nSets a default value for the schema.\n\n```typescript\nconst defaultSchema = stringSchema.default('default value');\n```\n\n#### `catch(catchValue)`\n\nReturns a fallback value whenever parsing fails, instead of throwing or returning an error.\nUnlike `default()` which only fires when the input is `undefined`, `catch()` fires on **any** validation failure.\n\nThe fallback can be a static value or a function that receives a context object `{ input, error }`:\n- `input` — the original raw input value\n- `error` — the `ErrorStructure` with the failure message\n\n**Note:** The fallback value is returned as-is without re-validation. `catch()` only intercepts failures from schemas and refinements placed **before** it in the chain.\n\n```typescript\n// Static fallback\nconst schema = s.string().catch('unknown');\nschema.parse(123);       // 'unknown'\nschema.parse('hello');   // 'hello'\n\n// Function fallback with context\nconst schema2 = s.number().catch((ctx) =\u003e {\n  console.warn(`Invalid input: ${ctx.input} — ${ctx.error.message}`);\n  return 0;\n});\nschema2.parse('bad');    // 0\n\n// Distinction from default()\ns.string().catch('fallback').parse(null);    // 'fallback' — catch fires for null\ns.string().default('fallback').parse(null);  // throws — default does not fire for null\n```\n\n#### `transform(callback)`\n\nTransforms the parsed value using the provided callback.\n\n```typescript\nconst transformedSchema = s.string().transform((value) =\u003e value.toUpperCase());\n```\n\n#### `pipe(schema)`\n\nPipes the output of one schema into another schema for further validation or transformation.\n\n```typescript\nconst pipedSchema = s.string().pipe(s.number());\n```\n\n#### `refine(validation, { message })`\n\nAdds a refinement to the schema with a custom validation function and error message.\n\n```typescript\nconst refinedSchema = s.string().refine((val) =\u003e val.length \u003c= 255, {\n  message: \"String can't be more than 255 characters\",\n});\n```\n\n#### Error Collection with `abortEarly` Option\n\nBoth `parse` and `safeParse` accept an optional second argument:\n`parseOptions: { abortEarly?: boolean }`\n\n- **`abortEarly`** (default: `true`):\n  If `true`, validation stops at the first error (previous behavior).\n  If `false`, all validation errors are collected and returned in the `errors` array.\n\n**Example:**\n\n```typescript\nconst schema = s.object({\n  name: s.string(),\n  age: s.number(),\n  email: s.string()\n});\n\n// Default behavior (abortEarly: true)\nconst result1 = schema.safeParse({\n  name: 123,\n  age: 'not a number',\n  email: 42\n});\nconsole.log(result1.success); // false\nconsole.log(result1.errors.length); // 1\n\n// Collect all errors (abortEarly: false)\nconst result2 = schema.safeParse({\n  name: 123,\n  age: 'not a number',\n  email: 42\n}, { abortEarly: false });\nconsole.log(result2.success); // false\nconsole.log(result2.errors.length); // 3\n```\n\n**Error Result Structure:**\n\n- `error`: The first error encountered (for compatibility)\n- `errors`: Array of all errors (when `abortEarly: false`)\n\n**Note:**  \nThe `abortEarly` option is propagated through nested schemas, arrays, unions, and refinements.  \nThis means you get all errors from deeply nested structures when using `{ abortEarly: false }`.\n\n**Example Output:**\n\n```json\n{\n  \"success\": false,\n  \"error\": {\n    \"message\": \"Error parsing key \\\"name\\\": The value \\\"123\\\" must be type of string but is type of \\\"number\\\".\",\n    \"cause\": { \"key\": \"name\" }\n  },\n  \"errors\": [\n    { \"message\": \"Error parsing key \\\"name\\\": ...\", \"cause\": { \"key\": \"name\" } },\n    { \"message\": \"Error parsing key \\\"age\\\": ...\", \"cause\": { \"key\": \"age\" } },\n    { \"message\": \"Error parsing key \\\"email\\\": ...\", \"cause\": { \"key\": \"email\" } }\n  ]\n}\n```\n\n### Extending Schemas\n\nYou can extend the schema system with custom validation methods. This is useful for adding domain-specific validations like email or URL formats.\n\n#### Basic Extension Example\n\n```typescript\nimport { extend, type SchemaType, type StringSchemaInterface } from '@esmj/schema';\n\n// First, declare the new methods you want to add\ndeclare module '@esmj/schema' {\n  interface StringSchemaInterface {\n    email(): StringSchemaInterface;\n    url(): StringSchemaInterface;\n    trim(): StringSchemaInterface;\n  }\n}\n\n// Define validation patterns\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\nconst URL_REGEX = /^(https?:\\/\\/[^\\s$.?#].[^\\s]*)$/;\n\n// Extend the schema system\nextend((schema: SchemaType, _, options) =\u003e {\n  // Only add methods to string schemas\n  if (options?.type === 'string') {\n    const stringSchema = schema as StringSchemaInterface;\n    \n    // Add email validation\n    stringSchema.email = function() {\n      return this.refine((value) =\u003e EMAIL_REGEX.test(value), {\n        message: 'Invalid email format'\n      });\n    };\n    \n    // Add URL validation\n    stringSchema.url = function() {\n      return this.refine((value) =\u003e URL_REGEX.test(value), {\n        message: 'Invalid URL format'\n      });\n    };\n    \n    // Add string trimming\n    stringSchema.trim = function() {\n      return this.transform((value) =\u003e value.trim());\n    };\n  }\n\n  return schema;\n});\n```\n\n#### Usage of Extended Schemas\n\nOnce extended, you can use your custom methods in schema definitions:\n\n```typescript\nconst userSchema = s.object({\n  name: s.string().trim(),\n  email: s.string().email(),\n  website: s.string().url().optional()\n});\n\n// Valid data\nuserSchema.parse({\n  name: '  John Doe  ', // Will be trimmed\n  email: 'john@example.com'\n});\n\n// Invalid data\ntry {\n  userSchema.parse({\n    name: 'John Doe',\n    email: 'not-an-email'\n  });\n} catch (error) {\n  console.error(error); // \"Invalid email format\"\n}\n```\n\n#### Advanced Extensions\n\nYou can extend any schema type and add complex validations:\n\n```typescript\ndeclare module '@esmj/schema' {\n  interface NumberSchemaInterface {\n    positive(): NumberSchemaInterface;\n    range(min: number, max: number): NumberSchemaInterface;\n  }\n  \n  interface ArraySchemaInterface\u003cT\u003e {\n    minLength(length: number): ArraySchemaInterface\u003cT\u003e;\n    unique(): ArraySchemaInterface\u003cT\u003e;\n  }\n}\n\nextend((schema: SchemaType, _, options) =\u003e {\n  if (options?.type === 'number') {\n    const numberSchema = schema as NumberSchemaInterface;\n    \n    numberSchema.positive = function() {\n      return this.refine((value) =\u003e value \u003e 0, {\n        message: 'Number must be positive'\n      });\n    };\n    \n    numberSchema.range = function(min, max) {\n      return this.refine((value) =\u003e value \u003e= min \u0026\u0026 value \u003c= max, {\n        message: `Number must be between ${min} and ${max}`\n      });\n    };\n  }\n  \n  if (options?.type === 'array') {\n    const arraySchema = schema as ArraySchemaInterface\u003cunknown\u003e;\n    \n    arraySchema.minLength = function(length) {\n      return this.refine((value) =\u003e value.length \u003e= length, {\n        message: `Array must contain at least ${length} items`\n      });\n    };\n    \n    arraySchema.unique = function() {\n      return this.refine((value) =\u003e {\n        const seen = new Set();\n        return value.every(item =\u003e {\n          const serialized = JSON.stringify(item);\n          if (seen.has(serialized)) return false;\n          seen.add(serialized);\n          return true;\n        });\n      }, { message: 'Array items must be unique' });\n    };\n  }\n  \n  return schema;\n});\n```\n\nThis extension system gives you the flexibility to create domain-specific validation rules while maintaining type safety and the fluent API style.\n\n### More Examples\n\n#### Nested Objects\n\nYou can define schemas for deeply nested objects.\n\n```typescript\nconst nestedSchema = s.object({\n  user: s.object({\n    id: s.number(),\n    profile: s.object({\n      name: s.string(),\n      age: s.number().optional(),\n    }),\n  }),\n});\n\nconst result = nestedSchema.parse({\n  user: {\n    id: 1,\n    profile: {\n      name: 'John Doe',\n    },\n  },\n});\n\nconsole.log(result);\n// {\n//   user: {\n//     id: 1,\n//     profile: {\n//       name: 'John Doe',\n//     },\n//   },\n// }\n```\n\n#### Arrays with Validation\n\nYou can validate arrays with specific item schemas.\n\n```typescript\nconst arraySchema = s.array(s.object({ id: s.number(), name: s.string() }));\n\nconst result = arraySchema.parse([\n  { id: 1, name: 'Item 1' },\n  { id: 2, name: 'Item 2' },\n]);\n\nconsole.log(result);\n// [\n//   { id: 1, name: 'Item 1' },\n//   { id: 2, name: 'Item 2' },\n// ]\n```\n\n#### Preprocessing Values\n\nUse `s.preprocess` to transform input values before validation.\n\n```typescript\nconst preprocessSchema = s.preprocess(\n  (value) =\u003e value.trim(),\n  s.string().refine((val) =\u003e val.length \u003e 0, { message: 'String cannot be empty' }),\n);\n\nconst result = preprocessSchema.parse('   hello   ');\n\nconsole.log(result);\n// 'hello'\n```\n\n#### Transforming Values\n\nUse `transform` to modify the parsed value.\n\n```typescript\nconst transformSchema = s.string().transform((value) =\u003e value.toUpperCase());\n\nconst result = transformSchema.parse('hello');\n\nconsole.log(result);\n// 'HELLO'\n```\n\n#### Piping Schemas\n\nPipe the output of one schema into another for further validation or transformation.\n\n```typescript\nconst pipedSchema = s.string()\n  .transform((value) =\u003e Number.parseInt(value))\n  .pipe(s.number().refine((val) =\u003e val \u003e 0, { message: 'Number must be positive' }));\n\nconst result = pipedSchema.parse('42');\n\nconsole.log(result);\n// 42\n```\n\n#### Refining Values\n\nAdd custom validation logic with `refine`.\n\n```typescript\nconst refinedSchema = s.string().refine((val) =\u003e val.startsWith('A'), {\n  message: 'String must start with \"A\"',\n});\n\nconst result = refinedSchema.parse('Apple');\n\nconsole.log(result);\n// 'Apple'\n```\n\n#### Default Values\n\nSet default values for optional fields.\n\n```typescript\nconst defaultSchema = s.object({\n  name: s.string().default('Anonymous'),\n  age: s.number().optional().default(18),\n});\n\nconst result = defaultSchema.parse({});\n\nconsole.log(result);\n// { name: 'Anonymous', age: 18 }\n```\n\n#### Safe Parsing\n\nUse `safeParse` to handle errors gracefully.\n\n```typescript\nconst safeSchema = s.number();\n\nconst result = safeSchema.safeParse('not a number');\n\nif (!result.success) {\n  console.error(result.error.message);\n} else {\n  console.log(result.data);\n}\n// Error: The value \"not a number\" must be type of number but is type of \"string\".\n```\n\n#### Combining Multiple Features\n\nCombine multiple features like preprocessing, transformations, and refinements.\n\n```typescript\nconst combinedSchema = s.preprocess(\n  (value) =\u003e value.trim(),\n  s.string()\n    .transform((value) =\u003e value.toUpperCase())\n    .refine((val) =\u003e val.length \u003c= 10, { message: 'String must be at most 10 characters' }),\n);\n\nconst result = combinedSchema.parse('   hello   ');\n\nconsole.log(result);\n// 'HELLO'\n```\n\n## Examples Folder\n\nThe `examples/` folder contains comprehensive, runnable examples demonstrating various use cases. See the [examples README](examples/README.md) for detailed documentation.\n\n### Basic Usage (`examples/basic-usage.ts`)\n\nDemonstrates the core validation features with strings, numbers, arrays, and unions:\n\n```bash\nnode --experimental-strip-types examples/basic-usage.ts\n```\n\n### Custom Validation (`examples/custom-validation.ts`)\n\nShows how to create custom validators for common use cases:\n- Email validation with regex\n- URL validation\n- Age range validation\n- Password strength validation\n- Cross-field validation (e.g., password confirmation)\n\n```bash\nnode --experimental-strip-types examples/custom-validation.ts\n```\n\n### Advanced Forms (`examples/advanced-forms.ts`)\n\nReal-world form validation examples:\n- User profile schema with nested objects\n- Address validation with postal codes\n- Phone number formatting and validation\n- API response validation\n- Complex nested structures\n\n```bash\nnode --experimental-strip-types examples/advanced-forms.ts\n```\n\n### Custom Extensions (`examples/custom-extensions.ts`)\n\nDemonstrates how to extend the library with custom methods:\n- Email validation extension\n- URL validation extension\n- UUID validation extension\n- Combining custom extensions with built-in validators\n\n```bash\nnode --experimental-strip-types examples/custom-extensions.ts\n```\n\n### Registration Form (`examples/registration-form.ts`)\n\nComplete user registration form validation with email and phone number validation:\n- Username validation with pattern matching\n- Email validation using custom extension\n- International phone number validation\n- Password strength requirements\n- Password confirmation matching\n- Age verification (18+)\n- Terms acceptance validation\n- Error collection with `abortEarly: false`\n\n```bash\nnode --experimental-strip-types examples/registration-form.ts\n```\n\n**To run all examples:**\n\n```bash\n# Using Node.js with experimental type stripping (built-in, no dependencies)\nnode --experimental-strip-types examples/basic-usage.ts\nnode --experimental-strip-types examples/custom-validation.ts\nnode --experimental-strip-types examples/advanced-forms.ts\nnode --experimental-strip-types examples/custom-extensions.ts\nnode --experimental-strip-types examples/registration-form.ts\n\n# OR using npm scripts from examples folder\ncd examples\nnpm install\nnpm run basic\nnpm run custom\nnpm run advanced\nnpm run extensions\nnpm run registration\nnpm run all  # Run all examples\n\n# OR using tsx (requires installation)\nnpm install -g tsx  # If not already installed\nnpx tsx examples/basic-usage.ts\nnpx tsx examples/custom-validation.ts\nnpx tsx examples/advanced-forms.ts\nnpx tsx examples/custom-extensions.ts\nnpx tsx examples/registration-form.ts\n```\n## Migration Guide\n\n### From Zod\n\n`@esmj/schema` has a similar API to Zod, making migration straightforward:\n\n```typescript\n// Zod\nimport { z } from 'zod';\n\nconst userSchema = z.object({\n  name: z.string().min(3).max(50),\n  email: z.string().email(),\n  age: z.number().positive().int(),\n  role: z.enum(['admin', 'user']),\n  tags: z.array(z.string()).optional()\n});\n\n// @esmj/schema (with extensions)\nimport { s } from '@esmj/schema/full';\n\nconst userSchema = s.object({\n  name: s.string().min(3).max(50),\n  email: s.string(), // Note: email() validation requires custom extension\n  age: s.number().positive().int(),\n  role: s.enum(['admin', 'user']),\n  tags: s.array(s.string()).optional()\n});\n```\n\n**Key Differences:**\n\n| Feature | Zod | @esmj/schema |\n|---------|-----|--------------|\n| Import | `import { z } from 'zod'` | `import { s } from '@esmj/schema'` |\n| Extensions | Built-in | Modular (`/string`, `/number`, `/array`, `/full`) |\n| Bundle size | ~13 KB | ~1.4 KB (core), ~4 KB (full) |\n| Email validation | `.email()` built-in | Custom extension (see [Extending Schemas](#extending-schemas)) |\n| Error format | Native Error | Plain object `{ success, error, errors }` |\n| Coerce | `z.coerce.number()` | `s.coerce.number()` |\n| Smart cast | No direct equivalent | `s.cast.number()` — rejects nulls, understands `'yes'/'no'`, etc. |\n\n**Migration Tips:**\n\n1. Replace `z` with `s` in your imports\n2. For string methods like `.min()`, `.trim()`, import from `@esmj/schema/full` or `@esmj/schema/string`\n3. Add custom extensions for email, URL validation (see examples below)\n4. Update error handling to use the plain object structure\n\n### From Yup\n\nMigrating from Yup requires a few adjustments in syntax:\n\n```typescript\n// Yup\nimport * as yup from 'yup';\n\nconst userSchema = yup.object({\n  name: yup.string().required().min(3).max(50),\n  email: yup.string().required().email(),\n  age: yup.number().required().positive().integer(),\n  website: yup.string().url().nullable(),\n  tags: yup.array().of(yup.string()).min(1)\n});\n\n// @esmj/schema (with extensions)\nimport { s } from '@esmj/schema/full';\n\nconst userSchema = s.object({\n  name: s.string().min(3).max(50), // Fields are required by default\n  email: s.string(), // Note: email() validation requires custom extension\n  age: s.number().positive().int(),\n  website: s.string().nullable(),\n  tags: s.array(s.string()).min(1)\n});\n```\n\n**Key Differences:**\n\n| Feature | Yup | @esmj/schema |\n|---------|-----|--------------|\n| Required fields | `.required()` explicit | Required by default |\n| Optional fields | Default behavior | `.optional()` explicit |\n| Array of type | `.array().of(type)` | `.array(type)` |\n| Integer | `.integer()` | `.int()` |\n| Email validation | `.email()` built-in | Custom extension needed |\n| Async validation | Supported | Not currently supported |\n\n**Migration Tips:**\n\n1. Remove `.required()` calls (fields are required by default)\n2. Add `.optional()` for optional fields\n3. Change `.array().of(type)` to `.array(type)`\n4. Change `.integer()` to `.int()`\n5. Add custom extensions for email, URL validation\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmjancarik%2Fesmj-schema","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmjancarik%2Fesmj-schema","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmjancarik%2Fesmj-schema/lists"}