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