Skip to main content

sembast-crud

Use when writing Dart or Flutter code that stores, reads, updates or deletes records with package:sembast: StoreRef, intMapStoreFactory, stringMapStoreFactory, RecordRef, RecordsRef, add/put/update/get/delete, merge and FieldValue.delete, dotted field paths, db.transaction and the Transaction/DatabaseClient parameter, supported value types (Timestamp, Blob, immutable read values, cloneMap) and store change triggers (addOnChangesListener). Opening databases and queries have their own skills.

Zur Installation springen

Quellinformationen

Repository
tekartik/sembast.dart
Letzte Quellaktivität
10. September 2026 um 13:15
Erkannte Sprache von SKILL.md
Englisch
Sterne
867
Forks
71

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
2 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
sembast-crud
description
Use when writing Dart or Flutter code that stores, reads, updates or deletes records with package:sembast: StoreRef, intMapStoreFactory, stringMapStoreFactory, RecordRef, RecordsRef, add/put/update/get/delete, merge and FieldValue.delete, dotted field paths, db.transaction and the Transaction/DatabaseClient parameter, supported value types (Timestamp, Blob, immutable read values, cloneMap) and store change triggers (addOnChangesListener). Opening databases and queries have their own skills.
# sembast: stores, records, writes and transactions `package:sembast` is a NoSQL document database (pure Dart, VM, Flutter and, with `sembast_web`, the web). Data lives in typed stores of records. A `StoreRef<K, V>` and a `RecordRef<K, V>` are immutable pointers, they hold no data; every data method takes a `DatabaseClient` (the `Database` or a `Transaction`) as first argument. ```dart import 'package:sembast/sembast.dart'; // Declare stores once, globally. Keys are int, values are maps. final productStore = intMapStoreFactory.store('product'); Future<void> demo(Database db) async { var key = await productStore.add(db, {'name': 'Lamp', 'price': 10}); await productStore.record(key).update(db, {'price': 12}); var value = await productStore.record(key).get(db); print(value); // {name: Lamp, price: 12} await productStore.record(key).delete(db); } ``` ## Guidelines ### Stores and references * Import `package:sembast/sembast.dart`. It exports `StoreRef`, `RecordRef`, `RecordsRef`, `Database`, `Transaction`, `DatabaseClient`, `Field`, `FieldValue`, `FieldKey` and the extension methods (`add`, `put`, `get`...) on the refs. `sembast_io.dart` and `sembast_memory.dart` re-export it. * Use `intMapStoreFactory.store('name')` for `StoreRef<int, Map<String, Object?>>` (auto-increment int keys) and `stringMapStoreFactory.store('name')` for `StoreRef<String, Map<String, Object?>>` (generated unique string keys). Omit the name (`intMapStoreFactory.store()`) for the main store. * For other value types use `StoreRef<K, V>('name')` or `StoreRef<K, V>.main()`, for example `StoreRef<String, String>.main()` for key/value settings. `K` must be `int` or `String`, nothing else. * Store names must not start with `_`. A store exists only while it has records; there is no create/delete store call. `store.drop(db)` deletes all its records, `db.dropAll()` (extension on `DatabaseClient`) clears the whole database. * Declare stores and fixed records as top-level `final` variables and reuse them; `store.record(key)` and `store.records(keys)` are cheap. * `RecordsRef` (from `store.records([k1, k2])` or `store.recordsFromRefs(refs)`) batches operations on known keys: `get`, `getSnapshots`, `add`, `put`, `update`, `delete`, `onSnapshots`, plus `length`, `refs` and `[index]`. ### Writing * `store.add(db, value)` inserts with a generated key and returns it. `store.addAll(db, values)` returns the list of keys in one transaction. `record.add(db, value)` inserts with the record's key and returns `null` when the record already exists (nothing is written). * `record.put(db, value)` creates or replaces (upsert) and returns the stored value. `put(..., merge: true)` merges a map into the existing map. `put(..., ifNotExists: true)` writes only when absent and returns the existing value otherwise. `merge` and `ifNotExists` cannot both be true. * `record.update(db, value)` merges into an existing record and returns the new value, or `null` when the record does not exist (nothing is created). `store.update(db, value, finder: finder)` merges the same value into every matching record and returns the count (all records when `finder` is null). * In `update` (and `put(merge: true)`) map keys are paths: `'address.city'` targets the nested `city` field. Set a field to `FieldValue.delete` to remove it. Use `FieldKey.escape('with.dots')` for a literal key containing a dot. `add` and plain `put` store keys verbatim, no path interpretation. * `record.delete(db)` returns the key or `null` if absent. `store.delete(db, finder: finder)` deletes matching records and returns the count; without a finder it clears the store. `records.delete(db)` deletes a batch. * Need a key before inserting? `store.generateKey(db)` (typed `K`) or `store.generateIntKey(db)`. ### Values * Supported value types: `String`, `int`, `double`, `bool`, `null` (inside maps and lists only), `Map<String, Object?>`, `List<Object?>`, `Timestamp` (`package:sembast/timestamp.dart`) and `Blob` (`package:sembast/blob.dart`). Anything else (`DateTime`, `Uint8List`, `Iterable`, enums, custom classes) throws `ArgumentError` on write. Convert with `Timestamp.fromDateTime(dt)` / `timestamp.toDateTime()`, `Blob(bytes)` / `blob.bytes`, `iterable.toList()`, `myEnum.name`. * A record's root value cannot be `null`; `put(db, null)` throws. * Maps are cast to `Map<String, Object?>` and lists to `List<Object?>` on write. Typed maps such as `Map<String, int>` work; `Map<int, ...>` does not. * Values returned by `get`, `find`, snapshots and streams are read-only: writing to them throws `StateError('read only')`. Clone before mutating: `cloneMap(value)`, `cloneList(value)`, `cloneValue(value)` from `package:sembast/utils/value_utils.dart`. * Keep records small (a few KB). Store big binaries in files and keep a reference in the record. ### Transactions * Group more than one write in `db.transaction((txn) async { ... })`. It is atomic (an exception rolls back every change and is rethrown) and much faster than separate writes: each standalone write is its own transaction. * Inside the callback use only `txn`. Calling any method with `db` inside a transaction deadlocks (the call waits for the transaction that waits for it). Set `debugSembastWarnDatabaseCallInTransaction = true` during development to get a printed diagnosis after 10 s. * The callback may return a value: `var key = await db.transaction((txn) => store.add(txn, value));`. * Write helpers that take a `DatabaseClient client` parameter so they work with both `db` and `txn`. * Make transaction bodies idempotent and side-effect free: on `sembast_web` and `sembast_sqflite` a transaction is re-run when another tab or process wrote concurrently. * Reads inside a transaction see the transaction's pending changes; other readers see them after commit. Reads never need a transaction. * Batch size: around 100-1000 writes per transaction; split larger imports. * Do not open a nested `db.transaction` from inside a transaction (also a deadlock). The only exception is `onVersionChanged` during `openDatabase`, where `db` calls join the open transaction. ### Change triggers (in-transaction listeners) * `store.addOnChangesListener(db, (txn, changes) async { ... })` runs inside the transaction that modified the store, before it commits. Use it for cascading deletes or derived data; use the `txn` argument for writes. * Each `RecordChange` has `oldSnapshot`/`newSnapshot` (null for add/delete), `oldValue`/`newValue`, `ref`, and `isAdd`, `isUpdate`, `isDelete`. * Writing to the same store from the listener triggers it again; guard against infinite loops. Remove with `store.removeOnChangesListener(db, sameCallback)`. * `db.addAllStoresOnChangesListener(onChanges, excludedStoreNames: [...], storePredicate: (name) => ...)` is the untyped variant for every store. * Register triggers right after opening, before the app uses the database. For reactive UI updates after commit use `onSnapshot`/`onSnapshots` streams instead (see the `sembast-query` skill). ## Examples ### Typed store with add, put, update, delete ```dart import 'package:sembast/sembast.dart'; final animals = intMapStoreFactory.store('animals'); Future<void> crud(Database db) async { // Insert, generated int key. var key = await animals.add(db, {'name': 'cat', 'age': 4}); // Upsert with an explicit key. await animals.record(100).put(db, {'name': 'dog'}); // Merge fields into an existing record. await animals.record(key).update(db, {'age': 5, 'color': 'black'}); // Read the value. var cat = await animals.record(key).get(db); print(cat); // {name: cat, age: 5, color: black} // Delete. await animals.record(100).delete(db); print(await animals.record(100).exists(db)); // false } ``` ### Key/value settings in the main store ```dart import 'package:sembast/sembast.dart'; final settings = StoreRef<String, String>.main(); final themeRecord = settings.record('theme'); Future<String> getTheme(Database db) async => await themeRecord.get(db) ?? 'light'; Future<void> setTheme(Database db, String theme) => themeRecord.put(db, theme); ``` ### Nested fields, dotted paths and FieldValue.delete ```dart import 'package:sembast/sembast.dart'; final store = intMapStoreFactory.store('people'); Future<void> paths(Database db) async { var key = await store.add(db, { 'name': 'Felix', 'age': 4, 'address': {'city': 'Ledignan'}, 'with.dots': 'kept as is by add', }); var record = store.record(key); await record.update(db, { 'address.city': 'San Francisco', // nested path 'age': FieldValue.delete, // remove a field FieldKey.escape('with.dots'): 'literal key', // key containing a dot }); print(await record.get(db)); // {name: Felix, address: {city: San Francisco}, with.dots: literal key} } ``` ### Transaction with a returned value and a batch delete ```dart import 'package:sembast/sembast.dart'; final shop = intMapStoreFactory.store('shop'); Future<List<int>> replaceAll( Database db, List<Map<String, Object?>> products, ) { return db.transaction((txn) async { // Only txn is used inside the transaction, never db. await shop.delete(txn); return shop.addAll(txn, products); }); } Future<void> deleteSome(Database db, List<int> keys) => db.transaction((txn) => shop.records(keys).delete(txn)); ``` ### Add or update by a unique field, race free ```dart import 'package:sembast/sembast.dart'; final products = intMapStoreFactory.store('product'); /// Insert or update the product whose 'code' matches. Future<void> addOrUpdateProduct(Database db, Map<String, Object?> map) { return db.transaction((txn) async { var existing = await products .query(finder: Finder(filter: Filter.equals('code', map['code']))) .getSnapshot(txn); if (existing == null) { await products.add(txn, map); } else { await existing.ref.update(txn, map); } }); } ``` ### Helpers accepting a DatabaseClient ```dart import 'package:sembast/sembast.dart'; final counters = StoreRef<String, int>('counters'); /// Works with a Database or a Transaction. Future<int> increment(DatabaseClient client, String name) async { var record = counters.record(name); var value = (await record.get(client) ?? 0) + 1; await record.put(client, value); return value; } Future<void> incrementTwo(Database db) => db.transaction((txn) async { await increment(txn, 'a'); await increment(txn, 'b'); }); ``` ### Timestamp and Blob values ```dart import 'dart:typed_data'; import 'package:sembast/blob.dart'; import 'package:sembast/sembast.dart'; import 'package:sembast/timestamp.dart'; final files = stringMapStoreFactory.store('files'); Future<void> saveFile(Database db, String id, Uint8List bytes) async { await files.record(id).put(db, { 'createdAt': Timestamp.now(), // never a DateTime 'thumbnail': Blob(bytes), // never a raw Uint8List }); var value = await files.record(id).get(db); var createdAt = (value!['createdAt'] as Timestamp).toDateTime(); var thumbnail = (value['thumbnail'] as Blob).bytes; print('$createdAt ${thumbnail.length} bytes'); } ``` ### Clone a read value before modifying it ```dart import 'package:sembast/sembast.dart'; import 'package:sembast/utils/value_utils.dart'; final store = intMapStoreFactory.store('notes'); Future<void> rename(Database db, int key) async { var value = await store.record(key).get(db); if (value == null) return; // value is read-only; value['title'] = 'x' would throw StateError. var map = cloneMap(value); map['title'] = 'Renamed'; await store.record(key).put(db, map); } ``` ### Cascade delete with a store trigger ```dart
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen