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