@dispatchtickets/sdk 0.1.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -44,16 +44,83 @@ const client = new DispatchTickets({
44
44
  apiKey: 'sk_live_...', // Required
45
45
  baseUrl: 'https://...', // Optional, default: production API
46
46
  timeout: 30000, // Optional, request timeout in ms
47
- maxRetries: 3, // Optional, retry count for failed requests
48
47
  debug: false, // Optional, enable debug logging
48
+ fetch: customFetch, // Optional, custom fetch for testing
49
+ retry: { // Optional, fine-grained retry config
50
+ maxRetries: 3,
51
+ retryableStatuses: [429, 500, 502, 503, 504],
52
+ initialDelayMs: 1000,
53
+ maxDelayMs: 30000,
54
+ },
55
+ hooks: { // Optional, observability hooks
56
+ onRequest: (ctx) => console.log(`${ctx.method} ${ctx.url}`),
57
+ onResponse: (ctx) => console.log(`${ctx.status} in ${ctx.durationMs}ms`),
58
+ onError: (error) => Sentry.captureException(error),
59
+ onRetry: (ctx, error, delay) => console.log(`Retrying in ${delay}ms`),
60
+ },
49
61
  });
50
62
  ```
51
63
 
64
+ ### Request Cancellation
65
+
66
+ Cancel long-running requests with an AbortController:
67
+
68
+ ```typescript
69
+ const controller = new AbortController();
70
+
71
+ // Cancel after 5 seconds
72
+ setTimeout(() => controller.abort(), 5000);
73
+
74
+ try {
75
+ const page = await client.tickets.listPage('ws_abc', {}, {
76
+ signal: controller.signal,
77
+ });
78
+ } catch (error) {
79
+ if (error.message.includes('aborted')) {
80
+ console.log('Request was cancelled');
81
+ }
82
+ }
83
+ ```
84
+
52
85
  ## Resources
53
86
 
87
+ ### Accounts
88
+
89
+ ```typescript
90
+ // Get current account
91
+ const account = await client.accounts.me();
92
+
93
+ // Get usage statistics
94
+ const usage = await client.accounts.getUsage();
95
+ console.log(`${usage.ticketsThisMonth}/${usage.plan?.ticketLimit} tickets used`);
96
+
97
+ // List API keys
98
+ const apiKeys = await client.accounts.listApiKeys();
99
+
100
+ // Create a new API key
101
+ const newKey = await client.accounts.createApiKey({
102
+ name: 'Production',
103
+ allBrands: true, // or brandIds: ['br_123']
104
+ });
105
+ console.log('Save this key:', newKey.key); // Only shown once!
106
+
107
+ // Update API key scope
108
+ await client.accounts.updateApiKeyScope('key_abc', {
109
+ allBrands: false,
110
+ brandIds: ['br_123', 'br_456'],
111
+ });
112
+
113
+ // Revoke an API key
114
+ await client.accounts.revokeApiKey('key_abc');
115
+ ```
116
+
54
117
  ### Brands
55
118
 
56
119
  ```typescript
120
+ // Get inbound email address for a brand
121
+ const inboundEmail = client.brands.getInboundEmail('br_abc123');
122
+ // Returns: br_abc123@inbound.dispatchtickets.com
123
+
57
124
  // Create a brand
58
125
  const brand = await client.brands.create({
59
126
  name: 'Acme Support',
@@ -268,30 +335,73 @@ await client.fields.delete('ws_abc123', 'ticket', 'order_id');
268
335
 
269
336
  ## Error Handling
270
337
 
338
+ Use type guards for clean error handling:
339
+
271
340
  ```typescript
272
341
  import {
273
342
  DispatchTickets,
274
- AuthenticationError,
275
- RateLimitError,
276
- ValidationError,
277
- NotFoundError,
343
+ isNotFoundError,
344
+ isAuthenticationError,
345
+ isRateLimitError,
346
+ isValidationError,
278
347
  } from '@dispatchtickets/sdk';
279
348
 
280
349
  try {
281
350
  await client.tickets.get('ws_abc123', 'tkt_invalid');
282
351
  } catch (error) {
283
- if (error instanceof NotFoundError) {
352
+ if (isNotFoundError(error)) {
284
353
  console.log('Ticket not found');
285
- } else if (error instanceof AuthenticationError) {
354
+ console.log('Request ID:', error.requestId); // For debugging with support
355
+ } else if (isAuthenticationError(error)) {
286
356
  console.log('Invalid API key');
287
- } else if (error instanceof RateLimitError) {
357
+ } else if (isRateLimitError(error)) {
288
358
  console.log(`Rate limited. Retry after ${error.retryAfter} seconds`);
289
- } else if (error instanceof ValidationError) {
359
+ console.log(`Limit: ${error.limit}, Remaining: ${error.remaining}`);
360
+ } else if (isValidationError(error)) {
290
361
  console.log('Validation errors:', error.errors);
291
362
  }
292
363
  }
293
364
  ```
294
365
 
366
+ All errors include a `requestId` for debugging with support.
367
+
368
+ ## Webhook Events
369
+
370
+ Handle webhook events with full type safety:
371
+
372
+ ```typescript
373
+ import {
374
+ DispatchTickets,
375
+ parseWebhookEvent,
376
+ isTicketCreatedEvent,
377
+ isTicketUpdatedEvent,
378
+ isCommentCreatedEvent,
379
+ } from '@dispatchtickets/sdk';
380
+
381
+ // Parse and validate webhook payload
382
+ const event = parseWebhookEvent(req.body);
383
+
384
+ // Use type guards for type-safe event handling
385
+ if (isTicketCreatedEvent(event)) {
386
+ // event.data is typed as TicketCreatedData
387
+ console.log('New ticket:', event.data.title);
388
+ console.log('Priority:', event.data.priority);
389
+ console.log('Customer:', event.data.customerEmail);
390
+ }
391
+
392
+ if (isTicketUpdatedEvent(event)) {
393
+ // event.data is typed as TicketUpdatedData
394
+ console.log('Ticket updated:', event.data.id);
395
+ console.log('Changed fields:', event.data.changes);
396
+ }
397
+
398
+ if (isCommentCreatedEvent(event)) {
399
+ // event.data is typed as CommentCreatedData
400
+ console.log('New comment on', event.data.ticketNumber);
401
+ console.log('Author:', event.data.comment.authorType);
402
+ }
403
+ ```
404
+
295
405
  ## Webhook Verification
296
406
 
297
407
  ```typescript
@@ -322,6 +432,55 @@ app.post('/webhooks', (req, res) => {
322
432
  });
323
433
  ```
324
434
 
435
+ ## Testing
436
+
437
+ Use the custom `fetch` option to mock API responses in tests:
438
+
439
+ ```typescript
440
+ import { DispatchTickets } from '@dispatchtickets/sdk';
441
+ import { vi } from 'vitest';
442
+
443
+ const mockFetch = vi.fn().mockResolvedValue({
444
+ ok: true,
445
+ status: 200,
446
+ headers: { get: () => 'application/json' },
447
+ json: () => Promise.resolve([{ id: 'br_123', name: 'Test' }]),
448
+ });
449
+
450
+ const client = new DispatchTickets({
451
+ apiKey: 'sk_test_123',
452
+ fetch: mockFetch,
453
+ });
454
+
455
+ const brands = await client.brands.list();
456
+ expect(brands).toHaveLength(1);
457
+ expect(mockFetch).toHaveBeenCalled();
458
+ ```
459
+
460
+ ## Examples
461
+
462
+ See the `/examples` directory for complete working examples:
463
+
464
+ - **[express-webhook.ts](./examples/express-webhook.ts)** - Express.js webhook handler with signature verification
465
+ - **[nextjs-api-route.ts](./examples/nextjs-api-route.ts)** - Next.js App Router webhook handler
466
+ - **[basic-usage.ts](./examples/basic-usage.ts)** - Common SDK operations (tickets, comments, pagination)
467
+
468
+ ## API Documentation
469
+
470
+ Generate TypeDoc API documentation locally:
471
+
472
+ ```bash
473
+ npm run docs
474
+ ```
475
+
476
+ This creates a `docs/` folder with HTML documentation for all exported types and methods.
477
+
478
+ ## Links
479
+
480
+ - [API Reference (Swagger)](https://dispatch-tickets-api.onrender.com/docs)
481
+ - [GitHub](https://github.com/Epic-Design-Labs/app-dispatchtickets-sdk)
482
+ - [Changelog](./CHANGELOG.md)
483
+
325
484
  ## License
326
485
 
327
486
  MIT