@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 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
@@ -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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"}
@@ -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
@@ -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"}
@@ -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":"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"}
@@ -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
+ }