@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/dist/index.js CHANGED
@@ -5,33 +5,45 @@ var DispatchTicketsError = class extends Error {
5
5
  code;
6
6
  statusCode;
7
7
  details;
8
- constructor(message, code, statusCode, details) {
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
- constructor(message = "Rate limit exceeded", retryAfter) {
26
- super(message, "rate_limit_error", 429);
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.config.maxRetries) {
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
- const response = await this.executeRequest(url, options.method, headers, options.body);
91
- return await this.handleResponse(response);
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 (error instanceof DispatchTicketsError) {
95
- if (error instanceof AuthenticationError || error instanceof ValidationError || error instanceof NotFoundError || error instanceof ConflictError) {
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 (error instanceof NetworkError || error instanceof TimeoutError) {
114
- if (attempt < this.config.maxRetries) {
115
- await this.sleep(this.calculateBackoff(attempt));
116
- attempt++;
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 error;
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
- const response = await fetch(url, {
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(message, retryAfter ? parseInt(retryAfter, 10) : void 0);
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(message, "api_error", response.status, errorData);
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