support for riverpod persistence

This commit is contained in:
Fabian Freund
2025-09-26 16:32:30 +02:00
parent e20cb336b1
commit e872901ee2
12 changed files with 2437 additions and 1 deletions
@@ -22,6 +22,7 @@ import 'package:weblibre/features/user/data/database/daos/cache.dart';
import 'package:weblibre/features/user/data/database/daos/onboarding.dart';
import 'package:weblibre/features/user/data/database/daos/setting.dart';
import 'package:weblibre/features/user/data/database/database.drift.dart';
import 'package:weblibre/features/user/data/database/database.steps.dart';
@DriftDatabase(
include: {'definitions.drift'},
@@ -29,13 +30,18 @@ import 'package:weblibre/features/user/data/database/database.drift.dart';
)
class UserDatabase extends $UserDatabase {
@override
final int schemaVersion = 1;
final int schemaVersion = 2;
@override
MigrationStrategy get migration => MigrationStrategy(
beforeOpen: (details) async {
await customStatement('PRAGMA foreign_keys = ON;');
},
onUpgrade: stepByStep(
from1To2: (m, schema) async {
await m.createTable(schema.riverpod);
},
),
);
UserDatabase(super.e);
@@ -17,6 +17,7 @@ abstract class $UserDatabase extends i0.GeneratedDatabase {
late final i1.Setting setting = i1.Setting(this);
late final i1.IconCache iconCache = i1.IconCache(this);
late final i1.Onboarding onboarding = i1.Onboarding(this);
late final i1.Riverpod riverpod = i1.Riverpod(this);
late final i2.SettingDao settingDao = i2.SettingDao(this as i3.UserDatabase);
late final i4.CacheDao cacheDao = i4.CacheDao(this as i3.UserDatabase);
late final i5.OnboardingDao onboardingDao = i5.OnboardingDao(
@@ -33,6 +34,7 @@ abstract class $UserDatabase extends i0.GeneratedDatabase {
setting,
iconCache,
onboarding,
riverpod,
];
}
@@ -45,6 +47,8 @@ class $UserDatabaseManager {
i1.$IconCacheTableManager(_db, _db.iconCache);
i1.$OnboardingTableManager get onboarding =>
i1.$OnboardingTableManager(_db, _db.onboarding);
i1.$RiverpodTableManager get riverpod =>
i1.$RiverpodTableManager(_db, _db.riverpod);
}
extension DefineFunctions on i7.CommonDatabase {
@@ -0,0 +1,214 @@
// dart format width=80
import 'package:drift/internal/versioned_schema.dart' as i0;
import 'package:drift/drift.dart' as i1;
import 'dart:typed_data' as i2;
import 'package:drift/drift.dart'; // ignore_for_file: type=lint,unused_import
// GENERATED BY drift_dev, DO NOT MODIFY.
final class Schema2 extends i0.VersionedSchema {
Schema2({required super.database}) : super(version: 2);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
setting,
iconCache,
onboarding,
riverpod,
];
late final Shape0 setting = Shape0(
source: i0.VersionedTable(
entityName: 'setting',
withoutRowId: false,
isStrict: true,
tableConstraints: [],
columns: [_column_0, _column_1, _column_2],
attachedDatabase: database,
),
alias: null,
);
late final Shape1 iconCache = Shape1(
source: i0.VersionedTable(
entityName: 'icon_cache',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [_column_3, _column_4, _column_5],
attachedDatabase: database,
),
alias: null,
);
late final Shape2 onboarding = Shape2(
source: i0.VersionedTable(
entityName: 'onboarding',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [_column_6, _column_7],
attachedDatabase: database,
),
alias: null,
);
late final Shape3 riverpod = Shape3(
source: i0.VersionedTable(
entityName: 'riverpod',
withoutRowId: true,
isStrict: false,
tableConstraints: [],
columns: [_column_0, _column_8, _column_9, _column_10],
attachedDatabase: database,
),
alias: null,
);
}
class Shape0 extends i0.VersionedTable {
Shape0({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<String> get key =>
columnsByName['key']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get partitionKey =>
columnsByName['partition_key']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<i1.DriftAny> get value =>
columnsByName['value']! as i1.GeneratedColumn<i1.DriftAny>;
}
i1.GeneratedColumn<String> _column_0(String aliasedName) =>
i1.GeneratedColumn<String>(
'key',
aliasedName,
false,
type: i1.DriftSqlType.string,
$customConstraints: 'PRIMARY KEY NOT NULL',
);
i1.GeneratedColumn<String> _column_1(String aliasedName) =>
i1.GeneratedColumn<String>(
'partition_key',
aliasedName,
true,
type: i1.DriftSqlType.string,
$customConstraints: '',
);
i1.GeneratedColumn<i1.DriftAny> _column_2(String aliasedName) =>
i1.GeneratedColumn<i1.DriftAny>(
'value',
aliasedName,
true,
type: i1.DriftSqlType.any,
$customConstraints: '',
);
class Shape1 extends i0.VersionedTable {
Shape1({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<String> get origin =>
columnsByName['origin']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<i2.Uint8List> get iconData =>
columnsByName['icon_data']! as i1.GeneratedColumn<i2.Uint8List>;
i1.GeneratedColumn<DateTime> get fetchDate =>
columnsByName['fetch_date']! as i1.GeneratedColumn<DateTime>;
}
i1.GeneratedColumn<String> _column_3(String aliasedName) =>
i1.GeneratedColumn<String>(
'origin',
aliasedName,
false,
type: i1.DriftSqlType.string,
$customConstraints: 'PRIMARY KEY NOT NULL',
);
i1.GeneratedColumn<i2.Uint8List> _column_4(String aliasedName) =>
i1.GeneratedColumn<i2.Uint8List>(
'icon_data',
aliasedName,
false,
type: i1.DriftSqlType.blob,
$customConstraints: 'NOT NULL',
);
i1.GeneratedColumn<DateTime> _column_5(String aliasedName) =>
i1.GeneratedColumn<DateTime>(
'fetch_date',
aliasedName,
false,
type: i1.DriftSqlType.dateTime,
$customConstraints: 'NOT NULL',
);
class Shape2 extends i0.VersionedTable {
Shape2({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get revision =>
columnsByName['revision']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<DateTime> get completionDate =>
columnsByName['completion_date']! as i1.GeneratedColumn<DateTime>;
}
i1.GeneratedColumn<int> _column_6(String aliasedName) =>
i1.GeneratedColumn<int>(
'revision',
aliasedName,
false,
type: i1.DriftSqlType.int,
$customConstraints: 'NOT NULL',
);
i1.GeneratedColumn<DateTime> _column_7(String aliasedName) =>
i1.GeneratedColumn<DateTime>(
'completion_date',
aliasedName,
false,
type: i1.DriftSqlType.dateTime,
$customConstraints: 'NOT NULL',
);
class Shape3 extends i0.VersionedTable {
Shape3({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<String> get key =>
columnsByName['key']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get json =>
columnsByName['json']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<DateTime> get expireAt =>
columnsByName['expireAt']! as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<String> get destroyKey =>
columnsByName['destroyKey']! as i1.GeneratedColumn<String>;
}
i1.GeneratedColumn<String> _column_8(String aliasedName) =>
i1.GeneratedColumn<String>(
'json',
aliasedName,
false,
type: i1.DriftSqlType.string,
$customConstraints: 'NOT NULL',
);
i1.GeneratedColumn<DateTime> _column_9(String aliasedName) =>
i1.GeneratedColumn<DateTime>(
'expireAt',
aliasedName,
true,
type: i1.DriftSqlType.dateTime,
$customConstraints: '',
);
i1.GeneratedColumn<String> _column_10(String aliasedName) =>
i1.GeneratedColumn<String>(
'destroyKey',
aliasedName,
true,
type: i1.DriftSqlType.string,
$customConstraints: '',
);
i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
}) {
return (currentVersion, database) async {
switch (currentVersion) {
case 1:
final schema = Schema2(database: database);
final migrator = i1.Migrator(database, schema);
await from1To2(migrator, schema);
return 2;
default:
throw ArgumentError.value('Unknown migration from $currentVersion');
}
};
}
i1.OnUpgrade stepByStep({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
}) => i0.VersionedSchema.stepByStepHelper(
step: migrationSteps(from1To2: from1To2),
);
@@ -15,6 +15,13 @@ CREATE TABLE onboarding (
completion_date DATETIME NOT NULL
);
CREATE TABLE riverpod (
"key" TEXT PRIMARY KEY NOT NULL,
json TEXT NOT NULL,
expireAt DATETIME,
destroyKey TEXT
) WITHOUT ROWID;
evictCacheEntries:
DELETE FROM icon_cache
WHERE rowid IN (
@@ -489,6 +489,187 @@ typedef $OnboardingProcessedTableManager =
i1.OnboardingData,
i0.PrefetchHooks Function()
>;
typedef $RiverpodCreateCompanionBuilder =
i1.RiverpodCompanion Function({
required String key,
required String json,
i0.Value<DateTime?> expireAt,
i0.Value<String?> destroyKey,
});
typedef $RiverpodUpdateCompanionBuilder =
i1.RiverpodCompanion Function({
i0.Value<String> key,
i0.Value<String> json,
i0.Value<DateTime?> expireAt,
i0.Value<String?> destroyKey,
});
class $RiverpodFilterComposer
extends i0.Composer<i0.GeneratedDatabase, i1.Riverpod> {
$RiverpodFilterComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
i0.ColumnFilters<String> get key => $composableBuilder(
column: $table.key,
builder: (column) => i0.ColumnFilters(column),
);
i0.ColumnFilters<String> get json => $composableBuilder(
column: $table.json,
builder: (column) => i0.ColumnFilters(column),
);
i0.ColumnFilters<DateTime> get expireAt => $composableBuilder(
column: $table.expireAt,
builder: (column) => i0.ColumnFilters(column),
);
i0.ColumnFilters<String> get destroyKey => $composableBuilder(
column: $table.destroyKey,
builder: (column) => i0.ColumnFilters(column),
);
}
class $RiverpodOrderingComposer
extends i0.Composer<i0.GeneratedDatabase, i1.Riverpod> {
$RiverpodOrderingComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
i0.ColumnOrderings<String> get key => $composableBuilder(
column: $table.key,
builder: (column) => i0.ColumnOrderings(column),
);
i0.ColumnOrderings<String> get json => $composableBuilder(
column: $table.json,
builder: (column) => i0.ColumnOrderings(column),
);
i0.ColumnOrderings<DateTime> get expireAt => $composableBuilder(
column: $table.expireAt,
builder: (column) => i0.ColumnOrderings(column),
);
i0.ColumnOrderings<String> get destroyKey => $composableBuilder(
column: $table.destroyKey,
builder: (column) => i0.ColumnOrderings(column),
);
}
class $RiverpodAnnotationComposer
extends i0.Composer<i0.GeneratedDatabase, i1.Riverpod> {
$RiverpodAnnotationComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
i0.GeneratedColumn<String> get key =>
$composableBuilder(column: $table.key, builder: (column) => column);
i0.GeneratedColumn<String> get json =>
$composableBuilder(column: $table.json, builder: (column) => column);
i0.GeneratedColumn<DateTime> get expireAt =>
$composableBuilder(column: $table.expireAt, builder: (column) => column);
i0.GeneratedColumn<String> get destroyKey => $composableBuilder(
column: $table.destroyKey,
builder: (column) => column,
);
}
class $RiverpodTableManager
extends
i0.RootTableManager<
i0.GeneratedDatabase,
i1.Riverpod,
i1.RiverpodData,
i1.$RiverpodFilterComposer,
i1.$RiverpodOrderingComposer,
i1.$RiverpodAnnotationComposer,
$RiverpodCreateCompanionBuilder,
$RiverpodUpdateCompanionBuilder,
(
i1.RiverpodData,
i0.BaseReferences<
i0.GeneratedDatabase,
i1.Riverpod,
i1.RiverpodData
>,
),
i1.RiverpodData,
i0.PrefetchHooks Function()
> {
$RiverpodTableManager(i0.GeneratedDatabase db, i1.Riverpod table)
: super(
i0.TableManagerState(
db: db,
table: table,
createFilteringComposer: () =>
i1.$RiverpodFilterComposer($db: db, $table: table),
createOrderingComposer: () =>
i1.$RiverpodOrderingComposer($db: db, $table: table),
createComputedFieldComposer: () =>
i1.$RiverpodAnnotationComposer($db: db, $table: table),
updateCompanionCallback:
({
i0.Value<String> key = const i0.Value.absent(),
i0.Value<String> json = const i0.Value.absent(),
i0.Value<DateTime?> expireAt = const i0.Value.absent(),
i0.Value<String?> destroyKey = const i0.Value.absent(),
}) => i1.RiverpodCompanion(
key: key,
json: json,
expireAt: expireAt,
destroyKey: destroyKey,
),
createCompanionCallback:
({
required String key,
required String json,
i0.Value<DateTime?> expireAt = const i0.Value.absent(),
i0.Value<String?> destroyKey = const i0.Value.absent(),
}) => i1.RiverpodCompanion.insert(
key: key,
json: json,
expireAt: expireAt,
destroyKey: destroyKey,
),
withReferenceMapper: (p0) => p0
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
.toList(),
prefetchHooksCallback: null,
),
);
}
typedef $RiverpodProcessedTableManager =
i0.ProcessedTableManager<
i0.GeneratedDatabase,
i1.Riverpod,
i1.RiverpodData,
i1.$RiverpodFilterComposer,
i1.$RiverpodOrderingComposer,
i1.$RiverpodAnnotationComposer,
$RiverpodCreateCompanionBuilder,
$RiverpodUpdateCompanionBuilder,
(
i1.RiverpodData,
i0.BaseReferences<i0.GeneratedDatabase, i1.Riverpod, i1.RiverpodData>,
),
i1.RiverpodData,
i0.PrefetchHooks Function()
>;
class Setting extends i0.Table with i0.TableInfo<Setting, i1.SettingData> {
@override
@@ -1126,6 +1307,257 @@ class OnboardingCompanion extends i0.UpdateCompanion<i1.OnboardingData> {
}
}
class Riverpod extends i0.Table with i0.TableInfo<Riverpod, i1.RiverpodData> {
@override
final i0.GeneratedDatabase attachedDatabase;
final String? _alias;
Riverpod(this.attachedDatabase, [this._alias]);
late final i0.GeneratedColumn<String> key = i0.GeneratedColumn<String>(
'key',
aliasedName,
false,
type: i0.DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: 'PRIMARY KEY NOT NULL',
);
late final i0.GeneratedColumn<String> json = i0.GeneratedColumn<String>(
'json',
aliasedName,
false,
type: i0.DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL',
);
late final i0.GeneratedColumn<DateTime> expireAt =
i0.GeneratedColumn<DateTime>(
'expireAt',
aliasedName,
true,
type: i0.DriftSqlType.dateTime,
requiredDuringInsert: false,
$customConstraints: '',
);
late final i0.GeneratedColumn<String> destroyKey = i0.GeneratedColumn<String>(
'destroyKey',
aliasedName,
true,
type: i0.DriftSqlType.string,
requiredDuringInsert: false,
$customConstraints: '',
);
@override
List<i0.GeneratedColumn> get $columns => [key, json, expireAt, destroyKey];
@override
String get aliasedName => _alias ?? actualTableName;
@override
String get actualTableName => $name;
static const String $name = 'riverpod';
@override
Set<i0.GeneratedColumn> get $primaryKey => {key};
@override
i1.RiverpodData map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return i1.RiverpodData(
key: attachedDatabase.typeMapping.read(
i0.DriftSqlType.string,
data['${effectivePrefix}key'],
)!,
json: attachedDatabase.typeMapping.read(
i0.DriftSqlType.string,
data['${effectivePrefix}json'],
)!,
expireAt: attachedDatabase.typeMapping.read(
i0.DriftSqlType.dateTime,
data['${effectivePrefix}expireAt'],
),
destroyKey: attachedDatabase.typeMapping.read(
i0.DriftSqlType.string,
data['${effectivePrefix}destroyKey'],
),
);
}
@override
Riverpod createAlias(String alias) {
return Riverpod(attachedDatabase, alias);
}
@override
bool get withoutRowId => true;
@override
bool get dontWriteConstraints => true;
}
class RiverpodData extends i0.DataClass
implements i0.Insertable<i1.RiverpodData> {
final String key;
final String json;
final DateTime? expireAt;
final String? destroyKey;
const RiverpodData({
required this.key,
required this.json,
this.expireAt,
this.destroyKey,
});
@override
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
final map = <String, i0.Expression>{};
map['key'] = i0.Variable<String>(key);
map['json'] = i0.Variable<String>(json);
if (!nullToAbsent || expireAt != null) {
map['expireAt'] = i0.Variable<DateTime>(expireAt);
}
if (!nullToAbsent || destroyKey != null) {
map['destroyKey'] = i0.Variable<String>(destroyKey);
}
return map;
}
factory RiverpodData.fromJson(
Map<String, dynamic> json, {
i0.ValueSerializer? serializer,
}) {
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
return RiverpodData(
key: serializer.fromJson<String>(json['key']),
json: serializer.fromJson<String>(json['json']),
expireAt: serializer.fromJson<DateTime?>(json['expireAt']),
destroyKey: serializer.fromJson<String?>(json['destroyKey']),
);
}
@override
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'key': serializer.toJson<String>(key),
'json': serializer.toJson<String>(json),
'expireAt': serializer.toJson<DateTime?>(expireAt),
'destroyKey': serializer.toJson<String?>(destroyKey),
};
}
i1.RiverpodData copyWith({
String? key,
String? json,
i0.Value<DateTime?> expireAt = const i0.Value.absent(),
i0.Value<String?> destroyKey = const i0.Value.absent(),
}) => i1.RiverpodData(
key: key ?? this.key,
json: json ?? this.json,
expireAt: expireAt.present ? expireAt.value : this.expireAt,
destroyKey: destroyKey.present ? destroyKey.value : this.destroyKey,
);
RiverpodData copyWithCompanion(i1.RiverpodCompanion data) {
return RiverpodData(
key: data.key.present ? data.key.value : this.key,
json: data.json.present ? data.json.value : this.json,
expireAt: data.expireAt.present ? data.expireAt.value : this.expireAt,
destroyKey: data.destroyKey.present
? data.destroyKey.value
: this.destroyKey,
);
}
@override
String toString() {
return (StringBuffer('RiverpodData(')
..write('key: $key, ')
..write('json: $json, ')
..write('expireAt: $expireAt, ')
..write('destroyKey: $destroyKey')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(key, json, expireAt, destroyKey);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is i1.RiverpodData &&
other.key == this.key &&
other.json == this.json &&
other.expireAt == this.expireAt &&
other.destroyKey == this.destroyKey);
}
class RiverpodCompanion extends i0.UpdateCompanion<i1.RiverpodData> {
final i0.Value<String> key;
final i0.Value<String> json;
final i0.Value<DateTime?> expireAt;
final i0.Value<String?> destroyKey;
const RiverpodCompanion({
this.key = const i0.Value.absent(),
this.json = const i0.Value.absent(),
this.expireAt = const i0.Value.absent(),
this.destroyKey = const i0.Value.absent(),
});
RiverpodCompanion.insert({
required String key,
required String json,
this.expireAt = const i0.Value.absent(),
this.destroyKey = const i0.Value.absent(),
}) : key = i0.Value(key),
json = i0.Value(json);
static i0.Insertable<i1.RiverpodData> custom({
i0.Expression<String>? key,
i0.Expression<String>? json,
i0.Expression<DateTime>? expireAt,
i0.Expression<String>? destroyKey,
}) {
return i0.RawValuesInsertable({
if (key != null) 'key': key,
if (json != null) 'json': json,
if (expireAt != null) 'expireAt': expireAt,
if (destroyKey != null) 'destroyKey': destroyKey,
});
}
i1.RiverpodCompanion copyWith({
i0.Value<String>? key,
i0.Value<String>? json,
i0.Value<DateTime?>? expireAt,
i0.Value<String?>? destroyKey,
}) {
return i1.RiverpodCompanion(
key: key ?? this.key,
json: json ?? this.json,
expireAt: expireAt ?? this.expireAt,
destroyKey: destroyKey ?? this.destroyKey,
);
}
@override
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
final map = <String, i0.Expression>{};
if (key.present) {
map['key'] = i0.Variable<String>(key.value);
}
if (json.present) {
map['json'] = i0.Variable<String>(json.value);
}
if (expireAt.present) {
map['expireAt'] = i0.Variable<DateTime>(expireAt.value);
}
if (destroyKey.present) {
map['destroyKey'] = i0.Variable<String>(destroyKey.value);
}
return map;
}
@override
String toString() {
return (StringBuffer('RiverpodCompanion(')
..write('key: $key, ')
..write('json: $json, ')
..write('expireAt: $expireAt, ')
..write('destroyKey: $destroyKey')
..write(')'))
.toString();
}
}
class DefinitionsDrift extends i3.ModularAccessor {
DefinitionsDrift(i0.GeneratedDatabase db) : super(db);
Future<int> evictCacheEntries({required int limit}) {
@@ -0,0 +1,57 @@
import 'dart:async';
import 'package:drift/drift.dart';
import 'package:nullability/nullability.dart';
import 'package:riverpod/experimental/persist.dart';
import 'package:weblibre/features/user/data/database/database.dart';
import 'package:weblibre/features/user/data/database/definitions.drift.dart';
final class RiverpodStorage extends Storage<String, String> {
final UserDatabase _db;
RiverpodStorage(this._db);
@override
Future<void> delete(String key) {
return _db.riverpod.deleteWhere((x) => x.key.equals(key));
}
@override
Future<void> deleteOutOfDate() {
return _db.riverpod.deleteWhere(
(x) => x.expireAt.isSmallerThanValue(DateTime.now()),
);
}
@override
Future<PersistedData<String>?> read(String key) async {
final query = _db.riverpod.select()..where((x) => x.key.equals(key));
final data = await query.getSingleOrNull();
return data.mapNotNull(
(data) => PersistedData(
data.json,
destroyKey: data.destroyKey,
expireAt: data.expireAt,
),
);
}
@override
Future<void> write(String key, String value, StorageOptions options) {
return _db.riverpod.insertOne(
RiverpodCompanion.insert(
key: key,
json: value,
destroyKey: Value(options.destroyKey),
expireAt: Value(
options.cacheTime.duration.mapNotNull(
(duration) => DateTime.now().add(duration),
),
),
),
mode: InsertMode.insertOrReplace,
);
}
}