@tileflow/search 0.1.0-alpha.3 → 0.1.0-alpha.5

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 CHANGED
@@ -11,14 +11,43 @@ selected-place resolution. It uses `fetch` and has no renderer or global interac
11
11
  npm install @tileflow/search@alpha
12
12
  ```
13
13
 
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.
14
+ Use Node.js 22, a browser or another runtime with `fetch`. Live requests require enabled Tileflow
15
+ Search and a Team credential with `geocoding:read`: a publishable credential in websites and apps,
16
+ or a server credential on your server.
17
17
 
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.
18
+ Free Teams, Starter trials and complimentary access run Search within their period's API units, up
19
+ to 100,000 Search units, while Search without payment is open; a finite evaluation grant can extend
20
+ them. Paid Starter runs Search within its shared API allowance and excess.
20
21
  Installing this package does not enable the service or grant access.
21
22
 
23
+ ## Call Search from a browser or app
24
+
25
+ A publishable credential (`tf_public_…`) is meant to be visible in client code. It grants Search
26
+ only, works only from the websites and native apps listed for it, and always carries per-minute and
27
+ per-day quotas. A Team Admin creates one under **Credentials** in the Tileflow dashboard.
28
+
29
+ ```ts
30
+ import {autocomplete} from '@tileflow/search';
31
+
32
+ const options = {apiKey: 'tf_public_…'};
33
+ const page = await autocomplete({query: 'Hospital La Paz', proximity: [-3.7, 40.42]}, options);
34
+ ```
35
+
36
+ Browsers send the page's origin, which must match one of the credential's websites exactly: scheme,
37
+ host and port. Native apps send no origin, so name the app instead. The SDK then sends its bundle ID
38
+ or package name and, on Android, the SHA-256 fingerprint of its signing certificate:
39
+
40
+ ```ts
41
+ const options = {
42
+ apiKey: 'tf_public_…',
43
+ app: {id: 'com.example.store', certificateSha256: '14:6D:E9:…'},
44
+ };
45
+ ```
46
+
47
+ Anyone can read a publishable credential and imitate an origin or app outside a browser, so these
48
+ restrictions limit casual reuse rather than prove identity. Its quotas and the Team's spending limit
49
+ bound what a copied credential can consume. Keep server credentials (`tf_live_…`) on your server.
50
+
22
51
  ## Find nearby places
23
52
 
24
53
  Run this example on your server with `TILEFLOW_API_KEY` set securely. It lists the current categories,
@@ -63,7 +92,7 @@ are temporary. Do not build a permanent place catalog, cache results, or persist
63
92
 
64
93
  Save the original request in the current interaction. Another page repeats that request with the
65
94
  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.
95
+ Each explicit page costs 10 API units. There is no total-count or snapshot-consistency promise.
67
96
 
68
97
  <!-- docs:check -->
69
98
 
@@ -110,15 +139,84 @@ export const suggestPlaces = (query: string) => autocomplete({query, language: '
110
139
  export const resolveSelection = (token: string) => resolveSuggestion({token}, options);
111
140
  ```
112
141
 
113
- Text search and Nearby are distinct operations. Suggestions have no position. Resolve only the
114
- selected suggestion. Reverse results are candidates in provider order, without containment,
115
- entrance precision, routability, deliverability or address-validation guarantees.
142
+ Text search and Nearby are distinct operations. A suggestion has a title, an optional one-line
143
+ `addressLabel` that tells same-named places apart, and no position. Resolve only the selected
144
+ suggestion. Reverse results are candidates in provider order, without containment, entrance
145
+ precision, routability, deliverability or address-validation guarantees.
146
+
147
+ To show positions, distances and categories while someone types, ask for each suggestion's place.
148
+ A completed request then uses 10 units instead of 4, and choosing a suggestion needs no further
149
+ resolution:
150
+
151
+ <!-- docs:check -->
152
+
153
+ ```ts
154
+ import {autocomplete} from '@tileflow/search';
155
+
156
+ const apiKey = process.env.TILEFLOW_API_KEY;
157
+ if (!apiKey) throw new Error('Set TILEFLOW_API_KEY on the server.');
158
+ const options = {apiKey};
159
+
160
+ export async function suggestNearbyPlaces(query: string, proximity: [number, number]) {
161
+ const {suggestions} = await autocomplete({query, proximity, include: ['place']}, options);
162
+ return suggestions.map(({label, addressLabel, place}) => ({
163
+ label,
164
+ addressLabel,
165
+ position: place?.position,
166
+ distanceMeters: place?.distanceMeters,
167
+ }));
168
+ }
169
+ ```
170
+
171
+ ## Place details
172
+
173
+ Results carry an optional `distanceMeters` when the provider reports one: from `proximity`, from
174
+ the centre of `bounds`, or from the reverse or Nearby position. Places can also carry `foodTypes`.
175
+ Other details are requested with `include` and make the operation an extended one:
176
+
177
+ | Detail | Returns | Available from |
178
+ | -------------- | ----------------------------------------------------------- | -------------------------------------------------------- |
179
+ | `contacts` | `phones`, `websites` and `emails`, each a `value` and label | Text search inside `bounds`, Nearby, explicit resolution |
180
+ | `openingHours` | Display lines per schedule and `openNow` at request time | Text search inside `bounds`, Nearby, explicit resolution |
181
+ | `timeZone` | IANA `name` and `utcOffsetSeconds` | Every place operation |
182
+ | `accessPoints` | Entrance or access positions, separate from `position` | Every place operation |
183
+
184
+ Asking geocoding without `bounds`, or reverse geocoding, for contacts or opening hours fails with
185
+ `GEOCODING_FILTER_UNSUPPORTED` before any request is sent. Each detail is optional in a response:
186
+ a place whose hours the provider does not supply has no `openingHours`.
187
+
188
+ <!-- docs:check -->
189
+
190
+ ```ts
191
+ import {resolvePlace} from '@tileflow/search';
192
+
193
+ const apiKey = process.env.TILEFLOW_API_KEY;
194
+ if (!apiKey) throw new Error('Set TILEFLOW_API_KEY on the server.');
195
+ const options = {apiKey};
196
+
197
+ export async function placeCard(token: string) {
198
+ const {result} = await resolvePlace({token, include: ['contacts', 'openingHours']}, options);
199
+ const website = result.contacts?.websites?.find(({value}) => /^https?:\/\//iu.test(value));
200
+ return {
201
+ name: result.name ?? result.label,
202
+ openNow: result.openingHours?.some(({openNow}) => openNow) ?? false,
203
+ hours: result.openingHours?.flatMap(({display}) => display ?? []) ?? [],
204
+ phone: result.contacts?.phones?.[0]?.value,
205
+ website: website?.value,
206
+ };
207
+ }
208
+ ```
209
+
210
+ Contact values are provider text, not validated links: check the scheme before rendering one as
211
+ a link, and expect several phones or websites for a place, not all equally relevant. `openNow`
212
+ describes the moment of the request. Like every result, details are for temporary display.
116
213
 
117
214
  ## Limits and temporary use
118
215
 
119
216
  Forward/autocomplete text accepts 1–200 characters. Their limit defaults to 5 and accepts 1–10;
120
217
  reverse defaults to 1 and accepts 1–10. Reverse kinds are an OR of `address`, `street`, `locality`
121
- and `place`. Forward/autocomplete `bounds` and `proximity` are mutually exclusive.
218
+ and `place`. Forward/autocomplete `proximity` biases ranking and `bounds` restricts results; they
219
+ are mutually exclusive.
122
220
 
123
221
  Nearby requires `position`, defaults to 20 results and accepts 1–100. Optional `radiusMeters` accepts
124
222
  1–21,000,000. A bbox is `[west, south, east, north]`, with west < east and south < north; wrapping
@@ -140,31 +238,37 @@ retention, Advanced features and caller-selected provider options are rejected b
140
238
 
141
239
  ## Usage and errors
142
240
 
143
- | Completed operation | Shared API units |
144
- | ------------------------------------- | ---------------: |
145
- | Category discovery | 0 |
146
- | Autocomplete | 10 |
147
- | Forward, reverse, explicit resolution | 25 |
148
- | Each Nearby page | 25 |
241
+ | Completed operation | Shared API units | With `include` |
242
+ | ------------------------------------- | ---------------: | -------------: |
243
+ | Category discovery | 0 | — |
244
+ | Autocomplete | 4 | 10 |
245
+ | Forward, reverse, explicit resolution | 10 | 30 |
246
+ | Each Nearby page | 10 | 30 |
149
247
 
150
- Valid empty responses consume the same units. Three autocomplete calls and one resolution consume
151
- 55 units. These are part of Starter's shared API allowance, with no separate Search credits or
248
+ `searchOperationUnits(operation, include)` returns the same weights. Valid empty responses consume
249
+ the same units. Three autocomplete calls and one resolution consume 22 units. These are part of Starter's shared API allowance, with no separate Search credits or
152
250
  commercial search session. Hovering, displaying results and moving a map do not call Search.
153
251
 
154
252
  `GeocodingError` exposes `status`, nullable `code`, nullable `requestId` and nullable bounded
155
253
  `retryAfterSeconds`. The client never retries, including after throttling. Use `signal` for
156
254
  cancellation. Network failures can leave server completion uncertain.
157
255
 
158
- | Code | Action |
159
- | ---------------------------------------------------------------- | ------------------------------------------------------------------- |
160
- | `GEOCODING_PLAN_UNAVAILABLE` | Use paid Starter or request an eligible evaluation. |
161
- | `GEOCODING_EVALUATION_REQUIRED` / `GEOCODING_EVALUATION_EXPIRED` | Ask the Team's operator to review evaluation access. |
162
- | `GEOCODING_EVALUATION_EXHAUSTED` | Stop requests; review evaluation limits. |
163
- | `GEOCODING_PICK_LIMIT_EXCEEDED` / `GEOCODING_UPSTREAM_THROTTLED` | Respect `retryAfterSeconds`; retry only deliberately. |
164
- | `GEOCODING_INVALID_CURSOR` / `GEOCODING_INVALID_SUGGESTION` | Check the Team and original query; start a fresh search explicitly. |
165
- | `GEOCODING_CURSOR_EXPIRED` / `GEOCODING_SUGGESTION_EXPIRED` | Request fresh results explicitly. |
166
- | `GEOCODING_USAGE_UNCONFIRMED` | Completion may already be charged. Inspect usage before repeating. |
167
- | `GEOCODING_DISABLED` | Search is disabled; installation does not activate it. |
256
+ | Code | Action |
257
+ | ---------------------------------------------------------------- | --------------------------------------------------------------------- |
258
+ | `GEOCODING_PLAN_UNAVAILABLE` | Use paid Starter or request an eligible evaluation. |
259
+ | `GEOCODING_EVALUATION_REQUIRED` / `GEOCODING_EVALUATION_EXPIRED` | Ask the Team's operator to review evaluation access. |
260
+ | `GEOCODING_EVALUATION_EXHAUSTED` | Stop requests; review evaluation limits. |
261
+ | `GEOCODING_UNPAID_LIMIT_REACHED` | Search without payment reached its period limit; upgrade to Starter. |
262
+ | `GEOCODING_UNPAID_BUDGET_EXHAUSTED` | Search without payment is paused; retry after `retryAfterSeconds`. |
263
+ | `GEOCODING_SUSPENDED` | Search is suspended for the Team; contact support. |
264
+ | `GEOCODING_SPENDING_LIMIT_REACHED` | The Team reached its spending limit; raise it or wait for the period. |
265
+ | `GEOCODING_CREDENTIAL_QUOTA_EXCEEDED` | The credential reached its quota; respect `retryAfterSeconds`. |
266
+ | `GEOCODING_ORIGIN_NOT_ALLOWED` | Add this website or app to the publishable credential. |
267
+ | `GEOCODING_PICK_LIMIT_EXCEEDED` / `GEOCODING_UPSTREAM_THROTTLED` | Respect `retryAfterSeconds`; retry only deliberately. |
268
+ | `GEOCODING_INVALID_CURSOR` / `GEOCODING_INVALID_SUGGESTION` | Check the Team and original query; start a fresh search explicitly. |
269
+ | `GEOCODING_CURSOR_EXPIRED` / `GEOCODING_SUGGESTION_EXPIRED` | Request fresh results explicitly. |
270
+ | `GEOCODING_USAGE_UNCONFIRMED` | Completion may already be charged. Inspect usage before repeating. |
271
+ | `GEOCODING_DISABLED` | Search is disabled; installation does not activate it. |
168
272
 
169
273
  Rejected or failed operations do not return a completed usage record. Never translate an unconfirmed
170
274
  completion into zero units. Confirmed server completion is not undone by a later disconnection.
@@ -184,10 +288,16 @@ touch/click explicitly selects it, and Escape dismisses it. Give the input a lab
184
288
  and active-option semantics, and announce loading, empty results and errors through a polite status
185
289
  region. Preserve attribution beside displayed results. Do not resolve on hover or focus changes.
186
290
 
291
+ Text search over a map should favor the view without being confined to it. Send the map's centre as
292
+ `proximity` while it shows a city or closer, and omit it at country or world scale; nearer matches
293
+ then rank first, and a better match elsewhere, such as the city of Madrid typed over Lisbon, still
294
+ appears. `bounds` excludes everything outside the box, so do not derive it from the view for typed
295
+ search.
296
+
187
297
  An explicit category action can call Nearby. Moving a map offers a “Search this area” button; only
188
298
  pressing it submits the new bounds. A late reply must not move the camera after a newer selection or
189
299
  view change. Keep independent generations for independent widgets. The SDK's tests demonstrate
190
- cancellation and stale-suggestion handling; it does not supply a UI component or client credentials.
300
+ cancellation and stale-suggestion handling; it does not supply a UI component.
191
301
 
192
302
  For a published package, its installed README and declarations describe that release; a link to
193
303
  GitHub `main` can describe newer source.
@@ -10,8 +10,10 @@ import {
10
10
  nearbyResponseSchema,
11
11
  resolveSuggestionRequestSchema,
12
12
  resolveSuggestionResponseSchema,
13
- searchCategoriesResponseSchema
14
- } from "./chunk-R7KE2IN7.js";
13
+ searchCategoriesResponseSchema,
14
+ searchContactDetails,
15
+ searchOperationUnits
16
+ } from "./chunk-W7IIJLOI.js";
15
17
 
16
18
  // src/client.ts
17
19
  import { z } from "zod";
@@ -27,11 +29,17 @@ var GEOCODING_ERROR_CODES = [
27
29
  "GEOCODING_DISABLED",
28
30
  "GEOCODING_INVALID_REQUEST",
29
31
  "GEOCODING_INVALID_SUGGESTION",
32
+ "GEOCODING_ORIGIN_NOT_ALLOWED",
30
33
  "GEOCODING_PICK_LIMIT_EXCEEDED",
31
34
  "GEOCODING_QUOTA_EXCEEDED",
32
35
  "GEOCODING_REQUEST_TOO_LARGE",
36
+ "GEOCODING_SPENDING_LIMIT_REACHED",
37
+ "GEOCODING_CREDENTIAL_QUOTA_EXCEEDED",
33
38
  "GEOCODING_SUGGESTION_EXPIRED",
39
+ "GEOCODING_SUSPENDED",
34
40
  "GEOCODING_UNAVAILABLE",
41
+ "GEOCODING_UNPAID_BUDGET_EXHAUSTED",
42
+ "GEOCODING_UNPAID_LIMIT_REACHED",
35
43
  "GEOCODING_UPSTREAM_INVALID",
36
44
  "GEOCODING_UPSTREAM_THROTTLED",
37
45
  "GEOCODING_UPSTREAM_TIMEOUT",
@@ -50,11 +58,17 @@ var geocodingErrorStatuses = {
50
58
  GEOCODING_DISABLED: 503,
51
59
  GEOCODING_INVALID_REQUEST: 400,
52
60
  GEOCODING_INVALID_SUGGESTION: 400,
61
+ GEOCODING_ORIGIN_NOT_ALLOWED: 403,
53
62
  GEOCODING_PICK_LIMIT_EXCEEDED: 429,
54
63
  GEOCODING_QUOTA_EXCEEDED: 429,
55
64
  GEOCODING_REQUEST_TOO_LARGE: 413,
65
+ GEOCODING_SPENDING_LIMIT_REACHED: 429,
66
+ GEOCODING_CREDENTIAL_QUOTA_EXCEEDED: 429,
56
67
  GEOCODING_SUGGESTION_EXPIRED: 410,
68
+ GEOCODING_SUSPENDED: 403,
57
69
  GEOCODING_UNAVAILABLE: 503,
70
+ GEOCODING_UNPAID_BUDGET_EXHAUSTED: 429,
71
+ GEOCODING_UNPAID_LIMIT_REACHED: 429,
58
72
  GEOCODING_UPSTREAM_INVALID: 502,
59
73
  GEOCODING_UPSTREAM_THROTTLED: 429,
60
74
  GEOCODING_UPSTREAM_TIMEOUT: 504,
@@ -91,12 +105,14 @@ async function geocode(request, options) {
91
105
  if (!parsedRequest.success) {
92
106
  throw new Error("Invalid Tileflow geocoding request");
93
107
  }
108
+ if (!parsedRequest.data.bounds) rejectContactDetails(parsedRequest.data.include);
109
+ const units = searchOperationUnits("forward", parsedRequest.data.include);
94
110
  return requestGeocoding(
95
111
  "/v1/geocoding/forward",
96
112
  parsedRequest.data,
97
113
  options,
98
114
  geocodingForwardResponseSchema,
99
- (response) => response.results.length <= (parsedRequest.data.limit ?? geocodingLimits.defaultLimit)
115
+ (response) => response.usage.units === units && response.results.length <= (parsedRequest.data.limit ?? geocodingLimits.defaultLimit)
100
116
  );
101
117
  }
102
118
  async function geocodeReverse(request, options) {
@@ -104,12 +120,14 @@ async function geocodeReverse(request, options) {
104
120
  if (!parsedRequest.success) {
105
121
  throw new Error("Invalid Tileflow geocoding request");
106
122
  }
123
+ rejectContactDetails(parsedRequest.data.include);
124
+ const units = searchOperationUnits("reverse", parsedRequest.data.include);
107
125
  return requestGeocoding(
108
126
  "/v1/geocoding/reverse",
109
127
  parsedRequest.data,
110
128
  options,
111
129
  geocodingForwardResponseSchema,
112
- (response) => response.results.length <= parsedRequest.data.limit && containsOnlyKinds(response, parsedRequest.data.kinds)
130
+ (response) => response.usage.units === units && response.results.length <= parsedRequest.data.limit && containsOnlyKinds(response, parsedRequest.data.kinds)
113
131
  );
114
132
  }
115
133
  async function autocomplete(request, options) {
@@ -117,12 +135,13 @@ async function autocomplete(request, options) {
117
135
  if (!parsedRequest.success) {
118
136
  throw new Error("Invalid Tileflow geocoding request");
119
137
  }
138
+ const withPlaces = parsedRequest.data.include !== void 0;
120
139
  return requestGeocoding(
121
140
  "/v1/geocoding/autocomplete",
122
141
  parsedRequest.data,
123
142
  options,
124
143
  autocompleteResponseSchema,
125
- (response) => response.suggestions.length <= parsedRequest.data.limit
144
+ (response) => response.usage.units === searchOperationUnits("autocomplete", parsedRequest.data.include) && response.suggestions.length <= parsedRequest.data.limit && response.suggestions.every((suggestion) => suggestion.place !== void 0 === withPlaces)
126
145
  );
127
146
  }
128
147
  async function resolveSuggestion(request, options) {
@@ -130,11 +149,13 @@ async function resolveSuggestion(request, options) {
130
149
  if (!parsedRequest.success) {
131
150
  throw new Error("Invalid Tileflow geocoding request");
132
151
  }
152
+ const units = searchOperationUnits("resolve", parsedRequest.data.include);
133
153
  return requestGeocoding(
134
154
  "/v1/geocoding/resolve",
135
155
  parsedRequest.data,
136
156
  options,
137
- resolveSuggestionResponseSchema
157
+ resolveSuggestionResponseSchema,
158
+ (response) => response.usage.units === units
138
159
  );
139
160
  }
140
161
  var resolvePlace = resolveSuggestion;
@@ -148,12 +169,13 @@ async function searchNearby(request, options) {
148
169
  status: 422
149
170
  });
150
171
  }
172
+ const units = searchOperationUnits("nearby", query.include);
151
173
  return requestGeocoding(
152
174
  "/v1/geocoding/nearby",
153
175
  query,
154
176
  options,
155
177
  nearbyResponseSchema,
156
- (response) => response.results.length <= query.limit,
178
+ (response) => response.usage.units === units && response.results.length <= query.limit,
157
179
  nearbyLimits.maximumResponseBytes
158
180
  );
159
181
  }
@@ -173,6 +195,7 @@ async function requestGeocoding(path, request, options, responseSchema, isExpect
173
195
  if (bodyText && new TextEncoder().encode(bodyText).byteLength > maximumRequestBytes)
174
196
  throw new Error("Tileflow Search request is too large");
175
197
  const apiKey = normalizeApiKey(options.apiKey);
198
+ const app = normalizeApp(options.app);
176
199
  const apiUrl = normalizeApiUrl(options.apiUrl ?? "https://api.tileflow.dev");
177
200
  const fetcher = options.fetch ?? fetch;
178
201
  let response;
@@ -182,7 +205,9 @@ async function requestGeocoding(path, request, options, responseSchema, isExpect
182
205
  headers: {
183
206
  Accept: "application/json",
184
207
  Authorization: `Bearer ${apiKey}`,
185
- "Content-Type": "application/json"
208
+ "Content-Type": "application/json",
209
+ ...app ? { "X-Tileflow-App": app.id } : {},
210
+ ...app?.certificateSha256 ? { "X-Tileflow-App-Certificate": app.certificateSha256 } : {}
186
211
  },
187
212
  method: request === void 0 ? "GET" : "POST",
188
213
  redirect: "error",
@@ -216,11 +241,27 @@ async function requestGeocoding(path, request, options, responseSchema, isExpect
216
241
  }
217
242
  return parsedResponse.data;
218
243
  }
244
+ function rejectContactDetails(include) {
245
+ if (include?.some((detail) => searchContactDetails.includes(detail))) {
246
+ throw new GeocodingError("Unsupported geocoding filters", {
247
+ code: "GEOCODING_FILTER_UNSUPPORTED",
248
+ status: 422
249
+ });
250
+ }
251
+ }
219
252
  function containsOnlyKinds(response, allowedKinds) {
220
253
  if (!allowedKinds) return true;
221
254
  const kindFilter = new Set(allowedKinds);
222
255
  return response.results.every(({ kind }) => kindFilter.has(kind));
223
256
  }
257
+ function normalizeApp(value) {
258
+ if (value === void 0) return null;
259
+ const certificate = value?.certificateSha256?.replaceAll(":", "").toLowerCase();
260
+ if (typeof value?.id !== "string" || value.id.length > 255 || !/^[A-Za-z][A-Za-z0-9_-]*(\.[A-Za-z0-9_-]+)+$/u.test(value.id) || certificate !== void 0 && !/^[0-9a-f]{64}$/u.test(certificate)) {
261
+ throw new Error("Invalid Tileflow app identity");
262
+ }
263
+ return { id: value.id, certificateSha256: certificate };
264
+ }
224
265
  function normalizeApiKey(value) {
225
266
  if (typeof value !== "string" || value.length < 1 || value.length > 4096 || value !== value.trim() || /\p{Cc}/u.test(value)) {
226
267
  throw new Error("Invalid Tileflow API key");
@@ -16,7 +16,10 @@ var geocodingLimits = Object.freeze({
16
16
  maximumResponseBytes: 64 * 1024,
17
17
  maximumSafeErrorBytes: 8 * 1024,
18
18
  maximumSourceCharacters: 128,
19
- maximumSuggestionTokenCharacters: 2048
19
+ maximumSuggestionTokenCharacters: 2048,
20
+ maximumDetailEntries: 100,
21
+ maximumOpeningHoursEntries: 20,
22
+ maximumOpeningHoursLines: 50
20
23
  });
21
24
  var boundedText = (maximum) => z.string().trim().min(1).max(maximum).refine((value) => !controlCharacterPattern.test(value), {
22
25
  message: "Control characters are not allowed"
@@ -28,8 +31,14 @@ var geocodingRetentionModes = ["temporary"];
28
31
  var geocodingBoundsSchema = z.tuple([longitude, latitude, longitude, latitude]).refine(([west, , east]) => west < east, { message: "West must be less than east" }).refine(([, south, , north]) => south < north, {
29
32
  message: "South must be less than north"
30
33
  });
34
+ var searchPlaceDetails = ["contacts", "openingHours", "timeZone", "accessPoints"];
35
+ var searchPlaceDetailSchema = z.enum(searchPlaceDetails);
36
+ var searchPlaceDetailsSchema = z.array(searchPlaceDetailSchema).min(1).max(searchPlaceDetails.length).refine((values) => new Set(values).size === values.length, {
37
+ message: "Place details must be unique"
38
+ });
31
39
  var geocodingForwardRequestSchema = z.object({
32
40
  bounds: geocodingBoundsSchema.optional(),
41
+ include: searchPlaceDetailsSchema.optional(),
33
42
  language: z.string().max(geocodingLimits.maximumLanguageCharacters).regex(languagePattern).optional(),
34
43
  limit: z.number().int().min(1).max(geocodingLimits.maximumLimit).optional(),
35
44
  proximity: geocodingPositionSchema.optional(),
@@ -39,8 +48,10 @@ var geocodingForwardRequestSchema = z.object({
39
48
  message: "Bounds and proximity are mutually exclusive",
40
49
  path: ["proximity"]
41
50
  });
51
+ var autocompleteIncludeSchema = z.tuple([z.literal("place")]);
42
52
  var autocompleteRequestSchema = z.object({
43
53
  bounds: geocodingBoundsSchema.optional(),
54
+ include: autocompleteIncludeSchema.optional(),
44
55
  language: z.string().max(geocodingLimits.maximumLanguageCharacters).regex(languagePattern).optional(),
45
56
  limit: z.number().int().min(1).max(geocodingLimits.maximumLimit).default(geocodingLimits.defaultLimit),
46
57
  proximity: geocodingPositionSchema.optional(),
@@ -51,6 +62,7 @@ var autocompleteRequestSchema = z.object({
51
62
  });
52
63
  var reverseGeocodingKindSchema = z.enum(["address", "street", "locality", "place"]);
53
64
  var geocodingReverseRequestSchema = z.object({
65
+ include: searchPlaceDetailsSchema.optional(),
54
66
  kinds: z.array(reverseGeocodingKindSchema).min(1).max(4).optional(),
55
67
  language: z.string().max(geocodingLimits.maximumLanguageCharacters).regex(languagePattern).optional(),
56
68
  limit: z.number().int().min(1).max(geocodingLimits.maximumLimit).default(geocodingLimits.defaultReverseLimit),
@@ -93,10 +105,40 @@ var searchCategorySchema = z.object({
93
105
  var searchPlaceMetadata = {
94
106
  name: searchLiteralText(200).optional(),
95
107
  categories: z.array(searchCategorySchema).min(1).max(100).optional(),
108
+ foodTypes: z.array(searchCategorySchema).min(1).max(100).optional(),
96
109
  businessChains: z.array(z.object({ id: searchLiteralText(100), name: searchLiteralText(100) }).strict()).min(1).max(100).optional()
97
110
  };
111
+ var contactListSchema = z.array(
112
+ z.object({
113
+ value: searchLiteralText(2048),
114
+ label: searchLiteralText(256).optional()
115
+ }).strict()
116
+ ).min(1).max(geocodingLimits.maximumDetailEntries);
117
+ var searchContactsSchema = z.object({
118
+ phones: contactListSchema.optional(),
119
+ websites: contactListSchema.optional(),
120
+ emails: contactListSchema.optional()
121
+ }).strict();
122
+ var searchOpeningHoursSchema = z.array(
123
+ z.object({
124
+ display: z.array(searchLiteralText(256)).min(1).max(geocodingLimits.maximumOpeningHoursLines).optional(),
125
+ openNow: z.boolean().optional()
126
+ }).strict()
127
+ ).min(1).max(geocodingLimits.maximumOpeningHoursEntries);
128
+ var searchTimeZoneSchema = z.object({
129
+ name: searchLiteralText(64),
130
+ utcOffsetSeconds: z.number().int().min(-86400).max(86400).optional()
131
+ }).strict();
132
+ var searchPlaceDetailFields = {
133
+ distanceMeters: z.number().finite().nonnegative().optional(),
134
+ contacts: searchContactsSchema.optional(),
135
+ openingHours: searchOpeningHoursSchema.optional(),
136
+ timeZone: searchTimeZoneSchema.optional(),
137
+ accessPoints: z.array(z.object({ position: geocodingPositionSchema }).strict()).min(1).max(geocodingLimits.maximumDetailEntries).optional()
138
+ };
98
139
  var geocodingResultSchema = z.object({
99
140
  ...searchPlaceMetadata,
141
+ ...searchPlaceDetailFields,
100
142
  address: geocodingAddressSchema,
101
143
  bounds: geocodingBoundsSchema.optional(),
102
144
  kind: z.enum(geocodingResultKinds),
@@ -116,18 +158,21 @@ var geocodingAttributionSchema = z.object({
116
158
  message: "Expected an HTTPS URL without credentials"
117
159
  }).optional()
118
160
  }).strict();
161
+ var placeOperationUnitsSchema = z.union([z.literal(10), z.literal(30)]);
119
162
  var geocodingForwardResponseSchema = z.object({
120
163
  attribution: z.array(geocodingAttributionSchema).min(1).max(geocodingLimits.maximumAttributionEntries),
121
164
  queryId: z.string().regex(queryIdPattern),
122
165
  results: z.array(geocodingResultSchema).max(geocodingLimits.maximumLimit),
123
166
  schemaVersion: z.literal(1),
124
167
  source: geocodingSourceSchema,
125
- usage: z.object({ units: z.literal(25) }).strict()
168
+ usage: z.object({ units: placeOperationUnitsSchema }).strict()
126
169
  }).strict();
127
170
  var geocodingReverseResponseSchema = geocodingForwardResponseSchema;
128
171
  var geocodingSuggestionSchema = z.object({
129
172
  kind: z.enum(geocodingResultKinds),
130
173
  label: boundedText(512),
174
+ addressLabel: boundedText(512).optional(),
175
+ place: geocodingResultSchema.optional(),
131
176
  token: z.string().min(1).max(geocodingLimits.maximumSuggestionTokenCharacters).regex(/^[\x21-\x7e]+$/u)
132
177
  }).strict();
133
178
  var autocompleteResponseSchema = z.object({
@@ -135,9 +180,10 @@ var autocompleteResponseSchema = z.object({
135
180
  schemaVersion: z.literal(1),
136
181
  source: geocodingSourceSchema,
137
182
  suggestions: z.array(geocodingSuggestionSchema).max(geocodingLimits.maximumLimit),
138
- usage: z.object({ units: z.literal(10) }).strict()
183
+ usage: z.object({ units: z.union([z.literal(4), z.literal(10)]) }).strict()
139
184
  }).strict();
140
185
  var resolveSuggestionRequestSchema = z.object({
186
+ include: searchPlaceDetailsSchema.optional(),
141
187
  retention: z.enum(geocodingRetentionModes).default("temporary"),
142
188
  token: z.string().min(1).max(geocodingLimits.maximumSuggestionTokenCharacters).regex(/^[\x21-\x7e]+$/u)
143
189
  }).strict();
@@ -146,7 +192,7 @@ var resolveSuggestionResponseSchema = z.object({
146
192
  result: geocodingResultSchema,
147
193
  schemaVersion: z.literal(1),
148
194
  source: geocodingSourceSchema,
149
- usage: z.object({ units: z.literal(25) }).strict()
195
+ usage: z.object({ units: placeOperationUnitsSchema }).strict()
150
196
  }).strict();
151
197
  function isCredentialFreeHttpsUrl(value) {
152
198
  try {
@@ -177,12 +223,12 @@ var nearbyRequestSchema = z.object({
177
223
  excludeCategories: categoryFilter.optional(),
178
224
  countries: z.array(z.string().regex(/^(?:[A-Z]{2}|[A-Z]{3})$/u)).min(1).max(nearbyLimits.maximumCountries).optional(),
179
225
  language: geocodingForwardRequestSchema.shape.language,
226
+ include: searchPlaceDetailsSchema.optional(),
180
227
  limit: z.number().int().min(1).max(nearbyLimits.maximumLimit).default(nearbyLimits.defaultLimit),
181
228
  cursor: nearbyCursorSchema.optional()
182
229
  }).strict();
183
230
  var nearbyPlaceSchema = geocodingResultSchema.omit({ sourceRef: true }).extend({
184
231
  kind: z.literal("place"),
185
- distanceMeters: z.number().finite().nonnegative().optional(),
186
232
  token: geocodingSuggestionSchema.shape.token.optional()
187
233
  }).strict();
188
234
  var nearbyResponseSchema = z.object({
@@ -192,7 +238,7 @@ var nearbyResponseSchema = z.object({
192
238
  nextCursor: nearbyCursorSchema.optional(),
193
239
  source: geocodingSourceSchema,
194
240
  attribution: geocodingForwardResponseSchema.shape.attribution,
195
- usage: z.object({ units: z.literal(25) }).strict()
241
+ usage: z.object({ units: placeOperationUnitsSchema }).strict()
196
242
  }).strict().refine(
197
243
  (value) => new TextEncoder().encode(JSON.stringify(value)).byteLength <= nearbyLimits.maximumResponseBytes,
198
244
  { message: "Nearby response exceeds the encoded byte limit" }
@@ -207,13 +253,23 @@ var searchCategoriesResponseSchema = z.object({
207
253
  }).strict();
208
254
  var resolvePlaceRequestSchema = resolveSuggestionRequestSchema;
209
255
  var resolvePlaceResponseSchema = resolveSuggestionResponseSchema;
256
+ var searchContactDetails = ["contacts", "openingHours"];
257
+ function searchOperationUnits(operation, include) {
258
+ if (operation === "categories") return 0;
259
+ if (operation === "autocomplete") return include?.length ? 10 : 4;
260
+ return include?.length ? 30 : 10;
261
+ }
210
262
 
211
263
  export {
212
264
  geocodingLimits,
213
265
  geocodingPositionSchema,
214
266
  geocodingRetentionModes,
215
267
  geocodingBoundsSchema,
268
+ searchPlaceDetails,
269
+ searchPlaceDetailSchema,
270
+ searchPlaceDetailsSchema,
216
271
  geocodingForwardRequestSchema,
272
+ autocompleteIncludeSchema,
217
273
  autocompleteRequestSchema,
218
274
  reverseGeocodingKindSchema,
219
275
  geocodingReverseRequestSchema,
@@ -223,6 +279,10 @@ export {
223
279
  searchCategoryIdSchema,
224
280
  searchCategorySchema,
225
281
  searchPlaceMetadata,
282
+ searchContactsSchema,
283
+ searchOpeningHoursSchema,
284
+ searchTimeZoneSchema,
285
+ searchPlaceDetailFields,
226
286
  geocodingResultSchema,
227
287
  geocodingSourceSchema,
228
288
  geocodingAttributionSchema,
@@ -239,5 +299,7 @@ export {
239
299
  nearbyResponseSchema,
240
300
  searchCategoriesResponseSchema,
241
301
  resolvePlaceRequestSchema,
242
- resolvePlaceResponseSchema
302
+ resolvePlaceResponseSchema,
303
+ searchContactDetails,
304
+ searchOperationUnits
243
305
  };
package/dist/client.d.ts CHANGED
@@ -1,11 +1,19 @@
1
1
  import { AutocompleteRequest, AutocompleteResponse, GeocodingForwardRequest, GeocodingForwardResponse, GeocodingReverseRequest, GeocodingReverseResponse, SearchCategoriesResponse, ResolveSuggestionRequest, ResolveSuggestionResponse, NearbyRequest, NearbyResponse } from './contract.js';
2
2
  import 'zod';
3
3
 
4
- declare const GEOCODING_ERROR_CODES: readonly ["GEOCODING_FILTER_UNSUPPORTED", "GEOCODING_PLAN_UNAVAILABLE", "GEOCODING_EVALUATION_REQUIRED", "GEOCODING_EVALUATION_EXPIRED", "GEOCODING_EVALUATION_EXHAUSTED", "GEOCODING_INVALID_CURSOR", "GEOCODING_CURSOR_EXPIRED", "GEOCODING_ABORTED", "GEOCODING_DISABLED", "GEOCODING_INVALID_REQUEST", "GEOCODING_INVALID_SUGGESTION", "GEOCODING_PICK_LIMIT_EXCEEDED", "GEOCODING_QUOTA_EXCEEDED", "GEOCODING_REQUEST_TOO_LARGE", "GEOCODING_SUGGESTION_EXPIRED", "GEOCODING_UNAVAILABLE", "GEOCODING_UPSTREAM_INVALID", "GEOCODING_UPSTREAM_THROTTLED", "GEOCODING_UPSTREAM_TIMEOUT", "GEOCODING_UPSTREAM_UNAVAILABLE", "GEOCODING_USAGE_UNCONFIRMED"];
4
+ declare const GEOCODING_ERROR_CODES: readonly ["GEOCODING_FILTER_UNSUPPORTED", "GEOCODING_PLAN_UNAVAILABLE", "GEOCODING_EVALUATION_REQUIRED", "GEOCODING_EVALUATION_EXPIRED", "GEOCODING_EVALUATION_EXHAUSTED", "GEOCODING_INVALID_CURSOR", "GEOCODING_CURSOR_EXPIRED", "GEOCODING_ABORTED", "GEOCODING_DISABLED", "GEOCODING_INVALID_REQUEST", "GEOCODING_INVALID_SUGGESTION", "GEOCODING_ORIGIN_NOT_ALLOWED", "GEOCODING_PICK_LIMIT_EXCEEDED", "GEOCODING_QUOTA_EXCEEDED", "GEOCODING_REQUEST_TOO_LARGE", "GEOCODING_SPENDING_LIMIT_REACHED", "GEOCODING_CREDENTIAL_QUOTA_EXCEEDED", "GEOCODING_SUGGESTION_EXPIRED", "GEOCODING_SUSPENDED", "GEOCODING_UNAVAILABLE", "GEOCODING_UNPAID_BUDGET_EXHAUSTED", "GEOCODING_UNPAID_LIMIT_REACHED", "GEOCODING_UPSTREAM_INVALID", "GEOCODING_UPSTREAM_THROTTLED", "GEOCODING_UPSTREAM_TIMEOUT", "GEOCODING_UPSTREAM_UNAVAILABLE", "GEOCODING_USAGE_UNCONFIRMED"];
5
5
  type GeocodingErrorCode = (typeof GEOCODING_ERROR_CODES)[number];
6
6
  type GeocodeOptions = Readonly<{
7
7
  apiKey: string;
8
8
  apiUrl?: string;
9
+ /**
10
+ * The native app a publishable credential admits: its iOS bundle ID, or its Android package name
11
+ * with the SHA-256 fingerprint of its signing certificate. Browsers send their origin instead.
12
+ */
13
+ app?: Readonly<{
14
+ id: string;
15
+ certificateSha256?: string;
16
+ }>;
9
17
  fetch?: typeof fetch;
10
18
  signal?: AbortSignal;
11
19
  }>;
package/dist/client.js CHANGED
@@ -8,8 +8,8 @@ import {
8
8
  resolvePlace,
9
9
  resolveSuggestion,
10
10
  searchNearby
11
- } from "./chunk-FEWG4I4E.js";
12
- import "./chunk-R7KE2IN7.js";
11
+ } from "./chunk-7PHOZFMS.js";
12
+ import "./chunk-W7IIJLOI.js";
13
13
  export {
14
14
  GEOCODING_ERROR_CODES,
15
15
  GeocodingError,