@crvouga/mockingbird-service-google-maps 0.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/CHANGELOG.md +5 -0
- package/README.md +151 -0
- package/dist/chunk-5HEE5U7V.js +3393 -0
- package/dist/chunk-5HEE5U7V.js.map +7 -0
- package/dist/chunk-YHBPY6U2.js +358 -0
- package/dist/chunk-YHBPY6U2.js.map +7 -0
- package/dist/cli.js +19 -0
- package/dist/cli.js.map +7 -0
- package/dist/index.d.ts +948 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +7 -0
- package/dist/server.d.ts +1209 -0
- package/dist/server.js +12 -0
- package/dist/server.js.map +7 -0
- package/package.json +89 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# @crvouga/mockingbird-service-google-maps
|
|
2
|
+
|
|
3
|
+
Mock of the **Google Maps Platform** surface our member app uses for addresses: Places
|
|
4
|
+
Autocomplete, Place Details and Find Place From Text (the JSON web services), the Geocoding API,
|
|
5
|
+
and a **Maps JavaScript API shim** (`/maps/api/js?libraries=places`) exposing
|
|
6
|
+
`google.maps.places.*` and `google.maps.Geocoder` over the same data. Answers come from a corpus
|
|
7
|
+
matching our QA fixtures, so the address step that waits 5 s for Google predictions (and then
|
|
8
|
+
falls back to manual entry) resolves instantly and deterministically.
|
|
9
|
+
|
|
10
|
+
- Operation coverage: [SUPPORT.md](https://github.com/crvouga/mockingbird/blob/main/packages/service/google-maps/SUPPORT.md)
|
|
11
|
+
- 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.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install -D @crvouga/mockingbird-service-google-maps
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
ESM only. Node >= 22 or Bun >= 1.2. No native dependencies. Serve it with
|
|
21
|
+
`npx mockingbird-google-maps serve`, `createServer` from `./server` (Node), or `createRuntime`
|
|
22
|
+
with any Fetch server.
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
The app hardcodes `https://maps.googleapis.com` (seam **G-Y1**: a base-URL env for
|
|
27
|
+
`M/lib/ui/address-autocomplete/address-autocomplete-native-rest.tsx`,
|
|
28
|
+
`M/features/bloodwork/shared/lab-finder/use-geocoded-address.ts` and
|
|
29
|
+
`M/lib/ui/google-maps/load-google-maps-script.ts`). Until it lands, rewrite that host to the mock
|
|
30
|
+
in the stack's web dist (`stack-web-dist.ts`), as for PostHog. `PLACES_KEY` can be any non-empty
|
|
31
|
+
string unless you restrict keys.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx mockingbird-google-maps serve --port 8814 --api-key "$PLACES_KEY"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { createRuntime } from "@crvouga/mockingbird-service-google-maps"
|
|
39
|
+
|
|
40
|
+
const maps = createRuntime()
|
|
41
|
+
const get = async (path: string) =>
|
|
42
|
+
(await maps.fetch(new Request(`http://maps.test${path}`))).json()
|
|
43
|
+
|
|
44
|
+
const { predictions } = await get(
|
|
45
|
+
"/maps/api/place/autocomplete/json?input=1625%20N%20Central&types=address&components=country:us&key=k",
|
|
46
|
+
)
|
|
47
|
+
const details = await get(
|
|
48
|
+
`/maps/api/place/details/json?place_id=${predictions[0].place_id}&fields=address_component&key=k`,
|
|
49
|
+
)
|
|
50
|
+
// details.result.address_components → 1625 / N Central Ave / Phoenix / AZ / 85004
|
|
51
|
+
|
|
52
|
+
// Make the next two autocomplete calls fail, as QA's manual-entry fallback expects.
|
|
53
|
+
maps.applyPreset("autocomplete_over_query_limit", "default", { count: 2 })
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
On web, load `<mock>/maps/api/js?key=…&libraries=places` exactly as the app loads Google's
|
|
57
|
+
script; the shim calls back into the mock's REST endpoints on the same origin and namespace.
|
|
58
|
+
|
|
59
|
+
### Routes
|
|
60
|
+
|
|
61
|
+
Every web-service answer is **HTTP 200** with Google's `status` (`OK`, `ZERO_RESULTS`,
|
|
62
|
+
`INVALID_REQUEST`, `NOT_FOUND`, `REQUEST_DENIED`, `OVER_QUERY_LIMIT`, `UNKNOWN_ERROR`) and, for
|
|
63
|
+
errors, Google's `error_message`. Responses carry `access-control-allow-origin: *` so the shim
|
|
64
|
+
works from a browser.
|
|
65
|
+
|
|
66
|
+
| Route | Behaviour |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| `GET /maps/api/place/autocomplete/json` | `input` (required, else `INVALID_REQUEST`), `types`, `components=country:us`, `sessiontoken`. `predictions[]` with `description`, `place_id`, `reference`, `structured_formatting{main_text, main_text_matched_substrings, secondary_text}`, `terms`, `types`, `matched_substrings`; at most 5. Matches corpus rows by token prefix (street, city, state, ZIP); `"<number> <street> <corpus city>[ <ST>][ <ZIP>]"` is **synthesized** in that row's city/state/ZIP (QA's fuzzed search). Nothing → `ZERO_RESULTS`; a non-US `components` → `ZERO_RESULTS`. |
|
|
69
|
+
| `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
|
+
| `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
|
+
| `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. |
|
|
72
|
+
| `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
|
+
|
|
74
|
+
### Corpus
|
|
75
|
+
|
|
76
|
+
59 addresses: every row of QA's `ROUTING_ZIP_CORPUS` (`packages/qa/src/world/gen/addresses.ts`,
|
|
77
|
+
≥1 real ZIP per state + DC, ids kept), which includes the **Phoenix AZ demo member**
|
|
78
|
+
(`ADDRESS_AT_HOME_PHLEBOTOMY`: 1625 N Central Ave, Phoenix, AZ 85004) and the other
|
|
79
|
+
`packages/app/src/test-addresses` members, plus `ADDRESS_AT_HOME_PHLEBOTOMY_2` (501 N 5th St).
|
|
80
|
+
Each has its real county and the city's coordinates. Place ids are stable (`ChIJ…` for rows;
|
|
81
|
+
synthesized addresses get an `Ei…` id carrying the address, as Google's own address ids do, so
|
|
82
|
+
Details needs no stored state). `GET /health` reports the corpus.
|
|
83
|
+
|
|
84
|
+
### Admin (beyond the standard contract)
|
|
85
|
+
|
|
86
|
+
| Route | Effect |
|
|
87
|
+
| --- | --- |
|
|
88
|
+
| `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). |
|
|
90
|
+
| `DELETE /__admin/corpus` | Drop the custom addresses. |
|
|
91
|
+
| `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
|
+
|
|
93
|
+
Fault presets (`POST /__admin/faults {"preset": "<name>", "count"?: n}`; `GET /__admin/faults/presets`):
|
|
94
|
+
`over_query_limit`, `request_denied`, `unknown_error`, `zero_results` (every web service, HTTP 200
|
|
95
|
+
with that status), `geocode_zero_results` (exercises our Find Place fallback),
|
|
96
|
+
`autocomplete_over_query_limit` (two of these flip our sheet to manual entry), `server_error`
|
|
97
|
+
(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).
|
|
99
|
+
|
|
100
|
+
### Namespaces
|
|
101
|
+
|
|
102
|
+
`x-mockingbird-namespace`, a `/ns/<name>` prefix on the base URL (the JS shim served under a
|
|
103
|
+
prefix calls back through it), or by API key:
|
|
104
|
+
`PUT /__admin/credentials {"credentials": {"<PLACES_KEY>": "<namespace>"}}` (the `key` query
|
|
105
|
+
parameter is the credential). The request journal records operation, status, the resolved
|
|
106
|
+
`placeId` and the `sessionToken`; never the typed address.
|
|
107
|
+
|
|
108
|
+
### Tests
|
|
109
|
+
|
|
110
|
+
- `google-maps.property.test.ts`: self-parity over random walks of all four web services (each
|
|
111
|
+
exercised, every response validated against the spec) and a deliberately divergent instance
|
|
112
|
+
caught.
|
|
113
|
+
- `google-maps.acceptance.test.ts`: drives `test/consumer.ts`, a port of our
|
|
114
|
+
`address-autocomplete-native-rest.tsx` (URL builders, parsers, the two-consecutive-failures
|
|
115
|
+
manual-entry switch), `parse-place-details.ts`, `address-autocomplete-web.tsx` and
|
|
116
|
+
`use-geocoded-address.ts` (native REST and web through the shim evaluated in a fake window).
|
|
117
|
+
Also served over HTTP.
|
|
118
|
+
- There is no SDK drop-in test: native uses plain `fetch`, and the web SDK is Google's hosted
|
|
119
|
+
script, which the shim replaces.
|
|
120
|
+
- `bun scripts/parity.ts`: live parity against `maps.googleapis.com` with
|
|
121
|
+
`MOCKINGBIRD_GOOGLE_MAPS_API_KEY` (env or Vault `secret/personal/prd`); exits 2 without it.
|
|
122
|
+
|
|
123
|
+
### Deliberately not modelled
|
|
124
|
+
|
|
125
|
+
- Addresses outside the corpus: only corpus rows and streets synthesized in corpus cities
|
|
126
|
+
resolve. Add more with `PUT /__admin/corpus`.
|
|
127
|
+
- Google's route expansion (`N Central Ave` → long_name `North Central Avenue`): `long_name`
|
|
128
|
+
echoes the corpus spelling so QA's line1 round-trips. Live parity will show this difference.
|
|
129
|
+
- Maps rendering (`google.maps.Map`, markers, tiles, Static Maps), the new Places API
|
|
130
|
+
(`places.googleapis.com`, `Place` class, `AutocompleteSuggestion`), reverse geocoding
|
|
131
|
+
(`latlng=`), `locationbias`/`radius`, and billing/quotas beyond the presets.
|
|
132
|
+
- Session-token billing semantics: tokens are accepted and journaled, nothing more.
|
|
133
|
+
|
|
134
|
+
## API
|
|
135
|
+
|
|
136
|
+
| Export | Kind | Description |
|
|
137
|
+
| --- | --- | --- |
|
|
138
|
+
| `GoogleMapsAPI` | class | The in-process mock: `fetch(request)`, `reset()`, `corpus()`, `state`. Options: `sqlite`, `now`, `namespace`, `publicNamespace`, `corpus`, `settings`. |
|
|
139
|
+
| `createRuntime` | function | The mock with the full service contract (health, admin, namespaces, credentials, presets, journal). Options: `corpus`, `settings`, `clock`, `seed`, `adminKey`, `onLog`. |
|
|
140
|
+
| `GOOGLE_MAPS_PRESETS` | object | Every named fault preset. |
|
|
141
|
+
| `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). |
|
|
143
|
+
| `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
|
+
| `corpusPlaceId` | function | The stable place id of a corpus row. |
|
|
145
|
+
| `mapsJavaScript` | function | Render the Maps JavaScript shim for `{base, key, authFailed, callback}`. |
|
|
146
|
+
| `normalize` | function | The address normalization used for matching. |
|
|
147
|
+
| `MISSING_KEY_MESSAGE`, `INVALID_KEY_MESSAGE` | strings | Google's `error_message` for a missing / refused key. |
|
|
148
|
+
| `document`, `operationIds`, `supportedOperationIds` | values | The OpenAPI contract and its operation ids. |
|
|
149
|
+
| `createServer`, `serveTarget`, `DEFAULT_PORT` (`./server`) | Node | Serve over `node:http`; the `serve` CLI target (`--api-key`, `--public-url`); port 8814. |
|
|
150
|
+
|
|
151
|
+
Part of [mockingbird](https://github.com/crvouga/mockingbird).
|