recal-sdk 0.1.1 → 0.2.1
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 +488 -199
- package/dist/entities/organization.d.ts +2 -2
- package/dist/entities/organization.d.ts.map +1 -1
- package/dist/entities/organization.js +32 -0
- package/dist/entities/organization.js.map +1 -0
- package/dist/entities/user.d.ts +2 -2
- package/dist/entities/user.d.ts.map +1 -1
- package/dist/entities/user.js +45 -0
- package/dist/entities/user.js.map +1 -0
- package/dist/errors.js +36 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +52 -7046
- package/dist/index.js.map +1 -1
- package/dist/services/calendar.service.d.ts +82 -35
- package/dist/services/calendar.service.d.ts.map +1 -1
- package/dist/services/calendar.service.js +292 -0
- package/dist/services/calendar.service.js.map +1 -0
- package/dist/services/oauth.service.d.ts +2 -1
- package/dist/services/oauth.service.d.ts.map +1 -1
- package/dist/services/oauth.service.js +168 -0
- package/dist/services/oauth.service.js.map +1 -0
- package/dist/services/organizations.service.d.ts +1 -1
- package/dist/services/organizations.service.d.ts.map +1 -1
- package/dist/services/organizations.service.js +132 -0
- package/dist/services/organizations.service.js.map +1 -0
- package/dist/services/scheduling.service.d.ts +32 -29
- package/dist/services/scheduling.service.d.ts.map +1 -1
- package/dist/services/scheduling.service.js +122 -0
- package/dist/services/scheduling.service.js.map +1 -0
- package/dist/services/users.service.js +93 -0
- package/dist/services/users.service.js.map +1 -0
- package/dist/typebox/calendar.tb.d.ts +7 -6
- package/dist/typebox/calendar.tb.d.ts.map +1 -1
- package/dist/typebox/calendar.tb.js +92 -0
- package/dist/typebox/calendar.tb.js.map +1 -0
- package/dist/typebox/oauth.tb.d.ts +2 -2
- package/dist/typebox/oauth.tb.js +16 -0
- package/dist/typebox/oauth.tb.js.map +1 -0
- package/dist/typebox/organization.stripped.tb.js +7 -0
- package/dist/typebox/organization.stripped.tb.js.map +1 -0
- package/dist/typebox/organization.tb.js +8 -0
- package/dist/typebox/organization.tb.js.map +1 -0
- package/dist/typebox/scheduling.tb.js +118 -0
- package/dist/typebox/scheduling.tb.js.map +1 -0
- package/dist/typebox/timeString.tb.js +7 -0
- package/dist/typebox/timeString.tb.js.map +1 -0
- package/dist/typebox/user.stripped.tb.js +6 -0
- package/dist/typebox/user.stripped.tb.js.map +1 -0
- package/dist/typebox/user.tb.d.ts +1 -1
- package/dist/typebox/user.tb.js +10 -0
- package/dist/typebox/user.tb.js.map +1 -0
- package/dist/types/calendar.types.d.ts +5 -7
- package/dist/types/calendar.types.d.ts.map +1 -1
- package/dist/types/calendar.types.js +27 -56
- package/dist/types/calendar.types.js.map +1 -1
- package/dist/types/internal.types.js +2 -0
- package/dist/types/internal.types.js.map +1 -0
- package/dist/types/oauth.types.d.ts +1 -1
- package/dist/types/oauth.types.d.ts.map +1 -1
- package/dist/types/oauth.types.js +2 -0
- package/dist/types/oauth.types.js.map +1 -0
- package/dist/types/scheduling.types.js +2 -0
- package/dist/types/scheduling.types.js.map +1 -0
- package/dist/utils/fetch.helper.d.ts +1 -1
- package/dist/utils/fetch.helper.d.ts.map +1 -1
- package/dist/utils/fetch.helper.js +93 -0
- package/dist/utils/fetch.helper.js.map +1 -0
- package/dist/utils/fetchErrorHandler.js +32 -0
- package/dist/utils/fetchErrorHandler.js.map +1 -0
- package/dist/utils/functionize.js +6 -0
- package/dist/utils/functionize.js.map +1 -0
- package/dist/utils/includes.helper.js +10 -0
- package/dist/utils/includes.helper.js.map +1 -0
- package/dist/utils/omit.js +4 -0
- package/dist/utils/omit.js.map +1 -0
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -10,8 +10,8 @@ A powerful, type-safe SDK for interacting with the Recal calendar API. Build sop
|
|
|
10
10
|
|
|
11
11
|
- **Multi-Provider Support**: Seamlessly work with Google Calendar and Microsoft Outlook
|
|
12
12
|
- **Type Safety**: Full TypeScript support with runtime validation
|
|
13
|
-
- **Rich Calendar Operations**: Events,
|
|
14
|
-
- **Organization Management**: Handle
|
|
13
|
+
- **Rich Calendar Operations**: Events, busy queries, scheduling, and more
|
|
14
|
+
- **Organization Management**: Handle organizations and users calendars
|
|
15
15
|
- **OAuth Integration**: Built-in OAuth flow support for calendar connections
|
|
16
16
|
- **Error Handling**: Comprehensive error types for robust applications
|
|
17
17
|
- **Modern Architecture**: Clean, testable service-based design
|
|
@@ -36,31 +36,12 @@ bun add recal-sdk
|
|
|
36
36
|
```typescript
|
|
37
37
|
import { RecalClient } from 'recal-sdk'
|
|
38
38
|
|
|
39
|
-
// Initialize the client
|
|
40
|
-
const recal = new RecalClient(
|
|
41
|
-
token: 'recal_your_api_token', // or use RECAL_TOKEN env variable
|
|
42
|
-
url: 'https://api.recal.dev' // optional, defaults to production
|
|
43
|
-
})
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### Authentication
|
|
47
|
-
|
|
48
|
-
The SDK requires a Recal API token. You can provide it in three ways:
|
|
49
|
-
|
|
50
|
-
1. **Direct in constructor** (recommended for server-side apps):
|
|
51
|
-
```typescript
|
|
52
|
-
const recal = new RecalClient({ token: 'recal_your_token' })
|
|
53
|
-
```
|
|
39
|
+
// Initialize the client with token from .env file (RECAL_TOKEN)
|
|
40
|
+
const recal = new RecalClient()
|
|
54
41
|
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
export RECAL_TOKEN="recal_your_token"
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
3. **Function (for dynamic tokens)**:
|
|
61
|
-
```typescript
|
|
42
|
+
// Or manually provide the token
|
|
62
43
|
const recal = new RecalClient({
|
|
63
|
-
token:
|
|
44
|
+
token: "recal_xyz"
|
|
64
45
|
})
|
|
65
46
|
```
|
|
66
47
|
|
|
@@ -72,100 +53,169 @@ const recal = new RecalClient({
|
|
|
72
53
|
|
|
73
54
|
The SDK is organized into logical service modules:
|
|
74
55
|
|
|
75
|
-
- **`calendar`** - Event management and
|
|
56
|
+
- **`calendar`** - Event management and busy queries
|
|
76
57
|
- **`scheduling`** - Availability and booking management
|
|
77
58
|
- **`users`** - User profile and settings
|
|
78
59
|
- **`organizations`** - Team and organization management
|
|
79
60
|
- **`oauth`** - Calendar provider authentication
|
|
80
61
|
|
|
81
|
-
### Providers
|
|
82
|
-
|
|
83
|
-
Recal supports two calendar providers:
|
|
84
|
-
- `google` - Google Calendar
|
|
85
|
-
- `microsoft` - Microsoft Outlook/Office 365
|
|
86
|
-
|
|
87
62
|
### Time Zones
|
|
88
63
|
|
|
89
|
-
All date/time operations support timezone specification via the `timeZone` parameter
|
|
64
|
+
All date/time operations support timezone specification via the `timeZone` parameter.
|
|
90
65
|
|
|
91
66
|
## API Reference
|
|
92
67
|
|
|
68
|
+
> TypeScript note
|
|
69
|
+
>
|
|
70
|
+
> - Use the exported `Provider` enum for provider arguments.
|
|
71
|
+
> - Date/time fields in responses are parsed into `Date` objects at runtime.
|
|
72
|
+
>
|
|
73
|
+
> ```typescript
|
|
74
|
+
> import { Provider } from 'recal-sdk'
|
|
75
|
+
> ```
|
|
76
|
+
|
|
93
77
|
### Calendar Service
|
|
94
78
|
|
|
95
|
-
#### Get
|
|
79
|
+
#### Get Busy Information
|
|
96
80
|
|
|
97
81
|
```typescript
|
|
98
|
-
// Get user's availability
|
|
99
|
-
const
|
|
82
|
+
// Get user's availability (simplest form)
|
|
83
|
+
const busy = await recal.calendar.getBusy(
|
|
84
|
+
'user_id',
|
|
85
|
+
new Date('2024-01-01'),
|
|
86
|
+
new Date('2024-01-07')
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
// Or with optional filters
|
|
90
|
+
const busyFiltered = await recal.calendar.getBusy(
|
|
100
91
|
'user_id',
|
|
101
92
|
new Date('2024-01-01'),
|
|
102
93
|
new Date('2024-01-07'),
|
|
103
|
-
|
|
104
|
-
|
|
94
|
+
{
|
|
95
|
+
provider: 'google', // optional: filter by provider
|
|
96
|
+
timeZone: 'America/New_York', // optional: timezone
|
|
97
|
+
}
|
|
105
98
|
)
|
|
106
99
|
```
|
|
107
100
|
|
|
108
101
|
#### List Events
|
|
109
102
|
|
|
110
103
|
```typescript
|
|
111
|
-
// Get all events in a date range
|
|
104
|
+
// Get all events in a date range (simplest form)
|
|
112
105
|
const events = await recal.calendar.getEvents(
|
|
106
|
+
'user_id',
|
|
107
|
+
new Date('2024-01-01'),
|
|
108
|
+
new Date('2024-01-31')
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
// Or with optional filters
|
|
112
|
+
const eventsFiltered = await recal.calendar.getEvents(
|
|
113
113
|
'user_id',
|
|
114
114
|
new Date('2024-01-01'),
|
|
115
115
|
new Date('2024-01-31'),
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
{
|
|
117
|
+
provider: 'google', // optional: filter by provider
|
|
118
|
+
timeZone: 'Europe/London' // optional: timezone
|
|
119
|
+
}
|
|
118
120
|
)
|
|
119
121
|
```
|
|
120
122
|
|
|
121
123
|
#### Create Event
|
|
122
124
|
|
|
123
125
|
```typescript
|
|
124
|
-
// Create a new event
|
|
125
|
-
const event = await recal.calendar.createEvent(
|
|
126
|
-
'user_id',
|
|
127
|
-
'google',
|
|
128
|
-
'calendar_id',
|
|
129
|
-
{
|
|
130
|
-
|
|
126
|
+
// Create a new event (without optional timezone)
|
|
127
|
+
const event = await recal.calendar.createEvent({
|
|
128
|
+
userId: 'user_id',
|
|
129
|
+
provider: 'google',
|
|
130
|
+
calendarId: 'calendar_id',
|
|
131
|
+
event: {
|
|
132
|
+
subject: 'Team Meeting',
|
|
133
|
+
description: 'Weekly sync',
|
|
134
|
+
start: new Date('2024-01-15T10:00:00Z'),
|
|
135
|
+
end: new Date('2024-01-15T11:00:00Z'),
|
|
136
|
+
attendees: [
|
|
137
|
+
{ email: 'colleague@company.com' }
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// Or with timezone option
|
|
143
|
+
const eventWithTZ = await recal.calendar.createEvent({
|
|
144
|
+
userId: 'user_id',
|
|
145
|
+
provider: 'google',
|
|
146
|
+
calendarId: 'calendar_id',
|
|
147
|
+
event: {
|
|
148
|
+
subject: 'Team Meeting',
|
|
131
149
|
description: 'Weekly sync',
|
|
132
|
-
start:
|
|
133
|
-
end:
|
|
150
|
+
start: new Date('2024-01-15T10:00:00Z'),
|
|
151
|
+
end: new Date('2024-01-15T11:00:00Z'),
|
|
134
152
|
attendees: [
|
|
135
153
|
{ email: 'colleague@company.com' }
|
|
136
154
|
]
|
|
137
155
|
},
|
|
138
|
-
'
|
|
139
|
-
)
|
|
156
|
+
options: { timeZone: 'Europe/Berlin' } // optional
|
|
157
|
+
})
|
|
158
|
+
```
|
|
159
|
+
#### Get Event
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// Get an existing event
|
|
163
|
+
const event = await recal.calendar.getEvent({
|
|
164
|
+
userId: 'user_id',
|
|
165
|
+
provider: 'google',
|
|
166
|
+
calendarId: 'calendar_id',
|
|
167
|
+
eventId: 'event_id',
|
|
168
|
+
options: { timeZone: 'Europe/Berlin' } // optional
|
|
169
|
+
})
|
|
140
170
|
```
|
|
141
171
|
|
|
142
172
|
#### Update Event
|
|
143
173
|
|
|
144
174
|
```typescript
|
|
145
|
-
// Update an existing event
|
|
146
|
-
const updated = await recal.calendar.updateEvent(
|
|
147
|
-
'user_id',
|
|
148
|
-
'google',
|
|
149
|
-
'calendar_id',
|
|
150
|
-
'event_id',
|
|
151
|
-
{
|
|
152
|
-
|
|
153
|
-
start:
|
|
154
|
-
end:
|
|
175
|
+
// Update an existing event (simplest form)
|
|
176
|
+
const updated = await recal.calendar.updateEvent({
|
|
177
|
+
userId: 'user_id',
|
|
178
|
+
provider: 'google',
|
|
179
|
+
calendarId: 'calendar_id',
|
|
180
|
+
eventId: 'event_id',
|
|
181
|
+
event: {
|
|
182
|
+
subject: 'Updated Meeting Title',
|
|
183
|
+
start: new Date('2024-01-15T14:00:00Z'),
|
|
184
|
+
end: new Date('2024-01-15T15:00:00Z')
|
|
155
185
|
}
|
|
156
|
-
)
|
|
186
|
+
})
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
// or with more options
|
|
191
|
+
const updated = await recal.calendar.updateEvent({
|
|
192
|
+
userId: 'user_id',
|
|
193
|
+
provider: 'google',
|
|
194
|
+
calendarId: 'calendar_id',
|
|
195
|
+
eventId: 'event_id',
|
|
196
|
+
event: {
|
|
197
|
+
subject: 'Updated Meeting title',
|
|
198
|
+
description: 'Updated description',
|
|
199
|
+
start: new Date('2024-01-15T11:00:00Z'),
|
|
200
|
+
end: new Date('2024-01-15T12:00:00Z'),
|
|
201
|
+
attendees: [
|
|
202
|
+
{ email: 'colleague@company.com' }
|
|
203
|
+
]
|
|
204
|
+
},
|
|
205
|
+
options: { timeZone: 'Europe/Berlin' } // optional
|
|
206
|
+
})
|
|
157
207
|
```
|
|
158
208
|
|
|
159
209
|
#### Delete Event
|
|
160
210
|
|
|
161
211
|
```typescript
|
|
162
212
|
// Delete an event
|
|
163
|
-
await recal.calendar.deleteEvent(
|
|
164
|
-
'user_id',
|
|
165
|
-
'google',
|
|
166
|
-
'calendar_id',
|
|
167
|
-
'event_id'
|
|
168
|
-
)
|
|
213
|
+
await recal.calendar.deleteEvent({
|
|
214
|
+
userId: 'user_id',
|
|
215
|
+
provider: 'google',
|
|
216
|
+
calendarId: 'calendar_id',
|
|
217
|
+
eventId: 'event_id'
|
|
218
|
+
})
|
|
169
219
|
```
|
|
170
220
|
|
|
171
221
|
#### Cross-Calendar Operations (Meta Events)
|
|
@@ -173,22 +223,41 @@ await recal.calendar.deleteEvent(
|
|
|
173
223
|
Meta events allow you to work with events across multiple calendar providers:
|
|
174
224
|
|
|
175
225
|
```typescript
|
|
176
|
-
// Create event across all connected calendars
|
|
226
|
+
// Create event across all connected calendars (default behavior)
|
|
177
227
|
const metaEvent = await recal.calendar.createEventByMetaId(
|
|
178
228
|
'user_id',
|
|
179
229
|
{
|
|
180
|
-
|
|
181
|
-
start:
|
|
182
|
-
end:
|
|
230
|
+
subject: 'Cross-platform meeting',
|
|
231
|
+
start: new Date('2024-01-20T15:00:00Z'),
|
|
232
|
+
end: new Date('2024-01-20T16:00:00Z')
|
|
233
|
+
}
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
// Or specify which providers and timezone to use
|
|
237
|
+
const metaEventSpecific = await recal.calendar.createEventByMetaId(
|
|
238
|
+
'user_id',
|
|
239
|
+
{
|
|
240
|
+
subject: 'Cross-platform meeting',
|
|
241
|
+
start: new Date('2024-01-20T15:00:00Z'),
|
|
242
|
+
end: new Date('2024-01-20T16:00:00Z')
|
|
183
243
|
},
|
|
184
|
-
|
|
244
|
+
{
|
|
245
|
+
provider: ['google', 'microsoft'], // Create on specific providers
|
|
246
|
+
timeZone: 'Europe/Berlin' // optional
|
|
247
|
+
}
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
// Get event across all connected calendars (default behavior)
|
|
251
|
+
const metaEventGet = await recal.calendar.getEventByMetaId(
|
|
252
|
+
'user_id',
|
|
253
|
+
metaEvent.metaId
|
|
185
254
|
)
|
|
186
255
|
|
|
187
256
|
// Update across all calendars using meta ID
|
|
188
257
|
await recal.calendar.updateEventByMetaId(
|
|
189
258
|
'user_id',
|
|
190
259
|
metaEvent.metaId,
|
|
191
|
-
{
|
|
260
|
+
{ subject: 'Updated title' }
|
|
192
261
|
)
|
|
193
262
|
|
|
194
263
|
// Delete from all calendars
|
|
@@ -200,136 +269,318 @@ await recal.calendar.deleteEventByMetaId(
|
|
|
200
269
|
|
|
201
270
|
### Scheduling Service
|
|
202
271
|
|
|
203
|
-
#### Get Availability
|
|
272
|
+
#### Get User Availability (Basic)
|
|
204
273
|
|
|
205
274
|
```typescript
|
|
206
|
-
// Find available time slots
|
|
207
|
-
const availability = await recal.scheduling.
|
|
275
|
+
// Find available time slots (minimal config)
|
|
276
|
+
const availability = await recal.scheduling.userSchedulingBasic(
|
|
277
|
+
'user_id',
|
|
278
|
+
new Date('2024-01-15'),
|
|
279
|
+
new Date('2024-01-20'),
|
|
280
|
+
{
|
|
281
|
+
slotDuration: 30 // Only required: slot duration in minutes
|
|
282
|
+
}
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
// Or with more options
|
|
286
|
+
const availabilityDetailed = await recal.scheduling.userSchedulingBasic(
|
|
208
287
|
'user_id',
|
|
209
288
|
new Date('2024-01-15'),
|
|
210
289
|
new Date('2024-01-20'),
|
|
211
290
|
{
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
291
|
+
slotDuration: 30, // Duration of each slot in minutes
|
|
292
|
+
padding: 0, // Padding between slots
|
|
293
|
+
earliestTimeEachDay: '09:00', // Format: HH:mm
|
|
294
|
+
latestTimeEachDay: '17:00', // Format: HH:mm
|
|
295
|
+
provider: 'google', // optional: filter by provider
|
|
296
|
+
timeZone: 'America/New_York' // optional
|
|
216
297
|
}
|
|
217
298
|
)
|
|
218
299
|
```
|
|
219
300
|
|
|
220
|
-
####
|
|
301
|
+
#### Get User Availability (Advanced)
|
|
221
302
|
|
|
222
303
|
```typescript
|
|
223
|
-
//
|
|
224
|
-
const
|
|
304
|
+
// Find available time slots with custom schedules
|
|
305
|
+
const schedules = [
|
|
306
|
+
{
|
|
307
|
+
days: ['monday'], // Monday
|
|
308
|
+
start: '09:00',
|
|
309
|
+
end: '17:00'
|
|
310
|
+
},
|
|
311
|
+
// ... more schedule rules
|
|
312
|
+
]
|
|
313
|
+
|
|
314
|
+
// Minimal config
|
|
315
|
+
const availability = await recal.scheduling.userSchedulingAdvanced(
|
|
225
316
|
'user_id',
|
|
317
|
+
schedules,
|
|
318
|
+
new Date('2024-01-15'),
|
|
319
|
+
new Date('2024-01-20'),
|
|
320
|
+
{ slotDuration: 30 } // Only required option
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
// Or with more options
|
|
324
|
+
const availabilityDetailed = await recal.scheduling.userSchedulingAdvanced(
|
|
325
|
+
'user_id',
|
|
326
|
+
schedules,
|
|
327
|
+
new Date('2024-01-15'),
|
|
328
|
+
new Date('2024-01-20'),
|
|
329
|
+
{
|
|
330
|
+
slotDuration: 30,
|
|
331
|
+
padding: 15,
|
|
332
|
+
provider: 'google', // optional
|
|
333
|
+
timeZone: 'America/New_York' // optional
|
|
334
|
+
}
|
|
335
|
+
)
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
#### Get Organization-Wide Availability
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
// Find organization-wide available time slots (minimal)
|
|
342
|
+
const orgAvailability = await recal.scheduling.getOrgWideAvailability(
|
|
343
|
+
'org-slug',
|
|
344
|
+
new Date('2024-01-15'),
|
|
345
|
+
new Date('2024-01-20'),
|
|
346
|
+
{ slotDuration: 60 } // Only required option
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
// Or with constraints
|
|
350
|
+
const orgAvailabilityConstrained = await recal.scheduling.getOrgWideAvailability(
|
|
351
|
+
'org-slug',
|
|
352
|
+
new Date('2024-01-15'),
|
|
353
|
+
new Date('2024-01-20'),
|
|
226
354
|
{
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
355
|
+
slotDuration: 60,
|
|
356
|
+
padding: 0,
|
|
357
|
+
earliestTimeEachDay: '09:00',
|
|
358
|
+
latestTimeEachDay: '17:00',
|
|
359
|
+
provider: ['google', 'microsoft'], // optional
|
|
360
|
+
timeZone: 'America/New_York' // optional
|
|
231
361
|
}
|
|
232
362
|
)
|
|
233
363
|
```
|
|
234
364
|
|
|
235
365
|
### Users Service
|
|
236
366
|
|
|
237
|
-
#### Get User
|
|
367
|
+
#### Get User
|
|
238
368
|
|
|
239
369
|
```typescript
|
|
240
|
-
// Get user information
|
|
241
|
-
const user = await recal.users.
|
|
242
|
-
|
|
370
|
+
// Get user information (basic)
|
|
371
|
+
const user = await recal.users.get('user_id', {})
|
|
372
|
+
|
|
373
|
+
// Or with additional data
|
|
374
|
+
const userWithDetails = await recal.users.get('user_id', {
|
|
375
|
+
includeOrgs: true, // Include organizations
|
|
376
|
+
includeOAuth: true // Include OAuth connections
|
|
377
|
+
})
|
|
378
|
+
console.log(user.id)
|
|
243
379
|
```
|
|
244
380
|
|
|
245
|
-
#### List
|
|
381
|
+
#### List All Users
|
|
246
382
|
|
|
247
383
|
```typescript
|
|
248
|
-
// Get all
|
|
249
|
-
const
|
|
250
|
-
includeOrganization: true
|
|
251
|
-
})
|
|
384
|
+
// Get all users
|
|
385
|
+
const users = await recal.users.listAll()
|
|
252
386
|
```
|
|
253
387
|
|
|
254
|
-
####
|
|
388
|
+
#### Create User
|
|
255
389
|
|
|
256
390
|
```typescript
|
|
257
|
-
//
|
|
258
|
-
await recal.users.
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
391
|
+
// Create a new user (without organizations)
|
|
392
|
+
const user = await recal.users.create('user_id')
|
|
393
|
+
|
|
394
|
+
// Or with organization memberships
|
|
395
|
+
const userWithOrgs = await recal.users.create(
|
|
396
|
+
'user_id',
|
|
397
|
+
['org-slug-1', 'org-slug-2'] // optional: organization slugs
|
|
398
|
+
)
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
#### Update User
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
// Update user ID
|
|
405
|
+
const updatedUser = await recal.users.update('old_user_id', {
|
|
406
|
+
id: 'new_user_id'
|
|
265
407
|
})
|
|
266
408
|
```
|
|
267
409
|
|
|
410
|
+
#### Delete User
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
// Delete a user
|
|
414
|
+
const deletedUser = await recal.users.delete('user_id')
|
|
415
|
+
```
|
|
416
|
+
|
|
268
417
|
### Organizations Service
|
|
269
418
|
|
|
270
419
|
#### Get Organization
|
|
271
420
|
|
|
272
421
|
```typescript
|
|
273
422
|
// Get organization by slug
|
|
274
|
-
const org = await recal.organizations.
|
|
275
|
-
|
|
276
|
-
|
|
423
|
+
const org = await recal.organizations.get('acme-corp')
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
#### List All Organizations
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
// Get all organizations
|
|
430
|
+
const orgs = await recal.organizations.listAll()
|
|
431
|
+
|
|
432
|
+
// Get organizations for a specific user
|
|
433
|
+
const userOrgs = await recal.organizations.listAllFromUser('user_id')
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
#### Create Organization
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
// Create a new organization
|
|
440
|
+
const org = await recal.organizations.create(
|
|
441
|
+
'acme-corp', // slug
|
|
442
|
+
'Acme Corporation' // name
|
|
443
|
+
)
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
#### Update Organization
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
// Update organization
|
|
450
|
+
const updated = await recal.organizations.update('acme-corp', {
|
|
451
|
+
slug: 'new-slug',
|
|
452
|
+
name: 'New Name'
|
|
277
453
|
})
|
|
278
454
|
```
|
|
279
455
|
|
|
280
|
-
####
|
|
456
|
+
#### Manage Members
|
|
281
457
|
|
|
282
458
|
```typescript
|
|
283
459
|
// Get all members
|
|
284
|
-
const members = await recal.organizations.getMembers('
|
|
460
|
+
const members = await recal.organizations.getMembers('acme-corp')
|
|
461
|
+
|
|
462
|
+
// Add members
|
|
463
|
+
await recal.organizations.addMembers(
|
|
464
|
+
'acme-corp',
|
|
465
|
+
['user_id_1', 'user_id_2']
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
// Remove members
|
|
469
|
+
await recal.organizations.removeMembers(
|
|
470
|
+
'acme-corp',
|
|
471
|
+
['user_id_1', 'user_id_2']
|
|
472
|
+
)
|
|
285
473
|
```
|
|
286
474
|
|
|
287
|
-
#### Organization-Wide
|
|
475
|
+
#### Organization-Wide Busy
|
|
288
476
|
|
|
289
477
|
```typescript
|
|
290
|
-
// Get team availability
|
|
291
|
-
const teamBusy = await recal.calendar.
|
|
478
|
+
// Get team availability (simplest form)
|
|
479
|
+
const teamBusy = await recal.calendar.getOrgWideBusy(
|
|
480
|
+
'acme-corp',
|
|
481
|
+
new Date('2024-01-15'),
|
|
482
|
+
new Date('2024-01-20'),
|
|
483
|
+
true // primaryOnly: only check primary calendars
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
// Or with optional filters
|
|
487
|
+
const teamBusyFiltered = await recal.calendar.getOrgWideBusy(
|
|
292
488
|
'acme-corp',
|
|
293
489
|
new Date('2024-01-15'),
|
|
294
490
|
new Date('2024-01-20'),
|
|
295
491
|
true, // primaryOnly: only check primary calendars
|
|
296
|
-
|
|
492
|
+
{
|
|
493
|
+
provider: 'google', // optional: filter by provider
|
|
494
|
+
timeZone: 'America/New_York' // optional
|
|
495
|
+
}
|
|
297
496
|
)
|
|
298
497
|
```
|
|
299
498
|
|
|
300
499
|
### OAuth Service
|
|
301
500
|
|
|
302
|
-
####
|
|
501
|
+
#### Get OAuth Link
|
|
303
502
|
|
|
304
503
|
```typescript
|
|
305
|
-
//
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
504
|
+
// Get OAuth authorization URL (with defaults)
|
|
505
|
+
const link = await recal.oauth.getLink(
|
|
506
|
+
'user_id',
|
|
507
|
+
'google'
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
// Or with custom options
|
|
511
|
+
const linkWithOptions = await recal.oauth.getLink(
|
|
512
|
+
'user_id',
|
|
513
|
+
'google',
|
|
514
|
+
{
|
|
515
|
+
scope: 'edit', // 'edit' or 'free-busy' (for OAuth scopes)
|
|
516
|
+
accessType: 'offline', // 'offline' or 'online'
|
|
517
|
+
redirectUrl: 'https://app.example.com/callback' // optional
|
|
518
|
+
}
|
|
519
|
+
)
|
|
520
|
+
console.log(link.url) // Use this URL to redirect user
|
|
312
521
|
```
|
|
313
522
|
|
|
314
|
-
####
|
|
523
|
+
#### Get Multiple OAuth Links
|
|
315
524
|
|
|
316
525
|
```typescript
|
|
317
|
-
//
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
526
|
+
// Get OAuth URLs for all providers (simplest)
|
|
527
|
+
const links = await recal.oauth.getBulkLinks('user_id')
|
|
528
|
+
|
|
529
|
+
// Or with specific providers and options
|
|
530
|
+
const linksFiltered = await recal.oauth.getBulkLinks(
|
|
531
|
+
'user_id',
|
|
532
|
+
{
|
|
533
|
+
provider: ['google', 'microsoft'],
|
|
534
|
+
scope: 'edit',
|
|
535
|
+
accessType: 'offline'
|
|
536
|
+
}
|
|
537
|
+
)
|
|
323
538
|
```
|
|
324
539
|
|
|
325
|
-
####
|
|
540
|
+
#### Manage OAuth Connections
|
|
326
541
|
|
|
327
542
|
```typescript
|
|
328
|
-
//
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
543
|
+
// Get all OAuth connections for a user
|
|
544
|
+
const connections = await recal.oauth.getAllConnections(
|
|
545
|
+
'user_id',
|
|
546
|
+
true // redacted (default: true)
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
// Get specific provider connection
|
|
550
|
+
const googleConnection = await recal.oauth.getConnection(
|
|
551
|
+
'user_id',
|
|
552
|
+
'google',
|
|
553
|
+
false // redacted
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
// Set OAuth tokens manually
|
|
557
|
+
const connection = await recal.oauth.setConnection(
|
|
558
|
+
'user_id',
|
|
559
|
+
'google',
|
|
560
|
+
{
|
|
561
|
+
accessToken: 'access_token',
|
|
562
|
+
refreshToken: 'refresh_token', // optional
|
|
563
|
+
scope: ['calendar.events', 'calendar.readonly'],
|
|
564
|
+
expiresAt: new Date('2024-12-31'), // optional
|
|
565
|
+
email: 'user@example.com' // optional
|
|
566
|
+
}
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
// Disconnect a provider
|
|
570
|
+
await recal.oauth.disconnect('user_id', 'google')
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
#### Verify OAuth Callback
|
|
574
|
+
|
|
575
|
+
```typescript
|
|
576
|
+
// Verify OAuth code from callback
|
|
577
|
+
const result = await recal.oauth.verify(
|
|
578
|
+
'google',
|
|
579
|
+
'auth_code_from_callback',
|
|
580
|
+
'edit', // 'edit' or 'free-busy' - single scope, not array
|
|
581
|
+
'state_parameter',
|
|
582
|
+
'https://app.example.com/callback' // optional
|
|
583
|
+
)
|
|
333
584
|
```
|
|
334
585
|
|
|
335
586
|
## Advanced Usage
|
|
@@ -347,12 +598,12 @@ import {
|
|
|
347
598
|
} from 'recal-sdk'
|
|
348
599
|
|
|
349
600
|
try {
|
|
350
|
-
const event = await recal.calendar.getEvent(
|
|
351
|
-
'user_id',
|
|
352
|
-
'google',
|
|
353
|
-
'calendar_id',
|
|
354
|
-
'event_id'
|
|
355
|
-
)
|
|
601
|
+
const event = await recal.calendar.getEvent({
|
|
602
|
+
userId: 'user_id',
|
|
603
|
+
provider: 'google',
|
|
604
|
+
calendarId: 'calendar_id',
|
|
605
|
+
eventId: 'event_id'
|
|
606
|
+
})
|
|
356
607
|
} catch (error) {
|
|
357
608
|
if (error instanceof UserNotFoundError) {
|
|
358
609
|
console.log('User does not exist:', error.userId)
|
|
@@ -367,7 +618,7 @@ try {
|
|
|
367
618
|
### Batch Operations
|
|
368
619
|
|
|
369
620
|
```typescript
|
|
370
|
-
// Process multiple users' calendars
|
|
621
|
+
// Process multiple users' calendars (simplest form)
|
|
371
622
|
const userIds = ['user1', 'user2', 'user3']
|
|
372
623
|
const allEvents = await Promise.all(
|
|
373
624
|
userIds.map(userId =>
|
|
@@ -383,36 +634,41 @@ const allEvents = await Promise.all(
|
|
|
383
634
|
### Working with Multiple Providers
|
|
384
635
|
|
|
385
636
|
```typescript
|
|
386
|
-
//
|
|
637
|
+
// Get all busy data (without filtering)
|
|
638
|
+
const startDate = new Date('2024-01-01')
|
|
639
|
+
const endDate = new Date('2024-01-31')
|
|
640
|
+
|
|
641
|
+
const allBusy = await recal.calendar.getBusy(
|
|
642
|
+
'user_id',
|
|
643
|
+
startDate,
|
|
644
|
+
endDate
|
|
645
|
+
)
|
|
646
|
+
|
|
647
|
+
// Or aggregate by specific providers
|
|
387
648
|
const providers: Provider[] = ['google', 'microsoft']
|
|
388
649
|
const busyTimes = await Promise.all(
|
|
389
|
-
providers.map(provider =>
|
|
390
|
-
recal.calendar.
|
|
650
|
+
providers.map(provider =>
|
|
651
|
+
recal.calendar.getBusy(
|
|
391
652
|
'user_id',
|
|
392
653
|
startDate,
|
|
393
654
|
endDate,
|
|
394
|
-
provider
|
|
655
|
+
{ provider } // filter by specific provider
|
|
395
656
|
)
|
|
396
657
|
)
|
|
397
658
|
)
|
|
398
659
|
|
|
399
|
-
//
|
|
400
|
-
|
|
660
|
+
// Process the busy times as needed for your application
|
|
661
|
+
// Each element is Busy = TimeRange[]; flatten into a single array of TimeRange
|
|
662
|
+
const allBusyPeriods = busyTimes.flat()
|
|
401
663
|
```
|
|
402
664
|
|
|
403
665
|
### Custom Request Configuration
|
|
404
666
|
|
|
405
667
|
```typescript
|
|
406
|
-
// Use custom
|
|
668
|
+
// Use custom base URL
|
|
407
669
|
const recal = new RecalClient({
|
|
408
670
|
token: 'recal_token',
|
|
409
|
-
url: 'https://api.recal.dev',
|
|
410
|
-
requestConfig: {
|
|
411
|
-
timeout: 30000, // 30 seconds
|
|
412
|
-
headers: {
|
|
413
|
-
'X-Custom-Header': 'value'
|
|
414
|
-
}
|
|
415
|
-
}
|
|
671
|
+
url: 'https://api.recal.dev' // optional, this is the default
|
|
416
672
|
})
|
|
417
673
|
```
|
|
418
674
|
|
|
@@ -422,34 +678,42 @@ const recal = new RecalClient({
|
|
|
422
678
|
|
|
423
679
|
```typescript
|
|
424
680
|
// 1. Check availability
|
|
425
|
-
const
|
|
681
|
+
const availability = await recal.scheduling.userSchedulingBasic(
|
|
426
682
|
'consultant_id',
|
|
427
683
|
new Date(),
|
|
428
684
|
new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // Next 7 days
|
|
429
685
|
{
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
686
|
+
slotDuration: 60, // 60-minute slots
|
|
687
|
+
padding: 15, // 15-minute padding between slots
|
|
688
|
+
earliestTimeEachDay: '09:00',
|
|
689
|
+
latestTimeEachDay: '17:00',
|
|
690
|
+
timeZone: 'America/New_York'
|
|
434
691
|
}
|
|
435
692
|
)
|
|
436
693
|
|
|
437
694
|
// 2. Display available slots to user
|
|
438
|
-
const availableSlots =
|
|
695
|
+
const availableSlots = availability.availableSlots // Already filtered for availability
|
|
439
696
|
|
|
440
|
-
// 3.
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
697
|
+
// 3. User selects a slot and provides their information
|
|
698
|
+
const selectedSlot = availableSlots[0] // Example: first available slot
|
|
699
|
+
const clientName = 'John Doe'
|
|
700
|
+
const clientEmail = 'john@example.com'
|
|
701
|
+
|
|
702
|
+
// 4. Create an event for the selected slot (using calendar service)
|
|
703
|
+
const booking = await recal.calendar.createEvent({
|
|
704
|
+
userId: 'consultant_id',
|
|
705
|
+
provider: 'google',
|
|
706
|
+
calendarId: 'primary',
|
|
707
|
+
event: {
|
|
708
|
+
subject: 'Consultation with ' + clientName,
|
|
709
|
+
description: 'Initial consultation',
|
|
444
710
|
start: selectedSlot.start,
|
|
445
711
|
end: selectedSlot.end,
|
|
446
|
-
|
|
447
|
-
description: 'Initial consultation',
|
|
448
|
-
attendees: [clientEmail]
|
|
712
|
+
attendees: [{ email: clientEmail }]
|
|
449
713
|
}
|
|
450
|
-
)
|
|
714
|
+
})
|
|
451
715
|
|
|
452
|
-
//
|
|
716
|
+
// 5. Send confirmation
|
|
453
717
|
console.log('Booking confirmed:', booking.id)
|
|
454
718
|
```
|
|
455
719
|
|
|
@@ -458,29 +722,36 @@ console.log('Booking confirmed:', booking.id)
|
|
|
458
722
|
```typescript
|
|
459
723
|
// Sync events between providers
|
|
460
724
|
async function syncCalendars(userId: string) {
|
|
461
|
-
// Get events from
|
|
725
|
+
// Get all events from all providers
|
|
726
|
+
const allEvents = await recal.calendar.getEvents(
|
|
727
|
+
userId,
|
|
728
|
+
new Date(),
|
|
729
|
+
new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
// Or get events from Google only
|
|
462
733
|
const googleEvents = await recal.calendar.getEvents(
|
|
463
734
|
userId,
|
|
464
735
|
new Date(),
|
|
465
736
|
new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
|
|
466
|
-
'google'
|
|
737
|
+
{ provider: 'google' }
|
|
467
738
|
)
|
|
468
739
|
|
|
469
740
|
// Copy to Microsoft calendar
|
|
470
741
|
for (const event of googleEvents) {
|
|
471
742
|
if (!event.metaId) { // Not already synced
|
|
472
|
-
await recal.calendar.createEvent(
|
|
743
|
+
await recal.calendar.createEvent({
|
|
473
744
|
userId,
|
|
474
|
-
'microsoft',
|
|
475
|
-
'primary',
|
|
476
|
-
{
|
|
477
|
-
|
|
745
|
+
provider: 'microsoft',
|
|
746
|
+
calendarId: 'primary',
|
|
747
|
+
event: {
|
|
748
|
+
subject: event.subject,
|
|
478
749
|
description: event.description,
|
|
479
750
|
start: event.start,
|
|
480
751
|
end: event.end,
|
|
481
752
|
attendees: event.attendees
|
|
482
753
|
}
|
|
483
|
-
)
|
|
754
|
+
})
|
|
484
755
|
}
|
|
485
756
|
}
|
|
486
757
|
}
|
|
@@ -496,32 +767,40 @@ async function findTeamSlot(
|
|
|
496
767
|
startDate: Date,
|
|
497
768
|
endDate: Date
|
|
498
769
|
) {
|
|
499
|
-
// Get
|
|
500
|
-
const
|
|
501
|
-
includeUsers: true
|
|
502
|
-
})
|
|
503
|
-
|
|
504
|
-
// Get everyone's busy times
|
|
505
|
-
const busyTimes = await recal.calendar.getOrgWideFreeBusy(
|
|
770
|
+
// Option 1: Get raw busy times for manual processing
|
|
771
|
+
const busyTimes = await recal.calendar.getOrgWideBusy(
|
|
506
772
|
orgSlug,
|
|
507
773
|
startDate,
|
|
508
774
|
endDate,
|
|
509
|
-
true
|
|
775
|
+
true // Only check primary calendars
|
|
510
776
|
)
|
|
777
|
+
// Process busyTimes array to find gaps for your needs
|
|
511
778
|
|
|
512
|
-
//
|
|
513
|
-
const
|
|
779
|
+
// Option 2: Use the scheduling service (recommended)
|
|
780
|
+
const availability = await recal.scheduling.getOrgWideAvailability(
|
|
781
|
+
orgSlug,
|
|
782
|
+
startDate,
|
|
783
|
+
endDate,
|
|
784
|
+
{
|
|
785
|
+
slotDuration: duration,
|
|
786
|
+
padding: 0,
|
|
787
|
+
earliestTimeEachDay: '09:00',
|
|
788
|
+
latestTimeEachDay: '17:00'
|
|
789
|
+
}
|
|
790
|
+
)
|
|
514
791
|
|
|
515
|
-
|
|
792
|
+
// Returns ready-to-use available time slots
|
|
793
|
+
return availability.availableSlots
|
|
516
794
|
}
|
|
517
795
|
```
|
|
518
796
|
|
|
519
|
-
## Development
|
|
797
|
+
## SDK Development
|
|
520
798
|
|
|
521
799
|
### Prerequisites
|
|
522
800
|
|
|
523
|
-
- Node.js 18+ or Bun 1.0
|
|
801
|
+
- Node.js 18+ or Bun 1.0+, or any other JavaScript runtime
|
|
524
802
|
- TypeScript 5.0+
|
|
803
|
+
- Biome 2.1.2 (for contributing)
|
|
525
804
|
|
|
526
805
|
### Setup
|
|
527
806
|
|
|
@@ -553,17 +832,27 @@ src/
|
|
|
553
832
|
│ └── oauth.service.ts
|
|
554
833
|
├── entities/ # Domain models
|
|
555
834
|
│ ├── user.ts
|
|
556
|
-
│
|
|
557
|
-
│ └── event.ts
|
|
835
|
+
│ └── organization.ts
|
|
558
836
|
├── types/ # TypeScript type definitions
|
|
559
837
|
│ ├── calendar.types.ts
|
|
560
838
|
│ ├── scheduling.types.ts
|
|
561
|
-
│
|
|
839
|
+
│ ├── internal.types.ts
|
|
840
|
+
│ └── oauth.types.ts
|
|
562
841
|
├── typebox/ # Runtime validation schemas (auto-generated)
|
|
842
|
+
│ ├── calendar.tb.ts
|
|
843
|
+
│ ├── scheduling.tb.ts
|
|
844
|
+
│ ├── oauth.tb.ts
|
|
845
|
+
│ ├── organization.tb.ts
|
|
846
|
+
│ ├── user.tb.ts
|
|
847
|
+
│ ├── timeString.tb.ts
|
|
848
|
+
│ ├── organization.stripped.tb.ts
|
|
849
|
+
│ └── user.stripped.tb.ts
|
|
563
850
|
├── utils/ # Helper utilities
|
|
564
851
|
│ ├── fetch.helper.ts
|
|
852
|
+
│ ├── fetchErrorHandler.ts
|
|
565
853
|
│ ├── includes.helper.ts
|
|
566
|
-
│
|
|
854
|
+
│ ├── functionize.ts
|
|
855
|
+
│ └── omit.ts
|
|
567
856
|
└── errors.ts # Custom error classes
|
|
568
857
|
```
|
|
569
858
|
|
|
@@ -589,7 +878,7 @@ bun run check:fix
|
|
|
589
878
|
bun test
|
|
590
879
|
|
|
591
880
|
# Run specific test file
|
|
592
|
-
bun test tests/
|
|
881
|
+
bun test tests/integrations/users.test.ts
|
|
593
882
|
|
|
594
883
|
# Run with coverage
|
|
595
884
|
bun test --coverage
|