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