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.
- package/README.md +638 -0
- 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
|
+
[](https://www.npmjs.com/package/recal-sdk)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](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