@crvouga/mockingbird-service-google-maps 1.0.0 → 1.1.1
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/CHANGELOG.md +14 -4
- package/README.md +97 -10
- package/dist/{chunk-5HEE5U7V.js → chunk-G5DEDMPE.js} +729 -19
- package/dist/chunk-G5DEDMPE.js.map +7 -0
- package/dist/{chunk-YHBPY6U2.js → chunk-XR7ESTGM.js} +6 -3
- package/dist/chunk-XR7ESTGM.js.map +7 -0
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +179 -96
- package/dist/index.js +5 -1
- package/dist/server.d.ts +260 -165
- package/dist/server.js +2 -2
- package/package.json +5 -3
- package/dist/chunk-5HEE5U7V.js.map +0 -7
- package/dist/chunk-YHBPY6U2.js.map +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
# Changelog — @crvouga/mockingbird-service-google-maps
|
|
2
2
|
|
|
3
|
+
## 1.1.1 (2026-09-26)
|
|
4
|
+
|
|
5
|
+
### Fixes and improvements
|
|
6
|
+
|
|
7
|
+
- take the rejected-request byte count from content-length, never by reading the body ([a0e5fb2](https://github.com/crvouga/mockingbird/commit/a0e5fb2fc80ab725c3552b612f3f091fd6c857fb))
|
|
8
|
+
- reject addressLines over the vendored PostalAddress limits instead of echoing them ([843917c](https://github.com/crvouga/mockingbird/commit/843917c9fe47d83ecd5ab45166ee67c22de9d35c))
|
|
9
|
+
- reject a present body by media type, and log what a rejected request sent ([12e351a](https://github.com/crvouga/mockingbird/commit/12e351aba53cfb7b41e31a7809419455fc2902eb))
|
|
10
|
+
|
|
11
|
+
## 1.1.0 (2026-09-25)
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
- add the Address Validation API with USPS CASS/DPV verdicts ([7cec81e](https://github.com/crvouga/mockingbird/commit/7cec81e1d8271e4d55d6310f36324e47e135d510))
|
|
16
|
+
|
|
3
17
|
## 1.0.0 (2026-09-24)
|
|
4
18
|
|
|
5
19
|
### ⚠️ Breaking changes
|
|
6
20
|
|
|
7
21
|
- vendor branding for every service; purge internal mocks and consumer names ([f689534](https://github.com/crvouga/mockingbird/commit/f6895343543550dafcb7d76b426cae7635e03226))
|
|
8
22
|
|
|
9
|
-
### Dependencies
|
|
10
|
-
|
|
11
|
-
- `@crvouga/mockingbird-service-sqlite`
|
|
12
|
-
|
|
13
23
|
## 0.2.0 (2026-09-23)
|
|
14
24
|
|
|
15
25
|
### Features
|
package/README.md
CHANGED
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
Mock of the **Google Maps Platform** surface our member app uses for addresses: Places
|
|
4
4
|
Autocomplete, Place Details and Find Place From Text (the JSON web services), the Geocoding API,
|
|
5
|
-
|
|
6
|
-
`google.maps.places.*` and `google.maps.Geocoder` over the same data
|
|
5
|
+
a **Maps JavaScript API shim** (`/maps/api/js?libraries=places`) exposing
|
|
6
|
+
`google.maps.places.*` and `google.maps.Geocoder` over the same data, and the **Address
|
|
7
|
+
Validation API** (`POST /v1:validateAddress`, with USPS CASS/DPV data) for server-side ship-to
|
|
8
|
+
checks. Answers come from a corpus
|
|
7
9
|
matching our QA fixtures, so the address step that waits 5 s for Google predictions (and then
|
|
8
10
|
falls back to manual entry) resolves instantly and deterministically.
|
|
9
11
|
|
|
10
12
|
- Operation coverage: [SUPPORT.md](https://github.com/crvouga/mockingbird/blob/main/packages/service/google-maps/SUPPORT.md)
|
|
11
13
|
- Google publishes no OpenAPI document for these endpoints: `openapi.yaml` is hand-authored
|
|
12
|
-
from Google's documented shapes and the fields our consumer reads.
|
|
14
|
+
from Google's documented shapes and the fields our consumer reads. Address Validation follows
|
|
15
|
+
the [REST reference](https://developers.google.com/maps/documentation/address-validation/reference/rest/v1/TopLevel/validateAddress)
|
|
16
|
+
and [`ValidationResult`](https://developers.google.com/maps/documentation/address-validation/reference/rest/v1/ValidationResult).
|
|
13
17
|
|
|
14
18
|
## Install
|
|
15
19
|
|
|
@@ -53,6 +57,39 @@ const details = await get(
|
|
|
53
57
|
maps.applyPreset("autocomplete_over_query_limit", "default", { count: 2 })
|
|
54
58
|
```
|
|
55
59
|
|
|
60
|
+
### Address Validation
|
|
61
|
+
|
|
62
|
+
Google serves it from a different host, `https://addressvalidation.googleapis.com`; the paths do
|
|
63
|
+
not collide, so one mock serves both. Point the server-side client's Address Validation base URL
|
|
64
|
+
at the mock (or at `<mock>/ns/<namespace>`), with the key as `?key=` (or `X-Goog-Api-Key`).
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import { createRuntime } from "@crvouga/mockingbird-service-google-maps"
|
|
68
|
+
|
|
69
|
+
const maps = createRuntime()
|
|
70
|
+
const response = await maps.fetch(
|
|
71
|
+
new Request("http://maps.test/v1:validateAddress?key=k", {
|
|
72
|
+
method: "POST",
|
|
73
|
+
headers: { "content-type": "application/json" },
|
|
74
|
+
body: JSON.stringify({
|
|
75
|
+
address: {
|
|
76
|
+
regionCode: "US",
|
|
77
|
+
addressLines: ["1625 N Central Ave"],
|
|
78
|
+
locality: "Phoenix",
|
|
79
|
+
administrativeArea: "AZ",
|
|
80
|
+
postalCode: "85004",
|
|
81
|
+
},
|
|
82
|
+
enableUspsCass: true,
|
|
83
|
+
}),
|
|
84
|
+
}),
|
|
85
|
+
)
|
|
86
|
+
const { result } = await response.json()
|
|
87
|
+
// result.verdict → PREMISE / addressComplete / ACCEPT; result.uspsData.dpvConfirmation → "Y"
|
|
88
|
+
|
|
89
|
+
// Force the USPS "not deliverable" answer on the next call.
|
|
90
|
+
maps.applyPreset("address_validation_dpv_n", "default", { count: 1 })
|
|
91
|
+
```
|
|
92
|
+
|
|
56
93
|
On web, load `<mock>/maps/api/js?key=…&libraries=places` exactly as the app loads Google's
|
|
57
94
|
script; the shim calls back into the mock's REST endpoints on the same origin and namespace.
|
|
58
95
|
|
|
@@ -69,8 +106,36 @@ works from a browser.
|
|
|
69
106
|
| `GET /maps/api/place/details/json` | `place_id` (required), `fields` (honoured: `address_component` returns only `address_components`; unknown field → `INVALID_REQUEST`), `sessiontoken`. `result` with `address_components` (`street_number`, `route`, `locality`, `administrative_area_level_2`, `administrative_area_level_1` long/short, `country`, `postal_code`), `formatted_address`, `geometry{location, viewport}`, `place_id`, `types`, `name`, `url`, `vicinity`. Unknown id → `NOT_FOUND`. |
|
|
70
107
|
| `GET /maps/api/geocode/json` | `address`, `place_id` or `components=postal_code:…` (none → `INVALID_REQUEST`). `results[0]` with `address_components`, `formatted_address`, `geometry{location, location_type, viewport}`, `place_id`, `types`. A full address (`"line1, city, ST zip"`) resolves to its row or a synthesized address; a bare ZIP or `"City, ST"` to the ZIP centroid; a street without a locatable city is `ZERO_RESULTS`. |
|
|
71
108
|
| `GET /maps/api/place/findplacefromtext/json` | `input` + `inputtype=textquery` (else `INVALID_REQUEST`), `fields` (default: `place_id` only, as Google). Geocoding first, then the looser autocomplete match, so it finds what our geocode fallback needs. |
|
|
109
|
+
| `POST /v1:validateAddress` | Address Validation (see below). A Google Cloud API: errors are HTTP 4xx/5xx with `{"error": {code, message, status}}`, not a 200 with `status`. |
|
|
72
110
|
| `GET /maps/api/js` | The shim (`text/javascript`): `google.maps.places.AutocompleteService#getPlacePredictions`, `PlacesService#getDetails` / `#findPlaceFromQuery`, `AutocompleteSessionToken`, `PlacesServiceStatus`, `google.maps.Geocoder#geocode`, `GeocoderStatus`, `LatLng` (`lat()`/`lng()`), `LatLngBounds`, `importLibrary`. Callbacks and promises (rejecting with `MapsRequestError` on errors when no callback is given). Calls `window[callback]` for `&callback=`, and `window.gm_authFailure()` when the key is refused. |
|
|
73
111
|
|
|
112
|
+
### Address Validation verdicts
|
|
113
|
+
|
|
114
|
+
Answers are proto3 JSON, as Google sends them: `false` booleans and empty lists are omitted
|
|
115
|
+
(no `unconfirmedComponentTypes: []`). `result` carries `verdict` (`inputGranularity`,
|
|
116
|
+
`validationGranularity`, `geocodeGranularity`, `addressComplete`, `has*Components`,
|
|
117
|
+
`possibleNextAction`), `address` (`formattedAddress`, `postalAddress`, `addressComponents` with
|
|
118
|
+
`confirmationLevel` / `inferred` / `replaced`, `missingComponentTypes`,
|
|
119
|
+
`unconfirmedComponentTypes`), `geocode` (`location`, `placeId`, `placeTypes`) and, for US
|
|
120
|
+
addresses, `uspsData` (`standardizedAddress`, `dpvConfirmation`, `dpvFootnote`,
|
|
121
|
+
`postOfficeCity`/`State`, `errorMessage`, `cassProcessed` when `enableUspsCass`). Every answer
|
|
122
|
+
has a `responseId`. Send a componentized address (line 1 + optional unit line 2, `locality`,
|
|
123
|
+
`administrativeArea`, `postalCode`) or everything in `addressLines`.
|
|
124
|
+
|
|
125
|
+
| Input | Verdict |
|
|
126
|
+
| --- | --- |
|
|
127
|
+
| A corpus row (`1625 N Central Ave`, Phoenix AZ 85004); spelled-out words (`North`, `Avenue`) match | `PREMISE`, complete, `ACCEPT`, DPV `Y`; `postalAddress` echoes the corpus spelling with a deterministic ZIP+4 (`85004-NNNN`) |
|
|
128
|
+
| `<number> <street>` in a corpus city | Synthesized, as for Autocomplete: `PREMISE`, `ACCEPT`, DPV `Y`, the street USPS-abbreviated (`Maple Street` → `Maple St`) |
|
|
129
|
+
| A unit on a single-delivery-point row (`… Building A`, `Apt 4`) | `PREMISE`, complete, `CONFIRM`, `subpremise` unconfirmed, DPV `Y` |
|
|
130
|
+
| No house number (`N Central Ave`) in a corpus city | `ROUTE`, `FIX`, `missingComponentTypes: ["street_number"]`, no DPV code |
|
|
131
|
+
| A city/ZIP the corpus does not hold | `OTHER`, `FIX`, the unplaceable components unconfirmed, DPV `N` |
|
|
132
|
+
| A ZIP that is wrong for the city (or a city that is wrong for the ZIP) | Replaced: `hasReplacedComponents`, `CONFIRM`, the corrected value in `postalAddress` |
|
|
133
|
+
| A row with `validation.multiUnit` | No unit: `CONFIRM_ADD_SUBPREMISES`, `missingComponentTypes: ["subpremise"]`, DPV `D`. A unit in `validation.units` (or any, when `units` is absent): `SUB_PREMISE`, `ACCEPT`, `Y`. Another unit: `PREMISE`, `CONFIRM`, `subpremise` unconfirmed, DPV `S` |
|
|
134
|
+
| A row with `validation.uspsErrorMessage` | `uspsData: {errorMessage}` and no DPV code |
|
|
135
|
+
| No `address`, empty `addressLines`, an unknown field, non-JSON, over 280 characters, or `enableUspsCass` outside `US`/`PR` | 400 `INVALID_ARGUMENT` |
|
|
136
|
+
| No key | 403 `PERMISSION_DENIED` "The request is missing a valid API key." |
|
|
137
|
+
| A key outside `settings.keys` | 400 `INVALID_ARGUMENT` "API key not valid. Please pass a valid API key." (`reason: API_KEY_INVALID`), as Google answers an unknown key; `address_validation_denied` gives the 403 of a key without the API enabled |
|
|
138
|
+
|
|
74
139
|
### Corpus
|
|
75
140
|
|
|
76
141
|
59 addresses: every row of QA's `ROUTING_ZIP_CORPUS` (`packages/qa/src/world/gen/addresses.ts`,
|
|
@@ -86,7 +151,7 @@ Details needs no stored state). `GET /health` reports the corpus.
|
|
|
86
151
|
| Route | Effect |
|
|
87
152
|
| --- | --- |
|
|
88
153
|
| `GET /__admin/corpus` | The namespace's addresses (custom first) and the custom count. |
|
|
89
|
-
| `PUT /__admin/corpus` | `{addresses: [{line1, city, state, zip, id?, county?, lat?, lng?}]}` replaces the namespace's custom addresses (on top of the built-in corpus). |
|
|
154
|
+
| `PUT /__admin/corpus` | `{addresses: [{line1, city, state, zip, id?, county?, lat?, lng?, validation?}]}` replaces the namespace's custom addresses (on top of the built-in corpus). `validation` pins Address Validation for that row: `{granularity?, addressComplete?, possibleNextAction?, dpvConfirmation?: "Y"\|"N"\|"S"\|"D", unconfirmedComponentTypes?, uspsErrorMessage?, multiUnit?, units?}`. |
|
|
90
155
|
| `DELETE /__admin/corpus` | Drop the custom addresses. |
|
|
91
156
|
| `GET/PUT /__admin/settings` | `{keys?: string[], publicUrl?: string \| null}`. `keys` restricts accepted API keys (others get `REQUEST_DENIED` "The provided API key is invalid."; a missing key always does). `publicUrl` is the origin the JS shim calls back to when it differs from the request's (a rewriting proxy). |
|
|
92
157
|
|
|
@@ -95,15 +160,22 @@ Fault presets (`POST /__admin/faults {"preset": "<name>", "count"?: n}`; `GET /_
|
|
|
95
160
|
with that status), `geocode_zero_results` (exercises our Find Place fallback),
|
|
96
161
|
`autocomplete_over_query_limit` (two of these flip our sheet to manual entry), `server_error`
|
|
97
162
|
(HTTP 500), `slow` (6 s, past QA's 5 s wait), `script_unavailable` (the JS loader answers 503, so
|
|
98
|
-
the script's `onerror` fires).
|
|
163
|
+
the script's `onerror` fires). For Address Validation only: `address_validation_denied` (403
|
|
164
|
+
`PERMISSION_DENIED`, a key without the API enabled), `address_validation_unavailable` (503
|
|
165
|
+
`UNAVAILABLE`), `address_validation_slow` (3 s, past a 2.5 s checkout timeout),
|
|
166
|
+
`address_validation_no_verdict` (200 `{"result": {}}`: not something Google sends, for a
|
|
167
|
+
client's defensive branch) and `address_validation_dpv_n` (DPV `N`). `GET /__admin/metrics`
|
|
168
|
+
counts `ValidateAddress` on its own.
|
|
99
169
|
|
|
100
170
|
### Namespaces
|
|
101
171
|
|
|
102
172
|
`x-mockingbird-namespace`, a `/ns/<name>` prefix on the base URL (the JS shim served under a
|
|
103
173
|
prefix calls back through it), or by API key:
|
|
104
174
|
`PUT /__admin/credentials {"credentials": {"<PLACES_KEY>": "<namespace>"}}` (the `key` query
|
|
105
|
-
parameter is the credential). The request journal
|
|
106
|
-
`placeId` and the `sessionToken
|
|
175
|
+
parameter, or `X-Goog-Api-Key` for Address Validation, is the credential). The request journal
|
|
176
|
+
records operation, status, the resolved `placeId` and the `sessionToken`, and for
|
|
177
|
+
`ValidateAddress` the resolved corpus row id (`addressRowId`) and the verdict class
|
|
178
|
+
(`ACCEPT/Y`, `FIX/N`, …); never the typed address.
|
|
107
179
|
|
|
108
180
|
### Tests
|
|
109
181
|
|
|
@@ -115,10 +187,17 @@ parameter is the credential). The request journal records operation, status, the
|
|
|
115
187
|
manual-entry switch), `parse-place-details.ts`, `address-autocomplete-web.tsx` and
|
|
116
188
|
`use-geocoded-address.ts` (native REST and web through the shim evaluated in a fake window).
|
|
117
189
|
Also served over HTTP.
|
|
190
|
+
- `google-maps.address-validation.acceptance.test.ts`: every Address Validation verdict class,
|
|
191
|
+
the 400/403 envelopes, namespaces, the journal and each preset, through `test/consumer.ts`'s
|
|
192
|
+
port of a checkout's fail-open ship-to evaluation (non-2xx, no verdict → unavailable; USPS
|
|
193
|
+
error, granularity, incomplete, `FIX`/`CONFIRM_ADD_SUBPREMISES`, critical unconfirmed
|
|
194
|
+
components, DPV other than `Y`/`S` → reject). Self-parity walks include `ValidateAddress`.
|
|
118
195
|
- There is no SDK drop-in test: native uses plain `fetch`, and the web SDK is Google's hosted
|
|
119
196
|
script, which the shim replaces.
|
|
120
|
-
- `bun scripts/parity.ts`: live parity against `maps.googleapis.com`
|
|
121
|
-
`
|
|
197
|
+
- `bun scripts/parity.ts`: live parity against `maps.googleapis.com` (and
|
|
198
|
+
`addressvalidation.googleapis.com` for `ValidateAddress`) with
|
|
199
|
+
`GOOGLE_MAPS_API_KEY` (env / `.env.local`; Address Validation must be enabled on
|
|
200
|
+
the key); exits 2 without it.
|
|
122
201
|
|
|
123
202
|
### Deliberately not modelled
|
|
124
203
|
|
|
@@ -130,6 +209,13 @@ parameter is the credential). The request journal records operation, status, the
|
|
|
130
209
|
(`places.googleapis.com`, `Place` class, `AutocompleteSuggestion`), reverse geocoding
|
|
131
210
|
(`latlng=`), `locationbias`/`radius`, and billing/quotas beyond the presets.
|
|
132
211
|
- Session-token billing semantics: tokens are accepted and journaled, nothing more.
|
|
212
|
+
- Address Validation beyond `validateAddress`: `provideValidationFeedback`, `previousResponseId`
|
|
213
|
+
chaining, `languageOptions` and `sessionToken` (accepted, ignored), `metadata`
|
|
214
|
+
(business/residential/PO box), `englishLatinAddress`, and non-US regions (without
|
|
215
|
+
`enableUspsCass` they get a `FIX` answer and no `uspsData`; Google validates many countries).
|
|
216
|
+
ZIP+4 digits, `dpvFootnote` codes and the rest of the USPS record (carrier route, delivery
|
|
217
|
+
point, county FIPS…) are synthesized or omitted: compare verdict classes and DPV codes, not
|
|
218
|
+
those.
|
|
133
219
|
|
|
134
220
|
## API
|
|
135
221
|
|
|
@@ -139,12 +225,13 @@ parameter is the credential). The request journal records operation, status, the
|
|
|
139
225
|
| `createRuntime` | function | The mock with the full service contract (health, admin, namespaces, credentials, presets, journal). Options: `corpus`, `settings`, `clock`, `seed`, `adminKey`, `onLog`. |
|
|
140
226
|
| `GOOGLE_MAPS_PRESETS` | object | Every named fault preset. |
|
|
141
227
|
| `GOOGLE_MAPS_NAMESPACE` | string | The service name, `"google-maps"`. |
|
|
142
|
-
| `keyCredential` | function | The `key` query parameter of a request (how API keys map to namespaces). |
|
|
228
|
+
| `keyCredential` | function | The `key` query parameter (or `X-Goog-Api-Key` header) of a request (how API keys map to namespaces). |
|
|
143
229
|
| `DEFAULT_CORPUS`, `PHOENIX_DEMO_ADDRESS`, `STATE_NAMES` | values | The address corpus, the Phoenix demo member row, and state names for `administrative_area_level_1`. |
|
|
144
230
|
| `corpusPlaceId` | function | The stable place id of a corpus row. |
|
|
145
231
|
| `mapsJavaScript` | function | Render the Maps JavaScript shim for `{base, key, authFailed, callback}`. |
|
|
146
232
|
| `normalize` | function | The address normalization used for matching. |
|
|
147
233
|
| `MISSING_KEY_MESSAGE`, `INVALID_KEY_MESSAGE` | strings | Google's `error_message` for a missing / refused key. |
|
|
234
|
+
| `MISSING_API_KEY_MESSAGE`, `INVALID_API_KEY_MESSAGE` | strings | Address Validation's `error.message` for a missing (403) / invalid (400) key. |
|
|
148
235
|
| `document`, `operationIds`, `supportedOperationIds` | values | The OpenAPI contract and its operation ids. |
|
|
149
236
|
| `createServer`, `serveTarget`, `DEFAULT_PORT` (`./server`) | Node | Serve over `node:http`; the `serve` CLI target (`--api-key`, `--public-url`); port 8814. |
|
|
150
237
|
|