@vuevox/sdk 0.1.0 → 0.3.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 +53 -8
- package/dist/client.d.ts +19 -1
- package/dist/client.js +47 -4
- package/dist/errors.d.ts +2 -1
- package/dist/errors.js +3 -1
- package/dist/generated/schema.d.ts +109 -0
- package/dist/index.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,10 +18,11 @@ Settings -> Developer API -> Manage API Clients
|
|
|
18
18
|
|
|
19
19
|
Create a client, select the scopes it can request, and copy the `client_secret` immediately. VueVox shows each client secret only once.
|
|
20
20
|
|
|
21
|
-
Available
|
|
21
|
+
Available scopes:
|
|
22
22
|
|
|
23
23
|
```text
|
|
24
24
|
hello:read
|
|
25
|
+
spaces:read
|
|
25
26
|
```
|
|
26
27
|
|
|
27
28
|
## Basic Usage
|
|
@@ -32,18 +33,47 @@ import { createVueVoxClient } from "@vuevox/sdk";
|
|
|
32
33
|
const vuevox = createVueVoxClient({
|
|
33
34
|
clientId: process.env.VUEVOX_CLIENT_ID!,
|
|
34
35
|
clientSecret: process.env.VUEVOX_CLIENT_SECRET!,
|
|
35
|
-
scope: "hello:read",
|
|
36
|
+
scope: ["hello:read", "spaces:read"],
|
|
36
37
|
});
|
|
37
38
|
|
|
38
39
|
const hello = await vuevox.hello();
|
|
39
|
-
console.log(hello.message);
|
|
40
|
+
console.log(hello.data.message);
|
|
41
|
+
console.log(hello.requestId);
|
|
42
|
+
|
|
43
|
+
const spaces = await vuevox.listSpaces({ limit: 50 });
|
|
44
|
+
console.log(spaces.data.data);
|
|
45
|
+
console.log(spaces.requestId);
|
|
40
46
|
```
|
|
41
47
|
|
|
42
48
|
The SDK requests and caches a short-lived access token using client credentials, then sends it as a bearer token for API calls.
|
|
43
49
|
|
|
50
|
+
## Request IDs
|
|
51
|
+
|
|
52
|
+
Every Developer API response includes an `X-Request-Id` header. SDK endpoint methods return response metadata with the response body so you can log that ID for support requests.
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
const spaces = await vuevox.listSpaces({ limit: 50 });
|
|
56
|
+
|
|
57
|
+
console.log(spaces.requestId);
|
|
58
|
+
console.log(spaces.data.data);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
For centralized logging, pass `onResponse`. The hook runs for every SDK-managed HTTP response, including the token request.
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
const vuevox = createVueVoxClient({
|
|
65
|
+
clientId: process.env.VUEVOX_CLIENT_ID!,
|
|
66
|
+
clientSecret: process.env.VUEVOX_CLIENT_SECRET!,
|
|
67
|
+
scope: ["hello:read", "spaces:read"],
|
|
68
|
+
onResponse: ({ method, path, status, requestId }) => {
|
|
69
|
+
console.log({ method, path, status, requestId });
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
44
74
|
## Error Handling
|
|
45
75
|
|
|
46
|
-
API errors throw `VueVoxApiError`.
|
|
76
|
+
API errors throw `VueVoxApiError`. The SDK exposes `error.requestId` from either the response header or error body.
|
|
47
77
|
|
|
48
78
|
```ts
|
|
49
79
|
import { VueVoxApiError, createVueVoxClient } from "@vuevox/sdk";
|
|
@@ -51,14 +81,14 @@ import { VueVoxApiError, createVueVoxClient } from "@vuevox/sdk";
|
|
|
51
81
|
const vuevox = createVueVoxClient({
|
|
52
82
|
clientId: process.env.VUEVOX_CLIENT_ID!,
|
|
53
83
|
clientSecret: process.env.VUEVOX_CLIENT_SECRET!,
|
|
54
|
-
scope: "hello:read",
|
|
84
|
+
scope: ["hello:read", "spaces:read"],
|
|
55
85
|
});
|
|
56
86
|
|
|
57
87
|
try {
|
|
58
|
-
await vuevox.
|
|
88
|
+
await vuevox.listSpaces({ limit: 50 });
|
|
59
89
|
} catch (error) {
|
|
60
90
|
if (error instanceof VueVoxApiError) {
|
|
61
|
-
console.error(error.status, error.code, error.message);
|
|
91
|
+
console.error(error.status, error.code, error.message, error.requestId);
|
|
62
92
|
}
|
|
63
93
|
|
|
64
94
|
throw error;
|
|
@@ -100,7 +130,7 @@ const vuevox = createVueVoxClient({
|
|
|
100
130
|
baseUrl: "https://api.vuevox.com",
|
|
101
131
|
clientId: process.env.VUEVOX_CLIENT_ID!,
|
|
102
132
|
clientSecret: process.env.VUEVOX_CLIENT_SECRET!,
|
|
103
|
-
scope: "hello:read",
|
|
133
|
+
scope: ["hello:read", "spaces:read"],
|
|
104
134
|
});
|
|
105
135
|
```
|
|
106
136
|
|
|
@@ -115,3 +145,18 @@ const { data, error } = await vuevox.raw.GET("/v1/hello", {
|
|
|
115
145
|
},
|
|
116
146
|
});
|
|
117
147
|
```
|
|
148
|
+
|
|
149
|
+
## Pagination
|
|
150
|
+
|
|
151
|
+
List endpoints use cursor pagination.
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
const firstPage = await vuevox.listSpaces({ limit: 50 });
|
|
155
|
+
|
|
156
|
+
if (firstPage.data.pagination.nextCursor) {
|
|
157
|
+
const secondPage = await vuevox.listSpaces({
|
|
158
|
+
limit: 50,
|
|
159
|
+
cursor: firstPage.data.pagination.nextCursor,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
```
|
package/dist/client.d.ts
CHANGED
|
@@ -1,15 +1,33 @@
|
|
|
1
1
|
import type { components, paths } from "./generated/schema.js";
|
|
2
2
|
type HelloResponse = components["schemas"]["HelloResponse"];
|
|
3
|
+
type SpacesListResponse = components["schemas"]["SpacesListResponse"];
|
|
4
|
+
export interface ListSpacesOptions {
|
|
5
|
+
limit?: number;
|
|
6
|
+
cursor?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface VueVoxResponseMetadata {
|
|
9
|
+
requestId?: string;
|
|
10
|
+
status: number;
|
|
11
|
+
}
|
|
12
|
+
export interface VueVoxApiResponse<T> extends VueVoxResponseMetadata {
|
|
13
|
+
data: T;
|
|
14
|
+
}
|
|
15
|
+
export interface VueVoxResponseEvent extends VueVoxResponseMetadata {
|
|
16
|
+
method: string;
|
|
17
|
+
path: string;
|
|
18
|
+
}
|
|
3
19
|
export interface VueVoxClientOptions {
|
|
4
20
|
baseUrl?: string;
|
|
5
21
|
clientId: string;
|
|
6
22
|
clientSecret: string;
|
|
7
23
|
scope?: string | string[];
|
|
8
24
|
fetch?: typeof fetch;
|
|
25
|
+
onResponse?: (event: VueVoxResponseEvent) => void;
|
|
9
26
|
}
|
|
10
27
|
export declare function createVueVoxClient(options: VueVoxClientOptions): {
|
|
11
28
|
getAccessToken: () => Promise<string>;
|
|
12
|
-
hello: () => Promise<HelloResponse
|
|
29
|
+
hello: () => Promise<VueVoxApiResponse<HelloResponse>>;
|
|
30
|
+
listSpaces: (listOptions?: ListSpacesOptions) => Promise<VueVoxApiResponse<SpacesListResponse>>;
|
|
13
31
|
raw: import("openapi-fetch").Client<paths, `${string}/${string}`>;
|
|
14
32
|
};
|
|
15
33
|
export {};
|
package/dist/client.js
CHANGED
|
@@ -22,12 +22,14 @@ export function createVueVoxClient(options) {
|
|
|
22
22
|
}),
|
|
23
23
|
});
|
|
24
24
|
const body = await parseJson(response);
|
|
25
|
+
const requestId = getRequestId(response, body);
|
|
26
|
+
notifyResponse(options, "POST", "/oauth/token", response, requestId);
|
|
25
27
|
if (!response.ok) {
|
|
26
28
|
const error = isErrorResponse(body) ? body.error : null;
|
|
27
|
-
throw new VueVoxApiError(response.status, error?.code ?? "token_request_failed", error?.message ?? "VueVox token request failed.", isErrorResponse(body) ? body : undefined);
|
|
29
|
+
throw new VueVoxApiError(response.status, error?.code ?? "token_request_failed", error?.message ?? "VueVox token request failed.", isErrorResponse(body) ? body : undefined, requestId);
|
|
28
30
|
}
|
|
29
31
|
if (!isTokenResponse(body)) {
|
|
30
|
-
throw new VueVoxApiError(response.status, "invalid_token_response", "VueVox returned an invalid token response.");
|
|
32
|
+
throw new VueVoxApiError(response.status, "invalid_token_response", "VueVox returned an invalid token response.", undefined, requestId);
|
|
31
33
|
}
|
|
32
34
|
cachedToken = {
|
|
33
35
|
accessToken: body.access_token,
|
|
@@ -42,14 +44,34 @@ export function createVueVoxClient(options) {
|
|
|
42
44
|
Authorization: `Bearer ${accessToken}`,
|
|
43
45
|
},
|
|
44
46
|
});
|
|
47
|
+
const requestId = getRequestId(response, error);
|
|
48
|
+
notifyResponse(options, "GET", "/v1/hello", response, requestId);
|
|
45
49
|
if (error) {
|
|
46
|
-
throw new VueVoxApiError(response.status, error.error.code, error.error.message, error);
|
|
50
|
+
throw new VueVoxApiError(response.status, error.error.code, error.error.message, error, requestId);
|
|
47
51
|
}
|
|
48
|
-
return data;
|
|
52
|
+
return withMetadata(data, response, requestId);
|
|
53
|
+
}
|
|
54
|
+
async function listSpaces(listOptions = {}) {
|
|
55
|
+
const accessToken = await getAccessToken();
|
|
56
|
+
const { data, error, response } = await raw.GET("/v1/spaces", {
|
|
57
|
+
params: {
|
|
58
|
+
query: listOptions,
|
|
59
|
+
},
|
|
60
|
+
headers: {
|
|
61
|
+
Authorization: `Bearer ${accessToken}`,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
const requestId = getRequestId(response, error);
|
|
65
|
+
notifyResponse(options, "GET", "/v1/spaces", response, requestId);
|
|
66
|
+
if (error) {
|
|
67
|
+
throw new VueVoxApiError(response.status, error.error.code, error.error.message, error, requestId);
|
|
68
|
+
}
|
|
69
|
+
return withMetadata(data, response, requestId);
|
|
49
70
|
}
|
|
50
71
|
return {
|
|
51
72
|
getAccessToken,
|
|
52
73
|
hello,
|
|
74
|
+
listSpaces,
|
|
53
75
|
raw,
|
|
54
76
|
};
|
|
55
77
|
}
|
|
@@ -76,3 +98,24 @@ function isTokenResponse(value) {
|
|
|
76
98
|
function isErrorResponse(value) {
|
|
77
99
|
return Boolean(value && "error" in value);
|
|
78
100
|
}
|
|
101
|
+
function getRequestId(response, body) {
|
|
102
|
+
return response.headers.get("X-Request-Id") ?? (isErrorBody(body) ? body.error.requestId : undefined);
|
|
103
|
+
}
|
|
104
|
+
function isErrorBody(value) {
|
|
105
|
+
return Boolean(value && "error" in value);
|
|
106
|
+
}
|
|
107
|
+
function withMetadata(data, response, requestId) {
|
|
108
|
+
return {
|
|
109
|
+
data,
|
|
110
|
+
requestId,
|
|
111
|
+
status: response.status,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function notifyResponse(options, method, path, response, requestId) {
|
|
115
|
+
options.onResponse?.({
|
|
116
|
+
method,
|
|
117
|
+
path,
|
|
118
|
+
requestId,
|
|
119
|
+
status: response.status,
|
|
120
|
+
});
|
|
121
|
+
}
|
package/dist/errors.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export type VueVoxErrorResponse = components["schemas"]["ErrorResponse"];
|
|
|
3
3
|
export declare class VueVoxApiError extends Error {
|
|
4
4
|
readonly status: number;
|
|
5
5
|
readonly code: string;
|
|
6
|
+
readonly requestId?: string;
|
|
6
7
|
readonly response?: VueVoxErrorResponse;
|
|
7
|
-
constructor(status: number, code: string, message: string, response?: VueVoxErrorResponse);
|
|
8
|
+
constructor(status: number, code: string, message: string, response?: VueVoxErrorResponse, requestId?: string);
|
|
8
9
|
}
|
package/dist/errors.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
export class VueVoxApiError extends Error {
|
|
2
2
|
status;
|
|
3
3
|
code;
|
|
4
|
+
requestId;
|
|
4
5
|
response;
|
|
5
|
-
constructor(status, code, message, response) {
|
|
6
|
+
constructor(status, code, message, response, requestId) {
|
|
6
7
|
super(message);
|
|
7
8
|
this.name = "VueVoxApiError";
|
|
8
9
|
this.status = status;
|
|
9
10
|
this.code = code;
|
|
11
|
+
this.requestId = requestId ?? response?.error.requestId;
|
|
10
12
|
this.response = response;
|
|
11
13
|
}
|
|
12
14
|
}
|
|
@@ -44,6 +44,26 @@ export interface paths {
|
|
|
44
44
|
patch?: never;
|
|
45
45
|
trace?: never;
|
|
46
46
|
};
|
|
47
|
+
"/v1/spaces": {
|
|
48
|
+
parameters: {
|
|
49
|
+
query?: never;
|
|
50
|
+
header?: never;
|
|
51
|
+
path?: never;
|
|
52
|
+
cookie?: never;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* List organization spaces
|
|
56
|
+
* @description Returns spaces for the API client's organization using cursor pagination.
|
|
57
|
+
*/
|
|
58
|
+
get: operations["listSpaces"];
|
|
59
|
+
put?: never;
|
|
60
|
+
post?: never;
|
|
61
|
+
delete?: never;
|
|
62
|
+
options?: never;
|
|
63
|
+
head?: never;
|
|
64
|
+
patch?: never;
|
|
65
|
+
trace?: never;
|
|
66
|
+
};
|
|
47
67
|
}
|
|
48
68
|
export type webhooks = Record<string, never>;
|
|
49
69
|
export interface components {
|
|
@@ -72,6 +92,30 @@ export interface components {
|
|
|
72
92
|
/** @example Hello world */
|
|
73
93
|
message: string;
|
|
74
94
|
};
|
|
95
|
+
Space: {
|
|
96
|
+
/** Format: uuid */
|
|
97
|
+
id: string;
|
|
98
|
+
/** @example Sales Team */
|
|
99
|
+
name: string;
|
|
100
|
+
/** @example Workspace for inbound sales calls. */
|
|
101
|
+
description: string | null;
|
|
102
|
+
/** Format: date-time */
|
|
103
|
+
createdAt: string;
|
|
104
|
+
/** Format: date-time */
|
|
105
|
+
updatedAt: string;
|
|
106
|
+
};
|
|
107
|
+
SpacesListResponse: {
|
|
108
|
+
data: components["schemas"]["Space"][];
|
|
109
|
+
pagination: components["schemas"]["CursorPagination"];
|
|
110
|
+
};
|
|
111
|
+
CursorPagination: {
|
|
112
|
+
/** @example 50 */
|
|
113
|
+
limit: number;
|
|
114
|
+
/** @example false */
|
|
115
|
+
hasMore: boolean;
|
|
116
|
+
/** @description Opaque cursor to pass as cursor on the next request. */
|
|
117
|
+
nextCursor: string | null;
|
|
118
|
+
};
|
|
75
119
|
ErrorResponse: {
|
|
76
120
|
error: components["schemas"]["Error"];
|
|
77
121
|
};
|
|
@@ -197,4 +241,69 @@ export interface operations {
|
|
|
197
241
|
};
|
|
198
242
|
};
|
|
199
243
|
};
|
|
244
|
+
listSpaces: {
|
|
245
|
+
parameters: {
|
|
246
|
+
query?: {
|
|
247
|
+
/** @description Number of spaces to return. Defaults to 50. Maximum is 100. */
|
|
248
|
+
limit?: number;
|
|
249
|
+
/** @description Opaque cursor from the previous response's pagination.nextCursor value. */
|
|
250
|
+
cursor?: string;
|
|
251
|
+
};
|
|
252
|
+
header?: never;
|
|
253
|
+
path?: never;
|
|
254
|
+
cookie?: never;
|
|
255
|
+
};
|
|
256
|
+
requestBody?: never;
|
|
257
|
+
responses: {
|
|
258
|
+
/** @description Paginated spaces response. */
|
|
259
|
+
200: {
|
|
260
|
+
headers: {
|
|
261
|
+
[name: string]: unknown;
|
|
262
|
+
};
|
|
263
|
+
content: {
|
|
264
|
+
"application/json": components["schemas"]["SpacesListResponse"];
|
|
265
|
+
};
|
|
266
|
+
};
|
|
267
|
+
/** @description Bearer token is missing, invalid, or expired. */
|
|
268
|
+
401: {
|
|
269
|
+
headers: {
|
|
270
|
+
[name: string]: unknown;
|
|
271
|
+
};
|
|
272
|
+
content: {
|
|
273
|
+
"application/json": components["schemas"]["ErrorResponse"];
|
|
274
|
+
};
|
|
275
|
+
};
|
|
276
|
+
/** @description Bearer token does not include the required scope. */
|
|
277
|
+
403: {
|
|
278
|
+
headers: {
|
|
279
|
+
[name: string]: unknown;
|
|
280
|
+
};
|
|
281
|
+
content: {
|
|
282
|
+
"application/json": components["schemas"]["ErrorResponse"];
|
|
283
|
+
};
|
|
284
|
+
};
|
|
285
|
+
/** @description Query parameters failed validation. */
|
|
286
|
+
422: {
|
|
287
|
+
headers: {
|
|
288
|
+
[name: string]: unknown;
|
|
289
|
+
};
|
|
290
|
+
content: {
|
|
291
|
+
"application/json": components["schemas"]["ErrorResponse"];
|
|
292
|
+
};
|
|
293
|
+
};
|
|
294
|
+
/** @description Per-client rate limit exceeded. */
|
|
295
|
+
429: {
|
|
296
|
+
headers: {
|
|
297
|
+
/** @description Seconds to wait before retrying. */
|
|
298
|
+
"Retry-After"?: string;
|
|
299
|
+
/** @description Request limit per minute for this API client. */
|
|
300
|
+
"X-RateLimit-Limit"?: string;
|
|
301
|
+
[name: string]: unknown;
|
|
302
|
+
};
|
|
303
|
+
content: {
|
|
304
|
+
"application/json": components["schemas"]["ErrorResponse"];
|
|
305
|
+
};
|
|
306
|
+
};
|
|
307
|
+
};
|
|
308
|
+
};
|
|
200
309
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { createVueVoxClient } from "./client.js";
|
|
2
|
-
export type { VueVoxClientOptions } from "./client.js";
|
|
2
|
+
export type { ListSpacesOptions, VueVoxApiResponse, VueVoxClientOptions, VueVoxResponseEvent, VueVoxResponseMetadata, } from "./client.js";
|
|
3
3
|
export { VueVoxApiError } from "./errors.js";
|
|
4
4
|
export type { VueVoxErrorResponse } from "./errors.js";
|