@kubun/connector-google-calendar 0.17.4 → 0.18.0

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/lib/actions.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ActionHandler } from '@kubun/connector';
1
+ import { type ActionHandler } from '@kubun/connector';
2
2
  export type GoogleCalendarActionHandlerConfig = {
3
3
  credential: {
4
4
  accessToken: string;
package/lib/actions.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { carryField, refuseCutLists } from '@kubun/connector';
1
2
  import { createEvent, updateEvent } from './api.js';
2
3
  import { GoogleCalendarError } from './errors.js';
3
4
  import { deriveEventID } from './event-id.js';
@@ -6,8 +7,9 @@ import { deriveEventID } from './event-id.js';
6
7
  * of the (possibly stale) local document. The one exception is how a changed
7
8
  * start/end is written: it keeps the event's all-day form and time zone from
8
9
  * the target document, and a changed time zone or all-day flag rewrites the
9
- * start and end it applies to.
10
- */ function sparsePatchParams(params, existing) {
10
+ * start and end it applies to. A value carried from the target document that
11
+ * was trimmed at sync is refused (`carryField`), never sent as the prefix.
12
+ */ function sparsePatchParams(params, existing, context) {
11
13
  const patch = {
12
14
  ...params
13
15
  };
@@ -21,13 +23,19 @@ import { deriveEventID } from './event-id.js';
21
23
  'endAt'
22
24
  ]){
23
25
  if (patch[field] == null && reshapesTimes && existing?.[field] != null) {
24
- patch[field] = existing[field];
26
+ patch[field] = carryField(existing, field, context);
25
27
  }
26
28
  }
27
29
  const writesTimes = patch.startAt != null || patch.endAt != null;
28
30
  if (writesTimes) {
29
- if (!('allDay' in patch) && existing?.allDay != null) patch.allDay = existing.allDay;
30
- if (!('timeZone' in patch) && existing?.timeZone != null) patch.timeZone = existing.timeZone;
31
+ for (const field of [
32
+ 'allDay',
33
+ 'timeZone'
34
+ ]){
35
+ if (!(field in patch) && existing?.[field] != null) {
36
+ patch[field] = carryField(existing, field, context);
37
+ }
38
+ }
31
39
  }
32
40
  return patch;
33
41
  }
@@ -67,6 +75,7 @@ export function createGoogleCalendarActionHandler(config) {
67
75
  };
68
76
  },
69
77
  async update (_model, sourceID, params, existing, context) {
78
+ refuseCutLists(params, context);
70
79
  // sourceID is the composite calendarID/eventID. The target document
71
80
  // (`existing`) names the event; without it, recover both ids from the
72
81
  // composite — an eventID never contains '/', a calendarID may, so split
@@ -97,7 +106,7 @@ export function createGoogleCalendarActionHandler(config) {
97
106
  accessToken: config.credential.accessToken,
98
107
  calendarID,
99
108
  eventID,
100
- params: sparsePatchParams(params, existing),
109
+ params: sparsePatchParams(params, existing, context),
101
110
  etag: context?.providerETag,
102
111
  fetch: config.fetch
103
112
  });
package/lib/api.d.ts CHANGED
@@ -23,6 +23,25 @@ export type GoogleEventDateTime = {
23
23
  date?: string;
24
24
  timeZone?: string;
25
25
  };
26
+ export type GoogleResponseStatus = 'needsAction' | 'declined' | 'tentative' | 'accepted';
27
+ export type GoogleEventAttendee = {
28
+ email?: string;
29
+ displayName?: string;
30
+ optional?: boolean;
31
+ resource?: boolean;
32
+ organizer?: boolean;
33
+ responseStatus?: GoogleResponseStatus;
34
+ self?: boolean;
35
+ };
36
+ export type GoogleEventOrganizer = {
37
+ email?: string;
38
+ displayName?: string;
39
+ self?: boolean;
40
+ };
41
+ export type GoogleEventReminder = {
42
+ method: 'popup' | 'email';
43
+ minutes: number;
44
+ };
26
45
  export type GoogleEventResource = {
27
46
  id: string;
28
47
  etag?: string;
@@ -33,7 +52,22 @@ export type GoogleEventResource = {
33
52
  end: GoogleEventDateTime;
34
53
  status: 'confirmed' | 'tentative' | 'cancelled';
35
54
  htmlLink?: string;
36
- recurringEventID?: string;
55
+ recurringEventId?: string;
56
+ originalStartTime?: GoogleEventDateTime;
57
+ recurrence?: Array<string>;
58
+ organizer?: GoogleEventOrganizer;
59
+ attendees?: Array<GoogleEventAttendee>;
60
+ reminders?: {
61
+ useDefault?: boolean;
62
+ overrides?: Array<GoogleEventReminder>;
63
+ };
64
+ hangoutLink?: string;
65
+ conferenceData?: {
66
+ entryPoints?: Array<{
67
+ entryPointType?: string;
68
+ uri?: string;
69
+ }>;
70
+ };
37
71
  };
38
72
  export type GoogleEventsListResponse = {
39
73
  items?: Array<GoogleEventResource>;
@@ -49,6 +83,8 @@ export type BuildEventsListURLParams = {
49
83
  syncToken?: string;
50
84
  pageToken?: string;
51
85
  timeMin?: string;
86
+ timeMax?: string;
87
+ showDeleted?: boolean;
52
88
  };
53
89
  export declare function buildEventsListURL(params: BuildEventsListURLParams): string;
54
90
  export declare function mapCalendarToEntity(calendar: GoogleCalendarResource, accountKey?: string): EntityRecord;
@@ -66,6 +102,12 @@ export declare function splitEventSourceID(sourceID: string): {
66
102
  calendarID: string;
67
103
  eventID: string;
68
104
  };
105
+ export declare function fetchEventResource(config: {
106
+ accessToken: string;
107
+ calendarID: string;
108
+ eventID: string;
109
+ fetch?: typeof globalThis.fetch;
110
+ }): Promise<GoogleEventResource>;
69
111
  export declare function getEvent(config: {
70
112
  accessToken: string;
71
113
  calendarID: string;
package/lib/api.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { connectorHTTPError } from '@kubun/connector';
2
2
  import { createGoogleAPIClient } from '@kubun/provider-google';
3
- import { GoogleCalendarEventConflict } from './errors.js';
3
+ import { GoogleCalendarError, GoogleCalendarEventConflict } from './errors.js';
4
4
  import { eventHoldsIntent } from './intent.js';
5
5
  export { classifyHTTPStatus } from '@kubun/connector';
6
6
  const CALENDAR_BASE_URL = 'https://www.googleapis.com/calendar/v3';
@@ -44,24 +44,46 @@ export function buildEventsListURL(params) {
44
44
  url.searchParams.set('singleEvents', 'true');
45
45
  if (params.syncToken != null) {
46
46
  url.searchParams.set('syncToken', params.syncToken);
47
- } else if (params.timeMin != null) {
48
- url.searchParams.set('timeMin', params.timeMin);
47
+ } else {
48
+ if (params.timeMin != null) {
49
+ url.searchParams.set('timeMin', params.timeMin);
50
+ }
51
+ if (params.timeMax != null) {
52
+ url.searchParams.set('timeMax', params.timeMax);
53
+ }
54
+ if (params.showDeleted === true) {
55
+ url.searchParams.set('showDeleted', 'true');
56
+ }
49
57
  }
50
58
  if (params.pageToken != null) {
51
59
  url.searchParams.set('pageToken', params.pageToken);
52
60
  }
53
61
  return url.toString();
54
62
  }
63
+ const CALENDAR_ACCESS_ROLES = {
64
+ owner: 'OWNER',
65
+ writer: 'WRITER',
66
+ reader: 'READER',
67
+ freeBusyReader: 'FREE_BUSY'
68
+ };
69
+ const HEX_COLOR = /^#[0-9A-Fa-f]{6}$/;
70
+ // An empty provider string is no value: the model's text fields hold at least
71
+ // one character.
72
+ function text(value) {
73
+ return value == null || value === '' ? undefined : value;
74
+ }
55
75
  export function mapCalendarToEntity(calendar, accountKey) {
76
+ const color = calendar.backgroundColor;
56
77
  return {
57
78
  sourceService: 'google-calendar',
58
79
  sourceID: calendar.id,
59
80
  sourceURL: 'https://calendar.google.com',
60
- summary: calendar.summary,
61
- description: calendar.description,
81
+ name: text(calendar.summary) ?? '(No title)',
82
+ description: text(calendar.description),
62
83
  timeZone: calendar.timeZone,
63
84
  primary: calendar.primary ?? false,
64
- backgroundColor: calendar.backgroundColor,
85
+ color: color != null && HEX_COLOR.test(color) ? color : undefined,
86
+ accessRole: calendar.accessRole == null ? undefined : CALENDAR_ACCESS_ROLES[calendar.accessRole],
65
87
  ...accountKey != null ? {
66
88
  accountKey
67
89
  } : {}
@@ -73,10 +95,73 @@ export function mapCalendarToEntity(calendar, accountKey) {
73
95
  function allDayDateToDateTime(date) {
74
96
  return date == null ? undefined : `${date}T00:00:00.000Z`;
75
97
  }
98
+ function toDateTime(value) {
99
+ if (value == null) return undefined;
100
+ return value.date != null ? allDayDateToDateTime(value.date) : value.dateTime;
101
+ }
102
+ const RESPONSE_STATUS_TO_PARTY = {
103
+ needsAction: 'NEEDS_ACTION',
104
+ accepted: 'ACCEPTED',
105
+ declined: 'DECLINED',
106
+ tentative: 'TENTATIVE'
107
+ };
108
+ const PARTY_STATUS_TO_RESPONSE = {
109
+ NEEDS_ACTION: 'needsAction',
110
+ ACCEPTED: 'accepted',
111
+ DECLINED: 'declined',
112
+ TENTATIVE: 'tentative'
113
+ };
114
+ const REMINDER_TO_METHOD = {
115
+ popup: 'DISPLAY',
116
+ email: 'EMAIL'
117
+ };
118
+ const METHOD_TO_REMINDER = {
119
+ DISPLAY: 'popup',
120
+ EMAIL: 'email'
121
+ };
122
+ function mapAttendee(attendee) {
123
+ const party = {};
124
+ if (attendee.email != null) party.email = attendee.email;
125
+ const name = text(attendee.displayName);
126
+ if (name != null) party.name = name;
127
+ party.role = attendee.organizer === true ? 'CHAIR' : attendee.resource === true ? 'RESOURCE' : attendee.optional === true ? 'OPTIONAL' : 'REQUIRED';
128
+ const status = attendee.responseStatus == null ? undefined : RESPONSE_STATUS_TO_PARTY[attendee.responseStatus];
129
+ if (status != null) party.status = status;
130
+ if (attendee.self != null) party.self = attendee.self;
131
+ return party;
132
+ }
133
+ function mapOrganizer(organizer) {
134
+ if (organizer == null) return undefined;
135
+ const party = {};
136
+ if (organizer.email != null) party.email = organizer.email;
137
+ const name = text(organizer.displayName);
138
+ if (name != null) party.name = name;
139
+ party.role = 'CHAIR';
140
+ if (organizer.self != null) party.self = organizer.self;
141
+ return party;
142
+ }
143
+ // Calendar defaults (`useDefault`) are the calendar's, not the event's, so
144
+ // they leave the event's reminders unset. An event that turns them off with no
145
+ // overrides has no reminders: an empty list, distinct from the defaults.
146
+ function mapReminders(event) {
147
+ const reminders = event.reminders;
148
+ if (reminders == null || reminders.useDefault === true) return undefined;
149
+ return (reminders.overrides ?? []).map((reminder)=>({
150
+ method: REMINDER_TO_METHOD[reminder.method],
151
+ minutesBefore: reminder.minutes
152
+ }));
153
+ }
154
+ // The Meet link Google keeps on the event, or else the first video entry
155
+ // point of any other conference (Zoom, Teams...).
156
+ function mapConferenceURL(event) {
157
+ if (event.hangoutLink != null) return event.hangoutLink;
158
+ return event.conferenceData?.entryPoints?.find((entry)=>entry.entryPointType === 'video')?.uri;
159
+ }
160
+ function nonEmpty(items) {
161
+ return items == null || items.length === 0 ? undefined : items;
162
+ }
76
163
  export function mapEventToEntity(event, calendarID) {
77
164
  const allDay = event.start.date != null;
78
- const startAt = allDay ? allDayDateToDateTime(event.start.date) : event.start.dateTime;
79
- const endAt = allDay ? allDayDateToDateTime(event.end.date) : event.end.dateTime;
80
165
  return {
81
166
  sourceService: 'google-calendar',
82
167
  // Google event ids are unique per calendar, not per account; namespace the
@@ -85,16 +170,22 @@ export function mapEventToEntity(event, calendarID) {
85
170
  sourceID: `${calendarID}/${event.id}`,
86
171
  eventID: event.id,
87
172
  sourceURL: event.htmlLink,
88
- title: event.summary ?? '(No title)',
89
- description: event.description,
90
- location: event.location,
173
+ title: text(event.summary) ?? '(No title)',
174
+ description: text(event.description),
175
+ location: text(event.location),
91
176
  allDay,
92
- startAt,
93
- endAt,
177
+ startAt: toDateTime(event.start),
178
+ endAt: toDateTime(event.end),
94
179
  status: event.status.toUpperCase(),
95
180
  htmlLink: event.htmlLink,
96
- recurringEventID: event.recurringEventID,
97
181
  timeZone: event.start.timeZone,
182
+ organizer: mapOrganizer(event.organizer),
183
+ participants: nonEmpty(event.attendees)?.map(mapAttendee),
184
+ recurrence: nonEmpty(event.recurrence),
185
+ seriesID: event.recurringEventId,
186
+ originalStartAt: toDateTime(event.originalStartTime),
187
+ reminders: mapReminders(event),
188
+ conferenceURL: mapConferenceURL(event),
98
189
  calendarID,
99
190
  calendar: {
100
191
  sourceService: 'google-calendar',
@@ -103,6 +194,52 @@ export function mapEventToEntity(event, calendarID) {
103
194
  etag: event.etag
104
195
  };
105
196
  }
197
+ function participantToAttendee(participant) {
198
+ const party = participant ?? {};
199
+ if (typeof party.email !== 'string' || party.email === '') {
200
+ throw new GoogleCalendarError('a Google Calendar participant needs an email', {
201
+ kind: 'rejected'
202
+ });
203
+ }
204
+ // A chair is the event's organizer attending it. Google's attendee
205
+ // `organizer` flag is read-only and the organizer is fixed by the event
206
+ // itself, so a chair is sent as a plain required attendee: keeping the entry
207
+ // keeps the organizer's attendance. Google has no attendee role for a
208
+ // non-participant, so that one is refused, never sent as a plain attendee.
209
+ if (party.role === 'NON_PARTICIPANT') {
210
+ throw new GoogleCalendarError(`a Google Calendar participant cannot have the ${party.role} role`, {
211
+ kind: 'rejected'
212
+ });
213
+ }
214
+ const attendee = {
215
+ email: party.email
216
+ };
217
+ if (typeof party.name === 'string') attendee.displayName = party.name;
218
+ if (party.role === 'OPTIONAL') attendee.optional = true;
219
+ if (party.role === 'RESOURCE') attendee.resource = true;
220
+ const responseStatus = typeof party.status === 'string' ? PARTY_STATUS_TO_RESPONSE[party.status] : undefined;
221
+ if (responseStatus != null) attendee.responseStatus = responseStatus;
222
+ return attendee;
223
+ }
224
+ function remindersToGoogle(reminders) {
225
+ return {
226
+ // An explicit list, even an empty one, replaces the calendar defaults.
227
+ useDefault: false,
228
+ overrides: reminders.map((value)=>{
229
+ const reminder = value ?? {};
230
+ const method = METHOD_TO_REMINDER[reminder.method];
231
+ if (method == null || typeof reminder.minutesBefore !== 'number') {
232
+ throw new GoogleCalendarError('a Google Calendar reminder needs a DISPLAY or EMAIL method and minutesBefore', {
233
+ kind: 'rejected'
234
+ });
235
+ }
236
+ return {
237
+ method,
238
+ minutes: reminder.minutesBefore
239
+ };
240
+ })
241
+ };
242
+ }
106
243
  export function buildEventFromParams(params, options) {
107
244
  const event = {};
108
245
  const allDay = params.allDay === true;
@@ -145,6 +282,17 @@ export function buildEventFromParams(params, options) {
145
282
  } : {}
146
283
  };
147
284
  }
285
+ // Each list replaces the event's whole list at Google: a sparse PATCH that
286
+ // carries one sends it complete.
287
+ if (Array.isArray(params.participants)) {
288
+ event.attendees = params.participants.map(participantToAttendee);
289
+ }
290
+ if (Array.isArray(params.recurrence)) {
291
+ event.recurrence = params.recurrence;
292
+ }
293
+ if (Array.isArray(params.reminders)) {
294
+ event.reminders = remindersToGoogle(params.reminders);
295
+ }
148
296
  return event;
149
297
  }
150
298
  // One calendar as the account's calendar list holds it (calendarList.get) —
@@ -167,7 +315,7 @@ export function splitEventSourceID(sourceID) {
167
315
  eventID: sourceID.slice(slash + 1)
168
316
  };
169
317
  }
170
- async function fetchEventResource(config) {
318
+ export async function fetchEventResource(config) {
171
319
  const url = `${CALENDAR_BASE_URL}/calendars/${encodeURIComponent(config.calendarID)}/events/${encodeURIComponent(config.eventID)}`;
172
320
  return calendarAPIClient(config.accessToken, config.fetch).get(url);
173
321
  }
@@ -231,7 +379,9 @@ export async function createEvent(config) {
231
379
  }
232
380
  export async function updateEvent(config) {
233
381
  const event = buildEventFromParams(config.params);
234
- const url = `${CALENDAR_BASE_URL}/calendars/${encodeURIComponent(config.calendarID)}/events/${encodeURIComponent(config.eventID)}`;
382
+ // Guest notification is not part of a write-back: an edit synced from a
383
+ // device never emails the event's guests.
384
+ const url = `${CALENDAR_BASE_URL}/calendars/${encodeURIComponent(config.calendarID)}/events/${encodeURIComponent(config.eventID)}?sendUpdates=none`;
235
385
  const data = await calendarAPIClient(config.accessToken, config.fetch).send(url, {
236
386
  method: 'PATCH',
237
387
  body: event,
package/lib/connector.js CHANGED
@@ -1,9 +1,23 @@
1
+ import { calendarModelDefinitions } from '@kubun/models-calendar';
1
2
  import { createGoogleCalendarActionHandler } from './actions.js';
2
3
  import { canonicalizeEventIntent } from './intent.js';
3
4
  import { googleCalendarClusters } from './model.js';
4
5
  import { predictCreatedEventSourceID } from './predict.js';
5
6
  import { createGoogleCalendarProvider } from './provider.js';
6
7
  import { resolveCalendarTarget } from './target.js';
8
+ const eventKindProperties = calendarModelDefinitions.find((definition)=>definition.name === 'EventKind')?.schema?.properties ?? {};
9
+ function eventKindProperty(name) {
10
+ const property = eventKindProperties[name];
11
+ if (property == null) throw new Error(`EventKind declares no "${name}" field`);
12
+ return property;
13
+ }
14
+ // The event lists a write can set, in the EventKind shape. Each one replaces
15
+ // the event's whole list at Google.
16
+ const eventListInputProperties = {
17
+ participants: eventKindProperty('participants'),
18
+ recurrence: eventKindProperty('recurrence'),
19
+ reminders: eventKindProperty('reminders')
20
+ };
7
21
  export function createGoogleCalendarConnector(options) {
8
22
  return {
9
23
  name: 'google-calendar',
@@ -15,6 +29,8 @@ export function createGoogleCalendarConnector(options) {
15
29
  actions: [
16
30
  {
17
31
  name: 'create',
32
+ // Events only: no mutation is generated for GoogleCalendar.
33
+ entityModel: 'GoogleCalendarEvent',
18
34
  description: 'Create a Google Calendar event',
19
35
  input: {
20
36
  type: 'object',
@@ -42,7 +58,8 @@ export function createGoogleCalendarConnector(options) {
42
58
  },
43
59
  timeZone: {
44
60
  type: 'string'
45
- }
61
+ },
62
+ ...eventListInputProperties
46
63
  }
47
64
  },
48
65
  requiresCredential: true,
@@ -54,6 +71,8 @@ export function createGoogleCalendarConnector(options) {
54
71
  },
55
72
  {
56
73
  name: 'update',
74
+ // Events only: no mutation is generated for GoogleCalendar.
75
+ entityModel: 'GoogleCalendarEvent',
57
76
  description: 'Update a Google Calendar event',
58
77
  input: {
59
78
  type: 'object',
@@ -81,7 +100,8 @@ export function createGoogleCalendarConnector(options) {
81
100
  },
82
101
  timeZone: {
83
102
  type: 'string'
84
- }
103
+ },
104
+ ...eventListInputProperties
85
105
  }
86
106
  },
87
107
  requiresCredential: true,
package/lib/intent.d.ts CHANGED
@@ -7,6 +7,10 @@ import type { EntityRecord, IntentCanonicalizer } from '@kubun/connector';
7
7
  * value never matches a timed one;
8
8
  * - `calendarID` is compared exactly: an operation's intent already carries
9
9
  * the calendar the `'primary'` alias was resolved to before the claim;
10
+ * - participants are compared by email and role only, in any order: Google
11
+ * sets each attendee's response status and display name itself;
12
+ * - reminders are compared exactly: an empty list (the event's reminders
13
+ * turned off) is not the calendar defaults (unset);
10
14
  * - a cancelled (deleted) event holds no create or update.
11
15
  */
12
16
  export declare const canonicalizeEventIntent: IntentCanonicalizer;
package/lib/intent.js CHANGED
@@ -25,6 +25,10 @@ function canonicalDateTime(value, allDay) {
25
25
  * value never matches a timed one;
26
26
  * - `calendarID` is compared exactly: an operation's intent already carries
27
27
  * the calendar the `'primary'` alias was resolved to before the claim;
28
+ * - participants are compared by email and role only, in any order: Google
29
+ * sets each attendee's response status and display name itself;
30
+ * - reminders are compared exactly: an empty list (the event's reminders
31
+ * turned off) is not the calendar defaults (unset);
28
32
  * - a cancelled (deleted) event holds no create or update.
29
33
  */ export const canonicalizeEventIntent = ({ model, intent, record })=>{
30
34
  if (model !== googleCalendarEventDefinition.name) return {
@@ -54,11 +58,30 @@ function canonicalDateTime(value, allDay) {
54
58
  }
55
59
  canonicalRecord[field] = canonicalDateTime(canonicalRecord[field], recordAllDay);
56
60
  }
61
+ if ('participants' in canonicalIntent) {
62
+ canonicalIntent.participants = canonicalParticipants(canonicalIntent.participants);
63
+ canonicalRecord.participants = canonicalParticipants(canonicalRecord.participants);
64
+ }
57
65
  return {
58
66
  intent: canonicalIntent,
59
67
  record: canonicalRecord
60
68
  };
61
69
  };
70
+ // A participant list as Google keeps it: each attendee's email (compared
71
+ // case-insensitively) and role, in no particular order. Google sets the
72
+ // response status, the reader's own entry and display names itself, so they
73
+ // are not part of what a write intends.
74
+ function canonicalParticipants(value) {
75
+ if (value == null) return [];
76
+ if (!Array.isArray(value)) return value;
77
+ return value.map((item)=>{
78
+ const party = item ?? {};
79
+ return {
80
+ email: typeof party.email === 'string' ? party.email.toLowerCase() : party.email,
81
+ role: party.role ?? 'REQUIRED'
82
+ };
83
+ }).sort((a, b)=>String(a.email).localeCompare(String(b.email)));
84
+ }
62
85
  function canonicalOf(value) {
63
86
  return toB64U(digestJSON(value === undefined ? null : value));
64
87
  }
package/lib/model.d.ts CHANGED
@@ -1,25 +1,13 @@
1
1
  export declare const googleCalendarDefinition: {
2
2
  readonly name: "GoogleCalendar";
3
3
  readonly behavior: "default";
4
- readonly interfaces: [string];
4
+ readonly interfaces: [string, string];
5
5
  readonly schema: {
6
6
  readonly type: "object";
7
7
  readonly properties: {
8
- readonly summary: {
9
- readonly type: "string";
10
- };
11
- readonly description: {
12
- readonly type: "string";
13
- };
14
- readonly timeZone: {
15
- readonly $ref: "#/definitions/TimeZone";
16
- };
17
8
  readonly primary: {
18
9
  readonly type: "boolean";
19
10
  };
20
- readonly backgroundColor: {
21
- readonly type: "string";
22
- };
23
11
  readonly accountKey: {
24
12
  readonly type: "string";
25
13
  readonly minLength: 43;
@@ -38,10 +26,9 @@ export declare const googleCalendarDefinition: {
38
26
  readonly $ref: "#/definitions/DateTime";
39
27
  };
40
28
  };
41
- readonly required: ["summary", "sourceService", "sourceID", "lastSyncedAt"];
29
+ readonly required: ["sourceService", "sourceID", "lastSyncedAt"];
42
30
  readonly additionalProperties: false;
43
31
  readonly definitions: {
44
- readonly TimeZone: import("json-schema-typed").JSONSchema.String;
45
32
  readonly SourceService: {
46
33
  readonly type: "string";
47
34
  readonly title: "SourceService";
@@ -63,46 +50,20 @@ export declare const googleCalendarDefinition: {
63
50
  };
64
51
  };
65
52
  readonly fieldsMeta: {};
53
+ readonly providerMergePolicy: {
54
+ readonly group: [...string[], "primary", "sourceService", "sourceID", "sourceURL"];
55
+ };
66
56
  };
67
57
  export declare const googleCalendarEventDefinition: {
68
58
  readonly name: "GoogleCalendarEvent";
69
59
  readonly behavior: "default";
70
- readonly interfaces: [string, string];
60
+ readonly interfaces: [string, string, string];
71
61
  readonly schema: {
72
62
  readonly type: "object";
73
63
  readonly properties: {
74
- readonly title: {
75
- readonly type: "string";
76
- readonly minLength: 1;
77
- readonly maxLength: 200;
78
- };
79
- readonly startAt: {
80
- readonly $ref: "#/definitions/DateTime";
81
- };
82
- readonly endAt: {
83
- readonly $ref: "#/definitions/DateTime";
84
- };
85
- readonly allDay: {
86
- readonly type: "boolean";
87
- };
88
- readonly timeZone: {
89
- readonly $ref: "#/definitions/TimeZone";
90
- };
91
- readonly description: {
92
- readonly type: "string";
93
- };
94
- readonly location: {
95
- readonly type: "string";
96
- };
97
- readonly status: {
98
- readonly $ref: "#/definitions/EventStatus";
99
- };
100
64
  readonly htmlLink: {
101
65
  readonly $ref: "#/definitions/HtmlLink";
102
66
  };
103
- readonly recurringEventID: {
104
- readonly type: "string";
105
- };
106
67
  readonly eventID: {
107
68
  readonly type: "string";
108
69
  };
@@ -128,7 +89,7 @@ export declare const googleCalendarEventDefinition: {
128
89
  readonly $ref: "#/definitions/DateTime";
129
90
  };
130
91
  };
131
- readonly required: ["title", "startAt", "allDay", "calendarID", "calendar", "sourceService", "sourceID", "lastSyncedAt"];
92
+ readonly required: ["calendarID", "calendar", "sourceService", "sourceID", "lastSyncedAt"];
132
93
  readonly additionalProperties: false;
133
94
  readonly definitions: {
134
95
  readonly SourceID: {
@@ -137,7 +98,6 @@ export declare const googleCalendarEventDefinition: {
137
98
  readonly minLength: 1;
138
99
  readonly maxLength: 1400;
139
100
  };
140
- readonly TimeZone: import("json-schema-typed").JSONSchema.String;
141
101
  readonly HtmlLink: {
142
102
  readonly type: "string";
143
103
  readonly format: "uri";
@@ -148,11 +108,6 @@ export declare const googleCalendarEventDefinition: {
148
108
  readonly title: "DocID";
149
109
  readonly pattern: "^k[0-9a-z]{10,120}$";
150
110
  };
151
- readonly EventStatus: {
152
- readonly type: "string";
153
- readonly title: "EventStatus";
154
- readonly enum: ["CONFIRMED", "TENTATIVE", "CANCELLED"];
155
- };
156
111
  readonly SourceService: {
157
112
  readonly type: "string";
158
113
  readonly title: "SourceService";
@@ -174,7 +129,7 @@ export declare const googleCalendarEventDefinition: {
174
129
  };
175
130
  };
176
131
  readonly providerMergePolicy: {
177
- readonly group: ["title", "startAt", "endAt", "allDay", "timeZone", "description", "location", "status", "htmlLink", "recurringEventID", "eventID", "calendarID", "calendar", "sourceService", "sourceID", "sourceURL", "etag"];
132
+ readonly group: ["title", ...string[], "htmlLink", "eventID", "calendarID", "calendar", "sourceService", "sourceID", "sourceURL", "etag"];
178
133
  };
179
134
  };
180
135
  export declare const googleCalendarClusters: import("@kubun/protocol").ClustersRecord;