persisting-data-with-drift — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited persisting-data-with-drift (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Implement type-safe reactive SQL database using Drift (formerly Moor). Drift provides compile-time verified SQL queries, automatic migrations, and reactive streams built on SQLite.
dependencies:
drift: ^2.32.0
sqlite3_flutter_libs: ^0.6.0+eol
path_provider: ^2.1.0
path: ^1.9.1
dev_dependencies:
drift_dev: ^2.32.0
build_runner: ^2.14.1import 'package:drift/drift.dart';
import 'dart:io';
part 'database.g.dart';
// Define tables
class Users extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get name => text().withLength(min: 1, max: 50)();
TextColumn get email => text().unique()();
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
}
class Todos extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get title => text()();
BoolColumn get isCompleted => boolean().withDefault(const Constant(false))();
IntColumn get userId => integer().references(Users, #id, onDelete: KeyAction.cascade)();
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
}
@DriftDatabase(tables: [Users, Todos])
class AppDatabase extends _$AppDatabase {
AppDatabase() : super(_openConnection());
@override
int get schemaVersion => 1;
// Queries
Future<List<User>> getAllUsers() => select(users).get();
Stream<List<User>> watchAllUsers() => select(users).watch();
Future<User> getUserById(int id) =>
(select(users)..where((u) => u.id.equals(id))).getSingle();
Future<int> insertUser(UsersCompanion user) => into(users).insert(user);
Future<bool> updateUser(User user) => update(users).replace(user);
Future<int> deleteUser(int id) =>
(delete(users)..where((u) => u.id.equals(id))).go();
}
// Open connection
LazyDatabase _openConnection() {
return LazyDatabase(() async {
final dbFolder = await getApplicationDocumentsDirectory();
final file = File(p.join(dbFolder.path, 'app.db'));
return NativeDatabase(file);
});
}flutter pub run build_runner build --delete-conflicting-outputslate AppDatabase database;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
database = AppDatabase();
runApp(MyApp());
}Insert:
final userId = await database.insertUser(
UsersCompanion.insert(
name: 'John Doe',
email: '[email protected]',
),
);Query:
// Simple query
final allUsers = await database.getAllUsers();
// With where clause
final activeUsers = await (database.select(database.users)
..where((u) => u.isActive.equals(true)))
.get();
// Join query
final query = database.select(database.todos).join([
leftOuterJoin(database.users, database.users.id.equalsExp(database.todos.userId)),
]);
final results = await query.get();
for (final row in results) {
final todo = row.readTable(database.todos);
final user = row.readTable(database.users);
print('${todo.title} by ${user.name}');
}Update:
await database.updateUser(
user.copyWith(name: 'Jane Doe'),
);
// Or partial update
await (database.update(database.users)
..where((u) => u.id.equals(userId)))
.write(UsersCompanion(name: Value('Jane')));Delete:
await database.deleteUser(userId);// Watch changes
Stream<List<User>> watchUsers() {
return database.select(database.users).watch();
}
// In widget
StreamBuilder<List<User>>(
stream: database.watchAllUsers(),
builder: (context, snapshot) {
if (!snapshot.hasData) return CircularProgressIndicator();
return ListView(
children: snapshot.data!.map((u) => UserTile(u)).toList(),
);
},
)@DriftDatabase(tables: [Users, Todos])
class AppDatabase extends _$AppDatabase {
@override
int get schemaVersion => 2; // Increment version
@override
MigrationStrategy get migration => MigrationStrategy(
onCreate: (Migrator m) async {
await m.createAll();
},
onUpgrade: (Migrator m, int from, int to) async {
if (from == 1) {
// Add new column
await m.addColumn(users, users.phoneNumber);
}
},
);
}await database.transaction(() async {
final userId = await database.insertUser(userCompanion);
await database.into(database.todos).insert(
TodosCompanion.insert(
title: 'First todo',
userId: userId,
),
);
});Migrated to sqlite3 package v3.x:
sqlite3.wasm file in web/ directoryMigration checklist:
dependencies:
drift: ^2.32.0
sqlite3_flutter_libs: ^0.5.20 # Update to latestEnable Write-Ahead Logging for 10x better write performance:
LazyDatabase _openConnection() {
return LazyDatabase(() async {
final dbFolder = await getApplicationDocumentsDirectory();
final file = File(p.join(dbFolder.path, 'app.db'));
final database = NativeDatabase(file);
// Enable WAL mode
await database.customStatement('PRAGMA journal_mode=WAL;');
return database;
});
}WAL Mode Benefits:
⚠️ WAL Mode Caveats:
// Run checkpoint every app launch or periodically
await database.customStatement('PRAGMA wal_checkpoint(TRUNCATE);');*⚠️ NEVER manually edit generated files (`.g.dart`)**
ALWAYS follow this workflow:
.dart or .drift files dart run build_runner build --delete-conflicting-outputsCommon type safety errors:
// ❌ Wrong - nullable/non-nullable mismatch
TextColumn get name => text().nullable()();
final user = UsersCompanion.insert(name: null); // Type error
// ✅ Correct - use Value wrapper for nullable updates
await database.update(users).replace(
user.copyWith(name: Value(newName)), // or Value.absent()
);Error: "Table X doesn't exist in the database"
schemaVersion and add migrationError: "column Y has no value"
Companion.insert() with required fields.withDefault() to column definitionError: "Nested transactions causing deadlocks"
transaction() inside another transactionError: "Type mismatch after migration"
.g.dart filesbuild_runner clean then rebuildPerformance issues with watch() streams:
schemaVersion when changing schema.database.close() when app terminates.*.g.dart files - always regenerate with build_runner.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.