@kubun/connector-google-calendar 0.17.2 → 0.17.4

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/api.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { type EntityRecord } from '@kubun/connector';
2
+ import { type GoogleAPIClient } from '@kubun/provider-google';
2
3
  export { type ConnectorErrorKind, classifyHTTPStatus } from '@kubun/connector';
3
- export declare function parseGoogleError(response: Response): Promise<string>;
4
+ export declare function calendarAPIClient(accessToken: string, fetch?: typeof globalThis.fetch): GoogleAPIClient;
4
5
  export type GoogleCalendarResource = {
5
6
  id: string;
6
7
  summary: string;
@@ -43,12 +44,13 @@ export declare function buildCalendarListURL(params: {
43
44
  syncToken?: string;
44
45
  pageToken?: string;
45
46
  }): string;
46
- export declare function buildEventsListURL(params: {
47
+ export type BuildEventsListURLParams = {
47
48
  calendarID: string;
48
49
  syncToken?: string;
49
50
  pageToken?: string;
50
51
  timeMin?: string;
51
- }): string;
52
+ };
53
+ export declare function buildEventsListURL(params: BuildEventsListURLParams): string;
52
54
  export declare function mapCalendarToEntity(calendar: GoogleCalendarResource, accountKey?: string): EntityRecord;
53
55
  export declare function mapEventToEntity(event: GoogleEventResource, calendarID: string): EntityRecord;
54
56
  export declare function buildEventFromParams(params: Record<string, unknown>, options?: {
@@ -79,14 +81,15 @@ export declare function getEventWithETag(config: {
79
81
  record: EntityRecord;
80
82
  etag?: string;
81
83
  }>;
82
- export declare function createEvent(config: {
84
+ export type CreateEventParams = {
83
85
  accessToken: string;
84
86
  calendarID: string;
85
87
  params: Record<string, unknown>;
86
88
  /** The provider event id to insert under (see `deriveEventID`); takes precedence over `params.id`. */
87
89
  eventID?: string;
88
90
  fetch?: typeof globalThis.fetch;
89
- }): Promise<EntityRecord>;
91
+ };
92
+ export declare function createEvent(config: CreateEventParams): Promise<EntityRecord>;
90
93
  export declare function updateEvent(config: {
91
94
  accessToken: string;
92
95
  calendarID: string;
package/lib/api.js CHANGED
@@ -1,22 +1,16 @@
1
1
  import { connectorHTTPError } from '@kubun/connector';
2
+ import { createGoogleAPIClient } from '@kubun/provider-google';
2
3
  import { GoogleCalendarEventConflict } from './errors.js';
3
4
  import { eventHoldsIntent } from './intent.js';
4
5
  export { classifyHTTPStatus } from '@kubun/connector';
5
6
  const CALENDAR_BASE_URL = 'https://www.googleapis.com/calendar/v3';
6
- export async function parseGoogleError(response) {
7
- try {
8
- const body = await response.json();
9
- return body.error?.message ?? `${response.status} ${response.statusText}`;
10
- } catch {
11
- return `${response.status} ${response.statusText}`;
12
- }
13
- }
14
- /**
15
- * Build the error thrown by an action's provider call, tagged with the HTTP
16
- * status and its outcome classification so the caller can distinguish a
17
- * definite rejection from an indeterminate failure.
18
- */ function googleAPIError(status, detail) {
19
- return connectorHTTPError(`Google Calendar API error: ${detail}`, status);
7
+ export function calendarAPIClient(accessToken, fetch) {
8
+ return createGoogleAPIClient({
9
+ api: 'Google Calendar API',
10
+ accessToken,
11
+ fetch,
12
+ toError: ({ httpStatus, message })=>connectorHTTPError(message, httpStatus)
13
+ });
20
14
  }
21
15
  /**
22
16
  * A create that collided with an existing event it could not confirm as its
@@ -158,18 +152,8 @@ export function buildEventFromParams(params, options) {
158
152
  // colour. calendars.get carries neither, so a re-pull from it would clear
159
153
  // `primary` and break resolving the 'primary' alias.
160
154
  export async function getCalendarListEntry(config) {
161
- const fetchFn = config.fetch ?? globalThis.fetch;
162
155
  const url = `${CALENDAR_BASE_URL}/users/me/calendarList/${encodeURIComponent(config.calendarID)}`;
163
- const response = await fetchFn(url, {
164
- headers: {
165
- Authorization: `Bearer ${config.accessToken}`
166
- }
167
- });
168
- if (!response.ok) {
169
- const errorMessage = await parseGoogleError(response);
170
- throw googleAPIError(response.status, errorMessage);
171
- }
172
- return await response.json();
156
+ return calendarAPIClient(config.accessToken, config.fetch).get(url);
173
157
  }
174
158
  // A composite calendarID/eventID sourceID splits on the LAST '/': an eventID
175
159
  // never contains one, but a calendarID (email-like) may.
@@ -184,18 +168,8 @@ export function splitEventSourceID(sourceID) {
184
168
  };
185
169
  }
186
170
  async function fetchEventResource(config) {
187
- const fetchFn = config.fetch ?? globalThis.fetch;
188
171
  const url = `${CALENDAR_BASE_URL}/calendars/${encodeURIComponent(config.calendarID)}/events/${encodeURIComponent(config.eventID)}`;
189
- const response = await fetchFn(url, {
190
- headers: {
191
- Authorization: `Bearer ${config.accessToken}`
192
- }
193
- });
194
- if (!response.ok) {
195
- const errorMessage = await parseGoogleError(response);
196
- throw googleAPIError(response.status, errorMessage);
197
- }
198
- return await response.json();
172
+ return calendarAPIClient(config.accessToken, config.fetch).get(url);
199
173
  }
200
174
  export async function getEvent(config) {
201
175
  const data = await fetchEventResource(config);
@@ -212,20 +186,16 @@ export async function getEventWithETag(config) {
212
186
  };
213
187
  }
214
188
  export async function createEvent(config) {
215
- const fetchFn = config.fetch ?? globalThis.fetch;
189
+ const client = calendarAPIClient(config.accessToken, config.fetch);
216
190
  const event = buildEventFromParams(config.params, {
217
191
  create: true,
218
192
  eventID: config.eventID
219
193
  });
220
194
  const eventID = event.id;
221
195
  const url = `${CALENDAR_BASE_URL}/calendars/${encodeURIComponent(config.calendarID)}/events`;
222
- const response = await fetchFn(url, {
196
+ const response = await client.request(url, {
223
197
  method: 'POST',
224
- headers: {
225
- Authorization: `Bearer ${config.accessToken}`,
226
- 'Content-Type': 'application/json'
227
- },
228
- body: JSON.stringify(event)
198
+ body: event
229
199
  });
230
200
  if (!response.ok) {
231
201
  // A 409 on a caller-chosen id means an event already exists under it. It
@@ -254,31 +224,20 @@ export async function createEvent(config) {
254
224
  }
255
225
  return existing;
256
226
  }
257
- const errorMessage = await parseGoogleError(response);
258
- throw googleAPIError(response.status, errorMessage);
227
+ return client.fail(response);
259
228
  }
260
229
  const data = await response.json();
261
230
  return mapEventToEntity(data, config.calendarID);
262
231
  }
263
232
  export async function updateEvent(config) {
264
- const fetchFn = config.fetch ?? globalThis.fetch;
265
233
  const event = buildEventFromParams(config.params);
266
234
  const url = `${CALENDAR_BASE_URL}/calendars/${encodeURIComponent(config.calendarID)}/events/${encodeURIComponent(config.eventID)}`;
267
- const response = await fetchFn(url, {
235
+ const data = await calendarAPIClient(config.accessToken, config.fetch).send(url, {
268
236
  method: 'PATCH',
269
- headers: {
270
- Authorization: `Bearer ${config.accessToken}`,
271
- 'Content-Type': 'application/json',
272
- ...config.etag != null ? {
273
- 'If-Match': config.etag
274
- } : {}
275
- },
276
- body: JSON.stringify(event)
237
+ body: event,
238
+ headers: config.etag != null ? {
239
+ 'If-Match': config.etag
240
+ } : {}
277
241
  });
278
- if (!response.ok) {
279
- const errorMessage = await parseGoogleError(response);
280
- throw googleAPIError(response.status, errorMessage);
281
- }
282
- const data = await response.json();
283
242
  return mapEventToEntity(data, config.calendarID);
284
243
  }
package/lib/errors.d.ts CHANGED
@@ -20,13 +20,3 @@ export declare class GoogleCalendarEventConflict extends GoogleCalendarError {
20
20
  constructor(sourceID: string, options?: ErrorOptions);
21
21
  get sourceID(): string;
22
22
  }
23
- /**
24
- * A raw provider HTTP failure from a batch/list round trip (`fetchJSON` in
25
- * `provider.ts`), carrying the response status so `fetchChanges` can detect
26
- * an expired sync token (410) and fall back to a full resync.
27
- */
28
- export declare class GoogleCalendarAPIError extends Error {
29
- #private;
30
- constructor(status: number, detail: string);
31
- get status(): number;
32
- }
package/lib/errors.js CHANGED
@@ -29,18 +29,3 @@ import { ConnectorError } from '@kubun/connector';
29
29
  return this.#sourceID;
30
30
  }
31
31
  }
32
- /**
33
- * A raw provider HTTP failure from a batch/list round trip (`fetchJSON` in
34
- * `provider.ts`), carrying the response status so `fetchChanges` can detect
35
- * an expired sync token (410) and fall back to a full resync.
36
- */ export class GoogleCalendarAPIError extends Error {
37
- #status;
38
- constructor(status, detail){
39
- super(`Google Calendar API error: ${detail}`);
40
- this.name = 'GoogleCalendarAPIError';
41
- this.#status = status;
42
- }
43
- get status() {
44
- return this.#status;
45
- }
46
- }
package/lib/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { createGoogleCalendarActionHandler, type GoogleCalendarActionHandlerConfig, } from './actions.js';
2
- export { buildCalendarListURL, buildEventFromParams, buildEventsListURL, createEvent, type GoogleCalendarListResponse, type GoogleCalendarResource, type GoogleEventDateTime, type GoogleEventResource, type GoogleEventsListResponse, mapCalendarToEntity, mapEventToEntity, updateEvent, } from './api.js';
2
+ export { type BuildEventsListURLParams, buildCalendarListURL, buildEventFromParams, buildEventsListURL, type CreateEventParams, createEvent, type GoogleCalendarListResponse, type GoogleCalendarResource, type GoogleEventDateTime, type GoogleEventResource, type GoogleEventsListResponse, mapCalendarToEntity, mapEventToEntity, updateEvent, } from './api.js';
3
3
  export { type CreateGoogleCalendarConnectorOptions, createGoogleCalendarConnector, } from './connector.js';
4
- export { GoogleCalendarAPIError, GoogleCalendarError, type GoogleCalendarErrorCode, type GoogleCalendarErrorOptions, GoogleCalendarEventConflict, } from './errors.js';
4
+ export { GoogleCalendarError, type GoogleCalendarErrorCode, type GoogleCalendarErrorOptions, GoogleCalendarEventConflict, } from './errors.js';
5
5
  export { deriveEventID } from './event-id.js';
6
6
  export { canonicalizeEventIntent, eventHoldsIntent } from './intent.js';
7
7
  export { googleCalendarClusters, googleCalendarDefinition, googleCalendarEventDefinition, } from './model.js';
package/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export { createGoogleCalendarActionHandler } from './actions.js';
2
2
  export { buildCalendarListURL, buildEventFromParams, buildEventsListURL, createEvent, mapCalendarToEntity, mapEventToEntity, updateEvent } from './api.js';
3
3
  export { createGoogleCalendarConnector } from './connector.js';
4
- export { GoogleCalendarAPIError, GoogleCalendarError, GoogleCalendarEventConflict } from './errors.js';
4
+ export { GoogleCalendarError, GoogleCalendarEventConflict } from './errors.js';
5
5
  export { deriveEventID } from './event-id.js';
6
6
  export { canonicalizeEventIntent, eventHoldsIntent } from './intent.js';
7
7
  export { googleCalendarClusters, googleCalendarDefinition, googleCalendarEventDefinition } from './model.js';
package/lib/model.d.ts CHANGED
@@ -41,6 +41,7 @@ export declare const googleCalendarDefinition: {
41
41
  readonly required: ["summary", "sourceService", "sourceID", "lastSyncedAt"];
42
42
  readonly additionalProperties: false;
43
43
  readonly definitions: {
44
+ readonly TimeZone: import("json-schema-typed").JSONSchema.String;
44
45
  readonly SourceService: {
45
46
  readonly type: "string";
46
47
  readonly title: "SourceService";
@@ -59,7 +60,6 @@ export declare const googleCalendarDefinition: {
59
60
  readonly title: "URL";
60
61
  };
61
62
  readonly DateTime: import("json-schema-typed").JSONSchema.String;
62
- readonly TimeZone: import("json-schema-typed").JSONSchema.String;
63
63
  };
64
64
  };
65
65
  readonly fieldsMeta: {};
@@ -131,24 +131,12 @@ export declare const googleCalendarEventDefinition: {
131
131
  readonly required: ["title", "startAt", "allDay", "calendarID", "calendar", "sourceService", "sourceID", "lastSyncedAt"];
132
132
  readonly additionalProperties: false;
133
133
  readonly definitions: {
134
- readonly SourceService: {
135
- readonly type: "string";
136
- readonly title: "SourceService";
137
- readonly minLength: 1;
138
- readonly maxLength: 50;
139
- };
140
134
  readonly SourceID: {
141
135
  readonly type: "string";
142
136
  readonly title: "SourceID";
143
137
  readonly minLength: 1;
144
138
  readonly maxLength: 1400;
145
139
  };
146
- readonly SourceURL: {
147
- readonly type: "string";
148
- readonly format: "uri";
149
- readonly title: "URL";
150
- };
151
- readonly DateTime: import("json-schema-typed").JSONSchema.String;
152
140
  readonly TimeZone: import("json-schema-typed").JSONSchema.String;
153
141
  readonly HtmlLink: {
154
142
  readonly type: "string";
@@ -165,6 +153,18 @@ export declare const googleCalendarEventDefinition: {
165
153
  readonly title: "EventStatus";
166
154
  readonly enum: ["CONFIRMED", "TENTATIVE", "CANCELLED"];
167
155
  };
156
+ readonly SourceService: {
157
+ readonly type: "string";
158
+ readonly title: "SourceService";
159
+ readonly minLength: 1;
160
+ readonly maxLength: 50;
161
+ };
162
+ readonly SourceURL: {
163
+ readonly type: "string";
164
+ readonly format: "uri";
165
+ readonly title: "URL";
166
+ };
167
+ readonly DateTime: import("json-schema-typed").JSONSchema.String;
168
168
  };
169
169
  };
170
170
  readonly fieldsMeta: {
package/lib/model.js CHANGED
@@ -1,7 +1,7 @@
1
- import { connectorClusters, connectorModelIDs } from '@kubun/connector';
1
+ import { connectorClusters, connectorModelIDs, externalEntityDefinition } from '@kubun/connector';
2
2
  import { modelIDs, modelsClusters } from '@kubun/models';
3
3
  import { GraphBuilder } from '@kubun/protocol';
4
- import { dateTimeSchema, docIDSchema, timeZoneSchema, urlSchema } from '@kubun/scalars';
4
+ import { docIDSchema, timeZoneSchema, urlSchema } from '@kubun/scalars';
5
5
  export const googleCalendarDefinition = {
6
6
  name: 'GoogleCalendar',
7
7
  behavior: 'default',
@@ -52,20 +52,7 @@ export const googleCalendarDefinition = {
52
52
  ],
53
53
  additionalProperties: false,
54
54
  definitions: {
55
- SourceService: {
56
- type: 'string',
57
- title: 'SourceService',
58
- minLength: 1,
59
- maxLength: 50
60
- },
61
- SourceID: {
62
- type: 'string',
63
- title: 'SourceID',
64
- minLength: 1,
65
- maxLength: 500
66
- },
67
- SourceURL: urlSchema,
68
- DateTime: dateTimeSchema,
55
+ ...externalEntityDefinition.schema.definitions,
69
56
  TimeZone: timeZoneSchema
70
57
  }
71
58
  },
@@ -150,12 +137,7 @@ export const googleCalendarEventDefinition = {
150
137
  ],
151
138
  additionalProperties: false,
152
139
  definitions: {
153
- SourceService: {
154
- type: 'string',
155
- title: 'SourceService',
156
- minLength: 1,
157
- maxLength: 50
158
- },
140
+ ...externalEntityDefinition.schema.definitions,
159
141
  // A composite calendarID/eventID: calendar id (email-like, up to ~254) +
160
142
  // '/' + a Google event id (up to 1024). 500 is too small.
161
143
  SourceID: {
@@ -164,8 +146,6 @@ export const googleCalendarEventDefinition = {
164
146
  minLength: 1,
165
147
  maxLength: 1400
166
148
  },
167
- SourceURL: urlSchema,
168
- DateTime: dateTimeSchema,
169
149
  TimeZone: timeZoneSchema,
170
150
  HtmlLink: urlSchema,
171
151
  CalendarDocID: docIDSchema,
package/lib/provider.js CHANGED
@@ -1,30 +1,12 @@
1
- import { resolveModelID } from '@kubun/connector';
1
+ import { ConnectorError, fetchOrAbsent, parseMaxAgeDays, resolveModelID } from '@kubun/connector';
2
2
  import { deriveAccountKey } from './account-key.js';
3
- import { buildCalendarListURL, buildEventsListURL, getCalendarListEntry, getEvent, getEventWithETag, mapCalendarToEntity, mapEventToEntity, parseGoogleError, splitEventSourceID } from './api.js';
4
- import { GoogleCalendarAPIError } from './errors.js';
3
+ import { buildCalendarListURL, buildEventsListURL, calendarAPIClient, getCalendarListEntry, getEvent, getEventWithETag, mapCalendarToEntity, mapEventToEntity, splitEventSourceID } from './api.js';
5
4
  import { googleCalendarClusters } from './model.js';
6
5
  const CALENDAR_MODEL_ID = resolveModelID(googleCalendarClusters, 'GoogleCalendar');
7
6
  const CALENDAR_EVENT_MODEL_ID = resolveModelID(googleCalendarClusters, 'GoogleCalendarEvent');
8
- async function fetchJSON(url, credential, fetchFn, signal) {
9
- const response = await fetchFn(url, {
10
- headers: {
11
- Authorization: `Bearer ${credential.accessToken}`
12
- },
13
- signal
14
- });
15
- if (!response.ok) {
16
- throw new GoogleCalendarAPIError(response.status, await parseGoogleError(response));
17
- }
18
- return await response.json();
19
- }
20
7
  function computeTimeMin(boundary) {
21
- if (boundary.maxAge == null) return undefined;
22
- // Parse PnD format (e.g. "P30D" = 30 days)
23
- const match = /^P(\d+)D$/i.exec(boundary.maxAge);
24
- if (match == null) return undefined;
25
- const rawDays = match[1];
26
- if (rawDays == null) return undefined;
27
- const days = Number.parseInt(rawDays, 10);
8
+ const days = parseMaxAgeDays(boundary);
9
+ if (days == null) return undefined;
28
10
  const date = new Date();
29
11
  date.setDate(date.getDate() - days);
30
12
  return date.toISOString();
@@ -37,7 +19,9 @@ async function* fetchAllCalendars(params) {
37
19
  syncToken: isFirstPage ? params.syncToken : undefined,
38
20
  pageToken
39
21
  });
40
- const data = await fetchJSON(url, params.credential, params.fetchFn, params.signal);
22
+ const data = await params.client.get(url, {
23
+ signal: params.signal
24
+ });
41
25
  yield {
42
26
  items: data.items ?? [],
43
27
  nextSyncToken: data.nextSyncToken,
@@ -57,7 +41,9 @@ async function* fetchEventsForCalendar(params) {
57
41
  pageToken,
58
42
  timeMin: params.timeMin
59
43
  });
60
- const data = await fetchJSON(url, params.credential, params.fetchFn, params.signal);
44
+ const data = await params.client.get(url, {
45
+ signal: params.signal
46
+ });
61
47
  yield {
62
48
  items: data.items ?? [],
63
49
  nextSyncToken: data.nextSyncToken
@@ -68,18 +54,13 @@ async function* fetchEventsForCalendar(params) {
68
54
  }
69
55
  export function createGoogleCalendarProvider(config) {
70
56
  const fetchFn = config.fetch ?? globalThis.fetch;
57
+ const client = calendarAPIClient(config.credential.accessToken, fetchFn);
71
58
  const excludeSet = new Set(config.excludeCalendars ?? []);
72
59
  const mintFetchHLC = config.mintFetchHLC;
73
60
  // Derived once per provider instance — a pure function of (iss,sub), so it is
74
61
  // stable across refresh and re-auth. Absent when the credential predates
75
62
  // providerAccount capture.
76
63
  const accountKey = config.credential.providerAccount != null ? deriveAccountKey(config.credential.providerAccount) : undefined;
77
- // getCalendarListEntry/getEvent/getEventWithETag (api.ts) throw a plain Error tagged
78
- // with `.httpStatus` (googleAPIError) — this is the seam fetchEntity/reconcile
79
- // use to turn a 404 into a `present: false` result instead of a throw.
80
- function isNotFound(err) {
81
- return err?.httpStatus === 404;
82
- }
83
64
  async function* fetchAllEntities(params) {
84
65
  // Reuse a window frozen by a prior sync (carried in an expired checkpoint on
85
66
  // the 410 resync path, or a re-driven initial) instead of recomputing from
@@ -87,12 +68,11 @@ export function createGoogleCalendarProvider(config) {
87
68
  const prior = params.checkpoint;
88
69
  const windowStart = prior?.windowStart ?? computeTimeMin(params.boundary);
89
70
  const timeMin = windowStart;
90
- // Phase 1: fetch all calendars (collect all pages first, then yield single batch)
71
+ // Fetch all calendars (collect all pages first, then yield single batch)
91
72
  const allCalendars = [];
92
73
  let calendarListSyncToken;
93
74
  for await (const page of fetchAllCalendars({
94
- credential: config.credential,
95
- fetchFn,
75
+ client,
96
76
  signal: params.signal
97
77
  })){
98
78
  for (const cal of page.items){
@@ -115,7 +95,7 @@ export function createGoogleCalendarProvider(config) {
115
95
  })),
116
96
  hasMore: true
117
97
  };
118
- // Phase 2: fetch events per calendar
98
+ // Fetch events per calendar
119
99
  const calendarTokens = {};
120
100
  let eventBatchYielded = false;
121
101
  for (const [i, calendar] of allCalendars.entries()){
@@ -123,8 +103,7 @@ export function createGoogleCalendarProvider(config) {
123
103
  let eventsSyncToken;
124
104
  for await (const page of fetchEventsForCalendar({
125
105
  calendarID: calendar.id,
126
- credential: config.credential,
127
- fetchFn,
106
+ client,
128
107
  timeMin,
129
108
  signal: params.signal
130
109
  })){
@@ -193,13 +172,12 @@ export function createGoogleCalendarProvider(config) {
193
172
  // Carry the frozen window forward; new calendars are floored by it (not `now`).
194
173
  const windowStart = checkpoint.windowStart ?? computeTimeMin(params.boundary);
195
174
  const timeMin = windowStart;
196
- // Phase 1: fetch calendar list changes using stored syncToken
175
+ // Fetch calendar list changes using stored syncToken
197
176
  const activeCalendars = [];
198
177
  const deletedCalendarIDs = [];
199
178
  let newCalendarListSyncToken;
200
179
  for await (const page of fetchAllCalendars({
201
- credential: config.credential,
202
- fetchFn,
180
+ client,
203
181
  syncToken: checkpoint.calendarListSyncToken,
204
182
  signal: params.signal
205
183
  })){
@@ -230,7 +208,7 @@ export function createGoogleCalendarProvider(config) {
230
208
  })),
231
209
  hasMore: true
232
210
  };
233
- // Phase 2: determine the set of calendars to sync events for
211
+ // Determine the set of calendars to sync events for
234
212
  // Start from the previous calendar set, remove deleted, add newly discovered active ones
235
213
  const prevCalendarTokens = {
236
214
  ...checkpoint.calendars
@@ -263,8 +241,7 @@ export function createGoogleCalendarProvider(config) {
263
241
  let eventsSyncToken;
264
242
  for await (const page of fetchEventsForCalendar({
265
243
  calendarID,
266
- credential: config.credential,
267
- fetchFn,
244
+ client,
268
245
  syncToken: storedToken,
269
246
  timeMin: isNew ? timeMin : undefined,
270
247
  signal: params.signal
@@ -335,41 +312,32 @@ export function createGoogleCalendarProvider(config) {
335
312
  // the latter's sourceID is the composite calendarID/eventID.
336
313
  async function fetchEntity(model, sourceID) {
337
314
  const sourceFetchHLC = mintFetchHLC();
338
- try {
339
- if (model === CALENDAR_MODEL_ID) {
340
- const calendar = await getCalendarListEntry({
315
+ return fetchOrAbsent({
316
+ sourceID,
317
+ sourceFetchHLC,
318
+ read: async ()=>{
319
+ if (model === CALENDAR_MODEL_ID) {
320
+ const calendar = await getCalendarListEntry({
321
+ accessToken: config.credential.accessToken,
322
+ calendarID: sourceID,
323
+ fetch: fetchFn
324
+ });
325
+ return {
326
+ record: mapCalendarToEntity(calendar, accountKey)
327
+ };
328
+ }
329
+ const { calendarID, eventID } = splitEventSourceID(sourceID);
330
+ const record = await getEvent({
341
331
  accessToken: config.credential.accessToken,
342
- calendarID: sourceID,
332
+ calendarID,
333
+ eventID,
343
334
  fetch: fetchFn
344
335
  });
345
336
  return {
346
- present: true,
347
- record: mapCalendarToEntity(calendar, accountKey),
348
- sourceFetchHLC
337
+ record
349
338
  };
350
339
  }
351
- const { calendarID, eventID } = splitEventSourceID(sourceID);
352
- const record = await getEvent({
353
- accessToken: config.credential.accessToken,
354
- calendarID,
355
- eventID,
356
- fetch: fetchFn
357
- });
358
- return {
359
- present: true,
360
- record,
361
- sourceFetchHLC
362
- };
363
- } catch (err) {
364
- if (isNotFound(err)) {
365
- return {
366
- present: false,
367
- sourceID,
368
- sourceFetchHLC
369
- };
370
- }
371
- throw err;
372
- }
340
+ });
373
341
  }
374
342
  // Provider-authoritative re-read for write-back recovery. Unlike
375
343
  // fetchEntity, the present:true branch also surfaces a providerETag when the
@@ -377,44 +345,33 @@ export function createGoogleCalendarProvider(config) {
377
345
  // condition on.
378
346
  async function reconcile(model, sourceID) {
379
347
  const sourceFetchHLC = mintFetchHLC();
380
- try {
381
- if (model === CALENDAR_MODEL_ID) {
382
- const calendar = await getCalendarListEntry({
348
+ return fetchOrAbsent({
349
+ sourceID,
350
+ sourceFetchHLC,
351
+ read: async ()=>{
352
+ if (model === CALENDAR_MODEL_ID) {
353
+ const calendar = await getCalendarListEntry({
354
+ accessToken: config.credential.accessToken,
355
+ calendarID: sourceID,
356
+ fetch: fetchFn
357
+ });
358
+ return {
359
+ record: mapCalendarToEntity(calendar, accountKey)
360
+ };
361
+ }
362
+ const { calendarID, eventID } = splitEventSourceID(sourceID);
363
+ const { record, etag } = await getEventWithETag({
383
364
  accessToken: config.credential.accessToken,
384
- calendarID: sourceID,
365
+ calendarID,
366
+ eventID,
385
367
  fetch: fetchFn
386
368
  });
387
369
  return {
388
- present: true,
389
- record: mapCalendarToEntity(calendar, accountKey),
390
- sourceFetchHLC
391
- };
392
- }
393
- const { calendarID, eventID } = splitEventSourceID(sourceID);
394
- const { record, etag } = await getEventWithETag({
395
- accessToken: config.credential.accessToken,
396
- calendarID,
397
- eventID,
398
- fetch: fetchFn
399
- });
400
- return {
401
- present: true,
402
- record,
403
- sourceFetchHLC,
404
- ...etag != null ? {
370
+ record,
405
371
  providerETag: etag
406
- } : {}
407
- };
408
- } catch (err) {
409
- if (isNotFound(err)) {
410
- return {
411
- present: false,
412
- sourceID,
413
- sourceFetchHLC
414
372
  };
415
373
  }
416
- throw err;
417
- }
374
+ });
418
375
  }
419
376
  return {
420
377
  supportedStrategies: [
@@ -434,7 +391,7 @@ export function createGoogleCalendarProvider(config) {
434
391
  } catch (err) {
435
392
  // 410 Gone = the stored syncToken has expired; Google requires a full
436
393
  // resync. Discard the checkpoint by delegating to fetchAll.
437
- if (err instanceof GoogleCalendarAPIError && err.status === 410) {
394
+ if (err instanceof ConnectorError && err.httpStatus === 410) {
438
395
  yield* fetchAllEntities(params);
439
396
  return;
440
397
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubun/connector-google-calendar",
3
- "version": "0.17.2",
3
+ "version": "0.17.4",
4
4
  "keywords": [],
5
5
  "license": "see LICENSE.md",
6
6
  "sideEffects": false,
@@ -15,13 +15,13 @@
15
15
  "LICENSE.md"
16
16
  ],
17
17
  "dependencies": {
18
- "@kubun/connector": "^0.17.2",
18
+ "@kubun/connector": "^0.17.3",
19
19
  "@kubun/credential-types": "^0.17.2",
20
20
  "@kubun/hlc": "^0.17.0",
21
21
  "@kubun/id": "^0.17.0",
22
22
  "@kubun/models": "^0.17.0",
23
- "@kubun/protocol": "^0.17.1",
24
- "@kubun/provider-google": "^0.17.0",
23
+ "@kubun/protocol": "^0.17.2",
24
+ "@kubun/provider-google": "^0.17.1",
25
25
  "@kubun/scalars": "^0.17.0",
26
26
  "@sozai/codec": "^0.4.0"
27
27
  },