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.
Files changed (78) hide show
  1. package/README.md +488 -199
  2. package/dist/entities/organization.d.ts +2 -2
  3. package/dist/entities/organization.d.ts.map +1 -1
  4. package/dist/entities/organization.js +32 -0
  5. package/dist/entities/organization.js.map +1 -0
  6. package/dist/entities/user.d.ts +2 -2
  7. package/dist/entities/user.d.ts.map +1 -1
  8. package/dist/entities/user.js +45 -0
  9. package/dist/entities/user.js.map +1 -0
  10. package/dist/errors.js +36 -0
  11. package/dist/errors.js.map +1 -0
  12. package/dist/index.d.ts +7 -3
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +52 -7046
  15. package/dist/index.js.map +1 -1
  16. package/dist/services/calendar.service.d.ts +82 -35
  17. package/dist/services/calendar.service.d.ts.map +1 -1
  18. package/dist/services/calendar.service.js +292 -0
  19. package/dist/services/calendar.service.js.map +1 -0
  20. package/dist/services/oauth.service.d.ts +2 -1
  21. package/dist/services/oauth.service.d.ts.map +1 -1
  22. package/dist/services/oauth.service.js +168 -0
  23. package/dist/services/oauth.service.js.map +1 -0
  24. package/dist/services/organizations.service.d.ts +1 -1
  25. package/dist/services/organizations.service.d.ts.map +1 -1
  26. package/dist/services/organizations.service.js +132 -0
  27. package/dist/services/organizations.service.js.map +1 -0
  28. package/dist/services/scheduling.service.d.ts +32 -29
  29. package/dist/services/scheduling.service.d.ts.map +1 -1
  30. package/dist/services/scheduling.service.js +122 -0
  31. package/dist/services/scheduling.service.js.map +1 -0
  32. package/dist/services/users.service.js +93 -0
  33. package/dist/services/users.service.js.map +1 -0
  34. package/dist/typebox/calendar.tb.d.ts +7 -6
  35. package/dist/typebox/calendar.tb.d.ts.map +1 -1
  36. package/dist/typebox/calendar.tb.js +92 -0
  37. package/dist/typebox/calendar.tb.js.map +1 -0
  38. package/dist/typebox/oauth.tb.d.ts +2 -2
  39. package/dist/typebox/oauth.tb.js +16 -0
  40. package/dist/typebox/oauth.tb.js.map +1 -0
  41. package/dist/typebox/organization.stripped.tb.js +7 -0
  42. package/dist/typebox/organization.stripped.tb.js.map +1 -0
  43. package/dist/typebox/organization.tb.js +8 -0
  44. package/dist/typebox/organization.tb.js.map +1 -0
  45. package/dist/typebox/scheduling.tb.js +118 -0
  46. package/dist/typebox/scheduling.tb.js.map +1 -0
  47. package/dist/typebox/timeString.tb.js +7 -0
  48. package/dist/typebox/timeString.tb.js.map +1 -0
  49. package/dist/typebox/user.stripped.tb.js +6 -0
  50. package/dist/typebox/user.stripped.tb.js.map +1 -0
  51. package/dist/typebox/user.tb.d.ts +1 -1
  52. package/dist/typebox/user.tb.js +10 -0
  53. package/dist/typebox/user.tb.js.map +1 -0
  54. package/dist/types/calendar.types.d.ts +5 -7
  55. package/dist/types/calendar.types.d.ts.map +1 -1
  56. package/dist/types/calendar.types.js +27 -56
  57. package/dist/types/calendar.types.js.map +1 -1
  58. package/dist/types/internal.types.js +2 -0
  59. package/dist/types/internal.types.js.map +1 -0
  60. package/dist/types/oauth.types.d.ts +1 -1
  61. package/dist/types/oauth.types.d.ts.map +1 -1
  62. package/dist/types/oauth.types.js +2 -0
  63. package/dist/types/oauth.types.js.map +1 -0
  64. package/dist/types/scheduling.types.js +2 -0
  65. package/dist/types/scheduling.types.js.map +1 -0
  66. package/dist/utils/fetch.helper.d.ts +1 -1
  67. package/dist/utils/fetch.helper.d.ts.map +1 -1
  68. package/dist/utils/fetch.helper.js +93 -0
  69. package/dist/utils/fetch.helper.js.map +1 -0
  70. package/dist/utils/fetchErrorHandler.js +32 -0
  71. package/dist/utils/fetchErrorHandler.js.map +1 -0
  72. package/dist/utils/functionize.js +6 -0
  73. package/dist/utils/functionize.js.map +1 -0
  74. package/dist/utils/includes.helper.js +10 -0
  75. package/dist/utils/includes.helper.js.map +1 -0
  76. package/dist/utils/omit.js +4 -0
  77. package/dist/utils/omit.js.map +1 -0
  78. 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, free/busy queries, scheduling, and more
14
- - **Organization Management**: Handle teams and multi-user calendar scenarios
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
- 2. **Environment variable**:
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: () => getTokenFromSecureStore()
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 free/busy queries
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 or `x-timezone` header.
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 Free/Busy Information
79
+ #### Get Busy Information
96
80
 
97
81
  ```typescript
98
- // Get user's availability
99
- const freeBusy = await recal.calendar.getFreeBusy(
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
- 'google', // optional: filter by provider
104
- 'America/New_York' // optional: timezone
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
- ['google', 'microsoft'], // optional: multiple providers
117
- 'Europe/London'
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
- summary: 'Team Meeting',
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: { dateTime: '2024-01-15T10:00:00Z' },
133
- end: { dateTime: '2024-01-15T11:00:00Z' },
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
- 'America/Los_Angeles'
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
- summary: 'Updated Meeting Title',
153
- start: { dateTime: '2024-01-15T14:00:00Z' },
154
- end: { dateTime: '2024-01-15T15:00:00Z' }
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
- summary: 'Cross-platform meeting',
181
- start: { dateTime: '2024-01-20T15:00:00Z' },
182
- end: { dateTime: '2024-01-20T16:00:00Z' }
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
- ['google', 'microsoft'] // Create on both providers
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
- { summary: 'Updated title' }
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.getAvailability(
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
- duration: 30, // 30-minute slots
213
- interval: 15, // 15-minute intervals
214
- startTime: '09:00',
215
- endTime: '17:00'
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
- #### Book Time Slot
301
+ #### Get User Availability (Advanced)
221
302
 
222
303
  ```typescript
223
- // Book an available slot
224
- const booking = await recal.scheduling.bookSlot(
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
- start: '2024-01-15T10:00:00Z',
228
- end: '2024-01-15T10:30:00Z',
229
- title: 'Consultation',
230
- attendees: ['client@example.com']
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 Profile
367
+ #### Get User
238
368
 
239
369
  ```typescript
240
- // Get user information
241
- const user = await recal.users.getUser('user_id')
242
- console.log(user.email, user.name)
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 User's Calendars
381
+ #### List All Users
246
382
 
247
383
  ```typescript
248
- // Get all connected calendars
249
- const calendars = await recal.users.getCalendars('user_id', {
250
- includeOrganization: true
251
- })
384
+ // Get all users
385
+ const users = await recal.users.listAll()
252
386
  ```
253
387
 
254
- #### Update User Settings
388
+ #### Create User
255
389
 
256
390
  ```typescript
257
- // Update user preferences
258
- await recal.users.updateSettings('user_id', {
259
- defaultCalendarId: 'calendar_123',
260
- timezone: 'America/New_York',
261
- workingHours: {
262
- start: '09:00',
263
- end: '17:00'
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.getOrganization('acme-corp', {
275
- includeUsers: true,
276
- includeSettings: true
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
- #### List Organization Members
456
+ #### Manage Members
281
457
 
282
458
  ```typescript
283
459
  // Get all members
284
- const members = await recal.organizations.getMembers('org_id')
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 Free/Busy
475
+ #### Organization-Wide Busy
288
476
 
289
477
  ```typescript
290
- // Get team availability
291
- const teamBusy = await recal.calendar.getOrgWideFreeBusy(
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
- 'google'
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
- #### Generate OAuth URL
501
+ #### Get OAuth Link
303
502
 
304
503
  ```typescript
305
- // Create OAuth authorization URL
306
- const authUrl = await recal.oauth.generateAuthUrl({
307
- provider: 'google',
308
- userId: 'user_id',
309
- redirectUri: 'https://app.example.com/callback',
310
- scopes: ['calendar.events', 'calendar.readonly']
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
- #### Exchange OAuth Code
523
+ #### Get Multiple OAuth Links
315
524
 
316
525
  ```typescript
317
- // Exchange authorization code for tokens
318
- const tokens = await recal.oauth.exchangeCode({
319
- provider: 'google',
320
- code: 'auth_code_from_callback',
321
- redirectUri: 'https://app.example.com/callback'
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
- #### Refresh OAuth Token
540
+ #### Manage OAuth Connections
326
541
 
327
542
  ```typescript
328
- // Refresh expired token
329
- const newTokens = await recal.oauth.refreshToken({
330
- provider: 'google',
331
- refreshToken: 'stored_refresh_token'
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
- // Aggregate availability across providers
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.getFreeBusy(
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
- // Merge busy periods
400
- const merged = mergeBusyPeriods(busyTimes)
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 headers or timeout
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 slots = await recal.scheduling.getAvailability(
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
- duration: 60,
431
- interval: 30,
432
- startTime: '09:00',
433
- endTime: '17:00'
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 = slots.filter(slot => !slot.busy)
695
+ const availableSlots = availability.availableSlots // Already filtered for availability
439
696
 
440
- // 3. Book selected slot
441
- const booking = await recal.scheduling.bookSlot(
442
- 'consultant_id',
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
- title: 'Consultation with ' + clientName,
447
- description: 'Initial consultation',
448
- attendees: [clientEmail]
712
+ attendees: [{ email: clientEmail }]
449
713
  }
450
- )
714
+ })
451
715
 
452
- // 4. Send confirmation
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 Google
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
- summary: event.summary,
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 organization members
500
- const org = await recal.organizations.getOrganization(orgSlug, {
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 // Only check primary calendars
775
+ true // Only check primary calendars
510
776
  )
777
+ // Process busyTimes array to find gaps for your needs
511
778
 
512
- // Find gaps where everyone is free
513
- const freeSlots = findFreeSlots(busyTimes, duration)
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
- return freeSlots
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
- │ ├── organization.ts
557
- │ └── event.ts
835
+ │ └── organization.ts
558
836
  ├── types/ # TypeScript type definitions
559
837
  │ ├── calendar.types.ts
560
838
  │ ├── scheduling.types.ts
561
- │ └── internal.types.ts
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
- │ └── functionize.ts
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/calendar.test.ts
881
+ bun test tests/integrations/users.test.ts
593
882
 
594
883
  # Run with coverage
595
884
  bun test --coverage