@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
|
@@ -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
|