@vuevox/sdk 0.1.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 ADDED
@@ -0,0 +1,117 @@
1
+ # @vuevox/sdk
2
+
3
+ TypeScript SDK for the VueVox Developer API.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @vuevox/sdk
9
+ ```
10
+
11
+ ## Create API Credentials
12
+
13
+ In VueVox, open:
14
+
15
+ ```text
16
+ Settings -> Developer API -> Manage API Clients
17
+ ```
18
+
19
+ Create a client, select the scopes it can request, and copy the `client_secret` immediately. VueVox shows each client secret only once.
20
+
21
+ Available scope:
22
+
23
+ ```text
24
+ hello:read
25
+ ```
26
+
27
+ ## Basic Usage
28
+
29
+ ```ts
30
+ import { createVueVoxClient } from "@vuevox/sdk";
31
+
32
+ const vuevox = createVueVoxClient({
33
+ clientId: process.env.VUEVOX_CLIENT_ID!,
34
+ clientSecret: process.env.VUEVOX_CLIENT_SECRET!,
35
+ scope: "hello:read",
36
+ });
37
+
38
+ const hello = await vuevox.hello();
39
+ console.log(hello.message);
40
+ ```
41
+
42
+ The SDK requests and caches a short-lived access token using client credentials, then sends it as a bearer token for API calls.
43
+
44
+ ## Error Handling
45
+
46
+ API errors throw `VueVoxApiError`.
47
+
48
+ ```ts
49
+ import { VueVoxApiError, createVueVoxClient } from "@vuevox/sdk";
50
+
51
+ const vuevox = createVueVoxClient({
52
+ clientId: process.env.VUEVOX_CLIENT_ID!,
53
+ clientSecret: process.env.VUEVOX_CLIENT_SECRET!,
54
+ scope: "hello:read",
55
+ });
56
+
57
+ try {
58
+ await vuevox.hello();
59
+ } catch (error) {
60
+ if (error instanceof VueVoxApiError) {
61
+ console.error(error.status, error.code, error.message);
62
+ }
63
+
64
+ throw error;
65
+ }
66
+ ```
67
+
68
+ Common API error codes:
69
+
70
+ ```text
71
+ missing_token
72
+ invalid_token
73
+ invalid_client
74
+ invalid_scope
75
+ insufficient_scope
76
+ rate_limited
77
+ ```
78
+
79
+ ## Token Behavior
80
+
81
+ The SDK:
82
+
83
+ - Requests tokens using client credentials.
84
+ - Caches the access token in memory.
85
+ - Refreshes the token before expiry.
86
+ - Never stores credentials or tokens on disk.
87
+
88
+ ## Base URL
89
+
90
+ The SDK defaults to:
91
+
92
+ ```text
93
+ https://api.vuevox.com
94
+ ```
95
+
96
+ If VueVox support gives you a custom API base URL, pass it explicitly:
97
+
98
+ ```ts
99
+ const vuevox = createVueVoxClient({
100
+ baseUrl: "https://api.vuevox.com",
101
+ clientId: process.env.VUEVOX_CLIENT_ID!,
102
+ clientSecret: process.env.VUEVOX_CLIENT_SECRET!,
103
+ scope: "hello:read",
104
+ });
105
+ ```
106
+
107
+ ## Lower-Level Calls
108
+
109
+ For advanced integrations, `raw` exposes a typed lower-level API client.
110
+
111
+ ```ts
112
+ const { data, error } = await vuevox.raw.GET("/v1/hello", {
113
+ headers: {
114
+ Authorization: `Bearer ${await vuevox.getAccessToken()}`,
115
+ },
116
+ });
117
+ ```
@@ -0,0 +1,15 @@
1
+ import type { components, paths } from "./generated/schema.js";
2
+ type HelloResponse = components["schemas"]["HelloResponse"];
3
+ export interface VueVoxClientOptions {
4
+ baseUrl?: string;
5
+ clientId: string;
6
+ clientSecret: string;
7
+ scope?: string | string[];
8
+ fetch?: typeof fetch;
9
+ }
10
+ export declare function createVueVoxClient(options: VueVoxClientOptions): {
11
+ getAccessToken: () => Promise<string>;
12
+ hello: () => Promise<HelloResponse>;
13
+ raw: import("openapi-fetch").Client<paths, `${string}/${string}`>;
14
+ };
15
+ export {};
package/dist/client.js ADDED
@@ -0,0 +1,78 @@
1
+ import createClient from "openapi-fetch";
2
+ import { VueVoxApiError } from "./errors.js";
3
+ export function createVueVoxClient(options) {
4
+ const baseUrl = trimTrailingSlash(options.baseUrl ?? "https://api.vuevox.com");
5
+ const fetchFn = options.fetch ?? fetch;
6
+ const raw = createClient({ baseUrl, fetch: fetchFn });
7
+ let cachedToken = null;
8
+ async function getAccessToken() {
9
+ if (cachedToken && Date.now() < cachedToken.expiresAt - 30_000) {
10
+ return cachedToken.accessToken;
11
+ }
12
+ const response = await fetchFn(`${baseUrl}/oauth/token`, {
13
+ method: "POST",
14
+ headers: {
15
+ "Content-Type": "application/json",
16
+ },
17
+ body: JSON.stringify({
18
+ grant_type: "client_credentials",
19
+ client_id: options.clientId,
20
+ client_secret: options.clientSecret,
21
+ scope: formatScope(options.scope),
22
+ }),
23
+ });
24
+ const body = await parseJson(response);
25
+ if (!response.ok) {
26
+ 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);
28
+ }
29
+ if (!isTokenResponse(body)) {
30
+ throw new VueVoxApiError(response.status, "invalid_token_response", "VueVox returned an invalid token response.");
31
+ }
32
+ cachedToken = {
33
+ accessToken: body.access_token,
34
+ expiresAt: Date.now() + body.expires_in * 1000,
35
+ };
36
+ return cachedToken.accessToken;
37
+ }
38
+ async function hello() {
39
+ const accessToken = await getAccessToken();
40
+ const { data, error, response } = await raw.GET("/v1/hello", {
41
+ headers: {
42
+ Authorization: `Bearer ${accessToken}`,
43
+ },
44
+ });
45
+ if (error) {
46
+ throw new VueVoxApiError(response.status, error.error.code, error.error.message, error);
47
+ }
48
+ return data;
49
+ }
50
+ return {
51
+ getAccessToken,
52
+ hello,
53
+ raw,
54
+ };
55
+ }
56
+ function formatScope(scope) {
57
+ if (Array.isArray(scope)) {
58
+ return scope.join(" ");
59
+ }
60
+ return scope;
61
+ }
62
+ function trimTrailingSlash(value) {
63
+ return value.replace(/\/+$/, "");
64
+ }
65
+ async function parseJson(response) {
66
+ try {
67
+ return (await response.json());
68
+ }
69
+ catch {
70
+ return null;
71
+ }
72
+ }
73
+ function isTokenResponse(value) {
74
+ return Boolean(value && "access_token" in value);
75
+ }
76
+ function isErrorResponse(value) {
77
+ return Boolean(value && "error" in value);
78
+ }
@@ -0,0 +1,8 @@
1
+ import type { components } from "./generated/schema.js";
2
+ export type VueVoxErrorResponse = components["schemas"]["ErrorResponse"];
3
+ export declare class VueVoxApiError extends Error {
4
+ readonly status: number;
5
+ readonly code: string;
6
+ readonly response?: VueVoxErrorResponse;
7
+ constructor(status: number, code: string, message: string, response?: VueVoxErrorResponse);
8
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,12 @@
1
+ export class VueVoxApiError extends Error {
2
+ status;
3
+ code;
4
+ response;
5
+ constructor(status, code, message, response) {
6
+ super(message);
7
+ this.name = "VueVoxApiError";
8
+ this.status = status;
9
+ this.code = code;
10
+ this.response = response;
11
+ }
12
+ }
@@ -0,0 +1,200 @@
1
+ /**
2
+ * This file was auto-generated by openapi-typescript.
3
+ * Do not make direct changes to the file.
4
+ */
5
+
6
+ export interface paths {
7
+ "/oauth/token": {
8
+ parameters: {
9
+ query?: never;
10
+ header?: never;
11
+ path?: never;
12
+ cookie?: never;
13
+ };
14
+ get?: never;
15
+ put?: never;
16
+ /**
17
+ * Create an access token
18
+ * @description Issues a short-lived bearer token using OAuth client credentials.
19
+ */
20
+ post: operations["createAccessToken"];
21
+ delete?: never;
22
+ options?: never;
23
+ head?: never;
24
+ patch?: never;
25
+ trace?: never;
26
+ };
27
+ "/v1/hello": {
28
+ parameters: {
29
+ query?: never;
30
+ header?: never;
31
+ path?: never;
32
+ cookie?: never;
33
+ };
34
+ /**
35
+ * Return a hello world message
36
+ * @description Protected starter endpoint for validating developer API authentication and SDK wiring.
37
+ */
38
+ get: operations["getHello"];
39
+ put?: never;
40
+ post?: never;
41
+ delete?: never;
42
+ options?: never;
43
+ head?: never;
44
+ patch?: never;
45
+ trace?: never;
46
+ };
47
+ }
48
+ export type webhooks = Record<string, never>;
49
+ export interface components {
50
+ schemas: {
51
+ TokenRequest: {
52
+ /** @enum {string} */
53
+ grant_type: "client_credentials";
54
+ client_id: string;
55
+ client_secret: string;
56
+ /**
57
+ * @description Space-separated scopes requested for the access token.
58
+ * @example hello:read
59
+ */
60
+ scope?: string;
61
+ };
62
+ TokenResponse: {
63
+ access_token: string;
64
+ /** @enum {string} */
65
+ token_type: "Bearer";
66
+ /** @example 3600 */
67
+ expires_in: number;
68
+ /** @example hello:read */
69
+ scope: string;
70
+ };
71
+ HelloResponse: {
72
+ /** @example Hello world */
73
+ message: string;
74
+ };
75
+ ErrorResponse: {
76
+ error: components["schemas"]["Error"];
77
+ };
78
+ Error: {
79
+ /** @example invalid_token */
80
+ code: string;
81
+ /** @example Bearer token is invalid or expired. */
82
+ message: string;
83
+ /** @example req_123 */
84
+ requestId: string;
85
+ details?: {
86
+ [key: string]: unknown;
87
+ };
88
+ };
89
+ };
90
+ responses: never;
91
+ parameters: never;
92
+ requestBodies: never;
93
+ headers: never;
94
+ pathItems: never;
95
+ }
96
+ export type $defs = Record<string, never>;
97
+ export interface operations {
98
+ createAccessToken: {
99
+ parameters: {
100
+ query?: never;
101
+ header?: never;
102
+ path?: never;
103
+ cookie?: never;
104
+ };
105
+ requestBody: {
106
+ content: {
107
+ "application/json": components["schemas"]["TokenRequest"];
108
+ };
109
+ };
110
+ responses: {
111
+ /** @description Access token issued. */
112
+ 200: {
113
+ headers: {
114
+ [name: string]: unknown;
115
+ };
116
+ content: {
117
+ "application/json": components["schemas"]["TokenResponse"];
118
+ };
119
+ };
120
+ /** @description Requested scope is not allowed. */
121
+ 400: {
122
+ headers: {
123
+ [name: string]: unknown;
124
+ };
125
+ content: {
126
+ "application/json": components["schemas"]["ErrorResponse"];
127
+ };
128
+ };
129
+ /** @description Client authentication failed. */
130
+ 401: {
131
+ headers: {
132
+ [name: string]: unknown;
133
+ };
134
+ content: {
135
+ "application/json": components["schemas"]["ErrorResponse"];
136
+ };
137
+ };
138
+ /** @description Request body failed validation. */
139
+ 422: {
140
+ headers: {
141
+ [name: string]: unknown;
142
+ };
143
+ content: {
144
+ "application/json": components["schemas"]["ErrorResponse"];
145
+ };
146
+ };
147
+ };
148
+ };
149
+ getHello: {
150
+ parameters: {
151
+ query?: never;
152
+ header?: never;
153
+ path?: never;
154
+ cookie?: never;
155
+ };
156
+ requestBody?: never;
157
+ responses: {
158
+ /** @description Hello response. */
159
+ 200: {
160
+ headers: {
161
+ [name: string]: unknown;
162
+ };
163
+ content: {
164
+ "application/json": components["schemas"]["HelloResponse"];
165
+ };
166
+ };
167
+ /** @description Bearer token is missing, invalid, or expired. */
168
+ 401: {
169
+ headers: {
170
+ [name: string]: unknown;
171
+ };
172
+ content: {
173
+ "application/json": components["schemas"]["ErrorResponse"];
174
+ };
175
+ };
176
+ /** @description Bearer token does not include the required scope. */
177
+ 403: {
178
+ headers: {
179
+ [name: string]: unknown;
180
+ };
181
+ content: {
182
+ "application/json": components["schemas"]["ErrorResponse"];
183
+ };
184
+ };
185
+ /** @description Per-client rate limit exceeded. */
186
+ 429: {
187
+ headers: {
188
+ /** @description Seconds to wait before retrying. */
189
+ "Retry-After"?: string;
190
+ /** @description Request limit per minute for this API client. */
191
+ "X-RateLimit-Limit"?: string;
192
+ [name: string]: unknown;
193
+ };
194
+ content: {
195
+ "application/json": components["schemas"]["ErrorResponse"];
196
+ };
197
+ };
198
+ };
199
+ };
200
+ }
@@ -0,0 +1,4 @@
1
+ export { createVueVoxClient } from "./client.js";
2
+ export type { VueVoxClientOptions } from "./client.js";
3
+ export { VueVoxApiError } from "./errors.js";
4
+ export type { VueVoxErrorResponse } from "./errors.js";
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { createVueVoxClient } from "./client.js";
2
+ export { VueVoxApiError } from "./errors.js";
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@vuevox/sdk",
3
+ "version": "0.1.0",
4
+ "description": "TypeScript SDK for the VueVox Developer API.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "license": "MIT",
22
+ "keywords": [
23
+ "vuevox",
24
+ "sdk",
25
+ "api",
26
+ "typescript"
27
+ ],
28
+ "engines": {
29
+ "node": ">=18"
30
+ },
31
+ "scripts": {
32
+ "build": "rm -rf dist && tsc --outDir dist && mkdir -p dist/generated && cp src/generated/schema.d.ts dist/generated/schema.d.ts",
33
+ "typecheck": "tsc --noEmit",
34
+ "prepack": "npm run build",
35
+ "pack:dry-run": "npm pack --dry-run",
36
+ "publish:dry-run": "npm publish --dry-run --access public"
37
+ },
38
+ "dependencies": {
39
+ "openapi-fetch": "^0.14.0"
40
+ },
41
+ "devDependencies": {
42
+ "openapi-typescript": "^7.10.1",
43
+ "typescript": "^5.9.3"
44
+ }
45
+ }