Skip to content

feat(providers): tighten sdks types, add pagination, and typed errors - #468

Merged
JeanMeijer merged 1 commit into
mainfrom
feat/tighten-sdks-types-add-pagination-and-typed-errors
Jul 22, 2026
Merged

feat(providers): tighten sdks types, add pagination, and typed errors#468
JeanMeijer merged 1 commit into
mainfrom
feat/tighten-sdks-types-add-pagination-and-typed-errors

Conversation

@JeanMeijer

@JeanMeijer JeanMeijer commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

Description

Briefly describe what you did and why.

Screenshots / Recordings

Add screenshots or recordings here to help reviewers understand your changes.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • UI/UX update
  • Docs update
  • Refactor / Cleanup

Related Areas

  • Authentication
  • Calendar UI
  • Data/API
  • Docs

Testing

  • Manual testing performed
  • Cross-browser testing (if UI changes)
  • Mobile responsiveness verified (if UI changes)

Checklist

  • I’ve read the CONTRIBUTING guide
  • My code works and is understandable and follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in complex areas
  • I have updated the documentation
  • Any dependent changes are merged and published

Notes

(Optional) Add anything else you'd like to share.

By submitting, I confirm I understand and stand behind this code. If AI was used, I’ve reviewed and verified everything myself.


Summary by cubic

Tightened provider SDK types, added pagination helpers, and introduced typed errors across @analog/google-calendar, @analog/microsoft-calendar, and @analog/google-people. Wired attendee notifications end-to-end with a sendUpdate flag; Microsoft calendars always notify.

  • New Features

    • Microsoft Graph pagination: added listMore/deltaMore across calendars, calendar views, events, instances, extensions, attachments, calendar permissions, and groups.
    • Typed errors with richer shapes for Google Calendar, Google People, and Microsoft; consistent Connection/Timeout handling.
    • Stronger SDK types:
      • Google Calendar: event labels and labelProperties, stricter ACL role/scope enums, typed channel stop, required busy arrays in free/busy, header injection support.
      • Google People: stricter inputs (e.g., personFields and "people/me" resource name), ContactGroups.get, batch delete returns void, refined sources.
      • Microsoft: typed OData errors, safer absolute @odata.nextLink handling, required scheduleId in free/busy results.
    • sendUpdate support: API and providers accept sendUpdate on create/update (default true); dialogs and menu disable “save without notifying” for Microsoft using provider-aware gating.
    • Update flow: diff against a frozen form snapshot with If-Match etag to avoid overwriting remote edits; update queue returns the provider’s canonical event to advance local baselines.
    • RSVP safety: omit unchanged SELF RSVP from patches to prevent providers from rewriting the user’s response on save; notification intent rides on top-level sendUpdate.
    • Form UX: renamed isAllDay to allDay, preserved response in form state, and added deferred field patching so drags/RSVPs apply cleanly in dirty forms.
    • Sync and meetings: safer Google syncToken handling and emission of “cancelled” items for deleted recurring instances; Google Meet continues via events.patch with conferenceDataVersion=1.
  • Migration

    • Providers API
      • events.create and events.update accept optional sendUpdate (default true); pass false to suppress notifications where allowed.
      • Patch semantics: null clears a field; undefined leaves it unchanged.
      • notifications.unsubscribe now requires id and resourceId.
    • Update queues
      • replace requests can include a previous snapshot; diffs and If-Match use it to 412 on true conflicts.
      • onSuccess returns the canonical event so callers can advance their baseline.
    • UI/Forms
      • Rename isAllDay to allDay; update schema and usages.
      • The form carries response (SELF attendee) for accurate RSVP diffs.
      • “Save without notifying” is disabled for Microsoft calendars.
    • Types
      • Google Calendar/People: stricter request/response types and enums (e.g., channel stop requires id/resourceId, calendar list items always an array, free/busy busy always present; People personFields required and resourceName constrained).
      • Microsoft: new listMore/deltaMore methods; scheduleId required in free/busy responses; adopt new headers parameters where applicable.

Written for commit c0d1302. Summary will update on new commits.

Review in cubic

@vercel

vercel Bot commented Jul 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
analog Ready Ready Preview, Comment Jul 19, 2026 4:00pm

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

15 issues found and verified against the latest diff

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/web/src/components/calendar/flows/update-event/update-event-attendee-dialog.tsx">

<violation number="1" location="apps/web/src/components/calendar/flows/update-event/update-event-attendee-dialog.tsx:25">
P3: The Microsoft notification check and disabled Save behavior are now repeated verbatim across all attendee dialogs, so future provider-policy changes can leave one flow inconsistent with the others. A shared notification-choice component or helper for the provider rule would keep create, update, and delete behavior aligned.</violation>
</file>

<file name="packages/providers/src/interfaces/calendars.ts">

<violation number="1" location="packages/providers/src/interfaces/calendars.ts:26">
P2: The new typed-error variant is not produced by either free/busy adapter: Microsoft still rejects on `info.error`, while Google ignores `calendar.errors` and returns an empty `busy` result. This leaves the public typed-error path dead and causes per-calendar failures to be rejected or misreported as no availability; the adapters should normalize and return this shape, or the variant should be removed.</violation>
</file>

<file name="packages/providers/src/calendars/google-calendar/events/index.ts">

<violation number="1" location="packages/providers/src/calendars/google-calendar/events/index.ts:296">
P2: Updating events with completed or partial Google conference data can fail or drop the existing conference: `toGoogleCalendarEventInput(existingEvent)` does not preserve completed `createRequest` data and assumes `entryPoints` exists. Align the conversion with `parseConferenceData` (only treat non-success requests as pending and handle missing entry points) before sending the full update.</violation>
</file>

<file name="packages/providers/src/conferencing/google-meet.ts">

<violation number="1" location="packages/providers/src/conferencing/google-meet.ts:32">
P3: Conference creation now consumes more Google Calendar quota per call: `events.patch` costs 3 quota units, while the previous `get` plus `update` flow costs 2. Retaining the existing get/update sequence, or documenting and accepting this quota tradeoff, avoids a measurable API-quota regression.</violation>
</file>

<file name="packages/google-people/src/error.ts">

<violation number="1" location="packages/google-people/src/error.ts:28">
P2: Consumers handling batch contact failures cannot receive the specialized detail types through `APIError.error`: `details` is restricted to `GooglePeopleErrorDetail[]`, while `BatchCreateContactsErrorDetails` and `BatchUpdateContactsErrorDetails` are excluded. Including these variants in the details union would make the typed error definitions usable without casts.</violation>
</file>

<file name="packages/microsoft-calendar/src/error.ts">

<violation number="1" location="packages/microsoft-calendar/src/error.ts:22">
P2: The public `ODataInnerError` type rejects the string fields Microsoft Graph actually returns and gives consumers the wrong type when reading them. Using `Record<string, unknown>` (or a recursive model with string fields) keeps valid Graph errors assignable and accurately represents the payload.</violation>
</file>

<file name="packages/google-calendar/src/interfaces.ts">

<violation number="1" location="packages/google-calendar/src/interfaces.ts:8">
P3: Consumers cannot pass standard Fetch `HeadersInit` values such as a `Headers` object or tuple list through `GoogleCalendarRequestOptions.headers`. Typing this alias as `HeadersInit` would match the implementation and the Fetch API accepted inputs.</violation>
</file>

<file name="packages/api/src/routers/events.ts">

<violation number="1" location="packages/api/src/routers/events.ts:241">
P2: Microsoft event updates silently ignore the top-level `sendUpdate` value, so a `false` notification choice is not honored for ordinary edits. The provider should either propagate an equivalent control or reject unsupported values instead of accepting the flag as if it worked.</violation>
</file>

<file name="packages/google-calendar/src/freebusy/interfaces.ts">

<violation number="1" location="packages/google-calendar/src/freebusy/interfaces.ts:34">
P2: A valid `fields` partial-response request can omit `calendars` (or nested `busy`/`start`/`end`), but this type now guarantees those properties exist, so consumers can dereference `undefined` at runtime. These response properties should remain optional, or the SDK should model the response from the requested field mask.</violation>
</file>

<file name="packages/providers/src/calendars/google-calendar/events/utils.ts">

<violation number="1" location="packages/providers/src/calendars/google-calendar/events/utils.ts:136">
P2: Existing file attachments can be dropped when an unrelated event update is sent. Since this mapper now includes attachments in the full PUT body, the update request should set `supportsAttachments: true` whenever attachments are preserved.</violation>

<violation number="2" location="packages/providers/src/calendars/google-calendar/events/utils.ts:148">
P2: Events using a Google event label lose that label on any update. The full-replacement input should preserve `eventLabelId` and send `eventLabelVersion: 1` when that field is present.</violation>
</file>

<file name="packages/google-calendar/src/client.ts">

<violation number="1" location="packages/google-calendar/src/client.ts:63">
P2: Per-request `Authorization` and `Content-Type` headers are silently discarded because the SDK defaults overwrite them after construction. Installing each default only when `requestHeaders` does not already contain that key would make the new headers API behave consistently with its other values.</violation>
</file>

<file name="packages/providers/src/interfaces/providers/calendar.ts">

<violation number="1" location="packages/providers/src/interfaces/providers/calendar.ts:77">
P2: Microsoft callers cannot honor `sendUpdate: false`: `MicrosoftCalendarEvents.create` ignores the field and always posts `toMicrosoftEvent(event)`, so Graph sends invitations when attendees are present. The provider should reject unsupported `false` (as `delete` does) or implement an equivalent suppression path before exposing this option in the shared interface.</violation>

<violation number="2" location="packages/providers/src/interfaces/providers/calendar.ts:92">
P1: Deleting a Microsoft event with an `etag` still issues an unconditional DELETE because `MicrosoftCalendarEvents.delete` drops it, even though the SDK accepts `ifMatch` and emits `If-Match`. Passing the value through would make stale reads fail instead of deleting a concurrently changed event.</violation>
</file>

<file name="packages/google-people/src/interfaces.ts">

<violation number="1" location="packages/google-people/src/interfaces.ts:9">
P3: The raw and JSON query contracts now duplicate the same exclusion list, allowing the two definitions to drift. Reusing `GooglePeopleRawQueryParams` from `client.ts` would keep these SDK query types synchronized.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

export interface CalendarProviderEventsDeleteOptions {
calendarId: string;
eventId: string;
etag?: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Deleting a Microsoft event with an etag still issues an unconditional DELETE because MicrosoftCalendarEvents.delete drops it, even though the SDK accepts ifMatch and emits If-Match. Passing the value through would make stale reads fail instead of deleting a concurrently changed event.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/providers/src/interfaces/providers/calendar.ts, line 92:

<comment>Deleting a Microsoft event with an `etag` still issues an unconditional DELETE because `MicrosoftCalendarEvents.delete` drops it, even though the SDK accepts `ifMatch` and emits `If-Match`. Passing the value through would make stale reads fail instead of deleting a concurrently changed event.</comment>

<file context>
@@ -82,11 +83,13 @@ export interface CalendarProviderEventsUpdateOptions {
 export interface CalendarProviderEventsDeleteOptions {
   calendarId: string;
   eventId: string;
+  etag?: string;
   sendUpdate: boolean;
 }
</file context>

Comment thread packages/microsoft-calendar/src/error.ts
Comment thread packages/google-calendar/src/events/interfaces.ts Outdated
Comment thread packages/google-calendar/src/events/interfaces.ts Outdated
Comment thread packages/google-people/src/interfaces.ts Outdated
| undefined;
export type QueryParams = Record<string, QueryParamValue>;

export type GooglePeopleRawQueryParams = QueryParams & {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The raw and JSON query contracts now duplicate the same exclusion list, allowing the two definitions to drift. Reusing GooglePeopleRawQueryParams from client.ts would keep these SDK query types synchronized.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/google-people/src/interfaces.ts, line 9:

<comment>The raw and JSON query contracts now duplicate the same exclusion list, allowing the two definitions to drift. Reusing `GooglePeopleRawQueryParams` from `client.ts` would keep these SDK query types synchronized.</comment>

<file context>
@@ -6,10 +6,25 @@ export type QueryParamValue =
   | undefined;
 export type QueryParams = Record<string, QueryParamValue>;
 
+export type GooglePeopleRawQueryParams = QueryParams & {
+  alt?: never;
+  $alt?: never;
</file context>

snapshot.matches("askNotifyAttendee"),
);
// Microsoft Calendar always notifies attendees, so saving without notifying is not supported.
const notifyRequired = UpdateQueueContext.useSelector(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The Microsoft notification check and disabled Save behavior are now repeated verbatim across all attendee dialogs, so future provider-policy changes can leave one flow inconsistent with the others. A shared notification-choice component or helper for the provider rule would keep create, update, and delete behavior aligned.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/src/components/calendar/flows/update-event/update-event-attendee-dialog.tsx, line 25:

<comment>The Microsoft notification check and disabled Save behavior are now repeated verbatim across all attendee dialogs, so future provider-policy changes can leave one flow inconsistent with the others. A shared notification-choice component or helper for the provider rule would keep create, update, and delete behavior aligned.</comment>

<file context>
@@ -21,6 +21,11 @@ export function UpdateEventAttendeeDialog() {
     snapshot.matches("askNotifyAttendee"),
   );
+  // Microsoft Calendar always notifies attendees, so saving without notifying is not supported.
+  const notifyRequired = UpdateQueueContext.useSelector(
+    (snapshot) =>
+      snapshot.context.item?.event.calendar.provider.id === "microsoft",
</file context>

}

const existingEvent = await this.client.events.get({
const updatedEvent = await this.client.events.patch({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Conference creation now consumes more Google Calendar quota per call: events.patch costs 3 quota units, while the previous get plus update flow costs 2. Retaining the existing get/update sequence, or documenting and accepting this quota tradeoff, avoids a measurable API-quota regression.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/providers/src/conferencing/google-meet.ts, line 32:

<comment>Conference creation now consumes more Google Calendar quota per call: `events.patch` costs 3 quota units, while the previous `get` plus `update` flow costs 2. Retaining the existing get/update sequence, or documenting and accepting this quota tradeoff, avoids a measurable API-quota regression.</comment>

<file context>
@@ -29,15 +29,9 @@ export class GoogleMeetProvider implements ConferencingProvider {
       }
 
-      const existingEvent = await this.client.events.get({
+      const updatedEvent = await this.client.events.patch({
         calendarId,
         eventId,
</file context>

Comment thread packages/google-people/src/client.ts
| null
| undefined;
export type QueryParams = Record<string, QueryParamValue>;
export type RequestHeaders = Record<string, string>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Consumers cannot pass standard Fetch HeadersInit values such as a Headers object or tuple list through GoogleCalendarRequestOptions.headers. Typing this alias as HeadersInit would match the implementation and the Fetch API accepted inputs.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/google-calendar/src/interfaces.ts, line 8:

<comment>Consumers cannot pass standard Fetch `HeadersInit` values such as a `Headers` object or tuple list through `GoogleCalendarRequestOptions.headers`. Typing this alias as `HeadersInit` would match the implementation and the Fetch API accepted inputs.</comment>

<file context>
@@ -5,9 +5,11 @@ export type QueryParamValue =
   | null
   | undefined;
 export type QueryParams = Record<string, QueryParamValue>;
+export type RequestHeaders = Record<string, string>;
 
 export interface GoogleCalendarRequestOptions {
</file context>
Suggested change
export type RequestHeaders = Record<string, string>;
export type RequestHeaders = HeadersInit;

@JeanMeijer
JeanMeijer force-pushed the feat/tighten-sdks-types-add-pagination-and-typed-errors branch from aff161b to c0d1302 Compare July 21, 2026 23:59
@JeanMeijer
JeanMeijer merged commit fd17149 into main Jul 22, 2026
5 checks passed
@JeanMeijer
JeanMeijer deleted the feat/tighten-sdks-types-add-pagination-and-typed-errors branch July 22, 2026 00:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant