{"_id":"@aakashwije/lanka-nic","name":"@aakashwije/lanka-nic","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@aakashwije/lanka-nic","version":"1.0.0","description":"TypeScript validator and parser for Sri Lankan NIC numbers (old and new formats).","license":"MIT","type":"module","main":"dist/index.cjs","module":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"bin":{"lanka-nic":"dist/cli.cjs"},"sideEffects":false,"engines":{"node":">=18"},"scripts":{"build":"tsup","dev":"tsup --watch","test":"vitest run","test:watch":"vitest","coverage":"vitest run --coverage","lint":"eslint .","format":"prettier . --write","typecheck":"tsc --noEmit","release":"semantic-release"},"publishConfig":{"access":"public","provenance":true},"repository":{"type":"git","url":"git+https://github.com/Aakashwije/lanka-nic.git"},"bugs":{"url":"https://github.com/Aakashwije/lanka-nic/issues"},"homepage":"https://github.com/Aakashwije/lanka-nic#readme","keywords":["nic","sri-lanka","lanka","validator","parser","typescript","identity","government","id"],"release":{"repositoryUrl":"https://github.com/Aakashwije/lanka-nic.git","branches":["main"],"plugins":[["@semantic-release/commit-analyzer",{"preset":"conventionalcommits"}],["@semantic-release/release-notes-generator",{"preset":"conventionalcommits"}],"@semantic-release/changelog",["@semantic-release/npm",{"npmPublish":true}],["@semantic-release/git",{"assets":["CHANGELOG.md","package.json","package-lock.json"],"message":"chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"}],"@semantic-release/github"]},"devDependencies":{"@semantic-release/changelog":"^6.0.3","@semantic-release/commit-analyzer":"^13.0.1","@semantic-release/git":"^10.0.1","@semantic-release/github":"^11.0.1","@semantic-release/npm":"^12.0.2","@semantic-release/release-notes-generator":"^14.1.0","@types/node":"^22.15.29","@typescript-eslint/eslint-plugin":"^8.35.0","@typescript-eslint/parser":"^8.35.0","@vitest/coverage-v8":"^3.2.4","conventional-changelog-conventionalcommits":"^8.0.0","eslint":"^9.28.0","prettier":"^3.5.3","semantic-release":"^24.2.5","tsup":"^8.5.0","typescript":"^5.8.3","vitest":"^3.2.4"},"gitHead":"77e3c572596dba8e70f8a3ed42e808e47ac967b5","_id":"@aakashwije/lanka-nic@1.0.0","_nodeVersion":"26.3.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-wTlqjfwwZdB24Sbt8ELqSvu6Sq7rL6DdnzXLkOBVE34CKmn1/ZVtDe8XazmCwP5hM+N/raYNRJk/p6Qs7MJFSQ==","shasum":"ba9366d6df256e7f73e6b4893d8e5881d69c8228","tarball":"https://registry.npmjs.org/@aakashwije/lanka-nic/-/lanka-nic-1.0.0.tgz","fileCount":14,"unpackedSize":135978,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDL1nyYkCYfb7Tpllq/jV9Kslgl/EWYEceykj7r1bZTPAIgbGjTw0C501ULN78x1LyA42tT8LlRIIkJ3KpGiUI7FXs="}]},"_npmUser":{"name":"aakashwije","email":"aakashwije92@gmail.com"},"directories":{},"maintainers":[{"name":"aakashwije","email":"aakashwije92@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/lanka-nic_1.0.0_1781686113786_0.9479659613406652"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-17T08:48:33.607Z","1.0.0":"2026-06-17T08:48:33.942Z","modified":"2026-06-17T08:48:34.166Z"},"maintainers":[{"name":"aakashwije","email":"aakashwije92@gmail.com"}],"description":"TypeScript validator and parser for Sri Lankan NIC numbers (old and new formats).","homepage":"https://github.com/Aakashwije/lanka-nic#readme","keywords":["nic","sri-lanka","lanka","validator","parser","typescript","identity","government","id"],"repository":{"type":"git","url":"git+https://github.com/Aakashwije/lanka-nic.git"},"bugs":{"url":"https://github.com/Aakashwije/lanka-nic/issues"},"license":"MIT","readme":"<div align=\"center\">\n\n# `@aakashwije/lanka-nic`\n\n**Production-grade Sri Lankan NIC validation, parsing, and generation**\n\n[![npm](https://img.shields.io/npm/v/@aakashwije/lanka-nic?color=%230060c7&label=npm&logo=npm&style=flat-square)](https://www.npmjs.com/package/@aakashwije/lanka-nic)\n[![CI](https://img.shields.io/github/actions/workflow/status/aakashlk/lanka-nic/ci.yml?branch=main&logo=github-actions&logoColor=white&style=flat-square&label=CI)](https://github.com/aakashlk/lanka-nic/actions)\n[![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen?logo=vitest&style=flat-square)](#)\n[![License: MIT](https://img.shields.io/badge/license-MIT-22c55e?style=flat-square)](LICENSE)\n[![TypeScript](https://img.shields.io/badge/TypeScript-5.x-3178C6?logo=typescript&logoColor=white&style=flat-square)](https://www.typescriptlang.org/)\n[![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18-339933?logo=node.js&logoColor=white&style=flat-square)](https://nodejs.org/)\n[![ESM + CJS](https://img.shields.io/badge/module-ESM%20%2B%20CJS-f97316?style=flat-square)](#)\n[![Zero deps](https://img.shields.io/badge/dependencies-0-brightgreen?style=flat-square)](#)\n\n---\n\n*Engineered for correctness and developer ergonomics. UTC-safe date math, leap-year aware, typed error codes, and a first-class CLI — all with zero runtime dependencies.*\n\n</div>\n\n---\n\n## Table of Contents\n\n- [Overview](#overview)\n- [Technology Stack](#technology-stack)\n- [NIC Format Specification](#nic-format-specification)\n- [Architecture](#architecture)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [API Reference](#api-reference)\n- [CLI](#cli)\n- [Contributing](#contributing)\n- [License](#license)\n\n---\n\n## Overview\n\n`@aakashwije/lanka-nic` is a zero-dependency, fully-typed TypeScript library for working with Sri Lankan **National Identity Card (NIC)** numbers. It handles both the legacy 9-digit format (`YYXXXSSSSV`) and the modern 12-digit format (`YYYYXXXSSSSS`), covering:\n\n- **Parsing** — extract birth year, birth date, day-of-year, gender\n- **Validation** — format and semantic correctness including leap-year safety\n- **Normalization** — whitespace trimming, suffix casing\n- **Masking** — safe display in logs and UIs\n- **Age derivation** — UTC-correct age calculation at any reference date\n- **Format conversion** — old → new best-effort conversion\n- **Equality** — compare NICs across formats\n- **Batch operations** — validate or parse arrays efficiently\n- **Test data generation** — generate syntactically valid NICs for seeding\n\n---\n\n## Technology Stack\n\n| Layer | Technology | Purpose |\n|---|---|---|\n| Language | ![TypeScript](https://img.shields.io/badge/-TypeScript_5.x-3178C6?logo=typescript&logoColor=white&style=flat-square) | Type-safe implementation |\n| Runtime | ![Node.js](https://img.shields.io/badge/-Node.js_%E2%89%A518-339933?logo=node.js&logoColor=white&style=flat-square) | Server-side execution |\n| Build | ![tsup](https://img.shields.io/badge/-tsup-f97316?style=flat-square) | Dual ESM + CJS output |\n| Test | ![Vitest](https://img.shields.io/badge/-Vitest-6E9F18?logo=vitest&logoColor=white&style=flat-square) | Unit tests + coverage |\n| Lint | ![ESLint](https://img.shields.io/badge/-ESLint_9.x-4B32C3?logo=eslint&logoColor=white&style=flat-square) | Code quality |\n| Format | ![Prettier](https://img.shields.io/badge/-Prettier-F7B93E?logo=prettier&logoColor=black&style=flat-square) | Consistent style |\n| Release | ![semantic-release](https://img.shields.io/badge/-semantic--release-e10079?logo=semantic-release&logoColor=white&style=flat-square) | Automated versioning |\n| CI/CD | ![GitHub Actions](https://img.shields.io/badge/-GitHub_Actions-2088FF?logo=github-actions&logoColor=white&style=flat-square) | Continuous integration |\n\n---\n\n## NIC Format Specification\n\nSri Lanka uses two NIC formats, both encoding birth year, day-of-year, gender, and serial number in a compact string.\n\n### Old Format — `YYXXXSSSSV`\n\n```\n┌─ 2 ─┬──── 3 ────┬──── 4 ────┬─ 1 ─┐\n│  YY  │    DDD    │   SSSS    │  V   │\n└──────┴───────────┴───────────┴──────┘\n  Year   Day code    Serial   Check letter\n (1900+)  (001–866)  (0000–9999)  (V or X)\n\nExamples:\n  950012345V  →  born 1995, day 001, male, serial 2345\n  956512345V  →  born 1995, day 001, female (001 + 500 = 501... see gender encoding)\n```\n\n### New Format — `YYYYXXXSSSSSSS`\n\n```\n┌──── 4 ────┬──── 3 ────┬─────── 5 ───────┐\n│   YYYY    │    DDD    │     SSSSS        │\n└───────────┴───────────┴──────────────────┘\n  Birth year  Day code     Serial\n  (4 digits)  (001–866)   (00000–99999)\n\nExamples:\n  199500123456  →  born 1995, day 001, male, serial 23456\n```\n\n### Gender Encoding\n\nThe day-of-year is encoded directly for males. For females, **500 is added** to the raw day value:\n\n| Gender | Day code range | Resolved day-of-year |\n|--------|---------------|---------------------|\n| Male   | `001` – `366` | value as-is         |\n| Female | `501` – `866` | value − 500         |\n| Invalid | `000`, `367`–`500`, `867`+ | rejected |\n\n---\n\n## Architecture\n\n### Module Dependency Graph\n\n```mermaid\ngraph TD\n    subgraph Utilities[\"⚙️ Utilities\"]\n        constants[\"constants\\nregex patterns · day offsets\"]\n        date[\"date utils\\nleap year · day→date · format\"]\n    end\n\n    subgraph Parsing[\"🔍 Parsing & Normalization\"]\n        normalize[\"normalizeNIC\\ntrim · compact · uppercase suffix\"]\n        parse[\"safeParse · parseNIC\\nparseNICOrThrow\\ngetNICType · getBirthDate · getGender\"]\n    end\n\n    subgraph Derived[\"📦 Derived Operations\"]\n        validate[\"validateNIC\"]\n        mask[\"maskNIC\"]\n        age[\"getAge · getAgeOn\"]\n        equals[\"equalsNIC\"]\n        convert[\"convertOldToNew\"]\n        guards[\"isValidNIC · isOldNIC · isNewNIC\"]\n        batch[\"validateBatch · parseBatch\"]\n        generate[\"generateNIC\"]\n    end\n\n    subgraph CLI[\"💻 CLI\"]\n        cli_core[\"runCommand\\n(testable core)\"]\n        cli_bin[\"lanka-nic binary\"]\n    end\n\n    constants --> normalize & parse & generate\n    date --> parse & generate\n    normalize --> parse & mask & convert\n\n    parse --> validate & mask & age & equals & guards & batch\n    validate --> guards & batch & mask\n\n    validate & parse & mask & age --> cli_core\n    cli_core --> cli_bin\n```\n\n### Parse Pipeline\n\n```mermaid\nflowchart LR\n    A([raw input]) --> B{is string?}\n    B -- ❌ no --> E1([NON_STRING])\n    B -- ✅ yes --> C[normalizeNIC\\ntrim · compact\\nuppercase suffix]\n    C --> D{empty\\nafter trim?}\n    D -- ❌ yes --> E2([EMPTY])\n    D -- ✅ no --> F{matches\\nold or new regex?}\n    F -- ❌ neither --> E3([INVALID_FORMAT])\n    F -- old\\n9d+V/X --> G[extract yy · dayCode]\n    F -- new\\n12d --> H[extract yyyy · dayCode]\n    G & H --> I{birthYear\\n≤ today UTC?}\n    I -- ❌ no --> E4([FUTURE_YEAR])\n    I -- ✅ yes --> J{dayCode in\\n1–366 or 501–866?}\n    J -- ❌ no --> E5([INVALID_DAY_CODE])\n    J -- ✅ yes --> K{day exists\\nin that year?}\n    K -- ❌ no\\ne.g. day 366\\nnon-leap --> E6([INVALID_DAY_FOR_YEAR])\n    K -- ✅ yes --> L([✅ ParsedNIC])\n```\n\n### CLI Interaction Flow\n\n```mermaid\nsequenceDiagram\n    actor User\n    participant CLI as lanka-nic\n    participant Core as runCommand\n    participant Lib as @aakashwije/lanka-nic\n\n    User->>CLI: lanka-nic parse 950012345V\n\n    alt NIC passed as argument\n        CLI->>Core: argv=['parse','950012345V'], stdin=null\n    else NIC from stdin\n        User->>CLI: echo 950012345V | lanka-nic parse\n        CLI->>CLI: readStdin()\n        CLI->>Core: argv=['parse'], stdin='950012345V'\n    end\n\n    Core->>Lib: safeParse('950012345V')\n    Lib-->>Core: { success: true, data: ParsedNIC }\n    Core-->>CLI: { code: 0, stdout: JSON }\n    CLI-->>User: prints JSON · exits 0\n\n    Note over CLI,Lib: On invalid NIC → exits 1 with error JSON on stderr\n    Note over CLI,Lib: On bad command → exits 2 with usage text on stderr\n```\n\n---\n\n## Installation\n\n```bash\n# npm\nnpm install @aakashwije/lanka-nic\n\n# pnpm\npnpm add @aakashwije/lanka-nic\n\n# yarn\nyarn add @aakashwije/lanka-nic\n```\n\nRequires **Node.js ≥ 18**. Zero runtime dependencies. Dual ESM + CommonJS output.\n\n---\n\n## Quick Start\n\n```ts\nimport { parseNIC, validateNIC, getAge, maskNIC } from '@aakashwije/lanka-nic';\n\nconst nic = '950012345V';\n\nvalidateNIC(nic);   // true\ngetAge(nic);        // 30  (computed against today UTC)\nmaskNIC(nic);       // '95001****V'\n\nconst parsed = parseNIC(nic);\n// {\n//   input:      '950012345V',\n//   normalized: '950012345V',\n//   type:       'old',\n//   valid:      true,\n//   birthYear:  1995,\n//   dayOfYear:  1,\n//   birthDate:  '1995-01-01',\n//   gender:     'male'\n// }\n```\n\n---\n\n## API Reference\n\n### Parsing\n\n#### `safeParse(nic: unknown): SafeParseResult<ParsedNIC>`\n\nThe primary entry point. Returns a discriminated union — **never throws**. Accepts `unknown` so it is safe to call directly on unvalidated external input.\n\n```ts\nimport { safeParse } from '@aakashwije/lanka-nic';\n\nconst result = safeParse(req.body.nic);\n\nif (result.success) {\n  const { birthDate, gender, birthYear } = result.data;\n} else {\n  // result.error is a NICError with .code and .message\n  console.error(result.error.code);  // 'INVALID_FORMAT'\n}\n```\n\n#### `parseNIC(nic: string): ParsedNIC | null`\n\nReturns the parsed result or `null` for any invalid input. Useful with optional chaining.\n\n```ts\nconst age = parseNIC(nic)?.birthDate ?? 'unknown';\n```\n\n#### `parseNICOrThrow(nic: string): ParsedNIC`\n\nThrows a `NICError` on invalid input. Use when you control the input and want to fail fast.\n\n#### `getNICType(nic: string): 'old' | 'new' | 'invalid'`\n\nDetects the format without full semantic validation. Useful for routing logic before parsing.\n\n#### `getBirthDate(nic: string): string | null`\n\nReturns the birth date as `YYYY-MM-DD` (UTC) or `null`.\n\n#### `getGender(nic: string): 'male' | 'female' | null`\n\nResolves gender from the NIC day code.\n\n---\n\n### Validation\n\n#### `validateNIC(nic: string): boolean`\n\nReturns `true` only for fully valid NICs — format, day code range, and date integrity all checked.\n\n---\n\n### Normalization\n\n#### `normalizeNIC(nic: string): string`\n\nStrips surrounding whitespace, collapses internal whitespace, uppercases the check letter for old-format NICs.\n\n```ts\nnormalizeNIC('  950012345v  ');  // '950012345V'\nnormalizeNIC('1995 001 23456');  // '199500123456'\n```\n\n---\n\n### Masking\n\n#### `maskNIC(nic: string): string`\n\nMasks serial digits for safe logging and display. Returns the (normalized) input as-is for invalid NICs.\n\n```ts\nmaskNIC('950012345V');    // '95001****V'     (masks 4 serial digits)\nmaskNIC('199500123456');  // '1995001*****'   (masks 5 serial digits)\n```\n\n---\n\n### Age Calculation\n\n#### `getAge(nic: string, opts?: { now?: Date }): number | null`\n\nCalculates the person's age as of today (UTC). Pass `opts.now` to fix the reference date — useful in tests and audits.\n\n#### `getAgeOn(nic: string, date: Date): number | null`\n\nCalculates age as of a specific reference date. Returns `null` if the date precedes the birth date.\n\n```ts\nimport { getAge, getAgeOn } from '@aakashwije/lanka-nic';\n\ngetAge('950012345V');\n// → current age as of today UTC\n\ngetAgeOn('950012345V', new Date('2025-06-01'));\n// → 30\n\ngetAge('950012345V', { now: new Date('2030-01-01') });\n// → 35\n```\n\n---\n\n### Format Conversion\n\n#### `convertOldToNew(nic: string): string | null`\n\nConverts a valid old-format NIC to an 11-character new-format representation. Returns `null` for non-old-format input.\n\n```ts\nconvertOldToNew('950012345V');  // '19950012345'\n```\n\n> The official 12th check digit cannot be derived from old NIC data. The output is a best-effort 11-digit form; do not treat it as an authoritative new NIC.\n\n---\n\n### Equality\n\n#### `equalsNIC(a: string, b: string): boolean`\n\nReturns `true` when two NICs (old or new format, any casing) refer to the same person and serial — matching on birth year, day-of-year, gender, and serial digits.\n\n```ts\nequalsNIC('950012345V', '950012345v');  // true  (case normalized)\nequalsNIC('950012345V', '950012346V');  // false (different serial)\n```\n\n---\n\n### Type Guards\n\n```ts\nimport { isValidNIC, isOldNIC, isNewNIC } from '@aakashwije/lanka-nic';\n\nisValidNIC('950012345V');    // true  — any valid NIC (narrows to string)\nisOldNIC('950012345V');      // true  — valid old-format NIC\nisNewNIC('199500123456');    // true  — valid new-format NIC\n\n// TypeScript narrowing\nfunction process(value: unknown) {\n  if (isValidNIC(value)) {\n    // value: string\n    parseNIC(value);\n  }\n}\n```\n\n---\n\n### Batch Operations\n\n```ts\nimport { validateBatch, parseBatch } from '@aakashwije/lanka-nic';\n\nvalidateBatch(['950012345V', 'invalid', '199500123456']);\n// [true, false, true]\n\nparseBatch(['950012345V', 'bad']);\n// [ParsedNIC, null]\n```\n\n---\n\n### Test Data Generation\n\n#### `generateNIC(opts: NICGenerateOptions): string`\n\nGenerates a syntactically valid NIC for a given birth year, day, gender, and format. Designed for seeding test fixtures.\n\n```ts\nimport { generateNIC } from '@aakashwije/lanka-nic';\n\ngenerateNIC({ year: 1995, dayOfYear: 1, gender: 'male',   format: 'old' });\n// '950011234V'\n\ngenerateNIC({ year: 1995, dayOfYear: 1, gender: 'female', format: 'new' });\n// '199550100001'  (dayCode = 1 + 500 = 501)\n\ngenerateNIC({ year: 1995, dayOfYear: 1, gender: 'male', format: 'old', serial: 7 });\n// '950010007V'\n```\n\n| Option | Type | Required | Default | Description |\n|--------|------|----------|---------|-------------|\n| `year` | `number` | ✓ | — | Birth year ≥ 1900. Old format requires 1900–1999. |\n| `dayOfYear` | `number` | ✓ | — | Day of year (1–365/366). |\n| `gender` | `'male' \\| 'female'` | ✓ | — | Determines day-code offset (+500 for female). |\n| `format` | `'old' \\| 'new'` | ✓ | — | Output format. |\n| `serial` | `number` | — | `1234` | Serial digits. Must be ≥ 0. |\n\n---\n\n### Error Handling\n\n`NICError` extends `Error` with a structured `code` for programmatic branching:\n\n```ts\nimport { NICError, parseNICOrThrow } from '@aakashwije/lanka-nic';\n\ntry {\n  parseNICOrThrow('not-a-nic');\n} catch (e) {\n  if (e instanceof NICError) {\n    e.code;     // 'INVALID_FORMAT'\n    e.input;    // 'not-a-nic'\n    e.message;  // 'NIC does not match old or new format'\n  }\n}\n```\n\n| Code | Trigger |\n|------|---------|\n| `NON_STRING` | Input is not a string |\n| `EMPTY` | Input is empty or whitespace-only |\n| `INVALID_FORMAT` | Does not match old (`\\d{9}[VvXx]`) or new (`\\d{12}`) pattern |\n| `INVALID_DAY_CODE` | Day code outside `001–366` and `501–866` |\n| `INVALID_DAY_FOR_YEAR` | Day code maps to a day that does not exist in the year (e.g. day 366 on a non-leap year) |\n| `FUTURE_YEAR` | Birth year is after the current UTC year |\n\n---\n\n### Type Reference\n\n```ts\ntype ParsedNIC = {\n  input:      string;             // original input as provided\n  normalized: string;             // whitespace-stripped, suffix uppercased\n  type:       'old' | 'new';\n  valid:      true;\n  birthYear:  number;\n  dayOfYear:  number;             // 1–366, always male-normalised\n  birthDate:  string;             // ISO 8601, UTC e.g. '1995-01-01'\n  gender:     'male' | 'female';\n};\n\ntype SafeParseResult<T> =\n  | { success: true;  data:  T        }\n  | { success: false; error: NICError };\n\ntype NICErrorCode =\n  | 'EMPTY'\n  | 'NON_STRING'\n  | 'INVALID_FORMAT'\n  | 'INVALID_DAY_CODE'\n  | 'INVALID_DAY_FOR_YEAR'\n  | 'FUTURE_YEAR';\n```\n\n---\n\n## CLI\n\nThe `lanka-nic` CLI is bundled with the package.\n\n```bash\nnpm install -g @aakashwije/lanka-nic\n# or one-off\nnpx @aakashwije/lanka-nic <command> <nic>\n```\n\n### Commands\n\n```\nUsage:\n  lanka-nic validate <nic>\n  lanka-nic parse    <nic>\n  lanka-nic mask     <nic>\n  lanka-nic age      <nic>\n\nIf <nic> is omitted, the value is read from stdin.\n```\n\n### Examples\n\n```bash\n# Validate\n$ lanka-nic validate 950012345V\ntrue\n# exit 0\n\n$ lanka-nic validate bad-input\nfalse\n# exit 1\n\n# Parse\n$ lanka-nic parse 950012345V\n{\n  \"input\": \"950012345V\",\n  \"normalized\": \"950012345V\",\n  \"type\": \"old\",\n  \"valid\": true,\n  \"birthYear\": 1995,\n  \"dayOfYear\": 1,\n  \"birthDate\": \"1995-01-01\",\n  \"gender\": \"male\"\n}\n\n# Parse failure → JSON on stderr, exit 1\n$ lanka-nic parse not-a-nic\n{\"error\":\"INVALID_FORMAT\",\"message\":\"NIC does not match old or new format\"}\n\n# Mask\n$ lanka-nic mask 950012345V\n95001****V\n\n# Age\n$ lanka-nic age 950012345V\n30\n\n# Stdin pipeline\n$ echo 950012345V | lanka-nic parse\n$ cat nics.txt | xargs -I{} lanka-nic validate {}\n```\n\n**Exit codes:** `0` success · `1` invalid NIC · `2` usage error\n\n---\n\n## Edge Cases\n\n| Input | Behaviour |\n|-------|-----------|\n| Empty / whitespace-only | Invalid — `EMPTY` |\n| Non-string | Invalid — `NON_STRING` |\n| Day code `000` | Invalid — `INVALID_DAY_CODE` |\n| Day code `367–500` | Invalid — `INVALID_DAY_CODE` |\n| Day code `> 866` | Invalid — `INVALID_DAY_CODE` |\n| Day `366` on a non-leap year | Invalid — `INVALID_DAY_FOR_YEAR` |\n| Future birth year | Invalid — `FUTURE_YEAR` |\n| Lowercase suffix `v` / `x` | Normalized to uppercase |\n| Internal whitespace `93 123 4567 V` | Collapsed and parsed correctly |\n\n---\n\n## Contributing\n\n```bash\ngit clone https://github.com/aakashlk/lanka-nic.git\ncd lanka-nic\nnpm install\n\nnpm test            # run all tests\nnpm run coverage    # generate V8 coverage report\nnpm run typecheck   # TypeScript type-check only (no emit)\nnpm run lint        # ESLint\nnpm run format      # Prettier\nnpm run build       # tsup dual ESM+CJS build\n```\n\nCommits must follow **Conventional Commits** (`feat:`, `fix:`, `chore:`, `docs:` …). Versioning and changelog are fully automated via `semantic-release`.\n\n---\n\n## License\n\n[MIT](LICENSE) © 2024 Contributors\n\n---\n\n<div align=\"center\">\n<sub>Built with precision. Maintained with intent.</sub>\n</div>\n","readmeFilename":"README.md","_rev":"1-4aaededdf5087565e859809db3a7f8ef"}