/* SPDX-FileCopyrightText: © 2025 Hornwitser SPDX-License-Identifier: AGPL-3.0-or-later */ import { Info } from "~/shared/utils/luxon"; interface SyncOperation { controller: AbortController, promise: Promise>, } export const useSchedulesStore = defineStore("schedules", () => { const sessionStore = useSessionStore(); const accountStore = useAccountStore(); const state = { activeScheduleId: ref(111), schedules: ref>>(new Map()), pendingSyncs: ref>(new Map()), }; const getters = { activeSchedule: computed(() => { if (state.activeScheduleId.value === undefined) throw Error("No active schedule"); const schedule = state.schedules.value.get(state.activeScheduleId.value); if (!schedule) throw Error("Active schedule has not been fetched"); return schedule; }), }; const actions = { async fetch(id: number) { if (id !== 111) { throw Error("invalid id"); } console.log("schedules store fetch", id); const schedule = state.schedules.value.get(id); if (schedule) { console.log("return cached"); return schedule; } const pending = state.pendingSyncs.value.get(id); if (pending) { console.log("return pending"); return pending.promise; } console.log("return new fetch"); const requestFetch = useRequestFetch(); const controller = new AbortController(); const zone = Info.normalizeZone(accountStore.activeTimezone); const locale = accountStore.activeLocale; const promise = (async () => { try { const apiSchedule = await requestFetch("/api/schedule", { signal: controller.signal }); if (apiSchedule.deleted) { throw new Error("Unexpecetd deleted schedule"); } const schedule = ref(ClientSchedule.fromApi(apiSchedule, { zone, locale })) as Ref; state.schedules.value.set(id, schedule); state.pendingSyncs.value.delete(id); return schedule; } catch (err: any) { if (err.name !== "AbortError") state.pendingSyncs.value.delete(id); throw err; } })(); state.pendingSyncs.value.set(id, { controller, promise, }); return promise; }, async resync(id: number) { if (id !== 111) { throw Error("invalid id"); } const pending = state.pendingSyncs.value.get(id); if (pending) { pending.controller.abort(); } state.schedules.value.delete(id); state.pendingSyncs.value.delete(id); await actions.fetch(id); }, } watch(() => sessionStore.id, (id, oldId) => { for (const [scheduleId, pending] of state.pendingSyncs.value) { console.log("Aborting pending schedule sync", scheduleId, "due session.id change from", oldId, "to", id); pending.controller.abort(); state.pendingSyncs.value.delete(scheduleId); } }) appEventSource?.addEventListener("update", (event) => { if (event.data.type !== "schedule-update") { return; } const schedule = state.schedules.value.get(111); const update = event.data.data; // XXX validate updatedFrom/updatedAt here if (schedule && !schedule.value.deleted && !update.deleted) { const zone = Info.normalizeZone(accountStore.activeTimezone); const locale = accountStore.activeLocale; schedule.value.apiUpdate(update, { zone, locale }) } }); return { ...state, ...getters, ...actions }; });