@any-routing/here-data-provider 0.1.0 → 1.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  This library was generated with [Nx](https://nx.dev).
4
4
 
5
+ ## Building
6
+
7
+ Run `nx build here-data-provider` to build the library.
8
+
5
9
  ## Running unit tests
6
10
 
7
- Run `nx test here-data-provider` to execute the unit tests via [Jest](https://jestjs.io).
11
+ Run `nx test here-data-provider` to execute the unit tests via [Vitest](https://vitest.dev/).
@@ -0,0 +1,24 @@
1
+ import baseConfig from "../../eslint.config.mjs";
2
+
3
+ export default [
4
+ ...baseConfig,
5
+ {
6
+ files: [
7
+ "**/*.json"
8
+ ],
9
+ rules: {
10
+ "@nx/dependency-checks": [
11
+ "error",
12
+ {
13
+ "ignoredFiles": [
14
+ "{projectRoot}/eslint.config.{js,cjs,mjs,ts,cts,mts}",
15
+ "{projectRoot}/vite.config.{js,ts,mjs,mts}"
16
+ ]
17
+ }
18
+ ]
19
+ },
20
+ languageOptions: {
21
+ parser: await import("jsonc-eslint-parser")
22
+ }
23
+ }
24
+ ];
package/package.json CHANGED
@@ -1,18 +1,16 @@
1
1
  {
2
2
  "name": "@any-routing/here-data-provider",
3
- "version": "0.1.0",
4
- "private": false,
3
+ "version": "1.0.0-rc.1",
4
+ "type": "module",
5
+ "main": "./index.js",
6
+ "types": "./index.d.ts",
5
7
  "dependencies": {
6
- "@liberty-rider/flexpolyline": "0.1.0",
7
- "@turf/bbox": "6.5.0",
8
- "@turf/helpers": "6.5.0",
9
- "comlink": "4.3.1",
10
- "simplify-js": "1.2.4"
11
- },
12
- "peerDependencies": {
13
- "@any-routing/core": "^0.0.0",
14
- "tslib": "2.5.0"
15
- },
16
- "main": "./src/index.js",
17
- "types": "./src/index.d.ts"
18
- }
8
+ "@any-routing/core": "1.0.0-rc.1",
9
+ "comlink": "4.4.2",
10
+ "@here/flexpolyline": "0.1.0",
11
+ "simplify-js": "1.2.4",
12
+ "@turf/bbox": "7.4.0",
13
+ "@turf/helpers": "7.4.0",
14
+ "@types/geojson": "7946.0.16"
15
+ }
16
+ }
package/project.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "here-data-provider",
3
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "libs/here-data-provider/src",
5
+ "projectType": "library",
6
+ "tags": [],
7
+ "// targets": "to see all targets run: nx show project here-data-provider --web",
8
+ "targets": {}
9
+ }
@@ -1 +1,2 @@
1
1
  export * from './lib/here-data-provider';
2
+ export * from './lib/here-provider.types';
@@ -0,0 +1,7 @@
1
+ // import { HereProvider } from './here-data-provider';
2
+
3
+ // describe('hereDataProvider', () => {
4
+ // it('should work', () => {
5
+ // expect(HereProvider).toEqual('here-data-provider');
6
+ // });
7
+ // });
@@ -0,0 +1,148 @@
1
+ import { wrap, Remote } from 'comlink';
2
+
3
+ import { ExecutorRequestOptions, HereExecutor } from './here.executor';
4
+ import type {
5
+ AnyRoutingDataProvider,
6
+ RequestOptions,
7
+ Waypoint,
8
+ WaypointPosition,
9
+ } from '@any-routing/core';
10
+ import { HereRoutingData, PluginOptions } from './here-provider.types';
11
+
12
+ const defaultOptions: Partial<PluginOptions> = {
13
+ baseUrl: 'https://router.hereapi.com/v8/routes',
14
+ transportMode: 'car',
15
+ worker: true,
16
+ alternatives: 2,
17
+ shapePolylinePrecision: 0.0000065,
18
+ routeExcludeNotice: {
19
+ critical: 'all',
20
+ },
21
+ };
22
+
23
+ export class HereProvider implements AnyRoutingDataProvider {
24
+ private worker?: Worker;
25
+ // Comlink's `wrap` returns a `Remote<T>` proxy whose methods always
26
+ // resolve to promises, even for originally-synchronous ones — typing
27
+ // this union up front avoids the `@ts-ignore` the previous version
28
+ // needed on assignment.
29
+ private executorAPI: HereExecutor | Remote<HereExecutor>;
30
+ private _options: PluginOptions;
31
+
32
+ public get options(): PluginOptions {
33
+ return this._options;
34
+ }
35
+
36
+ constructor(defaultModeOptions: PluginOptions) {
37
+ this._options = { ...defaultOptions, ...defaultModeOptions };
38
+
39
+ if (this.options.worker === true) {
40
+ this.worker = new Worker(new URL('./here.worker', import.meta.url), {
41
+ type: 'module',
42
+ });
43
+
44
+ this.executorAPI = wrap<HereExecutor>(this.worker);
45
+ } else {
46
+ this.executorAPI = new HereExecutor();
47
+ }
48
+ }
49
+
50
+ public destroy(): void {
51
+ if (this.worker) {
52
+ this.worker.terminate();
53
+ }
54
+ }
55
+
56
+ public request(waypoints: Waypoint[], opts: Record<string, unknown>): Promise<HereRoutingData> {
57
+ const configOptions = this.options;
58
+
59
+ const url = this.buildUrl(waypoints, { ...configOptions, ...opts });
60
+
61
+ // `buildUrl` is a function and must never be forwarded into
62
+ // `requestParams` below — when running with `worker: true`,
63
+ // `executorAPI` is a Comlink `Remote<HereExecutor>` and the call goes
64
+ // through `postMessage`'s structured clone algorithm, which cannot
65
+ // clone functions and throws `DataCloneError` at runtime. A previous
66
+ // revision of this file destructured `buildUrl` out for exactly this
67
+ // reason; that got lost in the last edit — restoring it here.
68
+ const requestOptions = { ...configOptions, ...opts, url };
69
+ delete requestOptions.buildUrl;
70
+ const requestParams: ExecutorRequestOptions = requestOptions;
71
+
72
+ return this.executorAPI.request(requestParams);
73
+ }
74
+
75
+ public async abortAllRequests(): Promise<void> {
76
+ await this.executorAPI.abortAllRequests();
77
+ }
78
+
79
+ public setOption<T extends keyof PluginOptions>(optionKey: T, value: PluginOptions[T]): void {
80
+ this.options[optionKey] = value;
81
+ }
82
+
83
+ public async hasPendingRequests(): Promise<boolean> {
84
+ return await this.executorAPI.hasPendingRequests();
85
+ }
86
+
87
+ private buildUrl(waypoints: Waypoint[], opts: PluginOptions & RequestOptions): string {
88
+ if (!waypoints[0] || !waypoints[waypoints.length - 1]) {
89
+ throw new Error('At least two waypoints are required');
90
+ }
91
+
92
+ const start = waypoints[0].position;
93
+ const end = waypoints[waypoints.length - 1].position;
94
+
95
+ const spans = opts.spans?.join(',') ?? null;
96
+
97
+ const returnFields = [...(opts.return || []), 'polyline', 'summary', 'routeLabels'].join(',');
98
+
99
+ const queryParamsObj: Record<string, string | number | undefined | null> = {
100
+ apiKey: opts.apiKey,
101
+ origin: this.formatWp(start),
102
+ destination: this.formatWp(end),
103
+ transportMode: opts.transportMode || '',
104
+ spans,
105
+ return: returnFields,
106
+ alternatives: opts?.alternatives ?? 0,
107
+ currency: opts.currency || undefined,
108
+ ...opts.queryParams,
109
+ };
110
+
111
+ if (this.options.selectRouteStrategy === 'cheapest' && !String(queryParamsObj['return']).includes('tolls')) {
112
+ queryParamsObj['return'] = `${queryParamsObj['return']},tolls`;
113
+ }
114
+
115
+ // Preserves the original truthy filter (drops 0 / '' / null / undefined),
116
+ // just stringifies everything up front so `URLSearchParams` type-checks.
117
+ const queryParams: Record<string, string> = Object.entries(queryParamsObj).reduce(
118
+ (acc, [key, value]) => {
119
+ if (value) {
120
+ acc[key] = String(value);
121
+ }
122
+ return acc;
123
+ },
124
+ {} as Record<string, string>
125
+ );
126
+
127
+ let qp = new URLSearchParams(queryParams).toString();
128
+
129
+ if (waypoints.length > 2) {
130
+ qp += `&${this.serializeWaypoints(waypoints)}`;
131
+ }
132
+
133
+ const url = `${opts.baseUrl}?${qp}`;
134
+
135
+ return opts.buildUrl ? opts.buildUrl({ waypoints, options: opts }, url) : url;
136
+ }
137
+
138
+ private serializeWaypoints(waypoints: Waypoint[]): string {
139
+ return waypoints
140
+ .slice(1, waypoints.length - 1)
141
+ .map((w) => `via=${this.formatWp(w.position)}`)
142
+ .join('&');
143
+ }
144
+
145
+ private formatWp({ lat, lng }: WaypointPosition): string {
146
+ return `${lat},${lng}`;
147
+ }
148
+ }
@@ -0,0 +1,179 @@
1
+ import type { AnyRoutingDataResponse, LngLatPosition, RequestOptions, RouteSummary, Waypoint } from '@any-routing/core';
2
+ import type { ExecutorRequestOptions } from './here.executor';
3
+
4
+ export type SelectRouteStrategy = 'fastest' | 'shortest' | 'cheapest' | 'none';
5
+
6
+ export type RouteExcludeNoticeDefinitions = {
7
+ [key in 'critical' | 'info']?: string[] | 'all';
8
+ };
9
+
10
+ /**
11
+ * HERE's `action` values on `turnByTurnActions` are documented as an
12
+ * "extensible enum" — HERE can add new maneuver types over time, and
13
+ * clients are expected to handle unknown values gracefully rather than
14
+ * fail. We type the values we know about explicitly (for autocomplete /
15
+ * exhaustiveness where it matters) but keep the type open to any other
16
+ * string so a future HERE rollout doesn't produce values TypeScript
17
+ * silently rejects at compile time / mangles at runtime.
18
+ *
19
+ * Source: HERE Routing API v8 docs — "What is an action".
20
+ */
21
+ export type HereActionType =
22
+ | 'depart'
23
+ | 'arrive'
24
+ | 'continue'
25
+ | 'turn'
26
+ | 'uTurn'
27
+ | 'ramp'
28
+ | 'keep'
29
+ | 'roundaboutEnter'
30
+ | 'roundaboutPass'
31
+ | 'roundaboutExit'
32
+ | 'exit'
33
+ | 'ferry'
34
+ | (string & {});
35
+
36
+ export type TurnByTurnAction = {
37
+ action: HereActionType;
38
+ duration: number;
39
+ length: number;
40
+ position: { lat: number; lng: number };
41
+ offset: number;
42
+ };
43
+
44
+ export type RoutePath = LngLatPosition[];
45
+
46
+ export interface HereRouteSummary extends RouteSummary {
47
+ turnByTurnActions: TurnByTurnAction[];
48
+ shapePath: RoutePath;
49
+ rawRoute: HereRawRoute;
50
+ }
51
+
52
+ export interface HereRoutingData extends AnyRoutingDataResponse {
53
+ routes: HereRouteSummary[];
54
+ requestOptions: ExecutorRequestOptions;
55
+ }
56
+
57
+ export type GeoJsonSplitStrategy = 'jamFactor';
58
+
59
+ export type BaseOptions = {
60
+ alternatives?: number;
61
+ worker?: boolean;
62
+ apiKey: string;
63
+ baseUrl?: string;
64
+ selectRouteStrategy?: SelectRouteStrategy;
65
+ spans?: string[];
66
+ return?: string[];
67
+ currency?: string;
68
+ transportMode?: string;
69
+ queryParams?: Record<string, unknown>;
70
+ requestParams?: RequestInit;
71
+ routeExcludeNotice?: RouteExcludeNoticeDefinitions;
72
+ shapePolylinePrecision?: number;
73
+ geoJSONShapeSplitStrategies?: Array<GeoJsonSplitStrategy>;
74
+ buildUrl?: (ctx: { waypoints: Waypoint[]; options: BaseOptions & RequestOptions }, url: string) => string;
75
+ };
76
+
77
+ export type PluginOptions = BaseOptions;
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // HERE Routing API v8 raw response types (the subset the executor reads).
81
+ // These model the JSON documented at
82
+ // https://docs.here.com/routing/docs/routing-v8-get-started#response and the
83
+ // "What is a span" / "What is a notice" guides. Fields not consumed by this
84
+ // package are intentionally left out rather than fully modeled, and open
85
+ // dictionaries (`spans`) are typed loosely since their shape depends on the
86
+ // `spans=` request parameter the caller chose.
87
+ // ---------------------------------------------------------------------------
88
+
89
+ export interface HereRawPlace {
90
+ type: string;
91
+ location: { lat: number; lng: number };
92
+ /** Present only when this endpoint was geocoded from a raw waypoint. */
93
+ originalLocation?: { lat: number; lng: number };
94
+ }
95
+
96
+ export interface HereRawSectionEndpoint {
97
+ place: HereRawPlace;
98
+ time?: string;
99
+ }
100
+
101
+ export interface HereRawSummary {
102
+ duration?: number;
103
+ length?: number;
104
+ baseDuration?: number;
105
+ }
106
+
107
+ export interface HereRawFarePrice {
108
+ value: number;
109
+ currency: string;
110
+ }
111
+
112
+ export interface HereRawFare {
113
+ price?: HereRawFarePrice;
114
+ convertedPrice?: HereRawFarePrice;
115
+ }
116
+
117
+ export interface HereRawToll {
118
+ fares?: HereRawFare[];
119
+ }
120
+
121
+ export interface HereRawDynamicSpeedInfo {
122
+ baseSpeed: number;
123
+ trafficSpeed: number;
124
+ turnTime?: number;
125
+ }
126
+
127
+ /**
128
+ * A single `spans[]` entry. Which attributes are present depends entirely
129
+ * on the `spans=` query param sent in the request (see "What is a span" in
130
+ * the HERE docs), so beyond the `offset` every requested attribute name
131
+ * (`dynamicSpeedInfo`, `names`, `countryCode`, ...) shows up as an optional
132
+ * key — hence the index signature rather than an exhaustive interface.
133
+ */
134
+ export interface HereRawSpan {
135
+ offset: number;
136
+ dynamicSpeedInfo?: HereRawDynamicSpeedInfo;
137
+ [attribute: string]: unknown;
138
+ }
139
+
140
+ export interface HereRawAction {
141
+ action: HereActionType;
142
+ duration: number;
143
+ length: number;
144
+ offset: number;
145
+ instruction?: string;
146
+ }
147
+
148
+ export interface HereRawNotice {
149
+ code: string;
150
+ severity: 'critical' | 'info';
151
+ }
152
+
153
+ export interface HereRawSection {
154
+ id: string;
155
+ type: string;
156
+ departure: HereRawSectionEndpoint;
157
+ arrival: HereRawSectionEndpoint;
158
+ summary?: HereRawSummary;
159
+ polyline: string;
160
+ spans?: HereRawSpan[];
161
+ tolls?: HereRawToll[];
162
+ turnByTurnActions?: HereRawAction[];
163
+ notices?: HereRawNotice[];
164
+ }
165
+
166
+ export interface HereRawRouteLabel {
167
+ name: { value: string; language?: string };
168
+ }
169
+
170
+ export interface HereRawRoute {
171
+ id: string;
172
+ sections: HereRawSection[];
173
+ routeLabels?: HereRawRouteLabel[];
174
+ }
175
+
176
+ export interface HereApiResponse {
177
+ routes: HereRawRoute[];
178
+ notices?: HereRawNotice[];
179
+ }