Skip to main content
Every API call and webhook that flows through Corsair is stored in your database automatically. When something changes in Slack, GitHub, or Linear, Corsair updates the same row — so your UI can read from *.db.* instead of hitting third-party APIs on every page load. Third-party data becomes an extension of your own DB, always fresh.

Get started

Run the migration once, then pass your connection to createCorsair({ database, ... }). See Quick Start for a full setup example.
Install the driver, then run the migration:
migration.sql

Example: Slack

All data is scoped to tenants — withTenant() ensures you only read and write that tenant’s rows, with no cross-contamination.
Use *.api.* to write or force a fresh read. Use *.db.* for lists, search, and detail pages. Plugin-specific filters are in each plugin’s database reference (e.g. Slack).

Core tables

Four tables for every integration — Slack, GitHub, Linear, and the rest all share the same schema.
TablePurpose
corsair_integrationsOne row per enabled plugin (slack, github, …)
corsair_accountsPer-tenant connection + encrypted credentials (tenant_id, integration_id)
corsair_entitiesSynced resources — messages, channels, issues, repos (entity_id, entity_type, data)
corsair_eventsAudit log of API calls and webhooks
Schema types: packages/corsair/db/index.ts.

Keeping data fresh

Corsair doesn’t snapshot data once and forget it. Every write path keeps the same row current:
  1. API calls — when you call tenant.slack.api.messages.post, Corsair sends the request and upserts the response into corsair_entities.
  2. Webhooks — when Slack notifies you that a message changed, Corsair finds the row by entity_id and updates it in place.
  3. Out-of-order events — if an update arrives before you’ve seen the create, Corsair fetches from the API and backfills the row (see Webhooks).
You don’t build a sync layer or poll for changes. Query corsair_entities (or *.db.*) and trust that Corsair is maintaining it.

How to use it

Query through Corsair

Use the typed ORM on each plugin — no raw SQL required for most reads:

Join to your own tables

Reference corsair_entities.id from your schema and treat third-party data like first-class rows in your app:
Because Corsair updates corsair_entities on every API call and webhook, foreign keys to that table always point at current data — not a stale cache you wrote once.

When to use API vs DB

UseWhen
*.api.*Writes, deletes, or you need the latest value right now from the source
*.db.*UI lists, search, detail pages — fast reads, no rate limits

SQLite, Postgres, and ORMs

Corsair runs on your database. Pass a connection to createCorsair — it works alongside whatever ORM you already use for your own tables.
Best for local dev and small apps. Pass a better-sqlite3 instance:
Supported connection types: pg Pool, better-sqlite3, postgres.js Sql, and Kysely. Use the same database for Corsair tables and your app tables — one migration, one connection string.

What’s next

Multi-Tenancy

Scope credentials and data per user with withTenant().

Webhooks

How incoming events update your rows automatically.

API

Call third-party APIs with full type safety.