@mailwoman/api 8.6.0 → 9.1.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/out/schema.d.ts +127 -5
- package/out/schema.d.ts.map +1 -1
- package/out/schema.js +97 -6
- package/out/schema.js.map +1 -1
- package/package.json +5 -5
- package/schema.ts +103 -6
package/out/schema.d.ts
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* Zod wire schemas for the native `/v1` surface. Unlike the drop-ins (photon, nominatim,
|
|
7
7
|
* libpostal), nothing here is a vendor contract — this surface is ours to design, so request
|
|
8
8
|
* bodies are REQUIRED and validator-enforced (no legacy tolerance to preserve). A `defaultHook`
|
|
9
|
-
* on the app
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* on the app maps validation failures through the shared `APIErrorSchema` envelope
|
|
10
|
+
* (`apiError(c, 400, "invalid request body", <zod summary>)`) — the pattern boundary every
|
|
11
|
+
* surface holds to: where no legacy contract exists, the validator MAY speak, but only in
|
|
12
12
|
* our envelope.
|
|
13
13
|
*
|
|
14
14
|
* `APIErrorSchema` itself is owned by `@mailwoman/api-kit` (plumbing shared by every native
|
|
@@ -29,6 +29,20 @@ export declare const InputModeSchema: z.ZodEnum<{
|
|
|
29
29
|
fragmented: "fragmented";
|
|
30
30
|
formatted: "formatted";
|
|
31
31
|
}>;
|
|
32
|
+
/**
|
|
33
|
+
* Longest accepted `address`, in characters.
|
|
34
|
+
*
|
|
35
|
+
* Sized against what the model can actually read, not against a guess at abuse. The classifier's window is 128
|
|
36
|
+
* SentencePiece pieces — roughly 330 characters of address text — and everything past it is truncated before inference,
|
|
37
|
+
* so input beyond this bound cannot influence a result. The margin over that window leaves room for scripts that
|
|
38
|
+
* tokenize denser than Latin, and for the department-and-division prefixes web forms concatenate.
|
|
39
|
+
*
|
|
40
|
+
* The bound exists because preprocessing is linear but not free: a 1 MB body costs ~1.7 s across normalize, query-shape
|
|
41
|
+
* and the phrase grouper, and Node runs them on the one thread every other request is waiting on. A cap here is cheaper
|
|
42
|
+
* than fairness plumbing, and rejecting is more honest than accepting a body whose tail the parser will silently
|
|
43
|
+
* discard.
|
|
44
|
+
*/
|
|
45
|
+
export declare const MAX_ADDRESS_LENGTH = 1024;
|
|
32
46
|
/**
|
|
33
47
|
* `POST /v1/parse` request body.
|
|
34
48
|
*/
|
|
@@ -85,12 +99,39 @@ export declare const GeocodeRequestSchema: z.ZodObject<{
|
|
|
85
99
|
*/
|
|
86
100
|
export declare const GeocodeOutcomeSchema: z.ZodObject<{
|
|
87
101
|
input: z.ZodString;
|
|
102
|
+
components: z.ZodRecord<z.ZodEnum<{
|
|
103
|
+
country: "country";
|
|
104
|
+
region: "region";
|
|
105
|
+
locality: "locality";
|
|
106
|
+
dependent_locality: "dependent_locality";
|
|
107
|
+
postcode: "postcode";
|
|
108
|
+
subregion: "subregion";
|
|
109
|
+
house_number: "house_number";
|
|
110
|
+
street: "street";
|
|
111
|
+
street_prefix: "street_prefix";
|
|
112
|
+
street_prefix_particle: "street_prefix_particle";
|
|
113
|
+
street_suffix: "street_suffix";
|
|
114
|
+
intersection_a: "intersection_a";
|
|
115
|
+
intersection_b: "intersection_b";
|
|
116
|
+
unit: "unit";
|
|
117
|
+
venue: "venue";
|
|
118
|
+
attention: "attention";
|
|
119
|
+
po_box: "po_box";
|
|
120
|
+
cedex: "cedex";
|
|
121
|
+
prefecture: "prefecture";
|
|
122
|
+
municipality: "municipality";
|
|
123
|
+
district: "district";
|
|
124
|
+
block: "block";
|
|
125
|
+
sub_block: "sub_block";
|
|
126
|
+
building_number: "building_number";
|
|
127
|
+
building_name: "building_name";
|
|
128
|
+
}> & z.core.$partial, z.ZodString>;
|
|
88
129
|
lat: z.ZodNullable<z.ZodNumber>;
|
|
89
130
|
lon: z.ZodNullable<z.ZodNumber>;
|
|
90
131
|
resolution_tier: z.ZodEnum<{
|
|
132
|
+
street: "street";
|
|
91
133
|
address_point: "address_point";
|
|
92
134
|
interpolated: "interpolated";
|
|
93
|
-
street: "street";
|
|
94
135
|
admin: "admin";
|
|
95
136
|
}>;
|
|
96
137
|
uncertainty_m: z.ZodNullable<z.ZodNumber>;
|
|
@@ -101,6 +142,7 @@ export declare const GeocodeOutcomeSchema: z.ZodObject<{
|
|
|
101
142
|
street: z.ZodNullable<z.ZodString>;
|
|
102
143
|
venue: z.ZodNullable<z.ZodString>;
|
|
103
144
|
dependent_locality: z.ZodNullable<z.ZodString>;
|
|
145
|
+
unit: z.ZodNullable<z.ZodString>;
|
|
104
146
|
countryCode: z.ZodNullable<z.ZodString>;
|
|
105
147
|
hierarchy: z.ZodArray<z.ZodObject<{
|
|
106
148
|
tag: z.ZodString;
|
|
@@ -118,6 +160,32 @@ export declare const GeocodeOutcomeSchema: z.ZodObject<{
|
|
|
118
160
|
countryCode: z.ZodNullable<z.ZodString>;
|
|
119
161
|
placeID: z.ZodOptional<z.ZodString>;
|
|
120
162
|
}, z.core.$strip>>;
|
|
163
|
+
postcode_country_scope: z.ZodNullable<z.ZodString>;
|
|
164
|
+
intent_markers: z.ZodArray<z.ZodObject<{
|
|
165
|
+
kind: z.ZodEnum<{
|
|
166
|
+
intersection: "intersection";
|
|
167
|
+
po_box: "po_box";
|
|
168
|
+
postcode_only: "postcode_only";
|
|
169
|
+
locality_only: "locality_only";
|
|
170
|
+
structured_address: "structured_address";
|
|
171
|
+
landmark: "landmark";
|
|
172
|
+
poi_query: "poi_query";
|
|
173
|
+
vague: "vague";
|
|
174
|
+
bare_toponym: "bare_toponym";
|
|
175
|
+
route_pair: "route_pair";
|
|
176
|
+
near_me: "near_me";
|
|
177
|
+
poi_category: "poi_category";
|
|
178
|
+
}>;
|
|
179
|
+
code: z.ZodEnum<{
|
|
180
|
+
poi_category: "poi_category";
|
|
181
|
+
declared_ambiguity: "declared_ambiguity";
|
|
182
|
+
declared_fork: "declared_fork";
|
|
183
|
+
focus_point_required: "focus_point_required";
|
|
184
|
+
}>;
|
|
185
|
+
mechanism: z.ZodString;
|
|
186
|
+
message: z.ZodString;
|
|
187
|
+
evidence: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
188
|
+
}, z.core.$strip>>;
|
|
121
189
|
}, z.core.$loose>;
|
|
122
190
|
/**
|
|
123
191
|
* `POST /v1/batch` request body.
|
|
@@ -135,12 +203,39 @@ export declare const BatchRequestSchema: z.ZodObject<{
|
|
|
135
203
|
export declare const BatchResponseSchema: z.ZodObject<{
|
|
136
204
|
results: z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
137
205
|
input: z.ZodString;
|
|
206
|
+
components: z.ZodRecord<z.ZodEnum<{
|
|
207
|
+
country: "country";
|
|
208
|
+
region: "region";
|
|
209
|
+
locality: "locality";
|
|
210
|
+
dependent_locality: "dependent_locality";
|
|
211
|
+
postcode: "postcode";
|
|
212
|
+
subregion: "subregion";
|
|
213
|
+
house_number: "house_number";
|
|
214
|
+
street: "street";
|
|
215
|
+
street_prefix: "street_prefix";
|
|
216
|
+
street_prefix_particle: "street_prefix_particle";
|
|
217
|
+
street_suffix: "street_suffix";
|
|
218
|
+
intersection_a: "intersection_a";
|
|
219
|
+
intersection_b: "intersection_b";
|
|
220
|
+
unit: "unit";
|
|
221
|
+
venue: "venue";
|
|
222
|
+
attention: "attention";
|
|
223
|
+
po_box: "po_box";
|
|
224
|
+
cedex: "cedex";
|
|
225
|
+
prefecture: "prefecture";
|
|
226
|
+
municipality: "municipality";
|
|
227
|
+
district: "district";
|
|
228
|
+
block: "block";
|
|
229
|
+
sub_block: "sub_block";
|
|
230
|
+
building_number: "building_number";
|
|
231
|
+
building_name: "building_name";
|
|
232
|
+
}> & z.core.$partial, z.ZodString>;
|
|
138
233
|
lat: z.ZodNullable<z.ZodNumber>;
|
|
139
234
|
lon: z.ZodNullable<z.ZodNumber>;
|
|
140
235
|
resolution_tier: z.ZodEnum<{
|
|
236
|
+
street: "street";
|
|
141
237
|
address_point: "address_point";
|
|
142
238
|
interpolated: "interpolated";
|
|
143
|
-
street: "street";
|
|
144
239
|
admin: "admin";
|
|
145
240
|
}>;
|
|
146
241
|
uncertainty_m: z.ZodNullable<z.ZodNumber>;
|
|
@@ -151,6 +246,7 @@ export declare const BatchResponseSchema: z.ZodObject<{
|
|
|
151
246
|
street: z.ZodNullable<z.ZodString>;
|
|
152
247
|
venue: z.ZodNullable<z.ZodString>;
|
|
153
248
|
dependent_locality: z.ZodNullable<z.ZodString>;
|
|
249
|
+
unit: z.ZodNullable<z.ZodString>;
|
|
154
250
|
countryCode: z.ZodNullable<z.ZodString>;
|
|
155
251
|
hierarchy: z.ZodArray<z.ZodObject<{
|
|
156
252
|
tag: z.ZodString;
|
|
@@ -168,6 +264,32 @@ export declare const BatchResponseSchema: z.ZodObject<{
|
|
|
168
264
|
countryCode: z.ZodNullable<z.ZodString>;
|
|
169
265
|
placeID: z.ZodOptional<z.ZodString>;
|
|
170
266
|
}, z.core.$strip>>;
|
|
267
|
+
postcode_country_scope: z.ZodNullable<z.ZodString>;
|
|
268
|
+
intent_markers: z.ZodArray<z.ZodObject<{
|
|
269
|
+
kind: z.ZodEnum<{
|
|
270
|
+
intersection: "intersection";
|
|
271
|
+
po_box: "po_box";
|
|
272
|
+
postcode_only: "postcode_only";
|
|
273
|
+
locality_only: "locality_only";
|
|
274
|
+
structured_address: "structured_address";
|
|
275
|
+
landmark: "landmark";
|
|
276
|
+
poi_query: "poi_query";
|
|
277
|
+
vague: "vague";
|
|
278
|
+
bare_toponym: "bare_toponym";
|
|
279
|
+
route_pair: "route_pair";
|
|
280
|
+
near_me: "near_me";
|
|
281
|
+
poi_category: "poi_category";
|
|
282
|
+
}>;
|
|
283
|
+
code: z.ZodEnum<{
|
|
284
|
+
poi_category: "poi_category";
|
|
285
|
+
declared_ambiguity: "declared_ambiguity";
|
|
286
|
+
declared_fork: "declared_fork";
|
|
287
|
+
focus_point_required: "focus_point_required";
|
|
288
|
+
}>;
|
|
289
|
+
mechanism: z.ZodString;
|
|
290
|
+
message: z.ZodString;
|
|
291
|
+
evidence: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
292
|
+
}, z.core.$strip>>;
|
|
171
293
|
}, z.core.$loose>, z.ZodObject<{
|
|
172
294
|
input: z.ZodString;
|
|
173
295
|
error: z.ZodString;
|
package/out/schema.d.ts.map
CHANGED
|
@@ -1 +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;;GAEG;AACH;;;;GAIG;AACH,eAAO,MAAM,eAAe;;;EAA2D,CAAA;AAEvF;;GAEG;AACH,eAAO,MAAM,kBAAkB;;;;;;;iBAMN,CAAA;AAEzB;;GAEG;AACH,eAAO,MAAM,oBAAoB;;;iBAA6E,CAAA;AAE9G;;;;GAIG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;iBAON,CAAA;AAEzB;;GAEG;AACH,eAAO,MAAM,oBAAoB;;;;;;iBAKN,CAAA;
|
|
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;;GAEG;AACH;;;;GAIG;AACH,eAAO,MAAM,eAAe;;;EAA2D,CAAA;AAEvF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB,OAAO,CAAA;AAEtC;;GAEG;AACH,eAAO,MAAM,kBAAkB;;;;;;;iBAMN,CAAA;AAEzB;;GAEG;AACH,eAAO,MAAM,oBAAoB;;;iBAA6E,CAAA;AAE9G;;;;GAIG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;iBAON,CAAA;AAEzB;;GAEG;AACH,eAAO,MAAM,oBAAoB;;;;;;iBAKN,CAAA;AAuG3B;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA+BN,CAAA;AAE3B;;GAEG;AACH,eAAO,MAAM,kBAAkB;;;;;;iBAUN,CAAA;AAYzB;;GAEG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAIN,CAAA;AAE1B;;;GAGG;AACH,eAAO,MAAM,oBAAoB;;;;;iBAKN,CAAA;AAE3B;;GAEG;AACH,eAAO,MAAM,qBAAqB;;;;iBAIN,CAAA;AAQ5B;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB;;;;iBAMN,CAAA;AAE1B;;GAEG;AACH,eAAO,MAAM,oBAAoB;;;iBAKN,CAAA;AAE3B;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB;;;iBAMN,CAAA"}
|
package/out/schema.js
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* Zod wire schemas for the native `/v1` surface. Unlike the drop-ins (photon, nominatim,
|
|
7
7
|
* libpostal), nothing here is a vendor contract — this surface is ours to design, so request
|
|
8
8
|
* bodies are REQUIRED and validator-enforced (no legacy tolerance to preserve). A `defaultHook`
|
|
9
|
-
* on the app
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* on the app maps validation failures through the shared `APIErrorSchema` envelope
|
|
10
|
+
* (`apiError(c, 400, "invalid request body", <zod summary>)`) — the pattern boundary every
|
|
11
|
+
* surface holds to: where no legacy contract exists, the validator MAY speak, but only in
|
|
12
12
|
* our envelope.
|
|
13
13
|
*
|
|
14
14
|
* `APIErrorSchema` itself is owned by `@mailwoman/api-kit` (plumbing shared by every native
|
|
@@ -26,12 +26,26 @@ export { APIErrorSchema } from "@mailwoman/api-kit";
|
|
|
26
26
|
* `/v1/batch` defaults to `formatted` (batch rows are the record register by nature).
|
|
27
27
|
*/
|
|
28
28
|
export const InputModeSchema = z.enum(["fragmented", "formatted"]).openapi("InputMode");
|
|
29
|
+
/**
|
|
30
|
+
* Longest accepted `address`, in characters.
|
|
31
|
+
*
|
|
32
|
+
* Sized against what the model can actually read, not against a guess at abuse. The classifier's window is 128
|
|
33
|
+
* SentencePiece pieces — roughly 330 characters of address text — and everything past it is truncated before inference,
|
|
34
|
+
* so input beyond this bound cannot influence a result. The margin over that window leaves room for scripts that
|
|
35
|
+
* tokenize denser than Latin, and for the department-and-division prefixes web forms concatenate.
|
|
36
|
+
*
|
|
37
|
+
* The bound exists because preprocessing is linear but not free: a 1 MB body costs ~1.7 s across normalize, query-shape
|
|
38
|
+
* and the phrase grouper, and Node runs them on the one thread every other request is waiting on. A cap here is cheaper
|
|
39
|
+
* than fairness plumbing, and rejecting is more honest than accepting a body whose tail the parser will silently
|
|
40
|
+
* discard.
|
|
41
|
+
*/
|
|
42
|
+
export const MAX_ADDRESS_LENGTH = 1024;
|
|
29
43
|
/**
|
|
30
44
|
* `POST /v1/parse` request body.
|
|
31
45
|
*/
|
|
32
46
|
export const ParseRequestSchema = z
|
|
33
47
|
.object({
|
|
34
|
-
address: z.string(),
|
|
48
|
+
address: z.string().max(MAX_ADDRESS_LENGTH),
|
|
35
49
|
debug: z.boolean().optional(),
|
|
36
50
|
input_mode: InputModeSchema.optional(),
|
|
37
51
|
})
|
|
@@ -58,7 +72,7 @@ export const ParseOutcomeSchema = z
|
|
|
58
72
|
*/
|
|
59
73
|
export const GeocodeRequestSchema = z
|
|
60
74
|
.object({
|
|
61
|
-
address: z.string(),
|
|
75
|
+
address: z.string().max(MAX_ADDRESS_LENGTH),
|
|
62
76
|
input_mode: InputModeSchema.optional(),
|
|
63
77
|
})
|
|
64
78
|
.openapi("GeocodeRequest");
|
|
@@ -91,6 +105,71 @@ const GeocodeCandidateSchema = z
|
|
|
91
105
|
placeID: z.string().optional(),
|
|
92
106
|
})
|
|
93
107
|
.openapi("GeocodeCandidate");
|
|
108
|
+
/**
|
|
109
|
+
* Canonical parsed-component map carried by `GeocodeResult.components`. Spelled out at this engine-agnostic API
|
|
110
|
+
* boundary for the same reason the result schema is hand-modeled; the compile-time drift pin in
|
|
111
|
+
* `mailwoman/test/api-schema-drift.test.ts` catches any mismatch with the real `ComponentTag`-keyed result type.
|
|
112
|
+
*/
|
|
113
|
+
const GeocodeComponentsSchema = z.partialRecord(z.enum([
|
|
114
|
+
"country",
|
|
115
|
+
"region",
|
|
116
|
+
"locality",
|
|
117
|
+
"dependent_locality",
|
|
118
|
+
"postcode",
|
|
119
|
+
"subregion",
|
|
120
|
+
"house_number",
|
|
121
|
+
"street",
|
|
122
|
+
"street_prefix",
|
|
123
|
+
"street_prefix_particle",
|
|
124
|
+
"street_suffix",
|
|
125
|
+
"intersection_a",
|
|
126
|
+
"intersection_b",
|
|
127
|
+
"unit",
|
|
128
|
+
"venue",
|
|
129
|
+
"attention",
|
|
130
|
+
"po_box",
|
|
131
|
+
"cedex",
|
|
132
|
+
"prefecture",
|
|
133
|
+
"municipality",
|
|
134
|
+
"district",
|
|
135
|
+
"block",
|
|
136
|
+
"sub_block",
|
|
137
|
+
"building_number",
|
|
138
|
+
"building_name",
|
|
139
|
+
]), z.string());
|
|
140
|
+
/**
|
|
141
|
+
* One `GeocodeOutcome.intent_markers` entry — an advisory the ROAD_TO_V9 §4 intent vocabulary raised about the QUERY.
|
|
142
|
+
* Mirrors `QueryIntentMarker` (`core/pipeline/types.ts`).
|
|
143
|
+
*
|
|
144
|
+
* `evidence` is deliberately open (`z.record`): each `code` carries its own measurement — a dominance margin, a pair of
|
|
145
|
+
* interpretations, a taxonomy id — and flattening those into one closed shape would either lose the numbers or invent
|
|
146
|
+
* fields that do not apply. `code` is the discriminator a client branches on.
|
|
147
|
+
*/
|
|
148
|
+
const QueryIntentMarkerSchema = z
|
|
149
|
+
.object({
|
|
150
|
+
// Spelled out rather than `z.string()` so `mailwoman/test/api-schema-drift.test.ts`'s schema-too-wide direction
|
|
151
|
+
// keeps biting: a new `QueryKind` that never reaches this list is a documented contract that has quietly stopped
|
|
152
|
+
// describing the real one.
|
|
153
|
+
kind: z.enum([
|
|
154
|
+
"postcode_only",
|
|
155
|
+
"locality_only",
|
|
156
|
+
"structured_address",
|
|
157
|
+
"intersection",
|
|
158
|
+
"po_box",
|
|
159
|
+
"landmark",
|
|
160
|
+
"poi_query",
|
|
161
|
+
"vague",
|
|
162
|
+
"bare_toponym",
|
|
163
|
+
"route_pair",
|
|
164
|
+
"near_me",
|
|
165
|
+
"poi_category",
|
|
166
|
+
]),
|
|
167
|
+
code: z.enum(["declared_ambiguity", "declared_fork", "focus_point_required", "poi_category"]),
|
|
168
|
+
mechanism: z.string(),
|
|
169
|
+
message: z.string(),
|
|
170
|
+
evidence: z.record(z.string(), z.unknown()).optional(),
|
|
171
|
+
})
|
|
172
|
+
.openapi("QueryIntentMarker");
|
|
94
173
|
/**
|
|
95
174
|
* `POST /v1/geocode` response — a hand-modeled mirror of `GeocodeResult`'s wire shape (`mailwoman/geocode-core.ts`),
|
|
96
175
|
* `.loose()` so a field the engine adds that this schema doesn't yet know about still rides through undocumented rather
|
|
@@ -104,6 +183,7 @@ const GeocodeCandidateSchema = z
|
|
|
104
183
|
export const GeocodeOutcomeSchema = z
|
|
105
184
|
.object({
|
|
106
185
|
input: z.string(),
|
|
186
|
+
components: GeocodeComponentsSchema,
|
|
107
187
|
lat: z.number().nullable(),
|
|
108
188
|
lon: z.number().nullable(),
|
|
109
189
|
resolution_tier: z.enum(["address_point", "interpolated", "street", "admin"]),
|
|
@@ -117,9 +197,18 @@ export const GeocodeOutcomeSchema = z
|
|
|
117
197
|
venue: z.string().nullable(),
|
|
118
198
|
// The parsed dependent-locality span (parse view; `hierarchy` is the resolved view).
|
|
119
199
|
dependent_locality: z.string().nullable(),
|
|
200
|
+
// The parsed unit / sub-venue span (parse view) — "Terminal 5", "Suite 300".
|
|
201
|
+
unit: z.string().nullable(),
|
|
120
202
|
countryCode: z.string().nullable(),
|
|
121
203
|
hierarchy: z.array(GeocodeHierarchyEntrySchema),
|
|
122
204
|
candidates: z.array(GeocodeCandidateSchema),
|
|
205
|
+
// #42: the country the postcode-country coherence pass scoped the walk to, or null. Non-null ONLY when it
|
|
206
|
+
// OVERRODE the request's country prior — so a caller who asked for US and got an FR answer can see which
|
|
207
|
+
// evidence bought the change instead of reading it as a bug.
|
|
208
|
+
postcode_country_scope: z.string().nullable(),
|
|
209
|
+
// ROAD_TO_V9 §4: query-intent advisories. Always present; empty means the vocabulary looked and had nothing to
|
|
210
|
+
// say. Advisory ONLY — no marker changed which answer won, and a client is free to ignore the array entirely.
|
|
211
|
+
intent_markers: z.array(QueryIntentMarkerSchema),
|
|
123
212
|
})
|
|
124
213
|
.loose()
|
|
125
214
|
.openapi("GeocodeOutcome");
|
|
@@ -128,7 +217,9 @@ export const GeocodeOutcomeSchema = z
|
|
|
128
217
|
*/
|
|
129
218
|
export const BatchRequestSchema = z
|
|
130
219
|
.object({
|
|
131
|
-
|
|
220
|
+
// Per-ROW, not just per-request: the row cap (`batchMax`, default 500) bounds how many addresses arrive,
|
|
221
|
+
// and this bounds how large each may be. Without both, one request is 500 unbounded bodies.
|
|
222
|
+
addresses: z.array(z.string().max(MAX_ADDRESS_LENGTH)),
|
|
132
223
|
/**
|
|
133
224
|
* Register override for every row. DEFAULT `"formatted"` — batch rows are the record register by nature.
|
|
134
225
|
*/
|
package/out/schema.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","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;;GAEG;AACH;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;AAEvF;;GAEG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC;KACjC,MAAM,CAAC;IACP,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../schema.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mailwoman/api",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.1.0",
|
|
4
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
5
|
"license": "AGPL-3.0-only OR LicenseRef-Commercial",
|
|
6
6
|
"repository": {
|
|
@@ -43,10 +43,10 @@
|
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@hono/zod-openapi": "^1.5.1",
|
|
46
|
-
"@mailwoman/api-kit": "
|
|
47
|
-
"@mailwoman/core": "
|
|
48
|
-
"@mailwoman/formatter": "
|
|
49
|
-
"hono": "^4.
|
|
46
|
+
"@mailwoman/api-kit": "9.1.0",
|
|
47
|
+
"@mailwoman/core": "9.1.0",
|
|
48
|
+
"@mailwoman/formatter": "9.1.0",
|
|
49
|
+
"hono": "^4.13.0",
|
|
50
50
|
"zod": "^4.4.3"
|
|
51
51
|
}
|
|
52
52
|
}
|
package/schema.ts
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* Zod wire schemas for the native `/v1` surface. Unlike the drop-ins (photon, nominatim,
|
|
7
7
|
* libpostal), nothing here is a vendor contract — this surface is ours to design, so request
|
|
8
8
|
* bodies are REQUIRED and validator-enforced (no legacy tolerance to preserve). A `defaultHook`
|
|
9
|
-
* on the app
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* on the app maps validation failures through the shared `APIErrorSchema` envelope
|
|
10
|
+
* (`apiError(c, 400, "invalid request body", <zod summary>)`) — the pattern boundary every
|
|
11
|
+
* surface holds to: where no legacy contract exists, the validator MAY speak, but only in
|
|
12
12
|
* our envelope.
|
|
13
13
|
*
|
|
14
14
|
* `APIErrorSchema` itself is owned by `@mailwoman/api-kit` (plumbing shared by every native
|
|
@@ -30,12 +30,27 @@ export { APIErrorSchema } from "@mailwoman/api-kit"
|
|
|
30
30
|
*/
|
|
31
31
|
export const InputModeSchema = z.enum(["fragmented", "formatted"]).openapi("InputMode")
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Longest accepted `address`, in characters.
|
|
35
|
+
*
|
|
36
|
+
* Sized against what the model can actually read, not against a guess at abuse. The classifier's window is 128
|
|
37
|
+
* SentencePiece pieces — roughly 330 characters of address text — and everything past it is truncated before inference,
|
|
38
|
+
* so input beyond this bound cannot influence a result. The margin over that window leaves room for scripts that
|
|
39
|
+
* tokenize denser than Latin, and for the department-and-division prefixes web forms concatenate.
|
|
40
|
+
*
|
|
41
|
+
* The bound exists because preprocessing is linear but not free: a 1 MB body costs ~1.7 s across normalize, query-shape
|
|
42
|
+
* and the phrase grouper, and Node runs them on the one thread every other request is waiting on. A cap here is cheaper
|
|
43
|
+
* than fairness plumbing, and rejecting is more honest than accepting a body whose tail the parser will silently
|
|
44
|
+
* discard.
|
|
45
|
+
*/
|
|
46
|
+
export const MAX_ADDRESS_LENGTH = 1024
|
|
47
|
+
|
|
33
48
|
/**
|
|
34
49
|
* `POST /v1/parse` request body.
|
|
35
50
|
*/
|
|
36
51
|
export const ParseRequestSchema = z
|
|
37
52
|
.object({
|
|
38
|
-
address: z.string(),
|
|
53
|
+
address: z.string().max(MAX_ADDRESS_LENGTH),
|
|
39
54
|
debug: z.boolean().optional(),
|
|
40
55
|
input_mode: InputModeSchema.optional(),
|
|
41
56
|
})
|
|
@@ -65,7 +80,7 @@ export const ParseOutcomeSchema = z
|
|
|
65
80
|
*/
|
|
66
81
|
export const GeocodeRequestSchema = z
|
|
67
82
|
.object({
|
|
68
|
-
address: z.string(),
|
|
83
|
+
address: z.string().max(MAX_ADDRESS_LENGTH),
|
|
69
84
|
input_mode: InputModeSchema.optional(),
|
|
70
85
|
})
|
|
71
86
|
.openapi("GeocodeRequest")
|
|
@@ -101,6 +116,76 @@ const GeocodeCandidateSchema = z
|
|
|
101
116
|
})
|
|
102
117
|
.openapi("GeocodeCandidate")
|
|
103
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Canonical parsed-component map carried by `GeocodeResult.components`. Spelled out at this engine-agnostic API
|
|
121
|
+
* boundary for the same reason the result schema is hand-modeled; the compile-time drift pin in
|
|
122
|
+
* `mailwoman/test/api-schema-drift.test.ts` catches any mismatch with the real `ComponentTag`-keyed result type.
|
|
123
|
+
*/
|
|
124
|
+
const GeocodeComponentsSchema = z.partialRecord(
|
|
125
|
+
z.enum([
|
|
126
|
+
"country",
|
|
127
|
+
"region",
|
|
128
|
+
"locality",
|
|
129
|
+
"dependent_locality",
|
|
130
|
+
"postcode",
|
|
131
|
+
"subregion",
|
|
132
|
+
"house_number",
|
|
133
|
+
"street",
|
|
134
|
+
"street_prefix",
|
|
135
|
+
"street_prefix_particle",
|
|
136
|
+
"street_suffix",
|
|
137
|
+
"intersection_a",
|
|
138
|
+
"intersection_b",
|
|
139
|
+
"unit",
|
|
140
|
+
"venue",
|
|
141
|
+
"attention",
|
|
142
|
+
"po_box",
|
|
143
|
+
"cedex",
|
|
144
|
+
"prefecture",
|
|
145
|
+
"municipality",
|
|
146
|
+
"district",
|
|
147
|
+
"block",
|
|
148
|
+
"sub_block",
|
|
149
|
+
"building_number",
|
|
150
|
+
"building_name",
|
|
151
|
+
]),
|
|
152
|
+
z.string()
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* One `GeocodeOutcome.intent_markers` entry — an advisory the ROAD_TO_V9 §4 intent vocabulary raised about the QUERY.
|
|
157
|
+
* Mirrors `QueryIntentMarker` (`core/pipeline/types.ts`).
|
|
158
|
+
*
|
|
159
|
+
* `evidence` is deliberately open (`z.record`): each `code` carries its own measurement — a dominance margin, a pair of
|
|
160
|
+
* interpretations, a taxonomy id — and flattening those into one closed shape would either lose the numbers or invent
|
|
161
|
+
* fields that do not apply. `code` is the discriminator a client branches on.
|
|
162
|
+
*/
|
|
163
|
+
const QueryIntentMarkerSchema = z
|
|
164
|
+
.object({
|
|
165
|
+
// Spelled out rather than `z.string()` so `mailwoman/test/api-schema-drift.test.ts`'s schema-too-wide direction
|
|
166
|
+
// keeps biting: a new `QueryKind` that never reaches this list is a documented contract that has quietly stopped
|
|
167
|
+
// describing the real one.
|
|
168
|
+
kind: z.enum([
|
|
169
|
+
"postcode_only",
|
|
170
|
+
"locality_only",
|
|
171
|
+
"structured_address",
|
|
172
|
+
"intersection",
|
|
173
|
+
"po_box",
|
|
174
|
+
"landmark",
|
|
175
|
+
"poi_query",
|
|
176
|
+
"vague",
|
|
177
|
+
"bare_toponym",
|
|
178
|
+
"route_pair",
|
|
179
|
+
"near_me",
|
|
180
|
+
"poi_category",
|
|
181
|
+
]),
|
|
182
|
+
code: z.enum(["declared_ambiguity", "declared_fork", "focus_point_required", "poi_category"]),
|
|
183
|
+
mechanism: z.string(),
|
|
184
|
+
message: z.string(),
|
|
185
|
+
evidence: z.record(z.string(), z.unknown()).optional(),
|
|
186
|
+
})
|
|
187
|
+
.openapi("QueryIntentMarker")
|
|
188
|
+
|
|
104
189
|
/**
|
|
105
190
|
* `POST /v1/geocode` response — a hand-modeled mirror of `GeocodeResult`'s wire shape (`mailwoman/geocode-core.ts`),
|
|
106
191
|
* `.loose()` so a field the engine adds that this schema doesn't yet know about still rides through undocumented rather
|
|
@@ -114,6 +199,7 @@ const GeocodeCandidateSchema = z
|
|
|
114
199
|
export const GeocodeOutcomeSchema = z
|
|
115
200
|
.object({
|
|
116
201
|
input: z.string(),
|
|
202
|
+
components: GeocodeComponentsSchema,
|
|
117
203
|
lat: z.number().nullable(),
|
|
118
204
|
lon: z.number().nullable(),
|
|
119
205
|
resolution_tier: z.enum(["address_point", "interpolated", "street", "admin"]),
|
|
@@ -127,9 +213,18 @@ export const GeocodeOutcomeSchema = z
|
|
|
127
213
|
venue: z.string().nullable(),
|
|
128
214
|
// The parsed dependent-locality span (parse view; `hierarchy` is the resolved view).
|
|
129
215
|
dependent_locality: z.string().nullable(),
|
|
216
|
+
// The parsed unit / sub-venue span (parse view) — "Terminal 5", "Suite 300".
|
|
217
|
+
unit: z.string().nullable(),
|
|
130
218
|
countryCode: z.string().nullable(),
|
|
131
219
|
hierarchy: z.array(GeocodeHierarchyEntrySchema),
|
|
132
220
|
candidates: z.array(GeocodeCandidateSchema),
|
|
221
|
+
// #42: the country the postcode-country coherence pass scoped the walk to, or null. Non-null ONLY when it
|
|
222
|
+
// OVERRODE the request's country prior — so a caller who asked for US and got an FR answer can see which
|
|
223
|
+
// evidence bought the change instead of reading it as a bug.
|
|
224
|
+
postcode_country_scope: z.string().nullable(),
|
|
225
|
+
// ROAD_TO_V9 §4: query-intent advisories. Always present; empty means the vocabulary looked and had nothing to
|
|
226
|
+
// say. Advisory ONLY — no marker changed which answer won, and a client is free to ignore the array entirely.
|
|
227
|
+
intent_markers: z.array(QueryIntentMarkerSchema),
|
|
133
228
|
})
|
|
134
229
|
.loose()
|
|
135
230
|
.openapi("GeocodeOutcome")
|
|
@@ -139,7 +234,9 @@ export const GeocodeOutcomeSchema = z
|
|
|
139
234
|
*/
|
|
140
235
|
export const BatchRequestSchema = z
|
|
141
236
|
.object({
|
|
142
|
-
|
|
237
|
+
// Per-ROW, not just per-request: the row cap (`batchMax`, default 500) bounds how many addresses arrive,
|
|
238
|
+
// and this bounds how large each may be. Without both, one request is 500 unbounded bodies.
|
|
239
|
+
addresses: z.array(z.string().max(MAX_ADDRESS_LENGTH)),
|
|
143
240
|
/**
|
|
144
241
|
* Register override for every row. DEFAULT `"formatted"` — batch rows are the record register by nature.
|
|
145
242
|
*/
|