diff --git a/README.md b/README.md index 174cf0f..4dc692a 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ cronos list cronos list --date 2025-04-10 cronos next cronos add --date 2025-04-10 "Lunch 12pm-1pm #green" -cronos delete --id event-12-1713456789 +cronos delete --id event- ``` ## Keyboard Shortcuts diff --git a/src/cli/commands/list.ts b/src/cli/commands/list.ts index 55a0823..67fdf1c 100644 --- a/src/cli/commands/list.ts +++ b/src/cli/commands/list.ts @@ -1,4 +1,4 @@ -import { getAllEvents } from "@features/events/eventsState"; +import { findAllEvents } from "@data/repository"; import type { CalendarEvent } from "@shared/types"; import { Effect } from "effect"; import { formatEventLine } from "../format"; @@ -57,7 +57,7 @@ export function runList(options: ListOptions): void { return; } - const allEvents = Effect.runSync(getAllEvents); + const allEvents = Effect.runSync(findAllEvents()); const filtered = date ? allEvents.filter((event) => event.date === date) : filterByRange(allEvents, from, to); diff --git a/src/cli/help.ts b/src/cli/help.ts index 3be902b..317a94f 100644 --- a/src/cli/help.ts +++ b/src/cli/help.ts @@ -14,6 +14,6 @@ Examples: cronos list --date 2025-04-10 cronos next cronos add --date 2025-04-10 "Lunch 12pm-1pm #green" - cronos delete --id event-12-1713456789 + cronos delete --id event- `); } diff --git a/src/data/db.ts b/src/data/db.ts index 8f406cd..29fc07f 100644 --- a/src/data/db.ts +++ b/src/data/db.ts @@ -1,4 +1,5 @@ import { Database } from "bun:sqlite"; +import { Effect, Schedule } from "effect"; import { ensureDatabaseDir, getDatabasePath } from "./config"; import { runMigrations } from "./migrations"; @@ -31,11 +32,44 @@ export function initDatabase(): void { // Enable WAL mode for better concurrent read performance db.run("PRAGMA journal_mode = WAL"); + db.run("PRAGMA busy_timeout = 5000"); // Run any pending migrations runMigrations(db); } +const SQLITE_BUSY_RETRY_LIMIT = 5; +const SQLITE_BUSY_RETRY_BASE_DELAY = "50 millis"; + +function isSqliteBusyError(error: unknown): boolean { + if (!error) return false; + if (error instanceof Error && error.message.includes("SQLITE_BUSY")) { + return true; + } + if (typeof error === "object" && "code" in error) { + return (error as { code?: string }).code === "SQLITE_BUSY"; + } + return false; +} + +const sqliteBusyRetrySchedule = Schedule.recurWhile(isSqliteBusyError).pipe( + Schedule.intersect(Schedule.exponential(SQLITE_BUSY_RETRY_BASE_DELAY)), + Schedule.intersect(Schedule.recurs(SQLITE_BUSY_RETRY_LIMIT)), +); + +export function withDbWrite( + run: (db: Database) => A, +): Effect.Effect { + return Effect.retry( + Effect.try({ + try: () => run(getDatabase()), + catch: (error) => + error instanceof Error ? error : new Error(String(error)), + }), + sqliteBusyRetrySchedule, + ); +} + /** * Close the database connection. * Useful for cleanup in tests or graceful shutdown. diff --git a/src/data/repository.ts b/src/data/repository.ts index 85c490b..477625c 100644 --- a/src/data/repository.ts +++ b/src/data/repository.ts @@ -1,6 +1,6 @@ import type { CalendarEvent, ColorName } from "@shared/types"; import { Effect } from "effect"; -import { getDatabase } from "./db"; +import { getDatabase, withDbWrite } from "./db"; interface EventRow { id: string; @@ -64,8 +64,7 @@ function rowToEvent(row: EventRow): CalendarEvent { * Insert a new event into the database. */ export const insertEvent = (event: CalendarEvent) => - Effect.sync(() => { - const db = getDatabase(); + withDbWrite((db) => { const now = event.updatedAt ?? new Date().toISOString(); const stmt = db.prepare(` INSERT INTO events ( @@ -107,9 +106,7 @@ export const updateEventById = ( id: string, updates: Partial>, ) => - Effect.sync(() => { - const db = getDatabase(); - + withDbWrite((db) => { // Build dynamic update query based on provided fields const setClauses: string[] = []; const values: (string | number | null)[] = []; @@ -177,8 +174,7 @@ export const updateEventById = ( * Returns true if an event was deleted, false otherwise. */ export const deleteEventById = (id: string) => - Effect.sync(() => { - const db = getDatabase(); + withDbWrite((db) => { const stmt = db.prepare("DELETE FROM events WHERE id = ?"); const result = stmt.run(id); return result.changes > 0; @@ -254,27 +250,3 @@ export const findEventsMissingGoogleId = () => const rows = stmt.all() as EventRow[]; return rows.map(rowToEvent); }); - -/** - * Get the highest event ID counter value from existing events. - * Used to restore the counter on app startup. - */ -export const getMaxEventIdCounter = () => - Effect.sync(() => { - const db = getDatabase(); - const stmt = db.prepare("SELECT id FROM events"); - const rows = stmt.all() as { id: string }[]; - - let maxCounter = 0; - for (const row of rows) { - // Parse event IDs like "event-123-1234567890" - const match = row.id.match(/^event-(\d+)-/); - if (match?.[1]) { - const counter = parseInt(match[1], 10); - if (counter > maxCounter) { - maxCounter = counter; - } - } - } - return maxCounter; - }); diff --git a/src/features/events/eventsService.ts b/src/features/events/eventsService.ts index c63837a..53e99b9 100644 --- a/src/features/events/eventsService.ts +++ b/src/features/events/eventsService.ts @@ -1,14 +1,12 @@ import { deleteEventById, findAllEvents, - getMaxEventIdCounter, insertEvent, updateEventById, } from "@data/repository"; import type { CalendarEvent } from "@shared/types"; export const loadAllEvents = findAllEvents; -export const loadMaxEventIdCounter = getMaxEventIdCounter; export const persistEventCreate = (event: CalendarEvent) => insertEvent(event); diff --git a/src/features/events/eventsState.ts b/src/features/events/eventsState.ts index b98cbc1..8bb9a05 100644 --- a/src/features/events/eventsState.ts +++ b/src/features/events/eventsState.ts @@ -4,7 +4,6 @@ import { Effect, SubscriptionRef } from "effect"; import { MAX_EVENTS } from "./constants"; import { loadAllEvents, - loadMaxEventIdCounter, persistEventCreate, persistEventDelete, persistEventUpdate, @@ -13,7 +12,6 @@ import { eventStoreRef, generateEventId, groupEventsByDate, - restoreEventIdCounter, setEventStore, sortDayEvents, useEventStore, @@ -28,10 +26,6 @@ export const initEventStore = Effect.gen(function* () { const events = yield* loadAllEvents(); const store = groupEventsByDate(events); yield* setEventStore(store); - - // Restore the event ID counter to avoid collisions - const maxCounter = yield* loadMaxEventIdCounter(); - restoreEventIdCounter(maxCounter); }); // Add a new event @@ -43,133 +37,132 @@ export const addEvent = ( color: ColorName = "gray", attendees?: string[], ) => - Effect.gen(function* () { - const store = yield* SubscriptionRef.get(eventStoreRef); - - // Check capacity - let totalEvents = 0; - for (const events of store.values()) { - totalEvents += events.length; - } - if (totalEvents >= MAX_EVENTS) { - return null; // Capacity reached - } - - const newEvent: CalendarEvent = { - id: generateEventId(), - date: dateKey, - title, - startTime, - endTime, - color, - attendees, - updatedAt: new Date().toISOString(), - }; - - // Persist to SQLite - yield* persistEventCreate(newEvent); - - // Update in-memory cache - const newStore = new Map(store); - const dayEvents = sortDayEvents([ - ...(newStore.get(dateKey) ?? []), - newEvent, - ]); - newStore.set(dateKey, dayEvents); - - yield* setEventStore(newStore); - return newEvent; - }); + SubscriptionRef.modifyEffect(eventStoreRef, (store) => + Effect.gen(function* () { + // Check capacity + let totalEvents = 0; + for (const events of store.values()) { + totalEvents += events.length; + } + if (totalEvents >= MAX_EVENTS) { + return [null, store] as const; // Capacity reached + } + + const newEvent: CalendarEvent = { + id: generateEventId(), + date: dateKey, + title, + startTime, + endTime, + color, + attendees, + updatedAt: new Date().toISOString(), + }; + + // Persist to SQLite + yield* persistEventCreate(newEvent); + + // Update in-memory cache + const newStore = new Map(store); + const dayEvents = sortDayEvents([ + ...(newStore.get(dateKey) ?? []), + newEvent, + ]); + newStore.set(dateKey, dayEvents); + + return [newEvent, newStore] as const; + }), + ); // Update an existing event export const updateEvent = ( eventId: string, updates: Partial>, ) => - Effect.gen(function* () { - const store = yield* SubscriptionRef.get(eventStoreRef); - const newStore = new Map(store); - const nextUpdatedAt = updates.updatedAt ?? new Date().toISOString(); - - for (const [dateKey, events] of newStore.entries()) { - const eventIndex = events.findIndex((e) => e.id === eventId); - const event = events[eventIndex]; - if (eventIndex !== -1 && event) { - const updatedEvent = { - ...event, - ...updates, - updatedAt: nextUpdatedAt, - }; - - // Persist to SQLite - yield* persistEventUpdate(eventId, { - ...updates, - updatedAt: nextUpdatedAt, - }); - - // If date changed, move to new date - if (updates.date && updates.date !== dateKey) { - // Remove from old date - const newEvents = events.filter((e) => e.id !== eventId); - if (newEvents.length === 0) { - newStore.delete(dateKey); + SubscriptionRef.modifyEffect(eventStoreRef, (store) => + Effect.gen(function* () { + const nextUpdatedAt = updates.updatedAt ?? new Date().toISOString(); + + for (const [dateKey, events] of store.entries()) { + const eventIndex = events.findIndex((e) => e.id === eventId); + const event = events[eventIndex]; + if (eventIndex !== -1 && event) { + const updatedEvent = { + ...event, + ...updates, + updatedAt: nextUpdatedAt, + }; + + // Persist to SQLite + yield* persistEventUpdate(eventId, { + ...updates, + updatedAt: nextUpdatedAt, + }); + + const newStore = new Map(store); + + // If date changed, move to new date + if (updates.date && updates.date !== dateKey) { + // Remove from old date + const newEvents = events.filter((e) => e.id !== eventId); + if (newEvents.length === 0) { + newStore.delete(dateKey); + } else { + newStore.set(dateKey, newEvents); + } + // Add to new date + const targetEvents = sortDayEvents([ + ...(newStore.get(updates.date) ?? []), + updatedEvent, + ]); + newStore.set(updates.date, targetEvents); } else { - newStore.set(dateKey, newEvents); + // Update in place + const newEvents = [...events]; + newEvents[eventIndex] = updatedEvent; + newStore.set(dateKey, sortDayEvents(newEvents)); } - // Add to new date - const targetEvents = sortDayEvents([ - ...(newStore.get(updates.date) ?? []), - updatedEvent, - ]); - newStore.set(updates.date, targetEvents); - } else { - // Update in place - const newEvents = [...events]; - newEvents[eventIndex] = updatedEvent; - newStore.set(dateKey, sortDayEvents(newEvents)); - } - yield* setEventStore(newStore); - return updatedEvent; + return [updatedEvent, newStore] as const; + } } - } - return null; - }); + return [null, store] as const; + }), + ); // Delete an event export const deleteEvent = (eventId: string) => - Effect.gen(function* () { - const store = yield* SubscriptionRef.get(eventStoreRef); - const newStore = new Map(store); + SubscriptionRef.modifyEffect(eventStoreRef, (store) => + Effect.gen(function* () { + for (const [dateKey, events] of store.entries()) { + const eventIndex = events.findIndex((e) => e.id === eventId); + if (eventIndex !== -1) { + const event = events[eventIndex]; + if (event?.googleCalendarId && event.googleEventId) { + yield* recordGoogleDeletion( + event.googleCalendarId, + event.googleEventId, + ); + } + // Persist to SQLite + yield* persistEventDelete(eventId); - for (const [dateKey, events] of newStore.entries()) { - const eventIndex = events.findIndex((e) => e.id === eventId); - if (eventIndex !== -1) { - const event = events[eventIndex]; - if (event?.googleCalendarId && event.googleEventId) { - yield* recordGoogleDeletion( - event.googleCalendarId, - event.googleEventId, - ); - } - // Persist to SQLite - yield* persistEventDelete(eventId); - - // Update in-memory cache - const newEvents = events.filter((e) => e.id !== eventId); - if (newEvents.length === 0) { - newStore.delete(dateKey); - } else { - newStore.set(dateKey, newEvents); + // Update in-memory cache + const newStore = new Map(store); + const newEvents = events.filter((e) => e.id !== eventId); + if (newEvents.length === 0) { + newStore.delete(dateKey); + } else { + newStore.set(dateKey, newEvents); + } + return [true, newStore] as const; } - yield* setEventStore(newStore); - return true; } - } - return false; - }); + return [false, store] as const; + }), + ); // Get all events (for debugging or export) export const getAllEvents = Effect.gen(function* () { diff --git a/src/features/events/eventsStore.ts b/src/features/events/eventsStore.ts index e343928..fb0f918 100644 --- a/src/features/events/eventsStore.ts +++ b/src/features/events/eventsStore.ts @@ -1,3 +1,4 @@ +import { randomUUID } from "node:crypto"; import { createSubscriptionRef, getSubscriptionValue, @@ -11,15 +12,8 @@ export type EventStore = Map; // key: YYYY-MM-DD const initialStore: EventStore = new Map(); export const eventStoreRef = createSubscriptionRef(initialStore); -let eventIdCounter = 0; - export function generateEventId(): string { - eventIdCounter += 1; - return `event-${eventIdCounter}-${Date.now()}`; -} - -export function restoreEventIdCounter(value: number): void { - eventIdCounter = value; + return `event-${randomUUID()}`; } export function getEventStore(): EventStore { diff --git a/src/features/google/googleDb.ts b/src/features/google/googleDb.ts index 78fbc4c..a5d227a 100644 --- a/src/features/google/googleDb.ts +++ b/src/features/google/googleDb.ts @@ -1,4 +1,4 @@ -import { getDatabase } from "@data/db"; +import { getDatabase, withDbWrite } from "@data/db"; import type { ColorName } from "@shared/types"; import { Effect } from "effect"; @@ -35,8 +35,7 @@ function rowToCalendar(row: GoogleCalendarRow): GoogleCalendarRecord { } export const upsertGoogleCalendar = (calendar: GoogleCalendarRecord) => - Effect.sync(() => { - const db = getDatabase(); + withDbWrite((db) => { const stmt = db.prepare(` INSERT INTO google_calendars ( calendar_id, @@ -94,8 +93,7 @@ export const setGoogleCalendarEnabled = ( calendarId: string, enabled: boolean, ) => - Effect.sync(() => { - const db = getDatabase(); + withDbWrite((db) => { const stmt = db.prepare( "UPDATE google_calendars SET enabled = ?, updated_at = ? WHERE calendar_id = ?", ); @@ -107,8 +105,7 @@ export const updateGoogleCalendarSyncState = ( syncToken: string | null, lastSyncAt: string | null, ) => - Effect.sync(() => { - const db = getDatabase(); + withDbWrite((db) => { const stmt = db.prepare( "UPDATE google_calendars SET sync_token = ?, last_sync_at = ?, updated_at = ? WHERE calendar_id = ?", ); @@ -116,8 +113,7 @@ export const updateGoogleCalendarSyncState = ( }); export const recordGoogleDeletion = (calendarId: string, eventId: string) => - Effect.sync(() => { - const db = getDatabase(); + withDbWrite((db) => { const stmt = db.prepare(` INSERT INTO google_event_deletions (calendar_id, event_id, deleted_at) VALUES (?, ?, ?) @@ -141,8 +137,7 @@ export const getGoogleDeletions = (calendarId: string) => }); export const clearGoogleDeletion = (calendarId: string, eventId: string) => - Effect.sync(() => { - const db = getDatabase(); + withDbWrite((db) => { const stmt = db.prepare( "DELETE FROM google_event_deletions WHERE calendar_id = ? AND event_id = ?", ); diff --git a/src/features/google/googleSync.ts b/src/features/google/googleSync.ts index cf9c71d..d83d9c3 100644 --- a/src/features/google/googleSync.ts +++ b/src/features/google/googleSync.ts @@ -4,11 +4,11 @@ import { findEventByGoogleId, findEventsMissingGoogleId, findEventsUpdatedAfter, - getMaxEventIdCounter, insertEvent, updateEventById, } from "@data/repository"; import { initEventStore } from "@features/events/eventsState"; +import { generateEventId } from "@features/events/eventsStore"; import { clearGoogleDeletion, getEnabledGoogleCalendars, @@ -24,7 +24,6 @@ import { Effect } from "effect"; import { refreshGoogleToken, startGoogleOAuth, - toGoogleAllDayEvent, toGoogleEvent, } from "./googleApi"; @@ -388,7 +387,7 @@ const createGoogleEvent = ( never > => Effect.gen(function* () { - const body = toGoogleAllDayEvent(event); + const body = toGoogleEvent(event); const { data } = yield* googleRequestJson( accessToken, `/calendars/${encodeURIComponent(calendarId)}/events`, @@ -466,7 +465,7 @@ const updateGoogleEvent = ( event: CalendarEvent, ): Effect.Effect<{ etag?: string; updated?: string }, Error, never> => Effect.gen(function* () { - const body = toGoogleAllDayEvent(event); + const body = toGoogleEvent(event); const { data } = yield* googleRequestJson( accessToken, `/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent( @@ -524,8 +523,6 @@ const syncCalendar = ( let activeSyncToken = canWrite && !forceRefresh ? (syncToken ?? null) : null; const touchedLocalIds = new Set(); - const localCounterStart = yield* getMaxEventIdCounter(); - let localCounter = localCounterStart; const processEvent = (event: GoogleEvent) => Effect.gen(function* () { @@ -570,10 +567,9 @@ const syncCalendar = ( } if (!local) { - localCounter += 1; const newEvent: CalendarEvent = { ...mapped, - id: `event-${localCounter}-${Date.now()}`, + id: generateEventId(), }; yield* insertEvent(newEvent); touchedLocalIds.add(newEvent.id); diff --git a/src/features/settings/SettingsModal.tsx b/src/features/settings/SettingsModal.tsx index b313de4..7957496 100644 --- a/src/features/settings/SettingsModal.tsx +++ b/src/features/settings/SettingsModal.tsx @@ -86,33 +86,34 @@ export function SettingsModal() { const googleOptions = useMemo(() => { const isConnected = settings.google.connected; const isSyncing = googleState.status === "syncing" && isConnected; - const options: GoogleOption[] = [ - { - type: "action", - id: "connect", - label: isConnected ? "Disconnect Google" : "Connect Google", - disabled: isConnected ? false : isSyncing, - }, - { - type: "action", - id: "sync", - label: "Sync now", - disabled: !isConnected || isSyncing, - }, - ...(isConnected - ? googleState.calendars.map( - (calendar): GoogleOption => ({ - type: "calendar", - id: `calendar:${calendar.calendarId}`, - label: `${calendar.summary}${calendar.canWrite ? "" : " (read-only)"}`, - calendarId: calendar.calendarId, - enabled: calendar.enabled, - color: calendar.color, - canWrite: calendar.canWrite, - }), - ) - : []), - ]; + const connectOption: GoogleOption = { + type: "action", + id: "connect", + label: isConnected ? "Disconnect Google" : "Connect Google", + disabled: isConnected ? false : isSyncing, + }; + const syncOption: GoogleOption = { + type: "action", + id: "sync", + label: "Sync now", + disabled: !isConnected || isSyncing, + }; + const calendarOptions = isConnected + ? googleState.calendars.map( + (calendar): GoogleOption => ({ + type: "calendar", + id: `calendar:${calendar.calendarId}`, + label: `${calendar.summary}${calendar.canWrite ? "" : " (read-only)"}`, + calendarId: calendar.calendarId, + enabled: calendar.enabled, + color: calendar.color, + canWrite: calendar.canWrite, + }), + ) + : []; + const options: GoogleOption[] = isConnected + ? [syncOption, ...calendarOptions, connectOption] + : [connectOption, syncOption]; return options; }, [googleState.calendars, googleState.status, settings.google.connected]); @@ -162,13 +163,6 @@ export function SettingsModal() { draftNotificationsEnabled !== settings.notificationsEnabled || normalizedNotificationMinutes !== settings.notificationLeadMinutes || draftTheme !== settings.themeId; - const appliedWeekStartLabel = useMemo( - () => - WEEK_START_OPTIONS.find((option) => option.id === settings.weekStartDay) - ?.label ?? "Mon", - [settings.weekStartDay], - ); - const handleNotificationMinutesInput = useCallback((value: string) => { const sanitized = value.replace(/[^0-9]/g, ""); setDraftNotificationMinutes(sanitized); @@ -485,7 +479,6 @@ export function SettingsModal() { weekStartIndex={weekStartIndex} weekStartOptions={WEEK_START_OPTIONS} labelWidth={labelWidth} - appliedWeekStartLabel={appliedWeekStartLabel} /> ) : activeSection === "notifications" ? ( ; labelWidth: number; - appliedWeekStartLabel: string; } export function CalendarSection({ @@ -23,38 +22,32 @@ export function CalendarSection({ weekStartIndex, weekStartOptions, labelWidth, - appliedWeekStartLabel, }: CalendarSectionProps) { return ( - <> - - - Week start - - - {weekStartOptions.map((option, index) => { - const isSelected = option.id === draftWeekStart; - const fg = isSelected ? ui.selected : ui.foreground; - const isFocused = - focusArea === "fields" && index === weekStartIndex; - return ( - - - {isFocused ? "> " : " "} - {isSelected ? "(x)" : "( )"} {option.label} - - - ); - })} - + + + Week start + + + {weekStartOptions.map((option, index) => { + const isSelected = option.id === draftWeekStart; + const fg = isSelected ? ui.selected : ui.foreground; + const isFocused = focusArea === "fields" && index === weekStartIndex; + return ( + + + {isFocused ? "> " : " "} + {isSelected ? "(x)" : "( )"} {option.label} + + + ); + })} - - Saved: {appliedWeekStartLabel} - + ); } diff --git a/src/features/settings/sections/SyncSection.tsx b/src/features/settings/sections/SyncSection.tsx index abd90fa..4c8396d 100644 --- a/src/features/settings/sections/SyncSection.tsx +++ b/src/features/settings/sections/SyncSection.tsx @@ -49,6 +49,7 @@ export function SyncSection({ {googleOptions.map((option, index) => { + const prevOption = googleOptions[index - 1]; const isFocused = focusArea === "fields" && index === googleOptionIndex; const isAction = option.type === "action"; @@ -63,12 +64,18 @@ export function SyncSection({ option.type === "calendar" ? theme.eventColors[option.color] : undefined; + const addGroupSpacing = + (option.type === "calendar" && prevOption?.type === "action") || + (option.type === "action" && + option.id === "connect" && + prevOption?.type === "calendar"); return ( diff --git a/src/scripts/populateDb.ts b/src/scripts/populateDb.ts index a289798..5a2e5f4 100644 --- a/src/scripts/populateDb.ts +++ b/src/scripts/populateDb.ts @@ -1,5 +1,6 @@ import { closeDatabase, initDatabase } from "@data/db"; -import { getMaxEventIdCounter, insertEvent } from "@data/repository"; +import { insertEvent } from "@data/repository"; +import { generateEventId } from "@features/events/eventsStore"; import { formatDateKey } from "@shared/dateUtils"; import type { CalendarEvent, ColorName } from "@shared/types"; import { Effect } from "effect"; @@ -63,10 +64,7 @@ function pickSeed(list: readonly T[], index: number, label: string): T { return value; } -function makeEvent( - index: number, - counterRef: { value: number }, -): CalendarEvent { +function makeEvent(index: number): CalendarEvent { const [year, month, day] = pickSeed(DATE_SEEDS, index, "DATE_SEEDS"); const dateKey = formatDateKey(new Date(year, month, day)); const isAllDay = index % 5 === 0; @@ -80,11 +78,8 @@ function makeEvent( endTime = Math.min(startBase + duration, 23 * 60 + 45); } - counterRef.value += 1; - const id = `event-${counterRef.value}-${Date.now()}-${index}`; - return { - id, + id: generateEventId(), date: dateKey, title: `${TITLES[index % TITLES.length]} ${index + 1}`, startTime, @@ -96,12 +91,11 @@ function makeEvent( function main(): void { initDatabase(); - const counterRef = { value: Effect.runSync(getMaxEventIdCounter()) }; const events: CalendarEvent[] = []; const count = 20; for (let i = 0; i < count; i++) { - events.push(makeEvent(i, counterRef)); + events.push(makeEvent(i)); } for (const event of events) { diff --git a/tests/repository.test.ts b/tests/repository.test.ts index 7aa1aeb..b3298f5 100644 --- a/tests/repository.test.ts +++ b/tests/repository.test.ts @@ -13,7 +13,6 @@ import { deleteEventById, findAllEvents, findEventsByDate, - getMaxEventIdCounter, insertEvent, updateEventById, } from "@data/repository"; @@ -556,59 +555,6 @@ describe("Repository CRUD Operations", () => { }); }); - describe("getMaxEventIdCounter", () => { - test("returns 0 when no events exist", () => { - const maxCounter = Effect.runSync(getMaxEventIdCounter()); - expect(maxCounter).toBe(0); - }); - - test("returns the highest counter from event IDs", () => { - const event1 = createTestEvent({ id: "event-5-1234567890" }); - const event2 = createTestEvent({ id: "event-10-1234567890" }); - const event3 = createTestEvent({ id: "event-3-1234567890" }); - - Effect.runSync(insertEvent(event1)); - Effect.runSync(insertEvent(event2)); - Effect.runSync(insertEvent(event3)); - - const maxCounter = Effect.runSync(getMaxEventIdCounter()); - expect(maxCounter).toBe(10); - }); - - test("handles single event", () => { - const event = createTestEvent({ id: "event-42-1234567890" }); - Effect.runSync(insertEvent(event)); - - const maxCounter = Effect.runSync(getMaxEventIdCounter()); - expect(maxCounter).toBe(42); - }); - - test("ignores events with non-standard ID format", () => { - const event1 = createTestEvent({ id: "event-5-1234567890" }); - const event2 = createTestEvent({ id: "custom-id" }); - - Effect.runSync(insertEvent(event1)); - Effect.runSync(insertEvent(event2)); - - const maxCounter = Effect.runSync(getMaxEventIdCounter()); - expect(maxCounter).toBe(5); - }); - - test("updates after deleting the max counter event", () => { - const event1 = createTestEvent({ id: "event-5-1234567890" }); - const event2 = createTestEvent({ id: "event-10-1234567890" }); - - Effect.runSync(insertEvent(event1)); - Effect.runSync(insertEvent(event2)); - - // Delete the event with highest counter - Effect.runSync(deleteEventById("event-10-1234567890")); - - const maxCounter = Effect.runSync(getMaxEventIdCounter()); - expect(maxCounter).toBe(5); - }); - }); - describe("Integration - Full CRUD lifecycle", () => { test("complete event lifecycle: create, read, update, delete", () => { // CREATE