@unmap/sdk 0.10.0 → 0.13.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/README.md CHANGED
@@ -26,6 +26,21 @@ await unmap.router.route([-114.07, 51.05], [-113.99, 51.05], { mode: 'bicycle' }
26
26
  await unmap.router.isochrone([-114.07, 51.05], { minutes: [10, 20] })
27
27
  ```
28
28
 
29
+ ## Without a build step
30
+
31
+ The same SDK loads from a script tag, with MapLibre already inside it:
32
+
33
+ ```html
34
+ <link rel="stylesheet" href="https://cdn.unmap.dev/sdk/0.13.0/unmap.css">
35
+ <div id="map" style="height: 400px"></div>
36
+ <script src="https://cdn.unmap.dev/sdk/0.13.0/unmap.js"></script>
37
+ <script>
38
+ unmap.createMap({ key: 'um_live_...', container: '#map', center: [-114.07, 51.05] })
39
+ </script>
40
+ ```
41
+
42
+ Everything above is on the `unmap` global: `unmap.Unmap`, `unmap.Geocoder`, `unmap.Router`, `unmap.maplibregl`. Pin the exact version in production; `/sdk/0.13/` follows patches and `/sdk/latest/` follows everything.
43
+
29
44
  ## Options
30
45
 
31
46
  | Option | Type | Notes |
package/dist/index.d.ts CHANGED
@@ -1,13 +1,196 @@
1
- import { Map, UnmapMapOptions, UnmapControlsOptions } from '@unmap/maps';
2
- export { DEMO_CITIES, DemoCity, Map, Projection, STYLE_NAMES, StyleInput, StyleName, UnmapControlsOptions, UnmapMapOptions, createMap, maplibregl, unmapStyle } from '@unmap/maps';
3
- import { Geocoder } from '@unmap/geocoding';
1
+ import { Map, MapProfile, UnmapMapOptions, UnmapControlsOptions } from '@unmap/maps';
2
+ export { DEMO_CITIES, DemoCity, Map, MapProfile, Projection, STYLE_NAMES, StyleInput, StyleName, UnmapControlsOptions, UnmapMapOptions, createMap, maplibregl, unmapStyle } from '@unmap/maps';
3
+ import { Geocoder, Places, QueryOptions, QueryResponse, IdentifyOptions, IdentifyResponse } from '@unmap/geocoding';
4
4
  export * from '@unmap/geocoding';
5
5
  import { Router } from '@unmap/routing';
6
6
  export * from '@unmap/routing';
7
+ import { Elevation } from '@unmap/elevation';
8
+ export * from '@unmap/elevation';
9
+
10
+ /**
11
+ * Typed clients for road events (`/incidents`, `/closures`, `/road-conditions`) and for data
12
+ * correction (`POST /feedback`).
13
+ *
14
+ * These live in `@unmap/sdk` rather than in packages of their own. A twelfth and thirteenth npm
15
+ * package would each need an entry in the changesets fixed group and a trusted publisher
16
+ * configured on npmjs.com, which is founder work, and neither has a framework wrapper the way
17
+ * geocoding and routing do. If a customer ever wants roads without the rest of the SDK, that is
18
+ * the moment to split it out.
19
+ */
20
+ type RoadEventType = "closure" | "incident" | "construction" | "weather" | "restriction" | "ferry" | "hazard" | "special_event" | "unknown";
21
+ type RoadCondition = "dry" | "wet" | "snow" | "ice" | "packed_snow" | "reduced_visibility" | "travel_not_recommended" | "closed" | "unknown";
22
+ type RoadSeverity = "minor" | "major" | "unknown";
23
+ interface AffectedRoad {
24
+ name: string;
25
+ direction: "north" | "south" | "east" | "west" | "both" | "unknown";
26
+ /** True only when the publisher says closed. Never inferred from the description. */
27
+ closed: boolean;
28
+ from?: string;
29
+ to?: string;
30
+ condition: RoadCondition;
31
+ }
32
+ interface RoadEvent {
33
+ /** `um:road:<jurisdiction>:<publisher id>`. */
34
+ id: string;
35
+ /** The publisher's own id, so a report can be taken back to them. */
36
+ source_id: string;
37
+ /** ISO 3166-2, e.g. `CA-BC`. */
38
+ jurisdiction: string;
39
+ type: RoadEventType;
40
+ /** The publisher's own subtype strings, untranslated. */
41
+ subtypes: string[];
42
+ status: "active" | "planned" | "archived";
43
+ severity: RoadSeverity;
44
+ /**
45
+ * The publisher's description, verbatim.
46
+ *
47
+ * Believe this over `type` when they disagree: our categories are a lossy projection of someone
48
+ * else's taxonomy, and the sentence a driver needs is in the prose.
49
+ */
50
+ description: string;
51
+ headline?: string;
52
+ /** The worst surface condition across this event's roads. */
53
+ condition: RoadCondition;
54
+ roads: AffectedRoad[];
55
+ /** GeoJSON Point or LineString, WGS84. */
56
+ geometry: {
57
+ type: string;
58
+ coordinates: unknown;
59
+ } | null;
60
+ starts_at: string | null;
61
+ ends_at: string | null;
62
+ source_updated_at: string | null;
63
+ unmap_updated_at: string;
64
+ source: string;
65
+ }
66
+ interface RoadEventsResponse {
67
+ data: RoadEvent[];
68
+ meta: {
69
+ status: "current" | "stale" | "partial" | "unknown";
70
+ attribution: string;
71
+ unmap_updated_at: string;
72
+ /**
73
+ * Jurisdictions that could not be reached, each with a reason.
74
+ *
75
+ * **Check this.** On `/closures` an empty `data` reads as "nothing is shut" to someone
76
+ * deciding whether to drive, and that is a very different answer from "we could not reach
77
+ * British Columbia".
78
+ */
79
+ unavailable?: {
80
+ jurisdiction: string;
81
+ reason: string;
82
+ }[];
83
+ };
84
+ }
85
+ interface RoadEventQuery {
86
+ /** Comma-separated ISO 3166-2 codes, or an array. An uncovered code is a 400, not an empty list. */
87
+ jurisdiction?: string | readonly string[];
88
+ bbox?: [number, number, number, number];
89
+ near?: [number, number];
90
+ /** Metres from `near`. Default 25000, max 500000. */
91
+ radius?: number;
92
+ type?: RoadEventType | readonly RoadEventType[];
93
+ severity?: RoadSeverity | readonly RoadSeverity[];
94
+ status?: "active" | "planned" | "archived";
95
+ /** ISO 8601. Only events the publisher changed at or after this time. */
96
+ updated_since?: string;
97
+ /** 1 to 500, default 100. Applied AFTER the endpoint's own narrowing. */
98
+ limit?: number;
99
+ signal?: AbortSignal;
100
+ }
101
+ interface RoadsOptions {
102
+ key: string;
103
+ gateway?: string;
104
+ fetch?: typeof fetch;
105
+ }
106
+ declare class RoadsError extends Error {
107
+ readonly status: number;
108
+ readonly code?: string | undefined;
109
+ constructor(status: number, message: string, code?: string | undefined);
110
+ }
111
+ /**
112
+ * Canadian road events.
113
+ *
114
+ * const { data, meta } = await unmap.roads.closures({ jurisdiction: "CA-BC" });
115
+ *
116
+ * A 503 means every requested jurisdiction was unreachable; the body still carries
117
+ * `meta.unavailable`, so the thrown error is the exception rather than the information. Prefer
118
+ * reading `meta.unavailable` on a 200 over treating an empty list as good news.
119
+ */
120
+ declare class Roads {
121
+ private readonly key;
122
+ private readonly base;
123
+ private readonly fetchImpl;
124
+ constructor(opts: RoadsOptions);
125
+ /** Every current road event. */
126
+ incidents(query?: RoadEventQuery): Promise<RoadEventsResponse>;
127
+ /** The subset where a road is actually shut. Narrowed before `limit` is applied. */
128
+ closures(query?: RoadEventQuery): Promise<RoadEventsResponse>;
129
+ /**
130
+ * The subset carrying a surface condition.
131
+ *
132
+ * Thin for British Columbia by design: its feed reports lane availability rather than the
133
+ * surface, so unmap answers `unknown` instead of inferring a dry road from open lanes.
134
+ */
135
+ conditions(query?: RoadEventQuery): Promise<RoadEventsResponse>;
136
+ private request;
137
+ }
138
+ type FeedbackProduct = "tiles" | "terrain" | "names" | "geocoding" | "places" | "routing" | "layers" | "transit" | "roads" | "ev";
139
+ type FeedbackCategory = "incorrect_location" | "incorrect_address" | "missing_place" | "wrong_place_name" | "wrong_category" | "bad_route" | "missing_road_closure" | "incorrect_road_condition" | "incorrect_transit_result" | "other";
140
+ interface FeedbackReport {
141
+ product: FeedbackProduct;
142
+ category: FeedbackCategory;
143
+ /** Up to 2000 characters, stored exactly as written. One of this or `resultId` is required. */
144
+ description?: string;
145
+ /** The unmap id of the result being reported. */
146
+ resultId?: string;
147
+ /**
148
+ * The request being reported.
149
+ *
150
+ * **Only send this with the reporter's explicit agreement.** The gateway refuses it without
151
+ * `include_context: true`, which this sets whenever `context` is present, and that flag is
152
+ * meant to record a person's choice rather than a client default. unmap redacts query strings
153
+ * from its own logs; a client that attached them here would undo that.
154
+ */
155
+ context?: unknown;
156
+ signal?: AbortSignal;
157
+ }
158
+ interface FeedbackReceipt {
159
+ /** Quote this in a follow-up. */
160
+ id: string;
161
+ status: string;
162
+ created_at: string;
163
+ /** Whether a request context was actually kept. `false` when none was offered. */
164
+ context_stored: boolean;
165
+ }
166
+ /**
167
+ * Report wrong data.
168
+ *
169
+ * await unmap.feedback.submit({
170
+ * product: "geocoding",
171
+ * category: "incorrect_location",
172
+ * description: "17 Ave SW returns a street in Edmonton.",
173
+ * });
174
+ *
175
+ * Authenticated like everything else and **never metered**: telling us our data is wrong does not
176
+ * cost a call. At most 200 reports per account per day.
177
+ */
178
+ declare class Feedback {
179
+ private readonly key;
180
+ private readonly base;
181
+ private readonly fetchImpl;
182
+ constructor(opts: RoadsOptions);
183
+ submit(report: FeedbackReport): Promise<FeedbackReceipt>;
184
+ /** A report about one place. `product` and `result_id` come from the path. */
185
+ aboutPlace(placeId: string, report: Omit<FeedbackReport, "product" | "resultId">): Promise<FeedbackReceipt>;
186
+ private post;
187
+ }
7
188
 
8
189
  interface UnmapOptions {
9
190
  key: string;
10
191
  gateway?: string;
192
+ /** Override the fetch implementation (tests, non-global environments). */
193
+ fetch?: typeof fetch;
11
194
  /** If provided, a map is mounted in this container. */
12
195
  container?: string | HTMLElement;
13
196
  style?: string;
@@ -18,6 +201,10 @@ interface UnmapOptions {
18
201
  lang?: string;
19
202
  /** Compact `ut1.` theme code from unmap.dev/create. Wins over the path style on the gateway. */
20
203
  theme?: string;
204
+ /** Catalog layer ids composed onto the style (`municipalities`, `survey.dls`, …). */
205
+ overlays?: readonly string[];
206
+ /** Composition preset. Not a style. Explicit overlays replace the profile list. */
207
+ profile?: MapProfile;
21
208
  /** `"globe"` or `"mercator"`. See `@unmap/maps` `UnmapMapOptions.projection`. */
22
209
  projection?: UnmapMapOptions["projection"];
23
210
  center?: [number, number];
@@ -27,15 +214,27 @@ interface UnmapOptions {
27
214
  }
28
215
  /**
29
216
  * One Stripe-style key, the whole platform. With a `container` you get a rendered map;
30
- * `geocoder` and `router` are always available.
217
+ * `geocoder`, `router` and `elevation` are always available.
31
218
  *
32
219
  * const map = new Unmap({ key: "um_live_...", container: "#map", center: [-75.69, 45.42] });
33
220
  */
34
221
  declare class Unmap {
35
222
  readonly geocoder: Geocoder;
36
223
  readonly router: Router;
224
+ readonly elevation: Elevation;
225
+ /** Canadian points of interest. Same corpus as the geocoder, in the Place shape. */
226
+ readonly places: Places;
227
+ /** Closures, incidents, construction and road conditions. */
228
+ readonly roads: Roads;
229
+ /** Report wrong data. Never metered. */
230
+ readonly feedback: Feedback;
231
+ readonly data: {
232
+ query(opts: QueryOptions): Promise<QueryResponse>;
233
+ };
37
234
  readonly map?: Map;
38
235
  constructor(opts: UnmapOptions);
236
+ /** Which catalog features contain a point. See `Geocoder.identify`. */
237
+ identify(opts: IdentifyOptions): Promise<IdentifyResponse>;
39
238
  }
40
239
 
41
- export { Unmap, type UnmapOptions };
240
+ export { type AffectedRoad, Feedback, type FeedbackCategory, type FeedbackProduct, type FeedbackReceipt, type FeedbackReport, type RoadCondition, type RoadEvent, type RoadEventQuery, type RoadEventType, type RoadEventsResponse, type RoadSeverity, Roads, RoadsError, type RoadsOptions, Unmap, type UnmapOptions };
package/dist/index.js CHANGED
@@ -2,25 +2,163 @@
2
2
  import {
3
3
  createMap
4
4
  } from "@unmap/maps";
5
- import { Geocoder } from "@unmap/geocoding";
5
+ import { Geocoder, Places } from "@unmap/geocoding";
6
+
7
+ // src/roads.ts
8
+ import { DEFAULT_GATEWAY } from "@unmap/core";
9
+ var RoadsError = class extends Error {
10
+ constructor(status, message, code) {
11
+ super(message);
12
+ this.status = status;
13
+ this.code = code;
14
+ this.name = "RoadsError";
15
+ }
16
+ status;
17
+ code;
18
+ };
19
+ function list(value) {
20
+ if (value === void 0) return void 0;
21
+ return Array.isArray(value) ? value.join(",") : String(value);
22
+ }
23
+ async function errorFrom(res, path, name) {
24
+ const fallback = `${name} ${res.status} on ${path}`;
25
+ let body;
26
+ try {
27
+ body = await res.json();
28
+ } catch {
29
+ body = void 0;
30
+ }
31
+ const reason = typeof body?.error === "string" ? body.error : void 0;
32
+ const code = typeof body?.code === "string" ? body.code : void 0;
33
+ return new RoadsError(res.status, reason ? `${reason} (${fallback})` : fallback, code);
34
+ }
35
+ var Roads = class {
36
+ key;
37
+ base;
38
+ fetchImpl;
39
+ constructor(opts) {
40
+ this.key = opts.key;
41
+ this.base = (opts.gateway ?? DEFAULT_GATEWAY).replace(/\/+$/, "");
42
+ this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
43
+ }
44
+ /** Every current road event. */
45
+ incidents(query = {}) {
46
+ return this.request("/incidents", query);
47
+ }
48
+ /** The subset where a road is actually shut. Narrowed before `limit` is applied. */
49
+ closures(query = {}) {
50
+ return this.request("/closures", query);
51
+ }
52
+ /**
53
+ * The subset carrying a surface condition.
54
+ *
55
+ * Thin for British Columbia by design: its feed reports lane availability rather than the
56
+ * surface, so unmap answers `unknown` instead of inferring a dry road from open lanes.
57
+ */
58
+ conditions(query = {}) {
59
+ return this.request("/road-conditions", query);
60
+ }
61
+ async request(path, query) {
62
+ const q = new URLSearchParams({ key: this.key });
63
+ const params = {
64
+ jurisdiction: list(query.jurisdiction),
65
+ bbox: query.bbox?.join(","),
66
+ near: query.near?.join(","),
67
+ radius: query.radius != null ? String(query.radius) : void 0,
68
+ type: list(query.type),
69
+ severity: list(query.severity),
70
+ status: query.status,
71
+ updated_since: query.updated_since,
72
+ limit: query.limit != null ? String(query.limit) : void 0
73
+ };
74
+ for (const [k, v] of Object.entries(params)) if (v != null && v !== "") q.set(k, v);
75
+ const res = await this.fetchImpl(`${this.base}${path}?${q}`, {
76
+ signal: query.signal,
77
+ headers: { accept: "application/json" }
78
+ });
79
+ if (!res.ok) throw await errorFrom(res, path, "roads");
80
+ return await res.json();
81
+ }
82
+ };
83
+ var Feedback = class {
84
+ key;
85
+ base;
86
+ fetchImpl;
87
+ constructor(opts) {
88
+ this.key = opts.key;
89
+ this.base = (opts.gateway ?? DEFAULT_GATEWAY).replace(/\/+$/, "");
90
+ this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
91
+ }
92
+ submit(report) {
93
+ return this.post("/feedback", {
94
+ product: report.product,
95
+ category: report.category,
96
+ ...report.description ? { description: report.description } : {},
97
+ ...report.resultId ? { result_id: report.resultId } : {},
98
+ ...report.context !== void 0 ? { include_context: true, context: report.context } : {}
99
+ }, report.signal);
100
+ }
101
+ /** A report about one place. `product` and `result_id` come from the path. */
102
+ aboutPlace(placeId, report) {
103
+ return this.post(`/places/${encodeURIComponent(placeId)}/feedback`, {
104
+ category: report.category,
105
+ ...report.description ? { description: report.description } : {},
106
+ ...report.context !== void 0 ? { include_context: true, context: report.context } : {}
107
+ }, report.signal);
108
+ }
109
+ async post(path, body, signal) {
110
+ const res = await this.fetchImpl(`${this.base}${path}`, {
111
+ method: "POST",
112
+ signal,
113
+ headers: { "content-type": "application/json", authorization: `Bearer ${this.key}` },
114
+ body: JSON.stringify(body)
115
+ });
116
+ if (!res.ok) throw await errorFrom(res, path, "feedback");
117
+ return await res.json();
118
+ }
119
+ };
120
+
121
+ // src/index.ts
6
122
  import { Router } from "@unmap/routing";
123
+ import { Elevation } from "@unmap/elevation";
7
124
  export * from "@unmap/geocoding";
8
125
  export * from "@unmap/routing";
126
+ export * from "@unmap/elevation";
9
127
  import { createMap as createMap2, unmapStyle, DEMO_CITIES, STYLE_NAMES, maplibregl } from "@unmap/maps";
10
128
  var Unmap = class {
11
129
  geocoder;
12
130
  router;
131
+ elevation;
132
+ /** Canadian points of interest. Same corpus as the geocoder, in the Place shape. */
133
+ places;
134
+ /** Closures, incidents, construction and road conditions. */
135
+ roads;
136
+ /** Report wrong data. Never metered. */
137
+ feedback;
138
+ data;
13
139
  map;
14
140
  constructor(opts) {
15
141
  this.geocoder = new Geocoder(opts);
16
142
  this.router = new Router(opts);
143
+ this.elevation = new Elevation(opts);
144
+ this.places = new Places(opts);
145
+ this.roads = new Roads(opts);
146
+ this.feedback = new Feedback(opts);
147
+ this.data = { query: (queryOpts) => this.geocoder.queryLayer(queryOpts) };
17
148
  if (opts.container) {
18
149
  this.map = createMap({ ...opts, container: opts.container });
19
150
  }
20
151
  }
152
+ /** Which catalog features contain a point. See `Geocoder.identify`. */
153
+ identify(opts) {
154
+ return this.geocoder.identify(opts);
155
+ }
21
156
  };
22
157
  export {
23
158
  DEMO_CITIES,
159
+ Feedback,
160
+ Roads,
161
+ RoadsError,
24
162
  STYLE_NAMES,
25
163
  Unmap,
26
164
  createMap2 as createMap,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n createMap,\n type Map as MaplibreMap,\n type UnmapControlsOptions,\n type UnmapMapOptions,\n} from \"@unmap/maps\";\nimport { Geocoder } from \"@unmap/geocoding\";\nimport { Router } from \"@unmap/routing\";\n\nexport * from \"@unmap/geocoding\";\nexport * from \"@unmap/routing\";\nexport { createMap, unmapStyle, DEMO_CITIES, STYLE_NAMES, maplibregl } from \"@unmap/maps\";\nexport type {\n Map,\n UnmapMapOptions,\n UnmapControlsOptions,\n DemoCity,\n StyleName,\n StyleInput,\n Projection,\n} from \"@unmap/maps\";\n\nexport interface UnmapOptions {\n key: string;\n gateway?: string;\n /** If provided, a map is mounted in this container. */\n container?: string | HTMLElement;\n style?: string;\n flavor?: string;\n /** \"light\" or \"dark\". Selects the mode half of the named style. */\n mode?: string;\n /** Label language, e.g. \"fr\". Omit to follow the request's Accept-Language. */\n lang?: string;\n /** Compact `ut1.` theme code from unmap.dev/create. Wins over the path style on the gateway. */\n theme?: string;\n /** `\"globe\"` or `\"mercator\"`. See `@unmap/maps` `UnmapMapOptions.projection`. */\n projection?: UnmapMapOptions[\"projection\"];\n center?: [number, number];\n zoom?: number;\n /** On-map controls. `true` (the default) adds zoom and compass buttons; `false` adds none. */\n controls?: boolean | UnmapControlsOptions;\n}\n\n/**\n * One Stripe-style key, the whole platform. With a `container` you get a rendered map;\n * `geocoder` and `router` are always available.\n *\n * const map = new Unmap({ key: \"um_live_...\", container: \"#map\", center: [-75.69, 45.42] });\n */\nexport class Unmap {\n readonly geocoder: Geocoder;\n readonly router: Router;\n readonly map?: MaplibreMap;\n\n constructor(opts: UnmapOptions) {\n this.geocoder = new Geocoder(opts);\n this.router = new Router(opts);\n if (opts.container) {\n this.map = createMap({ ...opts, container: opts.container } as UnmapMapOptions);\n }\n }\n}\n"],"mappings":";AAAA;AAAA,EACE;AAAA,OAIK;AACP,SAAS,gBAAgB;AACzB,SAAS,cAAc;AAEvB,cAAc;AACd,cAAc;AACd,SAAS,aAAAA,YAAW,YAAY,aAAa,aAAa,kBAAkB;AAsCrE,IAAM,QAAN,MAAY;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,MAAoB;AAC9B,SAAK,WAAW,IAAI,SAAS,IAAI;AACjC,SAAK,SAAS,IAAI,OAAO,IAAI;AAC7B,QAAI,KAAK,WAAW;AAClB,WAAK,MAAM,UAAU,EAAE,GAAG,MAAM,WAAW,KAAK,UAAU,CAAoB;AAAA,IAChF;AAAA,EACF;AACF;","names":["createMap"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/roads.ts"],"sourcesContent":["import {\n createMap,\n type Map as MaplibreMap,\n type MapProfile,\n type UnmapControlsOptions,\n type UnmapMapOptions,\n} from \"@unmap/maps\";\nimport { Geocoder, Places, type IdentifyOptions, type IdentifyResponse, type QueryOptions, type QueryResponse } from \"@unmap/geocoding\";\nimport { Feedback, Roads } from \"./roads\";\nimport { Router } from \"@unmap/routing\";\nimport { Elevation } from \"@unmap/elevation\";\n\nexport * from \"@unmap/geocoding\";\nexport * from \"@unmap/routing\";\nexport * from \"@unmap/elevation\";\nexport * from \"./roads\";\nexport { createMap, unmapStyle, DEMO_CITIES, STYLE_NAMES, maplibregl } from \"@unmap/maps\";\nexport type {\n Map,\n UnmapMapOptions,\n UnmapControlsOptions,\n DemoCity,\n StyleName,\n StyleInput,\n Projection,\n MapProfile,\n} from \"@unmap/maps\";\n\nexport interface UnmapOptions {\n key: string;\n gateway?: string;\n /** Override the fetch implementation (tests, non-global environments). */\n fetch?: typeof fetch;\n /** If provided, a map is mounted in this container. */\n container?: string | HTMLElement;\n style?: string;\n flavor?: string;\n /** \"light\" or \"dark\". Selects the mode half of the named style. */\n mode?: string;\n /** Label language, e.g. \"fr\". Omit to follow the request's Accept-Language. */\n lang?: string;\n /** Compact `ut1.` theme code from unmap.dev/create. Wins over the path style on the gateway. */\n theme?: string;\n /** Catalog layer ids composed onto the style (`municipalities`, `survey.dls`, …). */\n overlays?: readonly string[];\n /** Composition preset. Not a style. Explicit overlays replace the profile list. */\n profile?: MapProfile;\n /** `\"globe\"` or `\"mercator\"`. See `@unmap/maps` `UnmapMapOptions.projection`. */\n projection?: UnmapMapOptions[\"projection\"];\n center?: [number, number];\n zoom?: number;\n /** On-map controls. `true` (the default) adds zoom and compass buttons; `false` adds none. */\n controls?: boolean | UnmapControlsOptions;\n}\n\n/**\n * One Stripe-style key, the whole platform. With a `container` you get a rendered map;\n * `geocoder`, `router` and `elevation` are always available.\n *\n * const map = new Unmap({ key: \"um_live_...\", container: \"#map\", center: [-75.69, 45.42] });\n */\nexport class Unmap {\n readonly geocoder: Geocoder;\n readonly router: Router;\n readonly elevation: Elevation;\n /** Canadian points of interest. Same corpus as the geocoder, in the Place shape. */\n readonly places: Places;\n /** Closures, incidents, construction and road conditions. */\n readonly roads: Roads;\n /** Report wrong data. Never metered. */\n readonly feedback: Feedback;\n readonly data: { query(opts: QueryOptions): Promise<QueryResponse> };\n readonly map?: MaplibreMap;\n\n constructor(opts: UnmapOptions) {\n this.geocoder = new Geocoder(opts);\n this.router = new Router(opts);\n this.elevation = new Elevation(opts);\n this.places = new Places(opts);\n this.roads = new Roads(opts);\n this.feedback = new Feedback(opts);\n this.data = { query: (queryOpts) => this.geocoder.queryLayer(queryOpts) };\n if (opts.container) {\n this.map = createMap({ ...opts, container: opts.container } as UnmapMapOptions);\n }\n }\n\n /** Which catalog features contain a point. See `Geocoder.identify`. */\n identify(opts: IdentifyOptions): Promise<IdentifyResponse> {\n return this.geocoder.identify(opts);\n }\n}\n","import { DEFAULT_GATEWAY } from \"@unmap/core\";\n\n/**\n * Typed clients for road events (`/incidents`, `/closures`, `/road-conditions`) and for data\n * correction (`POST /feedback`).\n *\n * These live in `@unmap/sdk` rather than in packages of their own. A twelfth and thirteenth npm\n * package would each need an entry in the changesets fixed group and a trusted publisher\n * configured on npmjs.com, which is founder work, and neither has a framework wrapper the way\n * geocoding and routing do. If a customer ever wants roads without the rest of the SDK, that is\n * the moment to split it out.\n */\n\nexport type RoadEventType =\n | \"closure\"\n | \"incident\"\n | \"construction\"\n | \"weather\"\n | \"restriction\"\n | \"ferry\"\n | \"hazard\"\n | \"special_event\"\n | \"unknown\";\n\nexport type RoadCondition =\n | \"dry\"\n | \"wet\"\n | \"snow\"\n | \"ice\"\n | \"packed_snow\"\n | \"reduced_visibility\"\n | \"travel_not_recommended\"\n | \"closed\"\n | \"unknown\";\n\nexport type RoadSeverity = \"minor\" | \"major\" | \"unknown\";\n\nexport interface AffectedRoad {\n name: string;\n direction: \"north\" | \"south\" | \"east\" | \"west\" | \"both\" | \"unknown\";\n /** True only when the publisher says closed. Never inferred from the description. */\n closed: boolean;\n from?: string;\n to?: string;\n condition: RoadCondition;\n}\n\nexport interface RoadEvent {\n /** `um:road:<jurisdiction>:<publisher id>`. */\n id: string;\n /** The publisher's own id, so a report can be taken back to them. */\n source_id: string;\n /** ISO 3166-2, e.g. `CA-BC`. */\n jurisdiction: string;\n type: RoadEventType;\n /** The publisher's own subtype strings, untranslated. */\n subtypes: string[];\n status: \"active\" | \"planned\" | \"archived\";\n severity: RoadSeverity;\n /**\n * The publisher's description, verbatim.\n *\n * Believe this over `type` when they disagree: our categories are a lossy projection of someone\n * else's taxonomy, and the sentence a driver needs is in the prose.\n */\n description: string;\n headline?: string;\n /** The worst surface condition across this event's roads. */\n condition: RoadCondition;\n roads: AffectedRoad[];\n /** GeoJSON Point or LineString, WGS84. */\n geometry: { type: string; coordinates: unknown } | null;\n starts_at: string | null;\n ends_at: string | null;\n source_updated_at: string | null;\n unmap_updated_at: string;\n source: string;\n}\n\nexport interface RoadEventsResponse {\n data: RoadEvent[];\n meta: {\n status: \"current\" | \"stale\" | \"partial\" | \"unknown\";\n attribution: string;\n unmap_updated_at: string;\n /**\n * Jurisdictions that could not be reached, each with a reason.\n *\n * **Check this.** On `/closures` an empty `data` reads as \"nothing is shut\" to someone\n * deciding whether to drive, and that is a very different answer from \"we could not reach\n * British Columbia\".\n */\n unavailable?: { jurisdiction: string; reason: string }[];\n };\n}\n\nexport interface RoadEventQuery {\n /** Comma-separated ISO 3166-2 codes, or an array. An uncovered code is a 400, not an empty list. */\n jurisdiction?: string | readonly string[];\n bbox?: [number, number, number, number];\n near?: [number, number];\n /** Metres from `near`. Default 25000, max 500000. */\n radius?: number;\n type?: RoadEventType | readonly RoadEventType[];\n severity?: RoadSeverity | readonly RoadSeverity[];\n status?: \"active\" | \"planned\" | \"archived\";\n /** ISO 8601. Only events the publisher changed at or after this time. */\n updated_since?: string;\n /** 1 to 500, default 100. Applied AFTER the endpoint's own narrowing. */\n limit?: number;\n signal?: AbortSignal;\n}\n\nexport interface RoadsOptions {\n key: string;\n gateway?: string;\n fetch?: typeof fetch;\n}\n\nexport class RoadsError extends Error {\n constructor(\n public readonly status: number,\n message: string,\n public readonly code?: string,\n ) {\n super(message);\n this.name = \"RoadsError\";\n }\n}\n\nfunction list(value: string | readonly string[] | undefined): string | undefined {\n if (value === undefined) return undefined;\n return Array.isArray(value) ? value.join(\",\") : String(value);\n}\n\nasync function errorFrom(res: Response, path: string, name: string): Promise<RoadsError> {\n const fallback = `${name} ${res.status} on ${path}`;\n let body: { error?: unknown; code?: unknown } | undefined;\n try {\n body = (await res.json()) as { error?: unknown; code?: unknown };\n } catch {\n body = undefined;\n }\n const reason = typeof body?.error === \"string\" ? body.error : undefined;\n const code = typeof body?.code === \"string\" ? body.code : undefined;\n return new RoadsError(res.status, reason ? `${reason} (${fallback})` : fallback, code);\n}\n\n/**\n * Canadian road events.\n *\n * const { data, meta } = await unmap.roads.closures({ jurisdiction: \"CA-BC\" });\n *\n * A 503 means every requested jurisdiction was unreachable; the body still carries\n * `meta.unavailable`, so the thrown error is the exception rather than the information. Prefer\n * reading `meta.unavailable` on a 200 over treating an empty list as good news.\n */\nexport class Roads {\n private readonly key: string;\n private readonly base: string;\n private readonly fetchImpl: typeof fetch;\n\n constructor(opts: RoadsOptions) {\n this.key = opts.key;\n this.base = (opts.gateway ?? DEFAULT_GATEWAY).replace(/\\/+$/, \"\");\n this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);\n }\n\n /** Every current road event. */\n incidents(query: RoadEventQuery = {}): Promise<RoadEventsResponse> {\n return this.request(\"/incidents\", query);\n }\n\n /** The subset where a road is actually shut. Narrowed before `limit` is applied. */\n closures(query: RoadEventQuery = {}): Promise<RoadEventsResponse> {\n return this.request(\"/closures\", query);\n }\n\n /**\n * The subset carrying a surface condition.\n *\n * Thin for British Columbia by design: its feed reports lane availability rather than the\n * surface, so unmap answers `unknown` instead of inferring a dry road from open lanes.\n */\n conditions(query: RoadEventQuery = {}): Promise<RoadEventsResponse> {\n return this.request(\"/road-conditions\", query);\n }\n\n private async request(path: string, query: RoadEventQuery): Promise<RoadEventsResponse> {\n const q = new URLSearchParams({ key: this.key });\n const params: Record<string, string | undefined> = {\n jurisdiction: list(query.jurisdiction),\n bbox: query.bbox?.join(\",\"),\n near: query.near?.join(\",\"),\n radius: query.radius != null ? String(query.radius) : undefined,\n type: list(query.type),\n severity: list(query.severity),\n status: query.status,\n updated_since: query.updated_since,\n limit: query.limit != null ? String(query.limit) : undefined,\n };\n for (const [k, v] of Object.entries(params)) if (v != null && v !== \"\") q.set(k, v);\n\n const res = await this.fetchImpl(`${this.base}${path}?${q}`, {\n signal: query.signal,\n headers: { accept: \"application/json\" },\n });\n if (!res.ok) throw await errorFrom(res, path, \"roads\");\n return (await res.json()) as RoadEventsResponse;\n }\n}\n\n// ---------------------------------------------------------------------------------------------\n// Feedback\n// ---------------------------------------------------------------------------------------------\n\nexport type FeedbackProduct =\n | \"tiles\"\n | \"terrain\"\n | \"names\"\n | \"geocoding\"\n | \"places\"\n | \"routing\"\n | \"layers\"\n | \"transit\"\n | \"roads\"\n | \"ev\";\n\nexport type FeedbackCategory =\n | \"incorrect_location\"\n | \"incorrect_address\"\n | \"missing_place\"\n | \"wrong_place_name\"\n | \"wrong_category\"\n | \"bad_route\"\n | \"missing_road_closure\"\n | \"incorrect_road_condition\"\n | \"incorrect_transit_result\"\n | \"other\";\n\nexport interface FeedbackReport {\n product: FeedbackProduct;\n category: FeedbackCategory;\n /** Up to 2000 characters, stored exactly as written. One of this or `resultId` is required. */\n description?: string;\n /** The unmap id of the result being reported. */\n resultId?: string;\n /**\n * The request being reported.\n *\n * **Only send this with the reporter's explicit agreement.** The gateway refuses it without\n * `include_context: true`, which this sets whenever `context` is present, and that flag is\n * meant to record a person's choice rather than a client default. unmap redacts query strings\n * from its own logs; a client that attached them here would undo that.\n */\n context?: unknown;\n signal?: AbortSignal;\n}\n\nexport interface FeedbackReceipt {\n /** Quote this in a follow-up. */\n id: string;\n status: string;\n created_at: string;\n /** Whether a request context was actually kept. `false` when none was offered. */\n context_stored: boolean;\n}\n\n/**\n * Report wrong data.\n *\n * await unmap.feedback.submit({\n * product: \"geocoding\",\n * category: \"incorrect_location\",\n * description: \"17 Ave SW returns a street in Edmonton.\",\n * });\n *\n * Authenticated like everything else and **never metered**: telling us our data is wrong does not\n * cost a call. At most 200 reports per account per day.\n */\nexport class Feedback {\n private readonly key: string;\n private readonly base: string;\n private readonly fetchImpl: typeof fetch;\n\n constructor(opts: RoadsOptions) {\n this.key = opts.key;\n this.base = (opts.gateway ?? DEFAULT_GATEWAY).replace(/\\/+$/, \"\");\n this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);\n }\n\n submit(report: FeedbackReport): Promise<FeedbackReceipt> {\n return this.post(\"/feedback\", {\n product: report.product,\n category: report.category,\n ...(report.description ? { description: report.description } : {}),\n ...(report.resultId ? { result_id: report.resultId } : {}),\n ...(report.context !== undefined ? { include_context: true, context: report.context } : {}),\n }, report.signal);\n }\n\n /** A report about one place. `product` and `result_id` come from the path. */\n aboutPlace(\n placeId: string,\n report: Omit<FeedbackReport, \"product\" | \"resultId\">,\n ): Promise<FeedbackReceipt> {\n return this.post(`/places/${encodeURIComponent(placeId)}/feedback`, {\n category: report.category,\n ...(report.description ? { description: report.description } : {}),\n ...(report.context !== undefined ? { include_context: true, context: report.context } : {}),\n }, report.signal);\n }\n\n private async post(path: string, body: unknown, signal?: AbortSignal): Promise<FeedbackReceipt> {\n const res = await this.fetchImpl(`${this.base}${path}`, {\n method: \"POST\",\n signal,\n headers: { \"content-type\": \"application/json\", authorization: `Bearer ${this.key}` },\n body: JSON.stringify(body),\n });\n if (!res.ok) throw await errorFrom(res, path, \"feedback\");\n return (await res.json()) as FeedbackReceipt;\n }\n}\n"],"mappings":"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","names":["createMap"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unmap/sdk",
3
- "version": "0.10.0",
3
+ "version": "0.13.0",
4
4
  "description": "One key, the whole unmap platform: map + geocoder + router",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -40,10 +40,11 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "maplibre-gl": "^6.7.0",
43
- "@unmap/maps": "0.10.0",
44
- "@unmap/geocoding": "0.10.0",
45
- "@unmap/routing": "0.10.0",
46
- "@unmap/core": "0.10.0"
43
+ "@unmap/core": "0.13.0",
44
+ "@unmap/elevation": "0.13.0",
45
+ "@unmap/geocoding": "0.13.0",
46
+ "@unmap/maps": "0.13.0",
47
+ "@unmap/routing": "0.13.0"
47
48
  },
48
49
  "devDependencies": {
49
50
  "tsup": "^8.3.0",