← All @molecule/* packages · App templates

@molecule/api-calendar

Core interface · calendar · API (Node) · v1.0.1 · Apache-2.0

Calendar core interface for molecule.dev — OAuth-backed calendar bonds (Google, Microsoft, iCloud)

npm install @molecule/api-calendar

npm · Source on GitHub

How it works

@molecule/api-calendar is the calendar core interface on the API (Node) side: the API your app calls, with no vendor inside.

Choose the implementation by bonding one of its 2 providers: @molecule/api-calendar-google, @molecule/api-calendar-microsoft.

import { setProvider, listEvents } from '@molecule/api-calendar'
import { provider } from '@molecule/api-calendar-google'

setProvider(provider)

const { data, credentials } = await listEvents(userCreds, 'primary', {
  timeMin: '2026-05-01T00:00:00Z',
  timeMax: '2026-05-08T00:00:00Z',
})

if (credentials) {
  await persistRefreshedCredentials(userId, credentials)
}

Providers (2): @molecule/api-calendar-google, @molecule/api-calendar-microsoft

Works with: @molecule/api-bond, @molecule/api-i18n

Reference

Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit src/index.ts JSDoc, not this file.

Calendar core interface for molecule.dev (server-side).

Defines a stack-neutral contract for OAuth-backed calendar bonds (Google, Microsoft, iCloud, etc.) consumed by handlers and background jobs. Bond a provider at startup, then call the convenience wrappers from anywhere.

Quick Start

import { setProvider, listEvents } from '@molecule/api-calendar'
import { provider } from '@molecule/api-calendar-google'

setProvider(provider)

const { data, credentials } = await listEvents(userCreds, 'primary', {
  timeMin: '2026-05-01T00:00:00Z',
  timeMax: '2026-05-08T00:00:00Z',
})

if (credentials) {
  await persistRefreshedCredentials(userId, credentials)
}

Type

core

Installation

npm install @molecule/api-calendar @molecule/api-bond @molecule/api-i18n

API

Interfaces

BusyBlock

A contiguous busy block returned from a free/busy query.

interface BusyBlock {
  /** ISO 8601 start of the busy block. */
  start: string
  /** ISO 8601 end of the busy block. */
  end: string
  /** Originating calendar id. */
  calendarId: string
}

CalendarEvent

A calendar event.

Times are ISO 8601 strings. For all-day events, prefer date-only strings (YYYY-MM-DD) and set {@link CalendarEvent.allDay} to true.

interface CalendarEvent {
  /** Provider-specific event identifier. May be undefined for new events. */
  id?: string
  /** Event title (caller is responsible for any i18n on user-visible text). */
  summary: string
  /** Optional rich description / notes. */
  description?: string
  /** Event location string. */
  location?: string
  /** ISO 8601 start (date-time, or `YYYY-MM-DD` if all-day). */
  start: string
  /** ISO 8601 end (date-time, or `YYYY-MM-DD` if all-day). */
  end: string
  /** IANA time zone for the start/end values. */
  timeZone?: string
  /** Whether this event spans entire day(s). Defaults to `false`. */
  allDay?: boolean
  /** Optional attendee list. */
  attendees?: EventAttendee[]
  /** Provider-specific status flag (`confirmed`, `tentative`, `cancelled`). */
  status?: 'confirmed' | 'tentative' | 'cancelled' | string
  /** Conferencing / meeting link, if present. */
  hangoutLink?: string
  /** Arbitrary provider data (kept for round-tripping). */
  metadata?: Record<string, unknown>
}

CalendarOperationResult

Wrapper returned from every {@link CalendarProvider} method so callers can persist refreshed credentials when the provider rotates them.

interface CalendarOperationResult<T> {
  /** Operation payload. `void` operations return `undefined`. */
  data: T
  /**
   * Refreshed credentials, present iff the access token was rotated during
   * this call. Callers MUST persist these so subsequent calls succeed.
   */
  credentials?: CalendarUserCredentials
}

CalendarProvider

Calendar provider contract.

Every method takes user-scoped {@link CalendarUserCredentials} so the same provider instance can serve multiple users. Implementations are responsible for refreshing expired tokens transparently and returning any updated credentials via {@link CalendarOperationResult.credentials}.

interface CalendarProvider {
  /**
   * Lists the calendars accessible to the authenticated user.
   *
   * @param credentials - The user's OAuth credentials.
   * @returns The user's calendar list and any refreshed credentials.
   */
  listCalendars(
    credentials: CalendarUserCredentials,
  ): Promise<CalendarOperationResult<CalendarSummary[]>>

  /**
   * Lists events on a specific calendar within a time window.
   *
   * @param credentials - The user's OAuth credentials.
   * @param calendarId - Provider-specific calendar id.
   * @param options - Time range and paging options.
   */
  listEvents(
    credentials: CalendarUserCredentials,
    calendarId: string,
    options: ListEventsOptions,
  ): Promise<CalendarOperationResult<CalendarEvent[]>>

  /**
   * Creates a new event on a calendar.
   *
   * @param credentials - The user's OAuth credentials.
   * @param calendarId - Provider-specific calendar id.
   * @param event - Event payload (id is ignored; provider assigns one).
   */
  createEvent(
    credentials: CalendarUserCredentials,
    calendarId: string,
    event: CalendarEvent,
  ): Promise<CalendarOperationResult<CalendarEvent>>

  /**
   * Updates an existing event by id.
   *
   * @param credentials - The user's OAuth credentials.
   * @param calendarId - Provider-specific calendar id.
   * @param eventId - Event id to update.
   * @param updates - Partial event payload to merge.
   */
  updateEvent(
    credentials: CalendarUserCredentials,
    calendarId: string,
    eventId: string,
    updates: Partial<Omit<CalendarEvent, 'id'>>,
  ): Promise<CalendarOperationResult<CalendarEvent>>

  /**
   * Deletes an event by id.
   *
   * @param credentials - The user's OAuth credentials.
   * @param calendarId - Provider-specific calendar id.
   * @param eventId - Event id to delete.
   */
  deleteEvent(
    credentials: CalendarUserCredentials,
    calendarId: string,
    eventId: string,
  ): Promise<CalendarOperationResult<void>>

  /**
   * Computes free slots across one or more calendars.
   *
   * @param credentials - The user's OAuth credentials.
   * @param calendarIds - One or more provider-specific calendar ids.
   * @param options - Time window and slot duration.
   */
  findFreeSlots(
    credentials: CalendarUserCredentials,
    calendarIds: string[],
    options: FindFreeSlotsOptions,
  ): Promise<CalendarOperationResult<FreeBusyResult>>
}

CalendarSummary

A single calendar in a user's calendar list.

interface CalendarSummary {
  /** Provider-specific calendar identifier (opaque). */
  id: string
  /** Human-readable calendar name. */
  summary: string
  /** Optional longer description. */
  description?: string
  /** IANA time zone, e.g. `America/Los_Angeles`. */
  timeZone?: string
  /** Whether this calendar is the user's primary calendar. */
  primary?: boolean
  /** Access role granted to the authenticated user (provider-specific). */
  accessRole?: string
}

CalendarUserCredentials

OAuth token bundle for a single user. Bonds use these to call the provider's API on behalf of that user, refreshing the access token automatically when it expires.

interface CalendarUserCredentials {
  /** Current access token. */
  accessToken: string
  /** Refresh token used to mint new access tokens. */
  refreshToken: string
  /** Optional epoch-millis timestamp for the access token's expiry. */
  expiresAt?: number
}

EventAttendee

Attendee information attached to a calendar event.

interface EventAttendee {
  /** Attendee email address. */
  email: string
  /** Optional display name. */
  displayName?: string
  /** Whether attendance is optional. */
  optional?: boolean
  /** RSVP response status (provider-specific values). */
  responseStatus?: 'needsAction' | 'declined' | 'tentative' | 'accepted' | string
}

FindFreeSlotsOptions

Query parameters for the free/busy / free-slot computation.

interface FindFreeSlotsOptions {
  /** ISO 8601 lower bound (inclusive). */
  timeMin: string
  /** ISO 8601 upper bound (exclusive). */
  timeMax: string
  /** Required slot duration in minutes. */
  durationMinutes: number
  /** Optional IANA time zone for the returned slot boundaries. */
  timeZone?: string
}

FreeBusyResult

Result of a free/busy lookup plus computed free slots.

interface FreeBusyResult {
  /** Aggregated busy blocks across all queried calendars. */
  busy: BusyBlock[]
  /** Computed free slots that fit `durationMinutes`. */
  freeSlots: FreeSlot[]
}

FreeSlot

A free slot of {@link FindFreeSlotsOptions.durationMinutes} length.

interface FreeSlot {
  /** ISO 8601 slot start. */
  start: string
  /** ISO 8601 slot end. */
  end: string
}

ListEventsOptions

Query parameters for listing events on a calendar.

interface ListEventsOptions {
  /** ISO 8601 lower bound (inclusive). */
  timeMin: string
  /** ISO 8601 upper bound (exclusive). */
  timeMax: string
  /** Maximum number of events to return. Provider-defined cap when omitted. */
  maxResults?: number
  /** When true, recurring events are returned as individual instances. */
  singleEvents?: boolean
  /** Optional sort order — `startTime` is provider-supported by Google. */
  orderBy?: 'startTime' | 'updated'
}

Functions

createEvent(credentials, calendarId, event)

Convenience wrapper that delegates to the bonded provider.

function createEvent(
  credentials: CalendarUserCredentials,
  calendarId: string,
  event: CalendarEvent,
): Promise<CalendarOperationResult<CalendarEvent>>
  • credentials — The user's OAuth credentials.
  • calendarId — Provider-specific calendar id.
  • event — Event payload.

deleteEvent(credentials, calendarId, eventId)

Convenience wrapper that delegates to the bonded provider.

function deleteEvent(
  credentials: CalendarUserCredentials,
  calendarId: string,
  eventId: string,
): Promise<CalendarOperationResult<void>>
  • credentials — The user's OAuth credentials.
  • calendarId — Provider-specific calendar id.
  • eventId — Event id to delete.

findFreeSlots(credentials, calendarIds, options)

Convenience wrapper that delegates to the bonded provider.

function findFreeSlots(
  credentials: CalendarUserCredentials,
  calendarIds: string[],
  options: FindFreeSlotsOptions,
): Promise<CalendarOperationResult<FreeBusyResult>>
  • credentials — The user's OAuth credentials.
  • calendarIds — One or more provider-specific calendar ids.
  • options — Time window and slot duration.

getOptionalProvider()

Retrieves the bonded calendar provider, returning null if none is bonded.

function getOptionalProvider(): CalendarProvider | null

Returns: The bonded calendar provider, or null.

getProvider()

Retrieves the bonded calendar provider, throwing if none is configured.

function getProvider(): CalendarProvider

Returns: The bonded calendar provider.

hasProvider()

Checks whether a calendar provider is currently bonded.

function hasProvider(): boolean

Returns: true if a calendar provider is bonded.

listCalendars(credentials)

Convenience wrapper that delegates to the bonded provider.

function listCalendars(
  credentials: CalendarUserCredentials,
): Promise<CalendarOperationResult<CalendarSummary[]>>
  • credentials — The user's OAuth credentials.

Returns: The user's calendar list and any refreshed credentials.

listEvents(credentials, calendarId, options)

Convenience wrapper that delegates to the bonded provider.

function listEvents(
  credentials: CalendarUserCredentials,
  calendarId: string,
  options: ListEventsOptions,
): Promise<CalendarOperationResult<CalendarEvent[]>>
  • credentials — The user's OAuth credentials.
  • calendarId — Provider-specific calendar id.
  • options — Time range and paging options.

setProvider(provider)

Registers a calendar provider as the active singleton. Called by bond packages (e.g. @molecule/api-calendar-google) during app startup.

function setProvider(provider: CalendarProvider): void
  • provider — The calendar provider implementation to bond.

updateEvent(credentials, calendarId, eventId, updates)

Convenience wrapper that delegates to the bonded provider.

function updateEvent(
  credentials: CalendarUserCredentials,
  calendarId: string,
  eventId: string,
  updates: Partial<Omit<CalendarEvent, 'id'>>,
): Promise<CalendarOperationResult<CalendarEvent>>
  • credentials — The user's OAuth credentials.
  • calendarId — Provider-specific calendar id.
  • eventId — Event id to update.
  • updates — Partial event payload to merge.

Available Providers

ProviderPackage
Google Calendar@molecule/api-calendar-google
Microsoft Calendar@molecule/api-calendar-microsoft

Injection Notes

Requirements

Peer dependencies:

  • @molecule/api-bond ^1.0.1
  • @molecule/api-i18n ^1.0.1

Runtime Dependencies

  • @molecule/api-bond

  • @molecule/api-i18n

  • Always persist rotated credentials. Every operation returns CalendarOperationResult<T>{ data, credentials? }. When credentials is present the provider refreshed the user's OAuth tokens: SAVE them over the stored ones immediately, or the user's next call fails with an expired/revoked token.

  • Credentials are PER-USER OAuth tokens (accessToken/refreshToken obtained by the app's OAuth flow with calendar scopes) — load the CALLING user's stored tokens for every call. Never share one user's credentials across users, and never send them to the client.

  • Calendar ids are provider-specific (many providers accept a default id such as 'primary') — discover them via listCalendars(credentials) instead of hardcoding.

  • All wrappers throw when no provider is bonded (setProvider at startup); times are ISO 8601 strings.

E2E Tests

Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:

  • Creating an event through the UI (title + start/end) persists it and it appears on the calendar view / event list at the right date AND time — reload the page and it is still there.
  • Editing or moving an event (new time, new title) reflects immediately in the view, and deleting one removes it from both the view and the list.
  • Timezone is correct: an event created for a local time shows at THAT local time, not shifted by hours — events carry an IANA timeZone and ISO 8601 start/end, so a UTC/offset bug surfaces as a wrong displayed hour.
  • If the app surfaces availability / free slots (findFreeSlots), a slot that overlaps an existing event no longer shows as free.
  • External-OAuth caveat: bonds sync to a real Google/Microsoft/iCloud calendar via per-user OAuth, which the sandbox usually cannot drive — verify against the app's OWN stored events (its DB-backed calendar), not the live external provider.
  • Authorization: a user sees and edits only their own events — no UI or endpoint returns or mutates another user's calendar/event by id, and the per-user OAuth credentials never reach the client.