# @jordanmgsoftware/postcode-nl-validator

Zero-setup Dutch postcode validator. Ships the data inside the package, so `npm install` is enough — no database to build, no environment variables, no network calls at runtime. Works identically on local Node, Vercel / AWS Lambda serverless, and any Node 20+ environment.

```ts
import { PostcodeValidator } from '@jordanmgsoftware/postcode-nl-validator';

const v = new PostcodeValidator();

v.validate('1011 ab');
// {
//   valid: true,
//   normalizedPostcode: '1011AB',
//   reason: 'ok',
//   matches: [{
//     postcode: '1011AB',
//     straat: 'De Ruijterkade',
//     woonplaats: 'Amsterdam',
//     gemeente: 'Amsterdam',
//     provincie: 'Noord-Holland',
//   }],
// }
```

## Install

```bash
npm install @jordanmgsoftware/postcode-nl-validator
```

That's it. No post-install step, no native modules, no environment variables.

## Scope

This package performs **postcode-level validation**:

- Checks that a Dutch postcode has valid PostNL format (e.g. rejects `0123AB`, `1011SA`).
- Resolves a postcode to its `straat`, `woonplaats`, `gemeente`, `provincie`.

It does **not** validate individual house numbers. The full BAG per-address dataset is ~10 M rows (~1 GB) and cannot be shipped inside an npm package. For most address forms, postcode-level validation is enough: once you know the postcode is real and belongs to a given street, the user's `huisnummer` is accepted as-is.

## API

### `new PostcodeValidator(options?)`

```ts
interface ValidatorOptions {
  /** Override the bundled data directory. Tests/forks only. */
  dataDir?: string;
}
```

### Methods

| Method | Returns | Notes |
|---|---|---|
| `validate(postcode, huisnummer?)` | `ValidationResult` | `huisnummer` is accepted for back-compat but ignored |
| `lookup(postcode)` | `Address[]` | 0 or 1 entries |
| `findExact(postcode)` | `Address \| null` | single entry or null |
| `exists(postcode)` | `boolean` | cheap existence check |
| `count()` | `number` | total postcodes in the bundled dataset |
| `dataVersion()` | `string \| null` | version of the bundled dataset |
| `close()` | `void` | no-op, kept for API compatibility |

### Types

```ts
interface Address {
  postcode: string;       // canonical '1234AB'
  straat: string;
  woonplaats: string;
  gemeente: string;
  provincie: string;
}

interface ValidationResult {
  valid: boolean;
  normalizedPostcode: string | null;
  matches: Address[];
  reason?: 'invalid_format' | 'invalid_huisnummer' | 'postcode_not_found' | 'ok';
}
```

### Standalone helpers

No instance required, no data loaded:

```ts
import {
  isValidPostcodeFormat,
  normalizePostcode,
  parseHuisnummer,
} from '@jordanmgsoftware/postcode-nl-validator';

isValidPostcodeFormat('1011 ab');     // true
normalizePostcode('  1011 ab  ');     // '1011AB'
parseHuisnummer('42');                // 42
```

## How it works

- At build time, the Dutch address dataset is collapsed to one record per unique postcode and sharded by the two-letter suffix (`AA`..`ZZ`) into ~500 small JSON files under `data/`.
- At runtime, `PostcodeValidator` reads only the shard a request touches (typically ~100 KB, parsed in under a millisecond) and caches it in memory.
- No SQLite, no native modules, no network. The whole package is pure JavaScript.

### Runtime footprint

- Tarball: ~10-15 MB gzipped on npm.
- Per lookup: one `readFileSync` on cold shard (sub-ms), `Map.get` on warm shard.
- Memory: only shards that have been queried are kept in memory.

## Vercel / serverless

Works on Vercel's Node runtime out of the box. No extra configuration, no environment variables, no external calls. The JSON shards are included in the deployed function bundle because they live inside `node_modules/@jordanmgsoftware/postcode-nl-validator/data/`.

On AWS Lambda and similar platforms, the same applies.

## Data updates

The dataset is pinned to the package version. To pick up a refreshed dataset, run `npm update @jordanmgsoftware/postcode-nl-validator` like you would with any dependency. You can inspect the current version at runtime via `validator.dataVersion()`.

## Migration from v0.x

v0.x required you to build a local SQLite database and pass `dbPath` or set `POSTCODE_NL_DB`. v1.x removes both:

- `new PostcodeValidator()` — no options required.
- `validate(postcode, huisnummer)` still compiles; `huisnummer` is now **ignored**.
- The CLI, HTTP server, and `postcode-nl build` command have been removed.
- `POSTCODE_NL_DB` is a no-op and can be dropped from your env.
- `Address` no longer includes `huisnummer`, `huisletter`, `huisnummertoevoeging`, `lat`, `lon`.

## Maintainers: regenerating the bundled data

```bash
npm install
npm run build:data -- --input ../Nederland.csv.zst
npm run build
npm publish
```

`build:data` streams the upstream `Nederland.csv.zst`, dedupes to postcode-level, and regenerates `data/*.json` plus `data/manifest.json`.
