@frogbotai/db-sqlite. It leverages Drizzle ORM and libSQL to interact with a SQLite database that you provide.
It automatically manages changes to your database for you in development mode, and exposes a full suite of migration controls for you to leverage in order to keep other database environments in sync with your schema. DDL transformations are automatically generated.
To configure FrogBot to use SQLite, pass the sqliteAdapter to your FrogBot Config as follows:
Install
Configure
Collection search
The SQLite adapter implements collection search with SQLite’s FTS5 full-text engine and LibSQL’s native vector functions. Lexical, vector, and hybrid queries each run as a single SQL statement, and ranking happens inside the database after access,where, draft, and locale filters are applied.
Prerequisites
Each mode needs a feature from the connected database. The adapter checks the connection when it connects, before development push and beforemigrate:
The local LibSQL client bundled with the adapter includes both. A remote LibSQL server must provide them too. If a configured index needs a feature the connection lacks, startup fails with
SearchCapabilityError and reason missing-prerequisite, naming the collection, index, and mode.
Two index options fail at startup with reason engine-gap:
- The
dotProductmetric. LibSQL has no inner-product distance, so usecosineoreuclidean. - A
lexical.languageother thanenglishorsimple.
Storage and indexes
Vector fields keep their ordinary JSON storage. Each search index adds its own objects, all named with afrogbot_search_ prefix:
- An FTS5 table over the index’s lexical fields.
lexical.languageselects the tokenizer:simple(the default) matches Unicode words case-insensitively with diacritics removed, andenglishadds English stemming, sorunningmatchesrun. - For an index with
vectorandapproximateleft on, a table holding a 32-bit float copy of every vector and a LibSQL vector index (DiskANN) on it, using the index’s metric. Setvector.approximate: falseto skip it and always search exactly.
draft: true queries rank the latest draft of each document. Records without a vector, or with a vector of different dimensions, are not indexed.
Ranking
Query text supports web search syntax: the terms must all match, quoted words match as a phrase,
or between terms accepts either one, and - excludes a term. Other punctuation separates words and is never parsed as FTS5 syntax. The text is always sent as a bound parameter.
Vector and hybrid queries on a vector index report approximate: true. The index cannot apply your filters itself, so it returns the resolved candidate count of nearest records, and the same statement then applies access and where filters and returns the top limit. A selective filter can therefore return fewer than limit hits even when more matching records exist; raise candidates to trade speed for completeness. Exact vector search applies filters first, compares the query with every remaining record, and ignores candidates.
Hybrid search takes up to the resolved candidate count from each component, joins them in the database, and returns the top limit by fused score. A record that matches only one component still ranks, with the other component reported as null. When the request already runs in a transaction, search reads through it and sees its uncommitted writes.
Push and migrations
Search tables, indexes, and triggers are part of your schema. Development push creates them and leaves them untouched while they match your configuration. When an index definition changes, or a push changes a table an index reads from, the adapter drops the search objects and rebuilds them from the stored records.migrate:create writes the same statements into your migration and rebuilds only the indexes whose definition or source tables changed. Changing a field list, metric, dimensions, or approximate produces a rebuild; embeddings are never regenerated.
Limits
- Localized fields are ranked on the requested locale’s stored value. Fallback-locale values are not ranked.
- If a search runs before its search objects match the configuration, for example before you run migrations, it fails with
SearchReadinessError. - The adapter removes objects with the
frogbot_search_prefix that no longer match your configuration. Do not create your own tables, indexes, or triggers with this prefix.
Options
Concurrent writes
SQLite allows one writer at a time. Within one process, the adapter queues writes: each write statement, and each transaction from its start to its commit or rollback, waits for the one ahead of it. Reads don’t wait, so a read withoutreq during an open transaction returns the last committed data at once.
A write that waits longer than writeLockTimeout fails with an error saying how long it waited. There are two causes. Most often, code inside a hook wrote through the Local API without passing req, so it waited on the very transaction it was part of. Pass req so the write joins the transaction:
writeLockTimeout, for example a hook that awaits a slow API call before the write returns. Shorten the transaction, by moving the slow work out of the hook, or raise writeLockTimeout.
If a commit fails, for example on a deferred foreign key, the adapter rolls the transaction back and the operation rejects with the commit error, so nothing reports success without being saved.
busyTimeout applies only when another process, such as a migration run, holds the lock. The wait blocks the server’s event loop, so keep it short.
Access to Drizzle
After FrogBot is initialized, this adapter will expose the full power of Drizzle to you for use if you need it. You can query the adapter’s runtime tables directly. These queries are untyped:Tables and relations
In addition to exposing Drizzle directly, all of the tables and Drizzle relations are exposed for you via thefrogbot.db property as well.
- Tables -
frogbot.db.tables - Relations -
frogbot.db.relations
Prototyping in development mode
Drizzle exposes two ways to work locally in development mode. The first isdb push, which automatically pushes changes you make to your FrogBot Config (and therefore, Drizzle schema) to your database so you don’t have to manually migrate every time you change your FrogBot Config. This only works in development mode, and should not be mixed with manually running migrate commands.
You will be warned if any changes that you make will entail data loss while in development mode. Push is enabled by default, but you can opt out if you’d like.
Alternatively, you can disable push and rely solely on migrations to keep your local database in sync with your FrogBot Config.
Migration workflows
In SQLite, migrations are a fundamental aspect of working with FrogBot and you should become familiar with how they work. For more information about migrations, click here.Drizzle schema hooks
Apps created withcreate-frogbot-app already depend on drizzle-orm at the adapter’s version; in other apps, install the same version used by your database adapter.
beforeSchemaInit
Runs before the schema is built. You can use this hook to extend your database structure with tables that won’t be managed by FrogBot.schema.ts file which may look like this:
beforeSchemaInit hook like this:
dbName to change the table name for this collection.
afterSchemaInit
Runs after the Drizzle schema is built. You can use this hook to modify the schema with features that aren’t supported by FrogBot, or if you want to add a column that you don’t want to be in the FrogBot config. To extend a table, FrogBot exposesextendTable utility to the args. You can refer to the Drizzle documentation.
The following example adds the extra_integer_column column and a composite index on country and city columns.
Adding tables through rawTables
You can also add tables, columns, and indexes to the adapter’s raw SQL schema throughadapter.rawTables in beforeSchemaInit. The raw schema supports fewer features than Drizzle’s table builders.
D1 Database
We also provide a separate adapter to connect to Cloudflare D1, which is a serverless SQLite database. To use it, install the package@frogbotai/db-d1-sqlite and configure it as follows:
client, busyTimeout, and wal, plus these:
Unlike
@frogbotai/db-sqlite, the D1 adapter doesn’t turn transactions on for collections that set auth.signIn.
You can see our Cloudflare D1 template for a full example of how to set this up.
D1 collection search
The D1 adapter implements lexical collection search with the same FTS5 tables, triggers, query syntax, andsqlite-fts5 ranking as the SQLite adapter. Search objects are part of development push and generated migrations, and ranking happens inside D1 after access, where, draft, and locale filters are applied.
vector fails startup with SearchCapabilityError and reason engine-gap, naming the collection and index, before any table is created. Vector fields still store and return supplied vectors without a search index; each vector is one JSON value, so its dimensions are not limited by D1’s bound-parameter count.
A lexical.language other than english or simple also fails with reason engine-gap. FTS5 is built into D1, so there is no prerequisite to install.
D1 does not wrap writes in transactions by default. Each trigger runs inside the statement that changed the row, so after every statement the search entries match the stored rows.
Keep these D1 limits in mind:
- Every lexical query reads the matching rows of the FTS5 table and the records it joins, and those reads count toward your plan’s rows-read quota. Broad terms on large collections read more rows and take longer; each query must finish within D1’s query time limit.
wrangler d1 exportdoes not support databases with virtual tables, which include FTS5 search tables. Remove the search indexes and run migrations before exporting, then restore them.- Adding or rebuilding an index fills its search table from every stored record in one statement, which must also finish within the query time limit.
- D1 accepts at most 100 bound parameters per statement. As with
find, a longinlist inwherecan exceed it;idlists are exempt except in draft queries. Adraft: truesearch also reads its hits back by ID, so keep itslimitbelow 100.
D1 Read Replicas
You can enable read replicas support with thefirst-primary strategy. This is experimental.
You must also enable it on your D1 database in the Cloudflare dashboard. Read more about it in the Cloudflare documentation.
All write queries are still forwarded to the primary database instance. Read
replication only improves the response time for read query requests.
WAL Mode
Write-Ahead Logging (WAL) is on by default forfile: databases. In WAL mode, reads don’t wait for a write that’s committing, and a large transaction doesn’t block reads. Pass wal: false to use SQLite’s rollback journal instead. A database that ran in WAL switches back at startup; if another process has it open then, it stays in WAL and the adapter logs a warning. WAL keeps two files beside the database, ending in -wal and -shm; back up, move, or delete all three together.
Example SQLite Adapter with WAL
client.url, WAL stays off; if you pass wal: true with one, the adapter logs a warning. Pass an object to change the WAL settings: