import { resolveMicrosoftIntegration, type MicrosoftAccountParams } from './accounts.ts'
import { graphItems, graphRequest, requireConfirmOrDryRun, type JsonObject } from './core.ts'
import { boundedTop } from './validation.ts'
export type CalendarListParams = MicrosoftAccountParams & {
top?: number
}
export type CalendarEventsParams = MicrosoftAccountParams & {
calendarId?: string
startDateTime?: string
endDateTime?: string
top?: number
filter?: string
search?: string
orderBy?: string
select?: string
}
export type CalendarEventGetParams = MicrosoftAccountParams & {
eventId: string
calendarId?: string
select?: string
}
export type CalendarEventMutationParams = MicrosoftAccountParams & {
calendarId?: string
event: JsonObject
dryRun?: boolean
confirm?: boolean
}
export type CalendarEventUpdateParams = CalendarEventMutationParams & {
eventId: string
}
const EVENT_SELECT =
'id,subject,start,end,location,organizer,attendees,isAllDay,showAs,webLink,onlineMeeting,bodyPreview'
function eventsPath(calendarId?: string): string {
return calendarId
? '/me/calendars/' + encodeURIComponent(calendarId) + '/events'
: '/me/events'
}
function calendarViewPath(calendarId?: string): string {
return calendarId
? '/me/calendars/' + encodeURIComponent(calendarId) + '/calendarView'
: '/me/calendarView'
}
/** List calendars the signed-in user can access. */
export async function listCalendars(params: CalendarListParams = {}) {
const data = await graphRequest<JsonObject>({
integration: resolveMicrosoftIntegration(params),
path: '/me/calendars',
query: {
$top: boundedTop(params.top, 20, 100),
$select: 'id,name,color,isDefaultCalendar,canEdit,owner',
},
})
return graphItems<JsonObject>(data)
}
/** List events. Pass startDateTime+endDateTime to use calendarView. */
export async function listEvents(params: CalendarEventsParams = {}) {
const top = boundedTop(params.top, 10)
const select = params.select ?? EVENT_SELECT
const query: Record<string, string | number | undefined> = {
$top: top,
$select: select,
$orderby: params.orderBy,
$filter: params.filter,
}
if (params.search) query.$search = '"' + params.search.replace(/"/g, '\\"') + '"'
const ranged = Boolean(params.startDateTime && params.endDateTime)
if (ranged) {
query.startDateTime = params.startDateTime
query.endDateTime = params.endDateTime
}
const data = await graphRequest<JsonObject>({
integration: resolveMicrosoftIntegration(params),
path: ranged ? calendarViewPath(params.calendarId) : eventsPath(params.calendarId),
query,
})
return graphItems<JsonObject>(data)
}
/** Read one calendar event. */
export async function getEvent(params: CalendarEventGetParams) {
const eventId = params.eventId?.trim()
if (!eventId) throw new Error('getEvent requires eventId.')
const path = params.calendarId
? '/me/calendars/' + encodeURIComponent(params.calendarId) + '/events/' + encodeURIComponent(eventId)
: '/me/events/' + encodeURIComponent(eventId)
return graphRequest<JsonObject>({
integration: resolveMicrosoftIntegration(params),
path,
query: { $select: params.select ?? EVENT_SELECT + ',body' },
})
}
/** Preview or create a calendar event. Creating requires confirm: true. */
export async function createEvent(params: CalendarEventMutationParams) {
if (!params.event || typeof params.event !== 'object') {
throw new Error('createEvent requires event.')
}
const path = eventsPath(params.calendarId)
const mode = requireConfirmOrDryRun({
dryRun: params.dryRun,
confirm: params.confirm,
action: 'createEvent',
})
if (mode.dryRun) {
return { dryRun: true as const, method: 'POST', path, body: params.event }
}
return graphRequest<JsonObject>({
integration: resolveMicrosoftIntegration(params),
method: 'POST',
path,
body: params.event,
})
}
/** Preview or patch a calendar event. Updating requires confirm: true. */
export async function updateEvent(params: CalendarEventUpdateParams) {
const eventId = params.eventId?.trim()
if (!eventId) throw new Error('updateEvent requires eventId.')
if (!params.event || typeof params.event !== 'object') {
throw new Error('updateEvent requires event.')
}
const path = params.calendarId
? '/me/calendars/' + encodeURIComponent(params.calendarId) + '/events/' + encodeURIComponent(eventId)
: '/me/events/' + encodeURIComponent(eventId)
const mode = requireConfirmOrDryRun({
dryRun: params.dryRun,
confirm: params.confirm,
action: 'updateEvent',
})
if (mode.dryRun) {
return { dryRun: true as const, method: 'PATCH', path, body: params.event }
}
return graphRequest<JsonObject>({
integration: resolveMicrosoftIntegration(params),
method: 'PATCH',
path,
body: params.event,
})
}
/**
* Outlook calendar helpers for Microsoft Graph.
* @example
* import calendar from 'kody:@kody/microsoft/calendar'
* const { items } = await calendar().listEvents({ startDateTime: '2026-08-22T00:00:00Z', endDateTime: '2026-08-23T00:00:00Z' })
*/
export default function calendar() {
return { listCalendars, listEvents, getEvent, createEvent, updateEvent }
}