@tileflow/search 0.1.0-alpha.2 → 0.1.0-alpha.4
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 +202 -86
- package/dist/{chunk-YMLUPVQH.js → chunk-QDEMNVJD.js} +100 -16
- package/dist/{chunk-EDYCZVXI.js → chunk-W66J2DAS.js} +147 -7
- package/dist/client.d.ts +8 -3
- package/dist/client.js +10 -4
- package/dist/contract.d.ts +759 -8
- package/dist/contract.js +47 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +55 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# @tileflow/search
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
batch interface.
|
|
3
|
+
Headless forward/reverse geocoding, autocomplete, category discovery, Nearby POIs and explicit
|
|
4
|
+
selected-place resolution. It uses `fetch` and has no renderer or global interaction state.
|
|
6
5
|
|
|
7
6
|
> Related packages and guides: [documentation index](https://raw.githubusercontent.com/tileflow/tileflow-sdk/main/llms.txt).
|
|
8
7
|
|
|
@@ -12,134 +11,251 @@ batch interface.
|
|
|
12
11
|
npm install @tileflow/search@alpha
|
|
13
12
|
```
|
|
14
13
|
|
|
15
|
-
Use
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
should call your own authorized endpoint.
|
|
14
|
+
Use Node.js 22 or another runtime with `fetch`. Live requests require enabled Tileflow Search and a
|
|
15
|
+
Team credential with `geocoding:read`. Keep privileged credentials on your application server.
|
|
16
|
+
Browser and mobile interfaces call their own authorized server endpoint.
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
Managed operations require paid Starter or a separate, finite Search evaluation grant. A Starter
|
|
19
|
+
trial or complimentary assignment alone does not authorize Search. Free Teams can read categories.
|
|
20
|
+
Installing this package does not enable the service or grant access.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
## Find nearby places
|
|
23
|
+
|
|
24
|
+
Run this example on your server with `TILEFLOW_API_KEY` set securely. It lists the current categories,
|
|
25
|
+
then makes one explicit Nearby request using a discovered ID:
|
|
23
26
|
|
|
24
27
|
<!-- docs:check -->
|
|
25
28
|
|
|
26
29
|
```ts
|
|
27
|
-
import {
|
|
30
|
+
import {listCategories, searchNearby} from '@tileflow/search';
|
|
28
31
|
|
|
29
32
|
const apiKey = process.env.TILEFLOW_API_KEY;
|
|
30
33
|
if (!apiKey) throw new Error('Set TILEFLOW_API_KEY on the server.');
|
|
34
|
+
const options = {apiKey};
|
|
35
|
+
const catalog = await listCategories(options);
|
|
36
|
+
const category = catalog.categories.find(({name}) => name.toLowerCase().includes('coffee'));
|
|
37
|
+
if (!category) throw new Error('Choose a category from the current catalog.');
|
|
31
38
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
const page = await searchNearby(
|
|
40
|
+
{
|
|
41
|
+
position: [-9.1393, 38.7223],
|
|
42
|
+
radiusMeters: 1000,
|
|
43
|
+
includeCategories: [category.id],
|
|
44
|
+
countries: ['PT'],
|
|
45
|
+
language: 'en',
|
|
46
|
+
limit: 20,
|
|
47
|
+
},
|
|
48
|
+
options,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
console.log(page.results, page.attribution, page.usage.units);
|
|
43
52
|
```
|
|
44
53
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
Coordinates are WGS84 `[longitude, latitude]`. Results preserve native AWS proximity ordering.
|
|
55
|
+
A POI's optional `name` is its establishment title; `label` and `address` describe its location.
|
|
56
|
+
Categories and business-chain labels appear only when supplied. Missing metadata does not establish
|
|
57
|
+
opening status, freshness or completeness. Co-located businesses are not merged.
|
|
58
|
+
|
|
59
|
+
Display every attribution entry with results, preserving supplied URLs. Results and opaque references
|
|
60
|
+
are temporary. Do not build a permanent place catalog, cache results, or persist provider references.
|
|
48
61
|
|
|
49
|
-
##
|
|
62
|
+
## Request another page or resolve a selection
|
|
50
63
|
|
|
51
|
-
|
|
64
|
+
Save the original request in the current interaction. Another page repeats that request with the
|
|
65
|
+
returned `nextCursor`; no page is fetched automatically. A successful empty page can have a cursor.
|
|
66
|
+
Each explicit page costs 25 API units. There is no total-count or snapshot-consistency promise.
|
|
52
67
|
|
|
53
68
|
<!-- docs:check -->
|
|
54
69
|
|
|
55
70
|
```ts
|
|
56
|
-
import {
|
|
71
|
+
import {type NearbyRequest, resolvePlace, searchNearby} from '@tileflow/search';
|
|
57
72
|
|
|
58
73
|
const apiKey = process.env.TILEFLOW_API_KEY;
|
|
59
74
|
if (!apiKey) throw new Error('Set TILEFLOW_API_KEY on the server.');
|
|
75
|
+
const options = {apiKey};
|
|
76
|
+
const original: NearbyRequest = {position: [-9.1393, 38.7223], radiusMeters: 1000};
|
|
60
77
|
|
|
61
|
-
|
|
62
|
-
{
|
|
63
|
-
|
|
64
|
-
kinds: ['address', 'place'],
|
|
65
|
-
language: 'en',
|
|
66
|
-
retention: 'temporary',
|
|
67
|
-
},
|
|
68
|
-
{apiKey},
|
|
69
|
-
);
|
|
78
|
+
export function requestPage(cursor?: string) {
|
|
79
|
+
return searchNearby({...original, ...(cursor ? {cursor} : {})}, options);
|
|
80
|
+
}
|
|
70
81
|
|
|
71
|
-
|
|
82
|
+
export function resolveSelectedPlace(token: string) {
|
|
83
|
+
return resolvePlace({token}, options);
|
|
84
|
+
}
|
|
72
85
|
```
|
|
73
86
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
87
|
+
Invoke these handlers only after explicit user actions. Use a returned POI token for resolution;
|
|
88
|
+
map feature IDs, click coordinates and raw AWS Place IDs are not selection tokens. A result without
|
|
89
|
+
a token can be displayed but cannot be resolved. Selection and pagination references cannot be
|
|
90
|
+
interchanged or moved between Teams. Changed query, language, filters or limit invalidates a cursor.
|
|
78
91
|
|
|
79
|
-
|
|
92
|
+
`resolvePlace` and `resolveSuggestion` perform the same single resolution operation. Neither fetches
|
|
93
|
+
other results or retries automatically. An expired reference requires an explicit fresh search.
|
|
80
94
|
|
|
81
|
-
|
|
95
|
+
## Geocode text, reverse a position, or offer suggestions
|
|
82
96
|
|
|
83
97
|
<!-- docs:check -->
|
|
84
98
|
|
|
85
99
|
```ts
|
|
86
|
-
import {autocomplete, resolveSuggestion} from '@tileflow/search';
|
|
100
|
+
import {autocomplete, geocode, geocodeReverse, resolveSuggestion} from '@tileflow/search';
|
|
87
101
|
|
|
88
102
|
const apiKey = process.env.TILEFLOW_API_KEY;
|
|
89
103
|
if (!apiKey) throw new Error('Set TILEFLOW_API_KEY on the server.');
|
|
90
104
|
const options = {apiKey};
|
|
91
105
|
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
export
|
|
96
|
-
|
|
97
|
-
return {result: resolved.result, source: resolved.source, attribution: resolved.attribution};
|
|
98
|
-
}
|
|
106
|
+
export const findAddress = (query: string) => geocode({query, language: 'en'}, options);
|
|
107
|
+
export const reversePosition = (position: [number, number]) =>
|
|
108
|
+
geocodeReverse({position, kinds: ['address', 'place']}, options);
|
|
109
|
+
export const suggestPlaces = (query: string) => autocomplete({query, language: 'en'}, options);
|
|
110
|
+
export const resolveSelection = (token: string) => resolveSuggestion({token}, options);
|
|
99
111
|
```
|
|
100
112
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
113
|
+
Text search and Nearby are distinct operations. A suggestion has a title, an optional one-line
|
|
114
|
+
`addressLabel` that tells same-named places apart, and no position. Resolve only the selected
|
|
115
|
+
suggestion. Reverse results are candidates in provider order, without containment, entrance
|
|
116
|
+
precision, routability, deliverability or address-validation guarantees.
|
|
104
117
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
of an idempotent request.
|
|
118
|
+
To show positions, distances and categories while someone types, ask for each suggestion's place.
|
|
119
|
+
A completed request then uses 25 units instead of 10, and choosing a suggestion needs no further
|
|
120
|
+
resolution:
|
|
109
121
|
|
|
110
|
-
|
|
122
|
+
<!-- docs:check -->
|
|
111
123
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
124
|
+
```ts
|
|
125
|
+
import {autocomplete} from '@tileflow/search';
|
|
126
|
+
|
|
127
|
+
const apiKey = process.env.TILEFLOW_API_KEY;
|
|
128
|
+
if (!apiKey) throw new Error('Set TILEFLOW_API_KEY on the server.');
|
|
129
|
+
const options = {apiKey};
|
|
116
130
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
131
|
+
export async function suggestNearbyPlaces(query: string, proximity: [number, number]) {
|
|
132
|
+
const {suggestions} = await autocomplete({query, proximity, include: ['place']}, options);
|
|
133
|
+
return suggestions.map(({label, addressLabel, place}) => ({
|
|
134
|
+
label,
|
|
135
|
+
addressLabel,
|
|
136
|
+
position: place?.position,
|
|
137
|
+
distanceMeters: place?.distanceMeters,
|
|
138
|
+
}));
|
|
139
|
+
}
|
|
140
|
+
```
|
|
122
141
|
|
|
123
|
-
|
|
124
|
-
means the requested mode is unsupported, including AWS HERE-backed geocoding in Japan. It applies
|
|
125
|
-
to forward, reverse, and resolution, not autocomplete. Follow the returned source terms rather
|
|
126
|
-
than treating an SDK option as a blanket storage license.
|
|
142
|
+
## Place details
|
|
127
143
|
|
|
128
|
-
|
|
144
|
+
Results carry an optional `distanceMeters` when the provider reports one: from `proximity`, from
|
|
145
|
+
the centre of `bounds`, or from the reverse or Nearby position. Places can also carry `foodTypes`.
|
|
146
|
+
Other details are requested with `include` and make the operation an extended one:
|
|
129
147
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
148
|
+
| Detail | Returns | Available from |
|
|
149
|
+
| -------------- | ----------------------------------------------------------- | -------------------------------------------------------- |
|
|
150
|
+
| `contacts` | `phones`, `websites` and `emails`, each a `value` and label | Text search inside `bounds`, Nearby, explicit resolution |
|
|
151
|
+
| `openingHours` | Display lines per schedule and `openNow` at request time | Text search inside `bounds`, Nearby, explicit resolution |
|
|
152
|
+
| `timeZone` | IANA `name` and `utcOffsetSeconds` | Every place operation |
|
|
153
|
+
| `accessPoints` | Entrance or access positions, separate from `position` | Every place operation |
|
|
133
154
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
`GEOCODING_SUGGESTION_EXPIRED` (410), `GEOCODING_UPSTREAM_THROTTLED` (429), and
|
|
138
|
-
`GEOCODING_PICK_LIMIT_EXCEEDED` (429). Refresh an expired suggestion rather than replaying its token.
|
|
155
|
+
Asking geocoding without `bounds`, or reverse geocoding, for contacts or opening hours fails with
|
|
156
|
+
`GEOCODING_FILTER_UNSUPPORTED` before any request is sent. Each detail is optional in a response:
|
|
157
|
+
a place whose hours the provider does not supply has no `openingHours`.
|
|
139
158
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
159
|
+
<!-- docs:check -->
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
import {resolvePlace} from '@tileflow/search';
|
|
163
|
+
|
|
164
|
+
const apiKey = process.env.TILEFLOW_API_KEY;
|
|
165
|
+
if (!apiKey) throw new Error('Set TILEFLOW_API_KEY on the server.');
|
|
166
|
+
const options = {apiKey};
|
|
167
|
+
|
|
168
|
+
export async function placeCard(token: string) {
|
|
169
|
+
const {result} = await resolvePlace({token, include: ['contacts', 'openingHours']}, options);
|
|
170
|
+
const website = result.contacts?.websites?.find(({value}) => /^https?:\/\//iu.test(value));
|
|
171
|
+
return {
|
|
172
|
+
name: result.name ?? result.label,
|
|
173
|
+
openNow: result.openingHours?.some(({openNow}) => openNow) ?? false,
|
|
174
|
+
hours: result.openingHours?.flatMap(({display}) => display ?? []) ?? [],
|
|
175
|
+
phone: result.contacts?.phones?.[0]?.value,
|
|
176
|
+
website: website?.value,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
```
|
|
143
180
|
|
|
144
|
-
|
|
145
|
-
|
|
181
|
+
Contact values are provider text, not validated links: check the scheme before rendering one as
|
|
182
|
+
a link, and expect several phones or websites for a place, not all equally relevant. `openNow`
|
|
183
|
+
describes the moment of the request. Like every result, details are for temporary display.
|
|
184
|
+
|
|
185
|
+
## Limits and temporary use
|
|
186
|
+
|
|
187
|
+
Forward/autocomplete text accepts 1–200 characters. Their limit defaults to 5 and accepts 1–10;
|
|
188
|
+
reverse defaults to 1 and accepts 1–10. Reverse kinds are an OR of `address`, `street`, `locality`
|
|
189
|
+
and `place`. Forward/autocomplete `bounds` and `proximity` are mutually exclusive.
|
|
190
|
+
|
|
191
|
+
Nearby requires `position`, defaults to 20 results and accepts 1–100. Optional `radiusMeters` accepts
|
|
192
|
+
1–21,000,000. A bbox is `[west, south, east, north]`, with west < east and south < north; wrapping
|
|
193
|
+
antimeridian boxes are unsupported. Position alone uses native provider scope and makes no hard
|
|
194
|
+
extent promise. Combined radius+bbox is currently rejected with `GEOCODING_FILTER_UNSUPPORTED`.
|
|
195
|
+
|
|
196
|
+
Use literal current category IDs, including spaces and punctuation. Include/exclude lists accept up
|
|
197
|
+
to ten IDs each. Inclusion is OR; exclusion vetoes matches. No parent-category expansion is implied.
|
|
198
|
+
Country filters accept up to 100 ISO alpha-2/alpha-3 codes and intersect spatial/category constraints.
|
|
199
|
+
Unknown IDs or countries are rejected. Language requests use provider fallback, without a translation
|
|
200
|
+
or localization completeness guarantee. Nearby does not accept text queries or an ordering selector.
|
|
201
|
+
|
|
202
|
+
Nearby requests are bounded to 32 KiB and responses to 512 KiB. Cursors accept at most 8192 characters;
|
|
203
|
+
selection tokens at most 2048. Oversized/invalid pages fail as a whole. The SDK parses strict schemas
|
|
204
|
+
and never drops malformed results to manufacture a valid success.
|
|
205
|
+
|
|
206
|
+
Forward, reverse and resolution accept only `retention: 'temporary'`, also the default. Persistent
|
|
207
|
+
retention, Advanced features and caller-selected provider options are rejected before provider work.
|
|
208
|
+
|
|
209
|
+
## Usage and errors
|
|
210
|
+
|
|
211
|
+
| Completed operation | Shared API units | With `include` |
|
|
212
|
+
| ------------------------------------- | ---------------: | -------------: |
|
|
213
|
+
| Category discovery | 0 | — |
|
|
214
|
+
| Autocomplete | 10 | 25 |
|
|
215
|
+
| Forward, reverse, explicit resolution | 25 | 75 |
|
|
216
|
+
| Each Nearby page | 25 | 75 |
|
|
217
|
+
|
|
218
|
+
`searchOperationUnits(operation, include)` returns the same weights. Valid empty responses consume
|
|
219
|
+
the same units. Three autocomplete calls and one resolution consume 55 units. These are part of Starter's shared API allowance, with no separate Search credits or
|
|
220
|
+
commercial search session. Hovering, displaying results and moving a map do not call Search.
|
|
221
|
+
|
|
222
|
+
`GeocodingError` exposes `status`, nullable `code`, nullable `requestId` and nullable bounded
|
|
223
|
+
`retryAfterSeconds`. The client never retries, including after throttling. Use `signal` for
|
|
224
|
+
cancellation. Network failures can leave server completion uncertain.
|
|
225
|
+
|
|
226
|
+
| Code | Action |
|
|
227
|
+
| ---------------------------------------------------------------- | ------------------------------------------------------------------- |
|
|
228
|
+
| `GEOCODING_PLAN_UNAVAILABLE` | Use paid Starter or request an eligible evaluation. |
|
|
229
|
+
| `GEOCODING_EVALUATION_REQUIRED` / `GEOCODING_EVALUATION_EXPIRED` | Ask the Team's operator to review evaluation access. |
|
|
230
|
+
| `GEOCODING_EVALUATION_EXHAUSTED` | Stop requests; review evaluation limits. |
|
|
231
|
+
| `GEOCODING_PICK_LIMIT_EXCEEDED` / `GEOCODING_UPSTREAM_THROTTLED` | Respect `retryAfterSeconds`; retry only deliberately. |
|
|
232
|
+
| `GEOCODING_INVALID_CURSOR` / `GEOCODING_INVALID_SUGGESTION` | Check the Team and original query; start a fresh search explicitly. |
|
|
233
|
+
| `GEOCODING_CURSOR_EXPIRED` / `GEOCODING_SUGGESTION_EXPIRED` | Request fresh results explicitly. |
|
|
234
|
+
| `GEOCODING_USAGE_UNCONFIRMED` | Completion may already be charged. Inspect usage before repeating. |
|
|
235
|
+
| `GEOCODING_DISABLED` | Search is disabled; installation does not activate it. |
|
|
236
|
+
|
|
237
|
+
Rejected or failed operations do not return a completed usage record. Never translate an unconfirmed
|
|
238
|
+
completion into zero units. Confirmed server completion is not undone by a later disconnection.
|
|
239
|
+
API URLs require HTTPS, except explicit loopback HTTP during development. `fetch` can be injected
|
|
240
|
+
for local tests. Request and response schemas are exported from `@tileflow/search/contract`;
|
|
241
|
+
transport is available from `@tileflow/search/client`.
|
|
242
|
+
|
|
243
|
+
## Integrate an independent search interaction
|
|
244
|
+
|
|
245
|
+
Each widget owns its timer, abort controller, request generation and selection state. Debounce input;
|
|
246
|
+
suppress requests while IME composition is active; launch the final input on `compositionend`.
|
|
247
|
+
Increment the generation and abort previous work on input, selection, dismissal or a view change.
|
|
248
|
+
Apply a response only if its generation still matches, even if abort did not stop the transport.
|
|
249
|
+
|
|
250
|
+
Use an accessible combobox/listbox for suggestions: arrow keys change the active option, Enter or a
|
|
251
|
+
touch/click explicitly selects it, and Escape dismisses it. Give the input a label, maintain focus
|
|
252
|
+
and active-option semantics, and announce loading, empty results and errors through a polite status
|
|
253
|
+
region. Preserve attribution beside displayed results. Do not resolve on hover or focus changes.
|
|
254
|
+
|
|
255
|
+
An explicit category action can call Nearby. Moving a map offers a “Search this area” button; only
|
|
256
|
+
pressing it submits the new bounds. A late reply must not move the camera after a newer selection or
|
|
257
|
+
view change. Keep independent generations for independent widgets. The SDK's tests demonstrate
|
|
258
|
+
cancellation and stale-suggestion handling; it does not supply a UI component or client credentials.
|
|
259
|
+
|
|
260
|
+
For a published package, its installed README and declarations describe that release; a link to
|
|
261
|
+
GitHub `main` can describe newer source.
|
|
@@ -5,13 +5,26 @@ import {
|
|
|
5
5
|
geocodingForwardResponseSchema,
|
|
6
6
|
geocodingLimits,
|
|
7
7
|
geocodingReverseRequestSchema,
|
|
8
|
+
nearbyLimits,
|
|
9
|
+
nearbyRequestSchema,
|
|
10
|
+
nearbyResponseSchema,
|
|
8
11
|
resolveSuggestionRequestSchema,
|
|
9
|
-
resolveSuggestionResponseSchema
|
|
10
|
-
|
|
12
|
+
resolveSuggestionResponseSchema,
|
|
13
|
+
searchCategoriesResponseSchema,
|
|
14
|
+
searchContactDetails,
|
|
15
|
+
searchOperationUnits
|
|
16
|
+
} from "./chunk-W66J2DAS.js";
|
|
11
17
|
|
|
12
18
|
// src/client.ts
|
|
13
19
|
import { z } from "zod";
|
|
14
20
|
var GEOCODING_ERROR_CODES = [
|
|
21
|
+
"GEOCODING_FILTER_UNSUPPORTED",
|
|
22
|
+
"GEOCODING_PLAN_UNAVAILABLE",
|
|
23
|
+
"GEOCODING_EVALUATION_REQUIRED",
|
|
24
|
+
"GEOCODING_EVALUATION_EXPIRED",
|
|
25
|
+
"GEOCODING_EVALUATION_EXHAUSTED",
|
|
26
|
+
"GEOCODING_INVALID_CURSOR",
|
|
27
|
+
"GEOCODING_CURSOR_EXPIRED",
|
|
15
28
|
"GEOCODING_ABORTED",
|
|
16
29
|
"GEOCODING_DISABLED",
|
|
17
30
|
"GEOCODING_INVALID_REQUEST",
|
|
@@ -20,7 +33,6 @@ var GEOCODING_ERROR_CODES = [
|
|
|
20
33
|
"GEOCODING_QUOTA_EXCEEDED",
|
|
21
34
|
"GEOCODING_REQUEST_TOO_LARGE",
|
|
22
35
|
"GEOCODING_SUGGESTION_EXPIRED",
|
|
23
|
-
"GEOCODING_TERRITORY_UNSUPPORTED",
|
|
24
36
|
"GEOCODING_UNAVAILABLE",
|
|
25
37
|
"GEOCODING_UPSTREAM_INVALID",
|
|
26
38
|
"GEOCODING_UPSTREAM_THROTTLED",
|
|
@@ -29,6 +41,13 @@ var GEOCODING_ERROR_CODES = [
|
|
|
29
41
|
"GEOCODING_USAGE_UNCONFIRMED"
|
|
30
42
|
];
|
|
31
43
|
var geocodingErrorStatuses = {
|
|
44
|
+
GEOCODING_FILTER_UNSUPPORTED: 422,
|
|
45
|
+
GEOCODING_PLAN_UNAVAILABLE: 403,
|
|
46
|
+
GEOCODING_EVALUATION_REQUIRED: 403,
|
|
47
|
+
GEOCODING_EVALUATION_EXPIRED: 403,
|
|
48
|
+
GEOCODING_EVALUATION_EXHAUSTED: 429,
|
|
49
|
+
GEOCODING_INVALID_CURSOR: 400,
|
|
50
|
+
GEOCODING_CURSOR_EXPIRED: 410,
|
|
32
51
|
GEOCODING_ABORTED: 499,
|
|
33
52
|
GEOCODING_DISABLED: 503,
|
|
34
53
|
GEOCODING_INVALID_REQUEST: 400,
|
|
@@ -37,7 +56,6 @@ var geocodingErrorStatuses = {
|
|
|
37
56
|
GEOCODING_QUOTA_EXCEEDED: 429,
|
|
38
57
|
GEOCODING_REQUEST_TOO_LARGE: 413,
|
|
39
58
|
GEOCODING_SUGGESTION_EXPIRED: 410,
|
|
40
|
-
GEOCODING_TERRITORY_UNSUPPORTED: 422,
|
|
41
59
|
GEOCODING_UNAVAILABLE: 503,
|
|
42
60
|
GEOCODING_UPSTREAM_INVALID: 502,
|
|
43
61
|
GEOCODING_UPSTREAM_THROTTLED: 429,
|
|
@@ -60,12 +78,14 @@ var GeocodingError = class extends Error {
|
|
|
60
78
|
code;
|
|
61
79
|
requestId;
|
|
62
80
|
status;
|
|
81
|
+
retryAfterSeconds;
|
|
63
82
|
constructor(message, input) {
|
|
64
83
|
super(message);
|
|
65
84
|
this.name = "GeocodingError";
|
|
66
85
|
this.code = input.code ?? null;
|
|
67
86
|
this.requestId = input.requestId ?? null;
|
|
68
87
|
this.status = input.status;
|
|
88
|
+
this.retryAfterSeconds = input.retryAfterSeconds ?? null;
|
|
69
89
|
}
|
|
70
90
|
};
|
|
71
91
|
async function geocode(request, options) {
|
|
@@ -73,12 +93,14 @@ async function geocode(request, options) {
|
|
|
73
93
|
if (!parsedRequest.success) {
|
|
74
94
|
throw new Error("Invalid Tileflow geocoding request");
|
|
75
95
|
}
|
|
96
|
+
if (!parsedRequest.data.bounds) rejectContactDetails(parsedRequest.data.include);
|
|
97
|
+
const units = searchOperationUnits("forward", parsedRequest.data.include);
|
|
76
98
|
return requestGeocoding(
|
|
77
99
|
"/v1/geocoding/forward",
|
|
78
100
|
parsedRequest.data,
|
|
79
101
|
options,
|
|
80
102
|
geocodingForwardResponseSchema,
|
|
81
|
-
(response) => response.results.length <= (parsedRequest.data.limit ?? geocodingLimits.defaultLimit)
|
|
103
|
+
(response) => response.usage.units === units && response.results.length <= (parsedRequest.data.limit ?? geocodingLimits.defaultLimit)
|
|
82
104
|
);
|
|
83
105
|
}
|
|
84
106
|
async function geocodeReverse(request, options) {
|
|
@@ -86,12 +108,14 @@ async function geocodeReverse(request, options) {
|
|
|
86
108
|
if (!parsedRequest.success) {
|
|
87
109
|
throw new Error("Invalid Tileflow geocoding request");
|
|
88
110
|
}
|
|
111
|
+
rejectContactDetails(parsedRequest.data.include);
|
|
112
|
+
const units = searchOperationUnits("reverse", parsedRequest.data.include);
|
|
89
113
|
return requestGeocoding(
|
|
90
114
|
"/v1/geocoding/reverse",
|
|
91
115
|
parsedRequest.data,
|
|
92
116
|
options,
|
|
93
117
|
geocodingForwardResponseSchema,
|
|
94
|
-
(response) => response.results.length <= parsedRequest.data.limit && containsOnlyKinds(response, parsedRequest.data.kinds)
|
|
118
|
+
(response) => response.usage.units === units && response.results.length <= parsedRequest.data.limit && containsOnlyKinds(response, parsedRequest.data.kinds)
|
|
95
119
|
);
|
|
96
120
|
}
|
|
97
121
|
async function autocomplete(request, options) {
|
|
@@ -99,12 +123,13 @@ async function autocomplete(request, options) {
|
|
|
99
123
|
if (!parsedRequest.success) {
|
|
100
124
|
throw new Error("Invalid Tileflow geocoding request");
|
|
101
125
|
}
|
|
126
|
+
const withPlaces = parsedRequest.data.include !== void 0;
|
|
102
127
|
return requestGeocoding(
|
|
103
128
|
"/v1/geocoding/autocomplete",
|
|
104
129
|
parsedRequest.data,
|
|
105
130
|
options,
|
|
106
131
|
autocompleteResponseSchema,
|
|
107
|
-
(response) => response.suggestions.length <= parsedRequest.data.limit
|
|
132
|
+
(response) => response.usage.units === searchOperationUnits("autocomplete", parsedRequest.data.include) && response.suggestions.length <= parsedRequest.data.limit && response.suggestions.every((suggestion) => suggestion.place !== void 0 === withPlaces)
|
|
108
133
|
);
|
|
109
134
|
}
|
|
110
135
|
async function resolveSuggestion(request, options) {
|
|
@@ -112,27 +137,64 @@ async function resolveSuggestion(request, options) {
|
|
|
112
137
|
if (!parsedRequest.success) {
|
|
113
138
|
throw new Error("Invalid Tileflow geocoding request");
|
|
114
139
|
}
|
|
140
|
+
const units = searchOperationUnits("resolve", parsedRequest.data.include);
|
|
115
141
|
return requestGeocoding(
|
|
116
|
-
"/v1/geocoding/resolve
|
|
142
|
+
"/v1/geocoding/resolve",
|
|
117
143
|
parsedRequest.data,
|
|
118
144
|
options,
|
|
119
|
-
resolveSuggestionResponseSchema
|
|
145
|
+
resolveSuggestionResponseSchema,
|
|
146
|
+
(response) => response.usage.units === units
|
|
120
147
|
);
|
|
121
148
|
}
|
|
122
|
-
|
|
149
|
+
var resolvePlace = resolveSuggestion;
|
|
150
|
+
async function searchNearby(request, options) {
|
|
151
|
+
const parsed = nearbyRequestSchema.safeParse(request);
|
|
152
|
+
if (!parsed.success) throw new Error("Invalid Tileflow Nearby request");
|
|
153
|
+
const query = parsed.data;
|
|
154
|
+
if (query.radiusMeters !== void 0 && query.bounds !== void 0 || query.includeCategories?.some((id) => query.excludeCategories?.includes(id))) {
|
|
155
|
+
throw new GeocodingError("Unsupported geocoding filters", {
|
|
156
|
+
code: "GEOCODING_FILTER_UNSUPPORTED",
|
|
157
|
+
status: 422
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
const units = searchOperationUnits("nearby", query.include);
|
|
161
|
+
return requestGeocoding(
|
|
162
|
+
"/v1/geocoding/nearby",
|
|
163
|
+
query,
|
|
164
|
+
options,
|
|
165
|
+
nearbyResponseSchema,
|
|
166
|
+
(response) => response.usage.units === units && response.results.length <= query.limit,
|
|
167
|
+
nearbyLimits.maximumResponseBytes
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
function listCategories(options) {
|
|
171
|
+
return requestGeocoding(
|
|
172
|
+
"/v1/geocoding/categories",
|
|
173
|
+
void 0,
|
|
174
|
+
options,
|
|
175
|
+
searchCategoriesResponseSchema,
|
|
176
|
+
() => true,
|
|
177
|
+
nearbyLimits.maximumResponseBytes
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
async function requestGeocoding(path, request, options, responseSchema, isExpectedResponse = () => true, maximumResponseBytes = geocodingLimits.maximumResponseBytes) {
|
|
181
|
+
const bodyText = request === void 0 ? void 0 : JSON.stringify(request);
|
|
182
|
+
const maximumRequestBytes = path === "/v1/geocoding/nearby" ? nearbyLimits.maximumRequestBytes : 8 * 1024;
|
|
183
|
+
if (bodyText && new TextEncoder().encode(bodyText).byteLength > maximumRequestBytes)
|
|
184
|
+
throw new Error("Tileflow Search request is too large");
|
|
123
185
|
const apiKey = normalizeApiKey(options.apiKey);
|
|
124
186
|
const apiUrl = normalizeApiUrl(options.apiUrl ?? "https://api.tileflow.dev");
|
|
125
187
|
const fetcher = options.fetch ?? fetch;
|
|
126
188
|
let response;
|
|
127
189
|
try {
|
|
128
190
|
response = await fetcher(`${apiUrl}${path}`, {
|
|
129
|
-
body:
|
|
191
|
+
...bodyText === void 0 ? {} : { body: bodyText },
|
|
130
192
|
headers: {
|
|
131
193
|
Accept: "application/json",
|
|
132
194
|
Authorization: `Bearer ${apiKey}`,
|
|
133
195
|
"Content-Type": "application/json"
|
|
134
196
|
},
|
|
135
|
-
method: "POST",
|
|
197
|
+
method: request === void 0 ? "GET" : "POST",
|
|
136
198
|
redirect: "error",
|
|
137
199
|
signal: options.signal
|
|
138
200
|
});
|
|
@@ -149,7 +211,7 @@ async function requestGeocoding(path, request, options, responseSchema, isExpect
|
|
|
149
211
|
}
|
|
150
212
|
let body;
|
|
151
213
|
try {
|
|
152
|
-
body = await readBoundedJson(response,
|
|
214
|
+
body = await readBoundedJson(response, maximumResponseBytes, options.signal);
|
|
153
215
|
} catch (error) {
|
|
154
216
|
if (options.signal?.aborted) throw options.signal.reason ?? error;
|
|
155
217
|
throw new GeocodingError("Tileflow geocoding returned an invalid response", {
|
|
@@ -164,6 +226,14 @@ async function requestGeocoding(path, request, options, responseSchema, isExpect
|
|
|
164
226
|
}
|
|
165
227
|
return parsedResponse.data;
|
|
166
228
|
}
|
|
229
|
+
function rejectContactDetails(include) {
|
|
230
|
+
if (include?.some((detail) => searchContactDetails.includes(detail))) {
|
|
231
|
+
throw new GeocodingError("Unsupported geocoding filters", {
|
|
232
|
+
code: "GEOCODING_FILTER_UNSUPPORTED",
|
|
233
|
+
status: 422
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
167
237
|
function containsOnlyKinds(response, allowedKinds) {
|
|
168
238
|
if (!allowedKinds) return true;
|
|
169
239
|
const kindFilter = new Set(allowedKinds);
|
|
@@ -189,18 +259,25 @@ function normalizeApiUrl(value) {
|
|
|
189
259
|
return url.href.replace(/\/$/u, "");
|
|
190
260
|
}
|
|
191
261
|
async function readGeocodingError(response, signal) {
|
|
262
|
+
const value = response.headers.get("Retry-After");
|
|
263
|
+
const delay = value && /^\d{1,8}$/u.test(value) ? Number(value) : null;
|
|
264
|
+
const retryAfterSeconds = delay !== null && delay <= 31536e3 ? delay : null;
|
|
192
265
|
let body;
|
|
193
266
|
try {
|
|
194
267
|
body = await readBoundedJson(response, geocodingLimits.maximumSafeErrorBytes, signal);
|
|
195
268
|
} catch (error) {
|
|
196
269
|
if (signal?.aborted) throw signal.reason ?? error;
|
|
197
|
-
return new GeocodingError("Tileflow geocoding request failed", {
|
|
270
|
+
return new GeocodingError("Tileflow geocoding request failed", {
|
|
271
|
+
retryAfterSeconds,
|
|
272
|
+
status: response.status
|
|
273
|
+
});
|
|
198
274
|
}
|
|
199
275
|
const geocoding = geocodingErrorResponseSchema.safeParse(body);
|
|
200
276
|
if (geocoding.success && geocodingErrorStatuses[geocoding.data.code] === response.status) {
|
|
201
277
|
return new GeocodingError(geocoding.data.error, {
|
|
202
278
|
code: geocoding.data.code,
|
|
203
279
|
requestId: geocoding.data.requestId,
|
|
280
|
+
retryAfterSeconds,
|
|
204
281
|
status: response.status
|
|
205
282
|
});
|
|
206
283
|
}
|
|
@@ -208,10 +285,14 @@ async function readGeocodingError(response, signal) {
|
|
|
208
285
|
if (credential.success && [401, 403, 429].includes(response.status)) {
|
|
209
286
|
return new GeocodingError(credential.data.error, {
|
|
210
287
|
requestId: credential.data.requestId,
|
|
288
|
+
retryAfterSeconds,
|
|
211
289
|
status: response.status
|
|
212
290
|
});
|
|
213
291
|
}
|
|
214
|
-
return new GeocodingError("Tileflow geocoding request failed", {
|
|
292
|
+
return new GeocodingError("Tileflow geocoding request failed", {
|
|
293
|
+
retryAfterSeconds,
|
|
294
|
+
status: response.status
|
|
295
|
+
});
|
|
215
296
|
}
|
|
216
297
|
async function readBoundedJson(response, maximumBytes, signal) {
|
|
217
298
|
const declaredLength = response.headers.get("Content-Length");
|
|
@@ -282,5 +363,8 @@ export {
|
|
|
282
363
|
geocode,
|
|
283
364
|
geocodeReverse,
|
|
284
365
|
autocomplete,
|
|
285
|
-
resolveSuggestion
|
|
366
|
+
resolveSuggestion,
|
|
367
|
+
resolvePlace,
|
|
368
|
+
searchNearby,
|
|
369
|
+
listCategories
|
|
286
370
|
};
|