@prismatic-io/components-google-calendar 0.0.0-stage → 0.0.1

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.
@@ -0,0 +1,1617 @@
1
+ let _prismatic_io_spectral = require("@prismatic-io/spectral");
2
+ let _prismatic_io_spectral_http = require("@prismatic-io/spectral/http");
3
+ let _googleapis_calendar = require("@googleapis/calendar");
4
+ let uuid = require("uuid");
5
+ //#region src/connections.ts
6
+ const googleConnection = (0, _prismatic_io_spectral.oauth2Connection)({
7
+ oauth2Type: _prismatic_io_spectral.OAuth2Type.AuthorizationCode,
8
+ key: "oauth2",
9
+ display: {
10
+ label: "OAuth 2.0",
11
+ description: "OAuth 2.0 connection for Google Calendar"
12
+ },
13
+ inputs: {
14
+ authorizeUrl: {
15
+ label: "Authorize URL",
16
+ placeholder: "Enter OAuth 2.0 Authorization URL",
17
+ type: "string",
18
+ required: true,
19
+ shown: false,
20
+ comments: "The OAuth 2.0 Authorization URL for Google Calendar API.",
21
+ default: "https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent"
22
+ },
23
+ tokenUrl: {
24
+ label: "Token URL",
25
+ placeholder: "Enter OAuth 2.0 Token URL",
26
+ type: "string",
27
+ required: true,
28
+ shown: false,
29
+ comments: "The OAuth 2.0 Token URL for Google Calendar API.",
30
+ default: "https://oauth2.googleapis.com/token"
31
+ },
32
+ scopes: {
33
+ label: "Scopes",
34
+ placeholder: "Enter OAuth scopes (space-delimited)",
35
+ type: "string",
36
+ required: false,
37
+ shown: true,
38
+ comments: "Space-delimited list of OAuth 2.0 scopes. See [Google Calendar API scopes](https://developers.google.com/calendar/api/auth) for available options.",
39
+ default: "https://www.googleapis.com/auth/calendar"
40
+ },
41
+ clientId: {
42
+ label: "Client ID",
43
+ placeholder: "Enter Client ID",
44
+ type: "string",
45
+ required: true,
46
+ example: "123456789012-abc123def456ghi789jkl012mno345pq.apps.googleusercontent.com",
47
+ comments: "Client ID from your Google Cloud Platform OAuth 2.0 credentials.",
48
+ shown: true
49
+ },
50
+ clientSecret: {
51
+ label: "Client Secret",
52
+ placeholder: "Enter Client Secret",
53
+ type: "password",
54
+ example: "GOCSPX-abcd1234efgh5678ijkl",
55
+ comments: "Client Secret from your Google Cloud Platform OAuth 2.0 credentials.",
56
+ required: true,
57
+ shown: true
58
+ }
59
+ }
60
+ });
61
+ var connections_default = [googleConnection];
62
+ //#endregion
63
+ //#region src/client.ts
64
+ const validateConnection = (connection) => {
65
+ if (connection.key !== googleConnection.key) throw new _prismatic_io_spectral.ConnectionError(connection, "Unknown Connection type provided.");
66
+ if (!connection.token?.access_token) throw new _prismatic_io_spectral.ConnectionError(connection, "Received valid Connection type but did not find valid access token.");
67
+ };
68
+ const createClient = ({ connection }) => {
69
+ validateConnection(connection);
70
+ const oauth2Client = new _googleapis_calendar.auth.OAuth2(_prismatic_io_spectral.util.types.toString(connection.fields.clientId), _prismatic_io_spectral.util.types.toString(connection.fields.clientSecret));
71
+ const token = _prismatic_io_spectral.util.types.toString(connection.token.access_token);
72
+ oauth2Client.setCredentials({ access_token: token });
73
+ return (0, _googleapis_calendar.calendar)({
74
+ version: "v3",
75
+ auth: oauth2Client
76
+ });
77
+ };
78
+ const getAccessToken = ({ connection }) => {
79
+ validateConnection(connection);
80
+ return _prismatic_io_spectral.util.types.toString(connection.token.access_token);
81
+ };
82
+ //#endregion
83
+ //#region src/utils/cleanInputs.ts
84
+ const YYYY_MM_DD = /^\d{4}-\d{2}-\d{2}$/;
85
+ const lookBackDateClean = (value) => {
86
+ const raw = _prismatic_io_spectral.util.types.toString(value).trim();
87
+ if (!raw) return "";
88
+ if (!YYYY_MM_DD.test(raw)) throw new Error("Look-back Date must be in YYYY-MM-DD format.");
89
+ const parsed = /* @__PURE__ */ new Date(`${raw}T00:00:00Z`);
90
+ if (Number.isNaN(parsed.getTime())) throw new Error("Look-back Date is not a valid calendar date.");
91
+ const [y, m, d] = raw.split("-").map(Number);
92
+ if (parsed.getUTCFullYear() !== y || parsed.getUTCMonth() + 1 !== m || parsed.getUTCDate() !== d) throw new Error("Look-back Date is not a valid calendar date.");
93
+ if (parsed.getTime() > Date.now()) throw new Error("Look-back Date cannot be a future date.");
94
+ return raw;
95
+ };
96
+ //#endregion
97
+ //#region src/inputs.ts
98
+ const calendarId = (0, _prismatic_io_spectral.input)({
99
+ label: "Calendar ID",
100
+ type: "string",
101
+ required: true,
102
+ placeholder: "Enter Calendar ID",
103
+ example: "en.usa#holiday@group.v.calendar.google.com",
104
+ comments: "The unique identifier of the calendar. Use 'primary' for the user's primary calendar.",
105
+ clean: _prismatic_io_spectral.util.types.toString,
106
+ dataSource: "selectCalendar"
107
+ });
108
+ const startTime = (0, _prismatic_io_spectral.input)({
109
+ label: "Start Time",
110
+ type: "string",
111
+ required: true,
112
+ placeholder: "Enter start time (ISO 8601 format)",
113
+ example: "2026-05-28T17:00:00-07:00",
114
+ comments: "The start time of the event in ISO 8601 format with timezone offset."
115
+ });
116
+ const timeZone = (0, _prismatic_io_spectral.input)({
117
+ label: "Time Zone",
118
+ type: "string",
119
+ required: true,
120
+ placeholder: "Enter timezone (IANA format)",
121
+ example: "America/Chicago",
122
+ comments: "The timezone of the event in IANA Time Zone Database format. See [IANA timezone list](https://www.iana.org/time-zones)."
123
+ });
124
+ const endTime = (0, _prismatic_io_spectral.input)({
125
+ label: "End Time",
126
+ type: "string",
127
+ required: true,
128
+ placeholder: "Enter end time (ISO 8601 format)",
129
+ example: "2026-05-28T19:00:00-07:00",
130
+ comments: "The end time of the event in ISO 8601 format with timezone offset."
131
+ });
132
+ const summary = (0, _prismatic_io_spectral.input)({
133
+ label: "Summary",
134
+ type: "string",
135
+ required: true,
136
+ placeholder: "Enter event summary",
137
+ example: "Team Standup Meeting",
138
+ comments: "The title or summary of the event."
139
+ });
140
+ const description = (0, _prismatic_io_spectral.input)({
141
+ label: "Description",
142
+ type: "string",
143
+ required: true,
144
+ placeholder: "Enter event description",
145
+ example: "Discuss sprint progress and blockers",
146
+ comments: "A detailed description of the event."
147
+ });
148
+ const attendees = (0, _prismatic_io_spectral.input)({
149
+ label: "Attendees",
150
+ type: "code",
151
+ default: JSON.stringify([{
152
+ email: "john.doe@example.com",
153
+ optional: false
154
+ }, {
155
+ email: "jane.smith@example.com",
156
+ optional: true
157
+ }], null, 2),
158
+ language: "json",
159
+ required: false,
160
+ comments: "Array of attendee objects with email addresses and optional flags. See [Google Calendar Events API](https://developers.google.com/calendar/api/v3/reference/events/insert) for full schema.",
161
+ clean: (value) => value ? _prismatic_io_spectral.util.types.toObject(value) : void 0
162
+ });
163
+ const remindMethod = (0, _prismatic_io_spectral.input)({
164
+ label: "Remind Method",
165
+ type: "string",
166
+ model: [{
167
+ label: "Email",
168
+ value: "email"
169
+ }, {
170
+ label: "Popup",
171
+ value: "popup"
172
+ }],
173
+ required: false,
174
+ placeholder: "Select reminder method",
175
+ comments: "How to send the event reminder. Only used when 'Default Reminder' is false."
176
+ });
177
+ const remindMinutes = (0, _prismatic_io_spectral.input)({
178
+ label: "Remind Before (minutes)",
179
+ type: "string",
180
+ required: false,
181
+ placeholder: "Enter minutes before event",
182
+ example: "30",
183
+ comments: "Number of minutes before the event to send the reminder. Only used when 'Default Reminder' is false."
184
+ });
185
+ const eventLocation = (0, _prismatic_io_spectral.input)({
186
+ label: "Event Location",
187
+ type: "string",
188
+ required: false,
189
+ placeholder: "Enter event location",
190
+ example: "Conference Room A, Building 3",
191
+ comments: "The physical or virtual location of the event."
192
+ });
193
+ const eventId = (0, _prismatic_io_spectral.input)({
194
+ label: "Event ID",
195
+ type: "string",
196
+ required: true,
197
+ placeholder: "Enter Event ID",
198
+ dataSource: "selectEvent",
199
+ example: "20260101_q8ue475rr4p7opsd4c0lr7g5pg",
200
+ comments: "The unique identifier of the event."
201
+ });
202
+ const useDefaultReminder = (0, _prismatic_io_spectral.input)({
203
+ label: "Default Reminder",
204
+ type: "boolean",
205
+ required: true,
206
+ comments: "When true, the event uses the default reminder settings from the calendar."
207
+ });
208
+ const maxResults = (0, _prismatic_io_spectral.input)({
209
+ label: "Max Results",
210
+ type: "string",
211
+ required: false,
212
+ placeholder: "Enter maximum number of results (1-250)",
213
+ comments: "Maximum number of results to return (1-250).",
214
+ example: "50",
215
+ clean: (value) => value !== "" ? _prismatic_io_spectral.util.types.toInt(value) : void 0
216
+ });
217
+ const pageToken = (0, _prismatic_io_spectral.input)({
218
+ label: "Page Token",
219
+ type: "string",
220
+ required: false,
221
+ placeholder: "Enter page token from previous response",
222
+ comments: "Pagination token returned from a previous request to retrieve the next page of results.",
223
+ example: "lslTXFcbLQKkb0vP9Kgh5hy0Y0OnC7Z9ZPHPwPmMnxSk3eiDRMkct7D8E",
224
+ clean: (value) => value !== "" ? _prismatic_io_spectral.util.types.toString(value) : void 0
225
+ });
226
+ const syncToken = (0, _prismatic_io_spectral.input)({
227
+ label: "Sync Token",
228
+ type: "string",
229
+ required: false,
230
+ placeholder: "Enter sync token from previous response",
231
+ comments: "Token for retrieving only resources modified since the last sync request.",
232
+ example: "lslTXFcbLQKkb0vP9Kgh5hy0Y0OnC7Z9ZPHPwPmMnxSk3eiDRMkct7D8E",
233
+ clean: (value) => value !== "" ? _prismatic_io_spectral.util.types.toString(value) : void 0
234
+ });
235
+ const connectionInput = (0, _prismatic_io_spectral.input)({
236
+ label: "Connection",
237
+ type: "connection",
238
+ required: true,
239
+ comments: "The Google Calendar connection to use."
240
+ });
241
+ const addConferenceEvent = (0, _prismatic_io_spectral.input)({
242
+ label: "Add Conference Event",
243
+ type: "boolean",
244
+ default: "false",
245
+ required: false,
246
+ comments: "When true, creates a Google Meet conference link for the event.",
247
+ clean: _prismatic_io_spectral.util.types.toBool
248
+ });
249
+ const sendUpdates = (0, _prismatic_io_spectral.input)({
250
+ label: "Send Updates",
251
+ type: "string",
252
+ model: [
253
+ {
254
+ label: "",
255
+ value: ""
256
+ },
257
+ {
258
+ label: "All",
259
+ value: "all"
260
+ },
261
+ {
262
+ label: "External Only",
263
+ value: "externalOnly"
264
+ },
265
+ {
266
+ label: "None",
267
+ value: "none"
268
+ }
269
+ ],
270
+ required: false,
271
+ default: "",
272
+ comments: "Whether to send notifications about the creation of the new event. Note that some emails might still be sent. The default is false.",
273
+ clean: _prismatic_io_spectral.util.types.toString
274
+ });
275
+ const selectCalendarInputs = { connection: connectionInput };
276
+ const selectEventInputs = {
277
+ connection: connectionInput,
278
+ calendarId: {
279
+ ...calendarId,
280
+ comments: "Calendar ID to get events from.",
281
+ dataSource: void 0
282
+ }
283
+ };
284
+ const fetchAll = (0, _prismatic_io_spectral.input)({
285
+ label: "Fetch All",
286
+ type: "boolean",
287
+ required: false,
288
+ comments: "When true, fetches all pages of results, ignoring the 'Max Results' and 'Page Token' inputs.",
289
+ clean: _prismatic_io_spectral.util.types.toBool
290
+ });
291
+ const calendarChangeEventsInputs = {
292
+ connection: connectionInput,
293
+ calendarId: {
294
+ ...calendarId,
295
+ comments: "The calendar to monitor for changes."
296
+ }
297
+ };
298
+ const lookBackDate = (0, _prismatic_io_spectral.input)({
299
+ label: "Look-back Date",
300
+ placeholder: "Enter look-back date (YYYY-MM-DD)",
301
+ type: "string",
302
+ required: false,
303
+ comments: "The date the initial sync starts from, in YYYY-MM-DD format. Cannot be a future date. Leave empty to start from the first recurrence with no backfill. When set, the initial sync seeds each record modified on or after this date once.",
304
+ example: "2026-01-01",
305
+ clean: lookBackDateClean
306
+ });
307
+ const pollEventsTriggerInputs = {
308
+ connection: connectionInput,
309
+ calendarId: {
310
+ ...calendarId,
311
+ comments: "The calendar to poll for event changes."
312
+ },
313
+ lookBackDate
314
+ };
315
+ //#endregion
316
+ //#region src/parseReturn.ts
317
+ const parseReturn = (config) => {
318
+ Object.fromEntries(Object.entries(config).filter(([key]) => !["paramsSerializer", "validateStatus"].includes(key)));
319
+ };
320
+ //#endregion
321
+ //#region src/actions/createCalendar.ts
322
+ const createCalendar = (0, _prismatic_io_spectral.action)({
323
+ display: {
324
+ label: "Create Calendar",
325
+ description: "Create a new calendar"
326
+ },
327
+ perform: async (_context, params) => {
328
+ const { config, ...base } = await createClient({ connection: params.googleConnection }).calendars.insert({ requestBody: {
329
+ summary: _prismatic_io_spectral.util.types.toString(params.summary),
330
+ timeZone: _prismatic_io_spectral.util.types.toString(params.timeZone),
331
+ description: _prismatic_io_spectral.util.types.toString(params.description)
332
+ } });
333
+ return { data: {
334
+ config: parseReturn(config),
335
+ ...base
336
+ } };
337
+ },
338
+ inputs: {
339
+ summary,
340
+ description,
341
+ timeZone,
342
+ googleConnection: connectionInput
343
+ }
344
+ });
345
+ //#endregion
346
+ //#region src/actions/createEvent.ts
347
+ const createEvent = (0, _prismatic_io_spectral.action)({
348
+ display: {
349
+ label: "Create Event",
350
+ description: "Create a new event in a given calendar"
351
+ },
352
+ perform: async (_context, params) => {
353
+ const useDefaultReminder = _prismatic_io_spectral.util.types.toBool(params.useDefaultReminder);
354
+ const remindMethod = _prismatic_io_spectral.util.types.toString(params.remindMethod);
355
+ const remindMinutes = _prismatic_io_spectral.util.types.toString(params.remindMinutes);
356
+ const { config, ...base } = await createClient({ connection: params.connection }).events.insert({
357
+ calendarId: _prismatic_io_spectral.util.types.toString(params.calendarId),
358
+ ...params.addConferenceEvent && { conferenceDataVersion: 1 },
359
+ requestBody: {
360
+ ...params.addConferenceEvent && { conferenceData: { createRequest: {
361
+ requestId: (0, uuid.v4)(),
362
+ conferenceSolutionKey: { type: "hangoutsMeet" }
363
+ } } },
364
+ summary: _prismatic_io_spectral.util.types.toString(params.summary),
365
+ location: _prismatic_io_spectral.util.types.toString(params.eventLocation),
366
+ description: _prismatic_io_spectral.util.types.toString(params.description),
367
+ start: {
368
+ dateTime: _prismatic_io_spectral.util.types.toString(params.startTime),
369
+ timeZone: _prismatic_io_spectral.util.types.toString(params.timeZone)
370
+ },
371
+ end: {
372
+ dateTime: _prismatic_io_spectral.util.types.toString(params.endTime),
373
+ timeZone: _prismatic_io_spectral.util.types.toString(params.timeZone)
374
+ },
375
+ attendees: params.attendees,
376
+ reminders: {
377
+ ...!useDefaultReminder && remindMethod && remindMinutes && { overrides: [{
378
+ method: remindMethod,
379
+ minutes: _prismatic_io_spectral.util.types.toInt(remindMinutes)
380
+ }] },
381
+ useDefault: useDefaultReminder
382
+ }
383
+ },
384
+ sendUpdates: params.sendUpdates || void 0
385
+ });
386
+ return { data: {
387
+ config: parseReturn(config),
388
+ ...base
389
+ } };
390
+ },
391
+ inputs: {
392
+ calendarId,
393
+ summary,
394
+ description,
395
+ timeZone,
396
+ startTime,
397
+ endTime,
398
+ eventLocation,
399
+ attendees,
400
+ remindMethod,
401
+ useDefaultReminder,
402
+ remindMinutes,
403
+ addConferenceEvent,
404
+ connection: connectionInput,
405
+ sendUpdates
406
+ }
407
+ });
408
+ //#endregion
409
+ //#region src/actions/deleteCalendar.ts
410
+ const deleteCalendar = (0, _prismatic_io_spectral.action)({
411
+ display: {
412
+ label: "Delete Calendar",
413
+ description: "Delete an existing calendar by Id"
414
+ },
415
+ perform: async (_context, params) => {
416
+ const { config, ...base } = await createClient({ connection: params.connection }).calendarList.delete({ calendarId: _prismatic_io_spectral.util.types.toString(params.calendarId) });
417
+ return { data: {
418
+ config: parseReturn(config),
419
+ ...base
420
+ } };
421
+ },
422
+ inputs: {
423
+ calendarId,
424
+ connection: connectionInput
425
+ }
426
+ });
427
+ //#endregion
428
+ //#region src/actions/deleteEvent.ts
429
+ const deleteEvent = (0, _prismatic_io_spectral.action)({
430
+ display: {
431
+ label: "Delete Event",
432
+ description: "Delete an event by an Id"
433
+ },
434
+ perform: async (_context, params) => {
435
+ const { config, ...base } = await createClient({ connection: params.connection }).events.delete({
436
+ calendarId: _prismatic_io_spectral.util.types.toString(params.calendarId),
437
+ eventId: _prismatic_io_spectral.util.types.toString(params.eventId),
438
+ sendUpdates: params.sendUpdates || void 0
439
+ });
440
+ return { data: {
441
+ config: parseReturn(config),
442
+ ...base
443
+ } };
444
+ },
445
+ inputs: {
446
+ calendarId,
447
+ eventId,
448
+ connection: connectionInput,
449
+ sendUpdates: {
450
+ ...sendUpdates,
451
+ comments: "Guests who should receive notifications about the deletion of the event."
452
+ }
453
+ }
454
+ });
455
+ //#endregion
456
+ //#region src/actions/getCalendar.ts
457
+ const getCalendar = (0, _prismatic_io_spectral.action)({
458
+ display: {
459
+ label: "Get Calendar",
460
+ description: "Get the information and metadata of a calendar by Id"
461
+ },
462
+ perform: async (_context, params) => {
463
+ const { config, ...base } = await createClient({ connection: params.connection }).calendars.get({ calendarId: _prismatic_io_spectral.util.types.toString(params.calendarId) });
464
+ return { data: {
465
+ config: parseReturn(config),
466
+ ...base
467
+ } };
468
+ },
469
+ inputs: {
470
+ connection: connectionInput,
471
+ calendarId
472
+ }
473
+ });
474
+ //#endregion
475
+ //#region src/actions/getEvent.ts
476
+ const getEvent = (0, _prismatic_io_spectral.action)({
477
+ display: {
478
+ label: "Get Event",
479
+ description: "Get the information and metadata of an event by Id"
480
+ },
481
+ perform: async (_context, params) => {
482
+ const { config, ...base } = await createClient({ connection: params.connection }).events.get({
483
+ calendarId: _prismatic_io_spectral.util.types.toString(params.calendarId),
484
+ eventId: _prismatic_io_spectral.util.types.toString(params.eventId)
485
+ });
486
+ return { data: {
487
+ config: parseReturn(config),
488
+ ...base
489
+ } };
490
+ },
491
+ inputs: {
492
+ calendarId,
493
+ eventId,
494
+ connection: connectionInput
495
+ }
496
+ });
497
+ //#endregion
498
+ //#region src/helpers/listAllCalendars.ts
499
+ const listAllCalendars = async (params) => {
500
+ const client = createClient({ connection: params.connection });
501
+ const MAX_RESULTS = 250;
502
+ if (params.fetchAll) params.pageToken = void 0;
503
+ const maxResults = params.fetchAll ? MAX_RESULTS : _prismatic_io_spectral.util.types.toInt(params.maxResults) || void 0;
504
+ let calendarList = await client.calendarList.list({
505
+ pageToken: _prismatic_io_spectral.util.types.toString(params.pageToken) || void 0,
506
+ maxResults
507
+ });
508
+ let nextPageToken = calendarList.data.nextPageToken;
509
+ if (!params.fetchAll || !nextPageToken) return calendarList;
510
+ const allItems = [...calendarList.data.items];
511
+ while (nextPageToken) {
512
+ calendarList = await client.calendarList.list({
513
+ pageToken: nextPageToken,
514
+ maxResults
515
+ });
516
+ allItems.push(...calendarList.data.items);
517
+ nextPageToken = calendarList.data.nextPageToken;
518
+ }
519
+ calendarList.data.items = allItems;
520
+ return calendarList;
521
+ };
522
+ //#endregion
523
+ //#region src/actions/listCalendar.ts
524
+ const listCalendar = (0, _prismatic_io_spectral.action)({
525
+ display: {
526
+ label: "List Calendars",
527
+ description: "List all calendars"
528
+ },
529
+ perform: async (_context, params) => {
530
+ const { config, ...base } = await listAllCalendars(params);
531
+ return { data: {
532
+ config: parseReturn(config),
533
+ ...base
534
+ } };
535
+ },
536
+ inputs: {
537
+ pageToken,
538
+ maxResults,
539
+ connection: connectionInput,
540
+ fetchAll
541
+ }
542
+ });
543
+ //#endregion
544
+ //#region src/helpers/listAllEvents.ts
545
+ const listAllEvents = async (params) => {
546
+ const client = createClient({ connection: params.googleConnection });
547
+ const MAX_RESULTS = 250;
548
+ if (params.fetchAll) params.pageToken = void 0;
549
+ const maxResults = params.fetchAll ? MAX_RESULTS : _prismatic_io_spectral.util.types.toInt(params.maxResults) || void 0;
550
+ const requestParams = {
551
+ calendarId: params.calendarId,
552
+ maxResults,
553
+ pageToken: params.pageToken,
554
+ syncToken: params.syncToken,
555
+ maxAttendees: params.maxAttendees,
556
+ timeZone: params.timeZone,
557
+ q: params.q,
558
+ timeMax: params.timeMax,
559
+ timeMin: params.timeMin,
560
+ orderBy: params.orderBy,
561
+ singleEvents: params.singleEvents,
562
+ updatedMin: params.updatedMin,
563
+ showDeleted: params.showDeleted,
564
+ showHiddenInvitations: params.showHiddenInvitations
565
+ };
566
+ let eventsList = await client.events.list(requestParams);
567
+ let nextPageToken = eventsList.data.nextPageToken;
568
+ if (!params.fetchAll || !nextPageToken) return eventsList;
569
+ const allItems = [...eventsList.data.items];
570
+ while (nextPageToken) {
571
+ eventsList = await client.events.list({
572
+ ...requestParams,
573
+ pageToken: nextPageToken
574
+ });
575
+ allItems.push(...eventsList.data.items);
576
+ nextPageToken = eventsList.data.nextPageToken;
577
+ }
578
+ eventsList.data.items = allItems;
579
+ return eventsList;
580
+ };
581
+ //#endregion
582
+ //#region src/actions/listEvents.ts
583
+ const listEvents = (0, _prismatic_io_spectral.action)({
584
+ display: {
585
+ label: "List Events",
586
+ description: "List all events in a given calendar"
587
+ },
588
+ perform: async (_context, params) => {
589
+ const { config, ...base } = await listAllEvents(params);
590
+ return { data: {
591
+ config: parseReturn(config),
592
+ ...base
593
+ } };
594
+ },
595
+ inputs: {
596
+ googleConnection: connectionInput,
597
+ calendarId,
598
+ fetchAll,
599
+ maxResults,
600
+ pageToken,
601
+ syncToken,
602
+ maxAttendees: (0, _prismatic_io_spectral.input)({
603
+ label: "Max Attendees",
604
+ type: "string",
605
+ required: false,
606
+ placeholder: "Enter maximum attendees",
607
+ example: "10",
608
+ comments: "Maximum number of attendees to include in the response. If there are more attendees, only the participant is returned.",
609
+ clean: (value) => value !== "" ? _prismatic_io_spectral.util.types.toInt(value) : void 0
610
+ }),
611
+ orderBy: (0, _prismatic_io_spectral.input)({
612
+ label: "Order By",
613
+ type: "string",
614
+ model: [{
615
+ label: "Start Time",
616
+ value: "startTime"
617
+ }, {
618
+ label: "Updated",
619
+ value: "updated"
620
+ }],
621
+ required: false,
622
+ placeholder: "Select sort order",
623
+ comments: "The order of events returned in the result. Default is an unspecified, stable order.",
624
+ clean: (value) => value !== "" ? _prismatic_io_spectral.util.types.toString(value) : void 0
625
+ }),
626
+ q: (0, _prismatic_io_spectral.input)({
627
+ label: "Query",
628
+ type: "string",
629
+ required: false,
630
+ placeholder: "Enter search terms",
631
+ example: "team meeting",
632
+ comments: "Free text search terms to find events matching in summary, description, location, or attendee fields.",
633
+ clean: (value) => value !== "" ? _prismatic_io_spectral.util.types.toString(value) : void 0
634
+ }),
635
+ showDeleted: (0, _prismatic_io_spectral.input)({
636
+ label: "Show Deleted",
637
+ type: "boolean",
638
+ default: "false",
639
+ required: false,
640
+ comments: "When true, includes deleted events in the response.",
641
+ clean: _prismatic_io_spectral.util.types.toBool
642
+ }),
643
+ showHiddenInvitations: (0, _prismatic_io_spectral.input)({
644
+ label: "Show Hidden Invitations",
645
+ type: "boolean",
646
+ required: false,
647
+ comments: "When true, includes hidden invitations in the response.",
648
+ clean: _prismatic_io_spectral.util.types.toBool
649
+ }),
650
+ singleEvents: (0, _prismatic_io_spectral.input)({
651
+ label: "Single Events",
652
+ type: "boolean",
653
+ default: "false",
654
+ required: false,
655
+ comments: "When true, expands recurring events into instances and returns only single one-off events and instances, not the underlying recurring events.",
656
+ clean: _prismatic_io_spectral.util.types.toBool
657
+ }),
658
+ timeMin: (0, _prismatic_io_spectral.input)({
659
+ label: "Time Min",
660
+ type: "string",
661
+ required: false,
662
+ placeholder: "Enter start time (ISO 8601 format)",
663
+ example: "2026-06-03T10:00:00-07:00",
664
+ comments: "Lower bound for filtering events by end time. Must be in ISO 8601 format with timezone offset.",
665
+ clean: (value) => value !== "" ? _prismatic_io_spectral.util.types.toString(value) : void 0
666
+ }),
667
+ timeMax: (0, _prismatic_io_spectral.input)({
668
+ label: "Time Max",
669
+ type: "string",
670
+ required: false,
671
+ placeholder: "Enter end time (ISO 8601 format)",
672
+ example: "2026-06-03T10:00:00-07:00",
673
+ comments: "Upper bound for filtering events by start time. Must be in ISO 8601 format with timezone offset.",
674
+ clean: (value) => value !== "" ? _prismatic_io_spectral.util.types.toString(value) : void 0
675
+ }),
676
+ updatedMin: (0, _prismatic_io_spectral.input)({
677
+ label: "Updated Min",
678
+ type: "string",
679
+ required: false,
680
+ placeholder: "Enter minimum update time (RFC 3339)",
681
+ example: "2026-01-01T00:00:00Z",
682
+ comments: "Lower bound for filtering by last modification time (RFC 3339 format). Deleted entries since this time are always included regardless of 'Show Deleted' setting.",
683
+ clean: (value) => value !== "" ? _prismatic_io_spectral.util.types.toString(value) : void 0
684
+ }),
685
+ timeZone: (0, _prismatic_io_spectral.input)({
686
+ label: "Time Zone",
687
+ type: "string",
688
+ required: false,
689
+ placeholder: "Enter timezone (IANA format)",
690
+ example: "America/Chicago",
691
+ comments: "Time zone used in the response.",
692
+ clean: (value) => value !== "" ? _prismatic_io_spectral.util.types.toString(value) : void 0
693
+ })
694
+ }
695
+ });
696
+ //#endregion
697
+ //#region src/actions/rawRequest.ts
698
+ const { debugRequest: _, ...rawRequestInputs } = _prismatic_io_spectral_http.inputs;
699
+ const rawRequest = (0, _prismatic_io_spectral.action)({
700
+ display: {
701
+ label: "Raw Request",
702
+ description: "Send raw HTTP request to Google Calendar"
703
+ },
704
+ inputs: {
705
+ connection: connectionInput,
706
+ ...rawRequestInputs,
707
+ url: {
708
+ ...rawRequestInputs.url,
709
+ comments: "Input the path only (/colors), The base URL is already included (https://www.googleapis.com/calendar/v3). For example, to connect to https://www.googleapis.com/calendar/v3/colors, only /colors is entered in this field.",
710
+ example: "/colors"
711
+ }
712
+ },
713
+ perform: async (context, { connection, ...rawRequestInputs }) => {
714
+ const accessToken = getAccessToken({ connection });
715
+ try {
716
+ const { data } = await (0, _prismatic_io_spectral_http.sendRawRequest)("https://www.googleapis.com/calendar/v3", {
717
+ ...rawRequestInputs,
718
+ debugRequest: context.debug.enabled
719
+ }, { Authorization: `Bearer ${accessToken}` });
720
+ return { data };
721
+ } catch (error) {
722
+ const handled = (0, _prismatic_io_spectral_http.handleErrors)(error);
723
+ const serialized = _prismatic_io_spectral.util.types.toJSON(handled);
724
+ throw new Error(serialized);
725
+ }
726
+ }
727
+ });
728
+ //#endregion
729
+ //#region src/actions/index.ts
730
+ var actions_default = {
731
+ listCalendar,
732
+ createCalendar,
733
+ getCalendar,
734
+ deleteCalendar,
735
+ listEvents,
736
+ createEvent,
737
+ getEvent,
738
+ updateEvent: (0, _prismatic_io_spectral.action)({
739
+ display: {
740
+ label: "Update Event",
741
+ description: "Update the information and metadata of an existing event"
742
+ },
743
+ perform: async (_context, params) => {
744
+ const reminderMethod = _prismatic_io_spectral.util.types.toString(params.remindMethod);
745
+ const remindMinutes = _prismatic_io_spectral.util.types.toInt(params.remindMinutes);
746
+ const reminderInfoNeeded = reminderMethod && remindMinutes;
747
+ const { config, ...base } = await createClient({ connection: params.connection }).events.update({
748
+ calendarId: _prismatic_io_spectral.util.types.toString(params.calendarId),
749
+ eventId: _prismatic_io_spectral.util.types.toString(params.eventId),
750
+ requestBody: {
751
+ summary: _prismatic_io_spectral.util.types.toString(params.summary) || void 0,
752
+ location: _prismatic_io_spectral.util.types.toString(params.eventLocation) || void 0,
753
+ description: _prismatic_io_spectral.util.types.toString(params.description) || void 0,
754
+ start: {
755
+ dateTime: _prismatic_io_spectral.util.types.toString(params.startTime),
756
+ timeZone: _prismatic_io_spectral.util.types.toString(params.timeZone)
757
+ },
758
+ end: {
759
+ dateTime: _prismatic_io_spectral.util.types.toString(params.endTime),
760
+ timeZone: _prismatic_io_spectral.util.types.toString(params.timeZone)
761
+ },
762
+ attendees: params.attendees,
763
+ reminders: {
764
+ overrides: _prismatic_io_spectral.util.types.toBool(params.useDefaultReminder) !== true && reminderInfoNeeded ? [{
765
+ method: _prismatic_io_spectral.util.types.toString(params.remindMethod),
766
+ minutes: _prismatic_io_spectral.util.types.toInt(params.remindMinutes)
767
+ }] : void 0,
768
+ useDefault: _prismatic_io_spectral.util.types.toBool(params.useDefaultReminder)
769
+ }
770
+ },
771
+ sendUpdates: params.sendUpdates || void 0
772
+ });
773
+ return { data: {
774
+ config: parseReturn(config),
775
+ ...base
776
+ } };
777
+ },
778
+ inputs: {
779
+ calendarId,
780
+ eventId,
781
+ summary: {
782
+ ...summary,
783
+ required: false
784
+ },
785
+ description: {
786
+ ...description,
787
+ required: false
788
+ },
789
+ timeZone,
790
+ startTime,
791
+ endTime,
792
+ eventLocation: {
793
+ ...eventLocation,
794
+ required: false
795
+ },
796
+ attendees,
797
+ useDefaultReminder,
798
+ remindMethod,
799
+ remindMinutes,
800
+ connection: connectionInput,
801
+ sendUpdates: {
802
+ ...sendUpdates,
803
+ comments: "Guests who should receive notifications about the event update (for example, title changes, etc.)."
804
+ }
805
+ }
806
+ }),
807
+ deleteEvent,
808
+ rawRequest
809
+ };
810
+ //#endregion
811
+ //#region src/dataSources/index.ts
812
+ var dataSources_default = {
813
+ selectCalendar: (0, _prismatic_io_spectral.dataSource)({
814
+ display: {
815
+ label: "Select Calendar",
816
+ description: "A list of selectable calendars"
817
+ },
818
+ inputs: selectCalendarInputs,
819
+ perform: async (_context, { connection }) => {
820
+ const { data: calendarList } = await listAllCalendars({
821
+ connection,
822
+ fetchAll: true
823
+ });
824
+ const pickListOptions = calendarList.items.map((calendar) => ({
825
+ key: calendar.id,
826
+ label: calendar.summary
827
+ }));
828
+ pickListOptions.sort((a, b) => a.label.localeCompare(b.label));
829
+ return { result: pickListOptions };
830
+ },
831
+ dataSourceType: "picklist",
832
+ examplePayload: { result: [{
833
+ label: "Holidays in United States",
834
+ key: "en.usa#holiday@group.v.calendar.google.com"
835
+ }] }
836
+ }),
837
+ selectEvent: (0, _prismatic_io_spectral.dataSource)({
838
+ display: {
839
+ label: "Select Event",
840
+ description: "A list of selectable events"
841
+ },
842
+ inputs: selectEventInputs,
843
+ perform: async (_context, { connection: googleConnection, calendarId }) => {
844
+ const pickListOptions = (await listAllEvents({
845
+ googleConnection,
846
+ calendarId,
847
+ fetchAll: true
848
+ })).data.items.map((event) => ({
849
+ key: event.id,
850
+ label: `${event.summary} (${event.start.date} to ${event.end.date})`
851
+ }));
852
+ pickListOptions.sort((a, b) => a.label.localeCompare(b.label));
853
+ return { result: pickListOptions };
854
+ },
855
+ dataSourceType: "picklist",
856
+ examplePayload: { result: [{
857
+ label: "New Year's Day (2020-01-01 to 2020-01-02)",
858
+ key: "20200101_q8ue475rr4p7opsd4c0lr7g5pg"
859
+ }] }
860
+ })
861
+ };
862
+ //#endregion
863
+ //#region src/examplePayloads.ts
864
+ const pollEventsTriggerPayload = {
865
+ branch: "default",
866
+ payload: {
867
+ headers: {},
868
+ queryParameters: null,
869
+ rawBody: {
870
+ data: null,
871
+ contentType: null
872
+ },
873
+ paginationState: void 0,
874
+ body: { data: {
875
+ changes: {
876
+ summary: {
877
+ totalChanges: 0,
878
+ created: 0,
879
+ updated: 0,
880
+ deleted: 0,
881
+ syncTokenAvailable: true
882
+ },
883
+ createdEvents: [],
884
+ updatedEvents: [],
885
+ deletedEvents: [],
886
+ allChanges: []
887
+ },
888
+ calendarId: "example@group.calendar.google.com",
889
+ syncToken: "exampleSyncToken"
890
+ } },
891
+ pathFragment: "",
892
+ webhookUrls: {},
893
+ webhookApiKeys: {},
894
+ invokeUrl: "",
895
+ executionId: "RXhhbXBsZUV4ZWN1dGlvblJlc3VsdElkMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY=",
896
+ customer: {
897
+ id: "testCustomerId",
898
+ name: "Test Customer",
899
+ externalId: "testCustomerExternalId"
900
+ },
901
+ instance: {
902
+ id: "SW5zdGFuY2U6OGRiNWQ4NmYtMjBkYS00MzI5LWI2MmMtM2ZhOWFiNDUwYjUw",
903
+ name: "Google Calendar - Polling Events"
904
+ },
905
+ user: {
906
+ id: "testUserId",
907
+ email: "testUserEmail@example.com",
908
+ name: "Test User",
909
+ externalId: "testUserExternalId"
910
+ },
911
+ integration: {
912
+ id: "SW50ZWdyYXRpb246ZDk2YzcxNmItODFmMS00NmQ4LTk0NzItNDU3NDZmMzg2Mjgw",
913
+ name: "Google Calendar - DEV",
914
+ versionSequenceId: "testIntegrationVersionSequenceId",
915
+ externalVersion: ""
916
+ },
917
+ flow: {
918
+ id: "SW50ZWdyYXRpb25GbG93OmM4Y2IzY2UwLTFjYTUtNDVhZC04Yjc1LWNmNDc4YWE1Y2RlYQ==",
919
+ name: "Poll Events",
920
+ stableId: "test-flow-stable-id"
921
+ },
922
+ startedAt: "2026-01-07 14:22:38.313677+00",
923
+ globalDebug: false
924
+ },
925
+ polledNoChanges: false
926
+ };
927
+ const calendarChangeEventsPayload = {
928
+ branch: "Push Notifications",
929
+ payload: {
930
+ headers: {
931
+ Accept: "*/*",
932
+ "Accept-Encoding": "gzip, deflate, br",
933
+ Host: "hooks.example.com",
934
+ "User-Agent": "APIs-Google; (+https://developers.google.com/webmasters/APIs-Google.html)",
935
+ "X-Amz-Cf-Id": "ExampleCloudFrontId123456789012345678901234567890123456==",
936
+ "X-Amzn-Trace-Id": "Root=1-12345678-abcdef0123456789abcdef01",
937
+ "X-Forwarded-For": "192.0.2.1, 198.51.100.1",
938
+ "X-Goog-Changed": "content,properties",
939
+ "X-Goog-Channel-Expiration": "Wed, 07 Jan 2026 07:10:37 GMT",
940
+ "X-Goog-Channel-ID": "12345678-abcd-1234-abcd-123456789012",
941
+ "X-Goog-Message-Number": "123456",
942
+ "X-Goog-Resource-ID": "ExampleResourceId1234567890",
943
+ "X-Goog-Resource-State": "update",
944
+ "X-Goog-Resource-URI": "https://www.googleapis.com/calendar/v3/calendars/primary/events?alt=json"
945
+ },
946
+ queryParameters: null,
947
+ rawBody: {
948
+ data: null,
949
+ contentType: "application/octet-stream"
950
+ },
951
+ body: {
952
+ data: {
953
+ calendar: {
954
+ id: "primary",
955
+ summary: "Example Calendar",
956
+ description: "This is an example calendar",
957
+ timeZone: "America/Chicago"
958
+ },
959
+ events: {
960
+ totalEvents: 2,
961
+ recentEvents: [{
962
+ id: "example_event_1",
963
+ summary: "Team Meeting",
964
+ start: {
965
+ dateTime: "2026-01-07T10:00:00-06:00",
966
+ timeZone: "America/Chicago"
967
+ },
968
+ end: {
969
+ dateTime: "2026-01-07T11:00:00-06:00",
970
+ timeZone: "America/Chicago"
971
+ },
972
+ updated: "2026-01-07T08:30:00.000Z"
973
+ }, {
974
+ id: "example_event_2",
975
+ summary: "Lunch Break",
976
+ start: {
977
+ dateTime: "2026-01-07T12:00:00-06:00",
978
+ timeZone: "America/Chicago"
979
+ },
980
+ end: {
981
+ dateTime: "2026-01-07T13:00:00-06:00",
982
+ timeZone: "America/Chicago"
983
+ },
984
+ updated: "2026-01-07T08:15:00.000Z"
985
+ }]
986
+ },
987
+ notification: {
988
+ resourceState: "update",
989
+ changedFields: "content,properties",
990
+ resourceId: "ExampleResourceId1234567890",
991
+ timestamp: "2026-01-07T14:26:35.618Z"
992
+ }
993
+ },
994
+ contentType: "application/octet-stream"
995
+ },
996
+ pathFragment: "",
997
+ webhookUrls: { "Calendar Change Events": "https://hooks.example.com/trigger/RXhhbXBsZUluc3RhbmNlRmxvd0NvbmZpZ0lkMTIzNDU2Nzg5MDEyMzQ1Njc4OTA=" },
998
+ webhookApiKeys: { "Calendar Change Events": [] },
999
+ invokeUrl: "https://hooks.example.com/trigger/RXhhbXBsZUluc3RhbmNlRmxvd0NvbmZpZ0lkMTIzNDU2Nzg5MDEyMzQ1Njc4OTA=",
1000
+ executionId: "RXhhbXBsZUV4ZWN1dGlvblJlc3VsdElkMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY=",
1001
+ customer: {
1002
+ id: "testCustomerId",
1003
+ name: "Test Customer",
1004
+ externalId: "testCustomerExternalId"
1005
+ },
1006
+ instance: {
1007
+ id: "SW5zdGFuY2U6OGRiNWQ4NmYtMjBkYS00MzI5LWI2MmMtM2ZhOWFiNDUwYjUw",
1008
+ name: "Google Calendar - DEV - Calendar Change Events"
1009
+ },
1010
+ user: {
1011
+ id: "testUserId",
1012
+ email: "testUserEmail@example.com",
1013
+ name: "Test User",
1014
+ externalId: "testUserExternalId"
1015
+ },
1016
+ integration: {
1017
+ id: "SW50ZWdyYXRpb246ZDk2YzcxNmItODFmMS00NmQ4LTk0NzItNDU3NDZmMzg2Mjgw",
1018
+ name: "Google Calendar - DEV",
1019
+ versionSequenceId: "testIntegrationVersionSequenceId",
1020
+ externalVersion: ""
1021
+ },
1022
+ flow: {
1023
+ id: "SW50ZWdyYXRpb25GbG93OmM4Y2IzY2UwLTFjYTUtNDVhZC04Yjc1LWNmNDc4YWE1Y2RlYQ==",
1024
+ name: "Calendar Change Events",
1025
+ stableId: "test-flow-stable-id"
1026
+ },
1027
+ startedAt: "2026-01-07 14:22:38.313677+00",
1028
+ globalDebug: false
1029
+ },
1030
+ response: {
1031
+ statusCode: 200,
1032
+ contentType: "application/json"
1033
+ }
1034
+ };
1035
+ //#endregion
1036
+ //#region src/utils/resolvePollingRecordChanges.ts
1037
+ const resolvePollingRecordChanges = (data) => {
1038
+ const changesObject = data ?? {
1039
+ createdEvents: [],
1040
+ updatedEvents: [],
1041
+ deletedEvents: [],
1042
+ allChanges: []
1043
+ };
1044
+ return [
1045
+ ...(changesObject.createdEvents ?? []).map((record) => ({
1046
+ changeType: "created",
1047
+ record
1048
+ })),
1049
+ ...(changesObject.updatedEvents ?? []).map((record) => ({
1050
+ changeType: "updated",
1051
+ record
1052
+ })),
1053
+ ...(changesObject.deletedEvents ?? []).map((record) => ({
1054
+ changeType: "deleted",
1055
+ record
1056
+ }))
1057
+ ];
1058
+ };
1059
+ //#endregion
1060
+ //#region src/utils/index.ts
1061
+ function getBase64FromUrl(url) {
1062
+ const lastPathSegmentMatch = url.match(/\/([^/]+)$/);
1063
+ return lastPathSegmentMatch ? lastPathSegmentMatch[1] : "";
1064
+ }
1065
+ //#endregion
1066
+ //#region src/helpers/stopWatch.ts
1067
+ const stopWatch = async ({ calendar, channelId, resourceId }) => {
1068
+ await calendar.channels.stop({ requestBody: {
1069
+ id: channelId,
1070
+ resourceId
1071
+ } });
1072
+ };
1073
+ const stopWatchWithErrorHandling = async (params, logger) => {
1074
+ try {
1075
+ await stopWatch(params);
1076
+ logger.info("Previous Calendar watch stopped successfully");
1077
+ } catch (err) {
1078
+ const error = err;
1079
+ logger.warn("Failed to stop previous Calendar watch (continuing):", error?.errors?.[0]?.reason || error?.message);
1080
+ }
1081
+ };
1082
+ //#endregion
1083
+ //#region src/helpers/manageWatch.ts
1084
+ const manageWatch = async ({ calendar, calendarId, webhookAddress, newChannelId, previousWatch, logger }) => {
1085
+ if (previousWatch?.channelId && previousWatch?.resourceId) await stopWatchWithErrorHandling({
1086
+ calendar,
1087
+ channelId: previousWatch.channelId,
1088
+ resourceId: previousWatch.resourceId
1089
+ }, logger);
1090
+ const expiration = Date.now() + 10080 * 60 * 1e3;
1091
+ try {
1092
+ const res = await calendar.events.watch({
1093
+ calendarId,
1094
+ requestBody: {
1095
+ id: newChannelId,
1096
+ type: "web_hook",
1097
+ address: webhookAddress,
1098
+ expiration: _prismatic_io_spectral.util.types.toString(expiration)
1099
+ }
1100
+ });
1101
+ logger.info("New Calendar watch created successfully");
1102
+ if (!res.data.id || !res.data.resourceId || !res.data.expiration) throw new Error("Calendar watch creation returned incomplete data");
1103
+ return {
1104
+ channelId: res.data.id,
1105
+ resourceId: res.data.resourceId,
1106
+ expiration: res.data.expiration
1107
+ };
1108
+ } catch (error) {
1109
+ logger.error("Failed to create Calendar watch. Does the calendar still exist?");
1110
+ throw error;
1111
+ }
1112
+ };
1113
+ //#endregion
1114
+ //#region src/helpers/calendarChangeEventsCreate.ts
1115
+ const calendarChangeEventsCreate = async (context, { connection, calendarId }) => {
1116
+ const calendar = createClient({ connection });
1117
+ const integrationFlowName = context.flow.name;
1118
+ const address = context.webhookUrls[integrationFlowName];
1119
+ const stateKey = getBase64FromUrl(address);
1120
+ const newChannelId = (0, uuid.v4)();
1121
+ const previousWatch = context.crossFlowState[stateKey];
1122
+ context.logger.info(`Started Calendar Change Events Trigger deploy for ${integrationFlowName}`);
1123
+ context.logger.info(`Creating watch for calendar ${calendarId}`);
1124
+ const watchResult = await manageWatch({
1125
+ calendar,
1126
+ calendarId,
1127
+ webhookAddress: address,
1128
+ newChannelId,
1129
+ logger: context.logger,
1130
+ previousWatch
1131
+ });
1132
+ context.crossFlowState[stateKey] = watchResult;
1133
+ context.logger.info("Establishing initial sync token for incremental sync");
1134
+ const syncStateKey = `${stateKey}_syncToken`;
1135
+ const lastListExecutedAtStateKey = `${stateKey}_lastListExecutedAt`;
1136
+ try {
1137
+ const response = await listAllEvents({
1138
+ googleConnection: connection,
1139
+ calendarId,
1140
+ fetchAll: true,
1141
+ singleEvents: true,
1142
+ showDeleted: true
1143
+ });
1144
+ if (response.data.nextSyncToken) {
1145
+ context.crossFlowState[syncStateKey] = response.data.nextSyncToken;
1146
+ context.crossFlowState[lastListExecutedAtStateKey] = (/* @__PURE__ */ new Date()).toISOString();
1147
+ context.logger.info("Initial sync token established. Future notifications will track changes from this point.");
1148
+ } else throw new Error("No sync token found");
1149
+ } catch (error) {
1150
+ context.logger.error("Failed to establish initial sync token:", error instanceof Error ? error.message : "Unknown error");
1151
+ throw error;
1152
+ }
1153
+ context.logger.info(`Finished Calendar Change Events Trigger deploy for ${integrationFlowName}`);
1154
+ };
1155
+ //#endregion
1156
+ //#region src/helpers/calendarChangeEventsDelete.ts
1157
+ const calendarChangeEventsDelete = async (context, { connection }) => {
1158
+ const integrationFlowName = context.flow.name;
1159
+ context.logger.info(`Started Calendar Change Events Trigger delete for ${integrationFlowName}`);
1160
+ const address = context.webhookUrls[integrationFlowName];
1161
+ const stateKey = getBase64FromUrl(address);
1162
+ const syncStateKey = `${stateKey}_syncToken`;
1163
+ const lastListExecutedAtStateKey = `${stateKey}_lastListExecutedAt`;
1164
+ const previousWatch = context.crossFlowState[stateKey];
1165
+ if (previousWatch?.channelId && previousWatch?.resourceId) await stopWatchWithErrorHandling({
1166
+ calendar: createClient({ connection }),
1167
+ channelId: previousWatch.channelId,
1168
+ resourceId: previousWatch.resourceId
1169
+ }, context.logger);
1170
+ else context.logger.info("No previous Calendar watch found in state, nothing to stop");
1171
+ delete context.crossFlowState[stateKey];
1172
+ delete context.crossFlowState[syncStateKey];
1173
+ delete context.crossFlowState[lastListExecutedAtStateKey];
1174
+ context.logger.info(`Finished Calendar Change Events Trigger delete for ${integrationFlowName}`);
1175
+ };
1176
+ //#endregion
1177
+ //#region src/helpers/retrieveIncrementalChanges.ts
1178
+ const retrieveIncrementalChanges = async (connection, calendarId, syncToken) => {
1179
+ const response = await listAllEvents({
1180
+ googleConnection: connection,
1181
+ calendarId,
1182
+ syncToken,
1183
+ fetchAll: true,
1184
+ singleEvents: true,
1185
+ showDeleted: true
1186
+ });
1187
+ return {
1188
+ events: response.data.items || [],
1189
+ nextSyncToken: response.data.nextSyncToken
1190
+ };
1191
+ };
1192
+ //#endregion
1193
+ //#region src/helpers/calendarChangeEventsPerform.ts
1194
+ const calendarChangeEventsPerform = async (context, payload, { connection, calendarId }) => {
1195
+ const headers = _prismatic_io_spectral.util.types.lowerCaseHeaders(payload.headers);
1196
+ const invokeType = headers["prismatic-invoke-type"];
1197
+ const integrationFlowName = context.flow.name;
1198
+ const address = context.webhookUrls[integrationFlowName];
1199
+ const stateKey = getBase64FromUrl(address);
1200
+ const previousWatch = context.crossFlowState[stateKey];
1201
+ if (!previousWatch) throw new Error("No previous watch found in state");
1202
+ if (invokeType === "Scheduled") {
1203
+ if (context.debug.enabled) context.logger.info("Scheduled renewal triggered");
1204
+ const watchResult = await manageWatch({
1205
+ calendar: createClient({ connection }),
1206
+ calendarId,
1207
+ webhookAddress: address,
1208
+ newChannelId: (0, uuid.v4)(),
1209
+ logger: context.logger,
1210
+ previousWatch
1211
+ });
1212
+ context.crossFlowState[stateKey] = watchResult;
1213
+ payload.body.data = watchResult;
1214
+ return {
1215
+ payload,
1216
+ branch: "Log Messages",
1217
+ response: {
1218
+ statusCode: 200,
1219
+ contentType: "application/json"
1220
+ }
1221
+ };
1222
+ }
1223
+ let changeDetails = null;
1224
+ const resourceState = headers["x-goog-resource-state"];
1225
+ const resourceId = headers["x-goog-resource-id"];
1226
+ const changedFields = headers["x-goog-changed"];
1227
+ const isValidChannel = headers["x-goog-channel-id"] === previousWatch.channelId;
1228
+ const isValidResource = resourceId === previousWatch.resourceId;
1229
+ if (!isValidChannel || !isValidResource) throw new Error("Unauthorized webhook call: Channel or resource ID mismatch");
1230
+ if (resourceState === "sync") {
1231
+ if (context.debug.enabled) context.logger.info("Sync notification received");
1232
+ payload.body.data = { notification: {
1233
+ resourceState,
1234
+ resourceId,
1235
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1236
+ type: "sync"
1237
+ } };
1238
+ return {
1239
+ payload,
1240
+ branch: "Log Messages",
1241
+ response: {
1242
+ statusCode: 200,
1243
+ contentType: "application/json"
1244
+ }
1245
+ };
1246
+ }
1247
+ if (context.debug.enabled) context.logger.info("Notification received");
1248
+ if (resourceState === "exists") {
1249
+ const calendar = createClient({ connection });
1250
+ let calendarInfo = null;
1251
+ let changes = null;
1252
+ let syncExpired = false;
1253
+ const calendarResponse = await calendar.calendars.get({ calendarId });
1254
+ calendarInfo = {
1255
+ id: calendarResponse.data.id,
1256
+ summary: calendarResponse.data.summary,
1257
+ description: calendarResponse.data.description,
1258
+ timeZone: calendarResponse.data.timeZone
1259
+ };
1260
+ const syncStateKey = `${stateKey}_syncToken`;
1261
+ const previousSyncToken = context.crossFlowState[syncStateKey];
1262
+ let changedEvents = [];
1263
+ let newSyncToken;
1264
+ if (!previousSyncToken) throw new Error("No sync token found in state");
1265
+ try {
1266
+ if (context.debug.enabled) context.logger.info("Using sync token to fetch incremental changes");
1267
+ const eventsResponse = await retrieveIncrementalChanges(connection, calendarId, previousSyncToken);
1268
+ if (eventsResponse.events && eventsResponse.events.length > 0) changedEvents = eventsResponse.events.map((event) => {
1269
+ let changeType;
1270
+ if (event.status === "cancelled") changeType = "deleted";
1271
+ else if (!event.created || !event.updated) changeType = "unknown";
1272
+ else {
1273
+ const createdTime = new Date(event.created).getTime();
1274
+ changeType = new Date(event.updated).getTime() - createdTime < 1e3 ? "created" : "updated";
1275
+ }
1276
+ return {
1277
+ changeType,
1278
+ id: event.id,
1279
+ summary: event.summary || "[No title]",
1280
+ status: event.status,
1281
+ start: event.start,
1282
+ end: event.end,
1283
+ created: event.created,
1284
+ updated: event.updated,
1285
+ organizer: event.organizer?.email,
1286
+ attendees: event.attendees?.map((a) => ({
1287
+ email: a.email,
1288
+ responseStatus: a.responseStatus,
1289
+ optional: a.optional
1290
+ })),
1291
+ location: event.location,
1292
+ description: event.description,
1293
+ recurringEventId: event.recurringEventId,
1294
+ htmlLink: event.htmlLink
1295
+ };
1296
+ });
1297
+ newSyncToken = eventsResponse.nextSyncToken;
1298
+ } catch (e) {
1299
+ const syncError = e;
1300
+ if (syncError?.response?.status === 410) {
1301
+ context.logger.warn("Sync token expired, re-establishing baseline");
1302
+ syncExpired = true;
1303
+ const lastListExecutedAtStateKey = `${stateKey}_lastListExecutedAt`;
1304
+ const lastListExecutedAt = context.crossFlowState[lastListExecutedAtStateKey];
1305
+ const response = await listAllEvents({
1306
+ googleConnection: connection,
1307
+ calendarId,
1308
+ updatedMin: lastListExecutedAt,
1309
+ fetchAll: true,
1310
+ singleEvents: true,
1311
+ showDeleted: true
1312
+ });
1313
+ context.crossFlowState[lastListExecutedAtStateKey] = (/* @__PURE__ */ new Date()).toISOString();
1314
+ newSyncToken = response.data.nextSyncToken;
1315
+ } else throw syncError;
1316
+ }
1317
+ if (!newSyncToken) throw new Error("No new sync token found to continue incremental sync");
1318
+ context.crossFlowState[syncStateKey] = newSyncToken;
1319
+ if (context.debug.enabled) context.logger.info("Updated sync token for next incremental sync");
1320
+ if (syncExpired) {
1321
+ changes = {
1322
+ summary: {
1323
+ totalChanges: 0,
1324
+ created: 0,
1325
+ updated: 0,
1326
+ deleted: 0,
1327
+ syncTokenAvailable: !!previousSyncToken,
1328
+ syncTokenUpdated: !!newSyncToken
1329
+ },
1330
+ createdEvents: [],
1331
+ updatedEvents: [],
1332
+ deletedEvents: [],
1333
+ allChanges: []
1334
+ };
1335
+ changeDetails = {
1336
+ calendar: calendarInfo,
1337
+ changes,
1338
+ notification: {
1339
+ resourceState: "resync",
1340
+ reason: "sync_token_expired",
1341
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1342
+ }
1343
+ };
1344
+ } else {
1345
+ const createdEvents = changedEvents.filter((e) => e.changeType === "created");
1346
+ const updatedEvents = changedEvents.filter((e) => e.changeType === "updated");
1347
+ const deletedEvents = changedEvents.filter((e) => e.changeType === "deleted");
1348
+ changes = {
1349
+ summary: {
1350
+ totalChanges: changedEvents.length,
1351
+ created: createdEvents.length,
1352
+ updated: updatedEvents.length,
1353
+ deleted: deletedEvents.length,
1354
+ syncTokenAvailable: !!previousSyncToken,
1355
+ syncTokenUpdated: !!newSyncToken
1356
+ },
1357
+ createdEvents,
1358
+ updatedEvents,
1359
+ deletedEvents,
1360
+ allChanges: changedEvents
1361
+ };
1362
+ changeDetails = {
1363
+ calendar: calendarInfo,
1364
+ changes,
1365
+ notification: {
1366
+ resourceState,
1367
+ changedFields,
1368
+ resourceId,
1369
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1370
+ }
1371
+ };
1372
+ }
1373
+ } else changeDetails = {
1374
+ calendar: null,
1375
+ changes: null,
1376
+ notification: {
1377
+ resourceState,
1378
+ changedFields,
1379
+ resourceId,
1380
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1381
+ }
1382
+ };
1383
+ payload.body.data = changeDetails;
1384
+ return {
1385
+ payload,
1386
+ branch: "Push Notifications",
1387
+ response: {
1388
+ statusCode: 200,
1389
+ contentType: "application/json"
1390
+ }
1391
+ };
1392
+ };
1393
+ //#endregion
1394
+ //#region src/helpers/pollEventsTriggerPerform.ts
1395
+ const classifyEvent = (event) => {
1396
+ if (event.status === "cancelled") return "deleted";
1397
+ if (!event.created || !event.updated) return "unknown";
1398
+ const createdTime = new Date(event.created).getTime();
1399
+ return new Date(event.updated).getTime() - createdTime < 1e3 ? "created" : "updated";
1400
+ };
1401
+ const processEvents = (events) => {
1402
+ const createdEvents = [];
1403
+ const updatedEvents = [];
1404
+ const deletedEvents = [];
1405
+ return {
1406
+ processedEvents: events.map((event) => {
1407
+ const changeType = classifyEvent(event);
1408
+ const processed = {
1409
+ changeType,
1410
+ id: event.id,
1411
+ summary: event.summary || "[No title]",
1412
+ status: event.status,
1413
+ start: event.start,
1414
+ end: event.end,
1415
+ created: event.created,
1416
+ updated: event.updated,
1417
+ organizer: event.organizer?.email,
1418
+ attendees: event.attendees?.map((a) => ({
1419
+ email: a.email,
1420
+ responseStatus: a.responseStatus,
1421
+ optional: a.optional
1422
+ })),
1423
+ location: event.location,
1424
+ description: event.description,
1425
+ recurringEventId: event.recurringEventId,
1426
+ htmlLink: event.htmlLink
1427
+ };
1428
+ if (changeType === "created") createdEvents.push(processed);
1429
+ else if (changeType === "updated") updatedEvents.push(processed);
1430
+ else if (changeType === "deleted") deletedEvents.push(processed);
1431
+ return processed;
1432
+ }),
1433
+ createdEvents,
1434
+ updatedEvents,
1435
+ deletedEvents
1436
+ };
1437
+ };
1438
+ const pollEventsTriggerPerform = async (context, payload, { connection, calendarId, lookBackDate }) => {
1439
+ const pollState = context.polling.getState();
1440
+ const isBatching = context.batch?.enabled === true;
1441
+ const incoming = payload.paginationState;
1442
+ const newLastPolledAt = incoming?.drainStartedAt ?? (/* @__PURE__ */ new Date()).toISOString();
1443
+ let syncToken = incoming?.syncToken ?? pollState.syncToken;
1444
+ let events = [];
1445
+ let nextPageToken;
1446
+ let drained = true;
1447
+ try {
1448
+ if (incoming?.pageToken) {
1449
+ const response = await listAllEvents({
1450
+ googleConnection: connection,
1451
+ calendarId,
1452
+ pageToken: incoming.pageToken,
1453
+ fetchAll: false,
1454
+ maxResults: 50,
1455
+ singleEvents: true,
1456
+ showDeleted: true
1457
+ });
1458
+ events = response.data.items || [];
1459
+ nextPageToken = response.data.nextPageToken;
1460
+ if (response.data.nextSyncToken) syncToken = response.data.nextSyncToken;
1461
+ drained = !nextPageToken;
1462
+ } else if (syncToken) if (isBatching) {
1463
+ const response = await listAllEvents({
1464
+ googleConnection: connection,
1465
+ calendarId,
1466
+ syncToken,
1467
+ fetchAll: false,
1468
+ maxResults: 50,
1469
+ singleEvents: true,
1470
+ showDeleted: true
1471
+ });
1472
+ events = response.data.items || [];
1473
+ nextPageToken = response.data.nextPageToken;
1474
+ if (response.data.nextSyncToken) syncToken = response.data.nextSyncToken;
1475
+ drained = !nextPageToken;
1476
+ } else {
1477
+ const result = await retrieveIncrementalChanges(connection, calendarId, syncToken);
1478
+ events = result.events;
1479
+ syncToken = result.nextSyncToken;
1480
+ }
1481
+ else {
1482
+ if (context.debug.enabled) context.logger.info("No sync token found, performing initial sync");
1483
+ const response = await listAllEvents({
1484
+ googleConnection: connection,
1485
+ calendarId,
1486
+ updatedMin: lookBackDate ? (/* @__PURE__ */ new Date(`${lookBackDate}T00:00:00Z`)).toISOString() : newLastPolledAt,
1487
+ fetchAll: !isBatching,
1488
+ maxResults: isBatching ? 50 : void 0,
1489
+ singleEvents: true,
1490
+ showDeleted: true
1491
+ });
1492
+ syncToken = response.data.nextSyncToken;
1493
+ nextPageToken = response.data.nextPageToken;
1494
+ events = response.data.items || [];
1495
+ drained = !nextPageToken;
1496
+ if (!isBatching) events = lookBackDate ? events : [];
1497
+ }
1498
+ } catch (e) {
1499
+ const syncError = e;
1500
+ if (syncError.response?.status === 410) {
1501
+ if (context.debug.enabled) context.logger.warn("Sync token is no longer valid, performing incremental sync from last polled date");
1502
+ const resyncTimeMin = pollState.lastPolledAt;
1503
+ if (context.debug.enabled) context.logger.info(`Resyncing from ${resyncTimeMin}`);
1504
+ const initialResponse = await listAllEvents({
1505
+ googleConnection: connection,
1506
+ calendarId,
1507
+ updatedMin: resyncTimeMin,
1508
+ fetchAll: !isBatching,
1509
+ maxResults: isBatching ? 50 : void 0,
1510
+ singleEvents: true,
1511
+ showDeleted: true
1512
+ });
1513
+ syncToken = initialResponse.data.nextSyncToken;
1514
+ nextPageToken = initialResponse.data.nextPageToken;
1515
+ events = initialResponse.data.items || [];
1516
+ drained = !nextPageToken;
1517
+ if (!syncToken && drained) throw new Error("No sync token found after resync");
1518
+ } else throw syncError;
1519
+ }
1520
+ const { processedEvents, createdEvents, updatedEvents, deletedEvents } = processEvents(events);
1521
+ let nextPaginationState = null;
1522
+ if (!drained) nextPaginationState = {
1523
+ ...syncToken ? { syncToken } : {},
1524
+ pageToken: nextPageToken,
1525
+ drainStartedAt: newLastPolledAt
1526
+ };
1527
+ const stateUpdate = {
1528
+ syncToken,
1529
+ lastPolledAt: drained ? newLastPolledAt : pollState.lastPolledAt
1530
+ };
1531
+ context.polling.setState(stateUpdate);
1532
+ const changes = {
1533
+ summary: {
1534
+ totalChanges: processedEvents.length,
1535
+ created: createdEvents.length,
1536
+ updated: updatedEvents.length,
1537
+ deleted: deletedEvents.length,
1538
+ syncTokenAvailable: !!syncToken
1539
+ },
1540
+ createdEvents,
1541
+ updatedEvents,
1542
+ deletedEvents,
1543
+ allChanges: processedEvents
1544
+ };
1545
+ const polledNoChanges = processedEvents.length === 0 && nextPaginationState === null && !incoming;
1546
+ return {
1547
+ payload: {
1548
+ ...payload,
1549
+ paginationState: nextPaginationState ?? void 0,
1550
+ body: { data: {
1551
+ changes,
1552
+ calendarId,
1553
+ syncToken
1554
+ } }
1555
+ },
1556
+ polledNoChanges
1557
+ };
1558
+ };
1559
+ //#endregion
1560
+ //#region src/triggers/index.ts
1561
+ var triggers_default = {
1562
+ calendarChangeEvents: (0, _prismatic_io_spectral.trigger)({
1563
+ display: {
1564
+ label: "Calendar Change Events",
1565
+ description: "Receive change notifications for a Google Calendar. Automatically creates and manages a Google Calendar push notification subscription when the instance is deployed, and removes the subscription when the instance is deleted."
1566
+ },
1567
+ perform: calendarChangeEventsPerform,
1568
+ inputs: calendarChangeEventsInputs,
1569
+ synchronousResponseSupport: "invalid",
1570
+ scheduleSupport: "valid",
1571
+ allowsBranching: true,
1572
+ staticBranchNames: ["Push Notifications", "Log Messages"],
1573
+ examplePayload: calendarChangeEventsPayload,
1574
+ webhookLifecycleHandlers: {
1575
+ create: calendarChangeEventsCreate,
1576
+ delete: calendarChangeEventsDelete
1577
+ }
1578
+ }),
1579
+ pollEventsTrigger: (0, _prismatic_io_spectral.pollingTrigger)({
1580
+ display: {
1581
+ label: "New and Updated Events",
1582
+ description: "Retrieves existing and ongoing events in a specified Google Calendar. Load history once, check for changes on a schedule, or both."
1583
+ },
1584
+ inputs: pollEventsTriggerInputs,
1585
+ triggerResolverSupport: "valid",
1586
+ batchConfig: { batchSize: 50 },
1587
+ triggerResolver: {
1588
+ resolveItems: (_context, { payload }) => resolvePollingRecordChanges(payload.body.data?.changes),
1589
+ getNextPaginationState: (_context, { payload }) => payload.paginationState ?? null
1590
+ },
1591
+ perform: pollEventsTriggerPerform,
1592
+ examplePayload: pollEventsTriggerPayload
1593
+ })
1594
+ };
1595
+ //#endregion
1596
+ //#region src/index.ts
1597
+ var src_default = (0, _prismatic_io_spectral.component)({
1598
+ key: "google-calendar",
1599
+ public: true,
1600
+ documentationUrl: "https://prismatic.io/docs/components/google-calendar/",
1601
+ display: {
1602
+ label: "Google Calendar",
1603
+ description: "Manage calendars and events in Google Calendar",
1604
+ iconPath: "icon.png",
1605
+ category: "Application Connectors"
1606
+ },
1607
+ actions: actions_default,
1608
+ hooks: { error: _prismatic_io_spectral_http.handleErrors },
1609
+ connections: connections_default,
1610
+ dataSources: dataSources_default,
1611
+ triggers: triggers_default
1612
+ });
1613
+ //#endregion
1614
+ //#region npm-dist/.entry.ts
1615
+ var _entry_default = (0, _prismatic_io_spectral.callableComponent)(src_default);
1616
+ //#endregion
1617
+ module.exports = _entry_default;