Helpers
The fetch client, error mapping, options parsing, time zone and recurrence helpers in @cal-sdk/provider-utils.
Most of a provider is the same few chores: authenticated JSON requests, mapping HTTP errors, time zone arithmetic and recurrence rules. @cal-sdk/provider-utils holds the helpers the official providers use for them, so you can skip the parts that are easy to get subtly wrong.
npm install @cal-sdk/provider-utilsRequests
| Export | What it does |
|---|---|
createJsonFetcher({ baseURL, accessToken, headers?, fetch? }) | A JSON client. Sends Authorization: Bearer, resolving the token per request; validates responses with a schema; throws APICallError for non-2xx responses and network failures; never follows redirects |
urlPath | A template tag that percent-encodes every value: urlPath`/calendars/${id}/events` |
mapAPICallError(error, { provider, resource?, eventId? }) | Turns 401 into AuthenticationError, 403 into PermissionDeniedError, 404 and 410 into NotFoundError (given resource), 412 into ConflictError (given eventId) and 429 into RateLimitError. Other errors pass through unchanged |
parseRetryAfterMs(headers) | Reads Retry-After as milliseconds |
combineHeaders, withUserAgentSuffix | Merge header objects, and add your provider to the user agent |
Options and schemas
| Export | What it does |
|---|---|
parseProviderOptions({ provider, providerOptions, schema }) | Reads your key from providerOptions. Throws InvalidArgumentError for wrong values, warns about unknown keys |
validateSchema(schema, value) | Validates with any Standard Schema library, such as zod |
formatSchemaIssues(issues) | A readable message from schema issues |
Time
| Export | What it does |
|---|---|
isAllDay(time) | Whether an EventTime is all-day |
eventTimeToInstant(time, allDayTimeZone?) | Epoch milliseconds for an EventTime. All-day dates are read in allDayTimeZone, UTC by default |
addDaysToDate(date, days) | Adds days to a 'YYYY-MM-DD' date |
formatZonedDateTime(instant, zone) | An ISO 8601 date-time with the zone's offset, such as '2026-11-09T09:30:00-05:00' |
localToInstant, instantToLocal, parseLocalDateTime, formatLocalDateTime, formatLocalDate | Convert between wall-clock times in a zone and instants |
getTimeZoneOffset(instant, zone) | A zone's offset at an instant |
isValidTimeZone(zone) | Whether a string is an IANA zone |
windowsToIana, ianaToWindows | Convert Windows zone names, as Outlook uses them |
Recurrence
| Export | What it does |
|---|---|
parseRecurrence(lines) | Splits RFC 5545 lines into the rule, EXDATEs and RDATEs. Throws InvalidArgumentError for malformed lines |
parseRRule, formatRRule | Read and write one RRULE as an object |
parseICalDate, formatICalDate, instantToICalDate, instantToICalDateOnly | iCalendar date and date-time values |
formatExdates, formatRdates | Build EXDATE and RDATE lines |
WEEKDAYS | ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'] |
Each function's doc comment in the package has its exact signature.