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