@solvice/routing 0.1.0-beta.0 → 0.16.1-beta.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 +58 -31
- package/dist/index.d.ts +23 -7
- package/dist/index.js +38 -18
- package/dist/v3.d.ts +2088 -1269
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -16,33 +16,51 @@ import { createSolvice, SolviceError } from '@solvice/routing';
|
|
|
16
16
|
const solvice = createSolvice({ apiKey: process.env.SOLVICE_API_KEY! });
|
|
17
17
|
|
|
18
18
|
try {
|
|
19
|
-
const solution = await solvice.solve(
|
|
20
|
-
{
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
19
|
+
const solution = await solvice.solve({
|
|
20
|
+
problem: {
|
|
21
|
+
jobs: [
|
|
22
|
+
{
|
|
23
|
+
id: 'delivery-1',
|
|
24
|
+
location: [4.7005, 50.8798], // [longitude, latitude]
|
|
25
|
+
service_duration_s: 300,
|
|
26
|
+
time_windows: [{ from: '2026-04-01T09:00:00+02:00', to: '2026-04-01T12:00:00+02:00' }],
|
|
27
|
+
delivery_load: { weight: 10 },
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
vehicles: [
|
|
31
|
+
{
|
|
32
|
+
id: 'truck-1',
|
|
33
|
+
capacity: { weight: 100 },
|
|
34
|
+
shifts: [
|
|
35
|
+
{
|
|
36
|
+
id: 'mon',
|
|
37
|
+
from: '2026-04-01T08:00:00+02:00',
|
|
38
|
+
to: '2026-04-01T17:00:00+02:00',
|
|
39
|
+
start: { location: [4.3517, 50.8503] },
|
|
40
|
+
end: { location: [4.3517, 50.8503] },
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
],
|
|
39
45
|
},
|
|
40
|
-
{
|
|
41
|
-
);
|
|
42
|
-
|
|
46
|
+
options: { time_limit_s: 5 },
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
for (const route of solution.routes) {
|
|
50
|
+
console.log(`${route.vehicle} / ${route.shift}: ${route.distance_m} m`);
|
|
51
|
+
for (const stop of route.stops) {
|
|
52
|
+
console.log(` ${stop.type} ${stop.id ?? ''} arrives ${stop.arrival}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
for (const job of solution.unassigned) {
|
|
56
|
+
console.log(`unassigned ${job.task}: ${job.category}`);
|
|
57
|
+
}
|
|
43
58
|
} catch (err) {
|
|
44
59
|
if (!(err instanceof SolviceError)) throw err;
|
|
45
|
-
console.error(err.status, err.body?.
|
|
60
|
+
console.error(err.status, err.body?.code, err.message);
|
|
61
|
+
for (const item of err.body?.errors ?? []) {
|
|
62
|
+
console.error(` ${item.code} at ${item.pointer}: ${item.message}`);
|
|
63
|
+
}
|
|
46
64
|
}
|
|
47
65
|
```
|
|
48
66
|
|
|
@@ -51,10 +69,14 @@ try {
|
|
|
51
69
|
| `solve(request)` | `POST /v3/routing/solve` |
|
|
52
70
|
| `evaluate(request)` | `POST /v3/routing/evaluate` |
|
|
53
71
|
| `suggest(request)` | `POST /v3/routing/suggest` |
|
|
54
|
-
| `validate.solve(request)`, `validate.evaluate(request)`, `validate.suggest(request)` | the same three
|
|
72
|
+
| `validate.solve(request)`, `validate.evaluate(request)`, `validate.suggest(request)` | the same three sent to `/v3/routing/validate/solve`, `/evaluate`, `/suggest`: checked, not run |
|
|
55
73
|
|
|
56
|
-
Any non-2xx response throws a `SolviceError` with the HTTP `status
|
|
57
|
-
response carried one, the API's error `body`.
|
|
74
|
+
Any non-2xx response throws a `SolviceError` with the HTTP `status`, the
|
|
75
|
+
`response`, and, when the response carried one, the API's error `body`. A `401`
|
|
76
|
+
or a `429` comes from the API gateway and does not use that body (see
|
|
77
|
+
[Errors](https://docs.solvice.io/guides/vrp/errors)): branch on `status`, and read
|
|
78
|
+
`raw` for whatever JSON came back. Every `503` carries `Retry-After`; wait
|
|
79
|
+
`err.retryAfterS` seconds before retrying.
|
|
58
80
|
|
|
59
81
|
Every method takes a second argument, `{ signal, headers }`. The client sets no
|
|
60
82
|
timeout of its own: pass `AbortSignal.timeout(ms)` as `signal`, with a margin on
|
|
@@ -72,10 +94,15 @@ Documentation: <https://docs.solvice.io>
|
|
|
72
94
|
|
|
73
95
|
```bash
|
|
74
96
|
npm run generate # from mintlify/openapi.json
|
|
75
|
-
npm test # builds, typechecks tests/types.ts and examples/, runs the unit tests
|
|
97
|
+
npm test # builds, typechecks tests/types.ts and examples/, runs the unit tests and the solve-response fixture check
|
|
76
98
|
npm run smoke # live calls; needs SOLVICE_API_KEY
|
|
77
99
|
```
|
|
78
100
|
|
|
79
|
-
To release, bump `version` to the next `0.x.y-beta.n
|
|
80
|
-
|
|
81
|
-
|
|
101
|
+
To release, bump `version` to the next `0.x.y-beta.n`, merge it to `main`, then run
|
|
102
|
+
the "Publish TypeScript SDK" workflow on `main` (Actions → Run workflow). It runs
|
|
103
|
+
`npm run publish:beta` through npm trusted publishing; never publish
|
|
104
|
+
from a branch. The explicit `--tag beta` matters: npm 11 ignores
|
|
105
|
+
`publishConfig.tag` and would move `latest` to the prerelease. Until a stable version
|
|
106
|
+
exists, `latest` still points at the first prerelease (npm gives a package's first
|
|
107
|
+
version `latest` whatever the tag), so `npm install @solvice/routing` without
|
|
108
|
+
`@beta` installs the beta too.
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export type EvaluateRequest = Schemas["EvaluateRequest"];
|
|
|
6
6
|
export type SuggestRequest = Schemas["SuggestRequest"];
|
|
7
7
|
export type SolveResponse = Schemas["SolveResponse"];
|
|
8
8
|
export type SuggestResponse = Schemas["SuggestResponse"];
|
|
9
|
-
export type
|
|
9
|
+
export type ValidateResponse = Schemas["ValidateResponse"];
|
|
10
10
|
export type ErrorBody = Schemas["ErrorBody"];
|
|
11
11
|
export interface SolviceOptions {
|
|
12
12
|
/** Sent as the `Authorization` header value, with no scheme prefix. */
|
|
@@ -19,21 +19,37 @@ export interface RequestOptions {
|
|
|
19
19
|
signal?: AbortSignal;
|
|
20
20
|
headers?: Record<string, string>;
|
|
21
21
|
}
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Any non-2xx response. `body` is the API's error body when the response
|
|
24
|
+
* carried one. A `401` or a `429` comes from the API gateway and does not use
|
|
25
|
+
* that body (a JSON `{ message, error }` or nothing): branch on `status`, and
|
|
26
|
+
* read `raw` for whatever came back. `response` is the `Response` itself, for
|
|
27
|
+
* its headers: every `503` carries `Retry-After` (`retryAfterS`), and
|
|
28
|
+
* `X-Request-Id` survives a bodiless error.
|
|
29
|
+
*/
|
|
23
30
|
export declare class SolviceError extends Error {
|
|
24
31
|
readonly status: number;
|
|
32
|
+
readonly response: Response;
|
|
25
33
|
readonly body?: ErrorBody | undefined;
|
|
26
|
-
|
|
34
|
+
/** The parsed response body as received, whatever its shape; `undefined` when there was none. */
|
|
35
|
+
readonly raw?: unknown | undefined;
|
|
36
|
+
constructor(status: number, response: Response, body?: ErrorBody | undefined,
|
|
37
|
+
/** The parsed response body as received, whatever its shape; `undefined` when there was none. */
|
|
38
|
+
raw?: unknown | undefined);
|
|
39
|
+
/** Seconds to wait before retrying, from the `Retry-After` header; `undefined` without one (or with an HTTP date). */
|
|
40
|
+
get retryAfterS(): number | undefined;
|
|
27
41
|
}
|
|
42
|
+
/** The client `createSolvice` returns, for naming it in your own signatures. */
|
|
43
|
+
export type Solvice = ReturnType<typeof createSolvice>;
|
|
28
44
|
export declare function createSolvice({ apiKey, baseUrl, fetch }: SolviceOptions): {
|
|
29
45
|
solve: (body: SolveRequest, options?: RequestOptions) => Promise<SolveResponse>;
|
|
30
46
|
evaluate: (body: EvaluateRequest, options?: RequestOptions) => Promise<SolveResponse>;
|
|
31
47
|
suggest: (body: SuggestRequest, options?: RequestOptions) => Promise<SuggestResponse>;
|
|
32
|
-
/** The same requests
|
|
48
|
+
/** The same requests sent to `/v3/routing/validate/*`: no matrix fetch, no search, not billed. */
|
|
33
49
|
validate: {
|
|
34
|
-
solve: (body: SolveRequest, options?: RequestOptions) => Promise<
|
|
35
|
-
evaluate: (body: EvaluateRequest, options?: RequestOptions) => Promise<
|
|
36
|
-
suggest: (body: SuggestRequest, options?: RequestOptions) => Promise<
|
|
50
|
+
solve: (body: SolveRequest, options?: RequestOptions) => Promise<ValidateResponse>;
|
|
51
|
+
evaluate: (body: EvaluateRequest, options?: RequestOptions) => Promise<ValidateResponse>;
|
|
52
|
+
suggest: (body: SuggestRequest, options?: RequestOptions) => Promise<ValidateResponse>;
|
|
37
53
|
};
|
|
38
54
|
/** The underlying openapi-fetch client, for anything the methods above do not cover. */
|
|
39
55
|
client: import("openapi-fetch").Client<paths, `${string}/${string}`>;
|
package/dist/index.js
CHANGED
|
@@ -1,39 +1,59 @@
|
|
|
1
1
|
import createClient from "openapi-fetch";
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Any non-2xx response. `body` is the API's error body when the response
|
|
4
|
+
* carried one. A `401` or a `429` comes from the API gateway and does not use
|
|
5
|
+
* that body (a JSON `{ message, error }` or nothing): branch on `status`, and
|
|
6
|
+
* read `raw` for whatever came back. `response` is the `Response` itself, for
|
|
7
|
+
* its headers: every `503` carries `Retry-After` (`retryAfterS`), and
|
|
8
|
+
* `X-Request-Id` survives a bodiless error.
|
|
9
|
+
*/
|
|
3
10
|
export class SolviceError extends Error {
|
|
4
11
|
status;
|
|
12
|
+
response;
|
|
5
13
|
body;
|
|
6
|
-
|
|
7
|
-
|
|
14
|
+
raw;
|
|
15
|
+
constructor(status, response, body,
|
|
16
|
+
/** The parsed response body as received, whatever its shape; `undefined` when there was none. */
|
|
17
|
+
raw) {
|
|
18
|
+
super(body?.message ?? gatewayMessage(raw) ?? `Solvice API responded ${status}`);
|
|
8
19
|
this.status = status;
|
|
20
|
+
this.response = response;
|
|
9
21
|
this.body = body;
|
|
22
|
+
this.raw = raw;
|
|
10
23
|
this.name = "SolviceError";
|
|
11
24
|
}
|
|
25
|
+
/** Seconds to wait before retrying, from the `Retry-After` header; `undefined` without one (or with an HTTP date). */
|
|
26
|
+
get retryAfterS() {
|
|
27
|
+
const value = this.response.headers.get("retry-after");
|
|
28
|
+
if (value === null)
|
|
29
|
+
return undefined;
|
|
30
|
+
const seconds = Number(value);
|
|
31
|
+
return Number.isFinite(seconds) ? seconds : undefined;
|
|
32
|
+
}
|
|
12
33
|
}
|
|
34
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
35
|
+
/** The API's error body has `status`, `code` and `message`; the gateway's `401` has `message` and `error`. */
|
|
36
|
+
const isErrorBody = (value) => isRecord(value) && "status" in value && "code" in value && "message" in value;
|
|
37
|
+
const gatewayMessage = (raw) => isRecord(raw) && typeof raw.message === "string" ? raw.message : undefined;
|
|
13
38
|
function unwrap({ data, error, response }) {
|
|
14
39
|
if (!response.ok) {
|
|
15
|
-
// openapi-fetch hands back the raw text when the error is not JSON.
|
|
16
|
-
|
|
40
|
+
// openapi-fetch hands back the raw text when the error is not JSON, and "" for no body.
|
|
41
|
+
const raw = error === "" || error == null ? undefined : error;
|
|
42
|
+
throw new SolviceError(response.status, response, isErrorBody(raw) ? raw : undefined, raw);
|
|
17
43
|
}
|
|
18
44
|
return data;
|
|
19
45
|
}
|
|
20
|
-
// The spec types every 200 as `Response | ValidateOnlyResponse` (#1626 H).
|
|
21
|
-
// Which one comes back is decided by `validate_only`, which each method below
|
|
22
|
-
// fixes, so these two pick the matching side of the union.
|
|
23
|
-
const result = (r) => unwrap(r);
|
|
24
|
-
const validation = (r) => unwrap(r);
|
|
25
|
-
const VALIDATE_ONLY = { query: { validate_only: true } };
|
|
26
46
|
export function createSolvice({ apiKey, baseUrl = "https://api.solvice.io", fetch }) {
|
|
27
47
|
const client = createClient({ baseUrl, headers: { Authorization: apiKey }, fetch });
|
|
28
48
|
return {
|
|
29
|
-
solve: async (body, options) =>
|
|
30
|
-
evaluate: async (body, options) =>
|
|
31
|
-
suggest: async (body, options) =>
|
|
32
|
-
/** The same requests
|
|
49
|
+
solve: async (body, options) => unwrap(await client.POST("/v3/routing/solve", { body, ...options })),
|
|
50
|
+
evaluate: async (body, options) => unwrap(await client.POST("/v3/routing/evaluate", { body, ...options })),
|
|
51
|
+
suggest: async (body, options) => unwrap(await client.POST("/v3/routing/suggest", { body, ...options })),
|
|
52
|
+
/** The same requests sent to `/v3/routing/validate/*`: no matrix fetch, no search, not billed. */
|
|
33
53
|
validate: {
|
|
34
|
-
solve: async (body, options) =>
|
|
35
|
-
evaluate: async (body, options) =>
|
|
36
|
-
suggest: async (body, options) =>
|
|
54
|
+
solve: async (body, options) => unwrap(await client.POST("/v3/routing/validate/solve", { body, ...options })),
|
|
55
|
+
evaluate: async (body, options) => unwrap(await client.POST("/v3/routing/validate/evaluate", { body, ...options })),
|
|
56
|
+
suggest: async (body, options) => unwrap(await client.POST("/v3/routing/validate/suggest", { body, ...options })),
|
|
37
57
|
},
|
|
38
58
|
/** The underlying openapi-fetch client, for anything the methods above do not cover. */
|
|
39
59
|
client,
|