@workbenchcrm/sdk 1.0.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.mjs ADDED
@@ -0,0 +1,1043 @@
1
+ // src/resources/clients.ts
2
+ var ClientsResource = class {
3
+ client;
4
+ constructor(client) {
5
+ this.client = client;
6
+ }
7
+ /**
8
+ * List all clients
9
+ *
10
+ * Returns a paginated list of clients for the authenticated business.
11
+ *
12
+ * @param options - List options (pagination, filtering, sorting)
13
+ * @returns Paginated list of clients
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * // List all active clients
18
+ * const { data, pagination } = await workbench.clients.list({
19
+ * status: 'active',
20
+ * page: 1,
21
+ * per_page: 20
22
+ * });
23
+ *
24
+ * console.log(`Found ${pagination.total} clients`);
25
+ * ```
26
+ */
27
+ async list(options = {}) {
28
+ return this.client.get("/v1/clients", {
29
+ page: options.page,
30
+ per_page: options.per_page,
31
+ search: options.search,
32
+ sort: options.sort,
33
+ order: options.order,
34
+ status: options.status
35
+ });
36
+ }
37
+ /**
38
+ * Get a client by ID
39
+ *
40
+ * @param id - Client UUID
41
+ * @returns Client details
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * const { data: client } = await workbench.clients.get('client-uuid');
46
+ * console.log(`Client: ${client.first_name} ${client.last_name}`);
47
+ * ```
48
+ */
49
+ async get(id) {
50
+ return this.client.get(`/v1/clients/${id}`);
51
+ }
52
+ /**
53
+ * Create a new client
54
+ *
55
+ * @param data - Client data
56
+ * @returns Created client
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * const { data: client } = await workbench.clients.create({
61
+ * first_name: 'John',
62
+ * last_name: 'Doe',
63
+ * email: 'john@example.com',
64
+ * phone: '+1-555-123-4567',
65
+ * company: 'Acme Corp',
66
+ * status: 'active',
67
+ * source: 'referral',
68
+ * tags: ['vip', 'commercial']
69
+ * });
70
+ * ```
71
+ */
72
+ async create(data) {
73
+ return this.client.post("/v1/clients", data);
74
+ }
75
+ /**
76
+ * Update a client
77
+ *
78
+ * @param id - Client UUID
79
+ * @param data - Fields to update
80
+ * @returns Updated client
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * const { data: client } = await workbench.clients.update('client-uuid', {
85
+ * status: 'inactive',
86
+ * notes: 'Client requested to pause services'
87
+ * });
88
+ * ```
89
+ */
90
+ async update(id, data) {
91
+ return this.client.put(`/v1/clients/${id}`, data);
92
+ }
93
+ /**
94
+ * Delete a client
95
+ *
96
+ * Permanently deletes a client and all associated data.
97
+ * This action cannot be undone.
98
+ *
99
+ * @param id - Client UUID
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * await workbench.clients.delete('client-uuid');
104
+ * ```
105
+ */
106
+ async delete(id) {
107
+ await this.client.delete(`/v1/clients/${id}`);
108
+ }
109
+ };
110
+
111
+ // src/resources/invoices.ts
112
+ var InvoicesResource = class {
113
+ client;
114
+ constructor(client) {
115
+ this.client = client;
116
+ }
117
+ /**
118
+ * List all invoices
119
+ *
120
+ * Returns a paginated list of invoices for the authenticated business.
121
+ *
122
+ * @param options - List options (pagination, filtering, sorting)
123
+ * @returns Paginated list of invoices
124
+ *
125
+ * @example
126
+ * ```typescript
127
+ * // List unpaid invoices
128
+ * const { data, pagination } = await workbench.invoices.list({
129
+ * status: 'sent',
130
+ * per_page: 50
131
+ * });
132
+ * ```
133
+ */
134
+ async list(options = {}) {
135
+ return this.client.get("/v1/invoices", {
136
+ page: options.page,
137
+ per_page: options.per_page,
138
+ search: options.search,
139
+ sort: options.sort,
140
+ order: options.order,
141
+ status: options.status,
142
+ client_id: options.client_id
143
+ });
144
+ }
145
+ /**
146
+ * Get an invoice by ID
147
+ *
148
+ * @param id - Invoice UUID
149
+ * @returns Invoice details with line items
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * const { data: invoice } = await workbench.invoices.get('invoice-uuid');
154
+ * console.log(`Invoice ${invoice.invoice_number}: $${invoice.total}`);
155
+ * ```
156
+ */
157
+ async get(id) {
158
+ return this.client.get(`/v1/invoices/${id}`);
159
+ }
160
+ /**
161
+ * Create a new invoice
162
+ *
163
+ * Creates an invoice with line items. The invoice number is
164
+ * automatically generated by Workbench.
165
+ *
166
+ * @param data - Invoice data including line items
167
+ * @returns Created invoice
168
+ *
169
+ * @example
170
+ * ```typescript
171
+ * const { data: invoice } = await workbench.invoices.create({
172
+ * client_id: 'client-uuid',
173
+ * due_date: '2024-02-15',
174
+ * items: [
175
+ * { description: 'Web Development', quantity: 10, unit_price: 100 }
176
+ * ],
177
+ * tax_rate: 8.5,
178
+ * notes: 'Payment due within 30 days'
179
+ * });
180
+ * ```
181
+ */
182
+ async create(data) {
183
+ return this.client.post("/v1/invoices", data);
184
+ }
185
+ /**
186
+ * Update an invoice
187
+ *
188
+ * If items are provided, they will replace all existing line items.
189
+ *
190
+ * @param id - Invoice UUID
191
+ * @param data - Fields to update
192
+ * @returns Updated invoice
193
+ *
194
+ * @example
195
+ * ```typescript
196
+ * const { data: invoice } = await workbench.invoices.update('invoice-uuid', {
197
+ * status: 'paid',
198
+ * notes: 'Paid via bank transfer on 2024-01-15'
199
+ * });
200
+ * ```
201
+ */
202
+ async update(id, data) {
203
+ return this.client.put(`/v1/invoices/${id}`, data);
204
+ }
205
+ /**
206
+ * Delete an invoice
207
+ *
208
+ * Permanently deletes an invoice and all associated line items.
209
+ * This action cannot be undone.
210
+ *
211
+ * @param id - Invoice UUID
212
+ *
213
+ * @example
214
+ * ```typescript
215
+ * await workbench.invoices.delete('invoice-uuid');
216
+ * ```
217
+ */
218
+ async delete(id) {
219
+ await this.client.delete(`/v1/invoices/${id}`);
220
+ }
221
+ /**
222
+ * Send an invoice via email
223
+ *
224
+ * Sends the invoice to the client's email address. The invoice
225
+ * status will be updated to 'sent' if currently 'draft'.
226
+ *
227
+ * @param id - Invoice UUID
228
+ * @returns Success response
229
+ *
230
+ * @example
231
+ * ```typescript
232
+ * await workbench.invoices.send('invoice-uuid');
233
+ * console.log('Invoice sent successfully');
234
+ * ```
235
+ */
236
+ async send(id) {
237
+ return this.client.post(`/v1/invoices/${id}/send`);
238
+ }
239
+ };
240
+
241
+ // src/resources/quotes.ts
242
+ var QuotesResource = class {
243
+ client;
244
+ constructor(client) {
245
+ this.client = client;
246
+ }
247
+ /**
248
+ * List all quotes
249
+ *
250
+ * Returns a paginated list of quotes for the authenticated business.
251
+ *
252
+ * @param options - List options (pagination, filtering, sorting)
253
+ * @returns Paginated list of quotes
254
+ *
255
+ * @example
256
+ * ```typescript
257
+ * // List pending quotes
258
+ * const { data, pagination } = await workbench.quotes.list({
259
+ * status: 'sent',
260
+ * per_page: 20
261
+ * });
262
+ * ```
263
+ */
264
+ async list(options = {}) {
265
+ return this.client.get("/v1/quotes", {
266
+ page: options.page,
267
+ per_page: options.per_page,
268
+ search: options.search,
269
+ sort: options.sort,
270
+ order: options.order,
271
+ status: options.status,
272
+ client_id: options.client_id
273
+ });
274
+ }
275
+ /**
276
+ * Get a quote by ID
277
+ *
278
+ * @param id - Quote UUID
279
+ * @returns Quote details with line items
280
+ *
281
+ * @example
282
+ * ```typescript
283
+ * const { data: quote } = await workbench.quotes.get('quote-uuid');
284
+ * console.log(`Quote ${quote.quote_number}: $${quote.total}`);
285
+ * ```
286
+ */
287
+ async get(id) {
288
+ return this.client.get(`/v1/quotes/${id}`);
289
+ }
290
+ /**
291
+ * Create a new quote
292
+ *
293
+ * Creates a quote with line items. The quote number is
294
+ * automatically generated by Workbench.
295
+ *
296
+ * @param data - Quote data including line items
297
+ * @returns Created quote
298
+ *
299
+ * @example
300
+ * ```typescript
301
+ * const { data: quote } = await workbench.quotes.create({
302
+ * client_id: 'client-uuid',
303
+ * valid_until: '2024-03-01',
304
+ * items: [
305
+ * { description: 'Plumbing Service', quantity: 4, unit_price: 75 }
306
+ * ],
307
+ * notes: 'Quote valid for 30 days',
308
+ * terms: 'Payment due upon completion'
309
+ * });
310
+ * ```
311
+ */
312
+ async create(data) {
313
+ return this.client.post("/v1/quotes", data);
314
+ }
315
+ /**
316
+ * Update a quote
317
+ *
318
+ * If items are provided, they will replace all existing line items.
319
+ *
320
+ * @param id - Quote UUID
321
+ * @param data - Fields to update
322
+ * @returns Updated quote
323
+ *
324
+ * @example
325
+ * ```typescript
326
+ * const { data: quote } = await workbench.quotes.update('quote-uuid', {
327
+ * status: 'approved',
328
+ * notes: 'Client approved via email on 2024-01-15'
329
+ * });
330
+ * ```
331
+ */
332
+ async update(id, data) {
333
+ return this.client.put(`/v1/quotes/${id}`, data);
334
+ }
335
+ /**
336
+ * Delete a quote
337
+ *
338
+ * Permanently deletes a quote and all associated line items.
339
+ * This action cannot be undone.
340
+ *
341
+ * @param id - Quote UUID
342
+ *
343
+ * @example
344
+ * ```typescript
345
+ * await workbench.quotes.delete('quote-uuid');
346
+ * ```
347
+ */
348
+ async delete(id) {
349
+ await this.client.delete(`/v1/quotes/${id}`);
350
+ }
351
+ /**
352
+ * Send a quote via email
353
+ *
354
+ * Sends the quote to the client's email address. The quote
355
+ * status will be updated to 'sent' if currently 'draft'.
356
+ *
357
+ * @param id - Quote UUID
358
+ * @returns Success response
359
+ *
360
+ * @example
361
+ * ```typescript
362
+ * await workbench.quotes.send('quote-uuid');
363
+ * console.log('Quote sent successfully');
364
+ * ```
365
+ */
366
+ async send(id) {
367
+ return this.client.post(`/v1/quotes/${id}/send`);
368
+ }
369
+ };
370
+
371
+ // src/resources/jobs.ts
372
+ var JobsResource = class {
373
+ client;
374
+ constructor(client) {
375
+ this.client = client;
376
+ }
377
+ /**
378
+ * List all jobs
379
+ *
380
+ * Returns a paginated list of jobs for the authenticated business.
381
+ *
382
+ * @param options - List options (pagination, filtering, sorting)
383
+ * @returns Paginated list of jobs
384
+ *
385
+ * @example
386
+ * ```typescript
387
+ * // List scheduled jobs
388
+ * const { data, pagination } = await workbench.jobs.list({
389
+ * status: 'scheduled',
390
+ * priority: 'high',
391
+ * per_page: 20
392
+ * });
393
+ * ```
394
+ */
395
+ async list(options = {}) {
396
+ return this.client.get("/v1/jobs", {
397
+ page: options.page,
398
+ per_page: options.per_page,
399
+ search: options.search,
400
+ sort: options.sort,
401
+ order: options.order,
402
+ status: options.status,
403
+ priority: options.priority,
404
+ client_id: options.client_id
405
+ });
406
+ }
407
+ /**
408
+ * Get a job by ID
409
+ *
410
+ * @param id - Job UUID
411
+ * @returns Job details
412
+ *
413
+ * @example
414
+ * ```typescript
415
+ * const { data: job } = await workbench.jobs.get('job-uuid');
416
+ * console.log(`Job: ${job.title} (${job.status})`);
417
+ * ```
418
+ */
419
+ async get(id) {
420
+ return this.client.get(`/v1/jobs/${id}`);
421
+ }
422
+ /**
423
+ * Create a new job
424
+ *
425
+ * @param data - Job data
426
+ * @returns Created job
427
+ *
428
+ * @example
429
+ * ```typescript
430
+ * const { data: job } = await workbench.jobs.create({
431
+ * client_id: 'client-uuid',
432
+ * title: 'HVAC Maintenance',
433
+ * description: 'Annual AC maintenance and filter replacement',
434
+ * priority: 'medium',
435
+ * scheduled_start: '2024-01-25T10:00:00Z',
436
+ * estimated_duration: 120, // minutes
437
+ * notes: 'Customer prefers morning appointments'
438
+ * });
439
+ * ```
440
+ */
441
+ async create(data) {
442
+ return this.client.post("/v1/jobs", data);
443
+ }
444
+ /**
445
+ * Update a job
446
+ *
447
+ * @param id - Job UUID
448
+ * @param data - Fields to update
449
+ * @returns Updated job
450
+ *
451
+ * @example
452
+ * ```typescript
453
+ * // Mark job as started
454
+ * const { data: job } = await workbench.jobs.update('job-uuid', {
455
+ * status: 'in_progress',
456
+ * actual_start: new Date().toISOString()
457
+ * });
458
+ *
459
+ * // Mark job as completed
460
+ * await workbench.jobs.update('job-uuid', {
461
+ * status: 'completed',
462
+ * actual_end: new Date().toISOString()
463
+ * });
464
+ * ```
465
+ */
466
+ async update(id, data) {
467
+ return this.client.put(`/v1/jobs/${id}`, data);
468
+ }
469
+ /**
470
+ * Delete a job
471
+ *
472
+ * Permanently deletes a job. This action cannot be undone.
473
+ *
474
+ * @param id - Job UUID
475
+ *
476
+ * @example
477
+ * ```typescript
478
+ * await workbench.jobs.delete('job-uuid');
479
+ * ```
480
+ */
481
+ async delete(id) {
482
+ await this.client.delete(`/v1/jobs/${id}`);
483
+ }
484
+ };
485
+
486
+ // src/resources/service-requests.ts
487
+ var ServiceRequestsResource = class {
488
+ client;
489
+ constructor(client) {
490
+ this.client = client;
491
+ }
492
+ /**
493
+ * List all service requests
494
+ *
495
+ * Returns a paginated list of service requests for the authenticated business.
496
+ *
497
+ * @param options - List options (pagination, filtering, sorting)
498
+ * @returns Paginated list of service requests
499
+ *
500
+ * @example
501
+ * ```typescript
502
+ * // List new requests
503
+ * const { data, pagination } = await workbench.serviceRequests.list({
504
+ * status: 'new',
505
+ * priority: 'urgent',
506
+ * per_page: 50
507
+ * });
508
+ * ```
509
+ */
510
+ async list(options = {}) {
511
+ return this.client.get("/v1/service-requests", {
512
+ page: options.page,
513
+ per_page: options.per_page,
514
+ search: options.search,
515
+ sort: options.sort,
516
+ order: options.order,
517
+ status: options.status,
518
+ priority: options.priority,
519
+ client_id: options.client_id
520
+ });
521
+ }
522
+ /**
523
+ * Get a service request by ID
524
+ *
525
+ * @param id - Service request UUID
526
+ * @returns Service request details
527
+ *
528
+ * @example
529
+ * ```typescript
530
+ * const { data: request } = await workbench.serviceRequests.get('request-uuid');
531
+ * console.log(`Request: ${request.title} (${request.status})`);
532
+ * ```
533
+ */
534
+ async get(id) {
535
+ return this.client.get(`/v1/service-requests/${id}`);
536
+ }
537
+ /**
538
+ * Create a new service request
539
+ *
540
+ * @param data - Service request data
541
+ * @returns Created service request
542
+ *
543
+ * @example
544
+ * ```typescript
545
+ * const { data: request } = await workbench.serviceRequests.create({
546
+ * title: 'AC Not Cooling',
547
+ * description: 'Air conditioner is running but not producing cold air',
548
+ * contact_name: 'John Doe',
549
+ * contact_email: 'john@example.com',
550
+ * contact_phone: '+1-555-123-4567',
551
+ * address: '456 Oak Ave, Anytown, USA',
552
+ * priority: 'urgent',
553
+ * requested_date: '2024-01-20',
554
+ * preferred_time: 'Morning (8am-12pm)',
555
+ * source: 'website'
556
+ * });
557
+ * ```
558
+ */
559
+ async create(data) {
560
+ return this.client.post("/v1/service-requests", data);
561
+ }
562
+ /**
563
+ * Update a service request
564
+ *
565
+ * @param id - Service request UUID
566
+ * @param data - Fields to update
567
+ * @returns Updated service request
568
+ *
569
+ * @example
570
+ * ```typescript
571
+ * // Assign to client and schedule
572
+ * const { data: request } = await workbench.serviceRequests.update('request-uuid', {
573
+ * client_id: 'client-uuid',
574
+ * status: 'scheduled',
575
+ * notes: 'Scheduled for Monday morning'
576
+ * });
577
+ * ```
578
+ */
579
+ async update(id, data) {
580
+ return this.client.put(`/v1/service-requests/${id}`, data);
581
+ }
582
+ /**
583
+ * Delete a service request
584
+ *
585
+ * Permanently deletes a service request. This action cannot be undone.
586
+ *
587
+ * @param id - Service request UUID
588
+ *
589
+ * @example
590
+ * ```typescript
591
+ * await workbench.serviceRequests.delete('request-uuid');
592
+ * ```
593
+ */
594
+ async delete(id) {
595
+ await this.client.delete(`/v1/service-requests/${id}`);
596
+ }
597
+ };
598
+
599
+ // src/resources/webhooks.ts
600
+ var WebhooksResource = class {
601
+ client;
602
+ constructor(client) {
603
+ this.client = client;
604
+ }
605
+ /**
606
+ * List all webhooks
607
+ *
608
+ * Returns a paginated list of webhook subscriptions for the authenticated business.
609
+ *
610
+ * @param options - List options (pagination)
611
+ * @returns Paginated list of webhooks
612
+ *
613
+ * @example
614
+ * ```typescript
615
+ * const { data: webhooks } = await workbench.webhooks.list();
616
+ * webhooks.forEach(webhook => {
617
+ * console.log(`${webhook.name}: ${webhook.events.join(', ')}`);
618
+ * });
619
+ * ```
620
+ */
621
+ async list(options = {}) {
622
+ return this.client.get("/v1/webhooks", {
623
+ page: options.page,
624
+ per_page: options.per_page
625
+ });
626
+ }
627
+ /**
628
+ * Get a webhook by ID
629
+ *
630
+ * @param id - Webhook UUID
631
+ * @returns Webhook details
632
+ *
633
+ * @example
634
+ * ```typescript
635
+ * const { data: webhook } = await workbench.webhooks.get('webhook-uuid');
636
+ * console.log(`Webhook: ${webhook.name}`);
637
+ * console.log(`Events: ${webhook.events.join(', ')}`);
638
+ * ```
639
+ */
640
+ async get(id) {
641
+ return this.client.get(`/v1/webhooks/${id}`);
642
+ }
643
+ /**
644
+ * Create a new webhook
645
+ *
646
+ * Creates a webhook subscription. The webhook secret is returned
647
+ * in the response - store it securely to verify webhook signatures.
648
+ *
649
+ * @param data - Webhook data
650
+ * @returns Created webhook (includes secret)
651
+ *
652
+ * @example
653
+ * ```typescript
654
+ * const { data: webhook } = await workbench.webhooks.create({
655
+ * name: 'All Events',
656
+ * url: 'https://example.com/webhooks',
657
+ * events: [
658
+ * 'client.created',
659
+ * 'client.updated',
660
+ * 'invoice.created',
661
+ * 'invoice.paid',
662
+ * 'quote.accepted',
663
+ * 'job.completed'
664
+ * ]
665
+ * });
666
+ *
667
+ * // IMPORTANT: Store the secret securely!
668
+ * console.log('Store this secret:', webhook.secret);
669
+ * ```
670
+ */
671
+ async create(data) {
672
+ return this.client.post("/v1/webhooks", data);
673
+ }
674
+ /**
675
+ * Update a webhook
676
+ *
677
+ * @param id - Webhook UUID
678
+ * @param data - Fields to update
679
+ * @returns Updated webhook
680
+ *
681
+ * @example
682
+ * ```typescript
683
+ * // Add more events to the webhook
684
+ * const { data: webhook } = await workbench.webhooks.update('webhook-uuid', {
685
+ * events: ['invoice.created', 'invoice.paid', 'invoice.overdue']
686
+ * });
687
+ *
688
+ * // Disable a webhook
689
+ * await workbench.webhooks.update('webhook-uuid', { is_active: false });
690
+ * ```
691
+ */
692
+ async update(id, data) {
693
+ return this.client.put(`/v1/webhooks/${id}`, data);
694
+ }
695
+ /**
696
+ * Delete a webhook
697
+ *
698
+ * Permanently deletes a webhook subscription. No more events
699
+ * will be sent to this endpoint.
700
+ *
701
+ * @param id - Webhook UUID
702
+ *
703
+ * @example
704
+ * ```typescript
705
+ * await workbench.webhooks.delete('webhook-uuid');
706
+ * ```
707
+ */
708
+ async delete(id) {
709
+ await this.client.delete(`/v1/webhooks/${id}`);
710
+ }
711
+ /**
712
+ * List webhook deliveries
713
+ *
714
+ * Returns recent delivery attempts for a webhook. Useful for
715
+ * debugging and monitoring webhook health.
716
+ *
717
+ * @param webhookId - Webhook UUID
718
+ * @param options - List options (pagination, filtering)
719
+ * @returns Paginated list of delivery attempts
720
+ *
721
+ * @example
722
+ * ```typescript
723
+ * const { data: deliveries } = await workbench.webhooks.listDeliveries('webhook-uuid', {
724
+ * per_page: 50
725
+ * });
726
+ *
727
+ * deliveries.forEach(delivery => {
728
+ * const status = delivery.delivered_at ? 'delivered' : 'pending';
729
+ * console.log(`${delivery.event_type}: ${status}`);
730
+ * });
731
+ * ```
732
+ */
733
+ async listDeliveries(webhookId, options = {}) {
734
+ return this.client.get(`/v1/webhooks/${webhookId}/deliveries`, {
735
+ page: options.page,
736
+ per_page: options.per_page,
737
+ event_type: options.event_type
738
+ });
739
+ }
740
+ /**
741
+ * Send a test webhook
742
+ *
743
+ * Sends a test event to the webhook endpoint. Useful for
744
+ * verifying your webhook handler is working correctly.
745
+ *
746
+ * @param id - Webhook UUID
747
+ * @returns Test delivery result
748
+ *
749
+ * @example
750
+ * ```typescript
751
+ * const { data: result } = await workbench.webhooks.test('webhook-uuid');
752
+ * console.log('Test delivery:', result);
753
+ * ```
754
+ */
755
+ async test(id) {
756
+ return this.client.post(`/v1/webhooks/${id}/test`);
757
+ }
758
+ };
759
+
760
+ // src/client.ts
761
+ var DEFAULT_BASE_URL = "https://api.tryworkbench.app";
762
+ var DEFAULT_TIMEOUT = 3e4;
763
+ var DEFAULT_MAX_RETRIES = 3;
764
+ var WorkbenchError = class extends Error {
765
+ /** HTTP status code */
766
+ status;
767
+ /** Error code from the API */
768
+ code;
769
+ /** Additional error details */
770
+ details;
771
+ /** Request ID for debugging */
772
+ requestId;
773
+ constructor(message, status, code, details, requestId) {
774
+ super(message);
775
+ this.name = "WorkbenchError";
776
+ this.status = status;
777
+ this.code = code;
778
+ this.details = details;
779
+ this.requestId = requestId;
780
+ }
781
+ };
782
+ var WorkbenchClient = class {
783
+ baseUrl;
784
+ timeout;
785
+ maxRetries;
786
+ authHeader;
787
+ /** Clients resource */
788
+ clients;
789
+ /** Invoices resource */
790
+ invoices;
791
+ /** Quotes resource */
792
+ quotes;
793
+ /** Jobs resource */
794
+ jobs;
795
+ /** Service requests resource */
796
+ serviceRequests;
797
+ /** Webhooks resource */
798
+ webhooks;
799
+ /**
800
+ * Create a new Workbench client
801
+ *
802
+ * @param config - Client configuration
803
+ * @throws Error if neither apiKey nor accessToken is provided
804
+ */
805
+ constructor(config) {
806
+ if (!config.apiKey && !config.accessToken) {
807
+ throw new Error("Either apiKey or accessToken must be provided");
808
+ }
809
+ this.baseUrl = config.baseUrl || DEFAULT_BASE_URL;
810
+ this.timeout = config.timeout || DEFAULT_TIMEOUT;
811
+ this.maxRetries = config.maxRetries || DEFAULT_MAX_RETRIES;
812
+ const token = config.accessToken || config.apiKey;
813
+ this.authHeader = `Bearer ${token}`;
814
+ this.clients = new ClientsResource(this);
815
+ this.invoices = new InvoicesResource(this);
816
+ this.quotes = new QuotesResource(this);
817
+ this.jobs = new JobsResource(this);
818
+ this.serviceRequests = new ServiceRequestsResource(this);
819
+ this.webhooks = new WebhooksResource(this);
820
+ }
821
+ /**
822
+ * Build URL with query parameters
823
+ */
824
+ buildUrl(path, query) {
825
+ const url = new URL(path, this.baseUrl);
826
+ if (query) {
827
+ for (const [key, value] of Object.entries(query)) {
828
+ if (value !== void 0) {
829
+ url.searchParams.append(key, String(value));
830
+ }
831
+ }
832
+ }
833
+ return url.toString();
834
+ }
835
+ /**
836
+ * Sleep for a specified duration
837
+ */
838
+ sleep(ms) {
839
+ return new Promise((resolve) => setTimeout(resolve, ms));
840
+ }
841
+ /**
842
+ * Calculate exponential backoff delay
843
+ */
844
+ getRetryDelay(attempt) {
845
+ return Math.min(1e3 * Math.pow(2, attempt), 1e4);
846
+ }
847
+ /**
848
+ * Determine if an error is retryable
849
+ */
850
+ isRetryable(status) {
851
+ return status === 429 || status >= 500 && status < 600;
852
+ }
853
+ /**
854
+ * Make an API request
855
+ *
856
+ * @param options - Request options
857
+ * @returns API response
858
+ * @throws WorkbenchError if the request fails
859
+ */
860
+ async request(options) {
861
+ const { method, path, query, body, headers } = options;
862
+ const url = this.buildUrl(path, query);
863
+ const requestHeaders = {
864
+ "Authorization": this.authHeader,
865
+ "Content-Type": "application/json",
866
+ "Accept": "application/json",
867
+ ...headers
868
+ };
869
+ let lastError = null;
870
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
871
+ try {
872
+ const controller = new AbortController();
873
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
874
+ const response = await fetch(url, {
875
+ method,
876
+ headers: requestHeaders,
877
+ body: body ? JSON.stringify(body) : void 0,
878
+ signal: controller.signal
879
+ });
880
+ clearTimeout(timeoutId);
881
+ const responseText = await response.text();
882
+ let responseData;
883
+ try {
884
+ responseData = responseText ? JSON.parse(responseText) : {};
885
+ } catch {
886
+ throw new WorkbenchError(
887
+ "Invalid JSON response from API",
888
+ response.status,
889
+ "INVALID_RESPONSE"
890
+ );
891
+ }
892
+ if (!response.ok) {
893
+ const errorResponse = responseData;
894
+ if (this.isRetryable(response.status) && attempt < this.maxRetries) {
895
+ const delay = this.getRetryDelay(attempt);
896
+ await this.sleep(delay);
897
+ continue;
898
+ }
899
+ throw new WorkbenchError(
900
+ errorResponse.error?.message || "Unknown error",
901
+ response.status,
902
+ errorResponse.error?.code || "UNKNOWN_ERROR",
903
+ errorResponse.error?.details,
904
+ errorResponse.meta?.request_id
905
+ );
906
+ }
907
+ return responseData;
908
+ } catch (error) {
909
+ if (error instanceof WorkbenchError) {
910
+ throw error;
911
+ }
912
+ if (error instanceof Error && error.name === "AbortError") {
913
+ lastError = new WorkbenchError(
914
+ "Request timeout",
915
+ 0,
916
+ "TIMEOUT"
917
+ );
918
+ } else {
919
+ lastError = error instanceof Error ? error : new Error(String(error));
920
+ }
921
+ if (attempt < this.maxRetries) {
922
+ const delay = this.getRetryDelay(attempt);
923
+ await this.sleep(delay);
924
+ continue;
925
+ }
926
+ }
927
+ }
928
+ throw lastError || new WorkbenchError("Request failed", 0, "UNKNOWN_ERROR");
929
+ }
930
+ /**
931
+ * Make a GET request
932
+ */
933
+ async get(path, query) {
934
+ return this.request({ method: "GET", path, query });
935
+ }
936
+ /**
937
+ * Make a POST request
938
+ */
939
+ async post(path, body) {
940
+ return this.request({ method: "POST", path, body });
941
+ }
942
+ /**
943
+ * Make a PUT request
944
+ */
945
+ async put(path, body) {
946
+ return this.request({ method: "PUT", path, body });
947
+ }
948
+ /**
949
+ * Make a DELETE request
950
+ */
951
+ async delete(path) {
952
+ return this.request({ method: "DELETE", path });
953
+ }
954
+ };
955
+
956
+ // src/utils/webhook-verify.ts
957
+ import { createHmac, timingSafeEqual } from "crypto";
958
+ var WebhookVerificationError = class extends Error {
959
+ constructor(message) {
960
+ super(message);
961
+ this.name = "WebhookVerificationError";
962
+ }
963
+ };
964
+ function parseSignatureHeader(header) {
965
+ if (!header || typeof header !== "string") {
966
+ throw new WebhookVerificationError("Missing or invalid signature header");
967
+ }
968
+ const parts = header.split(",");
969
+ let timestamp;
970
+ let signature;
971
+ for (const part of parts) {
972
+ const [key, value] = part.split("=");
973
+ if (key === "t") {
974
+ timestamp = parseInt(value, 10);
975
+ if (isNaN(timestamp)) {
976
+ throw new WebhookVerificationError("Invalid timestamp in signature header");
977
+ }
978
+ } else if (key === "v1") {
979
+ signature = value;
980
+ }
981
+ }
982
+ if (timestamp === void 0 || !signature) {
983
+ throw new WebhookVerificationError("Invalid signature header format");
984
+ }
985
+ return { timestamp, signature };
986
+ }
987
+ function computeSignature(payload, secret, timestamp) {
988
+ const payloadString = typeof payload === "string" ? payload : payload.toString("utf8");
989
+ const signedPayload = `${timestamp}.${payloadString}`;
990
+ return createHmac("sha256", secret).update(signedPayload).digest("hex");
991
+ }
992
+ function verifyWebhookSignature(payload, signature, secret, options = {}) {
993
+ const { tolerance = 300 } = options;
994
+ const { timestamp, signature: providedSignature } = parseSignatureHeader(signature);
995
+ if (tolerance > 0) {
996
+ const now = Math.floor(Date.now() / 1e3);
997
+ const age = now - timestamp;
998
+ if (age > tolerance) {
999
+ throw new WebhookVerificationError(
1000
+ `Webhook timestamp is too old (${age} seconds). Maximum allowed age is ${tolerance} seconds.`
1001
+ );
1002
+ }
1003
+ if (age < -tolerance) {
1004
+ throw new WebhookVerificationError(
1005
+ "Webhook timestamp is in the future. Check your server clock."
1006
+ );
1007
+ }
1008
+ }
1009
+ const expectedSignature = computeSignature(payload, secret, timestamp);
1010
+ const expectedBuffer = Buffer.from(expectedSignature, "utf8");
1011
+ const providedBuffer = Buffer.from(providedSignature, "utf8");
1012
+ if (expectedBuffer.length !== providedBuffer.length) {
1013
+ throw new WebhookVerificationError("Invalid webhook signature");
1014
+ }
1015
+ if (!timingSafeEqual(expectedBuffer, providedBuffer)) {
1016
+ throw new WebhookVerificationError("Invalid webhook signature");
1017
+ }
1018
+ return true;
1019
+ }
1020
+ function constructWebhookEvent(payload, signature, secret, options = {}) {
1021
+ verifyWebhookSignature(payload, signature, secret, options);
1022
+ const payloadString = typeof payload === "string" ? payload : payload.toString("utf8");
1023
+ try {
1024
+ return JSON.parse(payloadString);
1025
+ } catch {
1026
+ throw new WebhookVerificationError("Invalid webhook payload: not valid JSON");
1027
+ }
1028
+ }
1029
+ export {
1030
+ ClientsResource,
1031
+ InvoicesResource,
1032
+ JobsResource,
1033
+ QuotesResource,
1034
+ ServiceRequestsResource,
1035
+ WebhookVerificationError,
1036
+ WebhooksResource,
1037
+ WorkbenchClient,
1038
+ WorkbenchError,
1039
+ computeSignature,
1040
+ constructWebhookEvent,
1041
+ parseSignatureHeader,
1042
+ verifyWebhookSignature
1043
+ };