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