@dispatchtickets/sdk 0.1.0 → 0.3.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 CHANGED
@@ -46,14 +46,49 @@ const client = new DispatchTickets({
46
46
  timeout: 30000, // Optional, request timeout in ms
47
47
  maxRetries: 3, // Optional, retry count for failed requests
48
48
  debug: false, // Optional, enable debug logging
49
+ fetch: customFetch, // Optional, custom fetch for testing
49
50
  });
50
51
  ```
51
52
 
52
53
  ## Resources
53
54
 
55
+ ### Accounts
56
+
57
+ ```typescript
58
+ // Get current account
59
+ const account = await client.accounts.me();
60
+
61
+ // Get usage statistics
62
+ const usage = await client.accounts.getUsage();
63
+ console.log(`${usage.ticketsThisMonth}/${usage.plan?.ticketLimit} tickets used`);
64
+
65
+ // List API keys
66
+ const apiKeys = await client.accounts.listApiKeys();
67
+
68
+ // Create a new API key
69
+ const newKey = await client.accounts.createApiKey({
70
+ name: 'Production',
71
+ allBrands: true, // or brandIds: ['br_123']
72
+ });
73
+ console.log('Save this key:', newKey.key); // Only shown once!
74
+
75
+ // Update API key scope
76
+ await client.accounts.updateApiKeyScope('key_abc', {
77
+ allBrands: false,
78
+ brandIds: ['br_123', 'br_456'],
79
+ });
80
+
81
+ // Revoke an API key
82
+ await client.accounts.revokeApiKey('key_abc');
83
+ ```
84
+
54
85
  ### Brands
55
86
 
56
87
  ```typescript
88
+ // Get inbound email address for a brand
89
+ const inboundEmail = client.brands.getInboundEmail('br_abc123');
90
+ // Returns: br_abc123@inbound.dispatchtickets.com
91
+
57
92
  // Create a brand
58
93
  const brand = await client.brands.create({
59
94
  name: 'Acme Support',
@@ -292,6 +327,43 @@ try {
292
327
  }
293
328
  ```
294
329
 
330
+ ## Webhook Events
331
+
332
+ Handle webhook events with full type safety:
333
+
334
+ ```typescript
335
+ import {
336
+ DispatchTickets,
337
+ parseWebhookEvent,
338
+ isTicketCreatedEvent,
339
+ isTicketUpdatedEvent,
340
+ isCommentCreatedEvent,
341
+ } from '@dispatchtickets/sdk';
342
+
343
+ // Parse and validate webhook payload
344
+ const event = parseWebhookEvent(req.body);
345
+
346
+ // Use type guards for type-safe event handling
347
+ if (isTicketCreatedEvent(event)) {
348
+ // event.data is typed as TicketCreatedData
349
+ console.log('New ticket:', event.data.title);
350
+ console.log('Priority:', event.data.priority);
351
+ console.log('Customer:', event.data.customerEmail);
352
+ }
353
+
354
+ if (isTicketUpdatedEvent(event)) {
355
+ // event.data is typed as TicketUpdatedData
356
+ console.log('Ticket updated:', event.data.id);
357
+ console.log('Changed fields:', event.data.changes);
358
+ }
359
+
360
+ if (isCommentCreatedEvent(event)) {
361
+ // event.data is typed as CommentCreatedData
362
+ console.log('New comment on', event.data.ticketNumber);
363
+ console.log('Author:', event.data.comment.authorType);
364
+ }
365
+ ```
366
+
295
367
  ## Webhook Verification
296
368
 
297
369
  ```typescript
@@ -322,6 +394,45 @@ app.post('/webhooks', (req, res) => {
322
394
  });
323
395
  ```
324
396
 
397
+ ## Testing
398
+
399
+ Use the custom `fetch` option to mock API responses in tests:
400
+
401
+ ```typescript
402
+ import { DispatchTickets } from '@dispatchtickets/sdk';
403
+ import { vi } from 'vitest';
404
+
405
+ const mockFetch = vi.fn().mockResolvedValue({
406
+ ok: true,
407
+ status: 200,
408
+ headers: { get: () => 'application/json' },
409
+ json: () => Promise.resolve([{ id: 'br_123', name: 'Test' }]),
410
+ });
411
+
412
+ const client = new DispatchTickets({
413
+ apiKey: 'sk_test_123',
414
+ fetch: mockFetch,
415
+ });
416
+
417
+ const brands = await client.brands.list();
418
+ expect(brands).toHaveLength(1);
419
+ expect(mockFetch).toHaveBeenCalled();
420
+ ```
421
+
422
+ ## Examples
423
+
424
+ See the `/examples` directory for complete working examples:
425
+
426
+ - **[express-webhook.ts](./examples/express-webhook.ts)** - Express.js webhook handler with signature verification
427
+ - **[nextjs-api-route.ts](./examples/nextjs-api-route.ts)** - Next.js App Router webhook handler
428
+ - **[basic-usage.ts](./examples/basic-usage.ts)** - Common SDK operations (tickets, comments, pagination)
429
+
430
+ ## Links
431
+
432
+ - [API Reference (Swagger)](https://dispatch-tickets-api.onrender.com/docs)
433
+ - [GitHub](https://github.com/Epic-Design-Labs/app-dispatchtickets-sdk)
434
+ - [Changelog](./CHANGELOG.md)
435
+
325
436
  ## License
326
437
 
327
438
  MIT
package/dist/index.cjs CHANGED
@@ -76,8 +76,10 @@ var NetworkError = class extends DispatchTicketsError {
76
76
  // src/utils/http.ts
77
77
  var HttpClient = class {
78
78
  config;
79
+ fetchFn;
79
80
  constructor(config) {
80
81
  this.config = config;
82
+ this.fetchFn = config.fetch ?? fetch;
81
83
  }
82
84
  /**
83
85
  * Execute an HTTP request with retry logic
@@ -157,7 +159,7 @@ var HttpClient = class {
157
159
  console.log("[DispatchTickets] Body:", JSON.stringify(body, null, 2));
158
160
  }
159
161
  }
160
- const response = await fetch(url, {
162
+ const response = await this.fetchFn(url, {
161
163
  method,
162
164
  headers,
163
165
  body: body ? JSON.stringify(body) : void 0,
@@ -270,6 +272,52 @@ var BaseResource = class {
270
272
  }
271
273
  };
272
274
 
275
+ // src/resources/accounts.ts
276
+ var AccountsResource = class extends BaseResource {
277
+ /**
278
+ * Get the current account
279
+ */
280
+ async me() {
281
+ return this._get("/accounts/me");
282
+ }
283
+ /**
284
+ * Get usage statistics for the current account
285
+ */
286
+ async getUsage() {
287
+ return this._get("/accounts/me/usage");
288
+ }
289
+ /**
290
+ * List all API keys for the current account
291
+ */
292
+ async listApiKeys() {
293
+ return this._get("/accounts/me/api-keys");
294
+ }
295
+ /**
296
+ * Create a new API key
297
+ *
298
+ * Note: The full key value is only returned once on creation.
299
+ * Store it securely as it cannot be retrieved again.
300
+ */
301
+ async createApiKey(data) {
302
+ return this._post("/accounts/me/api-keys", data);
303
+ }
304
+ /**
305
+ * Update the brand scope for an API key
306
+ */
307
+ async updateApiKeyScope(keyId, data) {
308
+ return this._patch(
309
+ `/accounts/me/api-keys/${keyId}/scope`,
310
+ data
311
+ );
312
+ }
313
+ /**
314
+ * Revoke an API key
315
+ */
316
+ async revokeApiKey(keyId) {
317
+ await this._delete(`/accounts/me/api-keys/${keyId}`);
318
+ }
319
+ };
320
+
273
321
  // src/resources/brands.ts
274
322
  var BrandsResource = class extends BaseResource {
275
323
  /**
@@ -320,6 +368,28 @@ var BrandsResource = class extends BaseResource {
320
368
  async updateSchema(brandId, schema) {
321
369
  return this._put(`/brands/${brandId}/schema`, schema);
322
370
  }
371
+ /**
372
+ * Get the inbound email address for a brand
373
+ *
374
+ * Emails sent to this address will automatically create tickets.
375
+ *
376
+ * @param brandId - The brand ID
377
+ * @param domain - Optional custom inbound domain (default: inbound.dispatchtickets.com)
378
+ * @returns The inbound email address
379
+ *
380
+ * @example
381
+ * ```typescript
382
+ * const email = client.brands.getInboundEmail('br_abc123');
383
+ * // Returns: br_abc123@inbound.dispatchtickets.com
384
+ *
385
+ * // With custom domain:
386
+ * const customEmail = client.brands.getInboundEmail('br_abc123', 'support.mycompany.com');
387
+ * // Returns: br_abc123@support.mycompany.com
388
+ * ```
389
+ */
390
+ getInboundEmail(brandId, domain = "inbound.dispatchtickets.com") {
391
+ return `${brandId}@${domain}`;
392
+ }
323
393
  };
324
394
 
325
395
  // src/resources/tickets.ts
@@ -834,6 +904,10 @@ var webhookUtils = {
834
904
  // src/client.ts
835
905
  var DispatchTickets = class {
836
906
  http;
907
+ /**
908
+ * Accounts resource for managing the current account and API keys
909
+ */
910
+ accounts;
837
911
  /**
838
912
  * Brands (workspaces) resource
839
913
  */
@@ -883,9 +957,11 @@ var DispatchTickets = class {
883
957
  apiKey: config.apiKey,
884
958
  timeout: config.timeout ?? 3e4,
885
959
  maxRetries: config.maxRetries ?? 3,
886
- debug: config.debug ?? false
960
+ debug: config.debug ?? false,
961
+ fetch: config.fetch
887
962
  };
888
963
  this.http = new HttpClient(httpConfig);
964
+ this.accounts = new AccountsResource(this.http);
889
965
  this.brands = new BrandsResource(this.http);
890
966
  this.tickets = new TicketsResource(this.http);
891
967
  this.comments = new CommentsResource(this.http);
@@ -898,6 +974,37 @@ var DispatchTickets = class {
898
974
  }
899
975
  };
900
976
 
977
+ // src/types/events.ts
978
+ function isTicketCreatedEvent(event) {
979
+ return event.event === "ticket.created";
980
+ }
981
+ function isTicketUpdatedEvent(event) {
982
+ return event.event === "ticket.updated";
983
+ }
984
+ function isCommentCreatedEvent(event) {
985
+ return event.event === "ticket.comment.created";
986
+ }
987
+ function parseWebhookEvent(payload) {
988
+ const event = typeof payload === "string" ? JSON.parse(payload) : payload;
989
+ if (!event || typeof event !== "object") {
990
+ throw new Error("Invalid webhook payload: expected object");
991
+ }
992
+ if (!event.event || typeof event.event !== "string") {
993
+ throw new Error("Invalid webhook payload: missing event type");
994
+ }
995
+ if (!event.id || typeof event.id !== "string") {
996
+ throw new Error("Invalid webhook payload: missing event id");
997
+ }
998
+ if (!event.data || typeof event.data !== "object") {
999
+ throw new Error("Invalid webhook payload: missing data");
1000
+ }
1001
+ const validEvents = ["ticket.created", "ticket.updated", "ticket.comment.created"];
1002
+ if (!validEvents.includes(event.event)) {
1003
+ throw new Error(`Invalid webhook payload: unknown event type "${event.event}"`);
1004
+ }
1005
+ return event;
1006
+ }
1007
+
901
1008
  // src/utils/pagination.ts
902
1009
  async function collectAll(iterable) {
903
1010
  const items = [];
@@ -918,6 +1025,10 @@ exports.ServerError = ServerError;
918
1025
  exports.TimeoutError = TimeoutError;
919
1026
  exports.ValidationError = ValidationError;
920
1027
  exports.collectAll = collectAll;
1028
+ exports.isCommentCreatedEvent = isCommentCreatedEvent;
1029
+ exports.isTicketCreatedEvent = isTicketCreatedEvent;
1030
+ exports.isTicketUpdatedEvent = isTicketUpdatedEvent;
1031
+ exports.parseWebhookEvent = parseWebhookEvent;
921
1032
  exports.webhookUtils = webhookUtils;
922
1033
  //# sourceMappingURL=index.cjs.map
923
1034
  //# sourceMappingURL=index.cjs.map