@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.
- package/README.md +167 -0
- package/dist/index.cjs.js +1463 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.mts +1149 -0
- package/dist/index.d.ts +1149 -0
- package/dist/index.esm.js +1422 -0
- package/dist/index.esm.js.map +1 -0
- package/package.json +52 -0
- package/src/client.ts +161 -0
- package/src/clients/__tests__/tenant-client.spec.ts +236 -0
- package/src/clients/booking-client.ts +98 -0
- package/src/clients/customer-client.ts +165 -0
- package/src/clients/data-quality-client.ts +93 -0
- package/src/clients/flow-client.ts +107 -0
- package/src/clients/notification-client.ts +108 -0
- package/src/clients/register-client.ts +19 -0
- package/src/clients/service-client.ts +215 -0
- package/src/clients/service-item-client.ts +103 -0
- package/src/clients/slot-client.ts +140 -0
- package/src/clients/studio-client.ts +128 -0
- package/src/clients/tenant-client.ts +148 -0
- package/src/errors.ts +41 -0
- package/src/index.ts +296 -0
- package/src/interceptors.ts +81 -0
- package/src/middleware/validateSlotlyRequest.ts +337 -0
- package/src/mocks/mockAdapter.ts +32 -0
- package/src/retry.ts +82 -0
- package/src/types/api.ts +192 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1149 @@
|
|
|
1
|
+
import { AxiosInstance } from 'axios';
|
|
2
|
+
import { NextApiRequest, NextApiResponse } from 'next';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Standard API response format for all Slotly API endpoints
|
|
6
|
+
*/
|
|
7
|
+
interface ApiResponse<T> {
|
|
8
|
+
success: boolean;
|
|
9
|
+
data?: T;
|
|
10
|
+
error?: {
|
|
11
|
+
code: string;
|
|
12
|
+
message: string;
|
|
13
|
+
details?: any;
|
|
14
|
+
};
|
|
15
|
+
pagination?: {
|
|
16
|
+
page: number;
|
|
17
|
+
limit: number;
|
|
18
|
+
total: number;
|
|
19
|
+
totalPages: number;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Placeholder types for domain models
|
|
24
|
+
* TODO: Replace with actual types from the Slotly API
|
|
25
|
+
*/
|
|
26
|
+
interface Tenant {
|
|
27
|
+
id: string;
|
|
28
|
+
slug: string;
|
|
29
|
+
name: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Tenant branding configuration
|
|
33
|
+
*/
|
|
34
|
+
interface TenantBranding {
|
|
35
|
+
logoUrl?: string;
|
|
36
|
+
primaryColor?: string;
|
|
37
|
+
secondaryColor?: string;
|
|
38
|
+
faviconUrl?: string;
|
|
39
|
+
customCss?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Service item within a service
|
|
43
|
+
*/
|
|
44
|
+
interface ServiceItem {
|
|
45
|
+
id: string;
|
|
46
|
+
serviceId: string;
|
|
47
|
+
name: string;
|
|
48
|
+
description?: string;
|
|
49
|
+
price?: number;
|
|
50
|
+
duration?: number;
|
|
51
|
+
available?: boolean;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Enhanced Service type with nested service items
|
|
55
|
+
*/
|
|
56
|
+
interface ServiceWithItems extends Service {
|
|
57
|
+
/** Array of service items nested within this service (optional, may be empty array) */
|
|
58
|
+
serviceItems?: ServiceItem[];
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Service item with parent service reference
|
|
62
|
+
*/
|
|
63
|
+
interface ServiceItemWithService extends ServiceItem {
|
|
64
|
+
/** Parent service reference (optional, for convenience when fetching items separately) */
|
|
65
|
+
parentService?: Service;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Category extracted from services
|
|
69
|
+
*/
|
|
70
|
+
interface Category {
|
|
71
|
+
id: string;
|
|
72
|
+
name: string;
|
|
73
|
+
slug?: string;
|
|
74
|
+
description?: string;
|
|
75
|
+
/** Array of service IDs in this category */
|
|
76
|
+
serviceIds?: string[];
|
|
77
|
+
/** Optional: services in this category (if expanded) */
|
|
78
|
+
services?: Service[];
|
|
79
|
+
}
|
|
80
|
+
interface Booking {
|
|
81
|
+
id: string;
|
|
82
|
+
tenantId: string;
|
|
83
|
+
slotId?: string;
|
|
84
|
+
customerId?: string;
|
|
85
|
+
status?: string;
|
|
86
|
+
}
|
|
87
|
+
interface Service {
|
|
88
|
+
id: string;
|
|
89
|
+
tenantId: string;
|
|
90
|
+
name: string;
|
|
91
|
+
description?: string;
|
|
92
|
+
duration?: number;
|
|
93
|
+
price?: number;
|
|
94
|
+
}
|
|
95
|
+
interface Slot {
|
|
96
|
+
id: string;
|
|
97
|
+
tenantId: string;
|
|
98
|
+
serviceId?: string;
|
|
99
|
+
startTime: string;
|
|
100
|
+
endTime: string;
|
|
101
|
+
available: boolean;
|
|
102
|
+
}
|
|
103
|
+
interface Customer {
|
|
104
|
+
id: string;
|
|
105
|
+
tenantId: string;
|
|
106
|
+
email?: string;
|
|
107
|
+
name?: string;
|
|
108
|
+
phone?: string;
|
|
109
|
+
}
|
|
110
|
+
interface Flow {
|
|
111
|
+
id: string;
|
|
112
|
+
tenantId: string;
|
|
113
|
+
name: string;
|
|
114
|
+
steps?: FlowStep[];
|
|
115
|
+
}
|
|
116
|
+
interface FlowStep {
|
|
117
|
+
id: string;
|
|
118
|
+
type: string;
|
|
119
|
+
config?: Record<string, any>;
|
|
120
|
+
}
|
|
121
|
+
interface Studio {
|
|
122
|
+
id: string;
|
|
123
|
+
name: string;
|
|
124
|
+
slug?: string;
|
|
125
|
+
}
|
|
126
|
+
interface Notification {
|
|
127
|
+
id: string;
|
|
128
|
+
tenantId?: string;
|
|
129
|
+
type: string;
|
|
130
|
+
recipient: string;
|
|
131
|
+
content: string;
|
|
132
|
+
sentAt?: string;
|
|
133
|
+
}
|
|
134
|
+
interface DataQualityIssue {
|
|
135
|
+
id: string;
|
|
136
|
+
tenantId?: string;
|
|
137
|
+
type: string;
|
|
138
|
+
severity: 'low' | 'medium' | 'high';
|
|
139
|
+
message: string;
|
|
140
|
+
resolved: boolean;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Full tenant configuration including branding, services, and service items
|
|
144
|
+
* Service items are nested within each service object (not as a separate top-level array)
|
|
145
|
+
*/
|
|
146
|
+
interface TenantFullConfig {
|
|
147
|
+
/** Tenant ID (required, non-nullable) */
|
|
148
|
+
id: string;
|
|
149
|
+
/** Tenant slug (required, non-nullable) */
|
|
150
|
+
slug: string;
|
|
151
|
+
/** Tenant name (required, non-nullable) */
|
|
152
|
+
name: string;
|
|
153
|
+
/** Branding configuration (optional) */
|
|
154
|
+
branding?: TenantBranding;
|
|
155
|
+
/** Array of services with nested service items (required, non-nullable, may be empty array) */
|
|
156
|
+
services: ServiceWithItems[];
|
|
157
|
+
/** Enabled features (optional) */
|
|
158
|
+
featuresEnabled?: string[];
|
|
159
|
+
/** Subscription level (optional) */
|
|
160
|
+
subscriptionLevel?: string;
|
|
161
|
+
/** Additional configuration (optional) */
|
|
162
|
+
config?: Record<string, any>;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
declare class TenantClient {
|
|
166
|
+
private client;
|
|
167
|
+
constructor(client: AxiosInstance);
|
|
168
|
+
/**
|
|
169
|
+
* Get tenant configuration by slug
|
|
170
|
+
* GET /api/v1/tenant-config/:slug
|
|
171
|
+
*/
|
|
172
|
+
getBySlug(slug: string): Promise<ApiResponse<Tenant>>;
|
|
173
|
+
/**
|
|
174
|
+
* Get tenant by ID
|
|
175
|
+
* GET /api/v1/tenants/:id
|
|
176
|
+
*/
|
|
177
|
+
getById(id: string): Promise<ApiResponse<Tenant>>;
|
|
178
|
+
/**
|
|
179
|
+
* List all tenants
|
|
180
|
+
* GET /api/v1/tenants
|
|
181
|
+
*/
|
|
182
|
+
list(params?: {
|
|
183
|
+
page?: number;
|
|
184
|
+
limit?: number;
|
|
185
|
+
}): Promise<ApiResponse<Tenant[]>>;
|
|
186
|
+
/**
|
|
187
|
+
* Create a new tenant
|
|
188
|
+
* POST /api/v1/tenants
|
|
189
|
+
*/
|
|
190
|
+
create(data: Partial<Tenant>): Promise<ApiResponse<Tenant>>;
|
|
191
|
+
/**
|
|
192
|
+
* Update tenant by ID
|
|
193
|
+
* PATCH /api/v1/tenants/:id
|
|
194
|
+
*/
|
|
195
|
+
update(id: string, data: Partial<Tenant>): Promise<ApiResponse<Tenant>>;
|
|
196
|
+
/**
|
|
197
|
+
* Delete tenant by ID
|
|
198
|
+
* DELETE /api/v1/tenants/:id
|
|
199
|
+
*/
|
|
200
|
+
delete(id: string): Promise<ApiResponse<void>>;
|
|
201
|
+
/**
|
|
202
|
+
* Get full tenant configuration including branding, services, and service items
|
|
203
|
+
* GET /api/v1/tenants/:slug
|
|
204
|
+
*
|
|
205
|
+
* This method returns the complete tenant configuration including:
|
|
206
|
+
* - Tenant basic info (id, slug, name)
|
|
207
|
+
* - Branding configuration (logo, colors, etc.)
|
|
208
|
+
* - All services associated with the tenant (with nested service items)
|
|
209
|
+
* - Feature flags and subscription level
|
|
210
|
+
*
|
|
211
|
+
* Note: Service items are nested within each service object (not as a separate top-level array).
|
|
212
|
+
* The endpoint returns full config by default (includes services with nested items).
|
|
213
|
+
*
|
|
214
|
+
* @param slug - Tenant slug identifier (required, non-nullable)
|
|
215
|
+
* @param includeServices - Include services with nested items (default: true)
|
|
216
|
+
* @returns Promise resolving to ApiResponse<TenantFullConfig>
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* ```typescript
|
|
220
|
+
* const result = await slotly.tenant.getFullConfig('bright-accountants');
|
|
221
|
+
* if (result.success && result.data) {
|
|
222
|
+
* const config = result.data;
|
|
223
|
+
* console.log('Tenant:', config.name);
|
|
224
|
+
* console.log('Logo:', config.branding?.logoUrl);
|
|
225
|
+
* console.log('Services:', config.services.length);
|
|
226
|
+
*
|
|
227
|
+
* // Service items are nested within each service
|
|
228
|
+
* config.services.forEach(service => {
|
|
229
|
+
* console.log(`Service: ${service.name}`);
|
|
230
|
+
* if (service.serviceItems) {
|
|
231
|
+
* console.log(` Items: ${service.serviceItems.length}`);
|
|
232
|
+
* }
|
|
233
|
+
* });
|
|
234
|
+
* }
|
|
235
|
+
* ```
|
|
236
|
+
*
|
|
237
|
+
* **Guaranteed fields (non-nullable):**
|
|
238
|
+
* - `id`: string
|
|
239
|
+
* - `slug`: string
|
|
240
|
+
* - `name`: string
|
|
241
|
+
* - `services`: ServiceWithItems[] (may be empty array, each service may have nested `serviceItems`)
|
|
242
|
+
*
|
|
243
|
+
* **Optional fields:**
|
|
244
|
+
* - `branding`: TenantBranding | undefined
|
|
245
|
+
* - `services[].serviceItems`: ServiceItem[] | undefined (nested within each service)
|
|
246
|
+
* - `featuresEnabled`: string[] | undefined
|
|
247
|
+
* - `subscriptionLevel`: string | undefined
|
|
248
|
+
* - `config`: Record<string, any> | undefined
|
|
249
|
+
*
|
|
250
|
+
* **Error Codes:**
|
|
251
|
+
* - `TENANT_NOT_FOUND` - Tenant with slug not found
|
|
252
|
+
* - `AUTH_ERROR` - Authentication failed
|
|
253
|
+
* - `PERMISSION_DENIED` - Insufficient permissions
|
|
254
|
+
* - `NETWORK_ERROR` - Network request failed
|
|
255
|
+
*/
|
|
256
|
+
getFullConfig(slug: string, includeServices?: boolean): Promise<ApiResponse<TenantFullConfig>>;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
declare class BookingClient {
|
|
260
|
+
private client;
|
|
261
|
+
constructor(client: AxiosInstance);
|
|
262
|
+
/**
|
|
263
|
+
* Get booking by ID
|
|
264
|
+
* GET /api/v1/bookings/:id
|
|
265
|
+
*/
|
|
266
|
+
getById(id: string): Promise<ApiResponse<Booking>>;
|
|
267
|
+
/**
|
|
268
|
+
* List bookings
|
|
269
|
+
* GET /api/v1/bookings
|
|
270
|
+
*/
|
|
271
|
+
list(params?: {
|
|
272
|
+
slot_id?: string;
|
|
273
|
+
customer_id?: string;
|
|
274
|
+
status?: string;
|
|
275
|
+
page?: number;
|
|
276
|
+
limit?: number;
|
|
277
|
+
}): Promise<ApiResponse<Booking[]>>;
|
|
278
|
+
/**
|
|
279
|
+
* Create a new booking (publishes booking.created event)
|
|
280
|
+
* POST /api/v1/bookings
|
|
281
|
+
*/
|
|
282
|
+
create(data: {
|
|
283
|
+
slot_id: string;
|
|
284
|
+
customer_info: {
|
|
285
|
+
name: string;
|
|
286
|
+
email: string;
|
|
287
|
+
phone?: string;
|
|
288
|
+
address?: string;
|
|
289
|
+
city?: string;
|
|
290
|
+
state?: string;
|
|
291
|
+
zipCode?: string;
|
|
292
|
+
isGuestCheckout?: boolean;
|
|
293
|
+
};
|
|
294
|
+
booking_data?: Record<string, unknown>;
|
|
295
|
+
}): Promise<ApiResponse<Booking>>;
|
|
296
|
+
/**
|
|
297
|
+
* Update booking by ID
|
|
298
|
+
* PUT /api/v1/bookings/:id
|
|
299
|
+
*/
|
|
300
|
+
update(id: string, data: {
|
|
301
|
+
status?: string;
|
|
302
|
+
customer_info?: Record<string, unknown>;
|
|
303
|
+
booking_data?: Record<string, unknown>;
|
|
304
|
+
}): Promise<ApiResponse<Booking>>;
|
|
305
|
+
/**
|
|
306
|
+
* Cancel booking by ID
|
|
307
|
+
* Note: Cancel is typically done via update with status change
|
|
308
|
+
* If dedicated cancel endpoint exists, update this method
|
|
309
|
+
*/
|
|
310
|
+
cancel(id: string): Promise<ApiResponse<Booking>>;
|
|
311
|
+
/**
|
|
312
|
+
* Delete booking by ID (soft delete)
|
|
313
|
+
* DELETE /api/v1/bookings/:id
|
|
314
|
+
*/
|
|
315
|
+
delete(id: string): Promise<ApiResponse<void>>;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
declare class ServiceClient {
|
|
319
|
+
private client;
|
|
320
|
+
constructor(client: AxiosInstance);
|
|
321
|
+
/**
|
|
322
|
+
* Get service by ID
|
|
323
|
+
* GET /api/v1/services/:id
|
|
324
|
+
*
|
|
325
|
+
* @param id - Service ID
|
|
326
|
+
* @param includeItems - Include nested service items (default: false)
|
|
327
|
+
* @returns Service details (with items if includeItems=true)
|
|
328
|
+
*/
|
|
329
|
+
getById(id: string, includeItems?: boolean): Promise<ApiResponse<Service | ServiceWithItems>>;
|
|
330
|
+
/**
|
|
331
|
+
* List services by tenant slug
|
|
332
|
+
* GET /api/v1/services?tenantSlug=:slug
|
|
333
|
+
*
|
|
334
|
+
* @param slug - Tenant slug (or use tenant_id in list() method)
|
|
335
|
+
* @param params - Optional parameters (includeItems, page, limit, is_active)
|
|
336
|
+
* @returns List of services for the tenant
|
|
337
|
+
*/
|
|
338
|
+
listServices(slug: string, params?: {
|
|
339
|
+
includeItems?: boolean;
|
|
340
|
+
page?: number;
|
|
341
|
+
limit?: number;
|
|
342
|
+
is_active?: boolean;
|
|
343
|
+
}): Promise<ApiResponse<Service[] | ServiceWithItems[]>>;
|
|
344
|
+
/**
|
|
345
|
+
* List services with optional filters
|
|
346
|
+
* GET /api/v1/services
|
|
347
|
+
*
|
|
348
|
+
* @param params - Query parameters (tenant_id, tenantSlug, slot_id, page, limit, is_active, includeItems)
|
|
349
|
+
* @returns Paginated list of services
|
|
350
|
+
*/
|
|
351
|
+
list(params?: {
|
|
352
|
+
tenant_id?: string;
|
|
353
|
+
tenantSlug?: string;
|
|
354
|
+
slot_id?: string;
|
|
355
|
+
page?: number;
|
|
356
|
+
limit?: number;
|
|
357
|
+
is_active?: boolean;
|
|
358
|
+
includeItems?: boolean;
|
|
359
|
+
}): Promise<ApiResponse<Service[] | ServiceWithItems[]>>;
|
|
360
|
+
/**
|
|
361
|
+
* Create a new service
|
|
362
|
+
* POST /api/v1/services
|
|
363
|
+
*
|
|
364
|
+
* @param data - Service creation data (tenant_id, name required)
|
|
365
|
+
* @returns Created service
|
|
366
|
+
*/
|
|
367
|
+
create(data: Partial<Service>): Promise<ApiResponse<Service>>;
|
|
368
|
+
/**
|
|
369
|
+
* Update service by ID
|
|
370
|
+
* PUT /api/v1/services/:id
|
|
371
|
+
*
|
|
372
|
+
* @param id - Service ID
|
|
373
|
+
* @param data - Service update data
|
|
374
|
+
* @returns Updated service
|
|
375
|
+
*/
|
|
376
|
+
update(id: string, data: Partial<Service>): Promise<ApiResponse<Service>>;
|
|
377
|
+
/**
|
|
378
|
+
* Delete service by ID
|
|
379
|
+
* DELETE /api/v1/services/:id
|
|
380
|
+
*
|
|
381
|
+
* @param id - Service ID
|
|
382
|
+
* @returns Empty response on success
|
|
383
|
+
*/
|
|
384
|
+
delete(id: string): Promise<ApiResponse<void>>;
|
|
385
|
+
/**
|
|
386
|
+
* Bulk create services
|
|
387
|
+
* POST /api/v1/services/bulk (if available)
|
|
388
|
+
*
|
|
389
|
+
* @param services - Array of services to create
|
|
390
|
+
* @returns Created services
|
|
391
|
+
*/
|
|
392
|
+
bulkCreate(services: Partial<Service>[]): Promise<ApiResponse<Service[]>>;
|
|
393
|
+
/**
|
|
394
|
+
* Get service with its service items nested
|
|
395
|
+
* GET /api/v1/services/:id?includeItems=true
|
|
396
|
+
*
|
|
397
|
+
* @param id - Service ID
|
|
398
|
+
* @returns Service with nested service items
|
|
399
|
+
*/
|
|
400
|
+
getWithItems(id: string): Promise<ApiResponse<ServiceWithItems>>;
|
|
401
|
+
/**
|
|
402
|
+
* List services with their service items nested
|
|
403
|
+
* GET /api/v1/services?includeItems=true
|
|
404
|
+
*
|
|
405
|
+
* @param params - Query parameters (tenant_id, tenantSlug, slot_id, page, limit, is_active)
|
|
406
|
+
* @returns Paginated list of services with nested service items
|
|
407
|
+
*/
|
|
408
|
+
listWithItems(params?: {
|
|
409
|
+
tenant_id?: string;
|
|
410
|
+
tenantSlug?: string;
|
|
411
|
+
slot_id?: string;
|
|
412
|
+
page?: number;
|
|
413
|
+
limit?: number;
|
|
414
|
+
is_active?: boolean;
|
|
415
|
+
}): Promise<ApiResponse<ServiceWithItems[]>>;
|
|
416
|
+
/**
|
|
417
|
+
* List all categories for a tenant (extracted from services)
|
|
418
|
+
* GET /api/v1/categories?tenantSlug=:slug
|
|
419
|
+
*
|
|
420
|
+
* @param tenantSlug - Tenant slug (or use tenant_id in params)
|
|
421
|
+
* @param params - Optional query parameters (tenant_id, includeServices)
|
|
422
|
+
* @returns List of categories for the tenant
|
|
423
|
+
*/
|
|
424
|
+
listCategories(tenantSlug?: string, params?: {
|
|
425
|
+
tenant_id?: string;
|
|
426
|
+
includeServices?: boolean;
|
|
427
|
+
}): Promise<ApiResponse<Category[]>>;
|
|
428
|
+
/**
|
|
429
|
+
* Get category by ID (category name, lowercase)
|
|
430
|
+
* GET /api/v1/categories/:id?tenantSlug=:slug
|
|
431
|
+
*
|
|
432
|
+
* @param categoryId - Category ID (lowercase category name, e.g., "consulting")
|
|
433
|
+
* @param tenantSlug - Tenant slug (or use tenant_id in params)
|
|
434
|
+
* @param params - Optional query parameters (tenant_id, includeServices, includeItems)
|
|
435
|
+
* @returns Category details
|
|
436
|
+
*/
|
|
437
|
+
getCategory(categoryId: string, tenantSlug?: string, params?: {
|
|
438
|
+
tenant_id?: string;
|
|
439
|
+
includeServices?: boolean;
|
|
440
|
+
includeItems?: boolean;
|
|
441
|
+
}): Promise<ApiResponse<Category>>;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* ServiceItemClient provides methods for managing service items independently
|
|
446
|
+
* or in relation to their parent services.
|
|
447
|
+
*/
|
|
448
|
+
declare class ServiceItemClient {
|
|
449
|
+
private client;
|
|
450
|
+
constructor(client: AxiosInstance);
|
|
451
|
+
/**
|
|
452
|
+
* Get service item by ID
|
|
453
|
+
* GET /api/v1/service-items/:id
|
|
454
|
+
*
|
|
455
|
+
* @param id - Service item ID
|
|
456
|
+
* @returns Service item details
|
|
457
|
+
*/
|
|
458
|
+
getById(id: string): Promise<ApiResponse<ServiceItem>>;
|
|
459
|
+
/**
|
|
460
|
+
* List all service items for a service
|
|
461
|
+
* GET /api/v1/services/:serviceId/items
|
|
462
|
+
*
|
|
463
|
+
* @param serviceId - Service ID
|
|
464
|
+
* @returns List of service items for the specified service
|
|
465
|
+
*/
|
|
466
|
+
listByService(serviceId: string): Promise<ApiResponse<ServiceItem[]>>;
|
|
467
|
+
/**
|
|
468
|
+
* Create a new service item for a service
|
|
469
|
+
* POST /api/v1/services/:serviceId/items
|
|
470
|
+
*
|
|
471
|
+
* @param serviceId - Service ID
|
|
472
|
+
* @param data - Service item creation data
|
|
473
|
+
* @returns Created service item
|
|
474
|
+
*/
|
|
475
|
+
createForService(serviceId: string, data: Partial<ServiceItem>): Promise<ApiResponse<ServiceItem>>;
|
|
476
|
+
/**
|
|
477
|
+
* Create a new service item
|
|
478
|
+
* POST /api/v1/service-items (alternative to createForService)
|
|
479
|
+
*
|
|
480
|
+
* @param data - Service item creation data (must include service_id)
|
|
481
|
+
* @returns Created service item
|
|
482
|
+
*/
|
|
483
|
+
create(data: Partial<ServiceItem>): Promise<ApiResponse<ServiceItem>>;
|
|
484
|
+
/**
|
|
485
|
+
* Update service item by ID
|
|
486
|
+
* PUT /api/v1/service-items/:id
|
|
487
|
+
*
|
|
488
|
+
* @param id - Service item ID
|
|
489
|
+
* @param data - Service item update data
|
|
490
|
+
* @returns Updated service item
|
|
491
|
+
*/
|
|
492
|
+
update(id: string, data: Partial<ServiceItem>): Promise<ApiResponse<ServiceItem>>;
|
|
493
|
+
/**
|
|
494
|
+
* Delete service item by ID
|
|
495
|
+
* DELETE /api/v1/service-items/:id
|
|
496
|
+
*
|
|
497
|
+
* @param id - Service item ID
|
|
498
|
+
* @returns Empty response on success
|
|
499
|
+
*/
|
|
500
|
+
delete(id: string): Promise<ApiResponse<void>>;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
declare class SlotClient {
|
|
504
|
+
private client;
|
|
505
|
+
constructor(client: AxiosInstance);
|
|
506
|
+
/**
|
|
507
|
+
* Get slot by ID
|
|
508
|
+
* GET /api/v1/slots/:id
|
|
509
|
+
*
|
|
510
|
+
* @param id - Slot ID
|
|
511
|
+
* @returns Slot details
|
|
512
|
+
*/
|
|
513
|
+
getById(id: string): Promise<ApiResponse<Slot>>;
|
|
514
|
+
/**
|
|
515
|
+
* List available slots
|
|
516
|
+
* GET /api/v1/slots
|
|
517
|
+
*
|
|
518
|
+
* @param params - Query parameters (tenant_id required, status, is_active, page, limit)
|
|
519
|
+
* @returns Paginated list of slots
|
|
520
|
+
*/
|
|
521
|
+
list(params: {
|
|
522
|
+
tenant_id: string;
|
|
523
|
+
status?: string;
|
|
524
|
+
is_active?: boolean;
|
|
525
|
+
page?: number;
|
|
526
|
+
limit?: number;
|
|
527
|
+
}): Promise<ApiResponse<Slot[]>>;
|
|
528
|
+
/**
|
|
529
|
+
* Create a new slot
|
|
530
|
+
* POST /api/v1/slots
|
|
531
|
+
*
|
|
532
|
+
* @param data - Slot creation data (tenant_id, name required)
|
|
533
|
+
* @returns Created slot
|
|
534
|
+
*/
|
|
535
|
+
create(data: Partial<Slot>): Promise<ApiResponse<Slot>>;
|
|
536
|
+
/**
|
|
537
|
+
* Update slot by ID
|
|
538
|
+
* PUT /api/v1/slots/:id
|
|
539
|
+
*
|
|
540
|
+
* @param id - Slot ID
|
|
541
|
+
* @param data - Slot update data
|
|
542
|
+
* @returns Updated slot
|
|
543
|
+
*/
|
|
544
|
+
update(id: string, data: Partial<Slot>): Promise<ApiResponse<Slot>>;
|
|
545
|
+
/**
|
|
546
|
+
* Delete slot by ID
|
|
547
|
+
* DELETE /api/v1/slots/:id
|
|
548
|
+
*
|
|
549
|
+
* @param id - Slot ID
|
|
550
|
+
* @returns Empty response on success
|
|
551
|
+
*/
|
|
552
|
+
delete(id: string): Promise<ApiResponse<void>>;
|
|
553
|
+
/**
|
|
554
|
+
* Bulk create slots
|
|
555
|
+
* POST /api/v1/slots/bulk (verify if available)
|
|
556
|
+
*
|
|
557
|
+
* @param slots - Array of slots to create
|
|
558
|
+
* @returns Created slots
|
|
559
|
+
*/
|
|
560
|
+
bulkCreate(slots: Partial<Slot>[]): Promise<ApiResponse<Slot[]>>;
|
|
561
|
+
/**
|
|
562
|
+
* Mark slot as available/unavailable
|
|
563
|
+
* Note: Use update() method with status or other fields
|
|
564
|
+
*/
|
|
565
|
+
setAvailability(id: string, available: boolean): Promise<ApiResponse<Slot>>;
|
|
566
|
+
/**
|
|
567
|
+
* Get all active services for a slot with their items
|
|
568
|
+
* GET /api/v1/slots/:id/services
|
|
569
|
+
*
|
|
570
|
+
* @param id - Slot ID
|
|
571
|
+
* @returns Services with nested service items
|
|
572
|
+
*/
|
|
573
|
+
getServices(id: string): Promise<ApiResponse<ServiceWithItems[]>>;
|
|
574
|
+
/**
|
|
575
|
+
* Get slot with tenant information
|
|
576
|
+
* GET /api/v1/slots/:id/tenant-info
|
|
577
|
+
*
|
|
578
|
+
* @param id - Slot ID
|
|
579
|
+
* @returns Slot with tenant data
|
|
580
|
+
*/
|
|
581
|
+
getWithTenant(id: string): Promise<ApiResponse<Slot & {
|
|
582
|
+
tenant: Tenant;
|
|
583
|
+
}>>;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
declare class CustomerClient {
|
|
587
|
+
private client;
|
|
588
|
+
constructor(client: AxiosInstance);
|
|
589
|
+
/**
|
|
590
|
+
* Get customer by ID
|
|
591
|
+
* GET /api/v1/customers/:id
|
|
592
|
+
*
|
|
593
|
+
* @param id - Customer ID
|
|
594
|
+
* @returns Customer details
|
|
595
|
+
*/
|
|
596
|
+
getById(id: string): Promise<ApiResponse<Customer>>;
|
|
597
|
+
/**
|
|
598
|
+
* Get customer by email
|
|
599
|
+
* GET /api/v1/customers?email=:email&tenant_id=:id
|
|
600
|
+
*
|
|
601
|
+
* @param email - Customer email
|
|
602
|
+
* @param tenantId - Required tenant ID for scoping
|
|
603
|
+
* @returns Customer details
|
|
604
|
+
*/
|
|
605
|
+
getByEmail(email: string, tenantId: string): Promise<ApiResponse<Customer>>;
|
|
606
|
+
/**
|
|
607
|
+
* List customers with optional filters
|
|
608
|
+
* GET /api/v1/customers
|
|
609
|
+
*
|
|
610
|
+
* @param params - Query parameters (tenant_id required, page, limit, search, is_guest)
|
|
611
|
+
* @returns Paginated list of customers
|
|
612
|
+
*/
|
|
613
|
+
list(params: {
|
|
614
|
+
tenant_id: string;
|
|
615
|
+
page?: number;
|
|
616
|
+
limit?: number;
|
|
617
|
+
search?: string;
|
|
618
|
+
is_guest?: boolean;
|
|
619
|
+
}): Promise<ApiResponse<Customer[]>>;
|
|
620
|
+
/**
|
|
621
|
+
* Create a new customer
|
|
622
|
+
* POST /api/v1/customers
|
|
623
|
+
*
|
|
624
|
+
* @param data - Customer creation data (tenant_id, name, email required)
|
|
625
|
+
* @returns Created customer
|
|
626
|
+
*/
|
|
627
|
+
create(data: Partial<Customer>): Promise<ApiResponse<Customer>>;
|
|
628
|
+
/**
|
|
629
|
+
* Update customer by ID
|
|
630
|
+
* PUT /api/v1/customers/:id
|
|
631
|
+
*
|
|
632
|
+
* @param id - Customer ID
|
|
633
|
+
* @param data - Customer update data
|
|
634
|
+
* @returns Updated customer
|
|
635
|
+
*/
|
|
636
|
+
update(id: string, data: Partial<Customer>): Promise<ApiResponse<Customer>>;
|
|
637
|
+
/**
|
|
638
|
+
* Delete customer by ID
|
|
639
|
+
* DELETE /api/v1/customers/:id
|
|
640
|
+
*
|
|
641
|
+
* @param id - Customer ID
|
|
642
|
+
* @returns Empty response on success
|
|
643
|
+
*/
|
|
644
|
+
delete(id: string): Promise<ApiResponse<void>>;
|
|
645
|
+
/**
|
|
646
|
+
* Lookup customer by userId or tenantId
|
|
647
|
+
* GET /api/v1/customers/lookup
|
|
648
|
+
*
|
|
649
|
+
* @param params - Query parameters (userId optional, tenantId required)
|
|
650
|
+
* @returns Customer or null
|
|
651
|
+
*/
|
|
652
|
+
lookup(params: {
|
|
653
|
+
userId?: string;
|
|
654
|
+
tenantId: string;
|
|
655
|
+
}): Promise<ApiResponse<Customer | null>>;
|
|
656
|
+
/**
|
|
657
|
+
* Get autocomplete data from existing customers and bookings
|
|
658
|
+
* GET /api/v1/customers/autocomplete
|
|
659
|
+
*
|
|
660
|
+
* @param tenantId - Tenant ID (required)
|
|
661
|
+
* @returns Autocomplete data
|
|
662
|
+
*/
|
|
663
|
+
autocomplete(tenantId: string): Promise<ApiResponse<any>>;
|
|
664
|
+
/**
|
|
665
|
+
* Get all bookings for a customer
|
|
666
|
+
* GET /api/v1/customers/bookings
|
|
667
|
+
*
|
|
668
|
+
* @param params - Query parameters (email optional, userId optional, tenantId required)
|
|
669
|
+
* @returns Customer bookings data
|
|
670
|
+
*/
|
|
671
|
+
getBookings(params: {
|
|
672
|
+
email?: string;
|
|
673
|
+
userId?: string;
|
|
674
|
+
tenantId: string;
|
|
675
|
+
}): Promise<ApiResponse<{
|
|
676
|
+
customer: Customer | null;
|
|
677
|
+
bookings: Booking[];
|
|
678
|
+
groupedBySlot: Record<string, Booking[]>;
|
|
679
|
+
total: number;
|
|
680
|
+
}>>;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
declare class FlowClient {
|
|
684
|
+
private client;
|
|
685
|
+
constructor(client: AxiosInstance);
|
|
686
|
+
/**
|
|
687
|
+
* Get flow by ID
|
|
688
|
+
* GET /api/v1/flows/:id
|
|
689
|
+
*
|
|
690
|
+
* @param id - Flow ID
|
|
691
|
+
* @returns Flow details
|
|
692
|
+
*/
|
|
693
|
+
getById(id: string): Promise<ApiResponse<Flow>>;
|
|
694
|
+
/**
|
|
695
|
+
* List flows with optional filters
|
|
696
|
+
* GET /api/v1/flows
|
|
697
|
+
*
|
|
698
|
+
* @param params - Query parameters (tenantId, page, limit)
|
|
699
|
+
* @returns Paginated list of flows
|
|
700
|
+
*/
|
|
701
|
+
list(params?: {
|
|
702
|
+
tenantId?: string;
|
|
703
|
+
page?: number;
|
|
704
|
+
limit?: number;
|
|
705
|
+
}): Promise<ApiResponse<Flow[]>>;
|
|
706
|
+
/**
|
|
707
|
+
* Create a new flow
|
|
708
|
+
* POST /api/v1/flows
|
|
709
|
+
*
|
|
710
|
+
* @param data - Flow creation data
|
|
711
|
+
* @returns Created flow
|
|
712
|
+
*/
|
|
713
|
+
create(data: Partial<Flow>): Promise<ApiResponse<Flow>>;
|
|
714
|
+
/**
|
|
715
|
+
* Update flow by ID
|
|
716
|
+
* PATCH /api/v1/flows/:id
|
|
717
|
+
*
|
|
718
|
+
* @param id - Flow ID
|
|
719
|
+
* @param data - Flow update data
|
|
720
|
+
* @returns Updated flow
|
|
721
|
+
*/
|
|
722
|
+
update(id: string, data: Partial<Flow>): Promise<ApiResponse<Flow>>;
|
|
723
|
+
/**
|
|
724
|
+
* Delete flow by ID
|
|
725
|
+
* DELETE /api/v1/flows/:id
|
|
726
|
+
*
|
|
727
|
+
* @param id - Flow ID
|
|
728
|
+
* @returns Empty response on success
|
|
729
|
+
*/
|
|
730
|
+
delete(id: string): Promise<ApiResponse<void>>;
|
|
731
|
+
/**
|
|
732
|
+
* Execute a flow
|
|
733
|
+
* POST /api/v1/flows/:id/execute
|
|
734
|
+
*
|
|
735
|
+
* @param id - Flow ID
|
|
736
|
+
* @param input - Optional input data for flow execution
|
|
737
|
+
* @returns Flow execution result
|
|
738
|
+
*/
|
|
739
|
+
execute(id: string, input?: Record<string, any>): Promise<ApiResponse<any>>;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
declare class StudioClient {
|
|
743
|
+
private client;
|
|
744
|
+
constructor(client: AxiosInstance);
|
|
745
|
+
/**
|
|
746
|
+
* Get studio by ID
|
|
747
|
+
* GET /api/v1/studios/:id
|
|
748
|
+
*
|
|
749
|
+
* @param id - Studio ID
|
|
750
|
+
* @returns Studio details
|
|
751
|
+
*/
|
|
752
|
+
getById(id: string): Promise<ApiResponse<Studio>>;
|
|
753
|
+
/**
|
|
754
|
+
* Get studio by slug
|
|
755
|
+
* GET /api/v1/studios/slug/:slug
|
|
756
|
+
*
|
|
757
|
+
* @param slug - Studio slug
|
|
758
|
+
* @returns Studio details
|
|
759
|
+
*/
|
|
760
|
+
getBySlug(slug: string): Promise<ApiResponse<Studio>>;
|
|
761
|
+
/**
|
|
762
|
+
* List studios with optional filters
|
|
763
|
+
* GET /api/v1/studios
|
|
764
|
+
*
|
|
765
|
+
* @param params - Query parameters (page, limit, search)
|
|
766
|
+
* @returns Paginated list of studios
|
|
767
|
+
*/
|
|
768
|
+
list(params?: {
|
|
769
|
+
page?: number;
|
|
770
|
+
limit?: number;
|
|
771
|
+
search?: string;
|
|
772
|
+
}): Promise<ApiResponse<Studio[]>>;
|
|
773
|
+
/**
|
|
774
|
+
* Create a new studio
|
|
775
|
+
* POST /api/v1/studios
|
|
776
|
+
*
|
|
777
|
+
* @param data - Studio creation data
|
|
778
|
+
* @returns Created studio
|
|
779
|
+
*/
|
|
780
|
+
create(data: Partial<Studio>): Promise<ApiResponse<Studio>>;
|
|
781
|
+
/**
|
|
782
|
+
* Update studio by ID
|
|
783
|
+
* PATCH /api/v1/studios/:id
|
|
784
|
+
*
|
|
785
|
+
* @param id - Studio ID
|
|
786
|
+
* @param data - Studio update data
|
|
787
|
+
* @returns Updated studio
|
|
788
|
+
*/
|
|
789
|
+
update(id: string, data: Partial<Studio>): Promise<ApiResponse<Studio>>;
|
|
790
|
+
/**
|
|
791
|
+
* Delete studio by ID
|
|
792
|
+
* DELETE /api/v1/studios/:id
|
|
793
|
+
*
|
|
794
|
+
* @param id - Studio ID
|
|
795
|
+
* @returns Empty response on success
|
|
796
|
+
*/
|
|
797
|
+
delete(id: string): Promise<ApiResponse<void>>;
|
|
798
|
+
/**
|
|
799
|
+
* Get data quality issues for a studio
|
|
800
|
+
* GET /api/v1/studios/:id/data-quality
|
|
801
|
+
*
|
|
802
|
+
* @param id - Studio ID
|
|
803
|
+
* @param params - Query parameters (resolved, severity)
|
|
804
|
+
* @returns List of data quality issues
|
|
805
|
+
*/
|
|
806
|
+
getDataQualityIssues(id: string, params?: {
|
|
807
|
+
resolved?: boolean;
|
|
808
|
+
severity?: 'low' | 'medium' | 'high';
|
|
809
|
+
}): Promise<ApiResponse<any[]>>;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
declare class NotificationClient {
|
|
813
|
+
private client;
|
|
814
|
+
constructor(client: AxiosInstance);
|
|
815
|
+
/**
|
|
816
|
+
* Get notification by ID
|
|
817
|
+
* GET /api/v1/notifications/:id
|
|
818
|
+
*
|
|
819
|
+
* @param id - Notification ID
|
|
820
|
+
* @returns Notification details
|
|
821
|
+
*/
|
|
822
|
+
getById(id: string): Promise<ApiResponse<Notification>>;
|
|
823
|
+
/**
|
|
824
|
+
* List notifications with optional filters
|
|
825
|
+
* GET /api/v1/notifications
|
|
826
|
+
*
|
|
827
|
+
* @param params - Query parameters (tenantId, type, recipient, page, limit)
|
|
828
|
+
* @returns Paginated list of notifications
|
|
829
|
+
*/
|
|
830
|
+
list(params?: {
|
|
831
|
+
tenantId?: string;
|
|
832
|
+
type?: string;
|
|
833
|
+
recipient?: string;
|
|
834
|
+
page?: number;
|
|
835
|
+
limit?: number;
|
|
836
|
+
}): Promise<ApiResponse<Notification[]>>;
|
|
837
|
+
/**
|
|
838
|
+
* Create a new notification
|
|
839
|
+
* POST /api/v1/notifications
|
|
840
|
+
*
|
|
841
|
+
* @param data - Notification creation data
|
|
842
|
+
* @returns Created notification
|
|
843
|
+
*/
|
|
844
|
+
create(data: Partial<Notification>): Promise<ApiResponse<Notification>>;
|
|
845
|
+
/**
|
|
846
|
+
* Send notification immediately
|
|
847
|
+
* POST /api/v1/notifications/send
|
|
848
|
+
*
|
|
849
|
+
* @param data - Notification data to send
|
|
850
|
+
* @returns Sent notification
|
|
851
|
+
*/
|
|
852
|
+
send(data: Partial<Notification>): Promise<ApiResponse<Notification>>;
|
|
853
|
+
/**
|
|
854
|
+
* Update notification by ID
|
|
855
|
+
* PATCH /api/v1/notifications/:id
|
|
856
|
+
*
|
|
857
|
+
* @param id - Notification ID
|
|
858
|
+
* @param data - Notification update data
|
|
859
|
+
* @returns Updated notification
|
|
860
|
+
*/
|
|
861
|
+
update(id: string, data: Partial<Notification>): Promise<ApiResponse<Notification>>;
|
|
862
|
+
/**
|
|
863
|
+
* Delete notification by ID
|
|
864
|
+
* DELETE /api/v1/notifications/:id
|
|
865
|
+
*
|
|
866
|
+
* @param id - Notification ID
|
|
867
|
+
* @returns Empty response on success
|
|
868
|
+
*/
|
|
869
|
+
delete(id: string): Promise<ApiResponse<void>>;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
declare class DataQualityClient {
|
|
873
|
+
private client;
|
|
874
|
+
constructor(client: AxiosInstance);
|
|
875
|
+
/**
|
|
876
|
+
* Get data quality issue by ID
|
|
877
|
+
* GET /api/v1/data-quality/:id
|
|
878
|
+
*
|
|
879
|
+
* @param id - Issue ID
|
|
880
|
+
* @returns Data quality issue details
|
|
881
|
+
*/
|
|
882
|
+
getById(id: string): Promise<ApiResponse<DataQualityIssue>>;
|
|
883
|
+
/**
|
|
884
|
+
* List data quality issues with optional filters
|
|
885
|
+
* GET /api/v1/data-quality
|
|
886
|
+
*
|
|
887
|
+
* @param params - Query parameters (tenantId, type, severity, resolved, page, limit)
|
|
888
|
+
* @returns Paginated list of data quality issues
|
|
889
|
+
*/
|
|
890
|
+
list(params?: {
|
|
891
|
+
tenantId?: string;
|
|
892
|
+
type?: string;
|
|
893
|
+
severity?: 'low' | 'medium' | 'high';
|
|
894
|
+
resolved?: boolean;
|
|
895
|
+
page?: number;
|
|
896
|
+
limit?: number;
|
|
897
|
+
}): Promise<ApiResponse<DataQualityIssue[]>>;
|
|
898
|
+
/**
|
|
899
|
+
* Run data quality check for a tenant
|
|
900
|
+
* POST /api/v1/data-quality/check
|
|
901
|
+
*
|
|
902
|
+
* @param tenantId - Tenant ID to check
|
|
903
|
+
* @returns List of detected issues
|
|
904
|
+
*/
|
|
905
|
+
runCheck(tenantId: string): Promise<ApiResponse<DataQualityIssue[]>>;
|
|
906
|
+
/**
|
|
907
|
+
* Resolve a data quality issue
|
|
908
|
+
* PATCH /api/v1/data-quality/:id/resolve
|
|
909
|
+
*
|
|
910
|
+
* @param id - Issue ID
|
|
911
|
+
* @returns Resolved issue
|
|
912
|
+
*/
|
|
913
|
+
resolve(id: string): Promise<ApiResponse<DataQualityIssue>>;
|
|
914
|
+
/**
|
|
915
|
+
* Get data quality summary for a tenant
|
|
916
|
+
* GET /api/v1/data-quality/summary?tenantId=:id
|
|
917
|
+
*
|
|
918
|
+
* @param tenantId - Tenant ID
|
|
919
|
+
* @returns Summary of data quality issues
|
|
920
|
+
*/
|
|
921
|
+
getSummary(tenantId: string): Promise<ApiResponse<any>>;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* Custom error classes for Slotly SDK
|
|
926
|
+
*/
|
|
927
|
+
declare class SlotlyApiError extends Error {
|
|
928
|
+
code: string;
|
|
929
|
+
statusCode?: number | undefined;
|
|
930
|
+
details?: any | undefined;
|
|
931
|
+
constructor(code: string, message: string, statusCode?: number | undefined, details?: any | undefined);
|
|
932
|
+
}
|
|
933
|
+
declare class SlotlyAuthError extends SlotlyApiError {
|
|
934
|
+
constructor(message: string, details?: any);
|
|
935
|
+
}
|
|
936
|
+
declare class SlotlyNetworkError extends Error {
|
|
937
|
+
originalError?: any | undefined;
|
|
938
|
+
constructor(message: string, originalError?: any | undefined);
|
|
939
|
+
}
|
|
940
|
+
declare class SlotlyConfigurationError extends Error {
|
|
941
|
+
details?: any | undefined;
|
|
942
|
+
constructor(message: string, details?: any | undefined);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/**
|
|
946
|
+
* Authentication context extracted from validated Slotly request
|
|
947
|
+
*/
|
|
948
|
+
interface SlotlyAuthContext {
|
|
949
|
+
clientKey: string;
|
|
950
|
+
userId?: string;
|
|
951
|
+
tenantId?: string;
|
|
952
|
+
permissions?: string[];
|
|
953
|
+
tokenClaims?: Record<string, any>;
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Extended Next.js request with Slotly context
|
|
957
|
+
*/
|
|
958
|
+
interface SlotlyRequest extends NextApiRequest {
|
|
959
|
+
slotlyContext: SlotlyAuthContext;
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Safely extracts Slotly context from Next.js request
|
|
963
|
+
*
|
|
964
|
+
* Use this helper when you need to access the context outside of the middleware wrapper,
|
|
965
|
+
* or to check if the context exists.
|
|
966
|
+
*
|
|
967
|
+
* **Important:** Other route handlers should guard user-required flows:
|
|
968
|
+
* ```ts
|
|
969
|
+
* const context = getSlotlyContext(req);
|
|
970
|
+
* if (!context?.userId) {
|
|
971
|
+
* return res.status(403).json({
|
|
972
|
+
* success: false,
|
|
973
|
+
* error: { code: 'USER_REQUIRED', message: 'User authentication required' }
|
|
974
|
+
* });
|
|
975
|
+
* }
|
|
976
|
+
* ```
|
|
977
|
+
*
|
|
978
|
+
* @param req Next.js API request
|
|
979
|
+
* @returns SlotlyAuthContext if present, null otherwise
|
|
980
|
+
*/
|
|
981
|
+
declare function getSlotlyContext(req: NextApiRequest): SlotlyAuthContext | null;
|
|
982
|
+
/**
|
|
983
|
+
* Middleware wrapper for Next.js API routes and Edge Functions
|
|
984
|
+
* Validates Slotly authentication headers and injects context into request
|
|
985
|
+
*
|
|
986
|
+
* **Supported Environments:**
|
|
987
|
+
* - Next.js API Routes (serverless functions)
|
|
988
|
+
* - Next.js Edge Functions (Edge Runtime)
|
|
989
|
+
* - Vercel Edge Functions
|
|
990
|
+
*
|
|
991
|
+
* **Note on Edge Functions:**
|
|
992
|
+
* Header names are normalized, but edge runtime may have different header casing.
|
|
993
|
+
* This middleware handles both `x-slotly-api-key` and `X-Slotly-Api-Key` variants.
|
|
994
|
+
*
|
|
995
|
+
* **Error Response Format:**
|
|
996
|
+
* All error responses follow the `ApiResponse<T>` format for consistency:
|
|
997
|
+
* ```ts
|
|
998
|
+
* {
|
|
999
|
+
* success: false,
|
|
1000
|
+
* error: {
|
|
1001
|
+
* code: string, // Error code (e.g., 'AUTH_ERROR', 'INTERNAL_ERROR')
|
|
1002
|
+
* message: string, // Human-readable error message
|
|
1003
|
+
* details?: any // Additional error details
|
|
1004
|
+
* }
|
|
1005
|
+
* }
|
|
1006
|
+
* ```
|
|
1007
|
+
*
|
|
1008
|
+
* **Usage:**
|
|
1009
|
+
* ```ts
|
|
1010
|
+
* import { validateSlotlyRequest } from "@slotly/sdk/middleware/validateSlotlyRequest";
|
|
1011
|
+
*
|
|
1012
|
+
* export default validateSlotlyRequest(async (req, res) => {
|
|
1013
|
+
* const { userId, clientKey, tenantId, permissions } = req.slotlyContext;
|
|
1014
|
+
*
|
|
1015
|
+
* // Check if user authentication is required for this endpoint
|
|
1016
|
+
* if (!userId) {
|
|
1017
|
+
* return res.status(403).json({
|
|
1018
|
+
* success: false,
|
|
1019
|
+
* error: { code: 'USER_REQUIRED', message: 'User authentication required' }
|
|
1020
|
+
* });
|
|
1021
|
+
* }
|
|
1022
|
+
*
|
|
1023
|
+
* // Authenticated + scoped — safe to proceed
|
|
1024
|
+
* res.json({ success: true, data: { userId, tenantId } });
|
|
1025
|
+
* });
|
|
1026
|
+
* ```
|
|
1027
|
+
*
|
|
1028
|
+
* **Performance Considerations:**
|
|
1029
|
+
* - For high-throughput scenarios (e.g., many public booking flows), consider:
|
|
1030
|
+
* - Caching API key lookups with TTL (Redis, in-memory cache)
|
|
1031
|
+
* - Rate limiting per client key
|
|
1032
|
+
* - Database connection pooling for key validation
|
|
1033
|
+
*
|
|
1034
|
+
* @param handler Next.js API route handler or Edge Function handler
|
|
1035
|
+
* @returns Wrapped handler with Slotly validation
|
|
1036
|
+
*/
|
|
1037
|
+
declare function validateSlotlyRequest(handler: (req: SlotlyRequest, res: NextApiResponse) => Promise<void> | void): (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
|
|
1038
|
+
|
|
1039
|
+
interface SlotlyClientOptions {
|
|
1040
|
+
/**
|
|
1041
|
+
* Required: Function that returns the API key or service token.
|
|
1042
|
+
* This must return a non-empty string.
|
|
1043
|
+
*/
|
|
1044
|
+
getClientKey: () => Promise<string>;
|
|
1045
|
+
/**
|
|
1046
|
+
* Optional: Function that returns a Clerk JWT for user traceability.
|
|
1047
|
+
* If provided and returns a token, it will be included in the Authorization header.
|
|
1048
|
+
*/
|
|
1049
|
+
getUserToken?: () => Promise<string | null>;
|
|
1050
|
+
/**
|
|
1051
|
+
* Optional: Custom base URL for the API.
|
|
1052
|
+
* Defaults to process.env.SLOTLY_API_URL or 'https://api.slotly.dev'
|
|
1053
|
+
*/
|
|
1054
|
+
baseURL?: string;
|
|
1055
|
+
/**
|
|
1056
|
+
* Optional: Additional headers to include with every request.
|
|
1057
|
+
*/
|
|
1058
|
+
headers?: Record<string, string>;
|
|
1059
|
+
/**
|
|
1060
|
+
* Optional: Retry configuration.
|
|
1061
|
+
*/
|
|
1062
|
+
retry?: {
|
|
1063
|
+
maxRetries?: number;
|
|
1064
|
+
retryDelay?: number;
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Unified Slotly API client
|
|
1069
|
+
* Provides typed access to all Slotly API domains
|
|
1070
|
+
*
|
|
1071
|
+
* Note: RegisterClient has been moved to @slotly/studio-sdk
|
|
1072
|
+
* for studio-specific booking register operations.
|
|
1073
|
+
*/
|
|
1074
|
+
interface SlotlyApi {
|
|
1075
|
+
tenant: TenantClient;
|
|
1076
|
+
booking: BookingClient;
|
|
1077
|
+
service: ServiceClient;
|
|
1078
|
+
serviceItem: ServiceItemClient;
|
|
1079
|
+
slot: SlotClient;
|
|
1080
|
+
customer: CustomerClient;
|
|
1081
|
+
flow: FlowClient;
|
|
1082
|
+
studio: StudioClient;
|
|
1083
|
+
notification: NotificationClient;
|
|
1084
|
+
dataQuality: DataQualityClient;
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Initialize and configure a new Slotly API client with dual authentication.
|
|
1088
|
+
*
|
|
1089
|
+
* This function creates an isolated Axios instance per call, ensuring:
|
|
1090
|
+
* - SSR safety: Each request/context gets its own client instance
|
|
1091
|
+
* - No cross-contamination: Different auth contexts don't interfere
|
|
1092
|
+
* - Scalable: Supports future expansion for scoped keys, roles, or per-request tokens
|
|
1093
|
+
*
|
|
1094
|
+
* Authentication model:
|
|
1095
|
+
* - **getClientKey()**: Required. Always sets `x-slotly-api-key` header.
|
|
1096
|
+
* Identifies the SDK/application making the request.
|
|
1097
|
+
* - **getUserToken()**: Optional. Sets `Authorization: Bearer <token>` header if provided.
|
|
1098
|
+
* Provides user traceability and authorization scoping.
|
|
1099
|
+
*
|
|
1100
|
+
* @example Basic usage with API key (public SDK client):
|
|
1101
|
+
* ```ts
|
|
1102
|
+
* import { useSlotly } from '@slotly/sdk';
|
|
1103
|
+
*
|
|
1104
|
+
* const slotly = useSlotly({
|
|
1105
|
+
* getClientKey: async () => process.env.SLOTLY_SDK_KEY!,
|
|
1106
|
+
* });
|
|
1107
|
+
* ```
|
|
1108
|
+
*
|
|
1109
|
+
* @example Server-side with Clerk (authenticated, user-scoped):
|
|
1110
|
+
* ```ts
|
|
1111
|
+
* import { getToken } from '@clerk/clerk-sdk-node';
|
|
1112
|
+
* import { useSlotly } from '@slotly/sdk';
|
|
1113
|
+
*
|
|
1114
|
+
* // In an API route or server action
|
|
1115
|
+
* const slotly = useSlotly({
|
|
1116
|
+
* getClientKey: async () => process.env.SLOTLY_SDK_KEY!,
|
|
1117
|
+
* getUserToken: async () => await getToken(req),
|
|
1118
|
+
* });
|
|
1119
|
+
* ```
|
|
1120
|
+
*
|
|
1121
|
+
* @example Client-side with Clerk (Next.js):
|
|
1122
|
+
* ```ts
|
|
1123
|
+
* import { useAuth } from '@clerk/nextjs';
|
|
1124
|
+
* import { useSlotly } from '@slotly/sdk';
|
|
1125
|
+
*
|
|
1126
|
+
* function MyComponent() {
|
|
1127
|
+
* const { getToken } = useAuth();
|
|
1128
|
+
*
|
|
1129
|
+
* const slotly = useSlotly({
|
|
1130
|
+
* getClientKey: async () => process.env.NEXT_PUBLIC_SLOTLY_SDK_KEY!,
|
|
1131
|
+
* getUserToken: getToken,
|
|
1132
|
+
* });
|
|
1133
|
+
* }
|
|
1134
|
+
* ```
|
|
1135
|
+
*
|
|
1136
|
+
* @example Future: Scoped keys or role-based access
|
|
1137
|
+
* ```ts
|
|
1138
|
+
* // Future enhancement: Per-request scoping
|
|
1139
|
+
* const slotly = useSlotly({
|
|
1140
|
+
* getClientKey: async () => getScopedKey(role, tenantId),
|
|
1141
|
+
* getUserToken: async () => getToken(),
|
|
1142
|
+
* });
|
|
1143
|
+
* ```
|
|
1144
|
+
*
|
|
1145
|
+
* @throws {SlotlyConfigurationError} If getClientKey is missing, returns null, undefined, or empty string.
|
|
1146
|
+
*/
|
|
1147
|
+
declare const useSlotly: (options: SlotlyClientOptions) => SlotlyApi;
|
|
1148
|
+
|
|
1149
|
+
export { type ApiResponse, type Booking, type Category, type Customer, type DataQualityIssue, type Flow, type FlowStep, type Notification, type Service, type ServiceItem, type ServiceItemWithService, type ServiceWithItems, type Slot, type SlotlyApi, SlotlyApiError, type SlotlyAuthContext, SlotlyAuthError, type SlotlyClientOptions, SlotlyConfigurationError, SlotlyNetworkError, type SlotlyRequest, type Studio, type Tenant, type TenantBranding, type TenantFullConfig, useSlotly as default, getSlotlyContext, useSlotly, validateSlotlyRequest };
|