{"id":45531166,"url":"https://github.com/marekh19/narrowland","last_synced_at":"2026-02-23T01:10:53.508Z","repository":{"id":318728176,"uuid":"1073955326","full_name":"marekh19/narrowland","owner":"marekh19","description":"A lightweight TypeScript library providing type-safe assertions, type guards, and error handling utilities.","archived":false,"fork":false,"pushed_at":"2026-02-15T21:26:50.000Z","size":896,"stargazers_count":15,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-02-16T04:26:30.322Z","etag":null,"topics":["assert","invariant","narrowing","type-guards","typescript"],"latest_commit_sha":null,"homepage":"","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/marekh19.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-10-10T22:22:18.000Z","updated_at":"2026-02-15T21:26:47.000Z","dependencies_parsed_at":"2025-10-13T07:27:14.881Z","dependency_job_id":"da84c150-e266-4644-a006-2fdc0cc4fb1e","html_url":"https://github.com/marekh19/narrowland","commit_stats":null,"previous_names":["marekh19/narrowland"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/marekh19/narrowland","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marekh19%2Fnarrowland","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marekh19%2Fnarrowland/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marekh19%2Fnarrowland/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marekh19%2Fnarrowland/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/marekh19","download_url":"https://codeload.github.com/marekh19/narrowland/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marekh19%2Fnarrowland/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29730365,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-22T20:09:16.275Z","status":"ssl_error","status_checked_at":"2026-02-22T20:09:13.750Z","response_time":110,"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":["assert","invariant","narrowing","type-guards","typescript"],"created_at":"2026-02-23T01:10:52.870Z","updated_at":"2026-02-23T01:10:53.497Z","avatar_url":"https://github.com/marekh19.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🛡️ Narrowland\n\n[![codecov](https://codecov.io/gh/marekh19/narrowland/branch/main/graph/badge.svg)](https://codecov.io/gh/marekh19/narrowland)\n[![npm version](https://img.shields.io/npm/v/narrowland.svg)](https://www.npmjs.com/package/narrowland)\n[![Bundle size](https://img.shields.io/bundlephobia/minzip/narrowland)](https://bundlephobia.com/package/narrowland)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nA lightweight TypeScript library providing type guards, type assertions, ensure functions, and invariant utilities for runtime type narrowing.\n\n## 🚀 30-Second Pitch\n\n**Narrowland** solves the problem of runtime type narrowing in TypeScript applications. Instead of writing verbose type checks and assertions, you get a clean, consistent API that provides:\n\n- **Type Guards** (`is.*`) - Check types without throwing errors, perfect for conditional logic\n- **Type Assertions** (`assert.*`) - Throw errors for invalid types with full TypeScript inference\n- **Ensure Functions** (`ensure.*`) - Validate and return the narrowed value in one step, throwing `EnsureError` on failure\n- **Invariant Utilities** (`invariant`, `raiseError`) - Handle edge cases and invariants gracefully\n\nAll functions are **tree-shakeable**, **zero-dependency**, and **fully typed** with TypeScript's type narrowing. You can import individual functions or use the grouped APIs.\n\n## 📦 Installation\n\n```bash\nnpm install narrowland\n# or\npnpm add narrowland\n# or\nyarn add narrowland\n```\n\n## 🎯 Quick Start\n\n```typescript\nimport { isString, assertNumber, ensureString, ensureDefined, invariant } from 'narrowland'\n\n// Or import grouped APIs\nimport { is, assert, ensure } from 'narrowland'\n\n// Type guards - return boolean, don't throw\nif (isString(userInput)) {\n  // userInput is now typed as string\n  console.log(userInput.toUpperCase())\n}\n\n// Type guards from grouped import\nif (is.string(userInput)) {\n  // userInput is now typed as string\n  console.log(userInput.toUpperCase())\n}\n\n// Assertions - throw on invalid types, narrow in same scope\nassertNumber(age) // throws if not a number\n// age is now typed as number\n\n// Ensure - validate and return the narrowed value in one step\nconst name = ensure.string(rawName) // throws EnsureError if not a string\n// name is typed as string\n\nconst config = ensure.defined(maybeConfig, 'Config is required')\n// config is typed as NonNullable\u003ctypeof maybeConfig\u003e\n\n// Invariant - check conditions (throws on falsy, does nothing on truthy)\ninvariant(user.age \u003e= 18, 'User must be an adult')\n```\n\n## 📚 API Reference\n\n\u003e All functions except `is.*` throw errors if the condition is not satisfied.\n\n### Type Guards (`is.*`)\n\nType guards return `boolean` and narrow types without throwing errors. **Safer than assertions** because they don't throw.\n\n| Function | Type Guard | Description |\n|----------|------------|-------------|\n| `is.defined(value)` | `value is NonNullable\u003cT\u003e` | Checks if value is not null or undefined |\n| `is.notNull(value)` | `value is Exclude\u003cT, null\u003e` | Checks if value is not null |\n| `is.truthy(value)` | `value is Exclude\u003cT, false \\| 0 \\| '' \\| null \\| undefined\u003e` | Checks if value is truthy |\n| `is.falsy(value)` ⚠️ | `value is Extract\u003cT, false \\| 0 \\| '' \\| null \\| undefined\u003e` | Checks if value is falsy (deprecated: type narrowing is incorrect for most types, will be removed in v2.0.0) |\n| `is.string(value)` | `value is string` | Checks if value is a string |\n| `is.nonEmptyString(value)` | `value is string` | Checks if value is a non-empty string |\n| `is.number(value)` | `value is number` | Checks if value is a finite number |\n| `is.boolean(value)` | `value is boolean` | Checks if value is a boolean |\n| `is.bigint(value)` | `value is bigint` | Checks if value is a bigint |\n| `is.symbol(value)` | `value is symbol` | Checks if value is a symbol |\n| `is.instanceOf(value, constructor)` | `value is T` | Checks if value is an instance of the given constructor |\n| `is.array(value)` | `value is T[]` | Checks if value is an array |\n| `is.nonEmptyArray(value)` | `value is [T, ...T[]]` | Checks if value is a non-empty array |\n| `is.arrayOf(value, guard)` | `value is T[]` | Checks if value is an array whose items satisfy the provided guard |\n| `is.stringLiteral(value, literals)` ⚠️ | `value is T[number]` | Checks if value is a member of string literals array (deprecated: use `is.oneOf` instead, will be removed in v2.0.0) |\n| `is.oneOf(value, collection)` | `value is T[number]` | Checks if value is a member of the provided collection (works with any type) |\n| `is.object(value)` | `value is T` | Checks if value is a plain object |\n| `is.propertyOf(key, predicate)` | `obj is T \u0026 { [P in K]-?: U }` | Checks that the selected property satisfies the provided predicate and makes it required |\n| `is.keyOf(value, record)` | `value is T \u0026 keyof U` | Checks if value is a key of the provided record |\n\n### Type Assertions (`assert.*`)\n\nAssertions throw errors for invalid types and narrow types in the same scope. **Use when you expect the value to be valid**.\n\n| Function | Assertion | Description |\n|----------|-----------|-------------|\n| `assert.defined(value, message?)` | `asserts value is NonNullable\u003cT\u003e` | Throws if value is null or undefined |\n| `assert.notNull(value, message?)` | `asserts value is Exclude\u003cT, null\u003e` | Throws if value is null |\n| `assert.truthy(value, message?)` | `asserts value is Exclude\u003cT, false \\| 0 \\| '' \\| null \\| undefined\u003e` | Throws if value is falsy |\n| `assert.falsy(value, message?)` ⚠️ | `asserts value is Extract\u003cT, false \\| 0 \\| '' \\| null \\| undefined\u003e` | Throws if value is truthy (deprecated: type narrowing is incorrect for most types, will be removed in v2.0.0) |\n| `assert.string(value, message?)` | `asserts value is string` | Throws if value is not a string |\n| `assert.nonEmptyString(value, message?)` | `asserts value is string` | Throws if value is not a non-empty string |\n| `assert.number(value, message?)` | `asserts value is number` | Throws if value is not a finite number |\n| `assert.boolean(value, message?)` | `asserts value is boolean` | Throws if value is not a boolean |\n| `assert.bigint(value, message?)` | `asserts value is bigint` | Throws if value is not a bigint |\n| `assert.symbol(value, message?)` | `asserts value is symbol` | Throws if value is not a symbol |\n| `assert.instanceOf(value, constructor, message?)` | `asserts value is T` | Throws if value is not an instance of the given constructor |\n| `assert.array(value, message?)` | `asserts value is T[]` | Throws if value is not an array |\n| `assert.nonEmptyArray(value, message?)` | `asserts value is [T, ...T[]]` | Throws if value is not a non-empty array |\n| `assert.arrayOf(value, guard, message?)` | `asserts value is T[]` | Throws if value is not an array whose items satisfy the provided guard |\n| `assert.stringLiteral(value, literals, message?)` ⚠️ | `value is T[number]` | Throws if value is not a member of provided string literals array (deprecated: use `assert.oneOf` instead, will be removed in v2.0.0) |\n| `assert.oneOf(value, collection, message?)` | `asserts value is T[number]` | Throws if value is not a member of the provided collection (works with any type) |\n| `assert.object(value, message?)` | `asserts value is T` | Throws if value is not a plain object |\n| `assert.keyOf(value, record, message?)` | `asserts value is T \u0026 keyof U` | Throws if value is not a key of the provided record |\n| `assert.fromPredicate(predicate, message?)` | `(value, message?) =\u003e asserts value is T` | Creates custom assertion from predicate |\n\n### Ensure Functions (`ensure.*`)\n\nEnsure functions validate a value and **return the narrowed value** directly, throwing `EnsureError` on failure. They combine the check and return into a single expression — ideal for inline assignments and pipelines.\n\n| Function | Return Type | Description |\n|----------|-------------|-------------|\n| `ensure.defined(value, message?)` | `NonNullable\u003cT\u003e` | Returns value or throws if null/undefined |\n| `ensure.notNull(value, message?)` | `Exclude\u003cT, null\u003e` | Returns value or throws if null |\n| `ensure.truthy(value, message?)` | `Exclude\u003cT, false \\| 0 \\| '' \\| null \\| undefined\u003e` | Returns value or throws if falsy |\n| `ensure.string(value, message?)` | `string` | Returns value or throws if not a string |\n| `ensure.nonEmptyString(value, message?)` | `string` | Returns value or throws if not a non-empty string |\n| `ensure.number(value, message?)` | `number` | Returns value or throws if not a finite number |\n| `ensure.boolean(value, message?)` | `boolean` | Returns value or throws if not a boolean |\n| `ensure.bigint(value, message?)` | `bigint` | Returns value or throws if not a bigint |\n| `ensure.symbol(value, message?)` | `symbol` | Returns value or throws if not a symbol |\n| `ensure.instanceOf(value, constructor, message?)` | `T` | Returns value or throws if not an instance of the given constructor |\n| `ensure.array(value, message?)` | `T[]` | Returns value or throws if not an array |\n| `ensure.nonEmptyArray(value, message?)` | `[T, ...T[]]` | Returns value or throws if not a non-empty array |\n| `ensure.arrayOf(value, guard, message?)` | `T[]` | Returns value or throws if not an array whose items satisfy the provided guard |\n| `ensure.oneOf(value, collection, message?)` | `T[number]` | Returns value or throws if not a member of the provided collection |\n| `ensure.object(value, message?)` | `T` | Returns value or throws if not a plain object |\n| `ensure.keyOf(value, record, message?)` | `T \u0026 keyof U` | Returns value or throws if not a key of the provided record |\n| `ensure.fromPredicate(predicate, message?)` | `(value, message?) =\u003e T` | Creates custom ensure function from predicate |\n\n### Invariant Utilities\n\n| Function | Return Type | Description |\n|----------|-------------|-------------|\n| `ensure(value, message?)` ⚠️ | `NonNullable\u003cT\u003e` | Calling `ensure` as a function is deprecated — use `ensure.defined` instead. In v2.0.0, the call signature will be removed. |\n| `invariant(condition, message?)` | `asserts condition` | Throws if condition is falsy (generic condition checker) |\n| `raiseError(message, options?)` | `never` | Throws error with custom name/code/cause (most flexible) |\n\n## 🎨 When to Use What?\n\nNarrowland provides a palette of solutions from most generic to very specific:\n\n### **Type Guards (`is.*`)** - Safest, Most Flexible\n\n- **Use when**: You want to check types without throwing\n- **Best for**: Conditional logic, filtering, optional validation\n- **Example**: `if (isString(value)) { /* value is string */ }`\n\n### **Type Assertions (`assert.*`)** - Narrow in Place\n\n- **Use when**: You expect the value to be valid and want to fail fast with narrowing in the same scope\n- **Best for**: Input validation at the top of functions, API boundaries\n- **Example**: `assertString(userInput)` - throws immediately if not string, narrows type below\n\n### **Ensure Functions (`ensure.*`)** - Validate and Return\n\n- **Use when**: You need the narrowed value as a return or assignment in one expression\n- **Best for**: Inline assignments, function arguments, pipelines, destructuring\n- **Throws**: `EnsureError` if validation fails\n- **Example**: `const name = ensure.string(rawInput)` - validates and returns the string\n\n### **Invariant** - Generic Condition Checker\n\n- **Use when**: You want to check any condition, not just types\n- **Best for**: Business logic validation, state checks, complex conditions\n- **Example**: `invariant(user.age \u003e= 18, 'User must be adult')`\n\n### **RaiseError** - Most Flexible Error Creation\n\n- **Use when**: You need custom error types with specific properties or inline assertions\n- **Best for**: API errors, domain-specific errors, when you need error codes, inline assertions\n- **Example**: `getUserById(maybeId ?? raiseError('User id must be defined'))`\n\n## 🍳 Copy-Paste Recipes\n\n### Type Guards - Simple Checks\n\n```typescript\nimport { isDefined, isTruthy } from 'narrowland'\n\n// Check if value exists\nif (isDefined(maybeValue)) {\n  // maybeValue is now NonNullable\u003ctypeof maybeValue\u003e\n  console.log(maybeValue)\n}\n\n// Check if value is truthy\nif (isTruthy(userInput)) {\n  // userInput is now truthy type\n  return userInput\n}\n```\n\n### Assertions - Fail Fast\n\n```typescript\nimport { assertString } from 'narrowland'\n\nfunction processInput(input: unknown) {\n  assertString(input) // throws if not string\n  // input is now string\n  return input.toUpperCase()\n}\n```\n\n### Ensure - Validate and Return\n\n```typescript\nimport { ensure } from 'narrowland'\n\n// Validate and assign in one step\nconst name = ensure.string(rawInput) // throws EnsureError if not a string\nconst age = ensure.number(rawAge) // throws EnsureError if not a finite number\n\n// Handle null/undefined values\nfunction getUserName(user: { name?: string | null }) {\n  const name = ensure.defined(user.name, 'Name is required')\n  // name is now string (was string | null | undefined)\n  return name\n}\n\n// Use inline as function arguments\nprocessUser(ensure.string(data.name), ensure.number(data.age))\n```\n\n### Ensure - Custom Validators\n\n```typescript\nimport { ensure, isString } from 'narrowland'\n\n// Create a reusable ensure function from any type guard\nconst ensureMyString = ensure.fromPredicate(isString, 'Must be a string')\n\nconst value = ensureMyString(input) // returns string or throws EnsureError\n```\n\n### Invariant - Business Rules\n\n```typescript\nimport { invariant } from 'narrowland'\n\nfunction calculateAge(birthYear: number) {\n  invariant(birthYear \u003e 1900, 'Invalid birth year')\n  invariant(birthYear \u003c= new Date().getFullYear(), 'Birth year cannot be in future')\n  \n  return new Date().getFullYear() - birthYear\n}\n```\n\n### Filtering Arrays\n\n```typescript\nimport { isString, isInstanceOf } from 'narrowland'\n\n// Filter strings from mixed array\nconst mixedArray = ['hello', 42, 'world', true]\nconst strings = mixedArray.filter(isString) // TypeScript knows these are strings\n\n// Filter Date instances from mixed array\nconst values = [new Date(), '2023-01-01', new Date('2024-01-01'), null, 42]\nconst dates = values.filter((v) =\u003e isInstanceOf(v, Date)) // TypeScript knows `dates` is Date[]\n```\n\n### Property Guards - Filter Objects by Required Keys\n\n```typescript\nimport { isDefined, isPropertyOf } from 'narrowland'\n\ntype User = {\n  id: string\n  email?: string | null\n}\n\nconst users: User[] = [\n  { id: '1', email: 'one@example.com' },\n  { id: '2', email: null },\n  { id: '3' },\n]\n\nconst hasEmail = isPropertyOf('email', isDefined)\n\nconst contactableUsers = users.filter(hasEmail)\n// ?^ contactableUsers is typed as PropNarrow\u003cUser, \"email\", {}\u003e[]\ncontactableUsers.forEach((user) =\u003e {\n  user.email.toLowerCase()\n  // ?^ user.email is typed as string\n})\n```\n\n### ArrayOf - Array Type Narrowing\n\n```typescript\nimport { isArrayOf, isString } from 'narrowland'\n\n// Narrow array of strings\nconst maybeStringArray: unknown = ['hello', 'world', 'typescript']\nif (isArrayOf(maybeStringArray, isString)) {\n  // maybeStringArray is now typed as string[]\n  const uppercased = maybeStringArray.map((s) =\u003e s.toUpperCase())\n}\n```\n\n### OneOf - Union Type Narrowing\n\n```typescript\nimport { is, assert, ensure } from 'narrowland'\n\n// Works with any type, not just strings\nconst status = 'pending' as unknown\nconst validStatuses = ['pending', 'completed', 'failed'] as const\n\n// Type guard - returns boolean\nif (is.oneOf(status, validStatuses)) {\n  // status is now typed as 'pending' | 'completed' | 'failed'\n  console.log(status.toUpperCase())\n}\n\n// Assertion - throws if invalid\nassert.oneOf(status, validStatuses)\n// status is now typed as 'pending' | 'completed' | 'failed'\n\n// Ensure - validate and return in one step\nconst validStatus = ensure.oneOf(status, validStatuses)\n// validStatus is typed as 'pending' | 'completed' | 'failed'\n```\n\n### KeyOf - Safe Object Key Access\n\n```typescript\nimport { is, assert, ensure } from 'narrowland'\n\nconst handlers = {\n  click: () =\u003e 'clicked',\n  submit: () =\u003e 'submitted',\n} as const\n\n// Type guard - returns boolean\nconst eventName = 'click' as string\nif (is.keyOf(eventName, handlers)) {\n  // eventName is now typed as 'click' | 'submit'\n  handlers[eventName]() // Safe to call\n}\n\n// Assertion - throws if invalid\nassert.keyOf(eventName, handlers)\n// eventName is now typed as 'click' | 'submit'\nhandlers[eventName]() // Safe to call\n\n// Ensure - validate and return in one step\nconst key = ensure.keyOf(eventName, handlers)\nhandlers[key]() // Safe to call\n```\n\n## 📊 Bundle Size\n\n- **Size**: 1.08kB (minified + brotli)\n- **Dependencies**: 0\n- **Tree-shakeable**: ✅ (import individual functions)\n- **ESM + CJS**: ✅\n\n## 🛠️ Development\n\n### Prerequisites\n\n- Node.js 20+\n- pnpm 8+\n\n### Setup\n\n```bash\n# Clone the repository\ngit clone https://github.com/your-username/narrowland.git\ncd narrowland\n\n# Install dependencies\npnpm install\n\n# Build the library\npnpm build\n\n# Run in development mode\npnpm dev\n```\n\n## 🤝 Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n1. Fork the repository\n2. Create your feature branch (`git switch -c feat/amazing-feature`)\n3. Commit your changes (`git commit -m 'feat: add some amazing feature'`)\n4. Push to the branch (`git push origin feat/amazing-feature`)\n5. Open a Pull Request\n\n### Development Guidelines\n\n- Add tests for new functionality\n- Update documentation as needed\n\n## 📄 License\n\nThis project is licensed under the MIT License - see the [LICENSE](https://github.com/marekh19/narrowland/blob/main/LICENSE) file for details.\n\n## 🙏 Acknowledgments\n\n- Inspired by [tiny-invariant](https://github.com/alexreardon/tiny-invariant) for the invariant pattern\n- Built with modern tooling: Vitest, Biome, RSLib\n- Zero dependencies for maximum compatibility\n\n---\n\n**Made with ❤️ for the TypeScript community**\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarekh19%2Fnarrowland","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarekh19%2Fnarrowland","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarekh19%2Fnarrowland/lists"}