@mulmoclaude/core 5.1.0 → 5.4.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/README.md +17 -1
- package/assets/helps/error-recovery.md +78 -0
- package/assets/helps/google-calendar-collection.md +92 -12
- package/dist/collection/core/schemaZ.d.ts +15 -2
- package/dist/collection/core/viewChatPolicy.d.ts +15 -3
- package/dist/collection/firestore.cjs +2 -1
- package/dist/collection/firestore.cjs.map +1 -1
- package/dist/collection/firestore.js +3 -2
- package/dist/collection/firestore.js.map +1 -1
- package/dist/collection/index.cjs +15 -3
- package/dist/collection/index.cjs.map +1 -1
- package/dist/collection/index.js +15 -3
- package/dist/collection/index.js.map +1 -1
- package/dist/collection/registry/server/index.cjs +2 -2
- package/dist/collection/registry/server/index.js +2 -2
- package/dist/collection/server/firestoreDocs.d.ts +10 -0
- package/dist/collection/server/index.cjs +2 -2
- package/dist/collection/server/index.js +2 -2
- package/dist/collection-watchers/index.cjs +2 -2
- package/dist/collection-watchers/index.js +2 -2
- package/dist/{discovery-DCMW05Bp.cjs → discovery-B79IxEM6.cjs} +15 -5
- package/dist/discovery-B79IxEM6.cjs.map +1 -0
- package/dist/{discovery-CBjI1lx9.js → discovery-DIGau7M8.js} +15 -5
- package/dist/discovery-DIGau7M8.js.map +1 -0
- package/dist/feeds/server/index.cjs +2 -2
- package/dist/feeds/server/index.js +2 -2
- package/dist/google/calendar.d.ts +24 -0
- package/dist/google/collectionPush.d.ts +45 -2
- package/dist/google/deletePlan.d.ts +32 -0
- package/dist/google/eventDerived.d.ts +19 -0
- package/dist/google/index.cjs +179 -18
- package/dist/google/index.cjs.map +1 -1
- package/dist/google/index.d.ts +4 -2
- package/dist/google/index.js +174 -19
- package/dist/google/index.js.map +1 -1
- package/dist/remote-view/index.cjs +9 -7
- package/dist/remote-view/index.cjs.map +1 -1
- package/dist/remote-view/index.d.ts +3 -3
- package/dist/remote-view/index.js +9 -7
- package/dist/remote-view/index.js.map +1 -1
- package/dist/{server-rA9FSkl2.cjs → server-BUbU9G-J.cjs} +2 -2
- package/dist/{server-rA9FSkl2.cjs.map → server-BUbU9G-J.cjs.map} +1 -1
- package/dist/{server-BMZ_BcPH.js → server-DZ7Cq7s_.js} +2 -2
- package/dist/{server-BMZ_BcPH.js.map → server-DZ7Cq7s_.js.map} +1 -1
- package/package.json +1 -1
- package/dist/discovery-CBjI1lx9.js.map +0 -1
- package/dist/discovery-DCMW05Bp.cjs.map +0 -1
package/dist/google/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { configureGoogleHost, type GoogleLogger } from './host.js';
|
|
2
2
|
export { isIsoDateTimeWithOffset } from './datetime.js';
|
|
3
|
+
export { deleteRefusalMessage, planDelete, type DeleteDecision, type DeleteRefusal } from './deletePlan.js';
|
|
4
|
+
export { conferenceVideoUri, selfResponseStatus } from './eventDerived.js';
|
|
3
5
|
export { isCalendarDateOnly, resolvePartialSpanInput, resolveSpanInput, toEventTimeInput, eventTimeHint, EVENT_TIME_HINT, ALL_DAY_END_HINT, LONE_ALL_DAY_HINT, MIXED_SPAN_HINT, type PartialSpanResult, type PartialSpanTimes, type SpanResult, type SpanTimes, } from './eventSpanInput.js';
|
|
4
6
|
export { googleConfigDir, googleSecretsDir, googleTokenPath, legacyGoogleTokenPath } from './paths.js';
|
|
5
7
|
export { clientSecretPresence, findClientSecretPath, loadClientSecret, type ClientSecretPresence, type InstalledClientSecret } from './clientSecret.js';
|
|
@@ -8,13 +10,13 @@ export { brokerBaseUrl, brokerExchange, brokerRefresh, brokerStart, type BrokerS
|
|
|
8
10
|
export { authorizeGoogle, commitLinkedTokens, getGoogleAccessToken, unlinkGoogle, waitForAuthCode, GOOGLE_AUTH_CANCELLED, GOOGLE_CALENDAR_SCOPE, GOOGLE_CALENDARLIST_SCOPE, GOOGLE_TASKS_SCOPE, GOOGLE_DRIVE_FILE_SCOPE, GOOGLE_SCOPES, type AuthorizeGoogleOptions, type RevokeFetch, } from './auth.js';
|
|
9
11
|
export { createGoogleAuthFlow, googleAuthFlow, type GoogleAuthFlow, type GoogleAuthFlowStatus } from './authFlow.js';
|
|
10
12
|
export { googleApiError, isGoogleApiError, GoogleApiError, DEFAULT_LIST_MAX_RESULTS, HTTP_FORBIDDEN, MAX_LIST_RESULTS } from './apiClient.js';
|
|
11
|
-
export { buildEventPatch, calendarApiError, collectCalendarPages, createCalendarEvent, deleteCalendarEvent, getCalendarColors, canonicalCalendarId, listCalendarEvents, listCalendars, syncCalendarEvents, toCalendarMeta, toCalendarSummary, toEventSummary, updateCalendarEvent, getCalendarEvent, getCalendarMeta, resolveEventSpan, CANCELLED_EVENT_STATUS, HTTP_CONFLICT, HTTP_PRECONDITION_FAILED, type CalendarColorEntry, type CalendarColors, type CalendarEventInput, type CalendarEventSpan, type CalendarEventSummary, type CalendarEventTime, type CalendarListPage, type CalendarMeta, type CalendarSummary, type CalendarSyncResult, type DeleteCalendarEventInput, type FetchedCalendarEvent, type ListEventsInput, type SyncEventsInput, type UpdateCalendarEventInput, syncPageParams, } from './calendar.js';
|
|
13
|
+
export { buildEventPatch, calendarApiError, collectCalendarPages, createCalendarEvent, deleteCalendarEvent, getCalendarColors, canonicalCalendarId, listCalendarEvents, listCalendars, syncCalendarEvents, toCalendarMeta, toCalendarSummary, toEventSummary, updateCalendarEvent, getCalendarEvent, getCalendarMeta, resolveEventSpan, CANCELLED_EVENT_STATUS, EVENT_ABSENT_STATUSES, HTTP_CONFLICT, HTTP_PRECONDITION_FAILED, type CalendarColorEntry, type CalendarColors, type CalendarEventInput, type CalendarEventSpan, type CalendarEventSummary, type CalendarEventTime, type CalendarListPage, type CalendarMeta, type CalendarSummary, type CalendarSyncResult, type DeleteCalendarEventInput, type FetchedCalendarEvent, type ListEventsInput, type SyncEventsInput, type UpdateCalendarEventInput, syncPageParams, } from './calendar.js';
|
|
12
14
|
export { calendarSyncStatePath, claimCalendarSyncIfDue, clearCalendarLastSyncedAt, clearCalendarSyncToken, loadCalendarLastSyncedAt, loadCalendarSyncToken, saveCalendarSyncToken, toolCalendarSyncKey, } from './calendarSyncStore.js';
|
|
13
15
|
export { calendarSyncDueWindowMs, isCalendarSyncDue } from './calendarSyncDue.js';
|
|
14
16
|
export { calendarBackfillPath, markCalendarBackfilled, needsCalendarBackfill } from './calendarBackfillState.js';
|
|
15
17
|
export { stateLockPath, withCalendarStateLock, liveLockClock, type LockClock } from './calendarStateLock.js';
|
|
16
18
|
export { calendarPushStatePath, clearCalendarShadow, loadCalendarShadow, mergeShadow, saveCalendarShadow, toShadowEvent, type ShadowEvent, } from './calendarPushState.js';
|
|
17
|
-
export { isDeniedAccessRole, isUnpushed, locallyEditedIds, pushCalendarForCollection, pushCollectionNow, reportedAccessRole, unsentLocalEdits, type PushOutcomeKind, type CalendarCollectionPushResult, type CalendarPushDeps, type CalendarPushOutcome, type CalendarWriteTarget, } from './collectionPush.js';
|
|
19
|
+
export { isDeniedAccessRole, isUnpushed, locallyEditedIds, pushCalendarForCollection, pushCollectionNow, reportedAccessRole, sweepDeletes, unsentLocalEdits, type DeleteSweep, type DeleteSweepDeps, type PushOutcomeKind, type CalendarCollectionPushResult, type CalendarPushDeps, type CalendarPushOutcome, type CalendarWriteTarget, } from './collectionPush.js';
|
|
18
20
|
export { toGoogleEventTime, zoneSuffixOf } from './pushDateTime.js';
|
|
19
21
|
export { baselineRecord, bySourceField, conflictingFields, fieldText, isClientSettableEventId, locallyChangedFields, locallyDeletedIds, mayAdoptExisting, planRecord, pushableMap, PUSHABLE_SOURCE_FIELDS, type PushableSourceField, type RecordPlan, } from './pushPlan.js';
|
|
20
22
|
export { toCollectionDateTime } from './collectionDateTime.js';
|
package/dist/google/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { n as writeFileAtomic } from "../root-BcS4-Kge.js";
|
|
|
5
5
|
import { fetchWithTimeout } from "../utils/fetch.js";
|
|
6
6
|
import { d as parseIsoDate, f as parseIsoDateTime } from "../itemId-4P9S_ehz.js";
|
|
7
7
|
import { t as mergeIntoExisting } from "../project-B0j_Vf_Q.js";
|
|
8
|
-
import { _t as createForwardingLogger, d as storeFor, n as discoverCollections, rt as getWorkspaceRoot, vt as createHostSlot } from "../discovery-
|
|
8
|
+
import { _t as createForwardingLogger, d as storeFor, n as discoverCollections, rt as getWorkspaceRoot, vt as createHostSlot } from "../discovery-DIGau7M8.js";
|
|
9
9
|
import { t as mulmoConfigDir } from "../paths-CkJxNZ7T.js";
|
|
10
10
|
import { promises } from "node:fs";
|
|
11
11
|
import path, { join } from "node:path";
|
|
@@ -41,6 +41,74 @@ var isIsoDateTimeWithOffset = (value) => {
|
|
|
41
41
|
return isRealCalendarDate(year, month, day) && timeInRange && offsetInRange;
|
|
42
42
|
};
|
|
43
43
|
//#endregion
|
|
44
|
+
//#region src/google/deletePlan.ts
|
|
45
|
+
var ATTENDEES_REASON = "it carries attendees — deleting an invited event withdraws it from their calendars too";
|
|
46
|
+
var ALREADY_GONE_REASON = "it is no longer in Google";
|
|
47
|
+
/** The message the push reports for a refusal, with the event named. */
|
|
48
|
+
var deleteRefusalMessage = (eventId, refusal) => `${eventId}: left in Google because ${refusal.kind === "has-attendees" ? ATTENDEES_REASON : ALREADY_GONE_REASON}`;
|
|
49
|
+
/** Decide one deletion against what Google currently holds.
|
|
50
|
+
*
|
|
51
|
+
* `null` means the event is already absent — a 404/410 on the read. Nothing to
|
|
52
|
+
* delete, and reported apart from a refusal so "we chose not to" never reads
|
|
53
|
+
* as "we could not".
|
|
54
|
+
*
|
|
55
|
+
* ANY attendee refuses, including the entry Google adds for the organiser. A
|
|
56
|
+
* rule that tried to exclude "only me" would have to work out which entry is
|
|
57
|
+
* the user from a payload that may not say, and being wrong there withdraws a
|
|
58
|
+
* real invitation. Solo events — the ones this feature exists for — carry no
|
|
59
|
+
* attendees at all, so the blunt rule costs them nothing. */
|
|
60
|
+
function planDelete(fetched) {
|
|
61
|
+
if (fetched === null) return {
|
|
62
|
+
ok: false,
|
|
63
|
+
kind: "already-gone"
|
|
64
|
+
};
|
|
65
|
+
return fetched.attendeeCount > 0 ? {
|
|
66
|
+
ok: false,
|
|
67
|
+
kind: "has-attendees"
|
|
68
|
+
} : {
|
|
69
|
+
ok: true,
|
|
70
|
+
etag: fetched.etag
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/google/util.ts
|
|
75
|
+
var ONE_SECOND_MS = 1e3;
|
|
76
|
+
var ONE_MINUTE_MS = 6e4;
|
|
77
|
+
function isRecord(value) {
|
|
78
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/google/eventDerived.ts
|
|
82
|
+
var stringAt = (record, key) => typeof record[key] === "string" ? record[key] : "";
|
|
83
|
+
var recordsIn = (value) => Array.isArray(value) ? value.filter(isRecord) : [];
|
|
84
|
+
/** The signed-in user's own `responseStatus` on this event, `""` when Google
|
|
85
|
+
* reported none.
|
|
86
|
+
*
|
|
87
|
+
* `""` is the common case, not an edge one: an event with no attendees has no
|
|
88
|
+
* entry to mark `self`, which is most of a personal calendar. So it means
|
|
89
|
+
* "Google said nothing", never "not going" — filter with `!= "declined"`, not
|
|
90
|
+
* with `== "accepted"`, or every solo event disappears too.
|
|
91
|
+
*
|
|
92
|
+
* Google's values: `needsAction`, `declined`, `tentative`, `accepted`. */
|
|
93
|
+
function selfResponseStatus(attendees) {
|
|
94
|
+
const self = recordsIn(attendees).find((attendee) => attendee.self === true);
|
|
95
|
+
return self === void 0 ? "" : stringAt(self, "responseStatus");
|
|
96
|
+
}
|
|
97
|
+
var VIDEO_ENTRY_POINT = "video";
|
|
98
|
+
/** The URL that joins this event's meeting, `""` when it has none.
|
|
99
|
+
*
|
|
100
|
+
* Only the `video` entry point. The others are a phone number and a dial-in
|
|
101
|
+
* PIN page, and a column named for joining that sometimes holds `tel:` would
|
|
102
|
+
* be worse than one that is empty — the caller can see empty and fall back.
|
|
103
|
+
*
|
|
104
|
+
* `hangoutLink` (#3229) already carries this for Google Meet; this is what
|
|
105
|
+
* reaches the events whose conference is Zoom or Teams. */
|
|
106
|
+
function conferenceVideoUri(conferenceData) {
|
|
107
|
+
if (!isRecord(conferenceData)) return "";
|
|
108
|
+
const video = recordsIn(conferenceData.entryPoints).find((entry) => entry.entryPointType === VIDEO_ENTRY_POINT);
|
|
109
|
+
return video === void 0 ? "" : stringAt(video, "uri");
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
44
112
|
//#region src/google/eventSpanInput.ts
|
|
45
113
|
var DATE_ONLY_RE$1 = /^\d{4}-\d{2}-\d{2}$/;
|
|
46
114
|
/** Whether `value` is a bare calendar day — the all-day spelling of an event
|
|
@@ -156,13 +224,6 @@ function googleSecretsDir(home) {
|
|
|
156
224
|
return join(home ?? homedir(), ".secrets");
|
|
157
225
|
}
|
|
158
226
|
//#endregion
|
|
159
|
-
//#region src/google/util.ts
|
|
160
|
-
var ONE_SECOND_MS = 1e3;
|
|
161
|
-
var ONE_MINUTE_MS = 6e4;
|
|
162
|
-
function isRecord(value) {
|
|
163
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
164
|
-
}
|
|
165
|
-
//#endregion
|
|
166
227
|
//#region src/google/clientSecret.ts
|
|
167
228
|
var isNonEmptyString = (value) => typeof value === "string" && value !== "";
|
|
168
229
|
var isInstalledClientSecret = (value) => {
|
|
@@ -693,6 +754,10 @@ var EVENT_SYNC_PAGE_SIZE = 2500;
|
|
|
693
754
|
var MAX_EVENT_SYNC_PAGES = 200;
|
|
694
755
|
var CALENDAR_META_PAGE_SIZE = 1;
|
|
695
756
|
var HTTP_GONE = 410;
|
|
757
|
+
/** A GET or DELETE on an event that is not there. Exported because the push's
|
|
758
|
+
* delete sweep has to read "already gone" as success, not as a failure to
|
|
759
|
+
* retry — a retained baseline would announce the same phantom deletion on
|
|
760
|
+
* every run. */
|
|
696
761
|
var EVENT_ABSENT_STATUSES = [404, HTTP_GONE];
|
|
697
762
|
/** An `If-Match` write whose etag no longer matches — someone else changed the
|
|
698
763
|
* event since it was read. */
|
|
@@ -710,6 +775,10 @@ var CANCELLED_EVENT_STATUS = "cancelled";
|
|
|
710
775
|
* apart while they share a sync token silently loses events (#2184). */
|
|
711
776
|
var canonicalCalendarId = (calendarId) => calendarId || DEFAULT_CALENDAR_ID;
|
|
712
777
|
var eventsUrl = (calendarId) => `${CALENDAR_BASE_URL}/calendars/${encodeURIComponent(canonicalCalendarId(calendarId))}/events`;
|
|
778
|
+
/** People Google lists on the event, `0` when it carries none. Counts EVERY
|
|
779
|
+
* entry, including the one Google adds for the organiser: see `deletePlan.ts`
|
|
780
|
+
* for why the guard is not cleverer than that. */
|
|
781
|
+
var attendeeCount = (record) => Array.isArray(record.attendees) ? record.attendees.length : 0;
|
|
713
782
|
var eventTime$1 = (value) => {
|
|
714
783
|
if (!isRecord(value)) return "";
|
|
715
784
|
if (typeof value.dateTime === "string") return value.dateTime;
|
|
@@ -733,7 +802,9 @@ var toEventSummary = (value) => {
|
|
|
733
802
|
updated: stringField(record, "updated"),
|
|
734
803
|
transparency: stringField(record, "transparency"),
|
|
735
804
|
eventType: stringField(record, "eventType"),
|
|
736
|
-
hangoutLink: stringField(record, "hangoutLink")
|
|
805
|
+
hangoutLink: stringField(record, "hangoutLink"),
|
|
806
|
+
selfResponseStatus: selfResponseStatus(record.attendees),
|
|
807
|
+
conferenceVideoUri: conferenceVideoUri(record.conferenceData)
|
|
737
808
|
};
|
|
738
809
|
};
|
|
739
810
|
var toCalendarSummary = (value) => {
|
|
@@ -824,9 +895,11 @@ async function updateCalendarEvent(accessToken, input) {
|
|
|
824
895
|
async function getCalendarEvent(accessToken, input) {
|
|
825
896
|
try {
|
|
826
897
|
const fetched = await googleRequest(CALENDAR_API_LABEL, accessToken, eventUrl(input.calendarId, input.eventId));
|
|
898
|
+
const record = asRecord(fetched);
|
|
827
899
|
return {
|
|
828
900
|
event: toEventSummary(fetched),
|
|
829
|
-
etag: stringField(
|
|
901
|
+
etag: stringField(record, "etag"),
|
|
902
|
+
attendeeCount: attendeeCount(record)
|
|
830
903
|
};
|
|
831
904
|
} catch (error) {
|
|
832
905
|
if (isGoogleApiError(error) && EVENT_ABSENT_STATUSES.includes(error.status)) return null;
|
|
@@ -837,7 +910,10 @@ async function getCalendarEvent(accessToken, input) {
|
|
|
837
910
|
* return; a second delete of the same id answers 410, which surfaces as a
|
|
838
911
|
* GoogleApiError rather than being swallowed. */
|
|
839
912
|
async function deleteCalendarEvent(accessToken, input) {
|
|
840
|
-
await googleRequest(CALENDAR_API_LABEL, accessToken, eventUrl(input.calendarId, input.eventId), {
|
|
913
|
+
await googleRequest(CALENDAR_API_LABEL, accessToken, eventUrl(input.calendarId, input.eventId), {
|
|
914
|
+
method: "DELETE",
|
|
915
|
+
...input.ifMatch ? { extraHeaders: { "If-Match": input.ifMatch } } : {}
|
|
916
|
+
});
|
|
841
917
|
}
|
|
842
918
|
async function listCalendarEvents(accessToken, input = {}) {
|
|
843
919
|
const params = new URLSearchParams({
|
|
@@ -1619,7 +1695,9 @@ var UNPUSHABLE_EVENT_FIELDS = {
|
|
|
1619
1695
|
updated: "",
|
|
1620
1696
|
transparency: "",
|
|
1621
1697
|
eventType: "",
|
|
1622
|
-
hangoutLink: ""
|
|
1698
|
+
hangoutLink: "",
|
|
1699
|
+
selfResponseStatus: "",
|
|
1700
|
+
conferenceVideoUri: ""
|
|
1623
1701
|
};
|
|
1624
1702
|
/** A baseline shaped as the event it came from, so the comparison can run
|
|
1625
1703
|
* through the very projection that wrote the record. */
|
|
@@ -1740,7 +1818,9 @@ var liveDeps = {
|
|
|
1740
1818
|
findCollection: findCalendarCollection,
|
|
1741
1819
|
isLinked: async () => Boolean((await loadGoogleTokens())?.refresh_token),
|
|
1742
1820
|
accessToken: getGoogleAccessToken,
|
|
1743
|
-
calendarMeta: liveCalendarMeta
|
|
1821
|
+
calendarMeta: liveCalendarMeta,
|
|
1822
|
+
fetchEvent: getCalendarEvent,
|
|
1823
|
+
deleteEvent: deleteCalendarEvent
|
|
1744
1824
|
};
|
|
1745
1825
|
var localValue = (ctx, record, source) => {
|
|
1746
1826
|
const field = ctx.bySource[source];
|
|
@@ -1950,7 +2030,7 @@ var UNPUSHED_KINDS = ["conflict", "error"];
|
|
|
1950
2030
|
/** Whether this outcome leaves a local edit that only exists locally, so the
|
|
1951
2031
|
* pull must not overwrite the record and the baseline must not advance. */
|
|
1952
2032
|
var isUnpushed = (kind) => UNPUSHED_KINDS.includes(kind);
|
|
1953
|
-
function tally(slug, attempts,
|
|
2033
|
+
function tally(slug, attempts, deletes) {
|
|
1954
2034
|
const outcomes = attempts.map((attempt) => attempt.outcome);
|
|
1955
2035
|
const count = (kind) => outcomes.filter((outcome) => outcome.kind === kind).length;
|
|
1956
2036
|
return {
|
|
@@ -1958,9 +2038,10 @@ function tally(slug, attempts, localDeletes) {
|
|
|
1958
2038
|
created: count("created"),
|
|
1959
2039
|
updated: count("updated"),
|
|
1960
2040
|
conflicts: count("conflict"),
|
|
1961
|
-
localDeletes,
|
|
1962
|
-
|
|
1963
|
-
|
|
2041
|
+
localDeletes: deletes.seen,
|
|
2042
|
+
deletedInGoogle: deletes.deleted.length,
|
|
2043
|
+
skipped: [...outcomes.flatMap((outcome) => outcome.kind === "skipped" ? [outcome.message] : []), ...deletes.skipped],
|
|
2044
|
+
errors: [...outcomes.flatMap((outcome) => outcome.kind === "error" ? [outcome.message] : []), ...deletes.errors],
|
|
1964
2045
|
unpushedIds: attempts.filter((attempt) => isUnpushed(attempt.outcome.kind)).map((attempt) => attempt.eventId)
|
|
1965
2046
|
};
|
|
1966
2047
|
}
|
|
@@ -1989,6 +2070,69 @@ async function unsentLocalEdits(collection, workspaceRoot) {
|
|
|
1989
2070
|
const shadow = await loadCalendarShadow(schema.googleCalendar?.calendarId, workspaceRoot);
|
|
1990
2071
|
return locallyEditedIds(await storeFor(collection, { workspaceRoot }).list(), shadow, pushableMap(schema.googleCalendar?.map ?? {}), schema.primaryKey, schema.fields);
|
|
1991
2072
|
}
|
|
2073
|
+
var noDeletes = (seen) => ({
|
|
2074
|
+
seen,
|
|
2075
|
+
deleted: [],
|
|
2076
|
+
skipped: [],
|
|
2077
|
+
errors: []
|
|
2078
|
+
});
|
|
2079
|
+
/** A delete whose event changed in Google between the guard reading it and the
|
|
2080
|
+
* delete landing. Reported rather than retried: what changed may be the very
|
|
2081
|
+
* thing the guard refuses, and re-reading in a loop would race the same way. */
|
|
2082
|
+
var staleDeleteMessage = (eventId) => `${eventId}: left in Google because it changed there while this push was checking it — press Push again to re-check`;
|
|
2083
|
+
/** Delete ONE event and forget its baseline, or say why it was left alone.
|
|
2084
|
+
*
|
|
2085
|
+
* The baseline entry goes only after Google confirms, and it must go: the
|
|
2086
|
+
* baseline is what `locallyDeletedIds` reads, so a kept entry would re-report
|
|
2087
|
+
* the same deletion on every run forever. A refusal keeps its entry on
|
|
2088
|
+
* purpose — the event IS still standing in Google, and the report is the only
|
|
2089
|
+
* thing saying so.
|
|
2090
|
+
*
|
|
2091
|
+
* The delete carries the etag the guard read. Without it, an attendee added
|
|
2092
|
+
* between the two requests would be withdrawn by a decision taken before they
|
|
2093
|
+
* existed — the same race `updateCalendarEvent` already guards, and it costs
|
|
2094
|
+
* more here because a delete cannot be undone from this side. */
|
|
2095
|
+
async function propagateOneDelete(eventId, deps) {
|
|
2096
|
+
const decision = planDelete(await deps.fetchEvent(eventId));
|
|
2097
|
+
if (!decision.ok) {
|
|
2098
|
+
if (decision.kind === "already-gone") {
|
|
2099
|
+
await deps.forget(eventId);
|
|
2100
|
+
return {};
|
|
2101
|
+
}
|
|
2102
|
+
return { skipped: [deleteRefusalMessage(eventId, decision)] };
|
|
2103
|
+
}
|
|
2104
|
+
try {
|
|
2105
|
+
await deps.deleteEvent(eventId, decision.etag);
|
|
2106
|
+
} catch (error) {
|
|
2107
|
+
if (!isGoogleApiError(error)) throw error;
|
|
2108
|
+
if (EVENT_ABSENT_STATUSES.includes(error.status)) {
|
|
2109
|
+
await deps.forget(eventId);
|
|
2110
|
+
return {};
|
|
2111
|
+
}
|
|
2112
|
+
if (error.status !== 412) throw error;
|
|
2113
|
+
return { skipped: [staleDeleteMessage(eventId)] };
|
|
2114
|
+
}
|
|
2115
|
+
await deps.forget(eventId);
|
|
2116
|
+
log.info("google", "calendar event deleted by push", { id: eventId });
|
|
2117
|
+
return { deleted: [eventId] };
|
|
2118
|
+
}
|
|
2119
|
+
/** Carry every local deletion, when the collection opted in.
|
|
2120
|
+
*
|
|
2121
|
+
* Sequential rather than concurrent: each one writes the shared baseline file,
|
|
2122
|
+
* and an interrupted run has to leave a state the next run resumes from rather
|
|
2123
|
+
* than a partially applied batch. */
|
|
2124
|
+
async function sweepDeletes(eventIds, propagate, deps) {
|
|
2125
|
+
if (!propagate || eventIds.length === 0) return noDeletes(eventIds.length);
|
|
2126
|
+
const sweep = noDeletes(eventIds.length);
|
|
2127
|
+
for (const eventId of eventIds) try {
|
|
2128
|
+
const one = await propagateOneDelete(eventId, deps);
|
|
2129
|
+
sweep.deleted.push(...one.deleted ?? []);
|
|
2130
|
+
sweep.skipped.push(...one.skipped ?? []);
|
|
2131
|
+
} catch (error) {
|
|
2132
|
+
sweep.errors.push(`${eventId}: could not be deleted in Google — ${errorMessage(error)}`);
|
|
2133
|
+
}
|
|
2134
|
+
return sweep;
|
|
2135
|
+
}
|
|
1992
2136
|
/** Push one collection WITHOUT taking the calendar lock.
|
|
1993
2137
|
*
|
|
1994
2138
|
* The caller must already hold it. The scheduled push→pull cycle
|
|
@@ -2027,7 +2171,18 @@ async function pushCollectionNow(collection, workspaceRoot, deps = liveDeps) {
|
|
|
2027
2171
|
});
|
|
2028
2172
|
await saveCalendarShadow(calendarId, pushedShadow([outcome]), workspaceRoot);
|
|
2029
2173
|
}
|
|
2030
|
-
const result = tally(slug, attempts, locallyDeletedIds(shadow, records.map((record) => fieldText(record[schema.primaryKey]))).
|
|
2174
|
+
const result = tally(slug, attempts, await sweepDeletes(locallyDeletedIds(shadow, records.map((record) => fieldText(record[schema.primaryKey]))), schema.googleCalendar?.propagateDeletes === true, {
|
|
2175
|
+
fetchEvent: (eventId) => (deps.fetchEvent ?? getCalendarEvent)(accessToken, {
|
|
2176
|
+
calendarId,
|
|
2177
|
+
eventId
|
|
2178
|
+
}),
|
|
2179
|
+
deleteEvent: (eventId, ifMatch) => (deps.deleteEvent ?? deleteCalendarEvent)(accessToken, {
|
|
2180
|
+
calendarId,
|
|
2181
|
+
eventId,
|
|
2182
|
+
ifMatch
|
|
2183
|
+
}),
|
|
2184
|
+
forget: (eventId) => saveCalendarShadow(calendarId, { [eventId]: null }, workspaceRoot)
|
|
2185
|
+
}));
|
|
2031
2186
|
if (result.errors.length > 0) log.warn("google", "calendar push finished with errors", {
|
|
2032
2187
|
slug,
|
|
2033
2188
|
errors: result.errors.length
|
|
@@ -2985,6 +3140,6 @@ async function deleteDriveFile(accessToken, input) {
|
|
|
2985
3140
|
await googleRequest(DRIVE_API_LABEL, accessToken, `${DRIVE_FILES_URL}/${encodeURIComponent(input.fileId)}`, { method: "DELETE" });
|
|
2986
3141
|
}
|
|
2987
3142
|
//#endregion
|
|
2988
|
-
export { ALL_DAY_END_HINT, CANCELLED_EVENT_STATUS, DEFAULT_LIST_MAX_RESULTS, EVENT_TIME_HINT, GOOGLE_AUTH_CANCELLED, GOOGLE_CALENDARLIST_SCOPE, GOOGLE_CALENDAR_SCOPE, GOOGLE_CALENDAR_SYNC_TASK_ID, GOOGLE_DRIVE_FILE_SCOPE, GOOGLE_SCOPES, GOOGLE_TASKS_SCOPE, GoogleApiError, HTTP_CONFLICT, HTTP_FORBIDDEN, HTTP_PRECONDITION_FAILED, LONE_ALL_DAY_HINT, MAX_LIST_RESULTS, MIXED_SPAN_HINT, PARTIAL_CALENDAR_WINDOW, PROTECTION_UNKNOWN, PUSHABLE_SOURCE_FIELDS, allUnpushed, anySyncedCollectionSurvives, applyPlanFor, assertSafeMimeType, authorizeGoogle, baselineRecord, brokerBaseUrl, brokerExchange, brokerRefresh, brokerStart, buildEventPatch, buildMultipartBody, buildTaskPatch, bySourceField, calendarApiError, calendarBackfillPath, calendarPushStatePath, calendarSyncDueWindowMs, calendarSyncStatePath, canonicalCalendarId, canonicalTaskListId, claimCalendarSyncIfDue, classifyDelete, classifyWrite, clearCalendarLastSyncedAt, clearCalendarShadow, clearCalendarSyncToken, clientSecretPresence, collectCalendarPages, collectionsNeedingBackfill, commitLinkedTokens, completeTask, configureGoogleHost, conflictingFields, createCalendarEvent, createDriveFile, createGoogleAuthFlow, createTask, deleteCalendarEvent, deleteDriveFile, deleteGoogleTokens, deleteTask, eventTimeHint, fieldText, findClientSecretPath, getCalendarColors, getCalendarEvent, getCalendarMeta, getGoogleAccessToken, googleApiError, googleAuthFlow, googleCalendarSyncTaskDef, googleConfigDir, googleSecretsDir, googleTokenPath, groupByCalendar, groupsNeedingBackfill, heldBack, isCalendarDateOnly, isCalendarSyncDue, isClientSettableEventId, isDeniedAccessRole, isGoogleApiError, isIsoDateTimeWithOffset, isTextMimeType, isUnpushed, legacyGoogleTokenPath, listCalendarEvents, listCalendars, listDriveFiles, listTaskLists, listTasks, liveLockClock, loadCalendarLastSyncedAt, loadCalendarShadow, loadCalendarSyncToken, loadClientSecret, loadGoogleTokens, locallyChangedFields, locallyDeletedIds, locallyEditedIds, markCalendarBackfilled, mayAdoptExisting, mergeGoogleTokens, mergeIntoExisting, mergeShadow, needsCalendarBackfill, orphanedCalendarId, pickBoundary, planRecord, pullProtectionFor, pullableEvents, pushAndProtect, pushCalendarForCollection, pushCollectionNow, pushableMap, readDriveFile, releaseOrphanedCalendarToken, reportedAccessRole, resolveEventSpan, resolvePartialSpanInput, resolveSpanInput, resumableToken, saveCalendarShadow, saveCalendarSyncToken, saveGoogleTokens, shadowUpdates, stateLockPath, syncCalendarEvents, syncCalendarForCollection, syncCalendarGroup, syncDueCalendarCollections, syncNewCalendarCollections, syncPageParams, toCalendarMeta, toCalendarSummary, toCollectionDateTime, toCollectionRecord, toDriveFileSummary, toEventSummary, toEventTimeInput, toGoogleEventTime, toShadowEvent, toTaskListSummary, toTaskSummary, toolCalendarSyncKey, uncompleteTask, unlinkGoogle, unpushedFor, unsentEditGuard, unsentLocalEdits, updateCalendarEvent, updateTask, waitForAuthCode, windowAdvance, withCalendarLock, withCalendarStateLock, withKeyedLock, withPartialWindowError, zoneSuffixOf };
|
|
3143
|
+
export { ALL_DAY_END_HINT, CANCELLED_EVENT_STATUS, DEFAULT_LIST_MAX_RESULTS, EVENT_ABSENT_STATUSES, EVENT_TIME_HINT, GOOGLE_AUTH_CANCELLED, GOOGLE_CALENDARLIST_SCOPE, GOOGLE_CALENDAR_SCOPE, GOOGLE_CALENDAR_SYNC_TASK_ID, GOOGLE_DRIVE_FILE_SCOPE, GOOGLE_SCOPES, GOOGLE_TASKS_SCOPE, GoogleApiError, HTTP_CONFLICT, HTTP_FORBIDDEN, HTTP_PRECONDITION_FAILED, LONE_ALL_DAY_HINT, MAX_LIST_RESULTS, MIXED_SPAN_HINT, PARTIAL_CALENDAR_WINDOW, PROTECTION_UNKNOWN, PUSHABLE_SOURCE_FIELDS, allUnpushed, anySyncedCollectionSurvives, applyPlanFor, assertSafeMimeType, authorizeGoogle, baselineRecord, brokerBaseUrl, brokerExchange, brokerRefresh, brokerStart, buildEventPatch, buildMultipartBody, buildTaskPatch, bySourceField, calendarApiError, calendarBackfillPath, calendarPushStatePath, calendarSyncDueWindowMs, calendarSyncStatePath, canonicalCalendarId, canonicalTaskListId, claimCalendarSyncIfDue, classifyDelete, classifyWrite, clearCalendarLastSyncedAt, clearCalendarShadow, clearCalendarSyncToken, clientSecretPresence, collectCalendarPages, collectionsNeedingBackfill, commitLinkedTokens, completeTask, conferenceVideoUri, configureGoogleHost, conflictingFields, createCalendarEvent, createDriveFile, createGoogleAuthFlow, createTask, deleteCalendarEvent, deleteDriveFile, deleteGoogleTokens, deleteRefusalMessage, deleteTask, eventTimeHint, fieldText, findClientSecretPath, getCalendarColors, getCalendarEvent, getCalendarMeta, getGoogleAccessToken, googleApiError, googleAuthFlow, googleCalendarSyncTaskDef, googleConfigDir, googleSecretsDir, googleTokenPath, groupByCalendar, groupsNeedingBackfill, heldBack, isCalendarDateOnly, isCalendarSyncDue, isClientSettableEventId, isDeniedAccessRole, isGoogleApiError, isIsoDateTimeWithOffset, isTextMimeType, isUnpushed, legacyGoogleTokenPath, listCalendarEvents, listCalendars, listDriveFiles, listTaskLists, listTasks, liveLockClock, loadCalendarLastSyncedAt, loadCalendarShadow, loadCalendarSyncToken, loadClientSecret, loadGoogleTokens, locallyChangedFields, locallyDeletedIds, locallyEditedIds, markCalendarBackfilled, mayAdoptExisting, mergeGoogleTokens, mergeIntoExisting, mergeShadow, needsCalendarBackfill, orphanedCalendarId, pickBoundary, planDelete, planRecord, pullProtectionFor, pullableEvents, pushAndProtect, pushCalendarForCollection, pushCollectionNow, pushableMap, readDriveFile, releaseOrphanedCalendarToken, reportedAccessRole, resolveEventSpan, resolvePartialSpanInput, resolveSpanInput, resumableToken, saveCalendarShadow, saveCalendarSyncToken, saveGoogleTokens, selfResponseStatus, shadowUpdates, stateLockPath, sweepDeletes, syncCalendarEvents, syncCalendarForCollection, syncCalendarGroup, syncDueCalendarCollections, syncNewCalendarCollections, syncPageParams, toCalendarMeta, toCalendarSummary, toCollectionDateTime, toCollectionRecord, toDriveFileSummary, toEventSummary, toEventTimeInput, toGoogleEventTime, toShadowEvent, toTaskListSummary, toTaskSummary, toolCalendarSyncKey, uncompleteTask, unlinkGoogle, unpushedFor, unsentEditGuard, unsentLocalEdits, updateCalendarEvent, updateTask, waitForAuthCode, windowAdvance, withCalendarLock, withCalendarStateLock, withKeyedLock, withPartialWindowError, zoneSuffixOf };
|
|
2989
3144
|
|
|
2990
3145
|
//# sourceMappingURL=index.js.map
|