chore: Update minimum dart version and run formatter

This commit is contained in:
Marcelo Soares 2026-01-25 23:23:47 -03:00
commit 63a73117e0
No known key found for this signature in database
GPG key ID: CB2F09CE66E99EAD
22 changed files with 588 additions and 563 deletions

View file

@ -123,6 +123,7 @@ linter:
formatter:
page_width: 88
trailing_commas: preserve
analyzer:
exclude:

View file

@ -45,7 +45,8 @@ extension ConvertCrdtDataEntry on TableInfo {
.firstWhere((e) => e.rowId == rowId && e.columnName == c.$name)
.rawValue
?.rawSqlValue,
}) as T,
})
as T,
];
}
}

View file

@ -68,13 +68,13 @@ abstract class OfflineSyncTriggers extends CrdtDataSqlBuilder {
return ' SELECT\n${[
' \'$c\' AS "column_name"',
' ${c == isDeletedColumnName ? 'TRUE' : 'NEW."$c"'} AS "raw_value"',
if (operation == Operation.update)
' NEW."$c" IS NOT OLD."$c" AS "value_changed"',
if (operation == Operation.update) ' NEW."$c" IS NOT OLD."$c" AS "value_changed"',
].join(',\n')}';
});
final whereClause =
operation == Operation.update ? '\n WHERE (value_changed = TRUE)' : '';
final whereClause = operation == Operation.update
? '\n WHERE (value_changed = TRUE)'
: '';
return '''
INSERT OR REPLACE INTO $crdtDataTableName (

View file

@ -45,8 +45,9 @@ class CrdtDataSqlBuilder {
if (table.$primaryKey.length == 1) {
return '$tableAlias."${table.$primaryKey.first.$name}"';
}
final columnNames =
table.$primaryKey.map((c) => '$tableAlias."${c.$name}"').join(', ');
final columnNames = table.$primaryKey
.map((c) => '$tableAlias."${c.$name}"')
.join(', ');
return "CONCAT_WS('$rowIdSeparator', $columnNames)";
}
}

View file

@ -4,7 +4,7 @@ version: 0.0.1
repository: https://github.com/marcelomendoncasoares/drift_offline_first
environment:
sdk: ^3.3.0
sdk: ^3.8.0
# TODO: Relax the package constraints to expand compatibility.
dependencies:

View file

@ -15,8 +15,7 @@ void main() {
(crdt, _, nodeId) = database.crdtContext;
});
test(
'when getting the canonical time '
test('when getting the canonical time '
'then it returns Hlc.zero for the nodeId.', () async {
expect(crdt.canonicalTime, equals(Hlc.zero(nodeId)));
});
@ -38,8 +37,7 @@ void main() {
createdHlc = allCrdtDataEntries.first.hlcTimestamp;
});
test(
'when getting the canonical time '
test('when getting the canonical time '
'then it returns the HLC timestamp of the created data.', () async {
expect(crdt.canonicalTime, equals(createdHlc));
});
@ -60,8 +58,7 @@ void main() {
);
});
test(
'when getting the canonical time '
test('when getting the canonical time '
'then the canonical time matches the cached HLC for the nodeId.', () async {
expect(
crdt.canonicalTime,

View file

@ -50,8 +50,9 @@ void main() {
});
test('then all entries have expected column names and values.', () async {
final mappedData =
await database.tableWithEveryColumnType.fromCrdtDataEntries(entries);
final mappedData = await database.tableWithEveryColumnType.fromCrdtDataEntries(
entries,
);
expect(mappedData.single, equals(createdData));
});
});

View file

@ -33,8 +33,7 @@ void main() {
createdData = await database.managers.tableWithEveryColumnType.getSingle();
});
test(
'when getting the data from the CRDT data table of the CRDT database '
test('when getting the data from the CRDT data table of the CRDT database '
'then it rebuilds the data perfectly.', () async {
final data = await crdtDb.getSingleFromCrdtData<TableWithEveryColumnTypeData>(
rowId,
@ -45,16 +44,17 @@ void main() {
test(
'when trying to get the data from the CRDT data table with a non-existent row id '
'then it returns null.', () async {
'then it returns null.',
() async {
final data = await crdtDb.getSingleFromCrdtData<TableWithEveryColumnTypeData>(
'non-existent-row-id',
);
expect(data, isNull);
});
},
);
test(
'when trying to get the data from the CRDT data table with multiple row ids '
test('when trying to get the data from the CRDT data table with multiple row ids '
'then it returns the data for the existing row ids.', () async {
final data = await crdtDb.getFromCrdtData<TableWithEveryColumnTypeData>(
[rowId, 'non-existent-row-id'],
@ -65,7 +65,8 @@ void main() {
test(
'when trying to get the data from the CRDT data table for a non-synchronized table '
'then it throws an error.', () async {
'then it throws an error.',
() async {
expect(
() => crdtDb.getSingleFromCrdtData<WithCustomTypeData>(rowId),
throwsA(
@ -76,6 +77,7 @@ void main() {
),
),
);
});
},
);
});
}

View file

@ -47,11 +47,14 @@ void main() {
await crdtDb.syncCrdtDataToTables();
});
test('then the data is inserted into the database according to the changeset.',
test(
'then the data is inserted into the database according to the changeset.',
() async {
final savedData = await database.managers.tableWithEveryColumnType.getSingle();
final savedData = await database.managers.tableWithEveryColumnType
.getSingle();
expect(savedData, equals(testData));
});
},
);
test(
'then no changes are made to the CRDT data table due to the sync.',
@ -62,7 +65,8 @@ void main() {
skip: 'Skipping test until the trigger disable mechanism is implemented.',
);
});
});
},
);
group(
'Given CRDT for a database with data and a pending changeset with conflicting data and older HLC',
@ -105,7 +109,8 @@ void main() {
expect(savedMergedHlcs.map((e) => e.lastReceivedHlc).single, lastHlc);
});
});
});
},
);
group(
'Given CRDT for a database with data and a pending changeset with conflicting data and some entries with newer HLC',
@ -154,5 +159,6 @@ void main() {
expect(savedMergedHlcs.map((e) => e.lastReceivedHlc).single, newerHlc);
});
});
});
},
);
}

View file

@ -13,16 +13,14 @@ void main() {
(crdt, _, _) = database.crdtContext;
});
test(
'when getting a changeset '
test('when getting a changeset '
'then it returns an empty changeset.', () async {
final changeset = await crdt.getChangeset();
expect(changeset, isEmpty);
});
test(
'when adding data to the database '
test('when adding data to the database '
'then the changeset contains the data.', () async {
await database.managers.todosTable.create(
(t) => t(content: 'test'),
@ -65,7 +63,8 @@ void main() {
expect(changeset.length, expectedColumns.length);
expect(changesetColumns, equals(expectedColumns));
});
},
);
test('then the raw values are set correctly.', () async {
final changeset = await crdt.getChangeset();
@ -81,8 +80,7 @@ void main() {
});
});
test(
'with onlyNodeId set to the nodeId '
test('with onlyNodeId set to the nodeId '
'then it returns a changeset with the data for the nodeId.', () async {
final changeset = await crdt.getChangeset(onlyNodeId: nodeId);
final nodeIds = changeset.map((e) => e.hlcTimestamp.nodeId).toSet();
@ -91,24 +89,21 @@ void main() {
expect(nodeIds, equals({nodeId}));
});
test(
'with onlyNodeId set to a different nodeId '
test('with onlyNodeId set to a different nodeId '
'then it returns an empty changeset.', () async {
final changeset = await crdt.getChangeset(onlyNodeId: 'wrong-node');
expect(changeset, isEmpty);
});
test(
'with exceptNodeId set to the nodeId '
test('with exceptNodeId set to the nodeId '
'then it returns an empty changeset.', () async {
final changeset = await crdt.getChangeset(exceptNodeId: nodeId);
expect(changeset, isEmpty);
});
test(
'with exceptNodeId set to a different nodeId '
test('with exceptNodeId set to a different nodeId '
'then it returns a changeset with data from other nodes.', () async {
const otherNodeId = 'other-node';
final changeset = await crdt.getChangeset(exceptNodeId: otherNodeId);
@ -127,10 +122,10 @@ void main() {
expect(changeset, isNotEmpty);
expect(changeset.map((e) => e.rowId).toSet(), equals({rowId}));
});
},
);
test(
'with modifiedAfter set to a timestamp after the created data '
test('with modifiedAfter set to a timestamp after the created data '
'then it returns an empty changeset.', () async {
final futureHlc = createdHlc.increment();
final changeset = await crdt.getChangeset(modifiedAfter: futureHlc);

View file

@ -13,8 +13,7 @@ void main() {
(crdt, _, _) = database.crdtContext;
});
test(
'when getting the last modified timestamp '
test('when getting the last modified timestamp '
'then it returns null since the database is empty.', () async {
final lastModified = await crdt.getLastModified();
expect(lastModified, isNull);
@ -39,36 +38,31 @@ void main() {
});
group('when getting the last modified timestamp', () {
test(
'without any filters '
test('without any filters '
'then it returns the HLC timestamp of the created data.', () async {
final lastModified = await crdt.getLastModified();
expect(lastModified, equals(createdHlc));
});
test(
'with onlyNodeId set to the nodeId '
test('with onlyNodeId set to the nodeId '
'then it returns the HLC timestamp of the created data.', () async {
final lastModified = await crdt.getLastModified(onlyNodeId: nodeId);
expect(lastModified, equals(createdHlc));
});
test(
'with onlyNodeId set to a different nodeId '
test('with onlyNodeId set to a different nodeId '
'then it returns null.', () async {
final lastModified = await crdt.getLastModified(onlyNodeId: 'other-node');
expect(lastModified, isNull);
});
test(
'with exceptNodeId set to the nodeId '
test('with exceptNodeId set to the nodeId '
'then it returns null.', () async {
final lastModified = await crdt.getLastModified(exceptNodeId: nodeId);
expect(lastModified, isNull);
});
test(
'with exceptNodeId set to a different nodeId '
test('with exceptNodeId set to a different nodeId '
'then it returns the HLC timestamp of the created data.', () async {
final lastModified = await crdt.getLastModified(exceptNodeId: 'other-node');
expect(lastModified, equals(createdHlc));

View file

@ -25,8 +25,7 @@ void main() {
changeset = crdtDb.convertToCrdtDataEntry(todo, Hlc.now(nodeId));
});
test(
'when merging a changeset '
test('when merging a changeset '
'then the changeset is saved to the database.', () async {
await crdt.merge(changeset);
@ -55,14 +54,14 @@ void main() {
lateOldChangeset = crdtDb.convertToCrdtDataEntry(todo, Hlc.zero(nodeId));
});
test(
'when merging a changeset with older HLC '
test('when merging a changeset with older HLC '
'then no changes are saved to the database.', () async {
await crdt.merge(lateOldChangeset);
expect(await crdtDb.managers.crdtDataTable.get(), firstChangeset);
});
});
},
);
group(
'Given CRDT for a database with data and a pending changeset with conflicting data and some entries with newer HLC',
@ -87,8 +86,7 @@ void main() {
.where((e) => e.columnName == 'content');
});
test(
'when merging a changeset with some entries with newer HLC '
test('when merging a changeset with some entries with newer HLC '
'then the entries with newer HLC are saved to the database.', () async {
await crdt.merge(newerChangeset);
@ -99,5 +97,6 @@ void main() {
expect(await crdtDb.managers.crdtDataTable.get(), expectedResultingChangeset);
});
});
},
);
}

View file

@ -87,7 +87,8 @@ void main() {
expect(savedMergedHlcs.map((e) => e.lastReceivedHlc).single, lastHlc);
});
});
});
},
);
group(
'Given CRDT for a database with data and a pending changeset with conflicting data and some entries with newer HLC',
@ -136,5 +137,6 @@ void main() {
expect(savedMergedHlcs.map((e) => e.lastReceivedHlc).single, newerHlc);
});
});
});
},
);
}

View file

@ -17,8 +17,7 @@ void main() {
await executor.close();
});
test(
'when calling the HLC function with valid arguments '
test('when calling the HLC function with valid arguments '
'then it returns a valid HLC timestamp.', () async {
final result = await executor.runSelect(
'SELECT $nextHlcFunction(?, ?) as hlc_timestamp;',
@ -30,8 +29,7 @@ void main() {
expect(hlcConverter.fromSql(hlcTimestamp!), isA<Hlc>());
});
test(
'when calling the HLC function with no arguments '
test('when calling the HLC function with no arguments '
'then it throws a SqliteException due to the MissingArgumentsError.', () async {
expect(
() => executor.runSelect(
@ -48,8 +46,7 @@ void main() {
);
});
test(
'when calling the HLC function with only the first argument '
test('when calling the HLC function with only the first argument '
'then it throws a SqliteException due to the MissingNodeIdError.', () async {
expect(
() => executor.runSelect(
@ -66,8 +63,7 @@ void main() {
);
});
test(
'when calling the HLC function with only the second argument '
test('when calling the HLC function with only the second argument '
'then it throws a SqliteException due to the MissingUserIdError.', () async {
expect(
() => executor.runSelect(
@ -84,8 +80,7 @@ void main() {
);
});
test(
'when calling the HLC function with more than two arguments '
test('when calling the HLC function with more than two arguments '
'then it throws a SqliteException due to the TooManyArgumentsError.', () async {
expect(
() => executor.runSelect(

View file

@ -17,12 +17,12 @@ void main() {
await testDatabaseFixture(initialHlc: initialHlc);
});
test(
'when retrieving the cached HLC '
test('when retrieving the cached HLC '
'then the HLC for the node is the initial HLC.', () async {
expect(StatefulHlc.cached(testUserId, testNodeId).lastHlc, equals(initialHlc));
});
});
},
);
group(
'Given a database opened with a CRDT control table having initial HLC from same user and different node',
@ -39,9 +39,14 @@ void main() {
'then the HLC for the node is the initial HLC for the requested node.',
() async {
final expectedHlc = initialHlc.apply(nodeId: testNodeId);
expect(StatefulHlc.cached(testUserId, testNodeId).lastHlc, equals(expectedHlc));
});
});
expect(
StatefulHlc.cached(testUserId, testNodeId).lastHlc,
equals(expectedHlc),
);
},
);
},
);
group(
'Given a database opened with a CRDT control table having initial HLC from different user and node',
@ -53,14 +58,16 @@ void main() {
await testDatabaseFixture(initialHlc: initialHlc);
});
test(
'when retrieving the cached HLC '
test('when retrieving the cached HLC '
'then the HLC for the node is zero for the requested node.', () async {
final expectedHlc = Hlc.zero(testNodeId);
expect(
StatefulHlc.cached('other_user_id', testNodeId).lastHlc, equals(expectedHlc));
});
StatefulHlc.cached('other_user_id', testNodeId).lastHlc,
equals(expectedHlc),
);
});
},
);
}
Future<TodoDb> testDatabaseFixture({

View file

@ -118,7 +118,8 @@ void main() {
expect(row.targetDate, targetDate);
});
group('then the trigger is executed and the updated row in the CRDT data table',
group(
'then the trigger is executed and the updated row in the CRDT data table',
() {
late List<CrdtDataEntry> allCrdtDataEntries;
@ -131,7 +132,10 @@ void main() {
final crdtData = allCrdtDataEntries.getField(rowId, 'content');
expect(crdtData.unwrappedValue, targetContent);
final targetDateCrdtData = allCrdtDataEntries.getField(rowId, 'target_date');
final targetDateCrdtData = allCrdtDataEntries.getField(
rowId,
'target_date',
);
expect(
targetDateCrdtData.unwrappedValue,
targetDate.toIso8600StringWithOffset(),
@ -160,7 +164,8 @@ void main() {
expect(otherEntriesHlc, everyElement(equals(createdHlc)));
});
});
},
);
});
group('when updating the existing row with the same value', () {
@ -173,7 +178,8 @@ void main() {
);
});
group('then the trigger is executed and the updated row in the CRDT data table',
group(
'then the trigger is executed and the updated row in the CRDT data table',
() {
late List<CrdtDataEntry> allCrdtDataEntries;
@ -186,7 +192,8 @@ void main() {
final updatedEntry = allCrdtDataEntries.getField(rowId, 'content');
expect(updatedEntry.hlcTimestamp, equals(createdHlc));
});
});
},
);
});
group('when deleting the existing row', () {
@ -202,7 +209,8 @@ void main() {
expect(row, isNull);
});
group('then the trigger is executed and the deleted row in the CRDT data table',
group(
'then the trigger is executed and the deleted row in the CRDT data table',
() {
late List<CrdtDataEntry> allCrdtDataEntries;
late String rowId;
@ -220,10 +228,14 @@ void main() {
});
test('has the deleted HLC timestamp greater than the created.', () async {
final deletedEntry = allCrdtDataEntries.getField(rowId, '__crdt_is_deleted');
final deletedEntry = allCrdtDataEntries.getField(
rowId,
'__crdt_is_deleted',
);
expect(deletedEntry.hlcTimestamp, greaterThan(createdHlc));
});
});
},
);
});
});
}

View file

@ -28,8 +28,9 @@ void main() {
});
test('then the insert trigger statement is correct.', () {
final insertStatement = triggers
.firstWhere((trigger) => trigger.contains('AFTER INSERT ON "todos"'));
final insertStatement = triggers.firstWhere(
(trigger) => trigger.contains('AFTER INSERT ON "todos"'),
);
expect(
insertStatement,
@ -79,8 +80,9 @@ END;''',
});
test('then the update trigger statement is correct.', () {
final updateStatement = triggers
.firstWhere((trigger) => trigger.contains('AFTER UPDATE ON "todos"'));
final updateStatement = triggers.firstWhere(
(trigger) => trigger.contains('AFTER UPDATE ON "todos"'),
);
expect(
updateStatement,
@ -137,8 +139,9 @@ END;''',
});
test('then the delete trigger statement is correct.', () {
final deleteStatement = triggers
.firstWhere((trigger) => trigger.contains('AFTER DELETE ON "todos"'));
final deleteStatement = triggers.firstWhere(
(trigger) => trigger.contains('AFTER DELETE ON "todos"'),
);
expect(
deleteStatement,

View file

@ -41,7 +41,7 @@ void main() {
final _ = second.users.all();
expect(second.didUpgrade, isFalse);
final __ = first.todosTable.all();
final _ = first.todosTable.all();
expect(first.didUpgrade, isTrue);
});
}

View file

@ -52,7 +52,8 @@ TodoDb? _database;
],
daos: [SomeDao],
queries: {
'allTodosWithCategory': 'SELECT t.*, c.id as catId, c."desc" as catDesc '
'allTodosWithCategory':
'SELECT t.*, c.id as catId, c."desc" as catDesc '
'FROM todos t INNER JOIN categories c ON c.id = t.category',
'deleteTodoById': 'DELETE FROM todos WHERE id = ?',
'withIn': 'SELECT * FROM todos WHERE title = ?2 OR id IN ? OR title = ?1',
@ -100,7 +101,8 @@ class TodoDb extends _$TodoDb {
tables: [Users, SharedTodos, TodosTable],
views: [TodoWithCategoryView],
queries: {
'todosForUser': 'SELECT t.* FROM todos t '
'todosForUser':
'SELECT t.* FROM todos t '
'INNER JOIN shared_todos st ON st.todo = t.id '
'INNER JOIN users u ON u.id = st.user '
'WHERE u.id = :user',

Some files were not shown because too many files have changed in this diff Show more