diff --git a/.changeset/shorter-readmes.md b/.changeset/shorter-readmes.md
new file mode 100644
index 0000000..01e6d56
--- /dev/null
+++ b/.changeset/shorter-readmes.md
@@ -0,0 +1,6 @@
+---
+'@gatepost/core': patch
+'@gatepost/client': patch
+---
+
+The README of each package is shorter and links the docs site for the full guide and the reference.
diff --git a/README.md b/README.md
index 668e7ed..e240310 100644
--- a/README.md
+++ b/README.md
@@ -1,50 +1,58 @@
-
-
+
+
TypeScript packages for Nigeria's National Digital Postcode.
+ + + ++ Docs + · + Playground + · + TypeScript guide + · + Contributing + · + Discussions +
-# Gatepost for TypeScript +> Unofficial. Not made or endorsed by NIPOST. -TypeScript packages for Nigeria's National Digital Postcode. +## Packages -> Unofficial. Not made or endorsed by NIPOST. +| Package | What it is | Status | +| ----------------------------------------------- | ----------------------------------------------- | -------------------------- | +| [`@gatepost/core`](packages/core) | Parse, check and format postcodes offline. | Alpha, not on npm yet | +| [`@gatepost/client`](packages/client) | Call NIPOST's gateway, with retries and limits. | Alpha, not on npm yet | +| [`@gatepost/mock-server`](packages/mock-server) | Mock NIPOST's gateway for client tests. | Private, never goes to npm | + +## Example + +```ts +import { parse } from '@gatepost/core'; + +const parsed = parse('ek 01 a03 fk 01'); +if (parsed.ok) { + parsed.value.canonical; // 'EK-01-A03-FK-01' +} +``` -| Package | What it does | -| ----------------------------------------------- | --------------------------------------------------- | -| [`@gatepost/core`](packages/core) | Parse, check and format postcodes offline. | -| [`@gatepost/client`](packages/client) | Call NIPOST's gateway, with retries and limits. | -| [`@gatepost/mock-server`](packages/mock-server) | Mock NIPOST's gateway for client tests. Not on npm. | +The [TypeScript guide](https://gatepost-dev.github.io/docs/guides/typescript/) and the [client guide](https://gatepost-dev.github.io/docs/guides/typescript-client/) show every function. The [reference](https://gatepost-dev.github.io/docs/reference/js/core/) lists every export. ## Develop -You need Node 24, `corepack enable`, Python 3.11 or later and `uv`. - - git clone --recurse-submodules https://github.com/gatepost-dev/js - pnpm install - pnpm check - -`pnpm check` also builds the packages. Then it runs publint, attw, size-limit and API Extractor on -the result, and it starts each built package once. It checks the licence of each dependency and of -each file, and it checks the workflows with zizmor. It starts REUSE and zizmor with `uvx` at pinned -versions. REUSE needs an encoding detector, and `uvx` installs REUSE without one, so the script pins -`charset-normalizer` as well. Renovate updates the three pins. After a change to the public API of a -package, run `pnpm --filter
+
+
Call NIPOST's postcode gateway from TypeScript: look up a postcode, find the postcode of a place, and complete a postcode that a user is typing.
+ + + ++ Docs + · + Playground + · + Guide + · + Reference + · + Contributing + · + Discussions +
> Unofficial. Not made or endorsed by NIPOST. -[](https://github.com/gatepost-dev/js/actions/workflows/ci.yml) -[](https://github.com/gatepost-dev/js/blob/main/LICENSE) - ## Install ```sh pnpm add @gatepost/client ``` -The package installs `@gatepost/core`, which parses each postcode before a request goes out. +The first alpha is not published yet, so this command does not work today. The package installs `@gatepost/core`, which parses each postcode before a request goes out. -## Quickstart +## Use ```ts import { PostcodeClient } from '@gatepost/client'; @@ -33,50 +52,9 @@ result.valid; // true The examples use `FC-01-Z99-ZZ-01`, a synthetic postcode, and show the answers of Gatepost's mock server, which CI runs them against. The real gateway does not know this postcode. -Get a key from NIPOST's dashboard. The gateway refuses every call without a key. A key that starts with `nipost_test_` or `nipost_live_` is a secret key, so keep it on a server. The client refuses a secret key in a page that has a `document`, such as a web page. - -## What it does - -- Looks up a postcode at a lookup level from 1 to 5. -- Finds the unit at a coordinate. -- Offers the values of the segment that a user is typing. -- Checks each postcode offline first, so a bad code sends no request. -- Retries a failed request when a retry can help, and waits between attempts. -- Shares one request between identical calls, and sends at most 4 requests at a time. -- Keeps results for a time that you choose. It keeps none by default. +Get a key from NIPOST's dashboard. The gateway refuses every call without a key. A key that starts with `nipost_test_` or `nipost_live_` is a secret key, so keep it on a server. The client refuses a secret key in a page that has a `document`. -The client sends only the requests that you make. It has no logging. It sends your key only in the `X-API-Key` header, and none of its own error messages holds the key. - -### Look up a postcode - -`lookup` takes a postcode in any form, or a postcode that `parse` from `@gatepost/core` returned. A postcode that the gateway does not know gives `valid` false and a `status` of `not_found`, not an error. - -```ts -import { PostcodeClient } from '@gatepost/client'; - -const client = new PostcodeClient({ apiKey: process.env['NIPOST_API_KEY'] }); -const unknown = await client.lookup('FC-01-Z99-ZZ-02'); -unknown.valid; // false -unknown.status; // 'not_found' -``` - -A key holds a lookup level, and `lookup` sends level 1 when you give none. NIPOST's docs say that level 1 tells only whether a postcode exists, level 2 adds the names of the places that hold it and a recent house address, and level 3 adds what the building is used for. A call above the key's level fails with `forbidden`. The gateway does not say which level it sent, so `levelReceived` comes from the fields of the response. - -```ts -import { PostcodeClient } from '@gatepost/client'; - -const client = new PostcodeClient({ apiKey: process.env['NIPOST_API_KEY'] }); -const result = await client.lookup('FC-01-Z99-ZZ-01', { level: 2 }); -result.levelRequested; // 2 -result.levelReceived; // 2 -result.administrativeAddress?.lgaName; // 'SYNTHETIC LGA' -``` - -Gatepost has seen only level 1 responses from the gateway. The fields of levels 2 to 5 are documented, not observed: they follow NIPOST's docs, and no Gatepost test has seen them from the gateway yet. The example above shows the answer of the mock server. - -### Handle errors - -Each call raises a `PostcodeError` when it cannot give a result. Its `code` is one of `invalid_input`, `unauthorized`, `insufficient_credits`, `origin_not_allowed`, `forbidden`, `rate_limited`, `server_error`, `unexpected_response`, `network_error` and `timeout`. `unexpected_response` means that the gateway answered 200 with a body that the client cannot read. `status` holds the HTTP status, and `apiCode` holds the gateway's own error code. +Each call raises a `PostcodeError` when it cannot give a result. Its `code` is one of `invalid_input`, `unauthorized`, `insufficient_credits`, `origin_not_allowed`, `forbidden`, `rate_limited`, `server_error`, `unexpected_response`, `network_error` and `timeout`. ```ts import { PostcodeClient, PostcodeError } from '@gatepost/client'; @@ -92,78 +70,38 @@ try { } ``` -`900108` is a legacy postcode, so the client sends no request for it. Show the user the message for the error code, not the error's own message, which is for developers. - -When the gateway limits a key, the error is `rate_limited`. The client waits and tries again when the gateway asks for a wait of 10 seconds or less. For a longer wait, `retryAfterMs` holds it, so you can tell the user when to try again. - -### Find the postcode of a place +`900108` is a legacy postcode, so the client sends no request for it. -`reverse` takes a latitude and a longitude in degrees, and an optional radius in metres. The gateway uses 25 m when you give no radius, and 250 m at most. `found` can be true while `unit` is null: the gateway then found an area, but no unit within the radius. +## Main functions -```ts -import { PostcodeClient } from '@gatepost/client'; - -const client = new PostcodeClient({ apiKey: process.env['NIPOST_API_KEY'] }); -const place = await client.reverse(9, 7); -place.found; // true -place.unit?.postcode.canonical; // 'FC-01-Z99-ZZ-01' -place.area; // 'FC-01-Z99-ZZ' -``` - -A GPS fix is only as good as its accuracy. `precisionForAccuracy` from `@gatepost/core` tells you which segment a fix supports. - -### Complete a typed postcode - -`autocomplete` takes the text that a user typed. It names the segment that the user is typing, and the gateway's values for it. Each value comes with the partial postcode that it completes. - -```ts -import { PostcodeClient } from '@gatepost/client'; - -const client = new PostcodeClient({ apiKey: process.env['NIPOST_API_KEY'] }); -const typing = await client.autocomplete('fc 01 z'); -typing.segment; // 'district' -typing.suggestions[0]?.code; // 'Z99' -typing.suggestions[0]?.postcode?.canonical; // 'FC-01-Z99' -``` - -Empty text sends no request and fails with `invalid_input`, because the gateway does not answer it. A timeout of `autocomplete` gets no retry, because the next keystroke replaces the call. `autocomplete` waits 15 seconds for one attempt, not 8, unless you set `timeoutMs`. - -### Cancel a call - -Each call takes an `AbortSignal`. A cancelled call ends with the signal's reason, not with a `PostcodeError`. When every caller of a shared request cancels, the request stops and frees its place. - -```ts -import { PostcodeClient } from '@gatepost/client'; - -const client = new PostcodeClient({ apiKey: process.env['NIPOST_API_KEY'] }); -const keystroke = new AbortController(); -const call = client.autocomplete('fc 0', { signal: keystroke.signal }); -keystroke.abort(); -const outcome = await call.catch((reason: unknown) => reason); -outcome === keystroke.signal.reason; // true -``` - -### Choose the settings +| Call | What it does | +| -------------------------- | ------------------------------------------------------------- | +| `new PostcodeClient(opts)` | Makes a client. See the settings below. | +| `client.lookup` | Looks up a postcode at a lookup level from 1 to 5. | +| `client.reverse` | Finds the unit at a latitude and a longitude. | +| `client.autocomplete` | Offers the values of the segment that a user is typing. | +| `client.clearCache` | Removes every result that the client kept. | +| `PostcodeError` | The error of a call. It holds `code`, `status` and `apiCode`. | +| `SPEC_VERSION` | The version of the Gatepost spec that the package implements. | | Option | Meaning | Default | | ------------ | --------------------------------------------------- | ---------------------------------- | | `apiKey` | the key for the `X-API-Key` header | none, so no key is sent | | `baseUrl` | the gateway's address | `https://api.postcode.gov.ng` | -| `transport` | the function that sends each request, like `fetch` | the platform's `fetch` | | `timeoutMs` | the longest wait for one attempt, in milliseconds | 8000, and 15000 for `autocomplete` | | `maxRetries` | the most retries after the first attempt | 2 | | `cacheTtlMs` | how long the client keeps a result, in milliseconds | 0, which keeps none | -A client retries a 502, 503 or 504, a lost connection and a timeout. It waits 500 ms before the first retry and 1000 ms before the second, plus up to 250 ms at random. With `cacheTtlMs` above 0, an identical call within that time gets the kept result with no request. Errors stay out of the cache, and `clearCache` removes every kept result. The cache of each call type keeps at most 1000 results and drops the oldest first. Identical calls share one result object, and so does the cache. Do not change a result that a call returned. +The client also takes a `transport` option, a function like `fetch`, and each call takes an `AbortSignal`. -```ts -import { PostcodeClient } from '@gatepost/client'; +## Features -const apiKey = process.env['NIPOST_API_KEY']; -const client = new PostcodeClient({ apiKey, cacheTtlMs: 60_000 }); -await client.lookup('FC-01-Z99-ZZ-01'); -client.clearCache(); -``` +- Checks each postcode offline first, so a bad code sends no request. +- Retries a failed request when a retry can help, and waits between attempts. +- Shares one request between identical calls, and sends at most 4 requests at a time. +- Keeps results for a time that you choose. It keeps none by default. +- Sends your key only in the `X-API-Key` header. It has no logging. +- Is under 6 kB with `@gatepost/core`, minified and brotlied. ## Requirements @@ -173,25 +111,17 @@ client.clearCache(); | Module format | ESM only | | Gatepost spec | 0.2.0 | -The package exports `SPEC_VERSION`, the version of the Gatepost spec that it implements. - -```ts -import { SPEC_VERSION } from '@gatepost/client'; - -SPEC_VERSION; // '0.2.0' -``` - ## Docs -The API report is in [`etc/client.api.md`](https://github.com/gatepost-dev/js/blob/main/packages/client/etc/client.api.md). [`client.md`](https://github.com/gatepost-dev/spec/blob/main/client.md) in the spec repo defines the results, the errors and the retries, and every Gatepost client passes the same contract scenarios. +The [guide](https://gatepost-dev.github.io/docs/guides/typescript-client/) covers levels, errors, cancelling and the settings. The [reference](https://gatepost-dev.github.io/docs/reference/js/client/) lists every export. The API report is in [`etc/client.api.md`](https://github.com/gatepost-dev/js/blob/main/packages/client/etc/client.api.md). [`client.md`](https://gatepost-dev.github.io/docs/spec/client/) defines the results, the errors and the retries, and every Gatepost client passes the same contract scenarios. ## Support -Ask questions and report bugs in [GitHub Issues](https://github.com/gatepost-dev/js/issues). Report security problems privately, as [`SECURITY.md`](https://github.com/gatepost-dev/.github/blob/main/SECURITY.md) describes. +Ask questions in [GitHub Discussions](https://github.com/gatepost-dev/js/discussions). Report bugs in [GitHub Issues](https://github.com/gatepost-dev/js/issues). Report security problems through the [private reporting form](https://github.com/gatepost-dev/js/security/advisories/new). -## Contributing +## Develop -Read [`CONTRIBUTING.md`](https://github.com/gatepost-dev/.github/blob/main/CONTRIBUTING.md) before you open a pull request. +Read [`docs/develop.md`](https://github.com/gatepost-dev/js/blob/main/docs/develop.md) and the [contributing guide](https://github.com/gatepost-dev/.github/blob/main/CONTRIBUTING.md) before you open a pull request. ## Licence diff --git a/packages/client/test/readme.test.ts b/packages/client/test/readme.test.ts index fd362d2..01336af 100644 --- a/packages/client/test/readme.test.ts +++ b/packages/client/test/readme.test.ts @@ -17,10 +17,8 @@ describe('README', () => { useMockServer(); runExamples(EXAMPLES, CLIENT_SOURCES); - it.each(Object.keys(client))('shows %s in an example', (name) => { - expect(EXAMPLES.map((example) => example.source).join('\n')).toMatch( - new RegExp(`\\b${name}\\b`), - ); + it.each(Object.keys(client))('names %s in the README', (name) => { + expect(README).toMatch(new RegExp(`\\b${name}\\b`)); }); it('says that the package is unofficial', () => { diff --git a/packages/core/README.md b/packages/core/README.md index 6ea4ccf..dc23b14 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -1,24 +1,45 @@ -
+
+
Parse, check and format Nigeria's digital postcodes, with no network access.
+ + + ++ Docs + · + Playground + · + Guide + · + Reference + · + Contributing + · + Discussions +
> Unofficial. Not made or endorsed by NIPOST. -[](https://github.com/gatepost-dev/js/actions/workflows/ci.yml) -[](https://github.com/gatepost-dev/js/blob/main/LICENSE) - ## Install ```sh pnpm add @gatepost/core ``` -## Quickstart +The first alpha is not published yet, so this command does not work today. + +## Use ```ts import { parse } from '@gatepost/core'; @@ -26,153 +47,59 @@ import { parse } from '@gatepost/core'; const parsed = parse('ek 01 a03 fk 01'); if (parsed.ok) { parsed.value.canonical; // 'EK-01-A03-FK-01' + parsed.value.display; // 'EK 01 A03 FK 01' } else { parsed.error.code; // for example 'bad_length' } ``` -`parse` accepts any spacing, dashes and letter case. For any string, it returns a result and never throws. - -## What it does - -- Reads codes that users type or paste. -- Names the problem in a bad code, and suggests a fix for common typos, such as O in place of 0. -- Recognises old 6-digit postcodes. -- Writes a code in its compact, canonical and display forms. -- Shortens a code to its area, district, LGA or state, and checks whether one code contains another. -- Hides the unit of a code for logs. - -The package checks the form of a postcode, with no network access. It checks the length, the characters, the state code and the rule for each segment. It cannot tell whether a building has a given postcode. Only NIPOST's API can say that. - -### Read a postcode - -A postcode has five segments: state, LGA, district, area and unit. When `parse` succeeds, `value` holds the postcode in its three forms, its segments and its precision. Set `allowPartial` to accept a code that stops after a segment. - -```ts -import { parse } from '@gatepost/core'; - -const full = parse('ek 01 a03 fk 01'); -if (full.ok) { - full.value.compact; // 'EK01A03FK01' - full.value.canonical; // 'EK-01-A03-FK-01' - full.value.display; // 'EK 01 A03 FK 01' - full.value.segments.state; // 'EK' - full.value.precision; // 'unit' -} - -const district = parse('EK-01-A03', { allowPartial: true }); -if (district.ok) { - district.value.precision; // 'district' -} -``` - -### Handle a bad code +`parse` accepts any spacing, dashes and letter case. For any string, it returns a result and never throws. It reads at most 64 code points of input, and a longer input fails with `bad_length`. -When `parse` fails, `error.code` gives the reason: `empty`, `legacy_code`, `bad_character`, `bad_length`, `unknown_state` or `bad_segment`. For `unknown_state` and `bad_segment`, `error.segment` names the failing segment, and `error.suggestion` can hold a fixed code. +When a code has a common typo, the error holds a `suggestion`. Show it to the user. `parse` never accepts the fixed code for you. ```ts -import { parse } from '@gatepost/core'; +import { contains, parse, redact, truncate } from '@gatepost/core'; const mistyped = parse('ek o1 a03 fk 01'); // a letter o where a zero belongs if (!mistyped.ok) { mistyped.error.code; // 'bad_segment' - mistyped.error.segment; // 'lga' mistyped.error.suggestion; // 'EK-01-A03-FK-01' } -``` - -A suggestion is a hint only. `parse` never returns the fixed code as a success. Show the suggestion to the user, and let the user decide. - -### Limit the input - -`parse` reads at most 64 code points of input. This is the input limit. Longer input fails with `bad_length`, even when it holds a valid code. The check runs before `parse` cleans the input, so long text cannot stall a server. `isLegacy` applies the same limit. - -```ts -import { parse } from '@gatepost/core'; - -const code = 'EK-01-A03-FK-01'; -parse(code.padEnd(64)).ok; // true -parse(code.padEnd(65)).ok; // false -``` - -### Clean typed text - -`normalize` cleans text that a user typed or pasted. It applies Unicode NFKC, removes white space, hyphens, dashes, full stops and some zero-width characters, and changes ASCII letters to upper case. It does not check the result. It has no input limit, so call `parse` for text from an untrusted source. The result depends on the Unicode version of the runtime. The spec names Unicode 17.0, which Node 22.22.2 and later 22.x releases have, and so do Node 24.15.0 and later. - -```ts -import { normalize } from '@gatepost/core'; - -normalize(' ek-01 a03.fk-01 '); // 'EK01A03FK01' -``` - -### Recognise a legacy postcode - -A legacy postcode is an old 6-digit postcode. It names an area, not a building. `isLegacy` recognises one, and `parse` fails on it with `legacy_code`. - -```ts -import { isLegacy, parse } from '@gatepost/core'; - -isLegacy('900 108'); // true -isLegacy('EK-01-A03-FK-01'); // false - -const legacy = parse('900108'); -if (!legacy.ok) { - legacy.error.code; // 'legacy_code' -} -``` - -### Name a state - -`stateName` returns the English name of a state from its two-letter code, or `null` for an unknown code. - -```ts -import { stateName } from '@gatepost/core'; - -stateName('EK'); // 'Ekiti' -stateName('fc'); // 'Federal Capital Territory' -stateName('XX'); // null -``` - -### Match a GPS fix to a precision - -`precisionForAccuracy` returns the most precise segment that a GPS fix supports, from its accuracy in metres. It returns `lga` when the accuracy is unknown or too large. - -```ts -import { precisionForAccuracy } from '@gatepost/core'; - -precisionForAccuracy(6); // 'unit' -precisionForAccuracy(35); // 'district' -precisionForAccuracy(null); // 'lga' -``` - -### Move between segments - -`truncate` shortens a postcode to a less precise segment. It throws a `RangeError` when you ask for more precision than the code has. `parent` returns the postcode one segment up, or `null` for a state. `contains` tells whether one postcode lies inside another. - -```ts -import { contains, parent, parse, truncate } from '@gatepost/core'; const building = parse('EK-01-A03-FK-01'); const district = parse('EK-01-A03', { allowPartial: true }); if (building.ok && district.ok) { truncate(building.value, 'district').canonical; // 'EK-01-A03' - parent(building.value)?.canonical; // 'EK-01-A03-FK' contains(district.value, building.value); // true + redact(building.value); // 'EK-01-A03-FK-**' } ``` -### Hide the unit in logs +## Main functions + +| Function | What it does | +| ---------------------- | ------------------------------------------------------------------------ | +| `parse` | Reads text and returns a postcode or an error. Never throws. | +| `normalize` | Cleans typed or pasted text. It does not check the result. | +| `isLegacy` | Recognises an old 6-digit postcode. | +| `stateName` | Returns the name of a state from its two-letter code, or `null`. | +| `precisionForAccuracy` | Returns the most precise segment that a GPS accuracy in metres supports. | +| `truncate` | Shortens a postcode to a less precise segment. | +| `parent` | Returns the postcode one segment up, or `null` for a state. | +| `contains` | Tells whether one postcode lies inside another. | +| `redact` | Replaces the unit with `**`, so that a log does not show the building. | +| `SPEC_VERSION` | The version of the Gatepost spec that the package implements. | + +## Features + +- Reads codes that users type or paste, including full-width characters. +- Names the problem in a bad code, and suggests a fix for common typos such as O in place of 0. +- Recognises old 6-digit postcodes. +- Writes a code in its compact, canonical and display forms. +- Moves between the levels of a postcode, from a building up to its state. +- Has no dependencies. It is about 2 kB, minified and brotlied. -`redact` replaces the unit with `**`, so that a log shows the area but not the building. - -```ts -import { parse, redact } from '@gatepost/core'; - -const building = parse('EK-01-A03-FK-01'); -if (building.ok) { - redact(building.value); // 'EK-01-A03-FK-**' -} -``` +The package checks the form of a postcode. It cannot tell whether a building has a given postcode. Only NIPOST's API can say that. For that, use [`@gatepost/client`](../client). ## Requirements @@ -182,25 +109,17 @@ if (building.ok) { | Module format | ESM only | | Gatepost spec | 0.2.0 | -The package exports `SPEC_VERSION`, the version of the Gatepost spec that it implements. - -```ts -import { SPEC_VERSION } from '@gatepost/core'; - -SPEC_VERSION; // '0.2.0' -``` - ## Docs -The API report is in [`etc/core.api.md`](https://github.com/gatepost-dev/js/blob/main/packages/core/etc/core.api.md). The Gatepost docs site will hold the full guide. +The [guide](https://gatepost-dev.github.io/docs/guides/typescript/) shows each function with an example. The [reference](https://gatepost-dev.github.io/docs/reference/js/core/) lists every export. Try the package in the [playground](https://gatepost-dev.github.io/docs/playground/). The API report is in [`etc/core.api.md`](https://github.com/gatepost-dev/js/blob/main/packages/core/etc/core.api.md). ## Support -Ask questions and report bugs in [GitHub Issues](https://github.com/gatepost-dev/js/issues). Report security problems privately, as [`SECURITY.md`](https://github.com/gatepost-dev/.github/blob/main/SECURITY.md) describes. +Ask questions in [GitHub Discussions](https://github.com/gatepost-dev/js/discussions). Report bugs in [GitHub Issues](https://github.com/gatepost-dev/js/issues). Report security problems through the [private reporting form](https://github.com/gatepost-dev/js/security/advisories/new). -## Contributing +## Develop -Read [`CONTRIBUTING.md`](https://github.com/gatepost-dev/.github/blob/main/CONTRIBUTING.md) before you open a pull request. +Read [`docs/develop.md`](https://github.com/gatepost-dev/js/blob/main/docs/develop.md) and the [contributing guide](https://github.com/gatepost-dev/.github/blob/main/CONTRIBUTING.md) before you open a pull request. ## Licence diff --git a/packages/core/test/readme.test.ts b/packages/core/test/readme.test.ts index 625fa2a..6a143e6 100644 --- a/packages/core/test/readme.test.ts +++ b/packages/core/test/readme.test.ts @@ -15,10 +15,8 @@ const EXAMPLES = tsBlocks(README).map((source, index) => ({ describe('README', () => { runExamples(EXAMPLES); - it.each(Object.keys(core))('shows %s in an example', (name) => { - expect(EXAMPLES.map((example) => example.source).join('\n')).toMatch( - new RegExp(`\\b${name}\\b`), - ); + it.each(Object.keys(core))('names %s in the README', (name) => { + expect(README).toMatch(new RegExp(`\\b${name}\\b`)); }); it('names the input limit of parse', () => { diff --git a/packages/mock-server/README.md b/packages/mock-server/README.md index 3084524..565bdb7 100644 --- a/packages/mock-server/README.md +++ b/packages/mock-server/README.md @@ -1,20 +1,37 @@ -
+
+
A local mock of NIPOST's postcode gateway, for the tests of Gatepost's clients.
+ + + ++ Docs + · + Playground + · + Client spec + · + Contributing + · + Discussions +
> Unofficial. Not made or endorsed by NIPOST. -[](https://github.com/gatepost-dev/js/actions/workflows/ci.yml) -[](https://github.com/gatepost-dev/js/blob/main/LICENSE) - ## Install -The package does not go to npm. Run it from a clone of this repo. It needs no install step, because it has no dependencies and Node runs its TypeScript source. +The package is private and does not go to npm. Run it from a clone of this repo. It needs no install step, because it has no dependencies and Node runs its TypeScript source. ```sh git clone --recurse-submodules https://github.com/gatepost-dev/js @@ -23,14 +40,14 @@ node js/packages/mock-server/src/main.ts The command listens on port 4010 and prints its address. `GET /healthz` answers 200 with no key. -A release tag starts a workflow that publishes the image `ghcr.io/gatepost-dev/postcode-mock`. The image listens on port 4010. Until the first release, build the image from the root of a clone: +A release tag starts a workflow that publishes the image `ghcr.io/gatepost-dev/postcode-mock:0.1.0`. The image is not public yet, so a pull of it fails today. Build the image from the root of a clone: ```sh docker build --file packages/mock-server/Dockerfile --tag postcode-mock . docker run --publish 4010:4010 postcode-mock ``` -## Quickstart +## Use ```ts import { startMockServer } from '@gatepost/mock-server'; @@ -45,13 +62,12 @@ await mock.close(); `body` is `{ data: { postcode: 'FC-01-Z99-ZZ-01', valid: true, status: 'valid', verified: false } }`, the gateway's answer at lookup level 1. -## What it does +## Features -- It answers the six gateway endpoints with the synthetic responses in `spec/fixtures`. `spec/fixtures/README.md` says which request gets which response. -- It treats each key in `spec/fixtures/keys.json` in its own way: a lookup level from 1 to 3, no credits, no lookup scope, a rate limit, or a publishable key for the origin `http://localhost:3000`. A request with no key gets 401 `auth_required`, and any other key gets 401 `invalid_api_key`. -- It counts the requests of each key in each clock minute, and sends the `X-RateLimit-Limit` and `X-RateLimit-Remaining` headers, as the gateway does. -- It does not answer an empty `q` of `autocomplete`, because the gateway does not answer one. The connection stays open until the client gives up. -- A request with the headers `X-Scenario-Id` and `X-Scenario-Run` gets the next response of that contract scenario in `spec/contract`. The CORS headers allow both, so browser tests can send them. The gateway itself does not allow them. +- Answers the six gateway endpoints with the synthetic responses in `spec/fixtures`. +- Treats each key in `spec/fixtures/keys.json` in its own way: a lookup level, no credits, a rate limit, or a publishable key. +- Counts the requests of each key in each clock minute, and sends the `X-RateLimit-Limit` and `X-RateLimit-Remaining` headers. +- Plays a contract scenario from `spec/contract` for a request with the headers `X-Scenario-Id` and `X-Scenario-Run`. | Variable | Meaning | Default | | ------------------- | ----------------------------------------------- | ----------------------------------- | @@ -60,10 +76,6 @@ await mock.close(); | `MOCK_DELAY_MS` | a wait before every response, for timeout tests | 0 | | `GATEPOST_SPEC_DIR` | the spec folder | the `spec` submodule | -An empty value counts as no value. At start, the server checks the shape of each file in the spec -folder and names a broken file. It does not check a fixture body against the OpenAPI file. Point -`GATEPOST_SPEC_DIR` only at a spec folder that `make check` has passed. - ## Requirements | Requirement | Version | @@ -73,16 +85,16 @@ folder and names a broken file. It does not check a fixture body against the Ope ## Docs -`spec/client.md` defines the behaviour that the contract scenarios test, and `spec/contract/README.md` defines their format. +`spec/fixtures/README.md` says which request gets which response. `spec/client.md` defines the behaviour that the contract scenarios test, and `spec/contract/README.md` defines their format. ## Support -Report bugs in GitHub Issues. Report security problems privately, as `SECURITY.md` describes. +Ask questions in [GitHub Discussions](https://github.com/gatepost-dev/js/discussions). Report bugs in [GitHub Issues](https://github.com/gatepost-dev/js/issues). Report security problems through the [private reporting form](https://github.com/gatepost-dev/js/security/advisories/new). -## Contributing +## Develop -Read [`CONTRIBUTING.md`](https://github.com/gatepost-dev/.github/blob/main/CONTRIBUTING.md) before you open a pull request. +Read [`docs/develop.md`](https://github.com/gatepost-dev/js/blob/main/docs/develop.md) and the [contributing guide](https://github.com/gatepost-dev/.github/blob/main/CONTRIBUTING.md) before you open a pull request. ## Licence -Apache-2.0. See `LICENSE` and `NOTICE`. +Apache-2.0. See [`LICENSE`](https://github.com/gatepost-dev/js/blob/main/LICENSE) and [`NOTICE`](https://github.com/gatepost-dev/js/blob/main/NOTICE).