@dispatchtickets/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/dist/index.js ADDED
@@ -0,0 +1,910 @@
1
+ import { createHmac, timingSafeEqual } from 'crypto';
2
+
3
+ // src/errors.ts
4
+ var DispatchTicketsError = class extends Error {
5
+ code;
6
+ statusCode;
7
+ details;
8
+ constructor(message, code, statusCode, details) {
9
+ super(message);
10
+ this.name = "DispatchTicketsError";
11
+ this.code = code;
12
+ this.statusCode = statusCode;
13
+ this.details = details;
14
+ Object.setPrototypeOf(this, new.target.prototype);
15
+ }
16
+ };
17
+ var AuthenticationError = class extends DispatchTicketsError {
18
+ constructor(message = "Invalid or missing API key") {
19
+ super(message, "authentication_error", 401);
20
+ this.name = "AuthenticationError";
21
+ }
22
+ };
23
+ var RateLimitError = class extends DispatchTicketsError {
24
+ retryAfter;
25
+ constructor(message = "Rate limit exceeded", retryAfter) {
26
+ super(message, "rate_limit_error", 429);
27
+ this.name = "RateLimitError";
28
+ this.retryAfter = retryAfter;
29
+ }
30
+ };
31
+ var ValidationError = class extends DispatchTicketsError {
32
+ errors;
33
+ constructor(message = "Validation failed", errors) {
34
+ super(message, "validation_error", 400, { errors });
35
+ this.name = "ValidationError";
36
+ this.errors = errors;
37
+ }
38
+ };
39
+ var NotFoundError = class extends DispatchTicketsError {
40
+ resourceType;
41
+ resourceId;
42
+ constructor(message = "Resource not found", resourceType, resourceId) {
43
+ super(message, "not_found", 404, { resourceType, resourceId });
44
+ this.name = "NotFoundError";
45
+ this.resourceType = resourceType;
46
+ this.resourceId = resourceId;
47
+ }
48
+ };
49
+ var ConflictError = class extends DispatchTicketsError {
50
+ constructor(message = "Resource conflict") {
51
+ super(message, "conflict", 409);
52
+ this.name = "ConflictError";
53
+ }
54
+ };
55
+ var ServerError = class extends DispatchTicketsError {
56
+ constructor(message = "Internal server error", statusCode = 500) {
57
+ super(message, "server_error", statusCode);
58
+ this.name = "ServerError";
59
+ }
60
+ };
61
+ var TimeoutError = class extends DispatchTicketsError {
62
+ constructor(message = "Request timed out") {
63
+ super(message, "timeout_error");
64
+ this.name = "TimeoutError";
65
+ }
66
+ };
67
+ var NetworkError = class extends DispatchTicketsError {
68
+ constructor(message = "Network error") {
69
+ super(message, "network_error");
70
+ this.name = "NetworkError";
71
+ }
72
+ };
73
+
74
+ // src/utils/http.ts
75
+ var HttpClient = class {
76
+ config;
77
+ constructor(config) {
78
+ this.config = config;
79
+ }
80
+ /**
81
+ * Execute an HTTP request with retry logic
82
+ */
83
+ async request(options) {
84
+ const url = this.buildUrl(options.path, options.query);
85
+ const headers = this.buildHeaders(options.headers, options.idempotencyKey);
86
+ let lastError;
87
+ let attempt = 0;
88
+ while (attempt <= this.config.maxRetries) {
89
+ try {
90
+ const response = await this.executeRequest(url, options.method, headers, options.body);
91
+ return await this.handleResponse(response);
92
+ } catch (error) {
93
+ 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
+ }
112
+ }
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;
118
+ }
119
+ }
120
+ throw error;
121
+ }
122
+ }
123
+ throw lastError || new NetworkError("Request failed after retries");
124
+ }
125
+ buildUrl(path, query) {
126
+ const url = new URL(path, this.config.baseUrl);
127
+ if (query) {
128
+ for (const [key, value] of Object.entries(query)) {
129
+ if (value !== void 0) {
130
+ url.searchParams.set(key, String(value));
131
+ }
132
+ }
133
+ }
134
+ return url.toString();
135
+ }
136
+ buildHeaders(customHeaders, idempotencyKey) {
137
+ const headers = {
138
+ "Authorization": `Bearer ${this.config.apiKey}`,
139
+ "Content-Type": "application/json",
140
+ "Accept": "application/json",
141
+ ...customHeaders
142
+ };
143
+ if (idempotencyKey) {
144
+ headers["X-Idempotency-Key"] = idempotencyKey;
145
+ }
146
+ return headers;
147
+ }
148
+ async executeRequest(url, method, headers, body) {
149
+ const controller = new AbortController();
150
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
151
+ try {
152
+ if (this.config.debug) {
153
+ console.log(`[DispatchTickets] ${method} ${url}`);
154
+ if (body) {
155
+ console.log("[DispatchTickets] Body:", JSON.stringify(body, null, 2));
156
+ }
157
+ }
158
+ const response = await fetch(url, {
159
+ method,
160
+ headers,
161
+ body: body ? JSON.stringify(body) : void 0,
162
+ signal: controller.signal
163
+ });
164
+ return response;
165
+ } catch (error) {
166
+ if (error instanceof Error) {
167
+ if (error.name === "AbortError") {
168
+ throw new TimeoutError(`Request timed out after ${this.config.timeout}ms`);
169
+ }
170
+ throw new NetworkError(error.message);
171
+ }
172
+ throw new NetworkError("Unknown network error");
173
+ } finally {
174
+ clearTimeout(timeoutId);
175
+ }
176
+ }
177
+ async handleResponse(response) {
178
+ const contentType = response.headers.get("content-type");
179
+ const isJson = contentType?.includes("application/json");
180
+ if (response.ok) {
181
+ if (response.status === 204 || !isJson) {
182
+ return void 0;
183
+ }
184
+ return await response.json();
185
+ }
186
+ let errorData = {};
187
+ if (isJson) {
188
+ try {
189
+ errorData = await response.json();
190
+ } catch {
191
+ }
192
+ }
193
+ const message = errorData.message || errorData.error || response.statusText;
194
+ switch (response.status) {
195
+ case 401:
196
+ throw new AuthenticationError(message);
197
+ case 400:
198
+ case 422:
199
+ throw new ValidationError(message, errorData.errors);
200
+ case 404:
201
+ throw new NotFoundError(message);
202
+ case 409:
203
+ throw new ConflictError(message);
204
+ case 429: {
205
+ const retryAfter = response.headers.get("retry-after");
206
+ throw new RateLimitError(message, retryAfter ? parseInt(retryAfter, 10) : void 0);
207
+ }
208
+ default:
209
+ if (response.status >= 500) {
210
+ throw new ServerError(message, response.status);
211
+ }
212
+ throw new DispatchTicketsError(message, "api_error", response.status, errorData);
213
+ }
214
+ }
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
+ sleep(ms) {
222
+ return new Promise((resolve) => setTimeout(resolve, ms));
223
+ }
224
+ };
225
+
226
+ // src/resources/base.ts
227
+ var BaseResource = class {
228
+ http;
229
+ constructor(http) {
230
+ this.http = http;
231
+ }
232
+ async _get(path, query) {
233
+ return this.http.request({
234
+ method: "GET",
235
+ path,
236
+ query
237
+ });
238
+ }
239
+ async _post(path, body, options) {
240
+ return this.http.request({
241
+ method: "POST",
242
+ path,
243
+ body,
244
+ idempotencyKey: options?.idempotencyKey,
245
+ query: options?.query
246
+ });
247
+ }
248
+ async _patch(path, body) {
249
+ return this.http.request({
250
+ method: "PATCH",
251
+ path,
252
+ body
253
+ });
254
+ }
255
+ async _put(path, body) {
256
+ return this.http.request({
257
+ method: "PUT",
258
+ path,
259
+ body
260
+ });
261
+ }
262
+ async _delete(path, query) {
263
+ return this.http.request({
264
+ method: "DELETE",
265
+ path,
266
+ query
267
+ });
268
+ }
269
+ };
270
+
271
+ // src/resources/brands.ts
272
+ var BrandsResource = class extends BaseResource {
273
+ /**
274
+ * Create a new brand
275
+ */
276
+ async create(data) {
277
+ return this._post("/brands", data);
278
+ }
279
+ /**
280
+ * List all brands
281
+ */
282
+ async list() {
283
+ return this._get("/brands");
284
+ }
285
+ /**
286
+ * Get a brand by ID
287
+ */
288
+ async get(brandId) {
289
+ return this._get(`/brands/${brandId}`);
290
+ }
291
+ /**
292
+ * Update a brand
293
+ */
294
+ async update(brandId, data) {
295
+ return this._patch(`/brands/${brandId}`, data);
296
+ }
297
+ /**
298
+ * Delete a brand
299
+ * @param brandId - The brand ID
300
+ * @param confirm - Set to true to actually delete; false to preview what would be deleted
301
+ */
302
+ async delete(brandId, confirm = true) {
303
+ if (confirm) {
304
+ await this._delete(`/brands/${brandId}`, { confirm: true });
305
+ return;
306
+ }
307
+ return this._delete(`/brands/${brandId}`);
308
+ }
309
+ /**
310
+ * Get the ticket schema for a brand
311
+ */
312
+ async getSchema(brandId) {
313
+ return this._get(`/brands/${brandId}/schema`);
314
+ }
315
+ /**
316
+ * Update the ticket schema for a brand
317
+ */
318
+ async updateSchema(brandId, schema) {
319
+ return this._put(`/brands/${brandId}/schema`, schema);
320
+ }
321
+ };
322
+
323
+ // src/resources/tickets.ts
324
+ var TicketsResource = class extends BaseResource {
325
+ /**
326
+ * Create a new ticket
327
+ */
328
+ async create(brandId, data, options) {
329
+ return this._post(`/brands/${brandId}/tickets`, data, {
330
+ idempotencyKey: options?.idempotencyKey
331
+ });
332
+ }
333
+ /**
334
+ * List tickets with pagination (async iterator)
335
+ * Automatically fetches all pages
336
+ */
337
+ async *list(brandId, filters) {
338
+ let cursor;
339
+ let hasMore = true;
340
+ while (hasMore) {
341
+ const page = await this.listPage(brandId, { ...filters, cursor });
342
+ for (const ticket of page.data) {
343
+ yield ticket;
344
+ }
345
+ cursor = page.pagination.cursor;
346
+ hasMore = page.pagination.hasMore;
347
+ }
348
+ }
349
+ /**
350
+ * List a single page of tickets
351
+ */
352
+ async listPage(brandId, filters) {
353
+ const query = this.buildListQuery(filters);
354
+ return this._get(`/brands/${brandId}/tickets`, query);
355
+ }
356
+ /**
357
+ * Get a ticket by ID
358
+ */
359
+ async get(brandId, ticketId) {
360
+ return this._get(`/brands/${brandId}/tickets/${ticketId}`);
361
+ }
362
+ /**
363
+ * Update a ticket
364
+ */
365
+ async update(brandId, ticketId, data) {
366
+ return this._patch(`/brands/${brandId}/tickets/${ticketId}`, data);
367
+ }
368
+ /**
369
+ * Delete a ticket
370
+ */
371
+ async delete(brandId, ticketId) {
372
+ return this._delete(`/brands/${brandId}/tickets/${ticketId}`);
373
+ }
374
+ /**
375
+ * Mark a ticket as spam or not spam
376
+ */
377
+ async markAsSpam(brandId, ticketId, isSpam) {
378
+ return this._post(`/brands/${brandId}/tickets/${ticketId}/spam`, { isSpam });
379
+ }
380
+ /**
381
+ * Merge tickets into a target ticket
382
+ */
383
+ async merge(brandId, targetTicketId, sourceTicketIds) {
384
+ return this._post(`/brands/${brandId}/tickets/${targetTicketId}/merge`, {
385
+ sourceTicketIds
386
+ });
387
+ }
388
+ /**
389
+ * Perform a bulk action on multiple tickets
390
+ */
391
+ async bulk(brandId, action, ticketIds, options) {
392
+ return this._post(`/brands/${brandId}/tickets/bulk`, {
393
+ action,
394
+ ticketIds,
395
+ ...options
396
+ });
397
+ }
398
+ buildListQuery(filters) {
399
+ if (!filters) return {};
400
+ const query = {};
401
+ if (filters.status) {
402
+ query.status = Array.isArray(filters.status) ? filters.status.join(",") : filters.status;
403
+ }
404
+ if (filters.priority) query.priority = filters.priority;
405
+ if (filters.assigneeId) query.assigneeId = filters.assigneeId;
406
+ if (filters.customerId) query.customerId = filters.customerId;
407
+ if (filters.customerEmail) query.customerEmail = filters.customerEmail;
408
+ if (filters.createdBy) query.createdBy = filters.createdBy;
409
+ if (filters.createdAfter) query.createdAfter = filters.createdAfter;
410
+ if (filters.createdBefore) query.createdBefore = filters.createdBefore;
411
+ if (filters.hasAttachments !== void 0) query.hasAttachments = filters.hasAttachments;
412
+ if (filters.isSpam !== void 0) query.isSpam = filters.isSpam;
413
+ if (filters.search) query.search = filters.search;
414
+ if (filters.categoryId) query.categoryId = filters.categoryId;
415
+ if (filters.tagIds) query.tagIds = filters.tagIds.join(",");
416
+ if (filters.source) query.source = filters.source;
417
+ if (filters.sort) query.sort = filters.sort;
418
+ if (filters.order) query.order = filters.order;
419
+ if (filters.limit) query.limit = filters.limit;
420
+ if (filters.cursor) query.cursor = filters.cursor;
421
+ return query;
422
+ }
423
+ };
424
+
425
+ // src/resources/comments.ts
426
+ var CommentsResource = class extends BaseResource {
427
+ /**
428
+ * Create a new comment on a ticket
429
+ */
430
+ async create(brandId, ticketId, data, options) {
431
+ return this._post(
432
+ `/brands/${brandId}/tickets/${ticketId}/comments`,
433
+ data,
434
+ { idempotencyKey: options?.idempotencyKey }
435
+ );
436
+ }
437
+ /**
438
+ * List all comments on a ticket
439
+ */
440
+ async list(brandId, ticketId) {
441
+ return this._get(`/brands/${brandId}/tickets/${ticketId}/comments`);
442
+ }
443
+ /**
444
+ * Get a comment by ID
445
+ */
446
+ async get(brandId, ticketId, commentId) {
447
+ return this._get(
448
+ `/brands/${brandId}/tickets/${ticketId}/comments/${commentId}`
449
+ );
450
+ }
451
+ /**
452
+ * Update a comment
453
+ */
454
+ async update(brandId, ticketId, commentId, data) {
455
+ return this._patch(
456
+ `/brands/${brandId}/tickets/${ticketId}/comments/${commentId}`,
457
+ data
458
+ );
459
+ }
460
+ /**
461
+ * Delete a comment
462
+ */
463
+ async delete(brandId, ticketId, commentId) {
464
+ return this._delete(
465
+ `/brands/${brandId}/tickets/${ticketId}/comments/${commentId}`
466
+ );
467
+ }
468
+ };
469
+
470
+ // src/resources/attachments.ts
471
+ var AttachmentsResource = class extends BaseResource {
472
+ /**
473
+ * Initiate an upload and get a presigned URL
474
+ */
475
+ async initiateUpload(brandId, ticketId, data) {
476
+ return this._post(
477
+ `/brands/${brandId}/tickets/${ticketId}/attachments`,
478
+ data
479
+ );
480
+ }
481
+ /**
482
+ * Confirm that an upload has completed
483
+ */
484
+ async confirmUpload(brandId, ticketId, attachmentId) {
485
+ return this._post(
486
+ `/brands/${brandId}/tickets/${ticketId}/attachments/${attachmentId}/confirm`
487
+ );
488
+ }
489
+ /**
490
+ * List all attachments on a ticket
491
+ */
492
+ async list(brandId, ticketId) {
493
+ return this._get(`/brands/${brandId}/tickets/${ticketId}/attachments`);
494
+ }
495
+ /**
496
+ * Get an attachment with its download URL
497
+ */
498
+ async get(brandId, ticketId, attachmentId) {
499
+ return this._get(
500
+ `/brands/${brandId}/tickets/${ticketId}/attachments/${attachmentId}`
501
+ );
502
+ }
503
+ /**
504
+ * Delete an attachment
505
+ */
506
+ async delete(brandId, ticketId, attachmentId) {
507
+ await this._delete(
508
+ `/brands/${brandId}/tickets/${ticketId}/attachments/${attachmentId}`
509
+ );
510
+ }
511
+ /**
512
+ * Convenience method: Upload a file in one step
513
+ * Handles: initiate -> upload to presigned URL -> confirm
514
+ */
515
+ async upload(brandId, ticketId, file, filename, contentType) {
516
+ let size;
517
+ if (file instanceof Blob) {
518
+ size = file.size;
519
+ } else if (Buffer.isBuffer(file)) {
520
+ size = file.length;
521
+ } else {
522
+ size = file.byteLength;
523
+ }
524
+ const { uploadUrl, attachmentId } = await this.initiateUpload(brandId, ticketId, {
525
+ filename,
526
+ contentType,
527
+ size
528
+ });
529
+ const uploadResponse = await fetch(uploadUrl, {
530
+ method: "PUT",
531
+ headers: {
532
+ "Content-Type": contentType,
533
+ "Content-Length": String(size)
534
+ },
535
+ body: file
536
+ });
537
+ if (!uploadResponse.ok) {
538
+ throw new Error(`Upload failed: ${uploadResponse.statusText}`);
539
+ }
540
+ return this.confirmUpload(brandId, ticketId, attachmentId);
541
+ }
542
+ };
543
+
544
+ // src/resources/webhooks.ts
545
+ var WebhooksResource = class extends BaseResource {
546
+ /**
547
+ * Create a new webhook
548
+ */
549
+ async create(brandId, data) {
550
+ return this._post(`/brands/${brandId}/webhooks`, data);
551
+ }
552
+ /**
553
+ * List all webhooks for a brand
554
+ */
555
+ async list(brandId) {
556
+ return this._get(`/brands/${brandId}/webhooks`);
557
+ }
558
+ /**
559
+ * Get a webhook by ID
560
+ */
561
+ async get(brandId, webhookId) {
562
+ return this._get(`/brands/${brandId}/webhooks/${webhookId}`);
563
+ }
564
+ /**
565
+ * Delete a webhook
566
+ */
567
+ async delete(brandId, webhookId) {
568
+ await this._delete(`/brands/${brandId}/webhooks/${webhookId}`);
569
+ }
570
+ /**
571
+ * Get webhook delivery history
572
+ */
573
+ async getDeliveries(brandId, webhookId) {
574
+ return this._get(
575
+ `/brands/${brandId}/webhooks/${webhookId}/deliveries`
576
+ );
577
+ }
578
+ };
579
+
580
+ // src/resources/categories.ts
581
+ var CategoriesResource = class extends BaseResource {
582
+ /**
583
+ * Create a new category
584
+ */
585
+ async create(brandId, data) {
586
+ return this._post(`/brands/${brandId}/categories`, data);
587
+ }
588
+ /**
589
+ * List all categories for a brand
590
+ */
591
+ async list(brandId) {
592
+ return this._get(`/brands/${brandId}/categories`);
593
+ }
594
+ /**
595
+ * Get a category by ID
596
+ */
597
+ async get(brandId, categoryId) {
598
+ return this._get(`/brands/${brandId}/categories/${categoryId}`);
599
+ }
600
+ /**
601
+ * Update a category
602
+ */
603
+ async update(brandId, categoryId, data) {
604
+ return this._patch(`/brands/${brandId}/categories/${categoryId}`, data);
605
+ }
606
+ /**
607
+ * Delete a category
608
+ */
609
+ async delete(brandId, categoryId) {
610
+ await this._delete(`/brands/${brandId}/categories/${categoryId}`);
611
+ }
612
+ /**
613
+ * Get category statistics (ticket counts)
614
+ */
615
+ async getStats(brandId) {
616
+ return this._get(`/brands/${brandId}/categories/stats`);
617
+ }
618
+ /**
619
+ * Reorder categories
620
+ */
621
+ async reorder(brandId, categoryIds) {
622
+ await this._post(`/brands/${brandId}/categories/reorder`, { categoryIds });
623
+ }
624
+ };
625
+
626
+ // src/resources/tags.ts
627
+ var TagsResource = class extends BaseResource {
628
+ /**
629
+ * Create a new tag
630
+ */
631
+ async create(brandId, data) {
632
+ return this._post(`/brands/${brandId}/tags`, data);
633
+ }
634
+ /**
635
+ * List all tags for a brand
636
+ */
637
+ async list(brandId) {
638
+ return this._get(`/brands/${brandId}/tags`);
639
+ }
640
+ /**
641
+ * Update a tag
642
+ */
643
+ async update(brandId, tagId, data) {
644
+ return this._patch(`/brands/${brandId}/tags/${tagId}`, data);
645
+ }
646
+ /**
647
+ * Delete a tag
648
+ */
649
+ async delete(brandId, tagId) {
650
+ await this._delete(`/brands/${brandId}/tags/${tagId}`);
651
+ }
652
+ /**
653
+ * Merge tags into a target tag
654
+ */
655
+ async merge(brandId, targetTagId, sourceTagIds) {
656
+ return this._post(`/brands/${brandId}/tags/${targetTagId}/merge`, {
657
+ sourceTagIds
658
+ });
659
+ }
660
+ };
661
+
662
+ // src/resources/customers.ts
663
+ var CustomersResource = class extends BaseResource {
664
+ /**
665
+ * Create a new customer
666
+ */
667
+ async create(brandId, data) {
668
+ return this._post(`/brands/${brandId}/customers`, data);
669
+ }
670
+ /**
671
+ * List customers with pagination (async iterator)
672
+ */
673
+ async *list(brandId, filters) {
674
+ let cursor;
675
+ let hasMore = true;
676
+ while (hasMore) {
677
+ const page = await this.listPage(brandId, { ...filters, cursor });
678
+ for (const customer of page.data) {
679
+ yield customer;
680
+ }
681
+ cursor = page.pagination.cursor;
682
+ hasMore = page.pagination.hasMore;
683
+ }
684
+ }
685
+ /**
686
+ * List a single page of customers
687
+ */
688
+ async listPage(brandId, filters) {
689
+ const query = this.buildListQuery(filters);
690
+ return this._get(`/brands/${brandId}/customers`, query);
691
+ }
692
+ /**
693
+ * Search customers (autocomplete)
694
+ */
695
+ async search(brandId, query) {
696
+ return this._get(`/brands/${brandId}/customers/search`, { q: query });
697
+ }
698
+ /**
699
+ * Get a customer by ID
700
+ */
701
+ async get(brandId, customerId) {
702
+ return this._get(`/brands/${brandId}/customers/${customerId}`);
703
+ }
704
+ /**
705
+ * Update a customer
706
+ */
707
+ async update(brandId, customerId, data) {
708
+ return this._patch(`/brands/${brandId}/customers/${customerId}`, data);
709
+ }
710
+ /**
711
+ * Delete a customer
712
+ */
713
+ async delete(brandId, customerId) {
714
+ return this._delete(`/brands/${brandId}/customers/${customerId}`);
715
+ }
716
+ buildListQuery(filters) {
717
+ if (!filters) return {};
718
+ const query = {};
719
+ if (filters.limit) query.limit = filters.limit;
720
+ if (filters.cursor) query.cursor = filters.cursor;
721
+ if (filters.sort) query.sort = filters.sort;
722
+ if (filters.order) query.order = filters.order;
723
+ return query;
724
+ }
725
+ };
726
+
727
+ // src/resources/fields.ts
728
+ var FieldsResource = class extends BaseResource {
729
+ /**
730
+ * Get all field definitions for a brand
731
+ */
732
+ async getAll(brandId) {
733
+ return this._get(`/brands/${brandId}/fields`);
734
+ }
735
+ /**
736
+ * Get field definitions for a specific entity type
737
+ */
738
+ async list(brandId, entityType) {
739
+ return this._get(`/brands/${brandId}/fields/${entityType}`);
740
+ }
741
+ /**
742
+ * Create a new field definition
743
+ */
744
+ async create(brandId, entityType, data) {
745
+ return this._post(`/brands/${brandId}/fields/${entityType}`, data);
746
+ }
747
+ /**
748
+ * Update a field definition
749
+ */
750
+ async update(brandId, entityType, key, data) {
751
+ return this._patch(
752
+ `/brands/${brandId}/fields/${entityType}/${key}`,
753
+ data
754
+ );
755
+ }
756
+ /**
757
+ * Delete a field definition
758
+ */
759
+ async delete(brandId, entityType, key) {
760
+ await this._delete(`/brands/${brandId}/fields/${entityType}/${key}`);
761
+ }
762
+ /**
763
+ * Reorder field definitions
764
+ */
765
+ async reorder(brandId, entityType, keys) {
766
+ await this._post(`/brands/${brandId}/fields/${entityType}/reorder`, { keys });
767
+ }
768
+ };
769
+ var webhookUtils = {
770
+ /**
771
+ * Verify a webhook signature
772
+ *
773
+ * @param payload - The raw request body as a string
774
+ * @param signature - The X-Dispatch-Signature header value
775
+ * @param secret - Your webhook secret
776
+ * @returns true if the signature is valid
777
+ *
778
+ * @example
779
+ * ```typescript
780
+ * import { DispatchTickets } from '@dispatchtickets/sdk';
781
+ *
782
+ * app.post('/webhooks', (req, res) => {
783
+ * const signature = req.headers['x-dispatch-signature'];
784
+ * const isValid = DispatchTickets.webhooks.verifySignature(
785
+ * req.rawBody,
786
+ * signature,
787
+ * process.env.WEBHOOK_SECRET
788
+ * );
789
+ *
790
+ * if (!isValid) {
791
+ * return res.status(401).send('Invalid signature');
792
+ * }
793
+ *
794
+ * // Process webhook...
795
+ * });
796
+ * ```
797
+ */
798
+ verifySignature(payload, signature, secret) {
799
+ if (!signature || !secret) {
800
+ return false;
801
+ }
802
+ const parts = signature.split("=");
803
+ if (parts.length !== 2 || parts[0] !== "sha256") {
804
+ return false;
805
+ }
806
+ const receivedSignature = parts[1];
807
+ const expectedSignature = createHmac("sha256", secret).update(payload).digest("hex");
808
+ try {
809
+ const receivedBuffer = Buffer.from(receivedSignature, "hex");
810
+ const expectedBuffer = Buffer.from(expectedSignature, "hex");
811
+ if (receivedBuffer.length !== expectedBuffer.length) {
812
+ return false;
813
+ }
814
+ return timingSafeEqual(receivedBuffer, expectedBuffer);
815
+ } catch {
816
+ return false;
817
+ }
818
+ },
819
+ /**
820
+ * Generate a signature for testing purposes
821
+ *
822
+ * @param payload - The payload to sign
823
+ * @param secret - The secret to sign with
824
+ * @returns The signature in the format sha256=<hex>
825
+ */
826
+ generateSignature(payload, secret) {
827
+ const signature = createHmac("sha256", secret).update(payload).digest("hex");
828
+ return `sha256=${signature}`;
829
+ }
830
+ };
831
+
832
+ // src/client.ts
833
+ var DispatchTickets = class {
834
+ http;
835
+ /**
836
+ * Brands (workspaces) resource
837
+ */
838
+ brands;
839
+ /**
840
+ * Tickets resource
841
+ */
842
+ tickets;
843
+ /**
844
+ * Comments resource
845
+ */
846
+ comments;
847
+ /**
848
+ * Attachments resource
849
+ */
850
+ attachments;
851
+ /**
852
+ * Webhooks resource
853
+ */
854
+ webhooks;
855
+ /**
856
+ * Categories resource
857
+ */
858
+ categories;
859
+ /**
860
+ * Tags resource
861
+ */
862
+ tags;
863
+ /**
864
+ * Customers resource
865
+ */
866
+ customers;
867
+ /**
868
+ * Custom fields resource
869
+ */
870
+ fields;
871
+ /**
872
+ * Static webhook utilities
873
+ */
874
+ static webhooks = webhookUtils;
875
+ constructor(config) {
876
+ if (!config.apiKey) {
877
+ throw new Error("API key is required");
878
+ }
879
+ const httpConfig = {
880
+ baseUrl: config.baseUrl || "https://dispatch-tickets-api.onrender.com/v1",
881
+ apiKey: config.apiKey,
882
+ timeout: config.timeout ?? 3e4,
883
+ maxRetries: config.maxRetries ?? 3,
884
+ debug: config.debug ?? false
885
+ };
886
+ this.http = new HttpClient(httpConfig);
887
+ this.brands = new BrandsResource(this.http);
888
+ this.tickets = new TicketsResource(this.http);
889
+ this.comments = new CommentsResource(this.http);
890
+ this.attachments = new AttachmentsResource(this.http);
891
+ this.webhooks = new WebhooksResource(this.http);
892
+ this.categories = new CategoriesResource(this.http);
893
+ this.tags = new TagsResource(this.http);
894
+ this.customers = new CustomersResource(this.http);
895
+ this.fields = new FieldsResource(this.http);
896
+ }
897
+ };
898
+
899
+ // src/utils/pagination.ts
900
+ async function collectAll(iterable) {
901
+ const items = [];
902
+ for await (const item of iterable) {
903
+ items.push(item);
904
+ }
905
+ return items;
906
+ }
907
+
908
+ export { AuthenticationError, ConflictError, DispatchTickets, DispatchTicketsError, NetworkError, NotFoundError, RateLimitError, ServerError, TimeoutError, ValidationError, collectAll, webhookUtils };
909
+ //# sourceMappingURL=index.js.map
910
+ //# sourceMappingURL=index.js.map