recal-sdk 0.1.0 → 0.1.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 (2) hide show
  1. package/README.md +638 -0
  2. package/package.json +2 -1
package/README.md ADDED
@@ -0,0 +1,638 @@
1
+ # Recal SDK for JavaScript/TypeScript
2
+
3
+ A powerful, type-safe SDK for interacting with the Recal calendar API. Build sophisticated calendar integrations with support for Google and Microsoft calendar providers.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/recal-sdk.svg)](https://www.npmjs.com/package/recal-sdk)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
8
+
9
+ ## Features
10
+
11
+ - **Multi-Provider Support**: Seamlessly work with Google Calendar and Microsoft Outlook
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
15
+ - **OAuth Integration**: Built-in OAuth flow support for calendar connections
16
+ - **Error Handling**: Comprehensive error types for robust applications
17
+ - **Modern Architecture**: Clean, testable service-based design
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ # Using npm
23
+ npm install recal-sdk
24
+
25
+ # Using yarn
26
+ yarn add recal-sdk
27
+
28
+ # Using bun
29
+ bun add recal-sdk
30
+ ```
31
+
32
+ ## Quick Start
33
+
34
+ ### Basic Setup
35
+
36
+ ```typescript
37
+ import { RecalClient } from 'recal-sdk'
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
+ ```
54
+
55
+ 2. **Environment variable**:
56
+ ```bash
57
+ export RECAL_TOKEN="recal_your_token"
58
+ ```
59
+
60
+ 3. **Function (for dynamic tokens)**:
61
+ ```typescript
62
+ const recal = new RecalClient({
63
+ token: () => getTokenFromSecureStore()
64
+ })
65
+ ```
66
+
67
+ > **Security Note**: This SDK is designed for server-side use. Never expose your API token in client-side code.
68
+
69
+ ## Core Concepts
70
+
71
+ ### Services
72
+
73
+ The SDK is organized into logical service modules:
74
+
75
+ - **`calendar`** - Event management and free/busy queries
76
+ - **`scheduling`** - Availability and booking management
77
+ - **`users`** - User profile and settings
78
+ - **`organizations`** - Team and organization management
79
+ - **`oauth`** - Calendar provider authentication
80
+
81
+ ### Providers
82
+
83
+ Recal supports two calendar providers:
84
+ - `google` - Google Calendar
85
+ - `microsoft` - Microsoft Outlook/Office 365
86
+
87
+ ### Time Zones
88
+
89
+ All date/time operations support timezone specification via the `timeZone` parameter or `x-timezone` header.
90
+
91
+ ## API Reference
92
+
93
+ ### Calendar Service
94
+
95
+ #### Get Free/Busy Information
96
+
97
+ ```typescript
98
+ // Get user's availability
99
+ const freeBusy = await recal.calendar.getFreeBusy(
100
+ 'user_id',
101
+ new Date('2024-01-01'),
102
+ new Date('2024-01-07'),
103
+ 'google', // optional: filter by provider
104
+ 'America/New_York' // optional: timezone
105
+ )
106
+ ```
107
+
108
+ #### List Events
109
+
110
+ ```typescript
111
+ // Get all events in a date range
112
+ const events = await recal.calendar.getEvents(
113
+ 'user_id',
114
+ new Date('2024-01-01'),
115
+ new Date('2024-01-31'),
116
+ ['google', 'microsoft'], // optional: multiple providers
117
+ 'Europe/London'
118
+ )
119
+ ```
120
+
121
+ #### Create Event
122
+
123
+ ```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',
131
+ description: 'Weekly sync',
132
+ start: { dateTime: '2024-01-15T10:00:00Z' },
133
+ end: { dateTime: '2024-01-15T11:00:00Z' },
134
+ attendees: [
135
+ { email: 'colleague@company.com' }
136
+ ]
137
+ },
138
+ 'America/Los_Angeles'
139
+ )
140
+ ```
141
+
142
+ #### Update Event
143
+
144
+ ```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' }
155
+ }
156
+ )
157
+ ```
158
+
159
+ #### Delete Event
160
+
161
+ ```typescript
162
+ // Delete an event
163
+ await recal.calendar.deleteEvent(
164
+ 'user_id',
165
+ 'google',
166
+ 'calendar_id',
167
+ 'event_id'
168
+ )
169
+ ```
170
+
171
+ #### Cross-Calendar Operations (Meta Events)
172
+
173
+ Meta events allow you to work with events across multiple calendar providers:
174
+
175
+ ```typescript
176
+ // Create event across all connected calendars
177
+ const metaEvent = await recal.calendar.createEventByMetaId(
178
+ 'user_id',
179
+ {
180
+ summary: 'Cross-platform meeting',
181
+ start: { dateTime: '2024-01-20T15:00:00Z' },
182
+ end: { dateTime: '2024-01-20T16:00:00Z' }
183
+ },
184
+ ['google', 'microsoft'] // Create on both providers
185
+ )
186
+
187
+ // Update across all calendars using meta ID
188
+ await recal.calendar.updateEventByMetaId(
189
+ 'user_id',
190
+ metaEvent.metaId,
191
+ { summary: 'Updated title' }
192
+ )
193
+
194
+ // Delete from all calendars
195
+ await recal.calendar.deleteEventByMetaId(
196
+ 'user_id',
197
+ metaEvent.metaId
198
+ )
199
+ ```
200
+
201
+ ### Scheduling Service
202
+
203
+ #### Get Availability
204
+
205
+ ```typescript
206
+ // Find available time slots
207
+ const availability = await recal.scheduling.getAvailability(
208
+ 'user_id',
209
+ new Date('2024-01-15'),
210
+ new Date('2024-01-20'),
211
+ {
212
+ duration: 30, // 30-minute slots
213
+ interval: 15, // 15-minute intervals
214
+ startTime: '09:00',
215
+ endTime: '17:00'
216
+ }
217
+ )
218
+ ```
219
+
220
+ #### Book Time Slot
221
+
222
+ ```typescript
223
+ // Book an available slot
224
+ const booking = await recal.scheduling.bookSlot(
225
+ 'user_id',
226
+ {
227
+ start: '2024-01-15T10:00:00Z',
228
+ end: '2024-01-15T10:30:00Z',
229
+ title: 'Consultation',
230
+ attendees: ['client@example.com']
231
+ }
232
+ )
233
+ ```
234
+
235
+ ### Users Service
236
+
237
+ #### Get User Profile
238
+
239
+ ```typescript
240
+ // Get user information
241
+ const user = await recal.users.getUser('user_id')
242
+ console.log(user.email, user.name)
243
+ ```
244
+
245
+ #### List User's Calendars
246
+
247
+ ```typescript
248
+ // Get all connected calendars
249
+ const calendars = await recal.users.getCalendars('user_id', {
250
+ includeOrganization: true
251
+ })
252
+ ```
253
+
254
+ #### Update User Settings
255
+
256
+ ```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
+ }
265
+ })
266
+ ```
267
+
268
+ ### Organizations Service
269
+
270
+ #### Get Organization
271
+
272
+ ```typescript
273
+ // Get organization by slug
274
+ const org = await recal.organizations.getOrganization('acme-corp', {
275
+ includeUsers: true,
276
+ includeSettings: true
277
+ })
278
+ ```
279
+
280
+ #### List Organization Members
281
+
282
+ ```typescript
283
+ // Get all members
284
+ const members = await recal.organizations.getMembers('org_id')
285
+ ```
286
+
287
+ #### Organization-Wide Free/Busy
288
+
289
+ ```typescript
290
+ // Get team availability
291
+ const teamBusy = await recal.calendar.getOrgWideFreeBusy(
292
+ 'acme-corp',
293
+ new Date('2024-01-15'),
294
+ new Date('2024-01-20'),
295
+ true, // primaryOnly: only check primary calendars
296
+ 'google'
297
+ )
298
+ ```
299
+
300
+ ### OAuth Service
301
+
302
+ #### Generate OAuth URL
303
+
304
+ ```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
+ })
312
+ ```
313
+
314
+ #### Exchange OAuth Code
315
+
316
+ ```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
+ })
323
+ ```
324
+
325
+ #### Refresh OAuth Token
326
+
327
+ ```typescript
328
+ // Refresh expired token
329
+ const newTokens = await recal.oauth.refreshToken({
330
+ provider: 'google',
331
+ refreshToken: 'stored_refresh_token'
332
+ })
333
+ ```
334
+
335
+ ## Advanced Usage
336
+
337
+ ### Error Handling
338
+
339
+ The SDK provides specific error types for different scenarios:
340
+
341
+ ```typescript
342
+ import {
343
+ UserNotFoundError,
344
+ EventNotFoundError,
345
+ OAuthConnectionNotFoundError,
346
+ OrganizationNotFoundError
347
+ } from 'recal-sdk'
348
+
349
+ try {
350
+ const event = await recal.calendar.getEvent(
351
+ 'user_id',
352
+ 'google',
353
+ 'calendar_id',
354
+ 'event_id'
355
+ )
356
+ } catch (error) {
357
+ if (error instanceof UserNotFoundError) {
358
+ console.log('User does not exist:', error.userId)
359
+ } else if (error instanceof EventNotFoundError) {
360
+ console.log('Event not found:', error.eventId)
361
+ } else if (error instanceof OAuthConnectionNotFoundError) {
362
+ console.log('Calendar not connected:', error.provider)
363
+ }
364
+ }
365
+ ```
366
+
367
+ ### Batch Operations
368
+
369
+ ```typescript
370
+ // Process multiple users' calendars
371
+ const userIds = ['user1', 'user2', 'user3']
372
+ const allEvents = await Promise.all(
373
+ userIds.map(userId =>
374
+ recal.calendar.getEvents(
375
+ userId,
376
+ new Date('2024-01-01'),
377
+ new Date('2024-01-31')
378
+ )
379
+ )
380
+ )
381
+ ```
382
+
383
+ ### Working with Multiple Providers
384
+
385
+ ```typescript
386
+ // Aggregate availability across providers
387
+ const providers: Provider[] = ['google', 'microsoft']
388
+ const busyTimes = await Promise.all(
389
+ providers.map(provider =>
390
+ recal.calendar.getFreeBusy(
391
+ 'user_id',
392
+ startDate,
393
+ endDate,
394
+ provider
395
+ )
396
+ )
397
+ )
398
+
399
+ // Merge busy periods
400
+ const merged = mergeBusyPeriods(busyTimes)
401
+ ```
402
+
403
+ ### Custom Request Configuration
404
+
405
+ ```typescript
406
+ // Use custom headers or timeout
407
+ const recal = new RecalClient({
408
+ 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
+ }
416
+ })
417
+ ```
418
+
419
+ ## Examples
420
+
421
+ ### Building a Booking System
422
+
423
+ ```typescript
424
+ // 1. Check availability
425
+ const slots = await recal.scheduling.getAvailability(
426
+ 'consultant_id',
427
+ new Date(),
428
+ new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // Next 7 days
429
+ {
430
+ duration: 60,
431
+ interval: 30,
432
+ startTime: '09:00',
433
+ endTime: '17:00'
434
+ }
435
+ )
436
+
437
+ // 2. Display available slots to user
438
+ const availableSlots = slots.filter(slot => !slot.busy)
439
+
440
+ // 3. Book selected slot
441
+ const booking = await recal.scheduling.bookSlot(
442
+ 'consultant_id',
443
+ {
444
+ start: selectedSlot.start,
445
+ end: selectedSlot.end,
446
+ title: 'Consultation with ' + clientName,
447
+ description: 'Initial consultation',
448
+ attendees: [clientEmail]
449
+ }
450
+ )
451
+
452
+ // 4. Send confirmation
453
+ console.log('Booking confirmed:', booking.id)
454
+ ```
455
+
456
+ ### Syncing Calendars
457
+
458
+ ```typescript
459
+ // Sync events between providers
460
+ async function syncCalendars(userId: string) {
461
+ // Get events from Google
462
+ const googleEvents = await recal.calendar.getEvents(
463
+ userId,
464
+ new Date(),
465
+ new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
466
+ 'google'
467
+ )
468
+
469
+ // Copy to Microsoft calendar
470
+ for (const event of googleEvents) {
471
+ if (!event.metaId) { // Not already synced
472
+ await recal.calendar.createEvent(
473
+ userId,
474
+ 'microsoft',
475
+ 'primary',
476
+ {
477
+ summary: event.summary,
478
+ description: event.description,
479
+ start: event.start,
480
+ end: event.end,
481
+ attendees: event.attendees
482
+ }
483
+ )
484
+ }
485
+ }
486
+ }
487
+ ```
488
+
489
+ ### Team Scheduling
490
+
491
+ ```typescript
492
+ // Find time when entire team is available
493
+ async function findTeamSlot(
494
+ orgSlug: string,
495
+ duration: number,
496
+ startDate: Date,
497
+ endDate: Date
498
+ ) {
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(
506
+ orgSlug,
507
+ startDate,
508
+ endDate,
509
+ true // Only check primary calendars
510
+ )
511
+
512
+ // Find gaps where everyone is free
513
+ const freeSlots = findFreeSlots(busyTimes, duration)
514
+
515
+ return freeSlots
516
+ }
517
+ ```
518
+
519
+ ## Development
520
+
521
+ ### Prerequisites
522
+
523
+ - Node.js 18+ or Bun 1.0+
524
+ - TypeScript 5.0+
525
+
526
+ ### Setup
527
+
528
+ ```bash
529
+ # Clone the repository
530
+ git clone https://github.com/recal-dev/recal-sdk-js.git
531
+ cd recal-sdk-js
532
+
533
+ # Install dependencies
534
+ bun install
535
+
536
+ # Run tests
537
+ bun test
538
+
539
+ # Build the SDK
540
+ bun run build
541
+ ```
542
+
543
+ ### Project Structure
544
+
545
+ ```
546
+ src/
547
+ ├── index.ts # Main client and exports
548
+ ├── services/ # Service implementations
549
+ │ ├── calendar.service.ts
550
+ │ ├── scheduling.service.ts
551
+ │ ├── users.service.ts
552
+ │ ├── organizations.service.ts
553
+ │ └── oauth.service.ts
554
+ ├── entities/ # Domain models
555
+ │ ├── user.ts
556
+ │ ├── organization.ts
557
+ │ └── event.ts
558
+ ├── types/ # TypeScript type definitions
559
+ │ ├── calendar.types.ts
560
+ │ ├── scheduling.types.ts
561
+ │ └── internal.types.ts
562
+ ├── typebox/ # Runtime validation schemas (auto-generated)
563
+ ├── utils/ # Helper utilities
564
+ │ ├── fetch.helper.ts
565
+ │ ├── includes.helper.ts
566
+ │ └── functionize.ts
567
+ └── errors.ts # Custom error classes
568
+ ```
569
+
570
+ ### Code Style
571
+
572
+ This project uses Biome for formatting and linting:
573
+
574
+ ```bash
575
+ # Format code
576
+ bun run format:fix
577
+
578
+ # Lint code
579
+ bun run lint:fix
580
+
581
+ # Run all checks
582
+ bun run check:fix
583
+ ```
584
+
585
+ ### Testing
586
+
587
+ ```bash
588
+ # Run all tests
589
+ bun test
590
+
591
+ # Run specific test file
592
+ bun test tests/calendar.test.ts
593
+
594
+ # Run with coverage
595
+ bun test --coverage
596
+ ```
597
+
598
+ ## Contributing
599
+
600
+ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
601
+
602
+ ### Development Workflow
603
+
604
+ 1. Fork the repository
605
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
606
+ 3. Make your changes
607
+ 4. Run tests and linting (`bun test && bun run check:fix`)
608
+ 5. Commit your changes (`git commit -m 'Add amazing feature'`)
609
+ 6. Push to your branch (`git push origin feature/amazing-feature`)
610
+ 7. Open a Pull Request
611
+
612
+ ### Reporting Issues
613
+
614
+ Found a bug or have a feature request? Please [open an issue](https://github.com/recal-dev/recal-sdk-js/issues) with:
615
+
616
+ - Clear description
617
+ - Steps to reproduce (for bugs)
618
+ - Expected vs actual behavior
619
+ - SDK version and environment details
620
+
621
+ ## Support
622
+
623
+ - **Documentation**: [https://docs.recal.dev](https://docs.recal.dev)
624
+ - **API Reference**: [https://api.recal.dev/docs](https://api.recal.dev/docs)
625
+ - **Email**: team@recal.dev
626
+ - **Discord**: [Join our community](https://discord.gg/recal)
627
+
628
+ ## License
629
+
630
+ This SDK is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
631
+
632
+ ## Changelog
633
+
634
+ See [CHANGELOG.md](CHANGELOG.md) for a list of changes in each version.
635
+
636
+ ---
637
+
638
+ Built with ❤️ by the [Recal](https://recal.dev) team
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "recal-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Recal SDK",
5
5
  "author": "Recal <team@recal.dev>",
6
+ "contributors": ["tkoehlerlg", "jschwxrz"],
6
7
  "license": "MIT",
7
8
  "main": "dist/index.js",
8
9
  "types": "dist/index.d.ts",