sqflite_common_ffi_web: custom factories, options and debugging
The default databaseFactoryFfiWeb loads sqflite_sw.js and sqlite3.wasm
relative to the page and stores everything in the IndexedDB database
sqflite_databases. createDatabaseFactoryFfiWeb builds a factory with other
locations, another store name or no worker. Basic setup is covered by
sqflite-common-ffi-web-setup.
import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';
final databaseFactoryCustomWeb = createDatabaseFactoryFfiWeb(
options: SqfliteFfiWebOptions(
sharedWorkerUri: Uri.parse('/assets/sqflite_sw.js'),
sqlite3WasmUri: Uri.parse('/assets/sqlite3.wasm'),
indexedDbName: 'my_app_databases',
),
);
Guidelines
createDatabaseFactoryFfiWeb
- Signature:
createDatabaseFactoryFfiWeb({SqfliteFfiWebOptions? options, bool noWebWorker = false, String? tag}). Web only; on io it throws
UnsupportedError.
- Create the factory once (top-level
final or a singleton). Each factory
starts its own worker connection / wasm instance on first use, guarded by a
lock, and keeps it for the life of the page.
noWebWorker: true loads the wasm in the main thread using the same
options (sqlite3WasmUri, indexedDbName); sharedWorkerUri is ignored.
databaseFactoryFfiWebNoWebWorker is this with default options.
tag (default ffi_web) only labels the factory in logs and
toString().
- Options are sent to the worker before the first database call
(
setWebOptions) and the worker loads the wasm and opens the IndexedDB
store once, at its first database call. A shared worker is keyed by its
script URL and serves every tab and every factory using that URL, so
options arriving later (another tab, a second factory with the same
sharedWorkerUri) do not change a running worker. Use one option set per
worker script; for a different indexedDbName or sqlite3WasmUri use a
different sharedWorkerUri (a copy of the worker file) or
noWebWorker: true.
SqfliteFfiWebOptions
sqlite3WasmUri (default sqlite3.wasm relative to the page in
no-worker mode, and relative to the worker script otherwise). Use an
absolute path (/sqlite3.wasm) when the app is served from nested routes.
sharedWorkerUri (default sqflite_sw.js): the worker script produced by
setup. Rename or version it (sqflite_sw_v2.js) to force browsers to
reload the worker after a package upgrade; the setup output name can be
fixed in the app pubspec.yaml:
sqflite:
sqflite_common_ffi_web:
sw_js_file: sqflite_sw_v2.js
then re-run dart run sqflite_common_ffi_web:setup and pass the same name
in sharedWorkerUri.
indexedDbName (default sqflite_databases): the IndexedDB database
hosting the virtual file system. Factories with different names (and
different workers, see above) are fully isolated stores, for example
production data vs. a scratch store.
inMemory: declared and transported, but the current loader always opens
the IndexedDB file system; do not rely on it. Use inMemoryDatabasePath
as the database path for a non-persistent database instead.
forceAsBasicWorker is @visibleForTesting (used by
databaseFactoryFfiWebBasicWebWorker): forces a dedicated Worker
instead of a SharedWorker. Do not set it in application code.
SqfliteFfiWebOptionsExt.toMap() serializes options (useful for logging).
Lower-level entry points
sqfliteFfiWebLoadSqlite3Wasm(options) opens the IndexedDB file system,
fetches the wasm and returns a SqfliteFfiWebContext (main thread, what
noWebWorker does). sqfliteFfiWebStartSharedWorker(options) spawns the
shared (or basic) worker and returns a context that forwards messages.
Both are only needed to build a custom worker or to preload; normal code
uses the factories.
SqfliteFfiWebContext exposes options; the web-only extension adds fs
(VirtualFileSystem from package:sqlite3/wasm.dart), wasmSqlite3,
sharedWorker and sendRawMessage(message). The extension lives in
src/ and is not part of the public export; treat it as unstable.
Debugging
- Set
sqliteFfiWebDebugWebWorker = true (setter is @Deprecated('testing only'); ignore the warning in a debug build) before the first call to log
every message sent to and received from the worker (main_send: /
main_recv:) and worker start-up steps.
- Inspect the shared worker (console, breakpoints, network) in Chrome at
chrome://inspect/#workers. Application > IndexedDB shows the
sqflite_databases store.
- The database is tied to the origin including the port: keep the same dev
server port between runs.
- A failure at the first call with the console error "An error occurred
while initializing the web worker" means the worker script could not be
loaded (wrong
sharedWorkerUri, setup not run, file not deployed); the
message names the URL tried.
- Wrong or outdated
sqlite3.wasm shows as a load failure inside the worker
(visible in the worker console); re-run setup with --force or download
the wasm matching the resolved sqlite3 version.
Examples
App served under a sub path with versioned worker
import 'package:sqflite_common/sqlite_api.dart';
import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';
// Files deployed as /app/sqflite_sw_v2.js and /app/sqlite3.wasm
final DatabaseFactory appDatabaseFactory = createDatabaseFactoryFfiWeb(
options: SqfliteFfiWebOptions(
sharedWorkerUri: Uri.parse('/app/sqflite_sw_v2.js'),
sqlite3WasmUri: Uri.parse('/app/sqlite3.wasm'),
),
);
Future<Database> openAppDatabase() =>
appDatabaseFactory.openDatabase('app.db');
Separate IndexedDB store for tests or scratch data
import 'package:sqflite_common/sqlite_api.dart';
import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';
// web/sqflite_sw_scratch.js is a copy of web/sqflite_sw.js: a distinct
// worker URL gives a distinct worker, so it can use its own store.
final DatabaseFactory scratchFactory = createDatabaseFactoryFfiWeb(
options: SqfliteFfiWebOptions(
sharedWorkerUri: Uri.parse('sqflite_sw_scratch.js'),
indexedDbName: 'scratch_databases',
),
tag: 'ffi_web_scratch',
);
Future<void> resetScratch() async {
// Same name as production but a different store: no collision.
await scratchFactory.deleteDatabase('app.db');
}
Main thread factory with a custom wasm location
import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';
final toolFactory = createDatabaseFactoryFfiWeb(
noWebWorker: true,
options: SqfliteFfiWebOptions(sqlite3WasmUri: Uri.parse('/wasm/sqlite3.wasm')),
);
Enabling worker message logging in debug builds
import 'package:flutter/foundation.dart' show kDebugMode, kIsWeb;
import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';
void enableSqfliteWebLogs() {
if (kIsWeb && kDebugMode) {
// ignore: deprecated_member_use
sqliteFfiWebDebugWebWorker = true;
}
}
Common mistakes
- Creating a new factory per call: each one spins up its own worker
connection; keep one instance.
- Changing
indexedDbName after users have data: the old store is
orphaned, data appears lost.
- Two factories with different
indexedDbName but the same worker script:
both talk to the same running worker and the same store.
- Setting
sharedWorkerUri without deploying a file at that URL, or
changing the setup output name without updating the option (or the
reverse).
- Relying on
inMemory: true for a private database.
- Using
forceAsBasicWorker in production code.
- Reading
SqfliteFfiWebContext.fs from application code (unstable, src/).
1---2name: sqflite-common-ffi-web-options3description: Use when customizing or debugging sqflite on the web with package:sqflite_common_ffi_web beyond the default factory: createDatabaseFactoryFfiWeb, SqfliteFfiWebOptions (sqlite3WasmUri, sharedWorkerUri, indexedDbName, inMemory, forceAsBasicWorker), serving sqlite3.wasm and sqflite_sw.js from another location or name, the sw_js_file pubspec override, several isolated IndexedDB stores, sqfliteFfiWebLoadSqlite3Wasm and sqfliteFfiWebStartSharedWorker, SqfliteFfiWebContext, sqliteFfiWebDebugWebWorker, and inspecting the worker in Chrome (chrome://inspect/#workers).4---56# sqflite_common_ffi_web: custom factories, options and debugging78The default `databaseFactoryFfiWeb` loads `sqflite_sw.js` and `sqlite3.wasm`9relative to the page and stores everything in the IndexedDB database10`sqflite_databases`. `createDatabaseFactoryFfiWeb` builds a factory with other11locations, another store name or no worker. Basic setup is covered by12`sqflite-common-ffi-web-setup`.1314```dart15import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';1617final databaseFactoryCustomWeb = createDatabaseFactoryFfiWeb(18 options: SqfliteFfiWebOptions(19 sharedWorkerUri: Uri.parse('/assets/sqflite_sw.js'),20 sqlite3WasmUri: Uri.parse('/assets/sqlite3.wasm'),21 indexedDbName: 'my_app_databases',22 ),23);24```2526## Guidelines2728### createDatabaseFactoryFfiWeb2930* Signature: `createDatabaseFactoryFfiWeb({SqfliteFfiWebOptions? options,31 bool noWebWorker = false, String? tag})`. Web only; on io it throws32 `UnsupportedError`.33* Create the factory once (top-level `final` or a singleton). Each factory34 starts its own worker connection / wasm instance on first use, guarded by a35 lock, and keeps it for the life of the page.36* `noWebWorker: true` loads the wasm in the main thread using the same37 options (`sqlite3WasmUri`, `indexedDbName`); `sharedWorkerUri` is ignored.38 `databaseFactoryFfiWebNoWebWorker` is this with default options.39* `tag` (default `ffi_web`) only labels the factory in logs and40 `toString()`.41* Options are sent to the worker before the first database call42 (`setWebOptions`) and the worker loads the wasm and opens the IndexedDB43 store once, at its first database call. A shared worker is keyed by its44 script URL and serves every tab and every factory using that URL, so45 options arriving later (another tab, a second factory with the same46 `sharedWorkerUri`) do not change a running worker. Use one option set per47 worker script; for a different `indexedDbName` or `sqlite3WasmUri` use a48 different `sharedWorkerUri` (a copy of the worker file) or49 `noWebWorker: true`.5051### SqfliteFfiWebOptions5253* `sqlite3WasmUri` (default `sqlite3.wasm` relative to the page in54 no-worker mode, and relative to the worker script otherwise). Use an55 absolute path (`/sqlite3.wasm`) when the app is served from nested routes.56* `sharedWorkerUri` (default `sqflite_sw.js`): the worker script produced by57 setup. Rename or version it (`sqflite_sw_v2.js`) to force browsers to58 reload the worker after a package upgrade; the setup output name can be59 fixed in the app `pubspec.yaml`:6061 ```yaml62 sqflite:63 sqflite_common_ffi_web:64 sw_js_file: sqflite_sw_v2.js65 ```6667 then re-run `dart run sqflite_common_ffi_web:setup` and pass the same name68 in `sharedWorkerUri`.69* `indexedDbName` (default `sqflite_databases`): the IndexedDB database70 hosting the virtual file system. Factories with different names (and71 different workers, see above) are fully isolated stores, for example72 production data vs. a scratch store.73* `inMemory`: declared and transported, but the current loader always opens74 the IndexedDB file system; do not rely on it. Use `inMemoryDatabasePath`75 as the database path for a non-persistent database instead.76* `forceAsBasicWorker` is `@visibleForTesting` (used by77 `databaseFactoryFfiWebBasicWebWorker`): forces a dedicated `Worker`78 instead of a `SharedWorker`. Do not set it in application code.79* `SqfliteFfiWebOptionsExt.toMap()` serializes options (useful for logging).8081### Lower-level entry points8283* `sqfliteFfiWebLoadSqlite3Wasm(options)` opens the IndexedDB file system,84 fetches the wasm and returns a `SqfliteFfiWebContext` (main thread, what85 `noWebWorker` does). `sqfliteFfiWebStartSharedWorker(options)` spawns the86 shared (or basic) worker and returns a context that forwards messages.87 Both are only needed to build a custom worker or to preload; normal code88 uses the factories.89* `SqfliteFfiWebContext` exposes `options`; the web-only extension adds `fs`90 (`VirtualFileSystem` from `package:sqlite3/wasm.dart`), `wasmSqlite3`,91 `sharedWorker` and `sendRawMessage(message)`. The extension lives in92 `src/` and is not part of the public export; treat it as unstable.9394### Debugging9596* Set `sqliteFfiWebDebugWebWorker = true` (setter is `@Deprecated('testing97 only')`; ignore the warning in a debug build) before the first call to log98 every message sent to and received from the worker (`main_send:` /99 `main_recv:`) and worker start-up steps.100* Inspect the shared worker (console, breakpoints, network) in Chrome at101 `chrome://inspect/#workers`. Application > IndexedDB shows the102 `sqflite_databases` store.103* The database is tied to the origin including the port: keep the same dev104 server port between runs.105* A failure at the first call with the console error "An error occurred106 while initializing the web worker" means the worker script could not be107 loaded (wrong `sharedWorkerUri`, setup not run, file not deployed); the108 message names the URL tried.109* Wrong or outdated `sqlite3.wasm` shows as a load failure inside the worker110 (visible in the worker console); re-run setup with `--force` or download111 the wasm matching the resolved `sqlite3` version.112113## Examples114115### App served under a sub path with versioned worker116117```dart118import 'package:sqflite_common/sqlite_api.dart';119import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';120121// Files deployed as /app/sqflite_sw_v2.js and /app/sqlite3.wasm122final DatabaseFactory appDatabaseFactory = createDatabaseFactoryFfiWeb(123 options: SqfliteFfiWebOptions(124 sharedWorkerUri: Uri.parse('/app/sqflite_sw_v2.js'),125 sqlite3WasmUri: Uri.parse('/app/sqlite3.wasm'),126 ),127);128129Future<Database> openAppDatabase() =>130 appDatabaseFactory.openDatabase('app.db');131```132133### Separate IndexedDB store for tests or scratch data134135```dart136import 'package:sqflite_common/sqlite_api.dart';137import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';138139// web/sqflite_sw_scratch.js is a copy of web/sqflite_sw.js: a distinct140// worker URL gives a distinct worker, so it can use its own store.141final DatabaseFactory scratchFactory = createDatabaseFactoryFfiWeb(142 options: SqfliteFfiWebOptions(143 sharedWorkerUri: Uri.parse('sqflite_sw_scratch.js'),144 indexedDbName: 'scratch_databases',145 ),146 tag: 'ffi_web_scratch',147);148149Future<void> resetScratch() async {150 // Same name as production but a different store: no collision.151 await scratchFactory.deleteDatabase('app.db');152}153```154155### Main thread factory with a custom wasm location156157```dart158import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';159160final toolFactory = createDatabaseFactoryFfiWeb(161 noWebWorker: true,162 options: SqfliteFfiWebOptions(sqlite3WasmUri: Uri.parse('/wasm/sqlite3.wasm')),163);164```165166### Enabling worker message logging in debug builds167168```dart169import 'package:flutter/foundation.dart' show kDebugMode, kIsWeb;170import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';171172void enableSqfliteWebLogs() {173 if (kIsWeb && kDebugMode) {174 // ignore: deprecated_member_use175 sqliteFfiWebDebugWebWorker = true;176 }177}178```179180## Common mistakes181182* Creating a new factory per call: each one spins up its own worker183 connection; keep one instance.184* Changing `indexedDbName` after users have data: the old store is185 orphaned, data appears lost.186* Two factories with different `indexedDbName` but the same worker script:187 both talk to the same running worker and the same store.188* Setting `sharedWorkerUri` without deploying a file at that URL, or189 changing the setup output name without updating the option (or the190 reverse).191* Relying on `inMemory: true` for a private database.192* Using `forceAsBasicWorker` in production code.193* Reading `SqfliteFfiWebContext.fs` from application code (unstable, `src/`).