Storage and restarts
Where Calendarpit stores data, how to choose between SQLite and in-memory mode, and what persists across restarts and resets.
Calendarpit stores workspace state in a local SQLite file by default. For throwaway test sessions, it can keep state in memory instead.
Choosing a storage mode
By default, Homebrew and standalone executable installs save to .calendarpit/calendarpit.sqlite in the directory where you start Calendarpit. Run it from your project directory to keep that project's data together.
To choose a file or :memory:, use --database <path> or CALENDARPIT_DATABASE. CLI flags override environment variables. Check prerequisites and publication status in Installation.
# Temporary E2E session; no database files are written
calendarpit serve --database :memory: --port 0
# Choose a persistent workspace
calendarpit serve --database ./.calendarpit/manual.sqliteModes
- SQLite file (default): Saves changes after each request that modifies state. It also saves after each webhook delivery finishes. Relative paths resolve against the working directory.
- In-memory (
:memory:): Writes no database or lock files. All state is discarded when the process exits. - Docker: The image sets
CALENDARPIT_DATABASE=/data/calendarpit.sqlite.- To retain data when the container is removed and re-created, mount a named volume with
-v calendarpit-data:/data. - For a temporary session, pass
-e CALENDARPIT_DATABASE=:memory:. - The Compose example also uses a named volume.
docker compose downpreserves data, whiledocker compose down -vdeletes it.
- To retain data when the container is removed and re-created, mount a named volume with
Check the active mode
You can see the active storage mode and path in three places:
- Startup log: The CLI prints
Data <absolute-path>(orData :memory:). When the server entry loads, it also printsCalendarpit data: <path or :memory:>. - Admin UI: The sidebar footer shows
SQLiteorIn-memory. Settings → Test data describes the mode and shows the path (or:memory:). - Management API:
GET /__calendarpit/healthreturnsstorage: "sqlite" | "memory".GET /__calendarpit/statereturnsstorage: { mode, path? }.
What survives a SQLite restart
Reopening an existing SQLite file restores saved data. Short-lived flow state is dropped.
On SIGINT or SIGTERM, Calendarpit:
- Aborts in-flight webhook deliveries.
- Saves state.
- Releases the database lock.
Each kind of state behaves as follows after a restart:
| State | SQLite restart behavior |
|---|---|
| Users, calendars, events, OAuth clients | Restored, including event IDs, timestamps, and etags |
| Calendar-list settings | Restored; cleared by a reset or fixture load |
| Reset baseline (startup fixture) | Original seed retained |
| Connections (grants), access and refresh tokens | Restored |
| Watch channels | Restored only under the conditions below |
| Request history, delivery log, notifications | Latest 200 entries of each restored |
| Queued or in-flight webhook deliveries | Marked cancelled; never replayed automatically |
| Sync tokens, calendar tombstones | Restored; cleared by a reset or fixture load |
| Pending consents, authorization codes, page tokens | Discarded; restart the flow or list query |
Details for some rows:
- Calendar-list settings include per-user overrides, colors, hidden/selected, reminders, and removed entries.
- Reset baseline:
--configandCALENDARPIT_CONFIGfile contents are ignored. - Tokens and connections:
- Access tokens keep their original expiry.
- Refresh tokens do not expire.
- Revoked connections stay revoked.
- Watch channels are restored if all of these hold. Restored channels keep their message numbers.
- The channel is unexpired.
- The connection is active.
- The callback host is still allowed.
- Page tokens are the
calendarListandeventscursors.
After a restart, refetch calendar state in your app. This reconciles watch notifications interrupted by shutdown.
Seed baseline, fixtures, and reset
A startup fixture seeds only a new database. The startup fixture comes from --config, CALENDARPIT_CONFIG, or the built-in default.
- Existing SQLite file: Reopens saved data and the original baseline.
- If
--configorCALENDARPIT_CONFIGis set, the fixture's contents are ignored and do not overwrite the database. - The file must still exist and contain valid JSON.
- If
- In-memory mode: Every start is a new database. The startup fixture is applied, and any omitted IDs are regenerated, on every start.
- Reset (
POST /__calendarpit/reset): Restores the original startup baseline, even after restarts.- It clears connections, tokens, codes, consents, watch channels, calendar-list settings, sync tokens, page tokens, calendar tombstones, request history, notifications, and deliveries.
- It saves immediately.
- It does not delete the SQLite file or modify the fixture file.
- Load fixture (
POST /__calendarpit/fixtures): Replaces current data and clears runtime state without changing the reset baseline. Subsequent resets still restore the original seed. See Test control API.
Locking, files, and parallel runs
One process per file
Only one Calendarpit process can use a SQLite file at a time.
- While running, Calendarpit holds an exclusive lock on
<db>.lock. - A second process on the same file fails at startup with
Database is already in use: <path>. Use a separate path or :memory:.The CLI prefixes this withCalendarpit:. - The OS releases the lock on exit, including after a crash.
Give each parallel test worker --database :memory: or a separate SQLite file.
Sidecar files and permissions
SQLite runs in WAL mode. While running, Calendarpit creates these sidecar files next to the database:
<db>-wal<db>-shm<db>.lock<db>.lock-journal
The database, -wal, -shm, and .lock files use mode 0600. Any database directory Calendarpit creates uses mode 0700.
Keep sidecar files with the database while running. Stop the server before copying a database file.
Keep the database out of Git
The database contains local test credentials and client secrets. Keep it out of Git.
In this repository, Git ignores:
.calendarpit/- The
*.sqlite,*.sqlite-wal,*.sqlite-shm, and*.sqlite.lockpatterns <db>.lock-journal, but only inside.calendarpit/