feat(offline): tier 2 phase 3 — write queue with optimistic UI

Offline writes now queue + apply optimistically instead of throwing.
On reconnect, the queue drains automatically; conflicts and rejections
surface as one-time SnackBars so the user knows their edit didn't land.

- Drift schema v3: `pending_writes` table (verb + payload + baseline +
  tries + last_error). Negative ids serve as cache placeholders for
  offline-created rows until replay assigns the server id.
- Each write repository (notes, tasks, projects, milestones, events)
  now catches NetworkException, applies the change to cache (with a
  fresh `nextTempId()` for creates), and enqueues the API call.
- Edits to a still-queued offline-created row coalesce into the
  original create payload — only one server call per row, in order.
- Deletes of still-queued offline-created rows drop the queue entry
  and the cache row; no server call ever happens.
- New WriteQueue service drains the queue oldest-first.
  Server-wins on `updated_at` baseline check (notes/tasks/projects);
  last-writer-wins for milestones/events (no getOne available).
  4xx → drop + surface as `rejected`; conflict → drop + `overwritten`;
  404 on update → drop + `missing`; 5xx/network → keep + retry next
  online cycle.
- Replay fires on AuthStatus → authenticated transitions
  (cold-start, came-back-online, login).
- OfflineBanner shows "Retry (N)" with the pending-writes count.
- Distinct ServerException added so 4xx no longer masquerade as
  NetworkException — the queue can drop them instead of looping.

flutter analyze clean; 21 tests pass (3 new for QueueFailure messaging).
Phase 4 (read-only UI indicators on cached/temp rows) still ahead.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-28 22:08:40 -04:00
parent fe10067761
commit 7df7b5ff85
14 changed files with 2090 additions and 93 deletions
+126 -1
View File
@@ -136,6 +136,29 @@ class SyncMetadata extends Table {
Set<Column> get primaryKey => {domain};
}
/// Phase 3 — generic offline-write queue. One row per pending API call;
/// drained in oldest-first order when the device comes back online.
///
/// `targetId` is the server id for updates/deletes; `tempId` is the
/// client-allocated negative placeholder for offline creates. `payloadJson`
/// is the typed args the API method needs (verb-specific shape).
/// `baselineUpdatedAt` snapshots the cached row's `updated_at` at edit
/// time so the replayer can drop the op if the server has moved on
/// (server-wins conflict resolution).
class PendingWrites extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get domain => text()();
TextColumn get verb => text()();
IntColumn get targetId => integer().nullable()();
IntColumn get tempId => integer().nullable()();
TextColumn get payloadJson => text().withDefault(const Constant('{}'))();
DateTimeColumn get baselineUpdatedAt => dateTime().nullable()();
DateTimeColumn get createdAt =>
dateTime().clientDefault(() => DateTime.now())();
IntColumn get tries => integer().withDefault(const Constant(0))();
TextColumn get lastError => text().nullable()();
}
const String kSyncDomainNotes = 'notes';
const String kSyncDomainTasks = 'tasks';
const String kSyncDomainProjects = 'projects';
@@ -143,6 +166,10 @@ const String kSyncDomainMilestones = 'milestones';
const String kSyncDomainEvents = 'events';
const String kSyncDomainConversations = 'conversations';
const String kWriteVerbCreate = 'create';
const String kWriteVerbUpdate = 'update';
const String kWriteVerbDelete = 'delete';
@DriftDatabase(tables: [
CachedNotes,
CachedTasks,
@@ -151,13 +178,14 @@ const String kSyncDomainConversations = 'conversations';
CachedCalendarEvents,
CachedConversations,
SyncMetadata,
PendingWrites,
])
class FabledDatabase extends _$FabledDatabase {
FabledDatabase() : super(_openConnection());
FabledDatabase.forTesting(super.executor);
@override
int get schemaVersion => 2;
int get schemaVersion => 3;
@override
MigrationStrategy get migration => MigrationStrategy(
@@ -171,6 +199,10 @@ class FabledDatabase extends _$FabledDatabase {
await m.createTable(cachedCalendarEvents);
await m.createTable(cachedConversations);
}
// v2 → v3: added the offline write queue.
if (from < 3) {
await m.createTable(pendingWrites);
}
},
);
@@ -448,6 +480,99 @@ class FabledDatabase extends _$FabledDatabase {
),
);
}
// ── Pending writes (Phase 3 offline queue) ───────────────────────────────
/// Allocate a fresh negative id used as a placeholder for an
/// offline-created row until its server id arrives via replay.
Future<int> nextTempId() async {
final query = customSelect(
'SELECT MIN(temp_id) AS m FROM pending_writes WHERE temp_id IS NOT NULL',
);
final row = await query.getSingleOrNull();
final current = row?.read<int?>('m');
return (current ?? 0) - 1;
}
Future<int> enqueuePending({
required String domain,
required String verb,
int? targetId,
int? tempId,
Map<String, dynamic> payload = const {},
DateTime? baselineUpdatedAt,
}) {
return into(pendingWrites).insert(
PendingWritesCompanion.insert(
domain: domain,
verb: verb,
targetId: Value(targetId),
tempId: Value(tempId),
payloadJson: Value(jsonEncode(payload)),
baselineUpdatedAt: Value(baselineUpdatedAt),
),
);
}
/// Find the queued create-op for an offline-created row so a follow-up
/// edit can be coalesced into the original payload (no separate update
/// op gets queued). Returns null if no matching create is queued.
Future<PendingWrite?> findQueuedCreate(String domain, int tempId) {
return (select(pendingWrites)
..where((t) =>
t.domain.equals(domain) &
t.verb.equals(kWriteVerbCreate) &
t.tempId.equals(tempId)))
.getSingleOrNull();
}
Future<void> updatePendingPayload(int opId, Map<String, dynamic> payload) {
return (update(pendingWrites)..where((t) => t.id.equals(opId))).write(
PendingWritesCompanion(payloadJson: Value(jsonEncode(payload))),
);
}
Future<void> deletePending(int opId) {
return (delete(pendingWrites)..where((t) => t.id.equals(opId))).go();
}
/// Pending ops oldest first — replay order.
Future<List<PendingWrite>> listPending() {
return (select(pendingWrites)
..orderBy([(t) => OrderingTerm.asc(t.id)]))
.get();
}
Future<int> queueDepth() async {
final row = await customSelect('SELECT COUNT(*) AS c FROM pending_writes')
.getSingle();
return row.read<int>('c');
}
Future<void> markPendingFailed(int opId, String error) {
return (update(pendingWrites)..where((t) => t.id.equals(opId))).write(
PendingWritesCompanion(
tries: const Value.absent(),
lastError: Value(error),
),
);
}
Future<void> incrementPendingTries(int opId) {
return customStatement(
'UPDATE pending_writes SET tries = tries + 1 WHERE id = ?',
[opId],
);
}
/// Watch queue depth — used by OfflineBanner for the "Retry (N)" affordance.
Stream<int> watchQueueDepth() {
final query = customSelect(
'SELECT COUNT(*) AS c FROM pending_writes',
readsFrom: {pendingWrites},
);
return query.watchSingle().map((row) => row.read<int>('c'));
}
}
// ── Row ↔ model converters ──────────────────────────────────────────────────
+902
View File
@@ -3831,6 +3831,609 @@ class SyncMetadataCompanion extends UpdateCompanion<SyncMetadataData> {
}
}
class $PendingWritesTable extends PendingWrites
with TableInfo<$PendingWritesTable, PendingWrite> {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
$PendingWritesTable(this.attachedDatabase, [this._alias]);
static const VerificationMeta _idMeta = const VerificationMeta('id');
@override
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id',
aliasedName,
false,
hasAutoIncrement: true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintIsAlways(
'PRIMARY KEY AUTOINCREMENT',
),
);
static const VerificationMeta _domainMeta = const VerificationMeta('domain');
@override
late final GeneratedColumn<String> domain = GeneratedColumn<String>(
'domain',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: true,
);
static const VerificationMeta _verbMeta = const VerificationMeta('verb');
@override
late final GeneratedColumn<String> verb = GeneratedColumn<String>(
'verb',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: true,
);
static const VerificationMeta _targetIdMeta = const VerificationMeta(
'targetId',
);
@override
late final GeneratedColumn<int> targetId = GeneratedColumn<int>(
'target_id',
aliasedName,
true,
type: DriftSqlType.int,
requiredDuringInsert: false,
);
static const VerificationMeta _tempIdMeta = const VerificationMeta('tempId');
@override
late final GeneratedColumn<int> tempId = GeneratedColumn<int>(
'temp_id',
aliasedName,
true,
type: DriftSqlType.int,
requiredDuringInsert: false,
);
static const VerificationMeta _payloadJsonMeta = const VerificationMeta(
'payloadJson',
);
@override
late final GeneratedColumn<String> payloadJson = GeneratedColumn<String>(
'payload_json',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: false,
defaultValue: const Constant('{}'),
);
static const VerificationMeta _baselineUpdatedAtMeta = const VerificationMeta(
'baselineUpdatedAt',
);
@override
late final GeneratedColumn<DateTime> baselineUpdatedAt =
GeneratedColumn<DateTime>(
'baseline_updated_at',
aliasedName,
true,
type: DriftSqlType.dateTime,
requiredDuringInsert: false,
);
static const VerificationMeta _createdAtMeta = const VerificationMeta(
'createdAt',
);
@override
late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>(
'created_at',
aliasedName,
false,
type: DriftSqlType.dateTime,
requiredDuringInsert: false,
clientDefault: () => DateTime.now(),
);
static const VerificationMeta _triesMeta = const VerificationMeta('tries');
@override
late final GeneratedColumn<int> tries = GeneratedColumn<int>(
'tries',
aliasedName,
false,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultValue: const Constant(0),
);
static const VerificationMeta _lastErrorMeta = const VerificationMeta(
'lastError',
);
@override
late final GeneratedColumn<String> lastError = GeneratedColumn<String>(
'last_error',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
);
@override
List<GeneratedColumn> get $columns => [
id,
domain,
verb,
targetId,
tempId,
payloadJson,
baselineUpdatedAt,
createdAt,
tries,
lastError,
];
@override
String get aliasedName => _alias ?? actualTableName;
@override
String get actualTableName => $name;
static const String $name = 'pending_writes';
@override
VerificationContext validateIntegrity(
Insertable<PendingWrite> instance, {
bool isInserting = false,
}) {
final context = VerificationContext();
final data = instance.toColumns(true);
if (data.containsKey('id')) {
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
}
if (data.containsKey('domain')) {
context.handle(
_domainMeta,
domain.isAcceptableOrUnknown(data['domain']!, _domainMeta),
);
} else if (isInserting) {
context.missing(_domainMeta);
}
if (data.containsKey('verb')) {
context.handle(
_verbMeta,
verb.isAcceptableOrUnknown(data['verb']!, _verbMeta),
);
} else if (isInserting) {
context.missing(_verbMeta);
}
if (data.containsKey('target_id')) {
context.handle(
_targetIdMeta,
targetId.isAcceptableOrUnknown(data['target_id']!, _targetIdMeta),
);
}
if (data.containsKey('temp_id')) {
context.handle(
_tempIdMeta,
tempId.isAcceptableOrUnknown(data['temp_id']!, _tempIdMeta),
);
}
if (data.containsKey('payload_json')) {
context.handle(
_payloadJsonMeta,
payloadJson.isAcceptableOrUnknown(
data['payload_json']!,
_payloadJsonMeta,
),
);
}
if (data.containsKey('baseline_updated_at')) {
context.handle(
_baselineUpdatedAtMeta,
baselineUpdatedAt.isAcceptableOrUnknown(
data['baseline_updated_at']!,
_baselineUpdatedAtMeta,
),
);
}
if (data.containsKey('created_at')) {
context.handle(
_createdAtMeta,
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta),
);
}
if (data.containsKey('tries')) {
context.handle(
_triesMeta,
tries.isAcceptableOrUnknown(data['tries']!, _triesMeta),
);
}
if (data.containsKey('last_error')) {
context.handle(
_lastErrorMeta,
lastError.isAcceptableOrUnknown(data['last_error']!, _lastErrorMeta),
);
}
return context;
}
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
PendingWrite map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return PendingWrite(
id: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}id'],
)!,
domain: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}domain'],
)!,
verb: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}verb'],
)!,
targetId: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}target_id'],
),
tempId: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}temp_id'],
),
payloadJson: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}payload_json'],
)!,
baselineUpdatedAt: attachedDatabase.typeMapping.read(
DriftSqlType.dateTime,
data['${effectivePrefix}baseline_updated_at'],
),
createdAt: attachedDatabase.typeMapping.read(
DriftSqlType.dateTime,
data['${effectivePrefix}created_at'],
)!,
tries: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}tries'],
)!,
lastError: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}last_error'],
),
);
}
@override
$PendingWritesTable createAlias(String alias) {
return $PendingWritesTable(attachedDatabase, alias);
}
}
class PendingWrite extends DataClass implements Insertable<PendingWrite> {
final int id;
final String domain;
final String verb;
final int? targetId;
final int? tempId;
final String payloadJson;
final DateTime? baselineUpdatedAt;
final DateTime createdAt;
final int tries;
final String? lastError;
const PendingWrite({
required this.id,
required this.domain,
required this.verb,
this.targetId,
this.tempId,
required this.payloadJson,
this.baselineUpdatedAt,
required this.createdAt,
required this.tries,
this.lastError,
});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['id'] = Variable<int>(id);
map['domain'] = Variable<String>(domain);
map['verb'] = Variable<String>(verb);
if (!nullToAbsent || targetId != null) {
map['target_id'] = Variable<int>(targetId);
}
if (!nullToAbsent || tempId != null) {
map['temp_id'] = Variable<int>(tempId);
}
map['payload_json'] = Variable<String>(payloadJson);
if (!nullToAbsent || baselineUpdatedAt != null) {
map['baseline_updated_at'] = Variable<DateTime>(baselineUpdatedAt);
}
map['created_at'] = Variable<DateTime>(createdAt);
map['tries'] = Variable<int>(tries);
if (!nullToAbsent || lastError != null) {
map['last_error'] = Variable<String>(lastError);
}
return map;
}
PendingWritesCompanion toCompanion(bool nullToAbsent) {
return PendingWritesCompanion(
id: Value(id),
domain: Value(domain),
verb: Value(verb),
targetId: targetId == null && nullToAbsent
? const Value.absent()
: Value(targetId),
tempId: tempId == null && nullToAbsent
? const Value.absent()
: Value(tempId),
payloadJson: Value(payloadJson),
baselineUpdatedAt: baselineUpdatedAt == null && nullToAbsent
? const Value.absent()
: Value(baselineUpdatedAt),
createdAt: Value(createdAt),
tries: Value(tries),
lastError: lastError == null && nullToAbsent
? const Value.absent()
: Value(lastError),
);
}
factory PendingWrite.fromJson(
Map<String, dynamic> json, {
ValueSerializer? serializer,
}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return PendingWrite(
id: serializer.fromJson<int>(json['id']),
domain: serializer.fromJson<String>(json['domain']),
verb: serializer.fromJson<String>(json['verb']),
targetId: serializer.fromJson<int?>(json['targetId']),
tempId: serializer.fromJson<int?>(json['tempId']),
payloadJson: serializer.fromJson<String>(json['payloadJson']),
baselineUpdatedAt: serializer.fromJson<DateTime?>(
json['baselineUpdatedAt'],
),
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
tries: serializer.fromJson<int>(json['tries']),
lastError: serializer.fromJson<String?>(json['lastError']),
);
}
@override
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'id': serializer.toJson<int>(id),
'domain': serializer.toJson<String>(domain),
'verb': serializer.toJson<String>(verb),
'targetId': serializer.toJson<int?>(targetId),
'tempId': serializer.toJson<int?>(tempId),
'payloadJson': serializer.toJson<String>(payloadJson),
'baselineUpdatedAt': serializer.toJson<DateTime?>(baselineUpdatedAt),
'createdAt': serializer.toJson<DateTime>(createdAt),
'tries': serializer.toJson<int>(tries),
'lastError': serializer.toJson<String?>(lastError),
};
}
PendingWrite copyWith({
int? id,
String? domain,
String? verb,
Value<int?> targetId = const Value.absent(),
Value<int?> tempId = const Value.absent(),
String? payloadJson,
Value<DateTime?> baselineUpdatedAt = const Value.absent(),
DateTime? createdAt,
int? tries,
Value<String?> lastError = const Value.absent(),
}) => PendingWrite(
id: id ?? this.id,
domain: domain ?? this.domain,
verb: verb ?? this.verb,
targetId: targetId.present ? targetId.value : this.targetId,
tempId: tempId.present ? tempId.value : this.tempId,
payloadJson: payloadJson ?? this.payloadJson,
baselineUpdatedAt: baselineUpdatedAt.present
? baselineUpdatedAt.value
: this.baselineUpdatedAt,
createdAt: createdAt ?? this.createdAt,
tries: tries ?? this.tries,
lastError: lastError.present ? lastError.value : this.lastError,
);
PendingWrite copyWithCompanion(PendingWritesCompanion data) {
return PendingWrite(
id: data.id.present ? data.id.value : this.id,
domain: data.domain.present ? data.domain.value : this.domain,
verb: data.verb.present ? data.verb.value : this.verb,
targetId: data.targetId.present ? data.targetId.value : this.targetId,
tempId: data.tempId.present ? data.tempId.value : this.tempId,
payloadJson: data.payloadJson.present
? data.payloadJson.value
: this.payloadJson,
baselineUpdatedAt: data.baselineUpdatedAt.present
? data.baselineUpdatedAt.value
: this.baselineUpdatedAt,
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
tries: data.tries.present ? data.tries.value : this.tries,
lastError: data.lastError.present ? data.lastError.value : this.lastError,
);
}
@override
String toString() {
return (StringBuffer('PendingWrite(')
..write('id: $id, ')
..write('domain: $domain, ')
..write('verb: $verb, ')
..write('targetId: $targetId, ')
..write('tempId: $tempId, ')
..write('payloadJson: $payloadJson, ')
..write('baselineUpdatedAt: $baselineUpdatedAt, ')
..write('createdAt: $createdAt, ')
..write('tries: $tries, ')
..write('lastError: $lastError')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(
id,
domain,
verb,
targetId,
tempId,
payloadJson,
baselineUpdatedAt,
createdAt,
tries,
lastError,
);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is PendingWrite &&
other.id == this.id &&
other.domain == this.domain &&
other.verb == this.verb &&
other.targetId == this.targetId &&
other.tempId == this.tempId &&
other.payloadJson == this.payloadJson &&
other.baselineUpdatedAt == this.baselineUpdatedAt &&
other.createdAt == this.createdAt &&
other.tries == this.tries &&
other.lastError == this.lastError);
}
class PendingWritesCompanion extends UpdateCompanion<PendingWrite> {
final Value<int> id;
final Value<String> domain;
final Value<String> verb;
final Value<int?> targetId;
final Value<int?> tempId;
final Value<String> payloadJson;
final Value<DateTime?> baselineUpdatedAt;
final Value<DateTime> createdAt;
final Value<int> tries;
final Value<String?> lastError;
const PendingWritesCompanion({
this.id = const Value.absent(),
this.domain = const Value.absent(),
this.verb = const Value.absent(),
this.targetId = const Value.absent(),
this.tempId = const Value.absent(),
this.payloadJson = const Value.absent(),
this.baselineUpdatedAt = const Value.absent(),
this.createdAt = const Value.absent(),
this.tries = const Value.absent(),
this.lastError = const Value.absent(),
});
PendingWritesCompanion.insert({
this.id = const Value.absent(),
required String domain,
required String verb,
this.targetId = const Value.absent(),
this.tempId = const Value.absent(),
this.payloadJson = const Value.absent(),
this.baselineUpdatedAt = const Value.absent(),
this.createdAt = const Value.absent(),
this.tries = const Value.absent(),
this.lastError = const Value.absent(),
}) : domain = Value(domain),
verb = Value(verb);
static Insertable<PendingWrite> custom({
Expression<int>? id,
Expression<String>? domain,
Expression<String>? verb,
Expression<int>? targetId,
Expression<int>? tempId,
Expression<String>? payloadJson,
Expression<DateTime>? baselineUpdatedAt,
Expression<DateTime>? createdAt,
Expression<int>? tries,
Expression<String>? lastError,
}) {
return RawValuesInsertable({
if (id != null) 'id': id,
if (domain != null) 'domain': domain,
if (verb != null) 'verb': verb,
if (targetId != null) 'target_id': targetId,
if (tempId != null) 'temp_id': tempId,
if (payloadJson != null) 'payload_json': payloadJson,
if (baselineUpdatedAt != null) 'baseline_updated_at': baselineUpdatedAt,
if (createdAt != null) 'created_at': createdAt,
if (tries != null) 'tries': tries,
if (lastError != null) 'last_error': lastError,
});
}
PendingWritesCompanion copyWith({
Value<int>? id,
Value<String>? domain,
Value<String>? verb,
Value<int?>? targetId,
Value<int?>? tempId,
Value<String>? payloadJson,
Value<DateTime?>? baselineUpdatedAt,
Value<DateTime>? createdAt,
Value<int>? tries,
Value<String?>? lastError,
}) {
return PendingWritesCompanion(
id: id ?? this.id,
domain: domain ?? this.domain,
verb: verb ?? this.verb,
targetId: targetId ?? this.targetId,
tempId: tempId ?? this.tempId,
payloadJson: payloadJson ?? this.payloadJson,
baselineUpdatedAt: baselineUpdatedAt ?? this.baselineUpdatedAt,
createdAt: createdAt ?? this.createdAt,
tries: tries ?? this.tries,
lastError: lastError ?? this.lastError,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
if (id.present) {
map['id'] = Variable<int>(id.value);
}
if (domain.present) {
map['domain'] = Variable<String>(domain.value);
}
if (verb.present) {
map['verb'] = Variable<String>(verb.value);
}
if (targetId.present) {
map['target_id'] = Variable<int>(targetId.value);
}
if (tempId.present) {
map['temp_id'] = Variable<int>(tempId.value);
}
if (payloadJson.present) {
map['payload_json'] = Variable<String>(payloadJson.value);
}
if (baselineUpdatedAt.present) {
map['baseline_updated_at'] = Variable<DateTime>(baselineUpdatedAt.value);
}
if (createdAt.present) {
map['created_at'] = Variable<DateTime>(createdAt.value);
}
if (tries.present) {
map['tries'] = Variable<int>(tries.value);
}
if (lastError.present) {
map['last_error'] = Variable<String>(lastError.value);
}
return map;
}
@override
String toString() {
return (StringBuffer('PendingWritesCompanion(')
..write('id: $id, ')
..write('domain: $domain, ')
..write('verb: $verb, ')
..write('targetId: $targetId, ')
..write('tempId: $tempId, ')
..write('payloadJson: $payloadJson, ')
..write('baselineUpdatedAt: $baselineUpdatedAt, ')
..write('createdAt: $createdAt, ')
..write('tries: $tries, ')
..write('lastError: $lastError')
..write(')'))
.toString();
}
}
abstract class _$FabledDatabase extends GeneratedDatabase {
_$FabledDatabase(QueryExecutor e) : super(e);
$FabledDatabaseManager get managers => $FabledDatabaseManager(this);
@@ -3845,6 +4448,7 @@ abstract class _$FabledDatabase extends GeneratedDatabase {
late final $CachedConversationsTable cachedConversations =
$CachedConversationsTable(this);
late final $SyncMetadataTable syncMetadata = $SyncMetadataTable(this);
late final $PendingWritesTable pendingWrites = $PendingWritesTable(this);
@override
Iterable<TableInfo<Table, Object?>> get allTables =>
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
@@ -3857,6 +4461,7 @@ abstract class _$FabledDatabase extends GeneratedDatabase {
cachedCalendarEvents,
cachedConversations,
syncMetadata,
pendingWrites,
];
}
@@ -5830,6 +6435,301 @@ typedef $$SyncMetadataTableProcessedTableManager =
SyncMetadataData,
PrefetchHooks Function()
>;
typedef $$PendingWritesTableCreateCompanionBuilder =
PendingWritesCompanion Function({
Value<int> id,
required String domain,
required String verb,
Value<int?> targetId,
Value<int?> tempId,
Value<String> payloadJson,
Value<DateTime?> baselineUpdatedAt,
Value<DateTime> createdAt,
Value<int> tries,
Value<String?> lastError,
});
typedef $$PendingWritesTableUpdateCompanionBuilder =
PendingWritesCompanion Function({
Value<int> id,
Value<String> domain,
Value<String> verb,
Value<int?> targetId,
Value<int?> tempId,
Value<String> payloadJson,
Value<DateTime?> baselineUpdatedAt,
Value<DateTime> createdAt,
Value<int> tries,
Value<String?> lastError,
});
class $$PendingWritesTableFilterComposer
extends Composer<_$FabledDatabase, $PendingWritesTable> {
$$PendingWritesTableFilterComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
ColumnFilters<int> get id => $composableBuilder(
column: $table.id,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get domain => $composableBuilder(
column: $table.domain,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get verb => $composableBuilder(
column: $table.verb,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<int> get targetId => $composableBuilder(
column: $table.targetId,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<int> get tempId => $composableBuilder(
column: $table.tempId,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get payloadJson => $composableBuilder(
column: $table.payloadJson,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<DateTime> get baselineUpdatedAt => $composableBuilder(
column: $table.baselineUpdatedAt,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<DateTime> get createdAt => $composableBuilder(
column: $table.createdAt,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<int> get tries => $composableBuilder(
column: $table.tries,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get lastError => $composableBuilder(
column: $table.lastError,
builder: (column) => ColumnFilters(column),
);
}
class $$PendingWritesTableOrderingComposer
extends Composer<_$FabledDatabase, $PendingWritesTable> {
$$PendingWritesTableOrderingComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
ColumnOrderings<int> get id => $composableBuilder(
column: $table.id,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get domain => $composableBuilder(
column: $table.domain,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get verb => $composableBuilder(
column: $table.verb,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<int> get targetId => $composableBuilder(
column: $table.targetId,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<int> get tempId => $composableBuilder(
column: $table.tempId,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get payloadJson => $composableBuilder(
column: $table.payloadJson,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<DateTime> get baselineUpdatedAt => $composableBuilder(
column: $table.baselineUpdatedAt,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<DateTime> get createdAt => $composableBuilder(
column: $table.createdAt,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<int> get tries => $composableBuilder(
column: $table.tries,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get lastError => $composableBuilder(
column: $table.lastError,
builder: (column) => ColumnOrderings(column),
);
}
class $$PendingWritesTableAnnotationComposer
extends Composer<_$FabledDatabase, $PendingWritesTable> {
$$PendingWritesTableAnnotationComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
GeneratedColumn<int> get id =>
$composableBuilder(column: $table.id, builder: (column) => column);
GeneratedColumn<String> get domain =>
$composableBuilder(column: $table.domain, builder: (column) => column);
GeneratedColumn<String> get verb =>
$composableBuilder(column: $table.verb, builder: (column) => column);
GeneratedColumn<int> get targetId =>
$composableBuilder(column: $table.targetId, builder: (column) => column);
GeneratedColumn<int> get tempId =>
$composableBuilder(column: $table.tempId, builder: (column) => column);
GeneratedColumn<String> get payloadJson => $composableBuilder(
column: $table.payloadJson,
builder: (column) => column,
);
GeneratedColumn<DateTime> get baselineUpdatedAt => $composableBuilder(
column: $table.baselineUpdatedAt,
builder: (column) => column,
);
GeneratedColumn<DateTime> get createdAt =>
$composableBuilder(column: $table.createdAt, builder: (column) => column);
GeneratedColumn<int> get tries =>
$composableBuilder(column: $table.tries, builder: (column) => column);
GeneratedColumn<String> get lastError =>
$composableBuilder(column: $table.lastError, builder: (column) => column);
}
class $$PendingWritesTableTableManager
extends
RootTableManager<
_$FabledDatabase,
$PendingWritesTable,
PendingWrite,
$$PendingWritesTableFilterComposer,
$$PendingWritesTableOrderingComposer,
$$PendingWritesTableAnnotationComposer,
$$PendingWritesTableCreateCompanionBuilder,
$$PendingWritesTableUpdateCompanionBuilder,
(
PendingWrite,
BaseReferences<_$FabledDatabase, $PendingWritesTable, PendingWrite>,
),
PendingWrite,
PrefetchHooks Function()
> {
$$PendingWritesTableTableManager(
_$FabledDatabase db,
$PendingWritesTable table,
) : super(
TableManagerState(
db: db,
table: table,
createFilteringComposer: () =>
$$PendingWritesTableFilterComposer($db: db, $table: table),
createOrderingComposer: () =>
$$PendingWritesTableOrderingComposer($db: db, $table: table),
createComputedFieldComposer: () =>
$$PendingWritesTableAnnotationComposer($db: db, $table: table),
updateCompanionCallback:
({
Value<int> id = const Value.absent(),
Value<String> domain = const Value.absent(),
Value<String> verb = const Value.absent(),
Value<int?> targetId = const Value.absent(),
Value<int?> tempId = const Value.absent(),
Value<String> payloadJson = const Value.absent(),
Value<DateTime?> baselineUpdatedAt = const Value.absent(),
Value<DateTime> createdAt = const Value.absent(),
Value<int> tries = const Value.absent(),
Value<String?> lastError = const Value.absent(),
}) => PendingWritesCompanion(
id: id,
domain: domain,
verb: verb,
targetId: targetId,
tempId: tempId,
payloadJson: payloadJson,
baselineUpdatedAt: baselineUpdatedAt,
createdAt: createdAt,
tries: tries,
lastError: lastError,
),
createCompanionCallback:
({
Value<int> id = const Value.absent(),
required String domain,
required String verb,
Value<int?> targetId = const Value.absent(),
Value<int?> tempId = const Value.absent(),
Value<String> payloadJson = const Value.absent(),
Value<DateTime?> baselineUpdatedAt = const Value.absent(),
Value<DateTime> createdAt = const Value.absent(),
Value<int> tries = const Value.absent(),
Value<String?> lastError = const Value.absent(),
}) => PendingWritesCompanion.insert(
id: id,
domain: domain,
verb: verb,
targetId: targetId,
tempId: tempId,
payloadJson: payloadJson,
baselineUpdatedAt: baselineUpdatedAt,
createdAt: createdAt,
tries: tries,
lastError: lastError,
),
withReferenceMapper: (p0) => p0
.map((e) => (e.readTable(table), BaseReferences(db, table, e)))
.toList(),
prefetchHooksCallback: null,
),
);
}
typedef $$PendingWritesTableProcessedTableManager =
ProcessedTableManager<
_$FabledDatabase,
$PendingWritesTable,
PendingWrite,
$$PendingWritesTableFilterComposer,
$$PendingWritesTableOrderingComposer,
$$PendingWritesTableAnnotationComposer,
$$PendingWritesTableCreateCompanionBuilder,
$$PendingWritesTableUpdateCompanionBuilder,
(
PendingWrite,
BaseReferences<_$FabledDatabase, $PendingWritesTable, PendingWrite>,
),
PendingWrite,
PrefetchHooks Function()
>;
class $FabledDatabaseManager {
final _$FabledDatabase _db;
@@ -5848,4 +6748,6 @@ class $FabledDatabaseManager {
$$CachedConversationsTableTableManager(_db, _db.cachedConversations);
$$SyncMetadataTableTableManager get syncMetadata =>
$$SyncMetadataTableTableManager(_db, _db.syncMetadata);
$$PendingWritesTableTableManager get pendingWrites =>
$$PendingWritesTableTableManager(_db, _db.pendingWrites);
}