@dispatchtickets/sdk 0.1.0 → 0.5.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 +168 -9
- package/dist/index.cjs +509 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +780 -20
- package/dist/index.d.ts +780 -20
- package/dist/index.js +497 -68
- package/dist/index.js.map +1 -1
- package/package.json +10 -2
package/dist/index.js
CHANGED
|
@@ -5,33 +5,45 @@ var DispatchTicketsError = class extends Error {
|
|
|
5
5
|
code;
|
|
6
6
|
statusCode;
|
|
7
7
|
details;
|
|
8
|
-
|
|
8
|
+
/** Request ID for debugging with support */
|
|
9
|
+
requestId;
|
|
10
|
+
constructor(message, code, statusCode, details, requestId) {
|
|
9
11
|
super(message);
|
|
10
12
|
this.name = "DispatchTicketsError";
|
|
11
13
|
this.code = code;
|
|
12
14
|
this.statusCode = statusCode;
|
|
13
15
|
this.details = details;
|
|
16
|
+
this.requestId = requestId;
|
|
14
17
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
15
18
|
}
|
|
16
19
|
};
|
|
17
20
|
var AuthenticationError = class extends DispatchTicketsError {
|
|
18
|
-
constructor(message = "Invalid or missing API key") {
|
|
19
|
-
super(message, "authentication_error", 401);
|
|
21
|
+
constructor(message = "Invalid or missing API key", requestId) {
|
|
22
|
+
super(message, "authentication_error", 401, void 0, requestId);
|
|
20
23
|
this.name = "AuthenticationError";
|
|
21
24
|
}
|
|
22
25
|
};
|
|
23
26
|
var RateLimitError = class extends DispatchTicketsError {
|
|
24
27
|
retryAfter;
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
/** Rate limit ceiling */
|
|
29
|
+
limit;
|
|
30
|
+
/** Remaining requests in current window */
|
|
31
|
+
remaining;
|
|
32
|
+
/** Unix timestamp when rate limit resets */
|
|
33
|
+
reset;
|
|
34
|
+
constructor(message = "Rate limit exceeded", retryAfter, requestId, rateLimitInfo) {
|
|
35
|
+
super(message, "rate_limit_error", 429, void 0, requestId);
|
|
27
36
|
this.name = "RateLimitError";
|
|
28
37
|
this.retryAfter = retryAfter;
|
|
38
|
+
this.limit = rateLimitInfo?.limit;
|
|
39
|
+
this.remaining = rateLimitInfo?.remaining;
|
|
40
|
+
this.reset = rateLimitInfo?.reset;
|
|
29
41
|
}
|
|
30
42
|
};
|
|
31
43
|
var ValidationError = class extends DispatchTicketsError {
|
|
32
44
|
errors;
|
|
33
|
-
constructor(message = "Validation failed", errors) {
|
|
34
|
-
super(message, "validation_error", 400, { errors });
|
|
45
|
+
constructor(message = "Validation failed", errors, requestId) {
|
|
46
|
+
super(message, "validation_error", 400, { errors }, requestId);
|
|
35
47
|
this.name = "ValidationError";
|
|
36
48
|
this.errors = errors;
|
|
37
49
|
}
|
|
@@ -39,22 +51,22 @@ var ValidationError = class extends DispatchTicketsError {
|
|
|
39
51
|
var NotFoundError = class extends DispatchTicketsError {
|
|
40
52
|
resourceType;
|
|
41
53
|
resourceId;
|
|
42
|
-
constructor(message = "Resource not found", resourceType, resourceId) {
|
|
43
|
-
super(message, "not_found", 404, { resourceType, resourceId });
|
|
54
|
+
constructor(message = "Resource not found", resourceType, resourceId, requestId) {
|
|
55
|
+
super(message, "not_found", 404, { resourceType, resourceId }, requestId);
|
|
44
56
|
this.name = "NotFoundError";
|
|
45
57
|
this.resourceType = resourceType;
|
|
46
58
|
this.resourceId = resourceId;
|
|
47
59
|
}
|
|
48
60
|
};
|
|
49
61
|
var ConflictError = class extends DispatchTicketsError {
|
|
50
|
-
constructor(message = "Resource conflict") {
|
|
51
|
-
super(message, "conflict", 409);
|
|
62
|
+
constructor(message = "Resource conflict", requestId) {
|
|
63
|
+
super(message, "conflict", 409, void 0, requestId);
|
|
52
64
|
this.name = "ConflictError";
|
|
53
65
|
}
|
|
54
66
|
};
|
|
55
67
|
var ServerError = class extends DispatchTicketsError {
|
|
56
|
-
constructor(message = "Internal server error", statusCode = 500) {
|
|
57
|
-
super(message, "server_error", statusCode);
|
|
68
|
+
constructor(message = "Internal server error", statusCode = 500, requestId) {
|
|
69
|
+
super(message, "server_error", statusCode, void 0, requestId);
|
|
58
70
|
this.name = "ServerError";
|
|
59
71
|
}
|
|
60
72
|
};
|
|
@@ -70,12 +82,68 @@ var NetworkError = class extends DispatchTicketsError {
|
|
|
70
82
|
this.name = "NetworkError";
|
|
71
83
|
}
|
|
72
84
|
};
|
|
85
|
+
function isDispatchTicketsError(error) {
|
|
86
|
+
return error instanceof DispatchTicketsError;
|
|
87
|
+
}
|
|
88
|
+
function isAuthenticationError(error) {
|
|
89
|
+
return error instanceof AuthenticationError;
|
|
90
|
+
}
|
|
91
|
+
function isRateLimitError(error) {
|
|
92
|
+
return error instanceof RateLimitError;
|
|
93
|
+
}
|
|
94
|
+
function isValidationError(error) {
|
|
95
|
+
return error instanceof ValidationError;
|
|
96
|
+
}
|
|
97
|
+
function isNotFoundError(error) {
|
|
98
|
+
return error instanceof NotFoundError;
|
|
99
|
+
}
|
|
100
|
+
function isConflictError(error) {
|
|
101
|
+
return error instanceof ConflictError;
|
|
102
|
+
}
|
|
103
|
+
function isServerError(error) {
|
|
104
|
+
return error instanceof ServerError;
|
|
105
|
+
}
|
|
106
|
+
function isTimeoutError(error) {
|
|
107
|
+
return error instanceof TimeoutError;
|
|
108
|
+
}
|
|
109
|
+
function isNetworkError(error) {
|
|
110
|
+
return error instanceof NetworkError;
|
|
111
|
+
}
|
|
73
112
|
|
|
74
113
|
// src/utils/http.ts
|
|
75
114
|
var HttpClient = class {
|
|
76
115
|
config;
|
|
116
|
+
fetchFn;
|
|
117
|
+
retryConfig;
|
|
118
|
+
/** Rate limit info from the last response */
|
|
119
|
+
_lastRateLimit;
|
|
120
|
+
/** Request ID from the last response */
|
|
121
|
+
_lastRequestId;
|
|
77
122
|
constructor(config) {
|
|
78
123
|
this.config = config;
|
|
124
|
+
this.fetchFn = config.fetch ?? fetch;
|
|
125
|
+
this.retryConfig = {
|
|
126
|
+
maxRetries: config.retry?.maxRetries ?? config.maxRetries,
|
|
127
|
+
retryableStatuses: config.retry?.retryableStatuses ?? [429, 500, 502, 503, 504],
|
|
128
|
+
retryOnNetworkError: config.retry?.retryOnNetworkError ?? true,
|
|
129
|
+
retryOnTimeout: config.retry?.retryOnTimeout ?? true,
|
|
130
|
+
initialDelayMs: config.retry?.initialDelayMs ?? 1e3,
|
|
131
|
+
maxDelayMs: config.retry?.maxDelayMs ?? 3e4,
|
|
132
|
+
backoffMultiplier: config.retry?.backoffMultiplier ?? 2,
|
|
133
|
+
jitter: config.retry?.jitter ?? 0.25
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Get rate limit info from the last response
|
|
138
|
+
*/
|
|
139
|
+
get lastRateLimit() {
|
|
140
|
+
return this._lastRateLimit;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Get request ID from the last response
|
|
144
|
+
*/
|
|
145
|
+
get lastRequestId() {
|
|
146
|
+
return this._lastRequestId;
|
|
79
147
|
}
|
|
80
148
|
/**
|
|
81
149
|
* Execute an HTTP request with retry logic
|
|
@@ -85,43 +153,89 @@ var HttpClient = class {
|
|
|
85
153
|
const headers = this.buildHeaders(options.headers, options.idempotencyKey);
|
|
86
154
|
let lastError;
|
|
87
155
|
let attempt = 0;
|
|
88
|
-
while (attempt <= this.
|
|
156
|
+
while (attempt <= this.retryConfig.maxRetries) {
|
|
157
|
+
const requestContext = {
|
|
158
|
+
method: options.method,
|
|
159
|
+
url,
|
|
160
|
+
headers,
|
|
161
|
+
body: options.body,
|
|
162
|
+
attempt
|
|
163
|
+
};
|
|
89
164
|
try {
|
|
90
|
-
|
|
91
|
-
|
|
165
|
+
if (this.config.hooks?.onRequest) {
|
|
166
|
+
await this.config.hooks.onRequest(requestContext);
|
|
167
|
+
}
|
|
168
|
+
const startTime = Date.now();
|
|
169
|
+
const response = await this.executeRequest(
|
|
170
|
+
url,
|
|
171
|
+
options.method,
|
|
172
|
+
headers,
|
|
173
|
+
options.body,
|
|
174
|
+
options.signal
|
|
175
|
+
);
|
|
176
|
+
const durationMs = Date.now() - startTime;
|
|
177
|
+
const result = await this.handleResponse(response, requestContext, durationMs);
|
|
178
|
+
return result;
|
|
92
179
|
} catch (error) {
|
|
93
180
|
lastError = error;
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
throw error;
|
|
97
|
-
}
|
|
98
|
-
if (error instanceof RateLimitError && error.retryAfter) {
|
|
99
|
-
if (attempt < this.config.maxRetries) {
|
|
100
|
-
await this.sleep(error.retryAfter * 1e3);
|
|
101
|
-
attempt++;
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
if (error instanceof ServerError) {
|
|
106
|
-
if (attempt < this.config.maxRetries) {
|
|
107
|
-
await this.sleep(this.calculateBackoff(attempt));
|
|
108
|
-
attempt++;
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
181
|
+
if (this.config.hooks?.onError) {
|
|
182
|
+
await this.config.hooks.onError(lastError, requestContext);
|
|
112
183
|
}
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
continue;
|
|
184
|
+
if (attempt < this.retryConfig.maxRetries && this.shouldRetry(lastError)) {
|
|
185
|
+
const delay = this.calculateDelay(lastError, attempt);
|
|
186
|
+
if (this.config.hooks?.onRetry) {
|
|
187
|
+
await this.config.hooks.onRetry(requestContext, lastError, delay);
|
|
118
188
|
}
|
|
189
|
+
await this.sleep(delay);
|
|
190
|
+
attempt++;
|
|
191
|
+
continue;
|
|
119
192
|
}
|
|
120
|
-
throw
|
|
193
|
+
throw lastError;
|
|
121
194
|
}
|
|
122
195
|
}
|
|
123
196
|
throw lastError || new NetworkError("Request failed after retries");
|
|
124
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Execute request and return response with rate limit info
|
|
200
|
+
*/
|
|
201
|
+
async requestWithRateLimit(options) {
|
|
202
|
+
const data = await this.request(options);
|
|
203
|
+
return {
|
|
204
|
+
data,
|
|
205
|
+
rateLimit: this._lastRateLimit,
|
|
206
|
+
requestId: this._lastRequestId
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
shouldRetry(error) {
|
|
210
|
+
if (error instanceof AuthenticationError || error instanceof ValidationError || error instanceof NotFoundError || error instanceof ConflictError) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
if (error instanceof RateLimitError) {
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
if (error instanceof ServerError && error.statusCode) {
|
|
217
|
+
return this.retryConfig.retryableStatuses.includes(error.statusCode);
|
|
218
|
+
}
|
|
219
|
+
if (error instanceof NetworkError) {
|
|
220
|
+
return this.retryConfig.retryOnNetworkError;
|
|
221
|
+
}
|
|
222
|
+
if (error instanceof TimeoutError) {
|
|
223
|
+
return this.retryConfig.retryOnTimeout;
|
|
224
|
+
}
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
calculateDelay(error, attempt) {
|
|
228
|
+
if (error instanceof RateLimitError && error.retryAfter) {
|
|
229
|
+
return error.retryAfter * 1e3;
|
|
230
|
+
}
|
|
231
|
+
const baseDelay = this.retryConfig.initialDelayMs;
|
|
232
|
+
const delay = Math.min(
|
|
233
|
+
baseDelay * Math.pow(this.retryConfig.backoffMultiplier, attempt),
|
|
234
|
+
this.retryConfig.maxDelayMs
|
|
235
|
+
);
|
|
236
|
+
const jitter = delay * this.retryConfig.jitter * Math.random();
|
|
237
|
+
return delay + jitter;
|
|
238
|
+
}
|
|
125
239
|
buildUrl(path, query) {
|
|
126
240
|
const url = new URL(path, this.config.baseUrl);
|
|
127
241
|
if (query) {
|
|
@@ -145,9 +259,11 @@ var HttpClient = class {
|
|
|
145
259
|
}
|
|
146
260
|
return headers;
|
|
147
261
|
}
|
|
148
|
-
async executeRequest(url, method, headers, body) {
|
|
262
|
+
async executeRequest(url, method, headers, body, userSignal) {
|
|
149
263
|
const controller = new AbortController();
|
|
150
264
|
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
265
|
+
const abortHandler = () => controller.abort();
|
|
266
|
+
userSignal?.addEventListener("abort", abortHandler);
|
|
151
267
|
try {
|
|
152
268
|
if (this.config.debug) {
|
|
153
269
|
console.log(`[DispatchTickets] ${method} ${url}`);
|
|
@@ -155,7 +271,10 @@ var HttpClient = class {
|
|
|
155
271
|
console.log("[DispatchTickets] Body:", JSON.stringify(body, null, 2));
|
|
156
272
|
}
|
|
157
273
|
}
|
|
158
|
-
|
|
274
|
+
if (userSignal?.aborted) {
|
|
275
|
+
throw new Error("Request aborted");
|
|
276
|
+
}
|
|
277
|
+
const response = await this.fetchFn(url, {
|
|
159
278
|
method,
|
|
160
279
|
headers,
|
|
161
280
|
body: body ? JSON.stringify(body) : void 0,
|
|
@@ -165,6 +284,9 @@ var HttpClient = class {
|
|
|
165
284
|
} catch (error) {
|
|
166
285
|
if (error instanceof Error) {
|
|
167
286
|
if (error.name === "AbortError") {
|
|
287
|
+
if (userSignal?.aborted) {
|
|
288
|
+
throw new NetworkError("Request aborted by user");
|
|
289
|
+
}
|
|
168
290
|
throw new TimeoutError(`Request timed out after ${this.config.timeout}ms`);
|
|
169
291
|
}
|
|
170
292
|
throw new NetworkError(error.message);
|
|
@@ -172,11 +294,42 @@ var HttpClient = class {
|
|
|
172
294
|
throw new NetworkError("Unknown network error");
|
|
173
295
|
} finally {
|
|
174
296
|
clearTimeout(timeoutId);
|
|
297
|
+
userSignal?.removeEventListener("abort", abortHandler);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
extractRateLimitInfo(response) {
|
|
301
|
+
const limit = response.headers.get("x-ratelimit-limit");
|
|
302
|
+
const remaining = response.headers.get("x-ratelimit-remaining");
|
|
303
|
+
const reset = response.headers.get("x-ratelimit-reset");
|
|
304
|
+
if (limit && remaining && reset) {
|
|
305
|
+
return {
|
|
306
|
+
limit: parseInt(limit, 10),
|
|
307
|
+
remaining: parseInt(remaining, 10),
|
|
308
|
+
reset: parseInt(reset, 10)
|
|
309
|
+
};
|
|
175
310
|
}
|
|
311
|
+
return void 0;
|
|
176
312
|
}
|
|
177
|
-
async handleResponse(response) {
|
|
313
|
+
async handleResponse(response, requestContext, durationMs) {
|
|
178
314
|
const contentType = response.headers.get("content-type");
|
|
179
315
|
const isJson = contentType?.includes("application/json");
|
|
316
|
+
const requestId = response.headers.get("x-request-id") ?? void 0;
|
|
317
|
+
const rateLimitInfo = this.extractRateLimitInfo(response);
|
|
318
|
+
this._lastRequestId = requestId;
|
|
319
|
+
this._lastRateLimit = rateLimitInfo;
|
|
320
|
+
if (this.config.debug && requestId) {
|
|
321
|
+
console.log(`[DispatchTickets] Request ID: ${requestId}`);
|
|
322
|
+
}
|
|
323
|
+
if (response.ok && this.config.hooks?.onResponse) {
|
|
324
|
+
await this.config.hooks.onResponse({
|
|
325
|
+
request: requestContext,
|
|
326
|
+
status: response.status,
|
|
327
|
+
headers: response.headers,
|
|
328
|
+
requestId,
|
|
329
|
+
rateLimit: rateLimitInfo,
|
|
330
|
+
durationMs
|
|
331
|
+
});
|
|
332
|
+
}
|
|
180
333
|
if (response.ok) {
|
|
181
334
|
if (response.status === 204 || !isJson) {
|
|
182
335
|
return void 0;
|
|
@@ -193,31 +346,36 @@ var HttpClient = class {
|
|
|
193
346
|
const message = errorData.message || errorData.error || response.statusText;
|
|
194
347
|
switch (response.status) {
|
|
195
348
|
case 401:
|
|
196
|
-
throw new AuthenticationError(message);
|
|
349
|
+
throw new AuthenticationError(message, requestId);
|
|
197
350
|
case 400:
|
|
198
351
|
case 422:
|
|
199
|
-
throw new ValidationError(message, errorData.errors);
|
|
352
|
+
throw new ValidationError(message, errorData.errors, requestId);
|
|
200
353
|
case 404:
|
|
201
|
-
throw new NotFoundError(message);
|
|
354
|
+
throw new NotFoundError(message, void 0, void 0, requestId);
|
|
202
355
|
case 409:
|
|
203
|
-
throw new ConflictError(message);
|
|
356
|
+
throw new ConflictError(message, requestId);
|
|
204
357
|
case 429: {
|
|
205
358
|
const retryAfter = response.headers.get("retry-after");
|
|
206
|
-
throw new RateLimitError(
|
|
359
|
+
throw new RateLimitError(
|
|
360
|
+
message,
|
|
361
|
+
retryAfter ? parseInt(retryAfter, 10) : void 0,
|
|
362
|
+
requestId,
|
|
363
|
+
rateLimitInfo
|
|
364
|
+
);
|
|
207
365
|
}
|
|
208
366
|
default:
|
|
209
367
|
if (response.status >= 500) {
|
|
210
|
-
throw new ServerError(message, response.status);
|
|
368
|
+
throw new ServerError(message, response.status, requestId);
|
|
211
369
|
}
|
|
212
|
-
throw new DispatchTicketsError(
|
|
370
|
+
throw new DispatchTicketsError(
|
|
371
|
+
message,
|
|
372
|
+
"api_error",
|
|
373
|
+
response.status,
|
|
374
|
+
errorData,
|
|
375
|
+
requestId
|
|
376
|
+
);
|
|
213
377
|
}
|
|
214
378
|
}
|
|
215
|
-
calculateBackoff(attempt) {
|
|
216
|
-
const baseDelay = 1e3;
|
|
217
|
-
const maxDelay = 3e4;
|
|
218
|
-
const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
|
|
219
|
-
return delay + Math.random() * delay * 0.25;
|
|
220
|
-
}
|
|
221
379
|
sleep(ms) {
|
|
222
380
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
223
381
|
}
|
|
@@ -229,11 +387,12 @@ var BaseResource = class {
|
|
|
229
387
|
constructor(http) {
|
|
230
388
|
this.http = http;
|
|
231
389
|
}
|
|
232
|
-
async _get(path, query) {
|
|
390
|
+
async _get(path, query, options) {
|
|
233
391
|
return this.http.request({
|
|
234
392
|
method: "GET",
|
|
235
393
|
path,
|
|
236
|
-
query
|
|
394
|
+
query,
|
|
395
|
+
signal: options?.signal
|
|
237
396
|
});
|
|
238
397
|
}
|
|
239
398
|
async _post(path, body, options) {
|
|
@@ -242,32 +401,82 @@ var BaseResource = class {
|
|
|
242
401
|
path,
|
|
243
402
|
body,
|
|
244
403
|
idempotencyKey: options?.idempotencyKey,
|
|
245
|
-
query: options?.query
|
|
404
|
+
query: options?.query,
|
|
405
|
+
signal: options?.signal
|
|
246
406
|
});
|
|
247
407
|
}
|
|
248
|
-
async _patch(path, body) {
|
|
408
|
+
async _patch(path, body, options) {
|
|
249
409
|
return this.http.request({
|
|
250
410
|
method: "PATCH",
|
|
251
411
|
path,
|
|
252
|
-
body
|
|
412
|
+
body,
|
|
413
|
+
signal: options?.signal
|
|
253
414
|
});
|
|
254
415
|
}
|
|
255
|
-
async _put(path, body) {
|
|
416
|
+
async _put(path, body, options) {
|
|
256
417
|
return this.http.request({
|
|
257
418
|
method: "PUT",
|
|
258
419
|
path,
|
|
259
|
-
body
|
|
420
|
+
body,
|
|
421
|
+
signal: options?.signal
|
|
260
422
|
});
|
|
261
423
|
}
|
|
262
|
-
async _delete(path, query) {
|
|
424
|
+
async _delete(path, query, options) {
|
|
263
425
|
return this.http.request({
|
|
264
426
|
method: "DELETE",
|
|
265
427
|
path,
|
|
266
|
-
query
|
|
428
|
+
query,
|
|
429
|
+
signal: options?.signal
|
|
267
430
|
});
|
|
268
431
|
}
|
|
269
432
|
};
|
|
270
433
|
|
|
434
|
+
// src/resources/accounts.ts
|
|
435
|
+
var AccountsResource = class extends BaseResource {
|
|
436
|
+
/**
|
|
437
|
+
* Get the current account
|
|
438
|
+
*/
|
|
439
|
+
async me() {
|
|
440
|
+
return this._get("/accounts/me");
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Get usage statistics for the current account
|
|
444
|
+
*/
|
|
445
|
+
async getUsage() {
|
|
446
|
+
return this._get("/accounts/me/usage");
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* List all API keys for the current account
|
|
450
|
+
*/
|
|
451
|
+
async listApiKeys() {
|
|
452
|
+
return this._get("/accounts/me/api-keys");
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Create a new API key
|
|
456
|
+
*
|
|
457
|
+
* Note: The full key value is only returned once on creation.
|
|
458
|
+
* Store it securely as it cannot be retrieved again.
|
|
459
|
+
*/
|
|
460
|
+
async createApiKey(data) {
|
|
461
|
+
return this._post("/accounts/me/api-keys", data);
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Update the brand scope for an API key
|
|
465
|
+
*/
|
|
466
|
+
async updateApiKeyScope(keyId, data) {
|
|
467
|
+
return this._patch(
|
|
468
|
+
`/accounts/me/api-keys/${keyId}/scope`,
|
|
469
|
+
data
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Revoke an API key
|
|
474
|
+
*/
|
|
475
|
+
async revokeApiKey(keyId) {
|
|
476
|
+
await this._delete(`/accounts/me/api-keys/${keyId}`);
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
|
|
271
480
|
// src/resources/brands.ts
|
|
272
481
|
var BrandsResource = class extends BaseResource {
|
|
273
482
|
/**
|
|
@@ -318,6 +527,28 @@ var BrandsResource = class extends BaseResource {
|
|
|
318
527
|
async updateSchema(brandId, schema) {
|
|
319
528
|
return this._put(`/brands/${brandId}/schema`, schema);
|
|
320
529
|
}
|
|
530
|
+
/**
|
|
531
|
+
* Get the inbound email address for a brand
|
|
532
|
+
*
|
|
533
|
+
* Emails sent to this address will automatically create tickets.
|
|
534
|
+
*
|
|
535
|
+
* @param brandId - The brand ID
|
|
536
|
+
* @param domain - Optional custom inbound domain (default: inbound.dispatchtickets.com)
|
|
537
|
+
* @returns The inbound email address
|
|
538
|
+
*
|
|
539
|
+
* @example
|
|
540
|
+
* ```typescript
|
|
541
|
+
* const email = client.brands.getInboundEmail('br_abc123');
|
|
542
|
+
* // Returns: br_abc123@inbound.dispatchtickets.com
|
|
543
|
+
*
|
|
544
|
+
* // With custom domain:
|
|
545
|
+
* const customEmail = client.brands.getInboundEmail('br_abc123', 'support.mycompany.com');
|
|
546
|
+
* // Returns: br_abc123@support.mycompany.com
|
|
547
|
+
* ```
|
|
548
|
+
*/
|
|
549
|
+
getInboundEmail(brandId, domain = "inbound.dispatchtickets.com") {
|
|
550
|
+
return `${brandId}@${domain}`;
|
|
551
|
+
}
|
|
321
552
|
};
|
|
322
553
|
|
|
323
554
|
// src/resources/tickets.ts
|
|
@@ -832,46 +1063,209 @@ var webhookUtils = {
|
|
|
832
1063
|
// src/client.ts
|
|
833
1064
|
var DispatchTickets = class {
|
|
834
1065
|
http;
|
|
1066
|
+
/**
|
|
1067
|
+
* Accounts resource for managing the current account and API keys
|
|
1068
|
+
*
|
|
1069
|
+
* @example
|
|
1070
|
+
* ```typescript
|
|
1071
|
+
* // Get current account
|
|
1072
|
+
* const account = await client.accounts.me();
|
|
1073
|
+
*
|
|
1074
|
+
* // Get usage statistics
|
|
1075
|
+
* const usage = await client.accounts.getUsage();
|
|
1076
|
+
*
|
|
1077
|
+
* // Create a new API key
|
|
1078
|
+
* const newKey = await client.accounts.createApiKey({
|
|
1079
|
+
* name: 'Production',
|
|
1080
|
+
* allBrands: true,
|
|
1081
|
+
* });
|
|
1082
|
+
* ```
|
|
1083
|
+
*/
|
|
1084
|
+
accounts;
|
|
835
1085
|
/**
|
|
836
1086
|
* Brands (workspaces) resource
|
|
1087
|
+
*
|
|
1088
|
+
* Brands are isolated containers for tickets. Each brand can have its own
|
|
1089
|
+
* email address, categories, tags, and settings.
|
|
1090
|
+
*
|
|
1091
|
+
* @example
|
|
1092
|
+
* ```typescript
|
|
1093
|
+
* // List all brands
|
|
1094
|
+
* const brands = await client.brands.list();
|
|
1095
|
+
*
|
|
1096
|
+
* // Create a new brand
|
|
1097
|
+
* const brand = await client.brands.create({
|
|
1098
|
+
* name: 'Acme Support',
|
|
1099
|
+
* slug: 'acme',
|
|
1100
|
+
* });
|
|
1101
|
+
*
|
|
1102
|
+
* // Get inbound email address
|
|
1103
|
+
* const email = client.brands.getInboundEmail('br_abc123');
|
|
1104
|
+
* // Returns: br_abc123@inbound.dispatchtickets.com
|
|
1105
|
+
* ```
|
|
837
1106
|
*/
|
|
838
1107
|
brands;
|
|
839
1108
|
/**
|
|
840
1109
|
* Tickets resource
|
|
1110
|
+
*
|
|
1111
|
+
* @example
|
|
1112
|
+
* ```typescript
|
|
1113
|
+
* // Create a ticket
|
|
1114
|
+
* const ticket = await client.tickets.create('ws_abc123', {
|
|
1115
|
+
* title: 'Issue with billing',
|
|
1116
|
+
* body: 'I was charged twice...',
|
|
1117
|
+
* priority: 'high',
|
|
1118
|
+
* });
|
|
1119
|
+
*
|
|
1120
|
+
* // Iterate through all tickets
|
|
1121
|
+
* for await (const ticket of client.tickets.list('ws_abc123', { status: 'open' })) {
|
|
1122
|
+
* console.log(ticket.title);
|
|
1123
|
+
* }
|
|
1124
|
+
* ```
|
|
841
1125
|
*/
|
|
842
1126
|
tickets;
|
|
843
1127
|
/**
|
|
844
1128
|
* Comments resource
|
|
1129
|
+
*
|
|
1130
|
+
* @example
|
|
1131
|
+
* ```typescript
|
|
1132
|
+
* // Add a comment
|
|
1133
|
+
* const comment = await client.comments.create('ws_abc123', 'tkt_xyz', {
|
|
1134
|
+
* body: 'Thanks for your patience!',
|
|
1135
|
+
* authorType: 'AGENT',
|
|
1136
|
+
* });
|
|
1137
|
+
*
|
|
1138
|
+
* // List comments
|
|
1139
|
+
* const comments = await client.comments.list('ws_abc123', 'tkt_xyz');
|
|
1140
|
+
* ```
|
|
845
1141
|
*/
|
|
846
1142
|
comments;
|
|
847
1143
|
/**
|
|
848
1144
|
* Attachments resource
|
|
1145
|
+
*
|
|
1146
|
+
* @example
|
|
1147
|
+
* ```typescript
|
|
1148
|
+
* // Simple upload
|
|
1149
|
+
* const attachment = await client.attachments.upload(
|
|
1150
|
+
* 'ws_abc123',
|
|
1151
|
+
* 'tkt_xyz',
|
|
1152
|
+
* fileBuffer,
|
|
1153
|
+
* 'document.pdf',
|
|
1154
|
+
* 'application/pdf'
|
|
1155
|
+
* );
|
|
1156
|
+
*
|
|
1157
|
+
* // Get download URL
|
|
1158
|
+
* const { downloadUrl } = await client.attachments.get('ws_abc123', 'tkt_xyz', 'att_abc');
|
|
1159
|
+
* ```
|
|
849
1160
|
*/
|
|
850
1161
|
attachments;
|
|
851
1162
|
/**
|
|
852
1163
|
* Webhooks resource
|
|
1164
|
+
*
|
|
1165
|
+
* @example
|
|
1166
|
+
* ```typescript
|
|
1167
|
+
* // Create a webhook
|
|
1168
|
+
* const webhook = await client.webhooks.create('ws_abc123', {
|
|
1169
|
+
* url: 'https://example.com/webhook',
|
|
1170
|
+
* secret: 'your-secret',
|
|
1171
|
+
* events: ['ticket.created', 'ticket.updated'],
|
|
1172
|
+
* });
|
|
1173
|
+
* ```
|
|
853
1174
|
*/
|
|
854
1175
|
webhooks;
|
|
855
1176
|
/**
|
|
856
1177
|
* Categories resource
|
|
1178
|
+
*
|
|
1179
|
+
* @example
|
|
1180
|
+
* ```typescript
|
|
1181
|
+
* // Create a category
|
|
1182
|
+
* await client.categories.create('ws_abc123', { name: 'Billing', color: '#ef4444' });
|
|
1183
|
+
*
|
|
1184
|
+
* // Get category stats
|
|
1185
|
+
* const stats = await client.categories.getStats('ws_abc123');
|
|
1186
|
+
* ```
|
|
857
1187
|
*/
|
|
858
1188
|
categories;
|
|
859
1189
|
/**
|
|
860
1190
|
* Tags resource
|
|
1191
|
+
*
|
|
1192
|
+
* @example
|
|
1193
|
+
* ```typescript
|
|
1194
|
+
* // Create a tag
|
|
1195
|
+
* await client.tags.create('ws_abc123', { name: 'urgent', color: '#f59e0b' });
|
|
1196
|
+
*
|
|
1197
|
+
* // Merge tags
|
|
1198
|
+
* await client.tags.merge('ws_abc123', 'tag_target', ['tag_source1', 'tag_source2']);
|
|
1199
|
+
* ```
|
|
861
1200
|
*/
|
|
862
1201
|
tags;
|
|
863
1202
|
/**
|
|
864
1203
|
* Customers resource
|
|
1204
|
+
*
|
|
1205
|
+
* @example
|
|
1206
|
+
* ```typescript
|
|
1207
|
+
* // Create a customer
|
|
1208
|
+
* const customer = await client.customers.create('ws_abc123', {
|
|
1209
|
+
* email: 'user@example.com',
|
|
1210
|
+
* name: 'Jane Doe',
|
|
1211
|
+
* });
|
|
1212
|
+
*
|
|
1213
|
+
* // Search customers
|
|
1214
|
+
* const results = await client.customers.search('ws_abc123', 'jane');
|
|
1215
|
+
* ```
|
|
865
1216
|
*/
|
|
866
1217
|
customers;
|
|
867
1218
|
/**
|
|
868
1219
|
* Custom fields resource
|
|
1220
|
+
*
|
|
1221
|
+
* @example
|
|
1222
|
+
* ```typescript
|
|
1223
|
+
* // Get all field definitions
|
|
1224
|
+
* const fields = await client.fields.getAll('ws_abc123');
|
|
1225
|
+
*
|
|
1226
|
+
* // Create a field
|
|
1227
|
+
* await client.fields.create('ws_abc123', 'ticket', {
|
|
1228
|
+
* key: 'order_id',
|
|
1229
|
+
* label: 'Order ID',
|
|
1230
|
+
* type: 'text',
|
|
1231
|
+
* required: true,
|
|
1232
|
+
* });
|
|
1233
|
+
* ```
|
|
869
1234
|
*/
|
|
870
1235
|
fields;
|
|
871
1236
|
/**
|
|
872
1237
|
* Static webhook utilities
|
|
1238
|
+
*
|
|
1239
|
+
* @example
|
|
1240
|
+
* ```typescript
|
|
1241
|
+
* // Verify webhook signature
|
|
1242
|
+
* const isValid = DispatchTickets.webhooks.verifySignature(
|
|
1243
|
+
* rawBody,
|
|
1244
|
+
* req.headers['x-dispatch-signature'],
|
|
1245
|
+
* 'your-secret'
|
|
1246
|
+
* );
|
|
1247
|
+
*
|
|
1248
|
+
* // Generate signature for testing
|
|
1249
|
+
* const signature = DispatchTickets.webhooks.generateSignature(
|
|
1250
|
+
* JSON.stringify(payload),
|
|
1251
|
+
* 'your-secret'
|
|
1252
|
+
* );
|
|
1253
|
+
* ```
|
|
873
1254
|
*/
|
|
874
1255
|
static webhooks = webhookUtils;
|
|
1256
|
+
/**
|
|
1257
|
+
* Create a new Dispatch Tickets client
|
|
1258
|
+
*
|
|
1259
|
+
* @param config - Client configuration options
|
|
1260
|
+
* @throws Error if API key is not provided
|
|
1261
|
+
*
|
|
1262
|
+
* @example
|
|
1263
|
+
* ```typescript
|
|
1264
|
+
* const client = new DispatchTickets({
|
|
1265
|
+
* apiKey: 'sk_live_...',
|
|
1266
|
+
* });
|
|
1267
|
+
* ```
|
|
1268
|
+
*/
|
|
875
1269
|
constructor(config) {
|
|
876
1270
|
if (!config.apiKey) {
|
|
877
1271
|
throw new Error("API key is required");
|
|
@@ -880,10 +1274,14 @@ var DispatchTickets = class {
|
|
|
880
1274
|
baseUrl: config.baseUrl || "https://dispatch-tickets-api.onrender.com/v1",
|
|
881
1275
|
apiKey: config.apiKey,
|
|
882
1276
|
timeout: config.timeout ?? 3e4,
|
|
883
|
-
maxRetries: config.maxRetries ?? 3,
|
|
884
|
-
debug: config.debug ?? false
|
|
1277
|
+
maxRetries: config.maxRetries ?? config.retry?.maxRetries ?? 3,
|
|
1278
|
+
debug: config.debug ?? false,
|
|
1279
|
+
fetch: config.fetch,
|
|
1280
|
+
retry: config.retry,
|
|
1281
|
+
hooks: config.hooks
|
|
885
1282
|
};
|
|
886
1283
|
this.http = new HttpClient(httpConfig);
|
|
1284
|
+
this.accounts = new AccountsResource(this.http);
|
|
887
1285
|
this.brands = new BrandsResource(this.http);
|
|
888
1286
|
this.tickets = new TicketsResource(this.http);
|
|
889
1287
|
this.comments = new CommentsResource(this.http);
|
|
@@ -896,6 +1294,37 @@ var DispatchTickets = class {
|
|
|
896
1294
|
}
|
|
897
1295
|
};
|
|
898
1296
|
|
|
1297
|
+
// src/types/events.ts
|
|
1298
|
+
function isTicketCreatedEvent(event) {
|
|
1299
|
+
return event.event === "ticket.created";
|
|
1300
|
+
}
|
|
1301
|
+
function isTicketUpdatedEvent(event) {
|
|
1302
|
+
return event.event === "ticket.updated";
|
|
1303
|
+
}
|
|
1304
|
+
function isCommentCreatedEvent(event) {
|
|
1305
|
+
return event.event === "ticket.comment.created";
|
|
1306
|
+
}
|
|
1307
|
+
function parseWebhookEvent(payload) {
|
|
1308
|
+
const event = typeof payload === "string" ? JSON.parse(payload) : payload;
|
|
1309
|
+
if (!event || typeof event !== "object") {
|
|
1310
|
+
throw new Error("Invalid webhook payload: expected object");
|
|
1311
|
+
}
|
|
1312
|
+
if (!event.event || typeof event.event !== "string") {
|
|
1313
|
+
throw new Error("Invalid webhook payload: missing event type");
|
|
1314
|
+
}
|
|
1315
|
+
if (!event.id || typeof event.id !== "string") {
|
|
1316
|
+
throw new Error("Invalid webhook payload: missing event id");
|
|
1317
|
+
}
|
|
1318
|
+
if (!event.data || typeof event.data !== "object") {
|
|
1319
|
+
throw new Error("Invalid webhook payload: missing data");
|
|
1320
|
+
}
|
|
1321
|
+
const validEvents = ["ticket.created", "ticket.updated", "ticket.comment.created"];
|
|
1322
|
+
if (!validEvents.includes(event.event)) {
|
|
1323
|
+
throw new Error(`Invalid webhook payload: unknown event type "${event.event}"`);
|
|
1324
|
+
}
|
|
1325
|
+
return event;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
899
1328
|
// src/utils/pagination.ts
|
|
900
1329
|
async function collectAll(iterable) {
|
|
901
1330
|
const items = [];
|
|
@@ -905,6 +1334,6 @@ async function collectAll(iterable) {
|
|
|
905
1334
|
return items;
|
|
906
1335
|
}
|
|
907
1336
|
|
|
908
|
-
export { AuthenticationError, ConflictError, DispatchTickets, DispatchTicketsError, NetworkError, NotFoundError, RateLimitError, ServerError, TimeoutError, ValidationError, collectAll, webhookUtils };
|
|
1337
|
+
export { AuthenticationError, ConflictError, DispatchTickets, DispatchTicketsError, NetworkError, NotFoundError, RateLimitError, ServerError, TimeoutError, ValidationError, collectAll, isAuthenticationError, isCommentCreatedEvent, isConflictError, isDispatchTicketsError, isNetworkError, isNotFoundError, isRateLimitError, isServerError, isTicketCreatedEvent, isTicketUpdatedEvent, isTimeoutError, isValidationError, parseWebhookEvent, webhookUtils };
|
|
909
1338
|
//# sourceMappingURL=index.js.map
|
|
910
1339
|
//# sourceMappingURL=index.js.map
|