@microboxlabs/miot-calendar-client 0.1.2 → 0.1.3
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 +567 -45
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# @microboxlabs/miot-calendar-client
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@microboxlabs/miot-calendar-client)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
|
|
3
6
|
TypeScript client for the ModularIoT Calendar API. Zero dependencies — uses the native `fetch` API.
|
|
4
7
|
|
|
5
8
|
## Installation
|
|
@@ -10,6 +13,8 @@ npm install @microboxlabs/miot-calendar-client
|
|
|
10
13
|
|
|
11
14
|
## Quick Start
|
|
12
15
|
|
|
16
|
+
A complete workflow: create a calendar, define a time window, generate slots, and book one.
|
|
17
|
+
|
|
13
18
|
```ts
|
|
14
19
|
import { createMiotCalendarClient } from "@microboxlabs/miot-calendar-client";
|
|
15
20
|
|
|
@@ -17,76 +22,593 @@ const client = createMiotCalendarClient({
|
|
|
17
22
|
baseUrl: "https://your-api-host.com",
|
|
18
23
|
});
|
|
19
24
|
|
|
20
|
-
//
|
|
21
|
-
const
|
|
25
|
+
// 1. Create a calendar
|
|
26
|
+
const calendar = await client.calendars.create({
|
|
27
|
+
code: "maintenance",
|
|
28
|
+
name: "Vehicle Maintenance",
|
|
29
|
+
timezone: "America/New_York", // defaults to "UTC" if omitted
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// 2. Add a time window (Mon–Fri, 8 AM – 5 PM, 30-min slots)
|
|
33
|
+
const timeWindow = await client.calendars.createTimeWindow(calendar.id, {
|
|
34
|
+
name: "Business Hours",
|
|
35
|
+
startHour: 8,
|
|
36
|
+
endHour: 17,
|
|
37
|
+
validFrom: "2026-03-01",
|
|
38
|
+
daysOfWeek: "1,2,3,4,5", // Mon–Fri
|
|
39
|
+
slotDurationMinutes: 30, // default: 30
|
|
40
|
+
capacityPerSlot: 2, // default: 1
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// 3. Generate slots for the first two weeks
|
|
44
|
+
const result = await client.slots.generate({
|
|
45
|
+
calendarId: calendar.id,
|
|
46
|
+
startDate: "2026-03-01",
|
|
47
|
+
endDate: "2026-03-14", // max range: 90 days
|
|
48
|
+
});
|
|
49
|
+
console.log(`Created ${result.slotsCreated} slots`);
|
|
22
50
|
|
|
23
|
-
//
|
|
24
|
-
const
|
|
25
|
-
calendarId:
|
|
26
|
-
|
|
27
|
-
|
|
51
|
+
// 4. Find available slots
|
|
52
|
+
const { data: slots } = await client.slots.list({
|
|
53
|
+
calendarId: calendar.id, // required
|
|
54
|
+
available: true,
|
|
55
|
+
startDate: "2026-03-03",
|
|
56
|
+
endDate: "2026-03-07",
|
|
28
57
|
});
|
|
58
|
+
|
|
59
|
+
// 5. Book the first available slot
|
|
60
|
+
const booking = await client.bookings.create(
|
|
61
|
+
{
|
|
62
|
+
calendarId: calendar.id,
|
|
63
|
+
resource: { id: "truck-42", type: "vehicle", label: "Truck 42" },
|
|
64
|
+
slot: { date: slots[0].slotDate, hour: slots[0].slotHour, minutes: slots[0].slotMinutes },
|
|
65
|
+
},
|
|
66
|
+
{ userId: "user-abc" }, // sets X-User-Id header → stored as createdBy
|
|
67
|
+
);
|
|
29
68
|
```
|
|
30
69
|
|
|
31
70
|
## Configuration
|
|
32
71
|
|
|
33
72
|
```ts
|
|
34
|
-
createMiotCalendarClient({
|
|
35
|
-
baseUrl: "https://your-api-host.com", // Required
|
|
36
|
-
headers: { Authorization: "Bearer ..." }, // Optional
|
|
37
|
-
fetch: customFetch, // Optional fetch implementation
|
|
73
|
+
const client = createMiotCalendarClient({
|
|
74
|
+
baseUrl: "https://your-api-host.com", // Required — API base URL
|
|
75
|
+
headers: { Authorization: "Bearer ..." }, // Optional — merged into every request
|
|
76
|
+
fetch: customFetch, // Optional — custom fetch implementation
|
|
38
77
|
});
|
|
39
78
|
```
|
|
40
79
|
|
|
80
|
+
| Option | Type | Required | Description |
|
|
81
|
+
|--------|------|----------|-------------|
|
|
82
|
+
| `baseUrl` | `string` | Yes | Base URL of the Calendar API |
|
|
83
|
+
| `headers` | `Record<string, string>` | No | Default headers sent with every request |
|
|
84
|
+
| `fetch` | `typeof fetch` | No | Custom `fetch` implementation (defaults to `globalThis.fetch`) |
|
|
85
|
+
|
|
41
86
|
## API Reference
|
|
42
87
|
|
|
43
|
-
###
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
|
50
|
-
|
|
51
|
-
| `
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
| `
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
|
72
|
-
|
|
73
|
-
| `
|
|
88
|
+
### Calendars
|
|
89
|
+
|
|
90
|
+
#### `calendars.list(params?)`
|
|
91
|
+
|
|
92
|
+
List all calendars, optionally filtered by active status.
|
|
93
|
+
|
|
94
|
+
| Param | Type | Required | Description |
|
|
95
|
+
|-------|------|----------|-------------|
|
|
96
|
+
| `active` | `boolean` | No | Filter by active status |
|
|
97
|
+
|
|
98
|
+
**Returns:** `CalendarResponse[]`
|
|
99
|
+
|
|
100
|
+
#### `calendars.get(id)`
|
|
101
|
+
|
|
102
|
+
Get a single calendar by ID.
|
|
103
|
+
|
|
104
|
+
| Param | Type | Required | Description |
|
|
105
|
+
|-------|------|----------|-------------|
|
|
106
|
+
| `id` | `string` | Yes | Calendar ID |
|
|
107
|
+
|
|
108
|
+
**Returns:** `CalendarResponse`
|
|
109
|
+
|
|
110
|
+
**Throws:** `MiotCalendarApiError` with status `404` if not found.
|
|
111
|
+
|
|
112
|
+
#### `calendars.create(body)`
|
|
113
|
+
|
|
114
|
+
Create a new calendar.
|
|
115
|
+
|
|
116
|
+
| Field | Type | Required | Default | Description |
|
|
117
|
+
|-------|------|----------|---------|-------------|
|
|
118
|
+
| `code` | `string` | Yes | — | Unique code identifier |
|
|
119
|
+
| `name` | `string` | Yes | — | Display name |
|
|
120
|
+
| `description` | `string` | No | — | Optional description |
|
|
121
|
+
| `timezone` | `string` | No | `"UTC"` | IANA timezone |
|
|
122
|
+
| `active` | `boolean` | No | `true` | Whether the calendar is active |
|
|
123
|
+
|
|
124
|
+
**Returns:** `CalendarResponse`
|
|
125
|
+
|
|
126
|
+
#### `calendars.update(id, body)`
|
|
127
|
+
|
|
128
|
+
Replace a calendar's fields. Takes the same body as `create`.
|
|
129
|
+
|
|
130
|
+
| Param | Type | Required | Description |
|
|
131
|
+
|-------|------|----------|-------------|
|
|
132
|
+
| `id` | `string` | Yes | Calendar ID |
|
|
133
|
+
| `body` | `CalendarRequest` | Yes | Updated calendar data |
|
|
134
|
+
|
|
135
|
+
**Returns:** `CalendarResponse`
|
|
136
|
+
|
|
137
|
+
#### `calendars.deactivate(id)`
|
|
138
|
+
|
|
139
|
+
Deactivate a calendar (soft delete).
|
|
140
|
+
|
|
141
|
+
| Param | Type | Required | Description |
|
|
142
|
+
|-------|------|----------|-------------|
|
|
143
|
+
| `id` | `string` | Yes | Calendar ID |
|
|
144
|
+
|
|
145
|
+
**Returns:** `void` (HTTP 204 — no content)
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
### Time Windows
|
|
150
|
+
|
|
151
|
+
Time windows are managed through the `calendars` namespace.
|
|
152
|
+
|
|
153
|
+
#### `calendars.listTimeWindows(calendarId)`
|
|
154
|
+
|
|
155
|
+
List all time windows for a calendar.
|
|
156
|
+
|
|
157
|
+
| Param | Type | Required | Description |
|
|
158
|
+
|-------|------|----------|-------------|
|
|
159
|
+
| `calendarId` | `string` | Yes | Calendar ID |
|
|
160
|
+
|
|
161
|
+
**Returns:** `TimeWindowResponse[]`
|
|
162
|
+
|
|
163
|
+
#### `calendars.createTimeWindow(calendarId, body)`
|
|
164
|
+
|
|
165
|
+
Create a time window within a calendar.
|
|
166
|
+
|
|
167
|
+
| Field | Type | Required | Default | Description |
|
|
168
|
+
|-------|------|----------|---------|-------------|
|
|
169
|
+
| `name` | `string` | Yes | — | Time window name |
|
|
170
|
+
| `startHour` | `number` | Yes | — | Start hour (0–23) |
|
|
171
|
+
| `endHour` | `number` | Yes | — | End hour (0–23, must be > startHour) |
|
|
172
|
+
| `validFrom` | `string` | Yes | — | Start date (`YYYY-MM-DD`) |
|
|
173
|
+
| `validTo` | `string` | No | — | End date (`YYYY-MM-DD`) |
|
|
174
|
+
| `slotDurationMinutes` | `number` | No | `30` | Slot duration in minutes |
|
|
175
|
+
| `capacityPerSlot` | `number` | No | `1` | Max bookings per slot |
|
|
176
|
+
| `daysOfWeek` | `string` | No | — | Comma-separated days (1=Mon … 7=Sun) |
|
|
177
|
+
| `active` | `boolean` | No | `true` | Whether the time window is active |
|
|
178
|
+
|
|
179
|
+
**Returns:** `TimeWindowResponse`
|
|
180
|
+
|
|
181
|
+
#### `calendars.updateTimeWindow(calendarId, timeWindowId, body)`
|
|
182
|
+
|
|
183
|
+
Update a time window. Takes the same body as `createTimeWindow`.
|
|
184
|
+
|
|
185
|
+
| Param | Type | Required | Description |
|
|
186
|
+
|-------|------|----------|-------------|
|
|
187
|
+
| `calendarId` | `string` | Yes | Calendar ID |
|
|
188
|
+
| `timeWindowId` | `string` | Yes | Time window ID |
|
|
189
|
+
| `body` | `TimeWindowRequest` | Yes | Updated time window data |
|
|
190
|
+
|
|
191
|
+
**Returns:** `TimeWindowResponse`
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
### Slots
|
|
196
|
+
|
|
197
|
+
#### `slots.list(params)`
|
|
198
|
+
|
|
199
|
+
List slots for a calendar. The `calendarId` parameter is **required**.
|
|
200
|
+
|
|
201
|
+
| Param | Type | Required | Default | Description |
|
|
202
|
+
|-------|------|----------|---------|-------------|
|
|
203
|
+
| `calendarId` | `string` | Yes | — | Calendar ID |
|
|
204
|
+
| `available` | `boolean` | No | — | Filter by availability |
|
|
205
|
+
| `startDate` | `string` | No | today | Start of date range (`YYYY-MM-DD`) |
|
|
206
|
+
| `endDate` | `string` | No | today + 7 days | End of date range (`YYYY-MM-DD`) |
|
|
207
|
+
|
|
208
|
+
**Returns:** `SlotListResponse` — `{ data: SlotResponse[], total: number }`
|
|
209
|
+
|
|
210
|
+
#### `slots.get(id)`
|
|
211
|
+
|
|
212
|
+
Get a single slot by ID.
|
|
213
|
+
|
|
214
|
+
| Param | Type | Required | Description |
|
|
215
|
+
|-------|------|----------|-------------|
|
|
216
|
+
| `id` | `string` | Yes | Slot ID |
|
|
217
|
+
|
|
218
|
+
**Returns:** `SlotResponse`
|
|
219
|
+
|
|
220
|
+
**Throws:** `MiotCalendarApiError` with status `404` if not found.
|
|
221
|
+
|
|
222
|
+
#### `slots.generate(body)`
|
|
223
|
+
|
|
224
|
+
Generate slots for a calendar based on its time windows.
|
|
225
|
+
|
|
226
|
+
| Field | Type | Required | Description |
|
|
227
|
+
|-------|------|----------|-------------|
|
|
228
|
+
| `calendarId` | `string` | Yes | Calendar ID |
|
|
229
|
+
| `startDate` | `string` | Yes | Start date (`YYYY-MM-DD`) |
|
|
230
|
+
| `endDate` | `string` | Yes | End date (`YYYY-MM-DD`) |
|
|
231
|
+
|
|
232
|
+
> **Note:** The date range must not exceed **90 days**. The API returns `400 Bad Request` if the range is larger.
|
|
233
|
+
|
|
234
|
+
**Returns:** `GenerateSlotsResponse` — `{ slotsCreated: number, slotsSkipped: number, message: string }`
|
|
235
|
+
|
|
236
|
+
#### `slots.updateStatus(id, body)`
|
|
237
|
+
|
|
238
|
+
Manually change a slot's status.
|
|
239
|
+
|
|
240
|
+
| Field | Type | Required | Description |
|
|
241
|
+
|-------|------|----------|-------------|
|
|
242
|
+
| `id` | `string` | Yes | Slot ID |
|
|
243
|
+
| `status` | `"OPEN" \| "CLOSED"` | Yes | New status |
|
|
244
|
+
|
|
245
|
+
> **Note:** Only `OPEN` and `CLOSED` are accepted. `FULL` is managed automatically by the system based on occupancy and cannot be set manually. Passing `FULL` returns `400 Bad Request`.
|
|
246
|
+
|
|
247
|
+
**Returns:** `SlotResponse`
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
### Bookings
|
|
252
|
+
|
|
253
|
+
#### `bookings.list(params?)`
|
|
254
|
+
|
|
255
|
+
List bookings, optionally filtered by calendar and date range.
|
|
256
|
+
|
|
257
|
+
| Param | Type | Required | Default | Description |
|
|
258
|
+
|-------|------|----------|---------|-------------|
|
|
259
|
+
| `calendarId` | `string` | No | — | Filter by calendar |
|
|
260
|
+
| `startDate` | `string` | No | today | Start of date range (`YYYY-MM-DD`) |
|
|
261
|
+
| `endDate` | `string` | No | today + 30 days | End of date range (`YYYY-MM-DD`) |
|
|
262
|
+
|
|
263
|
+
**Returns:** `BookingListResponse` — `{ data: BookingResponse[], total: number }`
|
|
264
|
+
|
|
265
|
+
#### `bookings.get(id)`
|
|
266
|
+
|
|
267
|
+
Get a single booking by ID.
|
|
268
|
+
|
|
269
|
+
| Param | Type | Required | Description |
|
|
270
|
+
|-------|------|----------|-------------|
|
|
271
|
+
| `id` | `string` | Yes | Booking ID |
|
|
272
|
+
|
|
273
|
+
**Returns:** `BookingResponse`
|
|
274
|
+
|
|
275
|
+
**Throws:** `MiotCalendarApiError` with status `404` if not found.
|
|
276
|
+
|
|
277
|
+
#### `bookings.create(body, options?)`
|
|
278
|
+
|
|
279
|
+
Create a booking for a specific slot.
|
|
280
|
+
|
|
281
|
+
**Body (`BookingRequest`):**
|
|
282
|
+
|
|
283
|
+
| Field | Type | Required | Description |
|
|
284
|
+
|-------|------|----------|-------------|
|
|
285
|
+
| `calendarId` | `string` | Yes | Calendar ID |
|
|
286
|
+
| `resource` | `ResourceData` | Yes | The resource being booked |
|
|
287
|
+
| `slot` | `SlotData` | Yes | Target slot (date + time) |
|
|
288
|
+
|
|
289
|
+
**Options:**
|
|
290
|
+
|
|
291
|
+
| Field | Type | Required | Description |
|
|
292
|
+
|-------|------|----------|-------------|
|
|
293
|
+
| `userId` | `string` | No | Sets `X-User-Id` header — stored as `createdBy` in the response |
|
|
294
|
+
|
|
295
|
+
**Returns:** `BookingResponse`
|
|
296
|
+
|
|
297
|
+
**Throws:**
|
|
298
|
+
|
|
299
|
+
- `404` — Calendar or slot not found
|
|
300
|
+
- `409` — Slot is full (no available capacity)
|
|
301
|
+
|
|
302
|
+
#### `bookings.cancel(id)`
|
|
303
|
+
|
|
304
|
+
Cancel a booking.
|
|
305
|
+
|
|
306
|
+
| Param | Type | Required | Description |
|
|
307
|
+
|-------|------|----------|-------------|
|
|
308
|
+
| `id` | `string` | Yes | Booking ID |
|
|
309
|
+
|
|
310
|
+
**Returns:** `void` (HTTP 204 — no content)
|
|
311
|
+
|
|
312
|
+
#### `bookings.listByResource(resourceId)`
|
|
313
|
+
|
|
314
|
+
List all bookings for a specific resource.
|
|
315
|
+
|
|
316
|
+
| Param | Type | Required | Description |
|
|
317
|
+
|-------|------|----------|-------------|
|
|
318
|
+
| `resourceId` | `string` | Yes | Resource ID |
|
|
319
|
+
|
|
320
|
+
**Returns:** `BookingListResponse`
|
|
321
|
+
|
|
322
|
+
## Types
|
|
323
|
+
|
|
324
|
+
### `CalendarRequest`
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
interface CalendarRequest {
|
|
328
|
+
code: string; // Unique code identifier
|
|
329
|
+
name: string; // Display name
|
|
330
|
+
description?: string; // Optional description
|
|
331
|
+
timezone?: string; // IANA timezone (default: "UTC")
|
|
332
|
+
active?: boolean; // Active status (default: true)
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### `CalendarResponse`
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
interface CalendarResponse {
|
|
340
|
+
id: string;
|
|
341
|
+
code: string;
|
|
342
|
+
name: string;
|
|
343
|
+
description?: string;
|
|
344
|
+
timezone: string; // Always present (default: "UTC")
|
|
345
|
+
active: boolean; // Always present (default: true)
|
|
346
|
+
createdAt: string; // ISO 8601
|
|
347
|
+
updatedAt: string; // ISO 8601
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### `TimeWindowRequest`
|
|
352
|
+
|
|
353
|
+
```ts
|
|
354
|
+
interface TimeWindowRequest {
|
|
355
|
+
name: string; // Time window name
|
|
356
|
+
startHour: number; // 0–23
|
|
357
|
+
endHour: number; // 0–23 (must be > startHour)
|
|
358
|
+
validFrom: string; // YYYY-MM-DD
|
|
359
|
+
validTo?: string; // YYYY-MM-DD
|
|
360
|
+
slotDurationMinutes?: number; // Default: 30
|
|
361
|
+
capacityPerSlot?: number; // Default: 1
|
|
362
|
+
daysOfWeek?: string; // Comma-separated: "1,2,3,4,5" (1=Mon, 7=Sun)
|
|
363
|
+
active?: boolean; // Default: true
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### `TimeWindowResponse`
|
|
368
|
+
|
|
369
|
+
```ts
|
|
370
|
+
interface TimeWindowResponse {
|
|
371
|
+
id: string;
|
|
372
|
+
calendarId: string;
|
|
373
|
+
name: string;
|
|
374
|
+
startHour: number;
|
|
375
|
+
endHour: number;
|
|
376
|
+
slotDurationMinutes: number; // Always present (default: 30)
|
|
377
|
+
capacityPerSlot: number; // Always present (default: 1)
|
|
378
|
+
daysOfWeek: string;
|
|
379
|
+
validFrom: string;
|
|
380
|
+
validTo?: string;
|
|
381
|
+
active: boolean; // Always present (default: true)
|
|
382
|
+
createdAt: string; // ISO 8601
|
|
383
|
+
updatedAt: string; // ISO 8601
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### `SlotResponse`
|
|
388
|
+
|
|
389
|
+
```ts
|
|
390
|
+
interface SlotResponse {
|
|
391
|
+
id: string;
|
|
392
|
+
calendarId: string;
|
|
393
|
+
timeWindowId?: string; // May be absent for manually created slots
|
|
394
|
+
slotDate: string; // YYYY-MM-DD
|
|
395
|
+
slotHour: number; // 0–23
|
|
396
|
+
slotMinutes: number; // 0–59
|
|
397
|
+
capacity: number; // Total capacity
|
|
398
|
+
currentOccupancy: number; // Current bookings count
|
|
399
|
+
availableCapacity: number; // capacity - currentOccupancy
|
|
400
|
+
status: SlotStatus; // "OPEN" | "FULL" | "CLOSED"
|
|
401
|
+
createdAt: string; // ISO 8601
|
|
402
|
+
updatedAt: string; // ISO 8601
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### `SlotListResponse`
|
|
407
|
+
|
|
408
|
+
```ts
|
|
409
|
+
interface SlotListResponse {
|
|
410
|
+
data: SlotResponse[];
|
|
411
|
+
total: number;
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### `GenerateSlotsRequest`
|
|
416
|
+
|
|
417
|
+
```ts
|
|
418
|
+
interface GenerateSlotsRequest {
|
|
419
|
+
calendarId: string;
|
|
420
|
+
startDate: string; // YYYY-MM-DD
|
|
421
|
+
endDate: string; // YYYY-MM-DD (max 90 days from startDate)
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### `GenerateSlotsResponse`
|
|
426
|
+
|
|
427
|
+
```ts
|
|
428
|
+
interface GenerateSlotsResponse {
|
|
429
|
+
slotsCreated: number;
|
|
430
|
+
slotsSkipped: number;
|
|
431
|
+
message: string;
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### `UpdateSlotStatusRequest`
|
|
436
|
+
|
|
437
|
+
```ts
|
|
438
|
+
interface UpdateSlotStatusRequest {
|
|
439
|
+
status: SlotStatus; // Only "OPEN" or "CLOSED" accepted
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### `BookingRequest`
|
|
444
|
+
|
|
445
|
+
```ts
|
|
446
|
+
interface BookingRequest {
|
|
447
|
+
calendarId: string;
|
|
448
|
+
resource: ResourceData;
|
|
449
|
+
slot: SlotData;
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### `BookingResponse`
|
|
454
|
+
|
|
455
|
+
```ts
|
|
456
|
+
interface BookingResponse {
|
|
457
|
+
id: string;
|
|
458
|
+
calendarId: string;
|
|
459
|
+
resource: ResourceData;
|
|
460
|
+
slot: SlotData;
|
|
461
|
+
createdAt: string; // ISO 8601
|
|
462
|
+
createdBy?: string; // Set via X-User-Id header on create
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### `BookingListResponse`
|
|
467
|
+
|
|
468
|
+
```ts
|
|
469
|
+
interface BookingListResponse {
|
|
470
|
+
data: BookingResponse[];
|
|
471
|
+
total: number;
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### `ResourceData`
|
|
476
|
+
|
|
477
|
+
```ts
|
|
478
|
+
interface ResourceData {
|
|
479
|
+
id: string; // Resource identifier
|
|
480
|
+
type?: string; // Resource type (e.g. "vehicle", "room")
|
|
481
|
+
label?: string; // Human-readable label
|
|
482
|
+
data?: Record<string, unknown>; // Arbitrary metadata
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
### `SlotData`
|
|
487
|
+
|
|
488
|
+
```ts
|
|
489
|
+
interface SlotData {
|
|
490
|
+
date: string; // YYYY-MM-DD
|
|
491
|
+
hour: number; // 0–23
|
|
492
|
+
minutes: number; // 0–59
|
|
493
|
+
}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### `SlotStatus`
|
|
497
|
+
|
|
498
|
+
```ts
|
|
499
|
+
type SlotStatus = "OPEN" | "FULL" | "CLOSED";
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### `ErrorResponse`
|
|
503
|
+
|
|
504
|
+
```ts
|
|
505
|
+
interface ErrorResponse {
|
|
506
|
+
error: string; // Error type (e.g. "Bad Request")
|
|
507
|
+
message: string; // Detailed error message
|
|
508
|
+
status: number; // HTTP status code
|
|
509
|
+
timestamp: string; // ISO 8601
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### `ClientConfig`
|
|
514
|
+
|
|
515
|
+
```ts
|
|
516
|
+
interface ClientConfig {
|
|
517
|
+
baseUrl: string;
|
|
518
|
+
headers?: Record<string, string>;
|
|
519
|
+
fetch?: typeof fetch;
|
|
520
|
+
}
|
|
521
|
+
```
|
|
74
522
|
|
|
75
523
|
## Error Handling
|
|
76
524
|
|
|
525
|
+
All API errors throw `MiotCalendarApiError`.
|
|
526
|
+
|
|
77
527
|
```ts
|
|
78
528
|
import { MiotCalendarApiError } from "@microboxlabs/miot-calendar-client";
|
|
79
529
|
|
|
80
530
|
try {
|
|
81
|
-
await client.bookings.
|
|
531
|
+
await client.bookings.create({
|
|
532
|
+
calendarId: "cal-123",
|
|
533
|
+
resource: { id: "truck-42", type: "vehicle", label: "Truck 42" },
|
|
534
|
+
slot: { date: "2026-03-01", hour: 10, minutes: 0 },
|
|
535
|
+
});
|
|
82
536
|
} catch (error) {
|
|
83
537
|
if (error instanceof MiotCalendarApiError) {
|
|
84
|
-
console.log(error.status);
|
|
85
|
-
console.log(error.
|
|
538
|
+
console.log(error.status); // HTTP status code (e.g. 409)
|
|
539
|
+
console.log(error.message); // Error message string
|
|
540
|
+
console.log(error.body); // ErrorResponse object or raw string
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
### Error Codes
|
|
546
|
+
|
|
547
|
+
| Status | Meaning | Common Causes |
|
|
548
|
+
|--------|---------|---------------|
|
|
549
|
+
| `400` | Bad Request | Validation errors, slot generation range > 90 days, setting status to `FULL` manually |
|
|
550
|
+
| `404` | Not Found | Calendar, slot, or booking does not exist |
|
|
551
|
+
| `409` | Conflict | Slot is full (no available capacity for booking) |
|
|
552
|
+
|
|
553
|
+
### Error Body
|
|
554
|
+
|
|
555
|
+
The `body` property on `MiotCalendarApiError` can be either:
|
|
556
|
+
|
|
557
|
+
- An **`ErrorResponse` object** with `error`, `message`, `status`, and `timestamp` fields — returned when the API sends a JSON error response.
|
|
558
|
+
- A **plain `string`** — returned as a fallback when the API response is not valid JSON (e.g. gateway errors, load balancer timeouts).
|
|
559
|
+
|
|
560
|
+
```ts
|
|
561
|
+
if (error instanceof MiotCalendarApiError) {
|
|
562
|
+
if (typeof error.body === "string") {
|
|
563
|
+
// Non-JSON error (gateway, timeout, etc.)
|
|
564
|
+
console.log("Raw error:", error.body);
|
|
565
|
+
} else {
|
|
566
|
+
// Structured API error
|
|
567
|
+
console.log(error.body.error); // "Conflict"
|
|
568
|
+
console.log(error.body.message); // "Slot is full"
|
|
569
|
+
console.log(error.body.timestamp); // "2026-03-01T10:00:00Z"
|
|
86
570
|
}
|
|
87
571
|
}
|
|
88
572
|
```
|
|
89
573
|
|
|
574
|
+
## Custom Fetch
|
|
575
|
+
|
|
576
|
+
Pass a custom `fetch` implementation for server-side rendering, auth interceptors, or testing.
|
|
577
|
+
|
|
578
|
+
### Auth interceptor
|
|
579
|
+
|
|
580
|
+
```ts
|
|
581
|
+
const client = createMiotCalendarClient({
|
|
582
|
+
baseUrl: "https://your-api-host.com",
|
|
583
|
+
fetch: async (input, init) => {
|
|
584
|
+
const token = await getAccessToken(); // your auth logic
|
|
585
|
+
const headers = new Headers(init?.headers);
|
|
586
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
587
|
+
return globalThis.fetch(input, { ...init, headers });
|
|
588
|
+
},
|
|
589
|
+
});
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
### Testing with a mock fetch
|
|
593
|
+
|
|
594
|
+
```ts
|
|
595
|
+
import { createMiotCalendarClient } from "@microboxlabs/miot-calendar-client";
|
|
596
|
+
|
|
597
|
+
const mockFetch = async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
598
|
+
return new Response(JSON.stringify({ id: "cal-1", code: "test", name: "Test" }), {
|
|
599
|
+
status: 200,
|
|
600
|
+
headers: { "Content-Type": "application/json" },
|
|
601
|
+
});
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
const client = createMiotCalendarClient({
|
|
605
|
+
baseUrl: "https://mock-api",
|
|
606
|
+
fetch: mockFetch,
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
const calendar = await client.calendars.get("cal-1");
|
|
610
|
+
```
|
|
611
|
+
|
|
90
612
|
## License
|
|
91
613
|
|
|
92
614
|
[Apache-2.0](./LICENSE)
|