@slotchain/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.
@@ -0,0 +1,1422 @@
1
+ // src/index.ts
2
+ import axios2 from "axios";
3
+
4
+ // src/clients/tenant-client.ts
5
+ var TenantClient = class {
6
+ constructor(client) {
7
+ this.client = client;
8
+ }
9
+ /**
10
+ * Get tenant configuration by slug
11
+ * GET /api/v1/tenant-config/:slug
12
+ */
13
+ async getBySlug(slug) {
14
+ const response = await this.client.get(
15
+ `/api/v1/tenant-config/${slug}`
16
+ );
17
+ return response.data;
18
+ }
19
+ /**
20
+ * Get tenant by ID
21
+ * GET /api/v1/tenants/:id
22
+ */
23
+ async getById(id) {
24
+ const response = await this.client.get(
25
+ `/api/v1/tenants/${id}`
26
+ );
27
+ return response.data;
28
+ }
29
+ /**
30
+ * List all tenants
31
+ * GET /api/v1/tenants
32
+ */
33
+ async list(params) {
34
+ const response = await this.client.get(
35
+ "/api/v1/tenants",
36
+ { params }
37
+ );
38
+ return response.data;
39
+ }
40
+ /**
41
+ * Create a new tenant
42
+ * POST /api/v1/tenants
43
+ */
44
+ async create(data) {
45
+ const response = await this.client.post(
46
+ "/api/v1/tenants",
47
+ data
48
+ );
49
+ return response.data;
50
+ }
51
+ /**
52
+ * Update tenant by ID
53
+ * PATCH /api/v1/tenants/:id
54
+ */
55
+ async update(id, data) {
56
+ const response = await this.client.patch(
57
+ `/api/v1/tenants/${id}`,
58
+ data
59
+ );
60
+ return response.data;
61
+ }
62
+ /**
63
+ * Delete tenant by ID
64
+ * DELETE /api/v1/tenants/:id
65
+ */
66
+ async delete(id) {
67
+ const response = await this.client.delete(
68
+ `/api/v1/tenants/${id}`
69
+ );
70
+ return response.data;
71
+ }
72
+ /**
73
+ * Get full tenant configuration including branding, services, and service items
74
+ * GET /api/v1/tenants/:slug
75
+ *
76
+ * This method returns the complete tenant configuration including:
77
+ * - Tenant basic info (id, slug, name)
78
+ * - Branding configuration (logo, colors, etc.)
79
+ * - All services associated with the tenant (with nested service items)
80
+ * - Feature flags and subscription level
81
+ *
82
+ * Note: Service items are nested within each service object (not as a separate top-level array).
83
+ * The endpoint returns full config by default (includes services with nested items).
84
+ *
85
+ * @param slug - Tenant slug identifier (required, non-nullable)
86
+ * @param includeServices - Include services with nested items (default: true)
87
+ * @returns Promise resolving to ApiResponse<TenantFullConfig>
88
+ *
89
+ * @example
90
+ * ```typescript
91
+ * const result = await slotly.tenant.getFullConfig('bright-accountants');
92
+ * if (result.success && result.data) {
93
+ * const config = result.data;
94
+ * console.log('Tenant:', config.name);
95
+ * console.log('Logo:', config.branding?.logoUrl);
96
+ * console.log('Services:', config.services.length);
97
+ *
98
+ * // Service items are nested within each service
99
+ * config.services.forEach(service => {
100
+ * console.log(`Service: ${service.name}`);
101
+ * if (service.serviceItems) {
102
+ * console.log(` Items: ${service.serviceItems.length}`);
103
+ * }
104
+ * });
105
+ * }
106
+ * ```
107
+ *
108
+ * **Guaranteed fields (non-nullable):**
109
+ * - `id`: string
110
+ * - `slug`: string
111
+ * - `name`: string
112
+ * - `services`: ServiceWithItems[] (may be empty array, each service may have nested `serviceItems`)
113
+ *
114
+ * **Optional fields:**
115
+ * - `branding`: TenantBranding | undefined
116
+ * - `services[].serviceItems`: ServiceItem[] | undefined (nested within each service)
117
+ * - `featuresEnabled`: string[] | undefined
118
+ * - `subscriptionLevel`: string | undefined
119
+ * - `config`: Record<string, any> | undefined
120
+ *
121
+ * **Error Codes:**
122
+ * - `TENANT_NOT_FOUND` - Tenant with slug not found
123
+ * - `AUTH_ERROR` - Authentication failed
124
+ * - `PERMISSION_DENIED` - Insufficient permissions
125
+ * - `NETWORK_ERROR` - Network request failed
126
+ */
127
+ async getFullConfig(slug, includeServices = true) {
128
+ const response = await this.client.get(
129
+ `/api/v1/tenants/${slug}`,
130
+ { params: { includeServices } }
131
+ );
132
+ return response.data;
133
+ }
134
+ };
135
+
136
+ // src/clients/booking-client.ts
137
+ var BookingClient = class {
138
+ constructor(client) {
139
+ this.client = client;
140
+ }
141
+ /**
142
+ * Get booking by ID
143
+ * GET /api/v1/bookings/:id
144
+ */
145
+ async getById(id) {
146
+ const response = await this.client.get(
147
+ `/api/v1/bookings/${id}`
148
+ );
149
+ return response.data;
150
+ }
151
+ /**
152
+ * List bookings
153
+ * GET /api/v1/bookings
154
+ */
155
+ async list(params) {
156
+ const response = await this.client.get(
157
+ "/api/v1/bookings",
158
+ { params }
159
+ );
160
+ return response.data;
161
+ }
162
+ /**
163
+ * Create a new booking (publishes booking.created event)
164
+ * POST /api/v1/bookings
165
+ */
166
+ async create(data) {
167
+ const response = await this.client.post(
168
+ "/api/v1/bookings",
169
+ data
170
+ );
171
+ return response.data;
172
+ }
173
+ /**
174
+ * Update booking by ID
175
+ * PUT /api/v1/bookings/:id
176
+ */
177
+ async update(id, data) {
178
+ const response = await this.client.put(
179
+ `/api/v1/bookings/${id}`,
180
+ data
181
+ );
182
+ return response.data;
183
+ }
184
+ /**
185
+ * Cancel booking by ID
186
+ * Note: Cancel is typically done via update with status change
187
+ * If dedicated cancel endpoint exists, update this method
188
+ */
189
+ async cancel(id) {
190
+ return this.update(id, { status: "cancelled" });
191
+ }
192
+ /**
193
+ * Delete booking by ID (soft delete)
194
+ * DELETE /api/v1/bookings/:id
195
+ */
196
+ async delete(id) {
197
+ const response = await this.client.delete(
198
+ `/api/v1/bookings/${id}`
199
+ );
200
+ return response.data;
201
+ }
202
+ };
203
+
204
+ // src/clients/service-client.ts
205
+ var ServiceClient = class {
206
+ constructor(client) {
207
+ this.client = client;
208
+ }
209
+ /**
210
+ * Get service by ID
211
+ * GET /api/v1/services/:id
212
+ *
213
+ * @param id - Service ID
214
+ * @param includeItems - Include nested service items (default: false)
215
+ * @returns Service details (with items if includeItems=true)
216
+ */
217
+ async getById(id, includeItems = false) {
218
+ const response = await this.client.get(
219
+ `/api/v1/services/${id}`,
220
+ includeItems ? { params: { includeItems: true } } : void 0
221
+ );
222
+ return response.data;
223
+ }
224
+ /**
225
+ * List services by tenant slug
226
+ * GET /api/v1/services?tenantSlug=:slug
227
+ *
228
+ * @param slug - Tenant slug (or use tenant_id in list() method)
229
+ * @param params - Optional parameters (includeItems, page, limit, is_active)
230
+ * @returns List of services for the tenant
231
+ */
232
+ async listServices(slug, params) {
233
+ const response = await this.client.get(
234
+ "/api/v1/services",
235
+ { params: { tenantSlug: slug, ...params } }
236
+ );
237
+ return response.data;
238
+ }
239
+ /**
240
+ * List services with optional filters
241
+ * GET /api/v1/services
242
+ *
243
+ * @param params - Query parameters (tenant_id, tenantSlug, slot_id, page, limit, is_active, includeItems)
244
+ * @returns Paginated list of services
245
+ */
246
+ async list(params) {
247
+ const response = await this.client.get(
248
+ "/api/v1/services",
249
+ { params }
250
+ );
251
+ return response.data;
252
+ }
253
+ /**
254
+ * Create a new service
255
+ * POST /api/v1/services
256
+ *
257
+ * @param data - Service creation data (tenant_id, name required)
258
+ * @returns Created service
259
+ */
260
+ async create(data) {
261
+ const response = await this.client.post(
262
+ "/api/v1/services",
263
+ data
264
+ );
265
+ return response.data;
266
+ }
267
+ /**
268
+ * Update service by ID
269
+ * PUT /api/v1/services/:id
270
+ *
271
+ * @param id - Service ID
272
+ * @param data - Service update data
273
+ * @returns Updated service
274
+ */
275
+ async update(id, data) {
276
+ const response = await this.client.put(
277
+ `/api/v1/services/${id}`,
278
+ data
279
+ );
280
+ return response.data;
281
+ }
282
+ /**
283
+ * Delete service by ID
284
+ * DELETE /api/v1/services/:id
285
+ *
286
+ * @param id - Service ID
287
+ * @returns Empty response on success
288
+ */
289
+ async delete(id) {
290
+ const response = await this.client.delete(
291
+ `/api/v1/services/${id}`
292
+ );
293
+ return response.data;
294
+ }
295
+ /**
296
+ * Bulk create services
297
+ * POST /api/v1/services/bulk (if available)
298
+ *
299
+ * @param services - Array of services to create
300
+ * @returns Created services
301
+ */
302
+ async bulkCreate(services) {
303
+ const response = await this.client.post(
304
+ "/api/v1/services/bulk",
305
+ { services }
306
+ );
307
+ return response.data;
308
+ }
309
+ /**
310
+ * Get service with its service items nested
311
+ * GET /api/v1/services/:id?includeItems=true
312
+ *
313
+ * @param id - Service ID
314
+ * @returns Service with nested service items
315
+ */
316
+ async getWithItems(id) {
317
+ const response = await this.client.get(
318
+ `/api/v1/services/${id}`,
319
+ { params: { includeItems: true } }
320
+ );
321
+ return response.data;
322
+ }
323
+ /**
324
+ * List services with their service items nested
325
+ * GET /api/v1/services?includeItems=true
326
+ *
327
+ * @param params - Query parameters (tenant_id, tenantSlug, slot_id, page, limit, is_active)
328
+ * @returns Paginated list of services with nested service items
329
+ */
330
+ async listWithItems(params) {
331
+ const response = await this.client.get(
332
+ "/api/v1/services",
333
+ { params: { includeItems: true, ...params } }
334
+ );
335
+ return response.data;
336
+ }
337
+ /**
338
+ * List all categories for a tenant (extracted from services)
339
+ * GET /api/v1/categories?tenantSlug=:slug
340
+ *
341
+ * @param tenantSlug - Tenant slug (or use tenant_id in params)
342
+ * @param params - Optional query parameters (tenant_id, includeServices)
343
+ * @returns List of categories for the tenant
344
+ */
345
+ async listCategories(tenantSlug, params) {
346
+ const response = await this.client.get(
347
+ "/api/v1/categories",
348
+ { params: { tenantSlug, ...params } }
349
+ );
350
+ return response.data;
351
+ }
352
+ /**
353
+ * Get category by ID (category name, lowercase)
354
+ * GET /api/v1/categories/:id?tenantSlug=:slug
355
+ *
356
+ * @param categoryId - Category ID (lowercase category name, e.g., "consulting")
357
+ * @param tenantSlug - Tenant slug (or use tenant_id in params)
358
+ * @param params - Optional query parameters (tenant_id, includeServices, includeItems)
359
+ * @returns Category details
360
+ */
361
+ async getCategory(categoryId, tenantSlug, params) {
362
+ const response = await this.client.get(
363
+ `/api/v1/categories/${categoryId}`,
364
+ { params: { tenantSlug, ...params } }
365
+ );
366
+ return response.data;
367
+ }
368
+ };
369
+
370
+ // src/clients/service-item-client.ts
371
+ var ServiceItemClient = class {
372
+ constructor(client) {
373
+ this.client = client;
374
+ }
375
+ /**
376
+ * Get service item by ID
377
+ * GET /api/v1/service-items/:id
378
+ *
379
+ * @param id - Service item ID
380
+ * @returns Service item details
381
+ */
382
+ async getById(id) {
383
+ const response = await this.client.get(
384
+ `/api/v1/service-items/${id}`
385
+ );
386
+ return response.data;
387
+ }
388
+ /**
389
+ * List all service items for a service
390
+ * GET /api/v1/services/:serviceId/items
391
+ *
392
+ * @param serviceId - Service ID
393
+ * @returns List of service items for the specified service
394
+ */
395
+ async listByService(serviceId) {
396
+ const response = await this.client.get(
397
+ `/api/v1/services/${serviceId}/items`
398
+ );
399
+ return response.data;
400
+ }
401
+ /**
402
+ * Create a new service item for a service
403
+ * POST /api/v1/services/:serviceId/items
404
+ *
405
+ * @param serviceId - Service ID
406
+ * @param data - Service item creation data
407
+ * @returns Created service item
408
+ */
409
+ async createForService(serviceId, data) {
410
+ const response = await this.client.post(
411
+ `/api/v1/services/${serviceId}/items`,
412
+ { ...data, service_id: serviceId }
413
+ );
414
+ return response.data;
415
+ }
416
+ /**
417
+ * Create a new service item
418
+ * POST /api/v1/service-items (alternative to createForService)
419
+ *
420
+ * @param data - Service item creation data (must include service_id)
421
+ * @returns Created service item
422
+ */
423
+ async create(data) {
424
+ const response = await this.client.post(
425
+ "/api/v1/service-items",
426
+ data
427
+ );
428
+ return response.data;
429
+ }
430
+ /**
431
+ * Update service item by ID
432
+ * PUT /api/v1/service-items/:id
433
+ *
434
+ * @param id - Service item ID
435
+ * @param data - Service item update data
436
+ * @returns Updated service item
437
+ */
438
+ async update(id, data) {
439
+ const response = await this.client.put(
440
+ `/api/v1/service-items/${id}`,
441
+ data
442
+ );
443
+ return response.data;
444
+ }
445
+ /**
446
+ * Delete service item by ID
447
+ * DELETE /api/v1/service-items/:id
448
+ *
449
+ * @param id - Service item ID
450
+ * @returns Empty response on success
451
+ */
452
+ async delete(id) {
453
+ const response = await this.client.delete(
454
+ `/api/v1/service-items/${id}`
455
+ );
456
+ return response.data;
457
+ }
458
+ };
459
+
460
+ // src/clients/slot-client.ts
461
+ var SlotClient = class {
462
+ constructor(client) {
463
+ this.client = client;
464
+ }
465
+ /**
466
+ * Get slot by ID
467
+ * GET /api/v1/slots/:id
468
+ *
469
+ * @param id - Slot ID
470
+ * @returns Slot details
471
+ */
472
+ async getById(id) {
473
+ const response = await this.client.get(
474
+ `/api/v1/slots/${id}`
475
+ );
476
+ return response.data;
477
+ }
478
+ /**
479
+ * List available slots
480
+ * GET /api/v1/slots
481
+ *
482
+ * @param params - Query parameters (tenant_id required, status, is_active, page, limit)
483
+ * @returns Paginated list of slots
484
+ */
485
+ async list(params) {
486
+ const response = await this.client.get(
487
+ "/api/v1/slots",
488
+ { params }
489
+ );
490
+ return response.data;
491
+ }
492
+ /**
493
+ * Create a new slot
494
+ * POST /api/v1/slots
495
+ *
496
+ * @param data - Slot creation data (tenant_id, name required)
497
+ * @returns Created slot
498
+ */
499
+ async create(data) {
500
+ const response = await this.client.post(
501
+ "/api/v1/slots",
502
+ data
503
+ );
504
+ return response.data;
505
+ }
506
+ /**
507
+ * Update slot by ID
508
+ * PUT /api/v1/slots/:id
509
+ *
510
+ * @param id - Slot ID
511
+ * @param data - Slot update data
512
+ * @returns Updated slot
513
+ */
514
+ async update(id, data) {
515
+ const response = await this.client.put(
516
+ `/api/v1/slots/${id}`,
517
+ data
518
+ );
519
+ return response.data;
520
+ }
521
+ /**
522
+ * Delete slot by ID
523
+ * DELETE /api/v1/slots/:id
524
+ *
525
+ * @param id - Slot ID
526
+ * @returns Empty response on success
527
+ */
528
+ async delete(id) {
529
+ const response = await this.client.delete(
530
+ `/api/v1/slots/${id}`
531
+ );
532
+ return response.data;
533
+ }
534
+ /**
535
+ * Bulk create slots
536
+ * POST /api/v1/slots/bulk (verify if available)
537
+ *
538
+ * @param slots - Array of slots to create
539
+ * @returns Created slots
540
+ */
541
+ async bulkCreate(slots) {
542
+ const response = await this.client.post(
543
+ "/api/v1/slots/bulk",
544
+ { slots }
545
+ );
546
+ return response.data;
547
+ }
548
+ /**
549
+ * Mark slot as available/unavailable
550
+ * Note: Use update() method with status or other fields
551
+ */
552
+ async setAvailability(id, available) {
553
+ return this.update(id, { status: available ? "active" : "inactive" });
554
+ }
555
+ /**
556
+ * Get all active services for a slot with their items
557
+ * GET /api/v1/slots/:id/services
558
+ *
559
+ * @param id - Slot ID
560
+ * @returns Services with nested service items
561
+ */
562
+ async getServices(id) {
563
+ const response = await this.client.get(
564
+ `/api/v1/slots/${id}/services`
565
+ );
566
+ return response.data;
567
+ }
568
+ /**
569
+ * Get slot with tenant information
570
+ * GET /api/v1/slots/:id/tenant-info
571
+ *
572
+ * @param id - Slot ID
573
+ * @returns Slot with tenant data
574
+ */
575
+ async getWithTenant(id) {
576
+ const response = await this.client.get(
577
+ `/api/v1/slots/${id}/tenant-info`
578
+ );
579
+ return response.data;
580
+ }
581
+ };
582
+
583
+ // src/clients/customer-client.ts
584
+ var CustomerClient = class {
585
+ constructor(client) {
586
+ this.client = client;
587
+ }
588
+ /**
589
+ * Get customer by ID
590
+ * GET /api/v1/customers/:id
591
+ *
592
+ * @param id - Customer ID
593
+ * @returns Customer details
594
+ */
595
+ async getById(id) {
596
+ const response = await this.client.get(
597
+ `/api/v1/customers/${id}`
598
+ );
599
+ return response.data;
600
+ }
601
+ /**
602
+ * Get customer by email
603
+ * GET /api/v1/customers?email=:email&tenant_id=:id
604
+ *
605
+ * @param email - Customer email
606
+ * @param tenantId - Required tenant ID for scoping
607
+ * @returns Customer details
608
+ */
609
+ async getByEmail(email, tenantId) {
610
+ const response = await this.client.get(
611
+ "/api/v1/customers",
612
+ { params: { email, tenant_id: tenantId } }
613
+ );
614
+ return response.data;
615
+ }
616
+ /**
617
+ * List customers with optional filters
618
+ * GET /api/v1/customers
619
+ *
620
+ * @param params - Query parameters (tenant_id required, page, limit, search, is_guest)
621
+ * @returns Paginated list of customers
622
+ */
623
+ async list(params) {
624
+ const response = await this.client.get(
625
+ "/api/v1/customers",
626
+ { params }
627
+ );
628
+ return response.data;
629
+ }
630
+ /**
631
+ * Create a new customer
632
+ * POST /api/v1/customers
633
+ *
634
+ * @param data - Customer creation data (tenant_id, name, email required)
635
+ * @returns Created customer
636
+ */
637
+ async create(data) {
638
+ const response = await this.client.post(
639
+ "/api/v1/customers",
640
+ data
641
+ );
642
+ return response.data;
643
+ }
644
+ /**
645
+ * Update customer by ID
646
+ * PUT /api/v1/customers/:id
647
+ *
648
+ * @param id - Customer ID
649
+ * @param data - Customer update data
650
+ * @returns Updated customer
651
+ */
652
+ async update(id, data) {
653
+ const response = await this.client.put(
654
+ `/api/v1/customers/${id}`,
655
+ data
656
+ );
657
+ return response.data;
658
+ }
659
+ /**
660
+ * Delete customer by ID
661
+ * DELETE /api/v1/customers/:id
662
+ *
663
+ * @param id - Customer ID
664
+ * @returns Empty response on success
665
+ */
666
+ async delete(id) {
667
+ const response = await this.client.delete(
668
+ `/api/v1/customers/${id}`
669
+ );
670
+ return response.data;
671
+ }
672
+ /**
673
+ * Lookup customer by userId or tenantId
674
+ * GET /api/v1/customers/lookup
675
+ *
676
+ * @param params - Query parameters (userId optional, tenantId required)
677
+ * @returns Customer or null
678
+ */
679
+ async lookup(params) {
680
+ const response = await this.client.get(
681
+ "/api/v1/customers/lookup",
682
+ { params: { userId: params.userId, tenantId: params.tenantId } }
683
+ );
684
+ return response.data;
685
+ }
686
+ /**
687
+ * Get autocomplete data from existing customers and bookings
688
+ * GET /api/v1/customers/autocomplete
689
+ *
690
+ * @param tenantId - Tenant ID (required)
691
+ * @returns Autocomplete data
692
+ */
693
+ async autocomplete(tenantId) {
694
+ const response = await this.client.get(
695
+ "/api/v1/customers/autocomplete",
696
+ { params: { tenantId } }
697
+ );
698
+ return response.data;
699
+ }
700
+ /**
701
+ * Get all bookings for a customer
702
+ * GET /api/v1/customers/bookings
703
+ *
704
+ * @param params - Query parameters (email optional, userId optional, tenantId required)
705
+ * @returns Customer bookings data
706
+ */
707
+ async getBookings(params) {
708
+ const response = await this.client.get(
709
+ "/api/v1/customers/bookings",
710
+ { params }
711
+ );
712
+ return response.data;
713
+ }
714
+ };
715
+
716
+ // src/clients/flow-client.ts
717
+ var FlowClient = class {
718
+ constructor(client) {
719
+ this.client = client;
720
+ }
721
+ /**
722
+ * Get flow by ID
723
+ * GET /api/v1/flows/:id
724
+ *
725
+ * @param id - Flow ID
726
+ * @returns Flow details
727
+ */
728
+ async getById(id) {
729
+ const response = await this.client.get(
730
+ `/api/v1/flows/${id}`
731
+ );
732
+ return response.data;
733
+ }
734
+ /**
735
+ * List flows with optional filters
736
+ * GET /api/v1/flows
737
+ *
738
+ * @param params - Query parameters (tenantId, page, limit)
739
+ * @returns Paginated list of flows
740
+ */
741
+ async list(params) {
742
+ const response = await this.client.get(
743
+ "/api/v1/flows",
744
+ { params }
745
+ );
746
+ return response.data;
747
+ }
748
+ /**
749
+ * Create a new flow
750
+ * POST /api/v1/flows
751
+ *
752
+ * @param data - Flow creation data
753
+ * @returns Created flow
754
+ */
755
+ async create(data) {
756
+ const response = await this.client.post(
757
+ "/api/v1/flows",
758
+ data
759
+ );
760
+ return response.data;
761
+ }
762
+ /**
763
+ * Update flow by ID
764
+ * PATCH /api/v1/flows/:id
765
+ *
766
+ * @param id - Flow ID
767
+ * @param data - Flow update data
768
+ * @returns Updated flow
769
+ */
770
+ async update(id, data) {
771
+ const response = await this.client.patch(
772
+ `/api/v1/flows/${id}`,
773
+ data
774
+ );
775
+ return response.data;
776
+ }
777
+ /**
778
+ * Delete flow by ID
779
+ * DELETE /api/v1/flows/:id
780
+ *
781
+ * @param id - Flow ID
782
+ * @returns Empty response on success
783
+ */
784
+ async delete(id) {
785
+ const response = await this.client.delete(
786
+ `/api/v1/flows/${id}`
787
+ );
788
+ return response.data;
789
+ }
790
+ /**
791
+ * Execute a flow
792
+ * POST /api/v1/flows/:id/execute
793
+ *
794
+ * @param id - Flow ID
795
+ * @param input - Optional input data for flow execution
796
+ * @returns Flow execution result
797
+ */
798
+ async execute(id, input) {
799
+ const response = await this.client.post(
800
+ `/api/v1/flows/${id}/execute`,
801
+ { input }
802
+ );
803
+ return response.data;
804
+ }
805
+ };
806
+
807
+ // src/clients/studio-client.ts
808
+ var StudioClient = class {
809
+ constructor(client) {
810
+ this.client = client;
811
+ }
812
+ /**
813
+ * Get studio by ID
814
+ * GET /api/v1/studios/:id
815
+ *
816
+ * @param id - Studio ID
817
+ * @returns Studio details
818
+ */
819
+ async getById(id) {
820
+ const response = await this.client.get(
821
+ `/api/v1/studios/${id}`
822
+ );
823
+ return response.data;
824
+ }
825
+ /**
826
+ * Get studio by slug
827
+ * GET /api/v1/studios/slug/:slug
828
+ *
829
+ * @param slug - Studio slug
830
+ * @returns Studio details
831
+ */
832
+ async getBySlug(slug) {
833
+ const response = await this.client.get(
834
+ `/api/v1/studios/slug/${slug}`
835
+ );
836
+ return response.data;
837
+ }
838
+ /**
839
+ * List studios with optional filters
840
+ * GET /api/v1/studios
841
+ *
842
+ * @param params - Query parameters (page, limit, search)
843
+ * @returns Paginated list of studios
844
+ */
845
+ async list(params) {
846
+ const response = await this.client.get(
847
+ "/api/v1/studios",
848
+ { params }
849
+ );
850
+ return response.data;
851
+ }
852
+ /**
853
+ * Create a new studio
854
+ * POST /api/v1/studios
855
+ *
856
+ * @param data - Studio creation data
857
+ * @returns Created studio
858
+ */
859
+ async create(data) {
860
+ const response = await this.client.post(
861
+ "/api/v1/studios",
862
+ data
863
+ );
864
+ return response.data;
865
+ }
866
+ /**
867
+ * Update studio by ID
868
+ * PATCH /api/v1/studios/:id
869
+ *
870
+ * @param id - Studio ID
871
+ * @param data - Studio update data
872
+ * @returns Updated studio
873
+ */
874
+ async update(id, data) {
875
+ const response = await this.client.patch(
876
+ `/api/v1/studios/${id}`,
877
+ data
878
+ );
879
+ return response.data;
880
+ }
881
+ /**
882
+ * Delete studio by ID
883
+ * DELETE /api/v1/studios/:id
884
+ *
885
+ * @param id - Studio ID
886
+ * @returns Empty response on success
887
+ */
888
+ async delete(id) {
889
+ const response = await this.client.delete(
890
+ `/api/v1/studios/${id}`
891
+ );
892
+ return response.data;
893
+ }
894
+ /**
895
+ * Get data quality issues for a studio
896
+ * GET /api/v1/studios/:id/data-quality
897
+ *
898
+ * @param id - Studio ID
899
+ * @param params - Query parameters (resolved, severity)
900
+ * @returns List of data quality issues
901
+ */
902
+ async getDataQualityIssues(id, params) {
903
+ const response = await this.client.get(
904
+ `/api/v1/studios/${id}/data-quality`,
905
+ { params }
906
+ );
907
+ return response.data;
908
+ }
909
+ };
910
+
911
+ // src/clients/notification-client.ts
912
+ var NotificationClient = class {
913
+ constructor(client) {
914
+ this.client = client;
915
+ }
916
+ /**
917
+ * Get notification by ID
918
+ * GET /api/v1/notifications/:id
919
+ *
920
+ * @param id - Notification ID
921
+ * @returns Notification details
922
+ */
923
+ async getById(id) {
924
+ const response = await this.client.get(
925
+ `/api/v1/notifications/${id}`
926
+ );
927
+ return response.data;
928
+ }
929
+ /**
930
+ * List notifications with optional filters
931
+ * GET /api/v1/notifications
932
+ *
933
+ * @param params - Query parameters (tenantId, type, recipient, page, limit)
934
+ * @returns Paginated list of notifications
935
+ */
936
+ async list(params) {
937
+ const response = await this.client.get(
938
+ "/api/v1/notifications",
939
+ { params }
940
+ );
941
+ return response.data;
942
+ }
943
+ /**
944
+ * Create a new notification
945
+ * POST /api/v1/notifications
946
+ *
947
+ * @param data - Notification creation data
948
+ * @returns Created notification
949
+ */
950
+ async create(data) {
951
+ const response = await this.client.post(
952
+ "/api/v1/notifications",
953
+ data
954
+ );
955
+ return response.data;
956
+ }
957
+ /**
958
+ * Send notification immediately
959
+ * POST /api/v1/notifications/send
960
+ *
961
+ * @param data - Notification data to send
962
+ * @returns Sent notification
963
+ */
964
+ async send(data) {
965
+ const response = await this.client.post(
966
+ "/api/v1/notifications/send",
967
+ data
968
+ );
969
+ return response.data;
970
+ }
971
+ /**
972
+ * Update notification by ID
973
+ * PATCH /api/v1/notifications/:id
974
+ *
975
+ * @param id - Notification ID
976
+ * @param data - Notification update data
977
+ * @returns Updated notification
978
+ */
979
+ async update(id, data) {
980
+ const response = await this.client.patch(
981
+ `/api/v1/notifications/${id}`,
982
+ data
983
+ );
984
+ return response.data;
985
+ }
986
+ /**
987
+ * Delete notification by ID
988
+ * DELETE /api/v1/notifications/:id
989
+ *
990
+ * @param id - Notification ID
991
+ * @returns Empty response on success
992
+ */
993
+ async delete(id) {
994
+ const response = await this.client.delete(
995
+ `/api/v1/notifications/${id}`
996
+ );
997
+ return response.data;
998
+ }
999
+ };
1000
+
1001
+ // src/clients/data-quality-client.ts
1002
+ var DataQualityClient = class {
1003
+ constructor(client) {
1004
+ this.client = client;
1005
+ }
1006
+ /**
1007
+ * Get data quality issue by ID
1008
+ * GET /api/v1/data-quality/:id
1009
+ *
1010
+ * @param id - Issue ID
1011
+ * @returns Data quality issue details
1012
+ */
1013
+ async getById(id) {
1014
+ const response = await this.client.get(
1015
+ `/api/v1/data-quality/${id}`
1016
+ );
1017
+ return response.data;
1018
+ }
1019
+ /**
1020
+ * List data quality issues with optional filters
1021
+ * GET /api/v1/data-quality
1022
+ *
1023
+ * @param params - Query parameters (tenantId, type, severity, resolved, page, limit)
1024
+ * @returns Paginated list of data quality issues
1025
+ */
1026
+ async list(params) {
1027
+ const response = await this.client.get(
1028
+ "/api/v1/data-quality",
1029
+ { params }
1030
+ );
1031
+ return response.data;
1032
+ }
1033
+ /**
1034
+ * Run data quality check for a tenant
1035
+ * POST /api/v1/data-quality/check
1036
+ *
1037
+ * @param tenantId - Tenant ID to check
1038
+ * @returns List of detected issues
1039
+ */
1040
+ async runCheck(tenantId) {
1041
+ const response = await this.client.post(
1042
+ "/api/v1/data-quality/check",
1043
+ { tenantId }
1044
+ );
1045
+ return response.data;
1046
+ }
1047
+ /**
1048
+ * Resolve a data quality issue
1049
+ * PATCH /api/v1/data-quality/:id/resolve
1050
+ *
1051
+ * @param id - Issue ID
1052
+ * @returns Resolved issue
1053
+ */
1054
+ async resolve(id) {
1055
+ const response = await this.client.patch(
1056
+ `/api/v1/data-quality/${id}/resolve`,
1057
+ { resolved: true }
1058
+ );
1059
+ return response.data;
1060
+ }
1061
+ /**
1062
+ * Get data quality summary for a tenant
1063
+ * GET /api/v1/data-quality/summary?tenantId=:id
1064
+ *
1065
+ * @param tenantId - Tenant ID
1066
+ * @returns Summary of data quality issues
1067
+ */
1068
+ async getSummary(tenantId) {
1069
+ const response = await this.client.get(
1070
+ "/api/v1/data-quality/summary",
1071
+ { params: { tenantId } }
1072
+ );
1073
+ return response.data;
1074
+ }
1075
+ };
1076
+
1077
+ // src/errors.ts
1078
+ var SlotlyApiError = class _SlotlyApiError extends Error {
1079
+ constructor(code, message, statusCode, details) {
1080
+ super(message);
1081
+ this.code = code;
1082
+ this.statusCode = statusCode;
1083
+ this.details = details;
1084
+ this.name = "SlotlyApiError";
1085
+ Object.setPrototypeOf(this, _SlotlyApiError.prototype);
1086
+ }
1087
+ };
1088
+ var SlotlyAuthError = class _SlotlyAuthError extends SlotlyApiError {
1089
+ constructor(message, details) {
1090
+ super("AUTH_ERROR", message, 401, details);
1091
+ this.name = "SlotlyAuthError";
1092
+ Object.setPrototypeOf(this, _SlotlyAuthError.prototype);
1093
+ }
1094
+ };
1095
+ var SlotlyNetworkError = class _SlotlyNetworkError extends Error {
1096
+ constructor(message, originalError) {
1097
+ super(message);
1098
+ this.originalError = originalError;
1099
+ this.name = "SlotlyNetworkError";
1100
+ Object.setPrototypeOf(this, _SlotlyNetworkError.prototype);
1101
+ }
1102
+ };
1103
+ var SlotlyConfigurationError = class _SlotlyConfigurationError extends Error {
1104
+ constructor(message, details) {
1105
+ super(message);
1106
+ this.details = details;
1107
+ this.name = "SlotlyConfigurationError";
1108
+ Object.setPrototypeOf(this, _SlotlyConfigurationError.prototype);
1109
+ }
1110
+ };
1111
+
1112
+ // src/interceptors.ts
1113
+ function setupRequestInterceptor() {
1114
+ return (config) => {
1115
+ return config;
1116
+ };
1117
+ }
1118
+ function setupResponseInterceptor() {
1119
+ return (response) => {
1120
+ return response;
1121
+ };
1122
+ }
1123
+ function setupErrorInterceptor() {
1124
+ return (error) => {
1125
+ if (!error.response) {
1126
+ const networkError = new SlotlyNetworkError(
1127
+ error.message || "Network error occurred",
1128
+ error
1129
+ );
1130
+ return Promise.reject(networkError);
1131
+ }
1132
+ const { status, data } = error.response;
1133
+ if (status === 401 || status === 403) {
1134
+ const authError = new SlotlyAuthError(
1135
+ data?.error?.message || "Authentication failed",
1136
+ data?.error?.details
1137
+ );
1138
+ return Promise.reject(authError);
1139
+ }
1140
+ if (data?.error) {
1141
+ const apiError2 = new SlotlyApiError(
1142
+ data.error.code || "API_ERROR",
1143
+ data.error.message || "An error occurred",
1144
+ status,
1145
+ data.error.details
1146
+ );
1147
+ return Promise.reject(apiError2);
1148
+ }
1149
+ const apiError = new SlotlyApiError(
1150
+ "HTTP_ERROR",
1151
+ error.message || `Request failed with status ${status}`,
1152
+ status,
1153
+ data
1154
+ );
1155
+ return Promise.reject(apiError);
1156
+ };
1157
+ }
1158
+
1159
+ // src/retry.ts
1160
+ import axios from "axios";
1161
+ var defaultRetryConfig = {
1162
+ maxRetries: 3,
1163
+ retryDelay: 1e3,
1164
+ // 1 second
1165
+ retryCondition: (error) => {
1166
+ if (!error.response) {
1167
+ return true;
1168
+ }
1169
+ const status = error.response.status;
1170
+ return status >= 500 && status < 600;
1171
+ }
1172
+ };
1173
+ function calculateRetryDelay(attempt, baseDelay) {
1174
+ const exponentialDelay = baseDelay * Math.pow(2, attempt);
1175
+ const jitter = Math.random() * 0.3 * exponentialDelay;
1176
+ return exponentialDelay + jitter;
1177
+ }
1178
+ function setupRetryInterceptor(config = defaultRetryConfig) {
1179
+ return async (error) => {
1180
+ const requestConfig = error.config;
1181
+ if (!requestConfig) {
1182
+ return Promise.reject(error);
1183
+ }
1184
+ if (requestConfig._retryCount === void 0) {
1185
+ requestConfig._retryCount = 0;
1186
+ }
1187
+ const shouldRetry = requestConfig._retryCount < config.maxRetries && (!config.retryCondition || config.retryCondition(error));
1188
+ if (!shouldRetry) {
1189
+ return Promise.reject(error);
1190
+ }
1191
+ requestConfig._retry = true;
1192
+ requestConfig._retryCount += 1;
1193
+ const delay = calculateRetryDelay(
1194
+ requestConfig._retryCount - 1,
1195
+ config.retryDelay
1196
+ );
1197
+ await new Promise((resolve) => setTimeout(resolve, delay));
1198
+ return axios(requestConfig);
1199
+ };
1200
+ }
1201
+
1202
+ // src/middleware/validateSlotlyRequest.ts
1203
+ import { jwtDecode } from "jwt-decode";
1204
+ var allowedKeys = {
1205
+ "test-key-123": {
1206
+ tenantId: "bright-accountants",
1207
+ permissions: ["booking:create"]
1208
+ },
1209
+ "admin-ui": {
1210
+ tenantId: "slotly-core",
1211
+ permissions: ["*"]
1212
+ }
1213
+ };
1214
+ function validateApiKey(clientKey) {
1215
+ if (!clientKey || typeof clientKey !== "string") {
1216
+ return null;
1217
+ }
1218
+ const keyInfo = allowedKeys[clientKey];
1219
+ if (!keyInfo) {
1220
+ return null;
1221
+ }
1222
+ return {
1223
+ tenantId: keyInfo.tenantId,
1224
+ permissions: keyInfo.permissions
1225
+ };
1226
+ }
1227
+ function extractUserToken(authHeader) {
1228
+ if (!authHeader || typeof authHeader !== "string") {
1229
+ return null;
1230
+ }
1231
+ const bearerMatch = authHeader.match(/^Bearer\s+(.+)$/i);
1232
+ if (!bearerMatch || !bearerMatch[1]) {
1233
+ return null;
1234
+ }
1235
+ const token = bearerMatch[1];
1236
+ try {
1237
+ const claims = jwtDecode(token);
1238
+ if (!claims || typeof claims !== "object") {
1239
+ console.error("[Slotly SDK] Invalid JWT token: claims is not an object");
1240
+ return null;
1241
+ }
1242
+ const userId = claims.sub || claims.user_id || claims.userId;
1243
+ return {
1244
+ userId: typeof userId === "string" ? userId : void 0,
1245
+ tokenClaims: claims
1246
+ };
1247
+ } catch (error) {
1248
+ console.error("[Slotly SDK] Failed to decode JWT token:", error);
1249
+ return null;
1250
+ }
1251
+ }
1252
+ function validateAndExtractContext(req) {
1253
+ const clientKey = req.headers["x-slotly-api-key"] || req.headers["X-Slotly-Api-Key"] || req.headers["x-slotly-api-key"];
1254
+ if (!clientKey || typeof clientKey !== "string" || clientKey.trim().length === 0) {
1255
+ const error = new SlotlyAuthError(
1256
+ "Missing or invalid x-slotly-api-key header. API key is required.",
1257
+ { header: "x-slotly-api-key" }
1258
+ );
1259
+ console.error("[Slotly SDK] Validation failed:", error.message);
1260
+ throw error;
1261
+ }
1262
+ const keyInfo = validateApiKey(clientKey);
1263
+ if (!keyInfo) {
1264
+ const error = new SlotlyAuthError(
1265
+ "Invalid API key. Client key not found in allowlist.",
1266
+ { clientKey: clientKey.substring(0, 8) + "..." }
1267
+ );
1268
+ console.error("[Slotly SDK] Validation failed:", error.message);
1269
+ throw error;
1270
+ }
1271
+ const authHeader = req.headers.authorization || req.headers.Authorization;
1272
+ const tokenInfo = authHeader ? extractUserToken(authHeader) : null;
1273
+ const context = {
1274
+ clientKey,
1275
+ tenantId: keyInfo.tenantId,
1276
+ permissions: keyInfo.permissions,
1277
+ ...tokenInfo?.userId && { userId: tokenInfo.userId },
1278
+ ...tokenInfo?.tokenClaims && { tokenClaims: tokenInfo.tokenClaims }
1279
+ };
1280
+ return context;
1281
+ }
1282
+ function getSlotlyContext(req) {
1283
+ const slotlyReq = req;
1284
+ return slotlyReq.slotlyContext || null;
1285
+ }
1286
+ function validateSlotlyRequest(handler) {
1287
+ return async (req, res) => {
1288
+ try {
1289
+ const context = validateAndExtractContext(req);
1290
+ const slotlyReq = req;
1291
+ slotlyReq.slotlyContext = context;
1292
+ await handler(slotlyReq, res);
1293
+ } catch (error) {
1294
+ if (error instanceof SlotlyAuthError) {
1295
+ console.error(
1296
+ "[Slotly SDK] Authentication failed:",
1297
+ error.message,
1298
+ error.details
1299
+ );
1300
+ res.status(error.statusCode || 401).json({
1301
+ success: false,
1302
+ error: {
1303
+ code: error.code,
1304
+ message: error.message,
1305
+ details: error.details
1306
+ }
1307
+ });
1308
+ return;
1309
+ }
1310
+ console.error("[Slotly SDK] Unexpected error:", error);
1311
+ res.status(500).json({
1312
+ success: false,
1313
+ error: {
1314
+ code: "INTERNAL_ERROR",
1315
+ message: "An unexpected error occurred"
1316
+ }
1317
+ });
1318
+ }
1319
+ };
1320
+ }
1321
+
1322
+ // src/index.ts
1323
+ var defaultBaseURL = typeof process !== "undefined" && process.env?.SLOTLY_API_URL ? process.env.SLOTLY_API_URL : "https://api.slotly.dev";
1324
+ function createAuthenticatedClient(options) {
1325
+ const client = axios2.create({
1326
+ baseURL: options.baseURL || defaultBaseURL,
1327
+ headers: {
1328
+ "Content-Type": "application/json",
1329
+ ...options.headers
1330
+ },
1331
+ timeout: 3e4
1332
+ // 30 seconds
1333
+ });
1334
+ client.interceptors.request.use(
1335
+ async (config) => {
1336
+ try {
1337
+ const [clientKeyPromise, userTokenPromise] = [
1338
+ options.getClientKey(),
1339
+ options.getUserToken?.() ?? Promise.resolve(null)
1340
+ ];
1341
+ const [clientKey, userToken] = await Promise.all([
1342
+ clientKeyPromise,
1343
+ userTokenPromise
1344
+ ]);
1345
+ if (!clientKey || typeof clientKey !== "string" || clientKey.trim().length === 0) {
1346
+ throw new SlotlyConfigurationError(
1347
+ "getClientKey() must return a non-empty string. API key is required for all requests.",
1348
+ { received: clientKey }
1349
+ );
1350
+ }
1351
+ config.headers["x-slotly-api-key"] = clientKey;
1352
+ if (userToken && typeof userToken === "string" && userToken.trim().length > 0) {
1353
+ config.headers["Authorization"] = `Bearer ${userToken}`;
1354
+ }
1355
+ return config;
1356
+ } catch (error) {
1357
+ if (error instanceof SlotlyConfigurationError) {
1358
+ throw error;
1359
+ }
1360
+ if (error && typeof error === "object" && "message" in error) {
1361
+ console.error("[Slotly SDK] Failed to retrieve authentication credentials:", error);
1362
+ throw new SlotlyConfigurationError(
1363
+ `Failed to retrieve authentication credentials: ${error.message}`,
1364
+ { originalError: error }
1365
+ );
1366
+ }
1367
+ throw error;
1368
+ }
1369
+ },
1370
+ (error) => Promise.reject(error)
1371
+ );
1372
+ client.interceptors.request.use(setupRequestInterceptor());
1373
+ client.interceptors.response.use(
1374
+ (response) => setupResponseInterceptor()(response),
1375
+ (error) => setupErrorInterceptor()(error)
1376
+ );
1377
+ client.interceptors.response.use(
1378
+ (response) => response,
1379
+ setupRetryInterceptor({
1380
+ maxRetries: options.retry?.maxRetries ?? 3,
1381
+ retryDelay: options.retry?.retryDelay ?? 1e3
1382
+ })
1383
+ );
1384
+ return client;
1385
+ }
1386
+ var useSlotly = (options) => {
1387
+ if (!options || typeof options !== "object") {
1388
+ throw new SlotlyConfigurationError(
1389
+ "useSlotly() requires an options object with getClientKey function"
1390
+ );
1391
+ }
1392
+ if (!options.getClientKey || typeof options.getClientKey !== "function") {
1393
+ throw new SlotlyConfigurationError(
1394
+ "getClientKey must be a function that returns Promise<string>"
1395
+ );
1396
+ }
1397
+ const client = createAuthenticatedClient(options);
1398
+ return {
1399
+ tenant: new TenantClient(client),
1400
+ booking: new BookingClient(client),
1401
+ service: new ServiceClient(client),
1402
+ serviceItem: new ServiceItemClient(client),
1403
+ slot: new SlotClient(client),
1404
+ customer: new CustomerClient(client),
1405
+ flow: new FlowClient(client),
1406
+ studio: new StudioClient(client),
1407
+ notification: new NotificationClient(client),
1408
+ dataQuality: new DataQualityClient(client)
1409
+ };
1410
+ };
1411
+ var index_default = useSlotly;
1412
+ export {
1413
+ SlotlyApiError,
1414
+ SlotlyAuthError,
1415
+ SlotlyConfigurationError,
1416
+ SlotlyNetworkError,
1417
+ index_default as default,
1418
+ getSlotlyContext,
1419
+ useSlotly,
1420
+ validateSlotlyRequest
1421
+ };
1422
+ //# sourceMappingURL=index.esm.js.map