@mailwoman/api 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -0
- package/out/app.d.ts +35 -0
- package/out/app.d.ts.map +1 -0
- package/out/app.js +92 -0
- package/out/app.js.map +1 -0
- package/out/engine.d.ts +49 -0
- package/out/engine.d.ts.map +1 -0
- package/out/engine.js +11 -0
- package/out/engine.js.map +1 -0
- package/out/index.d.ts +22 -0
- package/out/index.d.ts.map +1 -0
- package/out/index.js +22 -0
- package/out/index.js.map +1 -0
- package/out/routes.d.ts +36 -0
- package/out/routes.d.ts.map +1 -0
- package/out/routes.js +319 -0
- package/out/routes.js.map +1 -0
- package/out/schema.d.ts +177 -0
- package/out/schema.d.ts.map +1 -0
- package/out/schema.js +168 -0
- package/out/schema.js.map +1 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# @mailwoman/api
|
|
2
|
+
|
|
3
|
+
The **native [Mailwoman](https://mailwoman.sister.software) HTTP API** — an engine-agnostic `/v1` surface
|
|
4
|
+
(parse, geocode, batch, resolve, format) plus health, metrics, and an emitted OpenAPI document. Unlike its
|
|
5
|
+
drop-in siblings ([`@mailwoman/nominatim`](../nominatim), [`@mailwoman/photon`](../photon),
|
|
6
|
+
[`@mailwoman/libpostal`](../libpostal)), nothing here mimics a third-party API — this is Mailwoman's own
|
|
7
|
+
wire contract, so request bodies are strict and validator-enforced.
|
|
8
|
+
|
|
9
|
+
## Engine contract
|
|
10
|
+
|
|
11
|
+
The package takes a `MailwomanAPIEngine` — every method optional. An absent method answers `501` (`/v1/parse`)
|
|
12
|
+
or `503` (`/v1/geocode`, `/v1/batch`, `/v1/resolve`, `/v1/reload` — deps missing in production). `format` is
|
|
13
|
+
the one exception: it's wired in-package from [`@mailwoman/formatter`](../formatter) and always available,
|
|
14
|
+
with no engine method at all.
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import type { MailwomanAPIEngine } from "@mailwoman/api"
|
|
18
|
+
|
|
19
|
+
const engine: MailwomanAPIEngine = {
|
|
20
|
+
parse: async (address, opts) => {
|
|
21
|
+
/* → { input, solutions, debug? } */
|
|
22
|
+
},
|
|
23
|
+
geocode: async (address) => {
|
|
24
|
+
/* → GeocodeResult, passed through verbatim */
|
|
25
|
+
},
|
|
26
|
+
batch: async (addresses) => {
|
|
27
|
+
/* → { results } — one row per address, in order, per-row error isolation */
|
|
28
|
+
},
|
|
29
|
+
resolveTree: async (tree, opts) => {
|
|
30
|
+
/* → { tree } — the same tree, decorated with gazetteer coords + attribution */
|
|
31
|
+
},
|
|
32
|
+
reload: async () => {
|
|
33
|
+
/* → { reloaded, versions } — versioned data switchover */
|
|
34
|
+
},
|
|
35
|
+
health: () => {
|
|
36
|
+
/* → model card / data-root inventory, spread into GET /health */
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The `mailwoman` CLI wires the real parse/geocode/resolve stack (phase 4b); tests inject fixtures.
|
|
42
|
+
|
|
43
|
+
## Endpoints
|
|
44
|
+
|
|
45
|
+
| Endpoint | Method | Body / query | Absent-engine status |
|
|
46
|
+
| --------------- | --------- | -------------------------------------- | -------------------------- |
|
|
47
|
+
| `/v1/parse` | GET, POST | `{ address, debug? }` (or `?address=`) | `501` |
|
|
48
|
+
| `/v1/geocode` | POST | `{ address }` | `503` |
|
|
49
|
+
| `/v1/batch` | POST | `{ addresses: string[] }` | `503` |
|
|
50
|
+
| `/v1/resolve` | POST | `{ tree: AddressTree, opts? }` | `503` |
|
|
51
|
+
| `/v1/reload` | POST | — | `503` |
|
|
52
|
+
| `/v1/format` | POST | `{ components, country, options? }` | always available |
|
|
53
|
+
| `/health` | GET | — | `200` (status+uptime only) |
|
|
54
|
+
| `/metrics` | GET | — | always available |
|
|
55
|
+
| `/openapi.json` | GET | — | always available |
|
|
56
|
+
|
|
57
|
+
Every error response is the native envelope: `{ error: string, detail?: string }`. A validation failure on
|
|
58
|
+
a strict body (e.g. `/v1/format` with no `components`) maps through the same envelope — `{ error: "invalid
|
|
59
|
+
request body", detail: "<short zod summary>" }` — never the raw zod shape.
|
|
60
|
+
|
|
61
|
+
## Library use
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { serveNode } from "@mailwoman/api-kit"
|
|
65
|
+
import { createMailwomanAPI, type MailwomanAPIEngine } from "@mailwoman/api"
|
|
66
|
+
|
|
67
|
+
const engine: MailwomanAPIEngine = {
|
|
68
|
+
/* parse, geocode, batch, resolveTree, reload, health — backed by your Mailwoman pipeline */
|
|
69
|
+
}
|
|
70
|
+
const app = createMailwomanAPI(engine)
|
|
71
|
+
serveNode({ fetch: app.fetch, port: 3000, hostname: "0.0.0.0" })
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Options
|
|
75
|
+
|
|
76
|
+
`createMailwomanAPI(engine, options?)`:
|
|
77
|
+
|
|
78
|
+
- `cors` — permissive CORS (`Access-Control-Allow-Origin: *`, `GET, POST, OPTIONS`) on by default; browser
|
|
79
|
+
clients (the demo, a map widget) need it for the mutating `/v1/*` preflight. Set `false` when a reverse
|
|
80
|
+
proxy already owns the CORS headers.
|
|
81
|
+
- `bodyLimitBytes` — max request body size, enforced ahead of every `/v1/*` handler. Default 2 MiB (carried
|
|
82
|
+
from the express server's `express.json({ limit: "2mb" })`). Oversized bodies answer `413` before the body
|
|
83
|
+
is buffered into memory.
|
|
84
|
+
- `batchMax` — max `addresses` rows accepted by `POST /v1/batch`. Default 500. Exceeding it answers `413`.
|
|
85
|
+
|
|
86
|
+
## Metrics
|
|
87
|
+
|
|
88
|
+
`POST /v1/geocode` and `POST /v1/batch` record timing to [`@mailwoman/api-kit`](../api-kit)'s generic
|
|
89
|
+
in-process metrics — `GET /metrics` returns the live snapshot (latency percentiles, per-tier counts).
|
|
90
|
+
`/v1/geocode`'s tier is read from `outcome["resolution_tier"]` (falling back to `"admin"`); `/v1/batch`
|
|
91
|
+
records whole-call latency under the fixed `"batch"` tier — per-row tier metrics are the engine's job
|
|
92
|
+
(phase 4b). A thrown engine error records the reserved `"error"` tier before rethrowing into the `500`
|
|
93
|
+
safety net.
|
|
94
|
+
|
|
95
|
+
## Status
|
|
96
|
+
|
|
97
|
+
Phase 4a: the routes, app, and OpenAPI document ship engine-agnostic, with fixture-backed tests. Phase 4b
|
|
98
|
+
wires the real engine into the `mailwoman serve` CLI and repoints `RemoteResolver` at `/v1/resolve`.
|
package/out/app.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* The native Mailwoman Hono app: CORS + a request-body-size guard + the strict-validation error
|
|
7
|
+
* envelope + the `/v1` routes + the emitted OpenAPI document. Engine-agnostic — the `mailwoman`
|
|
8
|
+
* CLI wires the real parse/geocode/resolve stack (phase 4b); tests inject fixtures.
|
|
9
|
+
*/
|
|
10
|
+
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
11
|
+
import { type OpenAPIDocInfo } from "@mailwoman/api-kit";
|
|
12
|
+
import type { MailwomanAPIEngine } from "./engine.ts";
|
|
13
|
+
/** Options for {@link createMailwomanAPI}. */
|
|
14
|
+
export interface MailwomanAPIOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Emit permissive CORS headers (`Access-Control-Allow-Origin: *`) on every response and answer preflight `OPTIONS`
|
|
17
|
+
* with `204`. Default `true` — browser-embedded clients (the demo, a map widget) need it: a cross-origin XHR
|
|
18
|
+
* (including the `POST` preflight) is blocked without it (#1017). Set `false` when a reverse proxy already owns the
|
|
19
|
+
* CORS headers.
|
|
20
|
+
*/
|
|
21
|
+
cors?: boolean;
|
|
22
|
+
/** Max request body size in bytes, enforced ahead of every `/v1/*` handler. Default 2 MiB. */
|
|
23
|
+
bodyLimitBytes?: number;
|
|
24
|
+
/** Max `addresses` rows accepted by `POST /v1/batch`. Default 500 (see `routes.ts`'s `DEFAULT_BATCH_MAX`). */
|
|
25
|
+
batchMax?: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The document info stamped into the emitted OpenAPI document. Exported (not inlined) so the `mailwoman openapi`
|
|
29
|
+
* command can call `emitOpenAPIDocuments` with the SAME info the mounted `/openapi.json` route (below, via
|
|
30
|
+
* {@link attachOpenAPIDocs}) uses — one source of truth, no risk of the two drifting.
|
|
31
|
+
*/
|
|
32
|
+
export declare const MAILWOMAN_API_DOC_INFO: OpenAPIDocInfo;
|
|
33
|
+
/** Build the native Mailwoman app around an injected {@link MailwomanAPIEngine}. */
|
|
34
|
+
export declare function createMailwomanAPI(engine: MailwomanAPIEngine, options?: MailwomanAPIOptions): OpenAPIHono;
|
|
35
|
+
//# sourceMappingURL=app.d.ts.map
|
package/out/app.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../app.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAA+B,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAKrF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAMrD,8CAA8C;AAC9C,MAAM,WAAW,mBAAmB;IACnC;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;IAEd,8FAA8F;IAC9F,cAAc,CAAC,EAAE,MAAM,CAAA;IAEvB,8GAA8G;IAC9G,QAAQ,CAAC,EAAE,MAAM,CAAA;CACjB;AAUD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,cAoBpC,CAAA;AAED,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,EAAE,OAAO,GAAE,mBAAwB,GAAG,WAAW,CAkD7G"}
|
package/out/app.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* The native Mailwoman Hono app: CORS + a request-body-size guard + the strict-validation error
|
|
7
|
+
* envelope + the `/v1` routes + the emitted OpenAPI document. Engine-agnostic — the `mailwoman`
|
|
8
|
+
* CLI wires the real parse/geocode/resolve stack (phase 4b); tests inject fixtures.
|
|
9
|
+
*/
|
|
10
|
+
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
11
|
+
import { apiError, attachOpenAPIDocs } from "@mailwoman/api-kit";
|
|
12
|
+
import packageJson from "@mailwoman/api/package.json" with { type: "json" };
|
|
13
|
+
import { bodyLimit } from "hono/body-limit";
|
|
14
|
+
import { cors } from "hono/cors";
|
|
15
|
+
import { DEFAULT_BATCH_MAX, registerMailwomanAPIRoutes } from "./routes.js";
|
|
16
|
+
/** 2 MiB — carried from the express server's `express.json({ limit: "2mb" })` (`mailwoman/server/index.ts`). */
|
|
17
|
+
const DEFAULT_BODY_LIMIT_BYTES = 2 * 1024 * 1024;
|
|
18
|
+
/**
|
|
19
|
+
* Short, single-line summary of a zod validation failure for the envelope's `detail` field — not the full `ZodError`,
|
|
20
|
+
* which is multi-line and carries internal path/code detail not meant for a wire response.
|
|
21
|
+
*/
|
|
22
|
+
function summarizeValidationError(error) {
|
|
23
|
+
return error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The document info stamped into the emitted OpenAPI document. Exported (not inlined) so the `mailwoman openapi`
|
|
27
|
+
* command can call `emitOpenAPIDocuments` with the SAME info the mounted `/openapi.json` route (below, via
|
|
28
|
+
* {@link attachOpenAPIDocs}) uses — one source of truth, no risk of the two drifting.
|
|
29
|
+
*/
|
|
30
|
+
export const MAILWOMAN_API_DOC_INFO = {
|
|
31
|
+
title: packageJson.name,
|
|
32
|
+
version: packageJson.version,
|
|
33
|
+
description: packageJson.description,
|
|
34
|
+
license: { name: "AGPL-3.0-only OR LicenseRef-Commercial", identifier: "AGPL-3.0-only" },
|
|
35
|
+
contact: { name: "Sister Software", url: "https://mailwoman.sister.software" },
|
|
36
|
+
servers: [
|
|
37
|
+
{
|
|
38
|
+
url: "http://{host}:{port}",
|
|
39
|
+
variables: { host: { default: "127.0.0.1" }, port: { default: "3000" } },
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
security: [],
|
|
43
|
+
tags: [
|
|
44
|
+
{ name: "parsing", description: "Free-text address parsing." },
|
|
45
|
+
{ name: "geocoding", description: "Address-to-coordinate resolution." },
|
|
46
|
+
{ name: "resolving", description: "Gazetteer resolution over an already-decoded address tree." },
|
|
47
|
+
{ name: "formatting", description: "Component-dict rendering — the inverse of parsing." },
|
|
48
|
+
{ name: "meta", description: "Health, metrics, and deploy-time operations." },
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
/** Build the native Mailwoman app around an injected {@link MailwomanAPIEngine}. */
|
|
52
|
+
export function createMailwomanAPI(engine, options = {}) {
|
|
53
|
+
const app = new OpenAPIHono({
|
|
54
|
+
// This surface is ours (no vendor contract to preserve): every declared body/query schema is
|
|
55
|
+
// validator-enforced, and a failure maps through the shared api-kit envelope — never the raw zod
|
|
56
|
+
// `{success, error}` shape. Individual routes (routes.ts) override this per-call to answer their OWN
|
|
57
|
+
// friendly business message (e.g. "address is required"); this is the fallback for the rest (currently
|
|
58
|
+
// just `/v1/format`).
|
|
59
|
+
defaultHook: (result, c) => {
|
|
60
|
+
if (!result.success) {
|
|
61
|
+
return apiError(c, 400, "invalid request body", summarizeValidationError(result.error));
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
// Browser-embedded clients need CORS or their cross-origin XHR (including the mutating `/v1/*` preflight) is
|
|
67
|
+
// blocked before it completes (#1017). GET+POST, unlike the read-only drop-ins (photon, nominatim).
|
|
68
|
+
if (options.cors !== false) {
|
|
69
|
+
app.use(cors({ origin: "*", allowMethods: ["GET", "POST", "OPTIONS"], allowHeaders: ["*"], maxAge: 86400 }));
|
|
70
|
+
}
|
|
71
|
+
// Safety net: an engine fault answers the native envelope, never a crash. `detail` carries the raw message —
|
|
72
|
+
// this surface is ours to design, so (unlike the vendor-constrained drop-in envelopes) we can be helpful.
|
|
73
|
+
app.onError((error, c) => {
|
|
74
|
+
// A malformed request body is a client-side syntax error, not a server fault — Hono's zod-openapi
|
|
75
|
+
// validator throws before a route's own hook ever sees the body, so it lands here instead of the
|
|
76
|
+
// per-route 400s in routes.ts. Answer 400, not the 500 net (which stays reserved for engine faults).
|
|
77
|
+
if (error instanceof Error && error.message.includes("Malformed JSON")) {
|
|
78
|
+
return apiError(c, 400, "invalid request body", "malformed JSON");
|
|
79
|
+
}
|
|
80
|
+
return apiError(c, 500, "internal error", error instanceof Error ? error.message : String(error));
|
|
81
|
+
});
|
|
82
|
+
// Ahead of the handlers (which buffer the body into memory) so an oversized POST is rejected before that
|
|
83
|
+
// buffering happens, not after — mirrors the libpostal precedent.
|
|
84
|
+
app.use("/v1/*", bodyLimit({
|
|
85
|
+
maxSize: options.bodyLimitBytes ?? DEFAULT_BODY_LIMIT_BYTES,
|
|
86
|
+
onError: (c) => apiError(c, 413, "request body too large"),
|
|
87
|
+
}));
|
|
88
|
+
registerMailwomanAPIRoutes(app, engine, { batchMax: options.batchMax ?? DEFAULT_BATCH_MAX });
|
|
89
|
+
attachOpenAPIDocs(app, MAILWOMAN_API_DOC_INFO);
|
|
90
|
+
return app;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=app.js.map
|
package/out/app.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../app.ts"],"names":[],"mappings":"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"}
|
package/out/engine.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* The native-surface engine contract. Engine-agnostic like the drop-ins: the `mailwoman` CLI
|
|
7
|
+
* wires the real parse/geocode/resolve stack (phase 4b); tests inject fixtures. `format` is the
|
|
8
|
+
* exception — it's wired in-package from `@mailwoman/formatter` (the surface exists to expose it).
|
|
9
|
+
*/
|
|
10
|
+
import type { AddressTree } from "@mailwoman/core/decoder";
|
|
11
|
+
import type { SerializedSolution } from "@mailwoman/core/solver";
|
|
12
|
+
/** One parse outcome: the tokenized input span + ranked solutions (the legacy /parse shape). */
|
|
13
|
+
export interface ParseOutcome {
|
|
14
|
+
input: {
|
|
15
|
+
body: string;
|
|
16
|
+
start: number;
|
|
17
|
+
end: number;
|
|
18
|
+
};
|
|
19
|
+
solutions: SerializedSolution[];
|
|
20
|
+
debug?: string;
|
|
21
|
+
}
|
|
22
|
+
/** A geocode outcome — the engine returns the geocode-core `GeocodeResult` shape verbatim (passthrough). */
|
|
23
|
+
export type GeocodeOutcome = Record<string, unknown>;
|
|
24
|
+
/** A batch row: a GeocodeOutcome, or an `{ input, error }` slot (per-row isolation). */
|
|
25
|
+
export type BatchRow = GeocodeOutcome | {
|
|
26
|
+
input: string;
|
|
27
|
+
error: string;
|
|
28
|
+
};
|
|
29
|
+
export interface ResolveTreeOutcome {
|
|
30
|
+
tree: AddressTree;
|
|
31
|
+
}
|
|
32
|
+
/** The `/health` data block the engine contributes (model card, data-root inventory). */
|
|
33
|
+
export type HealthData = Record<string, unknown>;
|
|
34
|
+
export interface MailwomanAPIEngine {
|
|
35
|
+
parse?(address: string, opts: {
|
|
36
|
+
debug: boolean;
|
|
37
|
+
}): Promise<ParseOutcome>;
|
|
38
|
+
geocode?(address: string): Promise<GeocodeOutcome>;
|
|
39
|
+
batch?(addresses: string[]): Promise<{
|
|
40
|
+
results: BatchRow[];
|
|
41
|
+
}>;
|
|
42
|
+
resolveTree?(tree: AddressTree, opts: Record<string, unknown>): Promise<ResolveTreeOutcome>;
|
|
43
|
+
reload?(): Promise<{
|
|
44
|
+
reloaded: boolean;
|
|
45
|
+
versions: unknown;
|
|
46
|
+
}>;
|
|
47
|
+
health?(): HealthData;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAEhE,gGAAgG;AAChG,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;IACnD,SAAS,EAAE,kBAAkB,EAAE,CAAA;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAA;CACd;AAED,4GAA4G;AAC5G,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAEpD,wFAAwF;AACxF,MAAM,MAAM,QAAQ,GAAG,cAAc,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAExE,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,WAAW,CAAA;CACjB;AAED,yFAAyF;AACzF,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAEhD,MAAM,WAAW,kBAAkB;IAClC,KAAK,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IACxE,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;IAClD,KAAK,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,QAAQ,EAAE,CAAA;KAAE,CAAC,CAAA;IAC7D,WAAW,CAAC,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAA;IAC3F,MAAM,CAAC,IAAI,OAAO,CAAC;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;IAC5D,MAAM,CAAC,IAAI,UAAU,CAAA;CACrB"}
|
package/out/engine.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* The native-surface engine contract. Engine-agnostic like the drop-ins: the `mailwoman` CLI
|
|
7
|
+
* wires the real parse/geocode/resolve stack (phase 4b); tests inject fixtures. `format` is the
|
|
8
|
+
* exception — it's wired in-package from `@mailwoman/formatter` (the surface exists to expose it).
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
|
package/out/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `@mailwoman/api` — the native Mailwoman HTTP API: an engine-agnostic `/v1` surface (parse,
|
|
7
|
+
* geocode, batch, resolve, format) alongside health, metrics, and an emitted OpenAPI document.
|
|
8
|
+
* Unlike its drop-in siblings (`@mailwoman/nominatim`, `@mailwoman/photon`,
|
|
9
|
+
* `@mailwoman/libpostal`), nothing here mimics a third-party API — this is Mailwoman's own wire
|
|
10
|
+
* contract, so schemas are strict and validator-enforced rather than tolerant of legacy quirks.
|
|
11
|
+
*
|
|
12
|
+
* Like its siblings, the package is engine-agnostic: routes take a {@link MailwomanAPIEngine}; the
|
|
13
|
+
* `mailwoman` CLI wires the real parse/geocode/resolve stack (phase 4b). The Hono app (CORS +
|
|
14
|
+
* body-size guard + the strict-validation error envelope + the emitted OpenAPI document) lives in
|
|
15
|
+
* `app.ts`; route definitions + handlers (incl. `registerMailwomanAPIRoutes`) in `routes.ts`; the
|
|
16
|
+
* engine contract in `engine.ts`; the zod wire schemas in `schema.ts`.
|
|
17
|
+
*/
|
|
18
|
+
export * from "./app.ts";
|
|
19
|
+
export * from "./engine.ts";
|
|
20
|
+
export * from "./routes.ts";
|
|
21
|
+
export * from "./schema.ts";
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA"}
|
package/out/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `@mailwoman/api` — the native Mailwoman HTTP API: an engine-agnostic `/v1` surface (parse,
|
|
7
|
+
* geocode, batch, resolve, format) alongside health, metrics, and an emitted OpenAPI document.
|
|
8
|
+
* Unlike its drop-in siblings (`@mailwoman/nominatim`, `@mailwoman/photon`,
|
|
9
|
+
* `@mailwoman/libpostal`), nothing here mimics a third-party API — this is Mailwoman's own wire
|
|
10
|
+
* contract, so schemas are strict and validator-enforced rather than tolerant of legacy quirks.
|
|
11
|
+
*
|
|
12
|
+
* Like its siblings, the package is engine-agnostic: routes take a {@link MailwomanAPIEngine}; the
|
|
13
|
+
* `mailwoman` CLI wires the real parse/geocode/resolve stack (phase 4b). The Hono app (CORS +
|
|
14
|
+
* body-size guard + the strict-validation error envelope + the emitted OpenAPI document) lives in
|
|
15
|
+
* `app.ts`; route definitions + handlers (incl. `registerMailwomanAPIRoutes`) in `routes.ts`; the
|
|
16
|
+
* engine contract in `engine.ts`; the zod wire schemas in `schema.ts`.
|
|
17
|
+
*/
|
|
18
|
+
export * from "./app.js";
|
|
19
|
+
export * from "./engine.js";
|
|
20
|
+
export * from "./routes.js";
|
|
21
|
+
export * from "./schema.js";
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
package/out/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA"}
|
package/out/routes.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* Route definitions + handlers for the native `/v1` surface. The OpenAPI document is emitted from
|
|
7
|
+
* these definitions — there is no handwritten spec. Unlike the drop-ins (photon, nominatim,
|
|
8
|
+
* libpostal), nothing here mimics a vendor's legacy query-parsing tolerance: request bodies are
|
|
9
|
+
* validator-enforced, and a validation failure always answers through the shared api-kit envelope
|
|
10
|
+
* (`apiError`), never the raw zod shape. `GET /v1/parse` is the one query-string route, and it
|
|
11
|
+
* reads `c.req.query()` directly — a query string has no repeated-value contract worth preserving
|
|
12
|
+
* here (contrast the drop-ins' `legacyQuery` adapter), so there's nothing to tolerate.
|
|
13
|
+
*
|
|
14
|
+
* Per-route validation hooks (the 3rd arg to `app.openapi(route, handler, hook)`) override the
|
|
15
|
+
* app-level `defaultHook` (wired in `app.ts`) so each route can answer its OWN friendly business
|
|
16
|
+
* message — `"address is required"`, `"body must be { addresses: string[] }"`, etc. — matching the
|
|
17
|
+
* express `mailwoman/server` precedent this surface carries forward. Routes with no friendly
|
|
18
|
+
* carry-forward message (currently just `/v1/format`) fall through to the app-level hook's generic
|
|
19
|
+
* `"invalid request body"`.
|
|
20
|
+
*/
|
|
21
|
+
import { type OpenAPIHono } from "@hono/zod-openapi";
|
|
22
|
+
import type { MailwomanAPIEngine } from "./engine.ts";
|
|
23
|
+
/**
|
|
24
|
+
* Default `POST /v1/batch` row cap when {@link RegisterMailwomanAPIRoutesOptions.batchMax} is omitted. This is the
|
|
25
|
+
* standalone-engine default, not derived from env — `mailwoman serve` always passes the env-derived value explicitly
|
|
26
|
+
* (`$public.MAILWOMAN_BATCH_MAX`, default 1000; see `core/env/schema.ts`).
|
|
27
|
+
*/
|
|
28
|
+
export declare const DEFAULT_BATCH_MAX = 500;
|
|
29
|
+
/** Options for {@link registerMailwomanAPIRoutes}. */
|
|
30
|
+
export interface RegisterMailwomanAPIRoutesOptions {
|
|
31
|
+
/** Max `addresses` rows accepted by `POST /v1/batch`. Default {@link DEFAULT_BATCH_MAX}. */
|
|
32
|
+
batchMax?: number;
|
|
33
|
+
}
|
|
34
|
+
/** Register the native `/v1` routes + `/health` + `/metrics` against an injected engine. */
|
|
35
|
+
export declare function registerMailwomanAPIRoutes(app: OpenAPIHono, engine: MailwomanAPIEngine, options?: RegisterMailwomanAPIRoutesOptions): void;
|
|
36
|
+
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAe,KAAK,WAAW,EAAK,MAAM,mBAAmB,CAAA;AAMpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAgBrD;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,MAAM,CAAA;AAapC,sDAAsD;AACtD,MAAM,WAAW,iCAAiC;IACjD,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,MAAM,CAAA;CACjB;AA2LD,4FAA4F;AAC5F,wBAAgB,0BAA0B,CACzC,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,kBAAkB,EAC1B,OAAO,GAAE,iCAAsC,GAC7C,IAAI,CAiJN"}
|
package/out/routes.js
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* Route definitions + handlers for the native `/v1` surface. The OpenAPI document is emitted from
|
|
7
|
+
* these definitions — there is no handwritten spec. Unlike the drop-ins (photon, nominatim,
|
|
8
|
+
* libpostal), nothing here mimics a vendor's legacy query-parsing tolerance: request bodies are
|
|
9
|
+
* validator-enforced, and a validation failure always answers through the shared api-kit envelope
|
|
10
|
+
* (`apiError`), never the raw zod shape. `GET /v1/parse` is the one query-string route, and it
|
|
11
|
+
* reads `c.req.query()` directly — a query string has no repeated-value contract worth preserving
|
|
12
|
+
* here (contrast the drop-ins' `legacyQuery` adapter), so there's nothing to tolerate.
|
|
13
|
+
*
|
|
14
|
+
* Per-route validation hooks (the 3rd arg to `app.openapi(route, handler, hook)`) override the
|
|
15
|
+
* app-level `defaultHook` (wired in `app.ts`) so each route can answer its OWN friendly business
|
|
16
|
+
* message — `"address is required"`, `"body must be { addresses: string[] }"`, etc. — matching the
|
|
17
|
+
* express `mailwoman/server` precedent this surface carries forward. Routes with no friendly
|
|
18
|
+
* carry-forward message (currently just `/v1/format`) fall through to the app-level hook's generic
|
|
19
|
+
* `"invalid request body"`.
|
|
20
|
+
*/
|
|
21
|
+
import { createRoute, z } from "@hono/zod-openapi";
|
|
22
|
+
import { apiError, metricsSnapshot, recordTimed } from "@mailwoman/api-kit";
|
|
23
|
+
import { canonicalKey, formatAddress } from "@mailwoman/formatter";
|
|
24
|
+
import { APIErrorSchema, BatchRequestSchema, BatchResponseSchema, FormatRequestSchema, FormatResponseSchema, GeocodeOutcomeSchema, GeocodeRequestSchema, HealthResponseSchema, ParseOutcomeSchema, ParseRequestSchema, ResolveRequestSchema, ResolveResponseSchema, } from "./schema.js";
|
|
25
|
+
/**
|
|
26
|
+
* Default `POST /v1/batch` row cap when {@link RegisterMailwomanAPIRoutesOptions.batchMax} is omitted. This is the
|
|
27
|
+
* standalone-engine default, not derived from env — `mailwoman serve` always passes the env-derived value explicitly
|
|
28
|
+
* (`$public.MAILWOMAN_BATCH_MAX`, default 1000; see `core/env/schema.ts`).
|
|
29
|
+
*/
|
|
30
|
+
export const DEFAULT_BATCH_MAX = 500;
|
|
31
|
+
const startedAt = Date.now();
|
|
32
|
+
/**
|
|
33
|
+
* `detail` text for every 503 "engine method absent" response (`/v1/geocode`, `/v1/batch`, `/v1/resolve`, `/v1/reload`)
|
|
34
|
+
* — the express-era remediation carried forward: a stranger hitting a 503 must see the exact fix, not just "not
|
|
35
|
+
* available". Matches `mailwoman/api-engine.ts`'s `buildPreflightMessage()` boot-time banner in spirit (same two
|
|
36
|
+
* missing pieces — the packages, and the gazetteer data), condensed to one line for a JSON error body.
|
|
37
|
+
*/
|
|
38
|
+
const GEOCODER_UNAVAILABLE_DETAIL = "install @mailwoman/neural + @mailwoman/resolver-wof-sqlite and provide gazetteer data (MAILWOMAN_WOF_DB / MAILWOMAN_CANDIDATE_DB)";
|
|
39
|
+
const errorContent = (description) => ({
|
|
40
|
+
description,
|
|
41
|
+
content: { "application/json": { schema: APIErrorSchema } },
|
|
42
|
+
});
|
|
43
|
+
const parseQueryParams = z.object({
|
|
44
|
+
address: z.string().optional().openapi({ description: "The address to parse." }),
|
|
45
|
+
debug: z.string().optional().openapi({ description: '`"true"` to include a diagnostic report.' }),
|
|
46
|
+
});
|
|
47
|
+
const parseResponses = {
|
|
48
|
+
200: {
|
|
49
|
+
description: "The tokenized input span + ranked solutions.",
|
|
50
|
+
content: { "application/json": { schema: ParseOutcomeSchema } },
|
|
51
|
+
},
|
|
52
|
+
400: errorContent("`address` is required."),
|
|
53
|
+
501: errorContent("The backing engine method is not wired for this deployment."),
|
|
54
|
+
};
|
|
55
|
+
const geocodeResponses = {
|
|
56
|
+
200: {
|
|
57
|
+
description: "One geocode result (parse → resolve cascade), passed through from the engine verbatim.",
|
|
58
|
+
content: { "application/json": { schema: GeocodeOutcomeSchema } },
|
|
59
|
+
},
|
|
60
|
+
400: errorContent("`address` is required."),
|
|
61
|
+
503: errorContent("The geocoding engine is not wired for this deployment (dependencies missing)."),
|
|
62
|
+
};
|
|
63
|
+
const batchResponses = {
|
|
64
|
+
200: {
|
|
65
|
+
description: "One result per input address, in input order (per-row error isolation).",
|
|
66
|
+
content: { "application/json": { schema: BatchResponseSchema } },
|
|
67
|
+
},
|
|
68
|
+
400: errorContent("Body must be `{ addresses: string[] }`."),
|
|
69
|
+
413: errorContent("`addresses.length` exceeds the configured batch cap."),
|
|
70
|
+
503: errorContent("The geocoding engine is not wired for this deployment (dependencies missing)."),
|
|
71
|
+
};
|
|
72
|
+
const resolveResponses = {
|
|
73
|
+
200: {
|
|
74
|
+
description: "The same tree, decorated in place with gazetteer coordinates + attribution.",
|
|
75
|
+
content: { "application/json": { schema: ResolveResponseSchema } },
|
|
76
|
+
},
|
|
77
|
+
400: errorContent("Body must be `{ tree: AddressTree, opts? }`."),
|
|
78
|
+
503: errorContent("The resolver is not wired for this deployment (dependencies missing)."),
|
|
79
|
+
};
|
|
80
|
+
const reloadResponses = {
|
|
81
|
+
200: {
|
|
82
|
+
description: "Versioned data switchover result — the new per-shard version map.",
|
|
83
|
+
content: {
|
|
84
|
+
"application/json": { schema: z.looseObject({ reloaded: z.boolean(), versions: z.unknown() }) },
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
503: errorContent("The geocoding engine is not wired for this deployment (dependencies missing)."),
|
|
88
|
+
};
|
|
89
|
+
const formatResponses = {
|
|
90
|
+
200: {
|
|
91
|
+
description: "The rendered address string + the deterministic canonical match key.",
|
|
92
|
+
content: { "application/json": { schema: FormatResponseSchema } },
|
|
93
|
+
},
|
|
94
|
+
400: errorContent("Invalid request body."),
|
|
95
|
+
};
|
|
96
|
+
const healthResponses = {
|
|
97
|
+
200: {
|
|
98
|
+
description: "Liveness + engine health block. Answers 200 even when the engine is absent or broken.",
|
|
99
|
+
content: { "application/json": { schema: HealthResponseSchema } },
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
const metricsResponses = {
|
|
103
|
+
200: {
|
|
104
|
+
description: "The live in-process timing metrics snapshot (latency percentiles + per-tier counts).",
|
|
105
|
+
content: { "application/json": { schema: z.looseObject({}) } },
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
const parseGetRoute = createRoute({
|
|
109
|
+
method: "get",
|
|
110
|
+
path: "/v1/parse",
|
|
111
|
+
operationId: "parseGet",
|
|
112
|
+
summary: "Parse an address (query string)",
|
|
113
|
+
tags: ["parsing"],
|
|
114
|
+
request: { query: parseQueryParams },
|
|
115
|
+
responses: parseResponses,
|
|
116
|
+
});
|
|
117
|
+
const parsePostRoute = createRoute({
|
|
118
|
+
method: "post",
|
|
119
|
+
path: "/v1/parse",
|
|
120
|
+
operationId: "parsePost",
|
|
121
|
+
summary: "Parse an address (JSON body)",
|
|
122
|
+
tags: ["parsing"],
|
|
123
|
+
request: { body: { content: { "application/json": { schema: ParseRequestSchema } }, required: true } },
|
|
124
|
+
responses: parseResponses,
|
|
125
|
+
});
|
|
126
|
+
const geocodeRoute = createRoute({
|
|
127
|
+
method: "post",
|
|
128
|
+
path: "/v1/geocode",
|
|
129
|
+
operationId: "geocode",
|
|
130
|
+
summary: "Geocode an address to coordinates",
|
|
131
|
+
tags: ["geocoding"],
|
|
132
|
+
request: { body: { content: { "application/json": { schema: GeocodeRequestSchema } }, required: true } },
|
|
133
|
+
responses: geocodeResponses,
|
|
134
|
+
});
|
|
135
|
+
const batchRoute = createRoute({
|
|
136
|
+
method: "post",
|
|
137
|
+
path: "/v1/batch",
|
|
138
|
+
operationId: "batch",
|
|
139
|
+
summary: "Geocode a batch of addresses",
|
|
140
|
+
tags: ["geocoding"],
|
|
141
|
+
request: { body: { content: { "application/json": { schema: BatchRequestSchema } }, required: true } },
|
|
142
|
+
responses: batchResponses,
|
|
143
|
+
});
|
|
144
|
+
const resolveRoute = createRoute({
|
|
145
|
+
method: "post",
|
|
146
|
+
path: "/v1/resolve",
|
|
147
|
+
operationId: "resolve",
|
|
148
|
+
summary: "Resolve an already-decoded address tree against the gazetteer",
|
|
149
|
+
tags: ["resolving"],
|
|
150
|
+
request: { body: { content: { "application/json": { schema: ResolveRequestSchema } }, required: true } },
|
|
151
|
+
responses: resolveResponses,
|
|
152
|
+
});
|
|
153
|
+
const reloadRoute = createRoute({
|
|
154
|
+
method: "post",
|
|
155
|
+
path: "/v1/reload",
|
|
156
|
+
operationId: "reload",
|
|
157
|
+
summary: "Reload versioned data shards (deploy-only; gate at ingress)",
|
|
158
|
+
tags: ["meta"],
|
|
159
|
+
responses: reloadResponses,
|
|
160
|
+
});
|
|
161
|
+
const formatRoute = createRoute({
|
|
162
|
+
method: "post",
|
|
163
|
+
path: "/v1/format",
|
|
164
|
+
operationId: "format",
|
|
165
|
+
summary: "Render address components to a string + canonical match key",
|
|
166
|
+
tags: ["formatting"],
|
|
167
|
+
request: { body: { content: { "application/json": { schema: FormatRequestSchema } }, required: true } },
|
|
168
|
+
responses: formatResponses,
|
|
169
|
+
});
|
|
170
|
+
const healthRoute = createRoute({
|
|
171
|
+
method: "get",
|
|
172
|
+
path: "/health",
|
|
173
|
+
operationId: "health",
|
|
174
|
+
summary: "Liveness + engine health",
|
|
175
|
+
tags: ["meta"],
|
|
176
|
+
responses: healthResponses,
|
|
177
|
+
});
|
|
178
|
+
const metricsRoute = createRoute({
|
|
179
|
+
method: "get",
|
|
180
|
+
path: "/metrics",
|
|
181
|
+
operationId: "metrics",
|
|
182
|
+
summary: "In-process timing metrics snapshot",
|
|
183
|
+
tags: ["meta"],
|
|
184
|
+
responses: metricsResponses,
|
|
185
|
+
});
|
|
186
|
+
/**
|
|
187
|
+
* `components` accepts `string | string[]` per key on the wire (a caller may pass every span a multi-span match
|
|
188
|
+
* covered); `formatAddress`/`canonicalKey` want a single string per `ComponentTag`. Multi-span values collapse to their
|
|
189
|
+
* FIRST span here — the formatter template owns joining semantics, not this route.
|
|
190
|
+
*/
|
|
191
|
+
function toComponentDict(components) {
|
|
192
|
+
const out = {};
|
|
193
|
+
for (const [key, value] of Object.entries(components)) {
|
|
194
|
+
const first = Array.isArray(value) ? value[0] : value;
|
|
195
|
+
if (first !== undefined) {
|
|
196
|
+
out[key] = first;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return out;
|
|
200
|
+
}
|
|
201
|
+
/** Register the native `/v1` routes + `/health` + `/metrics` against an injected engine. */
|
|
202
|
+
export function registerMailwomanAPIRoutes(app, engine, options = {}) {
|
|
203
|
+
const batchMax = options.batchMax ?? DEFAULT_BATCH_MAX;
|
|
204
|
+
app.openapi(parseGetRoute, async (c) => {
|
|
205
|
+
if (!engine.parse)
|
|
206
|
+
return c.json({ error: "parse not implemented" }, 501);
|
|
207
|
+
const address = c.req.query("address")?.trim();
|
|
208
|
+
if (!address)
|
|
209
|
+
return c.json({ error: "address is required" }, 400);
|
|
210
|
+
const debug = c.req.query("debug") === "true";
|
|
211
|
+
const outcome = await engine.parse(address, { debug });
|
|
212
|
+
return c.json(outcome, 200);
|
|
213
|
+
});
|
|
214
|
+
app.openapi(parsePostRoute, async (c) => {
|
|
215
|
+
if (!engine.parse)
|
|
216
|
+
return c.json({ error: "parse not implemented" }, 501);
|
|
217
|
+
const { address, debug } = c.req.valid("json");
|
|
218
|
+
const trimmed = address.trim();
|
|
219
|
+
if (!trimmed)
|
|
220
|
+
return c.json({ error: "address is required" }, 400);
|
|
221
|
+
const outcome = await engine.parse(trimmed, { debug: debug ?? false });
|
|
222
|
+
return c.json(outcome, 200);
|
|
223
|
+
}, (result, c) => {
|
|
224
|
+
if (!result.success)
|
|
225
|
+
return c.json({ error: "address is required" }, 400);
|
|
226
|
+
return undefined;
|
|
227
|
+
});
|
|
228
|
+
app.openapi(geocodeRoute, async (c) => {
|
|
229
|
+
if (!engine.geocode)
|
|
230
|
+
return apiError(c, 503, "geocoder not available", GEOCODER_UNAVAILABLE_DETAIL);
|
|
231
|
+
const { address } = c.req.valid("json");
|
|
232
|
+
const trimmed = address.trim();
|
|
233
|
+
if (!trimmed)
|
|
234
|
+
return c.json({ error: "address is required" }, 400);
|
|
235
|
+
const t0 = performance.now();
|
|
236
|
+
try {
|
|
237
|
+
const outcome = await engine.geocode(trimmed);
|
|
238
|
+
recordTimed(performance.now() - t0, String(outcome["resolution_tier"] ?? "admin"));
|
|
239
|
+
// `GeocodeOutcome` (the engine contract) is a deliberate `Record<string, unknown>` passthrough —
|
|
240
|
+
// `GeocodeOutcomeSchema` is now a REAL typed shape (doc-accuracy only, per its own docstring), so a
|
|
241
|
+
// local cast at this wire boundary is needed, matching the established idiom below (`/v1/resolve`'s
|
|
242
|
+
// `tree as unknown as AddressTree`) for "documented wire shape looser than the domain type".
|
|
243
|
+
return c.json(outcome, 200);
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
recordTimed(performance.now() - t0, "error");
|
|
247
|
+
throw error;
|
|
248
|
+
}
|
|
249
|
+
}, (result, c) => {
|
|
250
|
+
if (!result.success)
|
|
251
|
+
return c.json({ error: "address is required" }, 400);
|
|
252
|
+
return undefined;
|
|
253
|
+
});
|
|
254
|
+
app.openapi(batchRoute, async (c) => {
|
|
255
|
+
const { addresses } = c.req.valid("json");
|
|
256
|
+
if (addresses.length === 0)
|
|
257
|
+
return c.json({ results: [] }, 200);
|
|
258
|
+
if (addresses.length > batchMax) {
|
|
259
|
+
return c.json({ error: `batch too large: ${addresses.length} > ${batchMax}` }, 413);
|
|
260
|
+
}
|
|
261
|
+
if (!engine.batch)
|
|
262
|
+
return apiError(c, 503, "geocoder not available", GEOCODER_UNAVAILABLE_DETAIL);
|
|
263
|
+
// Whole-call latency, recorded under the "batch" tier. Per-row tier metrics are the ENGINE's
|
|
264
|
+
// responsibility (phase 4b) — this app only times the call as a unit.
|
|
265
|
+
const t0 = performance.now();
|
|
266
|
+
try {
|
|
267
|
+
const outcome = await engine.batch(addresses);
|
|
268
|
+
recordTimed(performance.now() - t0, "batch");
|
|
269
|
+
// Same wire-vs-domain cast as `/v1/geocode` above — `BatchRow`'s `GeocodeOutcome` half is a
|
|
270
|
+
// `Record<string, unknown>` passthrough; `BatchResponseSchema` now types its `GeocodeOutcome` union
|
|
271
|
+
// member as the real shape.
|
|
272
|
+
return c.json(outcome, 200);
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
recordTimed(performance.now() - t0, "error");
|
|
276
|
+
throw error;
|
|
277
|
+
}
|
|
278
|
+
}, (result, c) => {
|
|
279
|
+
if (!result.success)
|
|
280
|
+
return c.json({ error: "body must be { addresses: string[] }" }, 400);
|
|
281
|
+
return undefined;
|
|
282
|
+
});
|
|
283
|
+
app.openapi(resolveRoute,
|
|
284
|
+
// Metrics are the ENGINE's responsibility here (phase 4b): the express predecessor recorded the
|
|
285
|
+
// street node's stamped resolution tier per call — the wired engine must carry that over, and
|
|
286
|
+
// must trim batch rows the same way (the route passes raw input through).
|
|
287
|
+
async (c) => {
|
|
288
|
+
if (!engine.resolveTree)
|
|
289
|
+
return apiError(c, 503, "resolver not available", GEOCODER_UNAVAILABLE_DETAIL);
|
|
290
|
+
const { tree, opts } = c.req.valid("json");
|
|
291
|
+
// The wire schema keeps `tree` loose (`{ roots: unknown[] }`, forward-compat) — a local cast at the
|
|
292
|
+
// boundary onto the engine's `AddressTree` contract, matching the established idiom (api-kit's
|
|
293
|
+
// `openapi.ts`, the drop-ins' response casts) for "documented wire shape looser than the domain type".
|
|
294
|
+
const outcome = await engine.resolveTree(tree, opts ?? {});
|
|
295
|
+
return c.json(outcome, 200);
|
|
296
|
+
}, (result, c) => {
|
|
297
|
+
if (!result.success)
|
|
298
|
+
return c.json({ error: "body must be { tree: AddressTree, opts? }" }, 400);
|
|
299
|
+
return undefined;
|
|
300
|
+
});
|
|
301
|
+
app.openapi(reloadRoute, async (c) => {
|
|
302
|
+
if (!engine.reload)
|
|
303
|
+
return apiError(c, 503, "geocoder not available", GEOCODER_UNAVAILABLE_DETAIL);
|
|
304
|
+
const outcome = await engine.reload();
|
|
305
|
+
return c.json(outcome, 200);
|
|
306
|
+
});
|
|
307
|
+
app.openapi(formatRoute, (c) => {
|
|
308
|
+
const { components, country, options: formatOptions } = c.req.valid("json");
|
|
309
|
+
const dict = toComponentDict(components);
|
|
310
|
+
const formatted = formatAddress(dict, country, formatOptions);
|
|
311
|
+
return c.json({ formatted, canonicalKey: canonicalKey(dict) }, 200);
|
|
312
|
+
});
|
|
313
|
+
app.openapi(healthRoute, (c) => {
|
|
314
|
+
const uptimeSeconds = Math.round((Date.now() - startedAt) / 1000);
|
|
315
|
+
return c.json({ status: "ok", uptime_s: uptimeSeconds, ...engine.health?.() }, 200);
|
|
316
|
+
});
|
|
317
|
+
app.openapi(metricsRoute, (c) => c.json(metricsSnapshot(), 200));
|
|
318
|
+
}
|
|
319
|
+
//# sourceMappingURL=routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,WAAW,EAAoB,CAAC,EAAE,MAAM,mBAAmB,CAAA;AACpE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAG3E,OAAO,EAAE,YAAY,EAAsB,aAAa,EAA6B,MAAM,sBAAsB,CAAA;AAGjH,OAAO,EACN,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,GACrB,MAAM,aAAa,CAAA;AAEpB;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAA;AAEpC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;AAE5B;;;;;GAKG;AACH,MAAM,2BAA2B,GAChC,mIAAmI,CAAA;AAQpI,MAAM,YAAY,GAAG,CAAC,WAAmB,EAAE,EAAE,CAAC,CAAC;IAC9C,WAAW;IACX,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,cAAc,EAAE,EAAE;CAC3D,CAAC,CAAA;AAEF,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACjC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,uBAAuB,EAAE,CAAC;IAChF,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,0CAA0C,EAAE,CAAC;CACjG,CAAC,CAAA;AAEF,MAAM,cAAc,GAAG;IACtB,GAAG,EAAE;QACJ,WAAW,EAAE,8CAA8C;QAC3D,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,EAAE;KAC/D;IACD,GAAG,EAAE,YAAY,CAAC,wBAAwB,CAAC;IAC3C,GAAG,EAAE,YAAY,CAAC,6DAA6D,CAAC;CAChF,CAAA;AAED,MAAM,gBAAgB,GAAG;IACxB,GAAG,EAAE;QACJ,WAAW,EAAE,wFAAwF;QACrG,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,oBAAoB,EAAE,EAAE;KACjE;IACD,GAAG,EAAE,YAAY,CAAC,wBAAwB,CAAC;IAC3C,GAAG,EAAE,YAAY,CAAC,+EAA+E,CAAC;CAClG,CAAA;AAED,MAAM,cAAc,GAAG;IACtB,GAAG,EAAE;QACJ,WAAW,EAAE,yEAAyE;QACtF,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,mBAAmB,EAAE,EAAE;KAChE;IACD,GAAG,EAAE,YAAY,CAAC,yCAAyC,CAAC;IAC5D,GAAG,EAAE,YAAY,CAAC,sDAAsD,CAAC;IACzE,GAAG,EAAE,YAAY,CAAC,+EAA+E,CAAC;CAClG,CAAA;AAED,MAAM,gBAAgB,GAAG;IACxB,GAAG,EAAE;QACJ,WAAW,EAAE,6EAA6E;QAC1F,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,qBAAqB,EAAE,EAAE;KAClE;IACD,GAAG,EAAE,YAAY,CAAC,8CAA8C,CAAC;IACjE,GAAG,EAAE,YAAY,CAAC,uEAAuE,CAAC;CAC1F,CAAA;AAED,MAAM,eAAe,GAAG;IACvB,GAAG,EAAE;QACJ,WAAW,EAAE,mEAAmE;QAChF,OAAO,EAAE;YACR,kBAAkB,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE;SAC/F;KACD;IACD,GAAG,EAAE,YAAY,CAAC,+EAA+E,CAAC;CAClG,CAAA;AAED,MAAM,eAAe,GAAG;IACvB,GAAG,EAAE;QACJ,WAAW,EAAE,sEAAsE;QACnF,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,oBAAoB,EAAE,EAAE;KACjE;IACD,GAAG,EAAE,YAAY,CAAC,uBAAuB,CAAC;CAC1C,CAAA;AAED,MAAM,eAAe,GAAG;IACvB,GAAG,EAAE;QACJ,WAAW,EAAE,uFAAuF;QACpG,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,oBAAoB,EAAE,EAAE;KACjE;CACD,CAAA;AAED,MAAM,gBAAgB,GAAG;IACxB,GAAG,EAAE;QACJ,WAAW,EAAE,sFAAsF;QACnG,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE;KAC9D;CACD,CAAA;AAED,MAAM,aAAa,GAAG,WAAW,CAAC;IACjC,MAAM,EAAE,KAAK;IACb,IAAI,EAAE,WAAW;IACjB,WAAW,EAAE,UAAU;IACvB,OAAO,EAAE,iCAAiC;IAC1C,IAAI,EAAE,CAAC,SAAS,CAAC;IACjB,OAAO,EAAE,EAAE,KAAK,EAAE,gBAAgB,EAAE;IACpC,SAAS,EAAE,cAAc;CACzB,CAAC,CAAA;AAEF,MAAM,cAAc,GAAG,WAAW,CAAC;IAClC,MAAM,EAAE,MAAM;IACd,IAAI,EAAE,WAAW;IACjB,WAAW,EAAE,WAAW;IACxB,OAAO,EAAE,8BAA8B;IACvC,IAAI,EAAE,CAAC,SAAS,CAAC;IACjB,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;IACtG,SAAS,EAAE,cAAc;CACzB,CAAC,CAAA;AAEF,MAAM,YAAY,GAAG,WAAW,CAAC;IAChC,MAAM,EAAE,MAAM;IACd,IAAI,EAAE,aAAa;IACnB,WAAW,EAAE,SAAS;IACtB,OAAO,EAAE,mCAAmC;IAC5C,IAAI,EAAE,CAAC,WAAW,CAAC;IACnB,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,oBAAoB,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;IACxG,SAAS,EAAE,gBAAgB;CAC3B,CAAC,CAAA;AAEF,MAAM,UAAU,GAAG,WAAW,CAAC;IAC9B,MAAM,EAAE,MAAM;IACd,IAAI,EAAE,WAAW;IACjB,WAAW,EAAE,OAAO;IACpB,OAAO,EAAE,8BAA8B;IACvC,IAAI,EAAE,CAAC,WAAW,CAAC;IACnB,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;IACtG,SAAS,EAAE,cAAc;CACzB,CAAC,CAAA;AAEF,MAAM,YAAY,GAAG,WAAW,CAAC;IAChC,MAAM,EAAE,MAAM;IACd,IAAI,EAAE,aAAa;IACnB,WAAW,EAAE,SAAS;IACtB,OAAO,EAAE,+DAA+D;IACxE,IAAI,EAAE,CAAC,WAAW,CAAC;IACnB,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,oBAAoB,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;IACxG,SAAS,EAAE,gBAAgB;CAC3B,CAAC,CAAA;AAEF,MAAM,WAAW,GAAG,WAAW,CAAC;IAC/B,MAAM,EAAE,MAAM;IACd,IAAI,EAAE,YAAY;IAClB,WAAW,EAAE,QAAQ;IACrB,OAAO,EAAE,6DAA6D;IACtE,IAAI,EAAE,CAAC,MAAM,CAAC;IACd,SAAS,EAAE,eAAe;CAC1B,CAAC,CAAA;AAEF,MAAM,WAAW,GAAG,WAAW,CAAC;IAC/B,MAAM,EAAE,MAAM;IACd,IAAI,EAAE,YAAY;IAClB,WAAW,EAAE,QAAQ;IACrB,OAAO,EAAE,6DAA6D;IACtE,IAAI,EAAE,CAAC,YAAY,CAAC;IACpB,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,mBAAmB,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;IACvG,SAAS,EAAE,eAAe;CAC1B,CAAC,CAAA;AAEF,MAAM,WAAW,GAAG,WAAW,CAAC;IAC/B,MAAM,EAAE,KAAK;IACb,IAAI,EAAE,SAAS;IACf,WAAW,EAAE,QAAQ;IACrB,OAAO,EAAE,0BAA0B;IACnC,IAAI,EAAE,CAAC,MAAM,CAAC;IACd,SAAS,EAAE,eAAe;CAC1B,CAAC,CAAA;AAEF,MAAM,YAAY,GAAG,WAAW,CAAC;IAChC,MAAM,EAAE,KAAK;IACb,IAAI,EAAE,UAAU;IAChB,WAAW,EAAE,SAAS;IACtB,OAAO,EAAE,oCAAoC;IAC7C,IAAI,EAAE,CAAC,MAAM,CAAC;IACd,SAAS,EAAE,gBAAgB;CAC3B,CAAC,CAAA;AAEF;;;;GAIG;AACH,SAAS,eAAe,CAAC,UAA6C;IACrE,MAAM,GAAG,GAAkB,EAAE,CAAA;IAE7B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QACvD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;QAErD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACzB,GAAG,CAAC,GAAmB,CAAC,GAAG,KAAK,CAAA;QACjC,CAAC;IACF,CAAC;IAED,OAAO,GAAG,CAAA;AACX,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,0BAA0B,CACzC,GAAgB,EAChB,MAA0B,EAC1B,UAA6C,EAAE;IAE/C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,iBAAiB,CAAA;IAEtD,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACtC,IAAI,CAAC,MAAM,CAAC,KAAK;YAAE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,uBAAuB,EAAE,EAAE,GAAG,CAAC,CAAA;QACzE,MAAM,OAAO,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,CAAA;QAE9C,IAAI,CAAC,OAAO;YAAE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,EAAE,GAAG,CAAC,CAAA;QAClE,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,MAAM,CAAA;QAC7C,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;QAEtD,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;IAC5B,CAAC,CAAC,CAAA;IAEF,GAAG,CAAC,OAAO,CACV,cAAc,EACd,KAAK,EAAE,CAAC,EAAE,EAAE;QACX,IAAI,CAAC,MAAM,CAAC,KAAK;YAAE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,uBAAuB,EAAE,EAAE,GAAG,CAAC,CAAA;QACzE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;QAC9C,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAA;QAE9B,IAAI,CAAC,OAAO;YAAE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,EAAE,GAAG,CAAC,CAAA;QAClE,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,IAAI,KAAK,EAAE,CAAC,CAAA;QAEtE,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;IAC5B,CAAC,EACD,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACb,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,EAAE,GAAG,CAAC,CAAA;QAEzE,OAAO,SAAS,CAAA;IACjB,CAAC,CACD,CAAA;IAED,GAAG,CAAC,OAAO,CACV,YAAY,EACZ,KAAK,EAAE,CAAC,EAAE,EAAE;QACX,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,wBAAwB,EAAE,2BAA2B,CAAC,CAAA;QACnG,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;QACvC,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAA;QAE9B,IAAI,CAAC,OAAO;YAAE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,EAAE,GAAG,CAAC,CAAA;QAClE,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,EAAE,CAAA;QAE5B,IAAI,CAAC;YACJ,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;YAC7C,WAAW,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,IAAI,OAAO,CAAC,CAAC,CAAA;YAElF,iGAAiG;YACjG,oGAAoG;YACpG,oGAAoG;YACpG,6FAA6F;YAC7F,OAAO,CAAC,CAAC,IAAI,CAAC,OAA0D,EAAE,GAAG,CAAC,CAAA;QAC/E,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,WAAW,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,OAAO,CAAC,CAAA;YAC5C,MAAM,KAAK,CAAA;QACZ,CAAC;IACF,CAAC,EACD,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACb,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,EAAE,GAAG,CAAC,CAAA;QAEzE,OAAO,SAAS,CAAA;IACjB,CAAC,CACD,CAAA;IAED,GAAG,CAAC,OAAO,CACV,UAAU,EACV,KAAK,EAAE,CAAC,EAAE,EAAE;QACX,MAAM,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;QAEzC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,CAAA;QAE/D,IAAI,SAAS,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC;YACjC,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,SAAS,CAAC,MAAM,MAAM,QAAQ,EAAE,EAAE,EAAE,GAAG,CAAC,CAAA;QACpF,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,KAAK;YAAE,OAAO,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,wBAAwB,EAAE,2BAA2B,CAAC,CAAA;QAEjG,6FAA6F;QAC7F,sEAAsE;QACtE,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,EAAE,CAAA;QAE5B,IAAI,CAAC;YACJ,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;YAC7C,WAAW,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,OAAO,CAAC,CAAA;YAE5C,4FAA4F;YAC5F,oGAAoG;YACpG,4BAA4B;YAC5B,OAAO,CAAC,CAAC,IAAI,CAAC,OAAyD,EAAE,GAAG,CAAC,CAAA;QAC9E,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,WAAW,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,OAAO,CAAC,CAAA;YAC5C,MAAM,KAAK,CAAA;QACZ,CAAC;IACF,CAAC,EACD,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACb,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,sCAAsC,EAAE,EAAE,GAAG,CAAC,CAAA;QAE1F,OAAO,SAAS,CAAA;IACjB,CAAC,CACD,CAAA;IAED,GAAG,CAAC,OAAO,CACV,YAAY;IACZ,gGAAgG;IAChG,8FAA8F;IAC9F,0EAA0E;IAC1E,KAAK,EAAE,CAAC,EAAE,EAAE;QACX,IAAI,CAAC,MAAM,CAAC,WAAW;YAAE,OAAO,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,wBAAwB,EAAE,2BAA2B,CAAC,CAAA;QACvG,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;QAC1C,oGAAoG;QACpG,+FAA+F;QAC/F,uGAAuG;QACvG,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,IAA8B,EAAE,IAAI,IAAI,EAAE,CAAC,CAAA;QAEpF,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;IAC5B,CAAC,EACD,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACb,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,2CAA2C,EAAE,EAAE,GAAG,CAAC,CAAA;QAE/F,OAAO,SAAS,CAAA;IACjB,CAAC,CACD,CAAA;IAED,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACpC,IAAI,CAAC,MAAM,CAAC,MAAM;YAAE,OAAO,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,wBAAwB,EAAE,2BAA2B,CAAC,CAAA;QAClG,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,MAAM,EAAE,CAAA;QAErC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;IAC5B,CAAC,CAAC,CAAA;IAEF,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE;QAC9B,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;QAC3E,MAAM,IAAI,GAAG,eAAe,CAAC,UAAU,CAAC,CAAA;QACxC,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,aAAiD,CAAC,CAAA;QAEjG,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,CAAC,CAAA;IACpE,CAAC,CAAC,CAAA;IAEF,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE;QAC9B,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC,CAAA;QAEjE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,aAAa,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,CAAA;IACpF,CAAC,CAAC,CAAA;IAEF,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,GAAG,CAAC,CAAC,CAAA;AACjE,CAAC"}
|
package/out/schema.d.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* Zod wire schemas for the native `/v1` surface. Unlike the drop-ins (photon, nominatim,
|
|
7
|
+
* libpostal), nothing here is a vendor contract — this surface is ours to design, so request
|
|
8
|
+
* bodies are REQUIRED and validator-enforced (no legacy tolerance to preserve). A `defaultHook`
|
|
9
|
+
* on the app (wired in Task 3) maps validation failures through the shared `APIErrorSchema`
|
|
10
|
+
* envelope (`apiError(c, 400, "invalid request body", <zod summary>)`) — the documented pattern
|
|
11
|
+
* boundary from phase 2: where no legacy contract exists, the validator MAY speak, but only in
|
|
12
|
+
* our envelope.
|
|
13
|
+
*
|
|
14
|
+
* `APIErrorSchema` itself is owned by `@mailwoman/api-kit` (plumbing shared by every native
|
|
15
|
+
* surface) — it's re-exported here so route modules can import every schema they need, request
|
|
16
|
+
* and error alike, from this one file.
|
|
17
|
+
*/
|
|
18
|
+
import { z } from "@hono/zod-openapi";
|
|
19
|
+
export { APIErrorSchema } from "@mailwoman/api-kit";
|
|
20
|
+
/** `POST /v1/parse` request body. */
|
|
21
|
+
export declare const ParseRequestSchema: z.ZodObject<{
|
|
22
|
+
address: z.ZodString;
|
|
23
|
+
debug: z.ZodOptional<z.ZodBoolean>;
|
|
24
|
+
}, z.core.$strip>;
|
|
25
|
+
/**
|
|
26
|
+
* `POST /v1/parse` response — a loose mirror of {@linkcode ParseOutcome} (`engine.ts`). `solutions` entries aren't fully
|
|
27
|
+
* modeled: `SerializedSolution` (`@mailwoman/core/solver`) carries the solver's internal match/classification detail,
|
|
28
|
+
* which is the engine's contract, not this wire schema's — `score`/`penalty` are the two fields every solution always
|
|
29
|
+
* carries (always-present numbers, cheap to pin accurately); `classifications`/`matches` stay loose passthrough.
|
|
30
|
+
*/
|
|
31
|
+
export declare const ParseOutcomeSchema: z.ZodObject<{
|
|
32
|
+
input: z.ZodObject<{
|
|
33
|
+
body: z.ZodString;
|
|
34
|
+
start: z.ZodNumber;
|
|
35
|
+
end: z.ZodNumber;
|
|
36
|
+
}, z.core.$strip>;
|
|
37
|
+
solutions: z.ZodArray<z.ZodObject<{
|
|
38
|
+
score: z.ZodNumber;
|
|
39
|
+
penalty: z.ZodNumber;
|
|
40
|
+
}, z.core.$loose>>;
|
|
41
|
+
debug: z.ZodOptional<z.ZodString>;
|
|
42
|
+
}, z.core.$strip>;
|
|
43
|
+
/** `POST /v1/geocode` request body. */
|
|
44
|
+
export declare const GeocodeRequestSchema: z.ZodObject<{
|
|
45
|
+
address: z.ZodString;
|
|
46
|
+
}, z.core.$strip>;
|
|
47
|
+
/**
|
|
48
|
+
* `POST /v1/geocode` response — a hand-modeled mirror of `GeocodeResult`'s wire shape (`mailwoman/geocode-core.ts`),
|
|
49
|
+
* `.loose()` so a field the engine adds that this schema doesn't yet know about still rides through undocumented rather
|
|
50
|
+
* than being stripped or rejected. DOC-ACCURACY ONLY: the route passes `engine.geocode()`'s outcome through verbatim
|
|
51
|
+
* (`GeocodeOutcome = Record<string, unknown>`, `api/engine.ts`) — nothing here validates a real response, so a
|
|
52
|
+
* schema/engine mismatch can never reject or mutate a result at runtime. Deliberately carries NO import from
|
|
53
|
+
* `mailwoman` (the engine-agnosticism boundary — `mailwoman` is the one workspace allowed to depend on
|
|
54
|
+
* `@mailwoman/api`, never the reverse). `mailwoman/test/api-schema-drift.test.ts` is the compile-time tripwire that
|
|
55
|
+
* catches this shape drifting from the real `GeocodeResult` interface.
|
|
56
|
+
*/
|
|
57
|
+
export declare const GeocodeOutcomeSchema: z.ZodObject<{
|
|
58
|
+
input: z.ZodString;
|
|
59
|
+
lat: z.ZodNullable<z.ZodNumber>;
|
|
60
|
+
lon: z.ZodNullable<z.ZodNumber>;
|
|
61
|
+
resolution_tier: z.ZodEnum<{
|
|
62
|
+
address_point: "address_point";
|
|
63
|
+
interpolated: "interpolated";
|
|
64
|
+
street: "street";
|
|
65
|
+
admin: "admin";
|
|
66
|
+
}>;
|
|
67
|
+
uncertainty_m: z.ZodNullable<z.ZodNumber>;
|
|
68
|
+
locality: z.ZodNullable<z.ZodString>;
|
|
69
|
+
region: z.ZodNullable<z.ZodString>;
|
|
70
|
+
postcode: z.ZodNullable<z.ZodString>;
|
|
71
|
+
house_number: z.ZodNullable<z.ZodString>;
|
|
72
|
+
street: z.ZodNullable<z.ZodString>;
|
|
73
|
+
countryCode: z.ZodNullable<z.ZodString>;
|
|
74
|
+
hierarchy: z.ZodArray<z.ZodObject<{
|
|
75
|
+
tag: z.ZodString;
|
|
76
|
+
value: z.ZodString;
|
|
77
|
+
name: z.ZodString;
|
|
78
|
+
lat: z.ZodOptional<z.ZodNumber>;
|
|
79
|
+
lon: z.ZodOptional<z.ZodNumber>;
|
|
80
|
+
placeID: z.ZodOptional<z.ZodString>;
|
|
81
|
+
}, z.core.$strip>>;
|
|
82
|
+
candidates: z.ZodArray<z.ZodObject<{
|
|
83
|
+
name: z.ZodString;
|
|
84
|
+
tag: z.ZodString;
|
|
85
|
+
lat: z.ZodNumber;
|
|
86
|
+
lon: z.ZodNumber;
|
|
87
|
+
countryCode: z.ZodNullable<z.ZodString>;
|
|
88
|
+
placeID: z.ZodOptional<z.ZodString>;
|
|
89
|
+
}, z.core.$strip>>;
|
|
90
|
+
}, z.core.$loose>;
|
|
91
|
+
/** `POST /v1/batch` request body. */
|
|
92
|
+
export declare const BatchRequestSchema: z.ZodObject<{
|
|
93
|
+
addresses: z.ZodArray<z.ZodString>;
|
|
94
|
+
}, z.core.$strip>;
|
|
95
|
+
/** `POST /v1/batch` response — one `GeocodeOutcome`, or an `{ input, error }` slot, per row (per-row isolation). */
|
|
96
|
+
export declare const BatchResponseSchema: z.ZodObject<{
|
|
97
|
+
results: z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
98
|
+
input: z.ZodString;
|
|
99
|
+
lat: z.ZodNullable<z.ZodNumber>;
|
|
100
|
+
lon: z.ZodNullable<z.ZodNumber>;
|
|
101
|
+
resolution_tier: z.ZodEnum<{
|
|
102
|
+
address_point: "address_point";
|
|
103
|
+
interpolated: "interpolated";
|
|
104
|
+
street: "street";
|
|
105
|
+
admin: "admin";
|
|
106
|
+
}>;
|
|
107
|
+
uncertainty_m: z.ZodNullable<z.ZodNumber>;
|
|
108
|
+
locality: z.ZodNullable<z.ZodString>;
|
|
109
|
+
region: z.ZodNullable<z.ZodString>;
|
|
110
|
+
postcode: z.ZodNullable<z.ZodString>;
|
|
111
|
+
house_number: z.ZodNullable<z.ZodString>;
|
|
112
|
+
street: z.ZodNullable<z.ZodString>;
|
|
113
|
+
countryCode: z.ZodNullable<z.ZodString>;
|
|
114
|
+
hierarchy: z.ZodArray<z.ZodObject<{
|
|
115
|
+
tag: z.ZodString;
|
|
116
|
+
value: z.ZodString;
|
|
117
|
+
name: z.ZodString;
|
|
118
|
+
lat: z.ZodOptional<z.ZodNumber>;
|
|
119
|
+
lon: z.ZodOptional<z.ZodNumber>;
|
|
120
|
+
placeID: z.ZodOptional<z.ZodString>;
|
|
121
|
+
}, z.core.$strip>>;
|
|
122
|
+
candidates: z.ZodArray<z.ZodObject<{
|
|
123
|
+
name: z.ZodString;
|
|
124
|
+
tag: z.ZodString;
|
|
125
|
+
lat: z.ZodNumber;
|
|
126
|
+
lon: z.ZodNumber;
|
|
127
|
+
countryCode: z.ZodNullable<z.ZodString>;
|
|
128
|
+
placeID: z.ZodOptional<z.ZodString>;
|
|
129
|
+
}, z.core.$strip>>;
|
|
130
|
+
}, z.core.$loose>, z.ZodObject<{
|
|
131
|
+
input: z.ZodString;
|
|
132
|
+
error: z.ZodString;
|
|
133
|
+
}, z.core.$strip>]>>;
|
|
134
|
+
}, z.core.$strip>;
|
|
135
|
+
/**
|
|
136
|
+
* `POST /v1/resolve` request body — an already-decoded `AddressTree` (the parser's output) to resolve against the
|
|
137
|
+
* gazetteer.
|
|
138
|
+
*/
|
|
139
|
+
export declare const ResolveRequestSchema: z.ZodObject<{
|
|
140
|
+
tree: z.ZodObject<{
|
|
141
|
+
roots: z.ZodArray<z.ZodUnknown>;
|
|
142
|
+
}, z.core.$loose>;
|
|
143
|
+
opts: z.ZodOptional<z.ZodObject<{}, z.core.$loose>>;
|
|
144
|
+
}, z.core.$strip>;
|
|
145
|
+
/** `POST /v1/resolve` response — the same tree, decorated in place with gazetteer coords + attribution. */
|
|
146
|
+
export declare const ResolveResponseSchema: z.ZodObject<{
|
|
147
|
+
tree: z.ZodObject<{
|
|
148
|
+
roots: z.ZodArray<z.ZodUnknown>;
|
|
149
|
+
}, z.core.$loose>;
|
|
150
|
+
}, z.core.$strip>;
|
|
151
|
+
/**
|
|
152
|
+
* `POST /v1/format` request body. `components` accepts `string | string[]` per key on the wire — a handler-side
|
|
153
|
+
* concern, not this schema's: `@mailwoman/formatter`'s `ComponentDict` (`format.ts`) is `Partial<Record<ComponentTag,
|
|
154
|
+
* string>>`, single-string only, so a route handler must join array values before calling
|
|
155
|
+
* `formatAddress`/`canonicalKey`.
|
|
156
|
+
*/
|
|
157
|
+
export declare const FormatRequestSchema: z.ZodObject<{
|
|
158
|
+
components: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
159
|
+
country: z.ZodString;
|
|
160
|
+
options: z.ZodOptional<z.ZodObject<{}, z.core.$loose>>;
|
|
161
|
+
}, z.core.$strip>;
|
|
162
|
+
/** `POST /v1/format` response — the rendered string plus the deterministic canonical match key. */
|
|
163
|
+
export declare const FormatResponseSchema: z.ZodObject<{
|
|
164
|
+
formatted: z.ZodString;
|
|
165
|
+
canonicalKey: z.ZodString;
|
|
166
|
+
}, z.core.$strip>;
|
|
167
|
+
/**
|
|
168
|
+
* `GET /health` response — `status`/`uptime_s` are stamped by the ROUTE itself, unconditionally, regardless of engine
|
|
169
|
+
* (`api/routes.ts`'s `healthRoute` handler: `{ status: "ok", uptime_s, ...engine.health?.() }`), so those two are cheap
|
|
170
|
+
* + accurate to pin. Everything else is `HealthData` (`api/engine.ts`) — an engine-defined block (model card, data-root
|
|
171
|
+
* inventory for `mailwoman serve`; something else entirely for another engine) — stays loose.
|
|
172
|
+
*/
|
|
173
|
+
export declare const HealthResponseSchema: z.ZodObject<{
|
|
174
|
+
status: z.ZodLiteral<"ok">;
|
|
175
|
+
uptime_s: z.ZodNumber;
|
|
176
|
+
}, z.core.$loose>;
|
|
177
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,mBAAmB,CAAA;AAErC,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAEnD,qCAAqC;AACrC,eAAO,MAAM,kBAAkB;;;iBAKN,CAAA;AAEzB;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;iBAUN,CAAA;AAEzB,uCAAuC;AACvC,eAAO,MAAM,oBAAoB;;iBAIN,CAAA;AAiC3B;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiBN,CAAA;AAE3B,qCAAqC;AACrC,eAAO,MAAM,kBAAkB;;iBAIN,CAAA;AAEzB,oHAAoH;AACpH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAIN,CAAA;AAE1B;;;GAGG;AACH,eAAO,MAAM,oBAAoB;;;;;iBAKN,CAAA;AAE3B,2GAA2G;AAC3G,eAAO,MAAM,qBAAqB;;;;iBAIN,CAAA;AAE5B;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB;;;;iBAMN,CAAA;AAE1B,mGAAmG;AACnG,eAAO,MAAM,oBAAoB;;;iBAKN,CAAA;AAE3B;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB;;;iBAMN,CAAA"}
|
package/out/schema.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* Zod wire schemas for the native `/v1` surface. Unlike the drop-ins (photon, nominatim,
|
|
7
|
+
* libpostal), nothing here is a vendor contract — this surface is ours to design, so request
|
|
8
|
+
* bodies are REQUIRED and validator-enforced (no legacy tolerance to preserve). A `defaultHook`
|
|
9
|
+
* on the app (wired in Task 3) maps validation failures through the shared `APIErrorSchema`
|
|
10
|
+
* envelope (`apiError(c, 400, "invalid request body", <zod summary>)`) — the documented pattern
|
|
11
|
+
* boundary from phase 2: where no legacy contract exists, the validator MAY speak, but only in
|
|
12
|
+
* our envelope.
|
|
13
|
+
*
|
|
14
|
+
* `APIErrorSchema` itself is owned by `@mailwoman/api-kit` (plumbing shared by every native
|
|
15
|
+
* surface) — it's re-exported here so route modules can import every schema they need, request
|
|
16
|
+
* and error alike, from this one file.
|
|
17
|
+
*/
|
|
18
|
+
import { z } from "@hono/zod-openapi";
|
|
19
|
+
export { APIErrorSchema } from "@mailwoman/api-kit";
|
|
20
|
+
/** `POST /v1/parse` request body. */
|
|
21
|
+
export const ParseRequestSchema = z
|
|
22
|
+
.object({
|
|
23
|
+
address: z.string(),
|
|
24
|
+
debug: z.boolean().optional(),
|
|
25
|
+
})
|
|
26
|
+
.openapi("ParseRequest");
|
|
27
|
+
/**
|
|
28
|
+
* `POST /v1/parse` response — a loose mirror of {@linkcode ParseOutcome} (`engine.ts`). `solutions` entries aren't fully
|
|
29
|
+
* modeled: `SerializedSolution` (`@mailwoman/core/solver`) carries the solver's internal match/classification detail,
|
|
30
|
+
* which is the engine's contract, not this wire schema's — `score`/`penalty` are the two fields every solution always
|
|
31
|
+
* carries (always-present numbers, cheap to pin accurately); `classifications`/`matches` stay loose passthrough.
|
|
32
|
+
*/
|
|
33
|
+
export const ParseOutcomeSchema = z
|
|
34
|
+
.object({
|
|
35
|
+
input: z.object({
|
|
36
|
+
body: z.string(),
|
|
37
|
+
start: z.number(),
|
|
38
|
+
end: z.number(),
|
|
39
|
+
}),
|
|
40
|
+
solutions: z.array(z.looseObject({ score: z.number(), penalty: z.number() })),
|
|
41
|
+
debug: z.string().optional(),
|
|
42
|
+
})
|
|
43
|
+
.openapi("ParseOutcome");
|
|
44
|
+
/** `POST /v1/geocode` request body. */
|
|
45
|
+
export const GeocodeRequestSchema = z
|
|
46
|
+
.object({
|
|
47
|
+
address: z.string(),
|
|
48
|
+
})
|
|
49
|
+
.openapi("GeocodeRequest");
|
|
50
|
+
/**
|
|
51
|
+
* One `GeocodeOutcome.hierarchy` entry — locality → country, most specific first. `name` is the resolved gazetteer name
|
|
52
|
+
* (proper-cased canonical); `value` is the raw parsed span. Mirrors `GeocodeResult["hierarchy"]` entries
|
|
53
|
+
* (`mailwoman/geocode-core.ts`), hand-modeled — see {@link GeocodeOutcomeSchema} for the no-import rationale.
|
|
54
|
+
*/
|
|
55
|
+
const GeocodeHierarchyEntrySchema = z
|
|
56
|
+
.object({
|
|
57
|
+
tag: z.string(),
|
|
58
|
+
value: z.string(),
|
|
59
|
+
name: z.string(),
|
|
60
|
+
lat: z.number().optional(),
|
|
61
|
+
lon: z.number().optional(),
|
|
62
|
+
placeID: z.string().optional(),
|
|
63
|
+
})
|
|
64
|
+
.openapi("GeocodeHierarchyEntry");
|
|
65
|
+
/**
|
|
66
|
+
* One `GeocodeOutcome.candidates` entry — a ranked alternative place for the query's primary result (the winning place
|
|
67
|
+
* first, then same-query runner-ups). Mirrors `GeocodeResult["candidates"]` entries.
|
|
68
|
+
*/
|
|
69
|
+
const GeocodeCandidateSchema = z
|
|
70
|
+
.object({
|
|
71
|
+
name: z.string(),
|
|
72
|
+
tag: z.string(),
|
|
73
|
+
lat: z.number(),
|
|
74
|
+
lon: z.number(),
|
|
75
|
+
countryCode: z.string().nullable(),
|
|
76
|
+
placeID: z.string().optional(),
|
|
77
|
+
})
|
|
78
|
+
.openapi("GeocodeCandidate");
|
|
79
|
+
/**
|
|
80
|
+
* `POST /v1/geocode` response — a hand-modeled mirror of `GeocodeResult`'s wire shape (`mailwoman/geocode-core.ts`),
|
|
81
|
+
* `.loose()` so a field the engine adds that this schema doesn't yet know about still rides through undocumented rather
|
|
82
|
+
* than being stripped or rejected. DOC-ACCURACY ONLY: the route passes `engine.geocode()`'s outcome through verbatim
|
|
83
|
+
* (`GeocodeOutcome = Record<string, unknown>`, `api/engine.ts`) — nothing here validates a real response, so a
|
|
84
|
+
* schema/engine mismatch can never reject or mutate a result at runtime. Deliberately carries NO import from
|
|
85
|
+
* `mailwoman` (the engine-agnosticism boundary — `mailwoman` is the one workspace allowed to depend on
|
|
86
|
+
* `@mailwoman/api`, never the reverse). `mailwoman/test/api-schema-drift.test.ts` is the compile-time tripwire that
|
|
87
|
+
* catches this shape drifting from the real `GeocodeResult` interface.
|
|
88
|
+
*/
|
|
89
|
+
export const GeocodeOutcomeSchema = z
|
|
90
|
+
.object({
|
|
91
|
+
input: z.string(),
|
|
92
|
+
lat: z.number().nullable(),
|
|
93
|
+
lon: z.number().nullable(),
|
|
94
|
+
resolution_tier: z.enum(["address_point", "interpolated", "street", "admin"]),
|
|
95
|
+
uncertainty_m: z.number().nullable(),
|
|
96
|
+
locality: z.string().nullable(),
|
|
97
|
+
region: z.string().nullable(),
|
|
98
|
+
postcode: z.string().nullable(),
|
|
99
|
+
house_number: z.string().nullable(),
|
|
100
|
+
street: z.string().nullable(),
|
|
101
|
+
countryCode: z.string().nullable(),
|
|
102
|
+
hierarchy: z.array(GeocodeHierarchyEntrySchema),
|
|
103
|
+
candidates: z.array(GeocodeCandidateSchema),
|
|
104
|
+
})
|
|
105
|
+
.loose()
|
|
106
|
+
.openapi("GeocodeOutcome");
|
|
107
|
+
/** `POST /v1/batch` request body. */
|
|
108
|
+
export const BatchRequestSchema = z
|
|
109
|
+
.object({
|
|
110
|
+
addresses: z.array(z.string()),
|
|
111
|
+
})
|
|
112
|
+
.openapi("BatchRequest");
|
|
113
|
+
/** `POST /v1/batch` response — one `GeocodeOutcome`, or an `{ input, error }` slot, per row (per-row isolation). */
|
|
114
|
+
export const BatchResponseSchema = z
|
|
115
|
+
.object({
|
|
116
|
+
results: z.array(z.union([GeocodeOutcomeSchema, z.object({ input: z.string(), error: z.string() })])),
|
|
117
|
+
})
|
|
118
|
+
.openapi("BatchResponse");
|
|
119
|
+
/**
|
|
120
|
+
* `POST /v1/resolve` request body — an already-decoded `AddressTree` (the parser's output) to resolve against the
|
|
121
|
+
* gazetteer.
|
|
122
|
+
*/
|
|
123
|
+
export const ResolveRequestSchema = z
|
|
124
|
+
.object({
|
|
125
|
+
tree: z.looseObject({ roots: z.array(z.unknown()) }),
|
|
126
|
+
opts: z.looseObject({}).optional(),
|
|
127
|
+
})
|
|
128
|
+
.openapi("ResolveRequest");
|
|
129
|
+
/** `POST /v1/resolve` response — the same tree, decorated in place with gazetteer coords + attribution. */
|
|
130
|
+
export const ResolveResponseSchema = z
|
|
131
|
+
.object({
|
|
132
|
+
tree: z.looseObject({ roots: z.array(z.unknown()) }),
|
|
133
|
+
})
|
|
134
|
+
.openapi("ResolveResponse");
|
|
135
|
+
/**
|
|
136
|
+
* `POST /v1/format` request body. `components` accepts `string | string[]` per key on the wire — a handler-side
|
|
137
|
+
* concern, not this schema's: `@mailwoman/formatter`'s `ComponentDict` (`format.ts`) is `Partial<Record<ComponentTag,
|
|
138
|
+
* string>>`, single-string only, so a route handler must join array values before calling
|
|
139
|
+
* `formatAddress`/`canonicalKey`.
|
|
140
|
+
*/
|
|
141
|
+
export const FormatRequestSchema = z
|
|
142
|
+
.object({
|
|
143
|
+
components: z.record(z.string(), z.union([z.string(), z.array(z.string())])),
|
|
144
|
+
country: z.string(),
|
|
145
|
+
options: z.looseObject({}).optional(),
|
|
146
|
+
})
|
|
147
|
+
.openapi("FormatRequest");
|
|
148
|
+
/** `POST /v1/format` response — the rendered string plus the deterministic canonical match key. */
|
|
149
|
+
export const FormatResponseSchema = z
|
|
150
|
+
.object({
|
|
151
|
+
formatted: z.string(),
|
|
152
|
+
canonicalKey: z.string(),
|
|
153
|
+
})
|
|
154
|
+
.openapi("FormatResponse");
|
|
155
|
+
/**
|
|
156
|
+
* `GET /health` response — `status`/`uptime_s` are stamped by the ROUTE itself, unconditionally, regardless of engine
|
|
157
|
+
* (`api/routes.ts`'s `healthRoute` handler: `{ status: "ok", uptime_s, ...engine.health?.() }`), so those two are cheap
|
|
158
|
+
* + accurate to pin. Everything else is `HealthData` (`api/engine.ts`) — an engine-defined block (model card, data-root
|
|
159
|
+
* inventory for `mailwoman serve`; something else entirely for another engine) — stays loose.
|
|
160
|
+
*/
|
|
161
|
+
export const HealthResponseSchema = z
|
|
162
|
+
.object({
|
|
163
|
+
status: z.literal("ok"),
|
|
164
|
+
uptime_s: z.number(),
|
|
165
|
+
})
|
|
166
|
+
.loose()
|
|
167
|
+
.openapi("HealthResponse");
|
|
168
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../schema.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mailwoman/api",
|
|
3
|
+
"version": "6.0.0",
|
|
4
|
+
"description": "The native Mailwoman HTTP API — engine-agnostic /v1 surface (parse, geocode, batch, resolve, format) with health, metrics, and an emitted OpenAPI document.",
|
|
5
|
+
"license": "AGPL-3.0-only OR LicenseRef-Commercial",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/sister-software/mailwoman.git",
|
|
9
|
+
"directory": "api"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"out/**/*.js",
|
|
13
|
+
"out/**/*.js.map",
|
|
14
|
+
"out/**/*.d.ts",
|
|
15
|
+
"out/**/*.d.ts.map",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"type": "module",
|
|
19
|
+
"exports": {
|
|
20
|
+
"./package.json": "./package.json",
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./out/index.d.ts",
|
|
23
|
+
"default": "./out/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"exports": {
|
|
28
|
+
"./package.json": "./package.json",
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./out/index.d.ts",
|
|
31
|
+
"default": "./out/index.js"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@hono/zod-openapi": "^1.4.0",
|
|
38
|
+
"@mailwoman/api-kit": "6.0.0",
|
|
39
|
+
"@mailwoman/core": "6.0.0",
|
|
40
|
+
"@mailwoman/formatter": "6.0.0",
|
|
41
|
+
"hono": "^4.12.29",
|
|
42
|
+
"zod": "^4.4.3"
|
|
43
|
+
}
|
|
44
|
+
}
|