sembast: opening, migrating and managing a database
A sembast database is opened through a DatabaseFactory; the factory decides
where and how data is stored. The whole database is loaded in memory on open,
so open it once at startup and keep a single Database instance for the
application lifetime.
import 'package:sembast/sembast_io.dart';
Future<Database> openAppDatabase(String dir) async {
// sembast_io.dart re-exports sembast.dart and provides databaseFactoryIo.
return databaseFactoryIo.openDatabase('$dir/app.db', version: 1);
}
Guidelines
Choosing the factory (platform)
- Dart VM and Flutter mobile/desktop:
databaseFactoryIo from
package:sembast/sembast_io.dart. One JSON-lines file per database, pure
Dart, no plugin. Not cross-process and not cross-isolate safe: use it from
the main isolate only.
- Web and Flutter web:
databaseFactoryWeb from package:sembast_web
(IndexedDB, cross-tab safe). databaseFactoryIo compiles on the web but
throws UnimplementedError at runtime. Select the factory with a
conditional import (sembast-web-setup skill).
- Cross-process safety on VM/Flutter (several processes on the same file):
sembast_sqflite (getDatabaseFactorySqflite). The Flutter package
tekartik_app_flutter_sembast bundles a getDatabaseFactory() that picks
the right one per platform.
- Tests and mocks:
newDatabaseFactoryMemory() (a fresh isolated factory)
or openNewInMemoryDatabase() (an always-empty database) from
package:sembast/sembast_memory.dart. databaseFactoryMemory is a shared
global memory factory: databases with the same path are the same database
until closed. databaseFactoryMemoryFs simulates the io file format in
memory (useful to test import/export or compact).
createDatabaseFactoryIo(rootPath: dir) makes relative paths resolve under
dir. anyFactory.sandbox(path: dir) does the same for any factory (io,
memory, web) and rejects paths escaping the sandbox. factory.pathContext
gives the path.Context used and factory.hasStorage is false for
memory.
Opening
factory.openDatabase(path, {version, onVersionChanged, mode, codec}).
On io path is a file path (create the parent directory first, see the
Flutter example). Opening an already open database returns the same
instance.
mode defaults to DatabaseMode.neverFails (create if missing, delete
and recreate when corrupted). Others: create (create if missing, fail on
corruption), existing (fail when missing), empty (wipe content),
readOnly (fail when missing, no writes).
db.version, db.path, await db.close(). Close before deleting:
factory.deleteDatabase(path); factory.databaseExists(path) checks.
- In Flutter, build the path with
path_provider
(getApplicationDocumentsDirectory()) and package:path join. Never
hard-code a directory. Open in main() before runApp, or lazily behind a
single cached Future<Database>.
- On Flutter, wrap the open in
disableSembastCooperator() /
enableSembastCooperator() if the splash screen should not yield, and call
disableSembastCooperator() once in flutter_test files (the cooperator's
Future.delayed can hang there).
- Size guidance: io below ~100K records / 100 MB, web below ~30K records.
Open time grows with record count.
Versioning and migrations
- A database created without
version has version 1. Pass a constant
version from day one: on creation onVersionChanged(db, 0, version) is
called, on later upgrades onVersionChanged(db, oldVersion, newVersion).
It is not called when the versions match.
- Inside
onVersionChanged use the db argument freely (store.add(db, ...), db.transaction(...)): those calls join the transaction opened for
the migration, and the new version is stored only when the callback
succeeds.
- Write migrations as
if (oldVersion < N) steps in increasing order so a
fresh install (oldVersion 0) runs every step. Seed data (initial records)
belongs in the oldVersion == 0 branch.
- Sembast is schema-less: renaming a field means reading, transforming and
putting records back; changing key type means copying into a new store
and
oldStore.drop(db).
- Lowering
version is not an error; onVersionChanged is called with
oldVersion > newVersion.
Codec and encryption
SembastCodec(signature: 'name', codec: myCodec) where myCodec is a
Codec<Object?, String> turning a JSON-encodable value into a single-line
string. Pass it to every openDatabase of that database; opening with a
different codec throws DatabaseException with code
DatabaseException.errInvalidCodec. The signature is stored (encoded by
the codec itself) so a wrong password is detected at open.
- Sembast ships no cipher. Bring your own (for example on top of
pointycastle); the repository's sembast_test/lib/encrypt_codec.dart
is a demonstration only, not production grade.
- A codec calling a plugin or async API: extend
AsyncContentCodecBase,
implement decodeAsync/encodeAsync and pass it as codec.
- The codec cannot be changed later. To encrypt an existing database:
exportDatabase it, importDatabase(export, factory, newPath, codec: codec), then delete the old one (or databaseMerge between two open
databases).
- Custom value types are handled by
JsonEncodableCodec type adapters
(package:sembast/utils/type_adapter.dart); sembastCodecDefault already
handles Timestamp and Blob. Do not write adapters for models: store
maps.
Export, import, maintenance
package:sembast/utils/sembast_import_export.dart: exportDatabase(db, {storeNames}) returns a JSON-encodable map, importDatabase(map, factory, path, {codec, storeNames}) creates (after deleting) a database from it and
returns it open. exportDatabaseLines / importDatabaseLines /
exportLinesToJsonlString are a line-based format friendlier to git;
importDatabaseAny accepts either format, a JSON string or a list of
strings. package:sembast/utils/import_export_io.dart adds
exportDatabaseToJsonlFile and importDatabaseFromFile on the VM.
package:sembast/utils/database_utils.dart: getNonEmptyStoreNames(db)
(there is no other way to list stores) and databaseMerge(db, sourceDatabase: other, storeNames: [...]) which makes db content equal
to other.
db.compact() rewrites the io file dropping obsolete lines (automatic
when 20% of lines are obsolete). db.checkForChanges() reloads external
changes on web/sqflite. db.reload() / db.reOpen() reread the io file
(unsafe if writes are in progress).
DatabaseException.code values: errBadParam, errDatabaseNotFound,
errInvalidCodec, errDatabaseClosed (using a closed database).
Examples
Flutter: single global database opened at startup
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sembast/sembast_io.dart';
Database? _db;
Future<Database> getDatabase() async {
if (_db != null) return _db!;
var dir = await getApplicationDocumentsDirectory();
await dir.create(recursive: true);
var dbPath = join(dir.path, 'my_app.db');
_db = await databaseFactoryIo.openDatabase(dbPath, version: 1);
return _db!;
}
Dart VM script with a sandboxed factory
import 'package:sembast/sembast_io.dart';
Future<void> main() async {
// All relative paths live under .local/db
var factory = databaseFactoryIo.sandbox(path: '.local/db');
var db = await factory.openDatabase('cache.db');
var store = StoreRef<String, int>.main();
await store.record('runs').put(db, (await store.record('runs').get(db) ?? 0) + 1);
print(await store.record('runs').get(db));
await db.close();
}
Versioned migrations with seed data
import 'package:sembast/sembast.dart';
const kDbVersion = 3;
final productStore = stringMapStoreFactory.store('product');
Future<Database> openWithMigrations(DatabaseFactory factory, String path) {
return factory.openDatabase(
path,
version: kDbVersion,
onVersionChanged: (db, oldVersion, newVersion) async {
// oldVersion is 0 on creation: every step below runs.
if (oldVersion < 1) {
await productStore.record('demo_1').put(db, {'name': 'Demo 1', 'price': 10});
}
if (oldVersion < 2) {
// Price change shipped in app version 2.
await productStore.record('demo_1').update(db, {'price': 15});
}
if (oldVersion < 3) {
// Tag every product whose name contains 'demo'.
await productStore.update(
db,
{'tag': 'demo'},
finder: Finder(
filter: Filter.custom(
(record) => (record['name'] as String).toLowerCase().contains('demo'),
),
),
);
}
},
);
}
Migrate int keys to String keys in a new store
import 'package:sembast/sembast.dart';
final fruitStoreV1 = intMapStoreFactory.store('fruit');
final fruitStore = stringMapStoreFactory.store('fruit_v2');
Future<Database> openFruits(DatabaseFactory factory, String path) {
return factory.openDatabase(
path,
version: 2,
onVersionChanged: (db, oldVersion, newVersion) async {
if (oldVersion == 1) {
await db.transaction((txn) async {
var values = (await fruitStoreV1.find(txn)).values.toList();
await fruitStore.addAll(txn, values);
await fruitStoreV1.drop(txn);
});
}
},
);
}
Unit test with an in-memory factory
import 'package:sembast/sembast_memory.dart';
import 'package:test/test.dart';
void main() {
test('put/get', () async {
var factory = newDatabaseFactoryMemory();
var db = await factory.openDatabase('test.db');
var record = StoreRef<String, String>.main().record('key');
await record.put(db, 'value');
expect(await record.get(db), 'value');
await db.close();
// Or an always-empty database in one call:
var db2 = await openNewInMemoryDatabase();
expect(await record.get(db2), isNull);
await db2.close();
});
}
Custom (async) codec and wrong-codec detection
import 'dart:convert';
import 'package:sembast/sembast.dart';
/// Demo only: reverses the JSON text. Replace with real encryption.
class ReverseCodec extends AsyncContentCodecBase {
String _reverse(String text) => text.split('').reversed.join();
@override
Future<Object?> decodeAsync(String encoded) async =>
jsonDecode(_reverse(encoded));
@override
Future<String> encodeAsync(Object? input) async =>
_reverse(jsonEncode(input));
}
Future<Database> openEncoded(DatabaseFactory factory, String path) async {
var codec = SembastCodec(signature: 'reverse_v1', codec: ReverseCodec());
try {
return await factory.openDatabase(path, codec: codec);
} on DatabaseException catch (e) {
if (e.code == DatabaseException.errInvalidCodec) {
// Existing file written with another codec (or password).
rethrow;
}
rethrow;
}
}
Convert an existing database to an encrypted one
import 'package:sembast/utils/sembast_import_export.dart';
Future<Database> migrateToCodec(
DatabaseFactory factory,
SembastCodec codec,
) async {
const plainPath = 'my_database.db';
const encryptedPath = 'my_database_encrypted.db';
if (await factory.databaseExists(plainPath)) {
var plainDb = await factory.openDatabase(plainPath);
var export = await exportDatabase(plainDb);
await plainDb.close();
var db = await importDatabase(export, factory, encryptedPath, codec: codec);
await factory.deleteDatabase(plainPath);
return db;
}
return factory.openDatabase(encryptedPath, codec: codec);
}
Backup and restore as JSON lines (VM)
import 'package:sembast/sembast_io.dart';
import 'package:sembast/utils/import_export_io.dart';
Future<void> backup(Database db) =>
exportDatabaseToJsonlFile(db, 'backup/app.jsonl');
Future<Database> restore() =>
importDatabaseFromFile('backup/app.jsonl', databaseFactoryIo, 'restored.db');
Common mistakes
- Opening the database in several places or on every screen. Keep one
instance; opening is the expensive operation.
- Using
databaseFactoryIo in a Flutter web build (runtime
UnimplementedError), or databaseFactoryMemory in production.
- Forgetting
dir.create(recursive: true) on Flutter, or using a relative
path such as 'app.db' on mobile.
- Opening without
version then adding one later: the existing database is
already at version 1, so creation logic guarded by oldVersion == 0 never
runs for old installs. Handle oldVersion == 1 too.
- Doing migration writes with a different
Database object than the db
passed to onVersionChanged.
- Changing the codec of an existing database in place. Export and import.
- Deleting the database file directly while it is open; call
db.close()
then factory.deleteDatabase(path).
More
See references/factories.md for the factory,
DatabaseMode, codec and import/export listings.
1---2name: sembast-database-setup3description: Use when opening, configuring, migrating, testing or exporting a package:sembast database: DatabaseFactory, databaseFactoryIo (sembast_io.dart), databaseFactoryMemory / newDatabaseFactoryMemory / openNewInMemoryDatabase (sembast_memory.dart), openDatabase path, version and onVersionChanged migrations, DatabaseMode, close, deleteDatabase, the factory.sandbox() extension, SembastCodec encryption and async codecs, exportDatabase / importDatabase, databaseMerge, choosing sembast_web or sembast_sqflite per platform, Flutter path_provider setup, unit tests and disableSembastCooperator.4---56# sembast: opening, migrating and managing a database78A sembast database is opened through a `DatabaseFactory`; the factory decides9where and how data is stored. The whole database is loaded in memory on open,10so open it once at startup and keep a single `Database` instance for the11application lifetime.1213```dart14import 'package:sembast/sembast_io.dart';1516Future<Database> openAppDatabase(String dir) async {17 // sembast_io.dart re-exports sembast.dart and provides databaseFactoryIo.18 return databaseFactoryIo.openDatabase('$dir/app.db', version: 1);19}20```2122## Guidelines2324### Choosing the factory (platform)2526* Dart VM and Flutter mobile/desktop: `databaseFactoryIo` from27 `package:sembast/sembast_io.dart`. One JSON-lines file per database, pure28 Dart, no plugin. Not cross-process and not cross-isolate safe: use it from29 the main isolate only.30* Web and Flutter web: `databaseFactoryWeb` from `package:sembast_web`31 (IndexedDB, cross-tab safe). `databaseFactoryIo` compiles on the web but32 throws `UnimplementedError` at runtime. Select the factory with a33 conditional import (`sembast-web-setup` skill).34* Cross-process safety on VM/Flutter (several processes on the same file):35 `sembast_sqflite` (`getDatabaseFactorySqflite`). The Flutter package36 `tekartik_app_flutter_sembast` bundles a `getDatabaseFactory()` that picks37 the right one per platform.38* Tests and mocks: `newDatabaseFactoryMemory()` (a fresh isolated factory)39 or `openNewInMemoryDatabase()` (an always-empty database) from40 `package:sembast/sembast_memory.dart`. `databaseFactoryMemory` is a shared41 global memory factory: databases with the same path are the same database42 until closed. `databaseFactoryMemoryFs` simulates the io file format in43 memory (useful to test import/export or `compact`).44* `createDatabaseFactoryIo(rootPath: dir)` makes relative paths resolve under45 `dir`. `anyFactory.sandbox(path: dir)` does the same for any factory (io,46 memory, web) and rejects paths escaping the sandbox. `factory.pathContext`47 gives the `path.Context` used and `factory.hasStorage` is false for48 memory.4950### Opening5152* `factory.openDatabase(path, {version, onVersionChanged, mode, codec})`.53 On io `path` is a file path (create the parent directory first, see the54 Flutter example). Opening an already open database returns the same55 instance.56* `mode` defaults to `DatabaseMode.neverFails` (create if missing, delete57 and recreate when corrupted). Others: `create` (create if missing, fail on58 corruption), `existing` (fail when missing), `empty` (wipe content),59 `readOnly` (fail when missing, no writes).60* `db.version`, `db.path`, `await db.close()`. Close before deleting:61 `factory.deleteDatabase(path)`; `factory.databaseExists(path)` checks.62* In Flutter, build the path with `path_provider`63 (`getApplicationDocumentsDirectory()`) and `package:path` `join`. Never64 hard-code a directory. Open in `main()` before `runApp`, or lazily behind a65 single cached `Future<Database>`.66* On Flutter, wrap the open in `disableSembastCooperator()` /67 `enableSembastCooperator()` if the splash screen should not yield, and call68 `disableSembastCooperator()` once in `flutter_test` files (the cooperator's69 `Future.delayed` can hang there).70* Size guidance: io below ~100K records / 100 MB, web below ~30K records.71 Open time grows with record count.7273### Versioning and migrations7475* A database created without `version` has version 1. Pass a constant76 `version` from day one: on creation `onVersionChanged(db, 0, version)` is77 called, on later upgrades `onVersionChanged(db, oldVersion, newVersion)`.78 It is not called when the versions match.79* Inside `onVersionChanged` use the `db` argument freely (`store.add(db,80 ...)`, `db.transaction(...)`): those calls join the transaction opened for81 the migration, and the new version is stored only when the callback82 succeeds.83* Write migrations as `if (oldVersion < N)` steps in increasing order so a84 fresh install (oldVersion 0) runs every step. Seed data (initial records)85 belongs in the `oldVersion == 0` branch.86* Sembast is schema-less: renaming a field means reading, transforming and87 putting records back; changing key type means copying into a new store88 and `oldStore.drop(db)`.89* Lowering `version` is not an error; `onVersionChanged` is called with90 `oldVersion > newVersion`.9192### Codec and encryption9394* `SembastCodec(signature: 'name', codec: myCodec)` where `myCodec` is a95 `Codec<Object?, String>` turning a JSON-encodable value into a single-line96 string. Pass it to every `openDatabase` of that database; opening with a97 different codec throws `DatabaseException` with code98 `DatabaseException.errInvalidCodec`. The signature is stored (encoded by99 the codec itself) so a wrong password is detected at open.100* Sembast ships no cipher. Bring your own (for example on top of101 `pointycastle`); the repository's `sembast_test/lib/encrypt_codec.dart`102 is a demonstration only, not production grade.103* A codec calling a plugin or async API: extend `AsyncContentCodecBase`,104 implement `decodeAsync`/`encodeAsync` and pass it as `codec`.105* The codec cannot be changed later. To encrypt an existing database:106 `exportDatabase` it, `importDatabase(export, factory, newPath, codec:107 codec)`, then delete the old one (or `databaseMerge` between two open108 databases).109* Custom value types are handled by `JsonEncodableCodec` type adapters110 (`package:sembast/utils/type_adapter.dart`); `sembastCodecDefault` already111 handles `Timestamp` and `Blob`. Do not write adapters for models: store112 maps.113114### Export, import, maintenance115116* `package:sembast/utils/sembast_import_export.dart`: `exportDatabase(db,117 {storeNames})` returns a JSON-encodable map, `importDatabase(map, factory,118 path, {codec, storeNames})` creates (after deleting) a database from it and119 returns it open. `exportDatabaseLines` / `importDatabaseLines` /120 `exportLinesToJsonlString` are a line-based format friendlier to git;121 `importDatabaseAny` accepts either format, a JSON string or a list of122 strings. `package:sembast/utils/import_export_io.dart` adds123 `exportDatabaseToJsonlFile` and `importDatabaseFromFile` on the VM.124* `package:sembast/utils/database_utils.dart`: `getNonEmptyStoreNames(db)`125 (there is no other way to list stores) and `databaseMerge(db,126 sourceDatabase: other, storeNames: [...])` which makes `db` content equal127 to `other`.128* `db.compact()` rewrites the io file dropping obsolete lines (automatic129 when 20% of lines are obsolete). `db.checkForChanges()` reloads external130 changes on web/sqflite. `db.reload()` / `db.reOpen()` reread the io file131 (unsafe if writes are in progress).132* `DatabaseException.code` values: `errBadParam`, `errDatabaseNotFound`,133 `errInvalidCodec`, `errDatabaseClosed` (using a closed database).134135## Examples136137### Flutter: single global database opened at startup138139```dart140import 'package:path/path.dart';141import 'package:path_provider/path_provider.dart';142import 'package:sembast/sembast_io.dart';143144Database? _db;145146Future<Database> getDatabase() async {147 if (_db != null) return _db!;148 var dir = await getApplicationDocumentsDirectory();149 await dir.create(recursive: true);150 var dbPath = join(dir.path, 'my_app.db');151 _db = await databaseFactoryIo.openDatabase(dbPath, version: 1);152 return _db!;153}154```155156### Dart VM script with a sandboxed factory157158```dart159import 'package:sembast/sembast_io.dart';160161Future<void> main() async {162 // All relative paths live under .local/db163 var factory = databaseFactoryIo.sandbox(path: '.local/db');164 var db = await factory.openDatabase('cache.db');165 var store = StoreRef<String, int>.main();166 await store.record('runs').put(db, (await store.record('runs').get(db) ?? 0) + 1);167 print(await store.record('runs').get(db));168 await db.close();169}170```171172### Versioned migrations with seed data173174```dart175import 'package:sembast/sembast.dart';176177const kDbVersion = 3;178final productStore = stringMapStoreFactory.store('product');179180Future<Database> openWithMigrations(DatabaseFactory factory, String path) {181 return factory.openDatabase(182 path,183 version: kDbVersion,184 onVersionChanged: (db, oldVersion, newVersion) async {185 // oldVersion is 0 on creation: every step below runs.186 if (oldVersion < 1) {187 await productStore.record('demo_1').put(db, {'name': 'Demo 1', 'price': 10});188 }189 if (oldVersion < 2) {190 // Price change shipped in app version 2.191 await productStore.record('demo_1').update(db, {'price': 15});192 }193 if (oldVersion < 3) {194 // Tag every product whose name contains 'demo'.195 await productStore.update(196 db,197 {'tag': 'demo'},198 finder: Finder(199 filter: Filter.custom(200 (record) => (record['name'] as String).toLowerCase().contains('demo'),201 ),202 ),203 );204 }205 },206 );207}208```209210### Migrate int keys to String keys in a new store211212```dart213import 'package:sembast/sembast.dart';214215final fruitStoreV1 = intMapStoreFactory.store('fruit');216final fruitStore = stringMapStoreFactory.store('fruit_v2');217218Future<Database> openFruits(DatabaseFactory factory, String path) {219 return factory.openDatabase(220 path,221 version: 2,222 onVersionChanged: (db, oldVersion, newVersion) async {223 if (oldVersion == 1) {224 await db.transaction((txn) async {225 var values = (await fruitStoreV1.find(txn)).values.toList();226 await fruitStore.addAll(txn, values);227 await fruitStoreV1.drop(txn);228 });229 }230 },231 );232}233```234235### Unit test with an in-memory factory236237```dart238import 'package:sembast/sembast_memory.dart';239import 'package:test/test.dart';240241void main() {242 test('put/get', () async {243 var factory = newDatabaseFactoryMemory();244 var db = await factory.openDatabase('test.db');245 var record = StoreRef<String, String>.main().record('key');246 await record.put(db, 'value');247 expect(await record.get(db), 'value');248 await db.close();249250 // Or an always-empty database in one call:251 var db2 = await openNewInMemoryDatabase();252 expect(await record.get(db2), isNull);253 await db2.close();254 });255}256```257258### Custom (async) codec and wrong-codec detection259260```dart261import 'dart:convert';262263import 'package:sembast/sembast.dart';264265/// Demo only: reverses the JSON text. Replace with real encryption.266class ReverseCodec extends AsyncContentCodecBase {267 String _reverse(String text) => text.split('').reversed.join();268269 @override270 Future<Object?> decodeAsync(String encoded) async =>271 jsonDecode(_reverse(encoded));272273 @override274 Future<String> encodeAsync(Object? input) async =>275 _reverse(jsonEncode(input));276}277278Future<Database> openEncoded(DatabaseFactory factory, String path) async {279 var codec = SembastCodec(signature: 'reverse_v1', codec: ReverseCodec());280 try {281 return await factory.openDatabase(path, codec: codec);282 } on DatabaseException catch (e) {283 if (e.code == DatabaseException.errInvalidCodec) {284 // Existing file written with another codec (or password).285 rethrow;286 }287 rethrow;288 }289}290```291292### Convert an existing database to an encrypted one293294```dart295import 'package:sembast/utils/sembast_import_export.dart';296297Future<Database> migrateToCodec(298 DatabaseFactory factory,299 SembastCodec codec,300) async {301 const plainPath = 'my_database.db';302 const encryptedPath = 'my_database_encrypted.db';303 if (await factory.databaseExists(plainPath)) {304 var plainDb = await factory.openDatabase(plainPath);305 var export = await exportDatabase(plainDb);306 await plainDb.close();307 var db = await importDatabase(export, factory, encryptedPath, codec: codec);308 await factory.deleteDatabase(plainPath);309 return db;310 }311 return factory.openDatabase(encryptedPath, codec: codec);312}313```314315### Backup and restore as JSON lines (VM)316317```dart318import 'package:sembast/sembast_io.dart';319import 'package:sembast/utils/import_export_io.dart';320321Future<void> backup(Database db) =>322 exportDatabaseToJsonlFile(db, 'backup/app.jsonl');323324Future<Database> restore() =>325 importDatabaseFromFile('backup/app.jsonl', databaseFactoryIo, 'restored.db');326```327328## Common mistakes329330* Opening the database in several places or on every screen. Keep one331 instance; opening is the expensive operation.332* Using `databaseFactoryIo` in a Flutter web build (runtime333 `UnimplementedError`), or `databaseFactoryMemory` in production.334* Forgetting `dir.create(recursive: true)` on Flutter, or using a relative335 path such as `'app.db'` on mobile.336* Opening without `version` then adding one later: the existing database is337 already at version 1, so creation logic guarded by `oldVersion == 0` never338 runs for old installs. Handle `oldVersion == 1` too.339* Doing migration writes with a different `Database` object than the `db`340 passed to `onVersionChanged`.341* Changing the codec of an existing database in place. Export and import.342* Deleting the database file directly while it is open; call `db.close()`343 then `factory.deleteDatabase(path)`.344345## More346347See [references/factories.md](references/factories.md) for the factory,348`DatabaseMode`, codec and import/export listings.