From 60ca067236ecaa2b3ca3b628f6a96905f87b6f93 Mon Sep 17 00:00:00 2001 From: Ashley Venn Date: Fri, 26 Jun 2026 19:37:40 -0700 Subject: [PATCH] feat: split scheduler into workspace packages --- .../scheduler_backup/analysis_options.yaml | 1 + packages/scheduler_backup/bin/backup.dart | 31 + packages/scheduler_backup/lib/backup.dart | 4 + .../lib/scheduler_backup.dart | 4 + .../lib/src/encrypted_backup.dart | 262 + packages/scheduler_backup/pubspec.yaml | 16 + .../test/encrypted_backup_test.dart | 87 + .../scheduler_core/analysis_options.yaml | 0 .../scheduler_core/lib}/scheduler_core.dart | 1 + .../lib}/src/application_commands.dart | 23 + .../lib}/src/application_layer.dart | 2 +- .../lib}/src/application_management.dart | 0 .../lib}/src/application_recovery.dart | 0 .../scheduler_core/lib}/src/backlog.dart | 0 .../scheduler_core/lib}/src/child_tasks.dart | 2 +- .../lib}/src/document_mapping.dart | 6 +- .../lib}/src/document_migration.dart | 0 .../scheduler_core/lib}/src/free_slots.dart | 0 .../scheduler_core/lib}/src/locked_time.dart | 0 .../scheduler_core/lib}/src/models.dart | 0 .../lib}/src/occupancy_policy.dart | 0 .../lib}/src/persistence_contract.dart | 10 +- .../lib}/src/project_statistics.dart | 0 .../lib}/src/quick_capture.dart | 0 .../lib}/src/reminder_policy.dart | 0 .../scheduler_core/lib}/src/repositories.dart | 5 +- .../lib/src/repository_values.dart | 92 + .../lib}/src/scheduling_engine.dart | 4 +- .../scheduler_core/lib}/src/task_actions.dart | 0 .../lib}/src/task_lifecycle.dart | 0 .../lib}/src/task_statistics.dart | 0 .../lib}/src/time_contracts.dart | 0 .../lib}/src/timeline_state.dart | 2 +- .../scheduler_core/lib}/src/today_state.dart | 0 packages/scheduler_core/pubspec.yaml | 12 + .../test}/application_commands_test.dart | 32 +- .../test}/application_layer_test.dart | 2 +- .../test}/application_management_test.dart | 2 +- .../test}/application_recovery_test.dart | 2 +- .../test}/child_tasks_test.dart | 2 +- .../test}/document_mapping_test.dart | 4 +- .../test}/document_migration_test.dart | 7 +- .../test}/domain_contracts_test.dart | 2 +- .../test}/domain_invariants_test.dart | 2 +- .../test}/edge_case_regression_test.dart | 2 +- .../fixtures/migration/locked_block_v0.json | 0 .../migration/locked_override_v0.json | 0 .../test}/fixtures/migration/project_v0.json | 0 .../migration/task_future_schema.json | 0 .../migration/task_v0_ambiguous_datetime.json | 0 .../fixtures/migration/task_v0_full.json | 0 .../task_v0_malformed_missing_title.json | 0 .../fixtures/migration/task_v0_minimum.json | 0 .../scheduler_core/test}/free_slots_test.dart | 2 +- .../test}/occupancy_policy_test.dart | 2 +- .../test}/persistence_edge_cases_test.dart | 2 +- .../persistence_index_contract_test.dart | 2 +- .../test}/project_statistics_test.dart | 2 +- .../test}/reminder_policy_test.dart | 2 +- .../test}/repositories_test.dart | 2 +- .../test}/required_task_actions_test.dart | 2 +- .../test}/scheduling_engine_test.dart | 2 +- .../test}/scheduling_invariants_test.dart | 2 +- .../test}/surprise_task_logging_test.dart | 2 +- .../test}/task_lifecycle_test.dart | 2 +- .../test}/time_contracts_test.dart | 2 +- .../test}/timeline_state_test.dart | 2 +- .../test}/today_state_test.dart | 2 +- .../scheduler_export/analysis_options.yaml | 1 + packages/scheduler_export/lib/export.dart | 4 + .../lib/scheduler_export.dart | 4 + .../lib/src/export_controller.dart | 303 + packages/scheduler_export/pubspec.yaml | 17 + .../test/export_controller_test.dart | 100 + .../analysis_options.yaml | 1 + .../scheduler_export_json/bin/export.dart | 49 + .../lib/export_json.dart | 4 + .../lib/scheduler_export_json.dart | 4 + .../lib/src/json_csv_writers.dart | 146 + packages/scheduler_export_json/pubspec.yaml | 17 + .../test/export_json_test.dart | 94 + .../analysis_options.yaml | 1 + .../integration/scheduler_flow_test.dart | 118 + .../scheduler_integration_tests/pubspec.yaml | 20 + .../test/scheduler_flow_test.dart | 5 + .../analysis_options.yaml | 1 + .../lib/notifications.dart | 4 + .../lib/scheduler_notifications.dart | 4 + .../lib/src/notification_adapter.dart | 150 + packages/scheduler_notifications/pubspec.yaml | 12 + .../test/fake_adapter_test.dart | 60 + .../test/notification_adapter_contract.dart | 71 + .../analysis_options.yaml | 1 + .../lib/desktop_notifications.dart | 4 + .../lib/scheduler_notifications_desktop.dart | 4 + .../lib/src/desktop_notification_adapter.dart | 2 + .../src/desktop_notification_adapter_io.dart | 228 + .../desktop_notification_adapter_stub.dart | 117 + .../pubspec.yaml | 15 + .../desktop_notification_adapter_test.dart | 152 + .../analysis_options.yaml | 1 + .../lib/persistence.dart | 325 + packages/scheduler_persistence/pubspec.yaml | 15 + .../test/repo_conformance.dart | 462 ++ .../test/repo_contract_smoke_test.dart | 40 + .../analysis_options.yaml | 1 + .../lib/memory.dart | 4 + .../lib/persistence_memory.dart | 852 +++ .../scheduler_persistence_memory/pubspec.yaml | 15 + .../memory_repository_conformance_test.dart | 15 + .../analysis_options.yaml | 1 + .../lib/sqlite.dart | 5 + .../lib/src/scheduler_db.dart | 231 + .../lib/src/scheduler_db.g.dart | 6639 +++++++++++++++++ .../lib/src/sqlite_repositories.dart | 1374 ++++ .../scheduler_persistence_sqlite/pubspec.yaml | 19 + .../test/scheduler_db_test.dart | 33 + .../sqlite_repository_conformance_test.dart | 57 + pubspec.yaml | 33 +- test/scheduler_core_test.dart | 105 + 120 files changed, 12543 insertions(+), 48 deletions(-) create mode 100644 packages/scheduler_backup/analysis_options.yaml create mode 100644 packages/scheduler_backup/bin/backup.dart create mode 100644 packages/scheduler_backup/lib/backup.dart create mode 100644 packages/scheduler_backup/lib/scheduler_backup.dart create mode 100644 packages/scheduler_backup/lib/src/encrypted_backup.dart create mode 100644 packages/scheduler_backup/pubspec.yaml create mode 100644 packages/scheduler_backup/test/encrypted_backup_test.dart rename analysis_options.yaml => packages/scheduler_core/analysis_options.yaml (100%) rename {lib => packages/scheduler_core/lib}/scheduler_core.dart (97%) rename {lib => packages/scheduler_core/lib}/src/application_commands.dart (98%) rename {lib => packages/scheduler_core/lib}/src/application_layer.dart (99%) rename {lib => packages/scheduler_core/lib}/src/application_management.dart (100%) rename {lib => packages/scheduler_core/lib}/src/application_recovery.dart (100%) rename {lib => packages/scheduler_core/lib}/src/backlog.dart (100%) rename {lib => packages/scheduler_core/lib}/src/child_tasks.dart (99%) rename {lib => packages/scheduler_core/lib}/src/document_mapping.dart (99%) rename {lib => packages/scheduler_core/lib}/src/document_migration.dart (100%) rename {lib => packages/scheduler_core/lib}/src/free_slots.dart (100%) rename {lib => packages/scheduler_core/lib}/src/locked_time.dart (100%) rename {lib => packages/scheduler_core/lib}/src/models.dart (100%) rename {lib => packages/scheduler_core/lib}/src/occupancy_policy.dart (100%) rename {lib => packages/scheduler_core/lib}/src/persistence_contract.dart (99%) rename {lib => packages/scheduler_core/lib}/src/project_statistics.dart (100%) rename {lib => packages/scheduler_core/lib}/src/quick_capture.dart (100%) rename {lib => packages/scheduler_core/lib}/src/reminder_policy.dart (100%) rename {lib => packages/scheduler_core/lib}/src/repositories.dart (99%) create mode 100644 packages/scheduler_core/lib/src/repository_values.dart rename {lib => packages/scheduler_core/lib}/src/scheduling_engine.dart (99%) rename {lib => packages/scheduler_core/lib}/src/task_actions.dart (100%) rename {lib => packages/scheduler_core/lib}/src/task_lifecycle.dart (100%) rename {lib => packages/scheduler_core/lib}/src/task_statistics.dart (100%) rename {lib => packages/scheduler_core/lib}/src/time_contracts.dart (100%) rename {lib => packages/scheduler_core/lib}/src/timeline_state.dart (99%) rename {lib => packages/scheduler_core/lib}/src/today_state.dart (100%) create mode 100644 packages/scheduler_core/pubspec.yaml rename {test => packages/scheduler_core/test}/application_commands_test.dart (94%) rename {test => packages/scheduler_core/test}/application_layer_test.dart (99%) rename {test => packages/scheduler_core/test}/application_management_test.dart (99%) rename {test => packages/scheduler_core/test}/application_recovery_test.dart (99%) rename {test => packages/scheduler_core/test}/child_tasks_test.dart (99%) rename {test => packages/scheduler_core/test}/document_mapping_test.dart (99%) rename {test => packages/scheduler_core/test}/document_migration_test.dart (97%) rename {test => packages/scheduler_core/test}/domain_contracts_test.dart (98%) rename {test => packages/scheduler_core/test}/domain_invariants_test.dart (99%) rename {test => packages/scheduler_core/test}/edge_case_regression_test.dart (99%) rename {test => packages/scheduler_core/test}/fixtures/migration/locked_block_v0.json (100%) rename {test => packages/scheduler_core/test}/fixtures/migration/locked_override_v0.json (100%) rename {test => packages/scheduler_core/test}/fixtures/migration/project_v0.json (100%) rename {test => packages/scheduler_core/test}/fixtures/migration/task_future_schema.json (100%) rename {test => packages/scheduler_core/test}/fixtures/migration/task_v0_ambiguous_datetime.json (100%) rename {test => packages/scheduler_core/test}/fixtures/migration/task_v0_full.json (100%) rename {test => packages/scheduler_core/test}/fixtures/migration/task_v0_malformed_missing_title.json (100%) rename {test => packages/scheduler_core/test}/fixtures/migration/task_v0_minimum.json (100%) rename {test => packages/scheduler_core/test}/free_slots_test.dart (99%) rename {test => packages/scheduler_core/test}/occupancy_policy_test.dart (99%) rename {test => packages/scheduler_core/test}/persistence_edge_cases_test.dart (99%) rename {test => packages/scheduler_core/test}/persistence_index_contract_test.dart (99%) rename {test => packages/scheduler_core/test}/project_statistics_test.dart (99%) rename {test => packages/scheduler_core/test}/reminder_policy_test.dart (99%) rename {test => packages/scheduler_core/test}/repositories_test.dart (99%) rename {test => packages/scheduler_core/test}/required_task_actions_test.dart (98%) rename {test => packages/scheduler_core/test}/scheduling_engine_test.dart (99%) rename {test => packages/scheduler_core/test}/scheduling_invariants_test.dart (99%) rename {test => packages/scheduler_core/test}/surprise_task_logging_test.dart (99%) rename {test => packages/scheduler_core/test}/task_lifecycle_test.dart (99%) rename {test => packages/scheduler_core/test}/time_contracts_test.dart (99%) rename {test => packages/scheduler_core/test}/timeline_state_test.dart (99%) rename {test => packages/scheduler_core/test}/today_state_test.dart (99%) create mode 100644 packages/scheduler_export/analysis_options.yaml create mode 100644 packages/scheduler_export/lib/export.dart create mode 100644 packages/scheduler_export/lib/scheduler_export.dart create mode 100644 packages/scheduler_export/lib/src/export_controller.dart create mode 100644 packages/scheduler_export/pubspec.yaml create mode 100644 packages/scheduler_export/test/export_controller_test.dart create mode 100644 packages/scheduler_export_json/analysis_options.yaml create mode 100644 packages/scheduler_export_json/bin/export.dart create mode 100644 packages/scheduler_export_json/lib/export_json.dart create mode 100644 packages/scheduler_export_json/lib/scheduler_export_json.dart create mode 100644 packages/scheduler_export_json/lib/src/json_csv_writers.dart create mode 100644 packages/scheduler_export_json/pubspec.yaml create mode 100644 packages/scheduler_export_json/test/export_json_test.dart create mode 100644 packages/scheduler_integration_tests/analysis_options.yaml create mode 100644 packages/scheduler_integration_tests/integration/scheduler_flow_test.dart create mode 100644 packages/scheduler_integration_tests/pubspec.yaml create mode 100644 packages/scheduler_integration_tests/test/scheduler_flow_test.dart create mode 100644 packages/scheduler_notifications/analysis_options.yaml create mode 100644 packages/scheduler_notifications/lib/notifications.dart create mode 100644 packages/scheduler_notifications/lib/scheduler_notifications.dart create mode 100644 packages/scheduler_notifications/lib/src/notification_adapter.dart create mode 100644 packages/scheduler_notifications/pubspec.yaml create mode 100644 packages/scheduler_notifications/test/fake_adapter_test.dart create mode 100644 packages/scheduler_notifications/test/notification_adapter_contract.dart create mode 100644 packages/scheduler_notifications_desktop/analysis_options.yaml create mode 100644 packages/scheduler_notifications_desktop/lib/desktop_notifications.dart create mode 100644 packages/scheduler_notifications_desktop/lib/scheduler_notifications_desktop.dart create mode 100644 packages/scheduler_notifications_desktop/lib/src/desktop_notification_adapter.dart create mode 100644 packages/scheduler_notifications_desktop/lib/src/desktop_notification_adapter_io.dart create mode 100644 packages/scheduler_notifications_desktop/lib/src/desktop_notification_adapter_stub.dart create mode 100644 packages/scheduler_notifications_desktop/pubspec.yaml create mode 100644 packages/scheduler_notifications_desktop/test/desktop_notification_adapter_test.dart create mode 100644 packages/scheduler_persistence/analysis_options.yaml create mode 100644 packages/scheduler_persistence/lib/persistence.dart create mode 100644 packages/scheduler_persistence/pubspec.yaml create mode 100644 packages/scheduler_persistence/test/repo_conformance.dart create mode 100644 packages/scheduler_persistence/test/repo_contract_smoke_test.dart create mode 100644 packages/scheduler_persistence_memory/analysis_options.yaml create mode 100644 packages/scheduler_persistence_memory/lib/memory.dart create mode 100644 packages/scheduler_persistence_memory/lib/persistence_memory.dart create mode 100644 packages/scheduler_persistence_memory/pubspec.yaml create mode 100644 packages/scheduler_persistence_memory/test/memory_repository_conformance_test.dart create mode 100644 packages/scheduler_persistence_sqlite/analysis_options.yaml create mode 100644 packages/scheduler_persistence_sqlite/lib/sqlite.dart create mode 100644 packages/scheduler_persistence_sqlite/lib/src/scheduler_db.dart create mode 100644 packages/scheduler_persistence_sqlite/lib/src/scheduler_db.g.dart create mode 100644 packages/scheduler_persistence_sqlite/lib/src/sqlite_repositories.dart create mode 100644 packages/scheduler_persistence_sqlite/pubspec.yaml create mode 100644 packages/scheduler_persistence_sqlite/test/scheduler_db_test.dart create mode 100644 packages/scheduler_persistence_sqlite/test/sqlite_repository_conformance_test.dart create mode 100644 test/scheduler_core_test.dart diff --git a/packages/scheduler_backup/analysis_options.yaml b/packages/scheduler_backup/analysis_options.yaml new file mode 100644 index 0000000..572dd23 --- /dev/null +++ b/packages/scheduler_backup/analysis_options.yaml @@ -0,0 +1 @@ +include: package:lints/recommended.yaml diff --git a/packages/scheduler_backup/bin/backup.dart b/packages/scheduler_backup/bin/backup.dart new file mode 100644 index 0000000..515bdda --- /dev/null +++ b/packages/scheduler_backup/bin/backup.dart @@ -0,0 +1,31 @@ +import 'dart:io'; + +import 'package:scheduler_backup/backup.dart'; + +Future main(List arguments) async { + final passphrase = _option(arguments, '--passphrase') ?? + Platform.environment['SCHEDULER_BACKUP_PASSPHRASE']; + if (passphrase == null || passphrase.isEmpty) { + stderr.writeln( + 'Usage: dart run scheduler_backup:backup --passphrase ', + ); + exitCode = 64; + return; + } + + try { + final backup = await createEncryptedBackup(passphrase: passphrase); + stdout.writeln(backup.path); + } on Object catch (error) { + stderr.writeln(error); + exitCode = 1; + } +} + +String? _option(List arguments, String name) { + final index = arguments.indexOf(name); + if (index == -1 || index + 1 >= arguments.length) { + return null; + } + return arguments[index + 1]; +} diff --git a/packages/scheduler_backup/lib/backup.dart b/packages/scheduler_backup/lib/backup.dart new file mode 100644 index 0000000..d5c4e44 --- /dev/null +++ b/packages/scheduler_backup/lib/backup.dart @@ -0,0 +1,4 @@ +/// Encrypted SQLite backup helpers. +library; + +export 'src/encrypted_backup.dart'; diff --git a/packages/scheduler_backup/lib/scheduler_backup.dart b/packages/scheduler_backup/lib/scheduler_backup.dart new file mode 100644 index 0000000..4f65026 --- /dev/null +++ b/packages/scheduler_backup/lib/scheduler_backup.dart @@ -0,0 +1,4 @@ +/// Package-name entry point for scheduler backup helpers. +library; + +export 'backup.dart'; diff --git a/packages/scheduler_backup/lib/src/encrypted_backup.dart b/packages/scheduler_backup/lib/src/encrypted_backup.dart new file mode 100644 index 0000000..c62b934 --- /dev/null +++ b/packages/scheduler_backup/lib/src/encrypted_backup.dart @@ -0,0 +1,262 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:cryptography/cryptography.dart'; + +/// Default scheduler data directory name under the user's home directory. +const defaultSchedulerDirectoryName = 'ADHD_Scheduler'; + +/// Default SQLite database file name used by local scheduler persistence. +const defaultSchedulerDatabaseFileName = 'scheduler.sqlite'; + +/// Default encrypted backup directory name under the scheduler data directory. +const defaultSchedulerBackupDirectoryName = 'backups'; + +const _formatMagic = 'ADHD_SCHEDULER_BACKUP_V1'; +const _fileExtension = '.db.enc'; +const _kdfIterations = 200000; +const _saltLength = 16; +const _nonceLength = 12; +const _keyLength = 32; + +final _utf8 = utf8.encoder; + +/// Thrown when an encrypted backup cannot be decrypted with the passphrase. +final class BackupDecryptionException implements Exception { + /// Creates a decryption failure with optional diagnostic [message]. + const BackupDecryptionException([ + this.message = 'Backup could not be decrypted.', + ]); + + /// Diagnostic text for logs and tests. + final String message; + + @override + String toString() => 'BackupDecryptionException: $message'; +} + +/// Thrown when backup input paths or file contents are invalid. +final class BackupException implements Exception { + /// Creates a backup exception with diagnostic [message]. + const BackupException(this.message); + + /// Diagnostic text for logs and tests. + final String message; + + @override + String toString() => 'BackupException: $message'; +} + +/// Create an encrypted backup of the scheduler SQLite file. +/// +/// Defaults: +/// - Source DB: `~/ADHD_Scheduler/scheduler.sqlite` +/// - Backup dir: `~/ADHD_Scheduler/backups` +/// - File name: `yyyymmdd_hhmm.db.enc` +Future createEncryptedBackup({ + required String passphrase, + File? sqliteFile, + Directory? backupDirectory, + DateTime? now, +}) async { + final source = sqliteFile ?? defaultSchedulerSqliteFile(); + if (!await source.exists()) { + throw BackupException('SQLite file does not exist: ${source.path}'); + } + + final destinationDirectory = + backupDirectory ?? defaultSchedulerBackupDirectory(); + await destinationDirectory.create(recursive: true); + + final timestamp = _backupTimestamp(now ?? DateTime.now()); + final destination = await _nextAvailableBackupFile( + destinationDirectory, + '$timestamp$_fileExtension', + ); + + final plaintext = await source.readAsBytes(); + final encoded = await _encryptBytes( + passphrase: passphrase, + plaintext: plaintext, + ); + return destination.writeAsBytes(encoded, flush: true); +} + +/// Restore an encrypted backup over the scheduler SQLite file. +/// +/// Defaults to restoring into `~/ADHD_Scheduler/scheduler.sqlite`. Tests and +/// tools can pass [targetFile] to restore into a temp or alternate database. +Future restoreEncryptedBackup( + File backup, + String passphrase, { + File? targetFile, +}) async { + if (!await backup.exists()) { + throw BackupException('Backup file does not exist: ${backup.path}'); + } + + final plaintext = await _decryptBytes( + passphrase: passphrase, + encoded: await backup.readAsBytes(), + ); + final target = targetFile ?? defaultSchedulerSqliteFile(); + await target.parent.create(recursive: true); + + final temporary = File('${target.path}.restore.tmp'); + await temporary.writeAsBytes(plaintext, flush: true); + if (await target.exists()) { + await target.delete(); + } + await temporary.rename(target.path); +} + +/// Default scheduler SQLite database path. +File defaultSchedulerSqliteFile() { + return File(_joinPath( + _homeDirectory().path, + defaultSchedulerDirectoryName, + defaultSchedulerDatabaseFileName, + )); +} + +/// Default encrypted backup output directory. +Directory defaultSchedulerBackupDirectory() { + return Directory(_joinPath( + _homeDirectory().path, + defaultSchedulerDirectoryName, + defaultSchedulerBackupDirectoryName, + )); +} + +Future> _encryptBytes({ + required String passphrase, + required List plaintext, +}) async { + final salt = _randomBytes(_saltLength); + final nonce = _randomBytes(_nonceLength); + final key = await _deriveKey(passphrase: passphrase, salt: salt); + final algorithm = AesGcm.with256bits(); + final secretBox = await algorithm.encrypt( + plaintext, + secretKey: key, + nonce: nonce, + ); + final envelope = { + 'magic': _formatMagic, + 'kdf': 'pbkdf2-hmac-sha256', + 'iterations': _kdfIterations, + 'cipher': 'aes-256-gcm', + 'salt': base64Encode(salt), + 'nonce': base64Encode(secretBox.nonce), + 'ciphertext': base64Encode(secretBox.cipherText), + 'mac': base64Encode(secretBox.mac.bytes), + }; + return _utf8.convert(jsonEncode(envelope)); +} + +Future> _decryptBytes({ + required String passphrase, + required List encoded, +}) async { + try { + final envelope = jsonDecode(utf8.decode(encoded)) as Map; + if (envelope['magic'] != _formatMagic || + envelope['kdf'] != 'pbkdf2-hmac-sha256' || + envelope['iterations'] != _kdfIterations || + envelope['cipher'] != 'aes-256-gcm') { + throw const BackupDecryptionException('Unsupported backup format.'); + } + + final salt = base64Decode(envelope['salt'] as String); + final nonce = base64Decode(envelope['nonce'] as String); + final cipherText = base64Decode(envelope['ciphertext'] as String); + final mac = Mac(base64Decode(envelope['mac'] as String)); + final key = await _deriveKey(passphrase: passphrase, salt: salt); + final algorithm = AesGcm.with256bits(); + return await algorithm.decrypt( + SecretBox(cipherText, nonce: nonce, mac: mac), + secretKey: key, + ); + } on BackupDecryptionException { + rethrow; + } on Object { + throw const BackupDecryptionException(); + } +} + +Future _deriveKey({ + required String passphrase, + required List salt, +}) { + final algorithm = Pbkdf2( + macAlgorithm: Hmac.sha256(), + iterations: _kdfIterations, + bits: _keyLength * 8, + ); + return algorithm.deriveKey( + secretKey: SecretKey(utf8.encode(passphrase)), + nonce: salt, + ); +} + +List _randomBytes(int length) { + final random = Random.secure(); + return Uint8List.fromList( + List.generate(length, (_) => random.nextInt(256)), + ); +} + +Future _nextAvailableBackupFile(Directory directory, String name) async { + final preferred = File(_joinPath(directory.path, name)); + if (!await preferred.exists()) { + return preferred; + } + final dot = name.lastIndexOf('.'); + final stem = dot == -1 ? name : name.substring(0, dot); + final extension = dot == -1 ? '' : name.substring(dot); + var counter = 2; + while (true) { + final candidate = + File(_joinPath(directory.path, '$stem-$counter$extension')); + if (!await candidate.exists()) { + return candidate; + } + counter += 1; + } +} + +String _backupTimestamp(DateTime value) { + final local = value.toLocal(); + return '${_four(local.year)}${_two(local.month)}${_two(local.day)}_' + '${_two(local.hour)}${_two(local.minute)}'; +} + +String _four(int value) => value.toString().padLeft(4, '0'); + +String _two(int value) => value.toString().padLeft(2, '0'); + +Directory _homeDirectory() { + final home = Platform.environment['HOME'] ?? + Platform.environment['USERPROFILE'] ?? + Platform.environment['HOMEDRIVE']; + if (home == null || home.trim().isEmpty) { + throw const BackupException('Home directory could not be resolved.'); + } + return Directory(home); +} + +String _joinPath(String first, String second, [String? third]) { + final separator = Platform.pathSeparator; + final left = first.endsWith(separator) + ? first.substring(0, first.length - separator.length) + : first; + final middle = second.startsWith(separator) ? second.substring(1) : second; + final joined = '$left$separator$middle'; + if (third == null) { + return joined; + } + final right = third.startsWith(separator) ? third.substring(1) : third; + return '$joined$separator$right'; +} diff --git a/packages/scheduler_backup/pubspec.yaml b/packages/scheduler_backup/pubspec.yaml new file mode 100644 index 0000000..277051a --- /dev/null +++ b/packages/scheduler_backup/pubspec.yaml @@ -0,0 +1,16 @@ +name: scheduler_backup +description: Encrypted SQLite backup and restore helpers for the ADHD scheduler. +version: 0.1.0 +publish_to: none +resolution: workspace + +environment: + sdk: '>=3.6.0 <4.0.0' + +dependencies: + cryptography: ^2.7.0 + encrypt: ^5.0.3 + +dev_dependencies: + lints: ^5.0.0 + test: ^1.25.0 diff --git a/packages/scheduler_backup/test/encrypted_backup_test.dart b/packages/scheduler_backup/test/encrypted_backup_test.dart new file mode 100644 index 0000000..6987d22 --- /dev/null +++ b/packages/scheduler_backup/test/encrypted_backup_test.dart @@ -0,0 +1,87 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:scheduler_backup/backup.dart'; +import 'package:test/test.dart'; + +void main() { + test('encrypted backup round trips a SQLite file using temp paths', () async { + final temp = + await Directory.systemTemp.createTemp('scheduler-backup-test-'); + addTearDown(() => temp.delete(recursive: true)); + final sqlite = + File('${temp.path}${Platform.pathSeparator}scheduler.sqlite'); + final backupDirectory = + Directory('${temp.path}${Platform.pathSeparator}backups'); + final restored = + File('${temp.path}${Platform.pathSeparator}restored.sqlite'); + const sqliteBytes = [ + 0x53, + 0x51, + 0x4c, + 0x69, + 0x74, + 0x65, + 0x20, + 0x66, + 0x6f, + 0x72, + 0x6d, + 0x61, + 0x74, + 0x20, + 0x33, + 0x00, + 0x01, + 0x02, + 0x03, + ]; + await sqlite.writeAsBytes(sqliteBytes); + + final backup = await createEncryptedBackup( + passphrase: 'correct horse battery staple', + sqliteFile: sqlite, + backupDirectory: backupDirectory, + now: DateTime(2026, 6, 26, 9, 7), + ); + + expect(backup.path, endsWith('20260626_0907.db.enc')); + expect(await backup.exists(), isTrue); + expect(await backup.readAsBytes(), isNot(sqliteBytes)); + final envelope = + jsonDecode(await backup.readAsString()) as Map; + expect(envelope['cipher'], 'aes-256-gcm'); + + await restoreEncryptedBackup( + backup, + 'correct horse battery staple', + targetFile: restored, + ); + + expect(await restored.readAsBytes(), sqliteBytes); + }); + + test('wrong passphrase throws BackupDecryptionException', () async { + final temp = + await Directory.systemTemp.createTemp('scheduler-backup-test-'); + addTearDown(() => temp.delete(recursive: true)); + final sqlite = + File('${temp.path}${Platform.pathSeparator}scheduler.sqlite'); + await sqlite.writeAsBytes(utf8.encode('sqlite bytes')); + final backup = await createEncryptedBackup( + passphrase: 'right-passphrase', + sqliteFile: sqlite, + backupDirectory: temp, + now: DateTime(2026, 6, 26, 9, 7), + ); + + expect( + () => restoreEncryptedBackup( + backup, + 'wrong-passphrase', + targetFile: File('${temp.path}${Platform.pathSeparator}restore.sqlite'), + ), + throwsA(isA()), + ); + }); +} diff --git a/analysis_options.yaml b/packages/scheduler_core/analysis_options.yaml similarity index 100% rename from analysis_options.yaml rename to packages/scheduler_core/analysis_options.yaml diff --git a/lib/scheduler_core.dart b/packages/scheduler_core/lib/scheduler_core.dart similarity index 97% rename from lib/scheduler_core.dart rename to packages/scheduler_core/lib/scheduler_core.dart index 3320c53..873efc3 100644 --- a/lib/scheduler_core.dart +++ b/packages/scheduler_core/lib/scheduler_core.dart @@ -33,6 +33,7 @@ export 'src/project_statistics.dart'; export 'src/quick_capture.dart'; export 'src/reminder_policy.dart'; export 'src/repositories.dart'; +export 'src/repository_values.dart'; export 'src/scheduling_engine.dart'; export 'src/task_actions.dart'; export 'src/task_lifecycle.dart'; diff --git a/lib/src/application_commands.dart b/packages/scheduler_core/lib/src/application_commands.dart similarity index 98% rename from lib/src/application_commands.dart rename to packages/scheduler_core/lib/src/application_commands.dart index 0cf1858..eb954ab 100644 --- a/lib/src/application_commands.dart +++ b/packages/scheduler_core/lib/src/application_commands.dart @@ -325,6 +325,7 @@ class V1ApplicationCommandUseCases { required ApplicationOperationContext context, required CivilDate localDate, required String taskId, + int? durationMinutes, DateTime? expectedUpdatedAt, }) { const commandCode = @@ -348,6 +349,20 @@ class V1ApplicationCommandUseCases { if (staleFailure != null) { return ApplicationResult.failure(staleFailure); } + if (durationMinutes != null && durationMinutes <= 0) { + return _failure( + ApplicationFailureCode.validation, + entityId: taskId, + detailCode: DomainValidationCode.nonPositiveDuration.name, + ); + } + if (durationMinutes != null) { + task = task.copyWith( + durationMinutes: durationMinutes, + updatedAt: context.now, + ); + state = state.replaceTask(task); + } final schedulingResult = const SchedulingEngine().insertBacklogTaskIntoNextAvailableSlot( @@ -1205,6 +1220,14 @@ class _PlanningState { lockedIntervals: lockedIntervals, ); } + + _PlanningState replaceTask(Task replacement) { + return _PlanningState( + tasks: _replaceTask(tasks, replacement), + window: window, + lockedIntervals: lockedIntervals, + ); + } } ApplicationResult _failure( diff --git a/lib/src/application_layer.dart b/packages/scheduler_core/lib/src/application_layer.dart similarity index 99% rename from lib/src/application_layer.dart rename to packages/scheduler_core/lib/src/application_layer.dart index 9787319..0832325 100644 --- a/lib/src/application_layer.dart +++ b/packages/scheduler_core/lib/src/application_layer.dart @@ -2,7 +2,7 @@ // // This layer sits above the pure domain services and repository interfaces. It // gives future Flutter/use-case code one atomic boundary per user command -// without importing widgets, MongoDB clients, or platform notification APIs. +// without importing widgets, database clients, or platform notification APIs. import 'backlog.dart'; import 'locked_time.dart'; diff --git a/lib/src/application_management.dart b/packages/scheduler_core/lib/src/application_management.dart similarity index 100% rename from lib/src/application_management.dart rename to packages/scheduler_core/lib/src/application_management.dart diff --git a/lib/src/application_recovery.dart b/packages/scheduler_core/lib/src/application_recovery.dart similarity index 100% rename from lib/src/application_recovery.dart rename to packages/scheduler_core/lib/src/application_recovery.dart diff --git a/lib/src/backlog.dart b/packages/scheduler_core/lib/src/backlog.dart similarity index 100% rename from lib/src/backlog.dart rename to packages/scheduler_core/lib/src/backlog.dart diff --git a/lib/src/child_tasks.dart b/packages/scheduler_core/lib/src/child_tasks.dart similarity index 99% rename from lib/src/child_tasks.dart rename to packages/scheduler_core/lib/src/child_tasks.dart index 5320bd2..03eb25c 100644 --- a/lib/src/child_tasks.dart +++ b/packages/scheduler_core/lib/src/child_tasks.dart @@ -139,7 +139,7 @@ class ChildTaskBreakUpResult { class ChildTaskBreakUpService { const ChildTaskBreakUpService(); - /// Break [request.parentTaskId] into direct child tasks. + /// Break `request.parentTaskId` into direct child tasks. /// /// This service validates the full requested set before returning any /// mutations. Persistence and transaction wiring are handled by later blocks. diff --git a/lib/src/document_mapping.dart b/packages/scheduler_core/lib/src/document_mapping.dart similarity index 99% rename from lib/src/document_mapping.dart rename to packages/scheduler_core/lib/src/document_mapping.dart index d84474c..231abb8 100644 --- a/lib/src/document_mapping.dart +++ b/packages/scheduler_core/lib/src/document_mapping.dart @@ -1,7 +1,7 @@ -// MongoDB-friendly V1 document mapping helpers. +// Adapter-neutral V1 document mapping helpers. // -// These helpers use plain Dart map shapes only. They do not import MongoDB APIs, -// create clients, or perform database I/O. +// These helpers use plain Dart map shapes only. They do not import database +// APIs, create clients, or perform database I/O. import 'application_layer.dart'; import 'backlog.dart'; diff --git a/lib/src/document_migration.dart b/packages/scheduler_core/lib/src/document_migration.dart similarity index 100% rename from lib/src/document_migration.dart rename to packages/scheduler_core/lib/src/document_migration.dart diff --git a/lib/src/free_slots.dart b/packages/scheduler_core/lib/src/free_slots.dart similarity index 100% rename from lib/src/free_slots.dart rename to packages/scheduler_core/lib/src/free_slots.dart diff --git a/lib/src/locked_time.dart b/packages/scheduler_core/lib/src/locked_time.dart similarity index 100% rename from lib/src/locked_time.dart rename to packages/scheduler_core/lib/src/locked_time.dart diff --git a/lib/src/models.dart b/packages/scheduler_core/lib/src/models.dart similarity index 100% rename from lib/src/models.dart rename to packages/scheduler_core/lib/src/models.dart diff --git a/lib/src/occupancy_policy.dart b/packages/scheduler_core/lib/src/occupancy_policy.dart similarity index 100% rename from lib/src/occupancy_policy.dart rename to packages/scheduler_core/lib/src/occupancy_policy.dart diff --git a/lib/src/persistence_contract.dart b/packages/scheduler_core/lib/src/persistence_contract.dart similarity index 99% rename from lib/src/persistence_contract.dart rename to packages/scheduler_core/lib/src/persistence_contract.dart index 37cf970..d78ab78 100644 --- a/lib/src/persistence_contract.dart +++ b/packages/scheduler_core/lib/src/persistence_contract.dart @@ -1,15 +1,15 @@ -// MongoDB-oriented persistence contract helpers. +// Adapter-neutral persistence contract helpers. // // This file defines stable field names and encoding conventions that document // codecs and future adapters should use. It deliberately does not serialize -// domain models or import a MongoDB client. +// domain models or import a database client. import 'time_contracts.dart'; /// V1 document-schema version. const v1SchemaVersion = 1; -/// Stable V1 collection names for future MongoDB adapters. +/// Stable V1 logical collection names for persistence adapters. abstract final class PersistenceCollections { static const tasks = 'tasks'; static const projects = 'projects'; @@ -553,9 +553,9 @@ extension PersistenceEnumName on Enum { String get persistenceName => name; } -/// Shared MongoDB document field names. +/// Shared V1 document field names. abstract final class DocumentFields { - /// MongoDB primary id field. + /// Primary id field. static const id = '_id'; /// Version of the document shape. diff --git a/lib/src/project_statistics.dart b/packages/scheduler_core/lib/src/project_statistics.dart similarity index 100% rename from lib/src/project_statistics.dart rename to packages/scheduler_core/lib/src/project_statistics.dart diff --git a/lib/src/quick_capture.dart b/packages/scheduler_core/lib/src/quick_capture.dart similarity index 100% rename from lib/src/quick_capture.dart rename to packages/scheduler_core/lib/src/quick_capture.dart diff --git a/lib/src/reminder_policy.dart b/packages/scheduler_core/lib/src/reminder_policy.dart similarity index 100% rename from lib/src/reminder_policy.dart rename to packages/scheduler_core/lib/src/reminder_policy.dart diff --git a/lib/src/repositories.dart b/packages/scheduler_core/lib/src/repositories.dart similarity index 99% rename from lib/src/repositories.dart rename to packages/scheduler_core/lib/src/repositories.dart index 4cb12dc..b390548 100644 --- a/lib/src/repositories.dart +++ b/packages/scheduler_core/lib/src/repositories.dart @@ -1,8 +1,7 @@ // Persistence boundary interfaces for the scheduling core. // -// These interfaces are intentionally database-client-free. A future MongoDB -// adapter can implement them with document storage while the domain layer and -// tests keep using the same pure Dart contracts. +// These interfaces are intentionally database-client-free. SQLite, in-memory, +// and future adapters implement the same pure Dart contracts. import 'locked_time.dart'; import 'models.dart'; diff --git a/packages/scheduler_core/lib/src/repository_values.dart b/packages/scheduler_core/lib/src/repository_values.dart new file mode 100644 index 0000000..c86ca60 --- /dev/null +++ b/packages/scheduler_core/lib/src/repository_values.dart @@ -0,0 +1,92 @@ +/// Shared repository value objects used by persistence adapters. +/// +/// These types are deliberately small and database-agnostic. They live in the +/// core package so every persistence adapter can use the same owner, revision, +/// and paging contracts without importing a storage implementation. +library; + +/// Stable owner scope for repository operations. +class OwnerId { + OwnerId(String value) : value = _requiredTrimmed(value, 'ownerId'); + + /// Raw owner identifier. + final String value; + + @override + String toString() => value; + + @override + bool operator ==(Object other) { + return other is OwnerId && other.value == value; + } + + @override + int get hashCode => value.hashCode; +} + +/// Optimistic concurrency revision for mutable repository records. +class Revision implements Comparable { + const Revision(this.value) : assert(value >= 0); + + /// First revision assigned to a newly inserted record. + static const initial = Revision(1); + + /// Numeric revision value. + final int value; + + /// Return the next monotonically increasing revision. + Revision next() => Revision(value + 1); + + @override + int compareTo(Revision other) => value.compareTo(other.value); + + @override + String toString() => value.toString(); + + @override + bool operator ==(Object other) { + return other is Revision && other.value == value; + } + + @override + int get hashCode => value.hashCode; +} + +/// Cursor contract for bounded repository queries. +class PageRequest { + const PageRequest({ + this.cursor, + this.limit = 100, + }) : assert(limit > 0); + + /// Adapter-neutral cursor. Implementations choose the cursor format. + final String? cursor; + + /// Maximum number of records to return. + final int limit; +} + +/// One bounded page of repository query results. +class Page { + Page({ + required Iterable items, + this.nextCursor, + }) : items = List.unmodifiable(items); + + /// Items returned for this page. + final List items; + + /// Cursor for the next page, or null when this is the last page. + final String? nextCursor; + + /// Whether another page is available. + bool get hasMore => nextCursor != null; +} + +String _requiredTrimmed(String value, String name) { + final trimmed = value.trim(); + if (trimmed.isEmpty) { + throw ArgumentError.value(value, name, 'Value must not be blank.'); + } + return trimmed; +} diff --git a/lib/src/scheduling_engine.dart b/packages/scheduler_core/lib/src/scheduling_engine.dart similarity index 99% rename from lib/src/scheduling_engine.dart rename to packages/scheduler_core/lib/src/scheduling_engine.dart index 0443a7f..5796bf3 100644 --- a/lib/src/scheduling_engine.dart +++ b/packages/scheduler_core/lib/src/scheduling_engine.dart @@ -449,7 +449,7 @@ class SchedulingEngine { /// flexible tasks at or after the insertion point may shift later, preserving /// their relative order. /// - /// The selected task must already exist in [input.tasks], be flexible, be in + /// The selected task must already exist in `input.tasks`, be flexible, be in /// backlog status, and have a positive duration. If any precondition fails, the /// original task list is returned with a no-fit/overflow notice. SchedulingResult insertBacklogTaskIntoNextAvailableSlot({ @@ -656,7 +656,7 @@ class SchedulingEngine { /// flexible tasks in that window may shift later, preserving their order. /// /// The method name says "tomorrow" because that is the product action, but the - /// engine only trusts [input.window]. Tests can pass any future window. + /// engine only trusts `input.window`. Tests can pass any future window. SchedulingResult pushFlexibleTaskToTomorrowTopOfQueue({ required SchedulingInput input, required String taskId, diff --git a/lib/src/task_actions.dart b/packages/scheduler_core/lib/src/task_actions.dart similarity index 100% rename from lib/src/task_actions.dart rename to packages/scheduler_core/lib/src/task_actions.dart diff --git a/lib/src/task_lifecycle.dart b/packages/scheduler_core/lib/src/task_lifecycle.dart similarity index 100% rename from lib/src/task_lifecycle.dart rename to packages/scheduler_core/lib/src/task_lifecycle.dart diff --git a/lib/src/task_statistics.dart b/packages/scheduler_core/lib/src/task_statistics.dart similarity index 100% rename from lib/src/task_statistics.dart rename to packages/scheduler_core/lib/src/task_statistics.dart diff --git a/lib/src/time_contracts.dart b/packages/scheduler_core/lib/src/time_contracts.dart similarity index 100% rename from lib/src/time_contracts.dart rename to packages/scheduler_core/lib/src/time_contracts.dart diff --git a/lib/src/timeline_state.dart b/packages/scheduler_core/lib/src/timeline_state.dart similarity index 99% rename from lib/src/timeline_state.dart rename to packages/scheduler_core/lib/src/timeline_state.dart index 1134988..d202b71 100644 --- a/lib/src/timeline_state.dart +++ b/packages/scheduler_core/lib/src/timeline_state.dart @@ -132,7 +132,7 @@ class CompactTimelineState { /// Whether the full timeline is expanded while compact mode is active. final bool fullTimelineExpanded; - /// Current visible task, if one is active or occupies [now]. + /// Current visible task, if one is active or occupies the query time. final TimelineItem? currentItem; /// Next critical or inflexible task after the current moment. diff --git a/lib/src/today_state.dart b/packages/scheduler_core/lib/src/today_state.dart similarity index 100% rename from lib/src/today_state.dart rename to packages/scheduler_core/lib/src/today_state.dart diff --git a/packages/scheduler_core/pubspec.yaml b/packages/scheduler_core/pubspec.yaml new file mode 100644 index 0000000..d87a90c --- /dev/null +++ b/packages/scheduler_core/pubspec.yaml @@ -0,0 +1,12 @@ +name: scheduler_core +description: A pure Dart scheduling core starter for an ADHD-focused scheduling app. +version: 0.1.0 +publish_to: none +resolution: workspace + +environment: + sdk: '>=3.6.0 <4.0.0' + +dev_dependencies: + lints: ^5.0.0 + test: ^1.25.0 diff --git a/test/application_commands_test.dart b/packages/scheduler_core/test/application_commands_test.dart similarity index 94% rename from test/application_commands_test.dart rename to packages/scheduler_core/test/application_commands_test.dart index d8397f1..4d01f46 100644 --- a/test/application_commands_test.dart +++ b/packages/scheduler_core/test/application_commands_test.dart @@ -1,4 +1,4 @@ -import 'package:adhd_scheduler_core/scheduler_core.dart'; +import 'package:scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { @@ -105,6 +105,36 @@ void main() { ]); }); + test('schedules backlog item after duration is supplied', () async { + final backlog = Task.quickCapture( + id: 'backlog', + title: 'Backlog', + projectId: 'home', + createdAt: createdAt, + updatedAt: createdAt, + ); + final store = storeWithSettings(tasks: [backlog]); + + final result = + await commands(store).scheduleBacklogItemToNextAvailableSlot( + context: appContext( + 'schedule-backlog-with-duration', + DateTime.utc(2026, 6, 25, 8), + ), + localDate: day, + taskId: backlog.id, + durationMinutes: 25, + expectedUpdatedAt: backlog.updatedAt, + ); + + expect(result.isSuccess, isTrue); + final scheduled = taskById(store.currentTasks, 'backlog'); + expect(scheduled.durationMinutes, 25); + expect(scheduled.status, TaskStatus.planned); + expect(scheduled.scheduledStart, DateTime.utc(2026, 6, 25, 9)); + expect(scheduled.scheduledEnd, DateTime.utc(2026, 6, 25, 9, 25)); + }); + test('stale expected update leaves repositories unchanged', () async { final planned = task( id: 'planned', diff --git a/test/application_layer_test.dart b/packages/scheduler_core/test/application_layer_test.dart similarity index 99% rename from test/application_layer_test.dart rename to packages/scheduler_core/test/application_layer_test.dart index bd53de6..0a94283 100644 --- a/test/application_layer_test.dart +++ b/packages/scheduler_core/test/application_layer_test.dart @@ -1,4 +1,4 @@ -import 'package:adhd_scheduler_core/scheduler_core.dart'; +import 'package:scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/application_management_test.dart b/packages/scheduler_core/test/application_management_test.dart similarity index 99% rename from test/application_management_test.dart rename to packages/scheduler_core/test/application_management_test.dart index aa3d05c..3448cc9 100644 --- a/test/application_management_test.dart +++ b/packages/scheduler_core/test/application_management_test.dart @@ -1,4 +1,4 @@ -import 'package:adhd_scheduler_core/scheduler_core.dart'; +import 'package:scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/application_recovery_test.dart b/packages/scheduler_core/test/application_recovery_test.dart similarity index 99% rename from test/application_recovery_test.dart rename to packages/scheduler_core/test/application_recovery_test.dart index 81e9cd2..4cebb56 100644 --- a/test/application_recovery_test.dart +++ b/packages/scheduler_core/test/application_recovery_test.dart @@ -1,4 +1,4 @@ -import 'package:adhd_scheduler_core/scheduler_core.dart'; +import 'package:scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/child_tasks_test.dart b/packages/scheduler_core/test/child_tasks_test.dart similarity index 99% rename from test/child_tasks_test.dart rename to packages/scheduler_core/test/child_tasks_test.dart index 0354ae2..de95b93 100644 --- a/test/child_tasks_test.dart +++ b/packages/scheduler_core/test/child_tasks_test.dart @@ -1,4 +1,4 @@ -import 'package:adhd_scheduler_core/scheduler_core.dart'; +import 'package:scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/document_mapping_test.dart b/packages/scheduler_core/test/document_mapping_test.dart similarity index 99% rename from test/document_mapping_test.dart rename to packages/scheduler_core/test/document_mapping_test.dart index 0c8ad5a..5dc144c 100644 --- a/test/document_mapping_test.dart +++ b/packages/scheduler_core/test/document_mapping_test.dart @@ -1,8 +1,8 @@ -import 'package:adhd_scheduler_core/scheduler_core.dart'; +import 'package:scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { - group('MongoDB V1 document codecs', () { + group('V1 document codecs', () { final createdAt = DateTime.utc(2026, 6, 23, 8); final updatedAt = DateTime.utc(2026, 6, 23, 8, 5); diff --git a/test/document_migration_test.dart b/packages/scheduler_core/test/document_migration_test.dart similarity index 97% rename from test/document_migration_test.dart rename to packages/scheduler_core/test/document_migration_test.dart index 0a1140a..3fb8788 100644 --- a/test/document_migration_test.dart +++ b/packages/scheduler_core/test/document_migration_test.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'dart:io'; -import 'package:adhd_scheduler_core/scheduler_core.dart'; +import 'package:scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { @@ -231,7 +231,10 @@ void main() { } Map fixture(String name) { - final file = File('test/fixtures/migration/$name'); + var file = File('test/fixtures/migration/$name'); + if (!file.existsSync()) { + file = File('packages/scheduler_core/test/fixtures/migration/$name'); + } final decoded = jsonDecode(file.readAsStringSync()) as Map; return Map.from(decoded); } diff --git a/test/domain_contracts_test.dart b/packages/scheduler_core/test/domain_contracts_test.dart similarity index 98% rename from test/domain_contracts_test.dart rename to packages/scheduler_core/test/domain_contracts_test.dart index 4e3ee92..6abadf0 100644 --- a/test/domain_contracts_test.dart +++ b/packages/scheduler_core/test/domain_contracts_test.dart @@ -1,4 +1,4 @@ -import 'package:adhd_scheduler_core/scheduler_core.dart'; +import 'package:scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/domain_invariants_test.dart b/packages/scheduler_core/test/domain_invariants_test.dart similarity index 99% rename from test/domain_invariants_test.dart rename to packages/scheduler_core/test/domain_invariants_test.dart index f0ef43b..912c718 100644 --- a/test/domain_invariants_test.dart +++ b/packages/scheduler_core/test/domain_invariants_test.dart @@ -1,4 +1,4 @@ -import 'package:adhd_scheduler_core/scheduler_core.dart'; +import 'package:scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/edge_case_regression_test.dart b/packages/scheduler_core/test/edge_case_regression_test.dart similarity index 99% rename from test/edge_case_regression_test.dart rename to packages/scheduler_core/test/edge_case_regression_test.dart index 3d68963..9bab02c 100644 --- a/test/edge_case_regression_test.dart +++ b/packages/scheduler_core/test/edge_case_regression_test.dart @@ -1,4 +1,4 @@ -import 'package:adhd_scheduler_core/scheduler_core.dart'; +import 'package:scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/fixtures/migration/locked_block_v0.json b/packages/scheduler_core/test/fixtures/migration/locked_block_v0.json similarity index 100% rename from test/fixtures/migration/locked_block_v0.json rename to packages/scheduler_core/test/fixtures/migration/locked_block_v0.json diff --git a/test/fixtures/migration/locked_override_v0.json b/packages/scheduler_core/test/fixtures/migration/locked_override_v0.json similarity index 100% rename from test/fixtures/migration/locked_override_v0.json rename to packages/scheduler_core/test/fixtures/migration/locked_override_v0.json diff --git a/test/fixtures/migration/project_v0.json b/packages/scheduler_core/test/fixtures/migration/project_v0.json similarity index 100% rename from test/fixtures/migration/project_v0.json rename to packages/scheduler_core/test/fixtures/migration/project_v0.json diff --git a/test/fixtures/migration/task_future_schema.json b/packages/scheduler_core/test/fixtures/migration/task_future_schema.json similarity index 100% rename from test/fixtures/migration/task_future_schema.json rename to packages/scheduler_core/test/fixtures/migration/task_future_schema.json diff --git a/test/fixtures/migration/task_v0_ambiguous_datetime.json b/packages/scheduler_core/test/fixtures/migration/task_v0_ambiguous_datetime.json similarity index 100% rename from test/fixtures/migration/task_v0_ambiguous_datetime.json rename to packages/scheduler_core/test/fixtures/migration/task_v0_ambiguous_datetime.json diff --git a/test/fixtures/migration/task_v0_full.json b/packages/scheduler_core/test/fixtures/migration/task_v0_full.json similarity index 100% rename from test/fixtures/migration/task_v0_full.json rename to packages/scheduler_core/test/fixtures/migration/task_v0_full.json diff --git a/test/fixtures/migration/task_v0_malformed_missing_title.json b/packages/scheduler_core/test/fixtures/migration/task_v0_malformed_missing_title.json similarity index 100% rename from test/fixtures/migration/task_v0_malformed_missing_title.json rename to packages/scheduler_core/test/fixtures/migration/task_v0_malformed_missing_title.json diff --git a/test/fixtures/migration/task_v0_minimum.json b/packages/scheduler_core/test/fixtures/migration/task_v0_minimum.json similarity index 100% rename from test/fixtures/migration/task_v0_minimum.json rename to packages/scheduler_core/test/fixtures/migration/task_v0_minimum.json diff --git a/test/free_slots_test.dart b/packages/scheduler_core/test/free_slots_test.dart similarity index 99% rename from test/free_slots_test.dart rename to packages/scheduler_core/test/free_slots_test.dart index bc35b0c..3319c7c 100644 --- a/test/free_slots_test.dart +++ b/packages/scheduler_core/test/free_slots_test.dart @@ -1,4 +1,4 @@ -import 'package:adhd_scheduler_core/scheduler_core.dart'; +import 'package:scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/occupancy_policy_test.dart b/packages/scheduler_core/test/occupancy_policy_test.dart similarity index 99% rename from test/occupancy_policy_test.dart rename to packages/scheduler_core/test/occupancy_policy_test.dart index b3b49cf..38b6804 100644 --- a/test/occupancy_policy_test.dart +++ b/packages/scheduler_core/test/occupancy_policy_test.dart @@ -1,4 +1,4 @@ -import 'package:adhd_scheduler_core/scheduler_core.dart'; +import 'package:scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/persistence_edge_cases_test.dart b/packages/scheduler_core/test/persistence_edge_cases_test.dart similarity index 99% rename from test/persistence_edge_cases_test.dart rename to packages/scheduler_core/test/persistence_edge_cases_test.dart index 064e4df..45934e9 100644 --- a/test/persistence_edge_cases_test.dart +++ b/packages/scheduler_core/test/persistence_edge_cases_test.dart @@ -1,4 +1,4 @@ -import 'package:adhd_scheduler_core/scheduler_core.dart'; +import 'package:scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/persistence_index_contract_test.dart b/packages/scheduler_core/test/persistence_index_contract_test.dart similarity index 99% rename from test/persistence_index_contract_test.dart rename to packages/scheduler_core/test/persistence_index_contract_test.dart index c08597d..8c81e73 100644 --- a/test/persistence_index_contract_test.dart +++ b/packages/scheduler_core/test/persistence_index_contract_test.dart @@ -1,4 +1,4 @@ -import 'package:adhd_scheduler_core/scheduler_core.dart'; +import 'package:scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/project_statistics_test.dart b/packages/scheduler_core/test/project_statistics_test.dart similarity index 99% rename from test/project_statistics_test.dart rename to packages/scheduler_core/test/project_statistics_test.dart index 6609448..c1b923f 100644 --- a/test/project_statistics_test.dart +++ b/packages/scheduler_core/test/project_statistics_test.dart @@ -1,4 +1,4 @@ -import 'package:adhd_scheduler_core/scheduler_core.dart'; +import 'package:scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/reminder_policy_test.dart b/packages/scheduler_core/test/reminder_policy_test.dart similarity index 99% rename from test/reminder_policy_test.dart rename to packages/scheduler_core/test/reminder_policy_test.dart index 3bd3053..e6f22d1 100644 --- a/test/reminder_policy_test.dart +++ b/packages/scheduler_core/test/reminder_policy_test.dart @@ -1,4 +1,4 @@ -import 'package:adhd_scheduler_core/scheduler_core.dart'; +import 'package:scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/repositories_test.dart b/packages/scheduler_core/test/repositories_test.dart similarity index 99% rename from test/repositories_test.dart rename to packages/scheduler_core/test/repositories_test.dart index 0100d8e..a26e83d 100644 --- a/test/repositories_test.dart +++ b/packages/scheduler_core/test/repositories_test.dart @@ -1,4 +1,4 @@ -import 'package:adhd_scheduler_core/scheduler_core.dart'; +import 'package:scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/required_task_actions_test.dart b/packages/scheduler_core/test/required_task_actions_test.dart similarity index 98% rename from test/required_task_actions_test.dart rename to packages/scheduler_core/test/required_task_actions_test.dart index 5e191e9..2cd1eb3 100644 --- a/test/required_task_actions_test.dart +++ b/packages/scheduler_core/test/required_task_actions_test.dart @@ -1,4 +1,4 @@ -import 'package:adhd_scheduler_core/scheduler_core.dart'; +import 'package:scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/scheduling_engine_test.dart b/packages/scheduler_core/test/scheduling_engine_test.dart similarity index 99% rename from test/scheduling_engine_test.dart rename to packages/scheduler_core/test/scheduling_engine_test.dart index 02f2c03..1e976ff 100644 --- a/test/scheduling_engine_test.dart +++ b/packages/scheduler_core/test/scheduling_engine_test.dart @@ -1,4 +1,4 @@ -import 'package:adhd_scheduler_core/scheduler_core.dart'; +import 'package:scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/scheduling_invariants_test.dart b/packages/scheduler_core/test/scheduling_invariants_test.dart similarity index 99% rename from test/scheduling_invariants_test.dart rename to packages/scheduler_core/test/scheduling_invariants_test.dart index 58511ad..4ed5b56 100644 --- a/test/scheduling_invariants_test.dart +++ b/packages/scheduler_core/test/scheduling_invariants_test.dart @@ -1,6 +1,6 @@ import 'dart:math'; -import 'package:adhd_scheduler_core/scheduler_core.dart'; +import 'package:scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/surprise_task_logging_test.dart b/packages/scheduler_core/test/surprise_task_logging_test.dart similarity index 99% rename from test/surprise_task_logging_test.dart rename to packages/scheduler_core/test/surprise_task_logging_test.dart index f2fe611..61393f3 100644 --- a/test/surprise_task_logging_test.dart +++ b/packages/scheduler_core/test/surprise_task_logging_test.dart @@ -1,4 +1,4 @@ -import 'package:adhd_scheduler_core/scheduler_core.dart'; +import 'package:scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/task_lifecycle_test.dart b/packages/scheduler_core/test/task_lifecycle_test.dart similarity index 99% rename from test/task_lifecycle_test.dart rename to packages/scheduler_core/test/task_lifecycle_test.dart index 61ee165..efc7ba4 100644 --- a/test/task_lifecycle_test.dart +++ b/packages/scheduler_core/test/task_lifecycle_test.dart @@ -1,4 +1,4 @@ -import 'package:adhd_scheduler_core/scheduler_core.dart'; +import 'package:scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/time_contracts_test.dart b/packages/scheduler_core/test/time_contracts_test.dart similarity index 99% rename from test/time_contracts_test.dart rename to packages/scheduler_core/test/time_contracts_test.dart index ba0a302..f57a223 100644 --- a/test/time_contracts_test.dart +++ b/packages/scheduler_core/test/time_contracts_test.dart @@ -1,4 +1,4 @@ -import 'package:adhd_scheduler_core/scheduler_core.dart'; +import 'package:scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/timeline_state_test.dart b/packages/scheduler_core/test/timeline_state_test.dart similarity index 99% rename from test/timeline_state_test.dart rename to packages/scheduler_core/test/timeline_state_test.dart index e6b441d..cd8eb33 100644 --- a/test/timeline_state_test.dart +++ b/packages/scheduler_core/test/timeline_state_test.dart @@ -1,4 +1,4 @@ -import 'package:adhd_scheduler_core/scheduler_core.dart'; +import 'package:scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { diff --git a/test/today_state_test.dart b/packages/scheduler_core/test/today_state_test.dart similarity index 99% rename from test/today_state_test.dart rename to packages/scheduler_core/test/today_state_test.dart index 1430001..4b942fd 100644 --- a/test/today_state_test.dart +++ b/packages/scheduler_core/test/today_state_test.dart @@ -1,4 +1,4 @@ -import 'package:adhd_scheduler_core/scheduler_core.dart'; +import 'package:scheduler_core/scheduler_core.dart'; import 'package:test/test.dart'; void main() { diff --git a/packages/scheduler_export/analysis_options.yaml b/packages/scheduler_export/analysis_options.yaml new file mode 100644 index 0000000..572dd23 --- /dev/null +++ b/packages/scheduler_export/analysis_options.yaml @@ -0,0 +1 @@ +include: package:lints/recommended.yaml diff --git a/packages/scheduler_export/lib/export.dart b/packages/scheduler_export/lib/export.dart new file mode 100644 index 0000000..21553e4 --- /dev/null +++ b/packages/scheduler_export/lib/export.dart @@ -0,0 +1,4 @@ +/// Readable export contracts and controller for scheduler data. +library; + +export 'src/export_controller.dart'; diff --git a/packages/scheduler_export/lib/scheduler_export.dart b/packages/scheduler_export/lib/scheduler_export.dart new file mode 100644 index 0000000..b46885b --- /dev/null +++ b/packages/scheduler_export/lib/scheduler_export.dart @@ -0,0 +1,4 @@ +/// Package-name entry point for scheduler export contracts. +library; + +export 'export.dart'; diff --git a/packages/scheduler_export/lib/src/export_controller.dart b/packages/scheduler_export/lib/src/export_controller.dart new file mode 100644 index 0000000..cbb8d41 --- /dev/null +++ b/packages/scheduler_export/lib/src/export_controller.dart @@ -0,0 +1,303 @@ +import 'dart:convert'; + +import 'package:scheduler_core/scheduler_core.dart' as core; +import 'package:scheduler_persistence/persistence.dart'; + +/// Export metadata shared with every writer. +final class ExportContext { + /// Creates an export context. + ExportContext({ + required this.ownerId, + required String timezone, + required this.version, + }) : timezone = _requiredTrimmed(timezone, 'timezone'); + + /// Owner scope being exported. + final core.OwnerId ownerId; + + /// Timezone id used to interpret local export presentation. + final String timezone; + + /// Export format/schema version. + final int version; +} + +/// Thrown when an export cannot complete. +final class ExportException implements Exception { + /// Creates an export exception with a diagnostic [message]. + const ExportException(this.message); + + /// Diagnostic text for logs and tests. + final String message; + + @override + String toString() => 'ExportException: $message'; +} + +/// Thrown when repository reads fail during export. +final class ExportRepositoryException extends ExportException { + /// Creates an exception wrapping a repository [failure]. + ExportRepositoryException(this.failure) + : super('Repository export read failed: ${failure.runtimeType}'); + + /// Repository failure returned by an adapter. + final RepositoryFailure failure; +} + +/// Writer interface for readable exports. +abstract interface class ExportWriter { + /// Start writing an export for [context]. + Future begin(ExportContext context); + + /// Write one task record. + Future writeTask(core.Task task); + + /// Finish writing the export. + Future end(); +} + +/// Factory signature for sink-backed export writers. +typedef ExportWriterFactory = ExportWriter Function(StringSink sink); + +/// Registry that resolves export format names to writer factories. +final class ExportWriterRegistry { + /// Creates a registry from [factories]. + ExportWriterRegistry({ + Map factories = + const {}, + }) : _factories = Map.fromEntries( + factories.entries.map( + (entry) => MapEntry(_normalizeFormat(entry.key), entry.value), + ), + ); + + final Map _factories; + + /// Default registry with JSON and CSV stub writers. + factory ExportWriterRegistry.defaults() { + return ExportWriterRegistry( + factories: { + 'json': JsonExportWriter.new, + 'csv': CsvExportWriter.new, + }, + ); + } + + /// Available format names. + Set get formats => Set.unmodifiable(_factories.keys); + + /// Register [factory] for [format]. + void register(String format, ExportWriterFactory factory) { + _factories[_normalizeFormat(format)] = factory; + } + + /// Create a writer for [format] using [sink]. + ExportWriter create(String format, StringSink sink) { + final normalized = _normalizeFormat(format); + final factory = _factories[normalized]; + if (factory == null) { + throw ExportException('Unknown export format: $format'); + } + return factory(sink); + } +} + +/// Coordinates repository reads and export writer calls. +final class ExportController { + /// Creates an export controller using [taskRepository]. + ExportController({ + required TaskRepository taskRepository, + ExportWriterRegistry? writerRegistry, + }) : _taskRepository = taskRepository, + _writerRegistry = writerRegistry ?? ExportWriterRegistry.defaults(); + + final TaskRepository _taskRepository; + final ExportWriterRegistry _writerRegistry; + + /// Create a writer for [format] and export owner-scoped tasks to [sink]. + Future exportTasksToSink({ + required ExportContext context, + required String format, + required StringSink sink, + int pageSize = 100, + }) { + final writer = _writerRegistry.create(format, sink); + return exportTasks( + context: context, + writer: writer, + pageSize: pageSize, + ); + } + + /// Export all owner-scoped tasks through [writer]. + Future exportTasks({ + required ExportContext context, + required ExportWriter writer, + int pageSize = 100, + }) async { + if (pageSize <= 0) { + throw ArgumentError.value(pageSize, 'pageSize', 'Must be positive.'); + } + + await writer.begin(context); + String? cursor; + do { + final result = await _taskRepository.findByOwner( + ownerId: context.ownerId, + page: core.PageRequest(cursor: cursor, limit: pageSize), + ); + if (result.isLeft) { + throw ExportRepositoryException(result.left); + } + final page = result.right; + for (final task in page.items) { + await writer.writeTask(task); + } + cursor = page.nextCursor; + } while (cursor != null); + await writer.end(); + } +} + +/// Minimal JSON export writer stub. +final class JsonExportWriter implements ExportWriter { + /// Creates a JSON writer that writes to the provided string sink. + JsonExportWriter(this._sink); + + final StringSink _sink; + ExportContext? _context; + bool _hasTask = false; + bool _ended = false; + + @override + Future begin(ExportContext context) async { + _assertNotEnded(); + _context = context; + _sink.write( + '{"context":${jsonEncode(_contextToJson(context))},"tasks":[', + ); + } + + @override + Future writeTask(core.Task task) async { + final context = _requireContext(); + if (_hasTask) { + _sink.write(','); + } + _sink.write(jsonEncode(core.TaskDocumentMapping.toDocument( + task, + ownerId: context.ownerId.value, + ))); + _hasTask = true; + } + + @override + Future end() async { + _requireContext(); + _assertNotEnded(); + _sink.write(']}'); + _ended = true; + } + + ExportContext _requireContext() { + final context = _context; + if (context == null) { + throw const ExportException('Writer has not started.'); + } + return context; + } + + void _assertNotEnded() { + if (_ended) { + throw const ExportException('Writer has already ended.'); + } + } +} + +/// Minimal CSV export writer stub. +final class CsvExportWriter implements ExportWriter { + /// Creates a CSV writer that writes to the provided string sink. + CsvExportWriter(this._sink); + + final StringSink _sink; + bool _started = false; + bool _ended = false; + + @override + Future begin(ExportContext context) async { + _assertNotEnded(); + _started = true; + _sink.writeln( + 'id,title,projectId,type,status,durationMinutes,scheduledStartUtc,' + 'scheduledEndUtc', + ); + } + + @override + Future writeTask(core.Task task) async { + if (!_started) { + throw const ExportException('Writer has not started.'); + } + _assertNotEnded(); + _sink.writeln([ + task.id, + task.title, + task.projectId, + core.PersistenceEnumMapping.encodeTaskType(task.type), + core.PersistenceEnumMapping.encodeTaskStatus(task.status), + task.durationMinutes?.toString() ?? '', + task.scheduledStart == null + ? '' + : core.PersistenceDateTimeConvention.toStoredString( + task.scheduledStart!, + ), + task.scheduledEnd == null + ? '' + : core.PersistenceDateTimeConvention.toStoredString( + task.scheduledEnd!, + ), + ].map(_csvCell).join(',')); + } + + @override + Future end() async { + if (!_started) { + throw const ExportException('Writer has not started.'); + } + _assertNotEnded(); + _ended = true; + } + + void _assertNotEnded() { + if (_ended) { + throw const ExportException('Writer has already ended.'); + } + } +} + +Map _contextToJson(ExportContext context) { + return { + 'ownerId': context.ownerId.value, + 'timezone': context.timezone, + 'version': context.version, + }; +} + +String _csvCell(String value) { + final needsQuotes = + value.contains(',') || value.contains('"') || value.contains('\n'); + final escaped = value.replaceAll('"', '""'); + return needsQuotes ? '"$escaped"' : escaped; +} + +String _normalizeFormat(String format) { + return _requiredTrimmed(format, 'format').toLowerCase(); +} + +String _requiredTrimmed(String value, String name) { + final trimmed = value.trim(); + if (trimmed.isEmpty) { + throw ArgumentError.value(value, name, 'Value must not be blank.'); + } + return trimmed; +} diff --git a/packages/scheduler_export/pubspec.yaml b/packages/scheduler_export/pubspec.yaml new file mode 100644 index 0000000..9ed4597 --- /dev/null +++ b/packages/scheduler_export/pubspec.yaml @@ -0,0 +1,17 @@ +name: scheduler_export +description: Export controller and writer contracts for the ADHD scheduler. +version: 0.1.0 +publish_to: none +resolution: workspace + +environment: + sdk: '>=3.6.0 <4.0.0' + +dependencies: + scheduler_core: any + scheduler_persistence: any + +dev_dependencies: + lints: ^5.0.0 + scheduler_persistence_memory: any + test: ^1.25.0 diff --git a/packages/scheduler_export/test/export_controller_test.dart b/packages/scheduler_export/test/export_controller_test.dart new file mode 100644 index 0000000..db58158 --- /dev/null +++ b/packages/scheduler_export/test/export_controller_test.dart @@ -0,0 +1,100 @@ +import 'dart:convert'; + +import 'package:scheduler_core/scheduler_core.dart' as core; +import 'package:scheduler_export/export.dart'; +import 'package:scheduler_persistence_memory/persistence_memory.dart'; +import 'package:test/test.dart'; + +void main() { + test('JSON writer exports owner-scoped tasks through controller', () async { + final ownerId = core.OwnerId('owner-a'); + final repository = InMemoryTaskRepository(); + await repository.insert(ownerId: ownerId, task: _task('task-b')); + await repository.insert(ownerId: ownerId, task: _task('task-a')); + await repository.insert( + ownerId: core.OwnerId('owner-b'), + task: _task('task-hidden'), + ); + final output = StringBuffer(); + + await ExportController(taskRepository: repository).exportTasksToSink( + context: ExportContext( + ownerId: ownerId, + timezone: 'America/Los_Angeles', + version: 1, + ), + format: 'json', + sink: output, + pageSize: 1, + ); + + final decoded = jsonDecode(output.toString()) as Map; + final context = decoded['context'] as Map; + final tasks = decoded['tasks'] as List; + + expect(context['ownerId'], 'owner-a'); + expect(context['timezone'], 'America/Los_Angeles'); + expect(tasks.map((task) => (task as Map)['_id']), + ['task-a', 'task-b']); + }); + + test('CSV writer exports task rows', () async { + final ownerId = core.OwnerId('owner-a'); + final repository = InMemoryTaskRepository(); + await repository.insert(ownerId: ownerId, task: _task('task-a')); + final output = StringBuffer(); + + await ExportController(taskRepository: repository).exportTasksToSink( + context: ExportContext( + ownerId: ownerId, + timezone: 'UTC', + version: 1, + ), + format: 'csv', + sink: output, + ); + + final lines = output.toString().trim().split('\n'); + expect(lines.first, startsWith('id,title,projectId,type,status')); + expect(lines.singleWhere((line) => line.startsWith('task-a,')), + contains('Task task-a')); + }); + + test('writer registry supports custom writer factories', () async { + final registry = ExportWriterRegistry(); + registry.register('custom', _CountingWriter.new); + final writer = registry.create(' CUSTOM ', StringBuffer()); + + expect(writer, isA<_CountingWriter>()); + }); +} + +core.Task _task(String id) { + return core.Task( + id: id, + title: 'Task $id', + projectId: 'project-a', + type: core.TaskType.flexible, + status: core.TaskStatus.backlog, + durationMinutes: 30, + createdAt: DateTime.utc(2026, 1), + updatedAt: DateTime.utc(2026, 1), + ); +} + +final class _CountingWriter implements ExportWriter { + _CountingWriter(StringSink sink); + + var count = 0; + + @override + Future begin(ExportContext context) async {} + + @override + Future writeTask(core.Task task) async { + count += 1; + } + + @override + Future end() async {} +} diff --git a/packages/scheduler_export_json/analysis_options.yaml b/packages/scheduler_export_json/analysis_options.yaml new file mode 100644 index 0000000..572dd23 --- /dev/null +++ b/packages/scheduler_export_json/analysis_options.yaml @@ -0,0 +1 @@ +include: package:lints/recommended.yaml diff --git a/packages/scheduler_export_json/bin/export.dart b/packages/scheduler_export_json/bin/export.dart new file mode 100644 index 0000000..ac2f133 --- /dev/null +++ b/packages/scheduler_export_json/bin/export.dart @@ -0,0 +1,49 @@ +import 'dart:io'; + +import 'package:scheduler_core/scheduler_core.dart' as core; +import 'package:scheduler_export/export.dart'; +import 'package:scheduler_export_json/export_json.dart'; +import 'package:scheduler_persistence_memory/persistence_memory.dart'; + +Future main(List arguments) async { + final format = _format(arguments); + if (format == null) { + stderr.writeln('Usage: dart run bin/export.dart --json|--csv'); + exitCode = 64; + return; + } + + final output = StringBuffer(); + final ownerId = + core.OwnerId(_option(arguments, '--owner') ?? 'default-owner'); + await ExportController( + taskRepository: InMemoryTaskRepository(), + writerRegistry: readableExportWriterRegistry(), + ).exportTasksToSink( + context: ExportContext( + ownerId: ownerId, + timezone: _option(arguments, '--timezone') ?? 'UTC', + version: 1, + ), + format: format, + sink: output, + ); + stdout.write(output.toString()); +} + +String? _format(List arguments) { + final wantsJson = arguments.contains('--json'); + final wantsCsv = arguments.contains('--csv'); + if (wantsJson == wantsCsv) { + return null; + } + return wantsJson ? 'json' : 'csv'; +} + +String? _option(List arguments, String name) { + final index = arguments.indexOf(name); + if (index == -1 || index + 1 >= arguments.length) { + return null; + } + return arguments[index + 1]; +} diff --git a/packages/scheduler_export_json/lib/export_json.dart b/packages/scheduler_export_json/lib/export_json.dart new file mode 100644 index 0000000..03bb5a4 --- /dev/null +++ b/packages/scheduler_export_json/lib/export_json.dart @@ -0,0 +1,4 @@ +/// JSON and CSV readable export writers. +library; + +export 'src/json_csv_writers.dart'; diff --git a/packages/scheduler_export_json/lib/scheduler_export_json.dart b/packages/scheduler_export_json/lib/scheduler_export_json.dart new file mode 100644 index 0000000..aa86deb --- /dev/null +++ b/packages/scheduler_export_json/lib/scheduler_export_json.dart @@ -0,0 +1,4 @@ +/// Package-name entry point for scheduler readable export writers. +library; + +export 'export_json.dart'; diff --git a/packages/scheduler_export_json/lib/src/json_csv_writers.dart b/packages/scheduler_export_json/lib/src/json_csv_writers.dart new file mode 100644 index 0000000..4edff3e --- /dev/null +++ b/packages/scheduler_export_json/lib/src/json_csv_writers.dart @@ -0,0 +1,146 @@ +import 'dart:convert'; + +import 'package:scheduler_core/scheduler_core.dart' as core; +import 'package:scheduler_export/export.dart'; + +/// Create the readable export writer registry used by export commands. +ExportWriterRegistry readableExportWriterRegistry() { + return ExportWriterRegistry( + factories: { + 'json': JsonExportWriter.new, + 'csv': CsvExportWriter.new, + }, + ); +} + +/// JSON export writer for scheduler tasks. +final class JsonExportWriter implements ExportWriter { + /// Creates a JSON writer that writes to the provided string sink. + JsonExportWriter(this._sink); + + final StringSink _sink; + ExportContext? _context; + bool _hasTask = false; + bool _ended = false; + + @override + Future begin(ExportContext context) async { + _assertNotEnded(); + _context = context; + _sink.write( + '{"context":${jsonEncode(_contextToJson(context))},"tasks":[', + ); + } + + @override + Future writeTask(core.Task task) async { + final context = _requireContext(); + _assertNotEnded(); + if (_hasTask) { + _sink.write(','); + } + _sink.write(jsonEncode(core.TaskDocumentMapping.toDocument( + task, + ownerId: context.ownerId.value, + ))); + _hasTask = true; + } + + @override + Future end() async { + _requireContext(); + _assertNotEnded(); + _sink.write(']}'); + _ended = true; + } + + ExportContext _requireContext() { + final context = _context; + if (context == null) { + throw const ExportException('Writer has not started.'); + } + return context; + } + + void _assertNotEnded() { + if (_ended) { + throw const ExportException('Writer has already ended.'); + } + } +} + +/// CSV export writer for scheduler tasks. +final class CsvExportWriter implements ExportWriter { + /// Creates a CSV writer that writes to the provided string sink. + CsvExportWriter(this._sink); + + final StringSink _sink; + bool _started = false; + bool _ended = false; + + @override + Future begin(ExportContext context) async { + _assertNotEnded(); + _started = true; + _sink.writeln( + 'id,title,projectId,type,status,durationMinutes,scheduledStartUtc,' + 'scheduledEndUtc', + ); + } + + @override + Future writeTask(core.Task task) async { + if (!_started) { + throw const ExportException('Writer has not started.'); + } + _assertNotEnded(); + _sink.writeln([ + task.id, + task.title, + task.projectId, + core.PersistenceEnumMapping.encodeTaskType(task.type), + core.PersistenceEnumMapping.encodeTaskStatus(task.status), + task.durationMinutes?.toString() ?? '', + task.scheduledStart == null + ? '' + : core.PersistenceDateTimeConvention.toStoredString( + task.scheduledStart!, + ), + task.scheduledEnd == null + ? '' + : core.PersistenceDateTimeConvention.toStoredString( + task.scheduledEnd!, + ), + ].map(_csvCell).join(',')); + } + + @override + Future end() async { + if (!_started) { + throw const ExportException('Writer has not started.'); + } + _assertNotEnded(); + _ended = true; + } + + void _assertNotEnded() { + if (_ended) { + throw const ExportException('Writer has already ended.'); + } + } +} + +Map _contextToJson(ExportContext context) { + return { + 'ownerId': context.ownerId.value, + 'timezone': context.timezone, + 'version': context.version, + }; +} + +String _csvCell(String value) { + final needsQuotes = + value.contains(',') || value.contains('"') || value.contains('\n'); + final escaped = value.replaceAll('"', '""'); + return needsQuotes ? '"$escaped"' : escaped; +} diff --git a/packages/scheduler_export_json/pubspec.yaml b/packages/scheduler_export_json/pubspec.yaml new file mode 100644 index 0000000..3c27b9c --- /dev/null +++ b/packages/scheduler_export_json/pubspec.yaml @@ -0,0 +1,17 @@ +name: scheduler_export_json +description: JSON and CSV readable export writers for the ADHD scheduler. +version: 0.1.0 +publish_to: none +resolution: workspace + +environment: + sdk: '>=3.6.0 <4.0.0' + +dependencies: + scheduler_core: any + scheduler_export: any + scheduler_persistence_memory: any + +dev_dependencies: + lints: ^5.0.0 + test: ^1.25.0 diff --git a/packages/scheduler_export_json/test/export_json_test.dart b/packages/scheduler_export_json/test/export_json_test.dart new file mode 100644 index 0000000..0ef8c55 --- /dev/null +++ b/packages/scheduler_export_json/test/export_json_test.dart @@ -0,0 +1,94 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:scheduler_core/scheduler_core.dart' as core; +import 'package:scheduler_export/export.dart'; +import 'package:scheduler_export_json/export_json.dart'; +import 'package:scheduler_persistence_memory/persistence_memory.dart'; +import 'package:test/test.dart'; + +void main() { + test('readable registry exports valid JSON through ExportController', + () async { + final ownerId = core.OwnerId('owner-a'); + final repository = InMemoryTaskRepository(); + await repository.insert(ownerId: ownerId, task: _task('task-a')); + final output = StringBuffer(); + + await ExportController( + taskRepository: repository, + writerRegistry: readableExportWriterRegistry(), + ).exportTasksToSink( + context: ExportContext(ownerId: ownerId, timezone: 'UTC', version: 1), + format: 'json', + sink: output, + ); + + final decoded = jsonDecode(output.toString()) as Map; + expect(decoded['context'], isA>()); + final tasks = decoded['tasks'] as List; + expect(tasks, hasLength(1)); + expect((tasks.single as Map)['_id'], 'task-a'); + }); + + test('CSV writer quotes cells with commas and quotes', () async { + final ownerId = core.OwnerId('owner-a'); + final repository = InMemoryTaskRepository(); + await repository.insert( + ownerId: ownerId, + task: _task('task-a', title: 'Task, "quoted"'), + ); + final output = StringBuffer(); + + await ExportController( + taskRepository: repository, + writerRegistry: readableExportWriterRegistry(), + ).exportTasksToSink( + context: ExportContext(ownerId: ownerId, timezone: 'UTC', version: 1), + format: 'csv', + sink: output, + ); + + expect(output.toString(), contains('"Task, ""quoted"""')); + }); + + test('export command --json outputs valid JSON', () async { + final result = await Process.run( + Platform.resolvedExecutable, + ['run', 'bin/export.dart', '--json'], + workingDirectory: _packageDirectory().path, + ); + + expect(result.exitCode, 0, reason: result.stderr as String?); + final decoded = jsonDecode(result.stdout as String) as Map; + expect(decoded['tasks'], isA>()); + }, timeout: const Timeout(Duration(seconds: 20))); +} + +Directory _packageDirectory() { + final currentPubspec = + File('${Directory.current.path}${Platform.pathSeparator}pubspec.yaml'); + if (currentPubspec.existsSync() && + currentPubspec + .readAsStringSync() + .contains('name: scheduler_export_json')) { + return Directory.current; + } + return Directory( + '${Directory.current.path}${Platform.pathSeparator}packages' + '${Platform.pathSeparator}scheduler_export_json', + ); +} + +core.Task _task(String id, {String? title}) { + return core.Task( + id: id, + title: title ?? 'Task $id', + projectId: 'project-a', + type: core.TaskType.flexible, + status: core.TaskStatus.backlog, + durationMinutes: 30, + createdAt: DateTime.utc(2026, 1), + updatedAt: DateTime.utc(2026, 1), + ); +} diff --git a/packages/scheduler_integration_tests/analysis_options.yaml b/packages/scheduler_integration_tests/analysis_options.yaml new file mode 100644 index 0000000..572dd23 --- /dev/null +++ b/packages/scheduler_integration_tests/analysis_options.yaml @@ -0,0 +1 @@ +include: package:lints/recommended.yaml diff --git a/packages/scheduler_integration_tests/integration/scheduler_flow_test.dart b/packages/scheduler_integration_tests/integration/scheduler_flow_test.dart new file mode 100644 index 0000000..f5768e7 --- /dev/null +++ b/packages/scheduler_integration_tests/integration/scheduler_flow_test.dart @@ -0,0 +1,118 @@ +import 'package:drift/native.dart'; +import 'package:scheduler_core/scheduler_core.dart' as core; +import 'package:scheduler_notifications/notifications.dart'; +import 'package:scheduler_persistence_memory/persistence_memory.dart'; +import 'package:scheduler_persistence_sqlite/sqlite.dart'; +import 'package:test/test.dart'; + +void main() { + test('scheduler flow schedules, notifies, persists, and reloads', () async { + final runtime = Stopwatch()..start(); + addTearDown(() { + runtime.stop(); + expect(runtime.elapsed, lessThan(const Duration(seconds: 2))); + }); + final ownerId = core.OwnerId('owner-a'); + final now = DateTime.utc(2026, 6, 26, 8); + final window = core.SchedulingWindow( + start: DateTime.utc(2026, 6, 26, 9), + end: DateTime.utc(2026, 6, 26, 10, 30), + ); + final lockedInterval = core.TimeInterval( + start: DateTime.utc(2026, 6, 26, 9), + end: DateTime.utc(2026, 6, 26, 9, 15), + label: 'Protected setup time', + ); + final backlogTask = core.Task( + id: 'backlog-a', + title: 'Write planning note', + projectId: 'project-a', + type: core.TaskType.flexible, + status: core.TaskStatus.backlog, + durationMinutes: 15, + createdAt: now, + updatedAt: now, + ); + final plannedTask = core.Task( + id: 'planned-a', + title: 'Existing focus block', + projectId: 'project-a', + type: core.TaskType.flexible, + status: core.TaskStatus.planned, + durationMinutes: 30, + scheduledStart: DateTime.utc(2026, 6, 26, 9, 30), + scheduledEnd: DateTime.utc(2026, 6, 26, 10), + createdAt: now, + updatedAt: now, + ); + + final memoryTasks = InMemoryTaskRepository(); + expect( + (await memoryTasks.insert(ownerId: ownerId, task: backlogTask)).isRight, + isTrue, + ); + expect( + (await memoryTasks.insert(ownerId: ownerId, task: plannedTask)).isRight, + isTrue, + ); + + final loaded = await memoryTasks.findByOwner(ownerId: ownerId); + expect(loaded.isRight, isTrue); + final result = + const core.SchedulingEngine().insertBacklogTaskIntoNextAvailableSlot( + input: core.SchedulingInput( + tasks: loaded.right.items, + window: window, + lockedIntervals: [lockedInterval], + ), + taskId: backlogTask.id, + updatedAt: now, + ); + + expect(result.outcomeCode, core.SchedulingOutcomeCode.success); + final scheduledBacklog = _taskById(result.tasks, backlogTask.id); + expect(scheduledBacklog.status, core.TaskStatus.planned); + expect(scheduledBacklog.scheduledStart, lockedInterval.end); + expect( + scheduledBacklog.scheduledEnd, + DateTime.utc(2026, 6, 26, 9, 30), + ); + + final notifications = FakeNotificationAdapter(); + addTearDown(notifications.close); + await notifications.schedule(NotificationRequest( + id: 'task-${scheduledBacklog.id}-start', + title: scheduledBacklog.title, + body: 'Scheduled task is ready.', + scheduledDateTimeUtc: scheduledBacklog.scheduledStart!, + payloadJson: '{"taskId":"${scheduledBacklog.id}"}', + )); + expect( + notifications.scheduledRequests.keys, + contains('task-backlog-a-start'), + ); + + final db = SchedulerDb(NativeDatabase.memory()); + addTearDown(db.close); + final sqliteTasks = SqliteTaskRepository(db); + for (final task in result.tasks) { + final inserted = await sqliteTasks.insert(ownerId: ownerId, task: task); + expect(inserted.isRight, isTrue); + } + + final reloaded = await sqliteTasks.findById( + ownerId: ownerId, + taskId: backlogTask.id, + ); + expect(reloaded.isRight, isTrue); + final persistedBacklog = reloaded.right; + expect(persistedBacklog, isNotNull); + expect(persistedBacklog!.scheduledStart, scheduledBacklog.scheduledStart); + expect(persistedBacklog.scheduledEnd, scheduledBacklog.scheduledEnd); + expect(persistedBacklog.status, core.TaskStatus.planned); + }); +} + +core.Task _taskById(Iterable tasks, String id) { + return tasks.singleWhere((task) => task.id == id); +} diff --git a/packages/scheduler_integration_tests/pubspec.yaml b/packages/scheduler_integration_tests/pubspec.yaml new file mode 100644 index 0000000..50207eb --- /dev/null +++ b/packages/scheduler_integration_tests/pubspec.yaml @@ -0,0 +1,20 @@ +name: scheduler_integration_tests +description: End-to-end scheduler integration tests. +version: 0.1.0 +publish_to: none +resolution: workspace + +environment: + sdk: '>=3.6.0 <4.0.0' + +dependencies: + drift: ^2.20.0 + scheduler_core: any + scheduler_notifications: any + scheduler_persistence: any + scheduler_persistence_memory: any + scheduler_persistence_sqlite: any + +dev_dependencies: + lints: ^5.0.0 + test: ^1.25.0 diff --git a/packages/scheduler_integration_tests/test/scheduler_flow_test.dart b/packages/scheduler_integration_tests/test/scheduler_flow_test.dart new file mode 100644 index 0000000..13cc182 --- /dev/null +++ b/packages/scheduler_integration_tests/test/scheduler_flow_test.dart @@ -0,0 +1,5 @@ +import '../integration/scheduler_flow_test.dart' as scheduler_flow_test; + +void main() { + scheduler_flow_test.main(); +} diff --git a/packages/scheduler_notifications/analysis_options.yaml b/packages/scheduler_notifications/analysis_options.yaml new file mode 100644 index 0000000..572dd23 --- /dev/null +++ b/packages/scheduler_notifications/analysis_options.yaml @@ -0,0 +1 @@ +include: package:lints/recommended.yaml diff --git a/packages/scheduler_notifications/lib/notifications.dart b/packages/scheduler_notifications/lib/notifications.dart new file mode 100644 index 0000000..168d793 --- /dev/null +++ b/packages/scheduler_notifications/lib/notifications.dart @@ -0,0 +1,4 @@ +/// Notification contracts for scheduler reminder delivery. +library; + +export 'src/notification_adapter.dart'; diff --git a/packages/scheduler_notifications/lib/scheduler_notifications.dart b/packages/scheduler_notifications/lib/scheduler_notifications.dart new file mode 100644 index 0000000..5f7f907 --- /dev/null +++ b/packages/scheduler_notifications/lib/scheduler_notifications.dart @@ -0,0 +1,4 @@ +/// Package-name entry point for scheduler notification contracts. +library; + +export 'notifications.dart'; diff --git a/packages/scheduler_notifications/lib/src/notification_adapter.dart b/packages/scheduler_notifications/lib/src/notification_adapter.dart new file mode 100644 index 0000000..ad1d492 --- /dev/null +++ b/packages/scheduler_notifications/lib/src/notification_adapter.dart @@ -0,0 +1,150 @@ +import 'dart:async'; + +/// Request to schedule one notification through a platform adapter. +sealed class NotificationRequest { + /// Creates a notification request. + factory NotificationRequest({ + required String id, + required String title, + required String body, + required DateTime scheduledDateTimeUtc, + String payloadJson = '{}', + }) { + return _NotificationRequest( + id: _requiredTrimmed(id, 'id'), + title: _requiredTrimmed(title, 'title'), + body: body, + scheduledDateTimeUtc: scheduledDateTimeUtc.toUtc(), + payloadJson: payloadJson, + ); + } + + const NotificationRequest._({ + required this.id, + required this.title, + required this.body, + required this.scheduledDateTimeUtc, + required this.payloadJson, + }); + + /// Stable notification id used for scheduling and cancellation. + final String id; + + /// User-visible notification title. + final String title; + + /// User-visible notification body. + final String body; + + /// UTC instant when the notification should be delivered. + final DateTime scheduledDateTimeUtc; + + /// Adapter-neutral JSON payload string. + final String payloadJson; +} + +final class _NotificationRequest extends NotificationRequest { + const _NotificationRequest({ + required super.id, + required super.title, + required super.body, + required super.scheduledDateTimeUtc, + required super.payloadJson, + }) : super._(); +} + +/// User feedback category emitted by a notification adapter. +enum NotificationFeedbackType { + /// The notification was opened or clicked. + clicked, + + /// The notification was dismissed. + dismissed, + + /// A notification action was selected. + action, +} + +/// Feedback event emitted by a notification adapter. +final class NotificationFeedback { + /// Creates a notification feedback event. + NotificationFeedback({ + required String id, + required this.type, + this.actionId, + this.payloadJson = '{}', + }) : id = _requiredTrimmed(id, 'id'); + + /// Stable notification id associated with the feedback. + final String id; + + /// Type of user feedback. + final NotificationFeedbackType type; + + /// Optional action id for [NotificationFeedbackType.action]. + final String? actionId; + + /// Adapter-neutral JSON payload string. + final String payloadJson; +} + +/// Platform-neutral notification adapter boundary. +abstract interface class NotificationAdapter { + /// Schedule [request] for future delivery. + Future schedule(NotificationRequest request); + + /// Cancel a pending notification by stable [id]. + Future cancel(String id); + + /// Stream of user feedback events from delivered notifications. + Stream get feedback; +} + +/// Test fake for notification adapter behavior. +final class FakeNotificationAdapter implements NotificationAdapter { + final StreamController _feedbackController = + StreamController.broadcast(); + final Map _scheduled = + {}; + final List _cancelledIds = []; + + /// Snapshot of currently scheduled requests by id. + Map get scheduledRequests => + Map.unmodifiable(_scheduled); + + /// Snapshot of cancellation ids in call order. + List get cancelledIds => List.unmodifiable(_cancelledIds); + + @override + Stream get feedback => _feedbackController.stream; + + @override + Future schedule(NotificationRequest request) async { + _scheduled[request.id] = request; + } + + @override + Future cancel(String id) async { + final trimmed = _requiredTrimmed(id, 'id'); + _scheduled.remove(trimmed); + _cancelledIds.add(trimmed); + } + + /// Emit [event] to feedback listeners. + void emitFeedback(NotificationFeedback event) { + _feedbackController.add(event); + } + + /// Release stream resources held by this fake. + Future close() { + return _feedbackController.close(); + } +} + +String _requiredTrimmed(String value, String name) { + final trimmed = value.trim(); + if (trimmed.isEmpty) { + throw ArgumentError.value(value, name, 'Value must not be blank.'); + } + return trimmed; +} diff --git a/packages/scheduler_notifications/pubspec.yaml b/packages/scheduler_notifications/pubspec.yaml new file mode 100644 index 0000000..e9d29dd --- /dev/null +++ b/packages/scheduler_notifications/pubspec.yaml @@ -0,0 +1,12 @@ +name: scheduler_notifications +description: Notification adapter contracts for the ADHD scheduler. +version: 0.1.0 +publish_to: none +resolution: workspace + +environment: + sdk: '>=3.6.0 <4.0.0' + +dev_dependencies: + lints: ^5.0.0 + test: ^1.25.0 diff --git a/packages/scheduler_notifications/test/fake_adapter_test.dart b/packages/scheduler_notifications/test/fake_adapter_test.dart new file mode 100644 index 0000000..a8954d1 --- /dev/null +++ b/packages/scheduler_notifications/test/fake_adapter_test.dart @@ -0,0 +1,60 @@ +import 'package:scheduler_notifications/notifications.dart'; +import 'package:test/test.dart'; + +import 'notification_adapter_contract.dart'; + +void main() { + runNotificationAdapterContractTests('fake', () { + final adapter = FakeNotificationAdapter(); + return NotificationAdapterContractHarness( + adapter: adapter, + scheduledRequests: () => adapter.scheduledRequests.values.toList(), + cancelledIds: () => adapter.cancelledIds, + dispose: adapter.close, + ); + }); + + test('fake adapter stores schedules and cancellations', () async { + final adapter = FakeNotificationAdapter(); + addTearDown(adapter.close); + final request = NotificationRequest( + id: ' reminder-1 ', + title: 'Start task', + body: 'Time to begin.', + scheduledDateTimeUtc: DateTime.utc(2026, 1, 1, 9), + payloadJson: '{"taskId":"task-1"}', + ); + + await adapter.schedule(request); + expect(adapter.scheduledRequests.keys, ['reminder-1']); + expect( + adapter.scheduledRequests['reminder-1']!.scheduledDateTimeUtc.isUtc, + isTrue, + ); + + await adapter.cancel(' reminder-1 '); + expect(adapter.scheduledRequests, isEmpty); + expect(adapter.cancelledIds, ['reminder-1']); + }); + + test('fake adapter emits feedback events', () async { + final adapter = FakeNotificationAdapter(); + addTearDown(adapter.close); + final event = NotificationFeedback( + id: 'reminder-1', + type: NotificationFeedbackType.clicked, + payloadJson: '{"taskId":"task-1"}', + ); + + expectLater( + adapter.feedback, + emits(predicate((feedback) { + return feedback.id == 'reminder-1' && + feedback.type == NotificationFeedbackType.clicked && + feedback.payloadJson == '{"taskId":"task-1"}'; + })), + ); + + adapter.emitFeedback(event); + }); +} diff --git a/packages/scheduler_notifications/test/notification_adapter_contract.dart b/packages/scheduler_notifications/test/notification_adapter_contract.dart new file mode 100644 index 0000000..4d48fe6 --- /dev/null +++ b/packages/scheduler_notifications/test/notification_adapter_contract.dart @@ -0,0 +1,71 @@ +import 'package:scheduler_notifications/notifications.dart'; +import 'package:test/test.dart'; + +/// Test harness for [NotificationAdapter] contract checks. +final class NotificationAdapterContractHarness { + /// Creates a harness around one adapter instance. + const NotificationAdapterContractHarness({ + required this.adapter, + required this.scheduledRequests, + required this.cancelledIds, + this.dispose, + }); + + /// Adapter under test. + final NotificationAdapter adapter; + + /// Snapshot of requests delivered through [adapter]. + final List Function() scheduledRequests; + + /// Snapshot of ids cancelled through [adapter]. + final List Function() cancelledIds; + + /// Optional cleanup callback. + final Future Function()? dispose; +} + +/// Runs shared notification adapter behavior checks. +void runNotificationAdapterContractTests( + String name, + NotificationAdapterContractHarness Function() createHarness, +) { + group('$name notification adapter contract', () { + test('schedules normalized notification requests', () async { + final harness = createHarness(); + final dispose = harness.dispose; + if (dispose != null) { + addTearDown(dispose); + } + + await harness.adapter.schedule(_request(' reminder-1 ')); + + expect(harness.scheduledRequests().map((request) => request.id), [ + 'reminder-1', + ]); + expect(harness.scheduledRequests().single.scheduledDateTimeUtc.isUtc, + isTrue); + }); + + test('cancels normalized ids', () async { + final harness = createHarness(); + final dispose = harness.dispose; + if (dispose != null) { + addTearDown(dispose); + } + + await harness.adapter.cancel(' reminder-1 '); + + expect(harness.cancelledIds(), ['reminder-1']); + }); + }); +} + +NotificationRequest _request(String id) { + return NotificationRequest( + id: id, + title: 'Start task', + body: 'Time to begin.', + scheduledDateTimeUtc: DateTime.utc(2026, 1, 1, 9), + payloadJson: '{"taskId":"task-1"}', + ); +} diff --git a/packages/scheduler_notifications_desktop/analysis_options.yaml b/packages/scheduler_notifications_desktop/analysis_options.yaml new file mode 100644 index 0000000..572dd23 --- /dev/null +++ b/packages/scheduler_notifications_desktop/analysis_options.yaml @@ -0,0 +1 @@ +include: package:lints/recommended.yaml diff --git a/packages/scheduler_notifications_desktop/lib/desktop_notifications.dart b/packages/scheduler_notifications_desktop/lib/desktop_notifications.dart new file mode 100644 index 0000000..34b3ec4 --- /dev/null +++ b/packages/scheduler_notifications_desktop/lib/desktop_notifications.dart @@ -0,0 +1,4 @@ +/// Desktop notification adapter for scheduler reminder delivery. +library; + +export 'src/desktop_notification_adapter.dart'; diff --git a/packages/scheduler_notifications_desktop/lib/scheduler_notifications_desktop.dart b/packages/scheduler_notifications_desktop/lib/scheduler_notifications_desktop.dart new file mode 100644 index 0000000..ab4603d --- /dev/null +++ b/packages/scheduler_notifications_desktop/lib/scheduler_notifications_desktop.dart @@ -0,0 +1,4 @@ +/// Package-name entry point for desktop notification adapters. +library; + +export 'desktop_notifications.dart'; diff --git a/packages/scheduler_notifications_desktop/lib/src/desktop_notification_adapter.dart b/packages/scheduler_notifications_desktop/lib/src/desktop_notification_adapter.dart new file mode 100644 index 0000000..9f1f30b --- /dev/null +++ b/packages/scheduler_notifications_desktop/lib/src/desktop_notification_adapter.dart @@ -0,0 +1,2 @@ +export 'desktop_notification_adapter_stub.dart' + if (dart.library.io) 'desktop_notification_adapter_io.dart'; diff --git a/packages/scheduler_notifications_desktop/lib/src/desktop_notification_adapter_io.dart b/packages/scheduler_notifications_desktop/lib/src/desktop_notification_adapter_io.dart new file mode 100644 index 0000000..b8ed267 --- /dev/null +++ b/packages/scheduler_notifications_desktop/lib/src/desktop_notification_adapter_io.dart @@ -0,0 +1,228 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:scheduler_notifications/notifications.dart'; + +/// Supported desktop platform categories. +enum DesktopNotificationPlatform { + /// Linux desktop. + linux, + + /// macOS desktop. + macos, + + /// Windows desktop. + windows, + + /// Unsupported or unknown platform. + unsupported, +} + +/// Logging callback used by fallback notification backends. +typedef DesktopNotificationLogSink = void Function(String line); + +/// Process runner used by command-backed notification backends. +typedef DesktopProcessRunner = Future Function( + String executable, + List arguments, +); + +/// Platform-neutral backend used by [DesktopNotificationAdapter]. +abstract interface class DesktopNotificationBackend { + /// Whether this backend has native notification support. + bool get isSupported; + + /// Deliver [request]. + Future deliver(NotificationRequest request); + + /// Cancel a pending notification by [id], when supported. + Future cancel(String id); +} + +/// Notification adapter that delegates to a desktop backend. +final class DesktopNotificationAdapter implements NotificationAdapter { + /// Creates an adapter backed by [backend]. + DesktopNotificationAdapter({required DesktopNotificationBackend backend}) + : _backend = backend; + + final DesktopNotificationBackend _backend; + + /// Creates the best available desktop adapter for the current platform. + factory DesktopNotificationAdapter.defaultInstance({ + DesktopNotificationPlatform? platform, + DesktopProcessRunner? processRunner, + DesktopNotificationLogSink? logSink, + }) { + return DesktopNotificationAdapter( + backend: createDefaultDesktopNotificationBackend( + platform: platform, + processRunner: processRunner, + logSink: logSink, + ), + ); + } + + @override + Stream get feedback => const Stream.empty(); + + @override + Future schedule(NotificationRequest request) { + return _backend.deliver(request); + } + + @override + Future cancel(String id) { + return _backend.cancel(id.trim()); + } +} + +/// Create the best default desktop notification backend. +DesktopNotificationBackend createDefaultDesktopNotificationBackend({ + DesktopNotificationPlatform? platform, + DesktopProcessRunner? processRunner, + DesktopNotificationLogSink? logSink, +}) { + final resolvedPlatform = platform ?? _currentPlatform(); + final runner = processRunner ?? Process.run; + final fallback = StdoutNotificationBackend( + logSink: logSink ?? stdout.writeln, + platform: resolvedPlatform, + ); + + return switch (resolvedPlatform) { + DesktopNotificationPlatform.linux => LinuxNotifySendBackend( + processRunner: runner, + fallback: fallback, + ), + DesktopNotificationPlatform.macos => MacOsNotificationBackend( + processRunner: runner, + fallback: fallback, + ), + DesktopNotificationPlatform.windows => fallback, + DesktopNotificationPlatform.unsupported => fallback, + }; +} + +/// Linux backend using `notify-send`. +final class LinuxNotifySendBackend implements DesktopNotificationBackend { + /// Creates a Linux command-backed backend. + LinuxNotifySendBackend({ + required DesktopProcessRunner processRunner, + required DesktopNotificationBackend fallback, + }) : _processRunner = processRunner, + _fallback = fallback; + + final DesktopProcessRunner _processRunner; + final DesktopNotificationBackend _fallback; + + @override + bool get isSupported => true; + + @override + Future deliver(NotificationRequest request) async { + await _runOrFallback( + request, + () => _processRunner('notify-send', [ + '--app-name=ADHD Scheduler', + request.title, + request.body, + ]), + ); + } + + @override + Future cancel(String id) async { + await _fallback.cancel(id); + } + + Future _runOrFallback( + NotificationRequest request, + Future Function() run, + ) async { + try { + final result = await run(); + if (result.exitCode != 0) { + await _fallback.deliver(request); + } + } on ProcessException { + await _fallback.deliver(request); + } + } +} + +/// macOS backend using `osascript`. +final class MacOsNotificationBackend implements DesktopNotificationBackend { + /// Creates a macOS command-backed backend. + MacOsNotificationBackend({ + required DesktopProcessRunner processRunner, + required DesktopNotificationBackend fallback, + }) : _processRunner = processRunner, + _fallback = fallback; + + final DesktopProcessRunner _processRunner; + final DesktopNotificationBackend _fallback; + + @override + bool get isSupported => true; + + @override + Future deliver(NotificationRequest request) async { + final script = 'display notification ${_appleScriptString(request.body)} ' + 'with title ${_appleScriptString(request.title)}'; + try { + final result = await _processRunner('osascript', ['-e', script]); + if (result.exitCode != 0) { + await _fallback.deliver(request); + } + } on ProcessException { + await _fallback.deliver(request); + } + } + + @override + Future cancel(String id) async { + await _fallback.cancel(id); + } +} + +/// Fallback backend that writes notification requests to stdout/logs. +final class StdoutNotificationBackend implements DesktopNotificationBackend { + /// Creates a stdout fallback backend. + StdoutNotificationBackend({ + required DesktopNotificationLogSink logSink, + required this.platform, + }) : _logSink = logSink; + + final DesktopNotificationLogSink _logSink; + + /// Platform this fallback represents. + final DesktopNotificationPlatform platform; + + @override + bool get isSupported => false; + + @override + Future deliver(NotificationRequest request) async { + _logSink( + 'notification[$platform] ${request.id}: ${request.title} - ' + '${request.body}', + ); + } + + @override + Future cancel(String id) async { + _logSink('notification[$platform] cancel: ${id.trim()}'); + } +} + +DesktopNotificationPlatform _currentPlatform() { + if (Platform.isLinux) return DesktopNotificationPlatform.linux; + if (Platform.isMacOS) return DesktopNotificationPlatform.macos; + if (Platform.isWindows) return DesktopNotificationPlatform.windows; + return DesktopNotificationPlatform.unsupported; +} + +String _appleScriptString(String value) { + return jsonEncode(value); +} diff --git a/packages/scheduler_notifications_desktop/lib/src/desktop_notification_adapter_stub.dart b/packages/scheduler_notifications_desktop/lib/src/desktop_notification_adapter_stub.dart new file mode 100644 index 0000000..3e653ad --- /dev/null +++ b/packages/scheduler_notifications_desktop/lib/src/desktop_notification_adapter_stub.dart @@ -0,0 +1,117 @@ +import 'dart:async'; + +import 'package:scheduler_notifications/notifications.dart'; + +/// Supported desktop platform categories. +enum DesktopNotificationPlatform { + /// Linux desktop. + linux, + + /// macOS desktop. + macos, + + /// Windows desktop. + windows, + + /// Unsupported or unknown platform. + unsupported, +} + +/// Logging callback used by fallback notification backends. +typedef DesktopNotificationLogSink = void Function(String line); + +/// Platform-neutral backend used by [DesktopNotificationAdapter]. +abstract interface class DesktopNotificationBackend { + /// Whether this backend has native notification support. + bool get isSupported; + + /// Deliver [request]. + Future deliver(NotificationRequest request); + + /// Cancel a pending notification by [id], when supported. + Future cancel(String id); +} + +/// Notification adapter that delegates to a desktop backend. +final class DesktopNotificationAdapter implements NotificationAdapter { + /// Creates an adapter backed by [backend]. + DesktopNotificationAdapter({required DesktopNotificationBackend backend}) + : _backend = backend; + + final DesktopNotificationBackend _backend; + + /// Creates the best available desktop adapter for the current platform. + factory DesktopNotificationAdapter.defaultInstance({ + DesktopNotificationPlatform? platform, + Object? processRunner, + DesktopNotificationLogSink? logSink, + }) { + return DesktopNotificationAdapter( + backend: createDefaultDesktopNotificationBackend( + platform: platform, + processRunner: processRunner, + logSink: logSink, + ), + ); + } + + @override + Stream get feedback => const Stream.empty(); + + @override + Future schedule(NotificationRequest request) { + return _backend.deliver(request); + } + + @override + Future cancel(String id) { + return _backend.cancel(id.trim()); + } +} + +/// Create the best default desktop notification backend. +DesktopNotificationBackend createDefaultDesktopNotificationBackend({ + DesktopNotificationPlatform? platform, + Object? processRunner, + DesktopNotificationLogSink? logSink, +}) { + return StdoutNotificationBackend( + logSink: logSink ?? _defaultLogSink, + platform: platform ?? DesktopNotificationPlatform.unsupported, + ); +} + +/// Fallback backend that writes notification requests to stdout/logs. +final class StdoutNotificationBackend implements DesktopNotificationBackend { + /// Creates a stdout fallback backend. + StdoutNotificationBackend({ + required DesktopNotificationLogSink logSink, + required this.platform, + }) : _logSink = logSink; + + final DesktopNotificationLogSink _logSink; + + /// Platform this fallback represents. + final DesktopNotificationPlatform platform; + + @override + bool get isSupported => false; + + @override + Future deliver(NotificationRequest request) async { + _logSink( + 'notification[$platform] ${request.id}: ${request.title} - ' + '${request.body}', + ); + } + + @override + Future cancel(String id) async { + _logSink('notification[$platform] cancel: ${id.trim()}'); + } +} + +void _defaultLogSink(String line) { + // ignore: avoid_print + print(line); +} diff --git a/packages/scheduler_notifications_desktop/pubspec.yaml b/packages/scheduler_notifications_desktop/pubspec.yaml new file mode 100644 index 0000000..71c23f7 --- /dev/null +++ b/packages/scheduler_notifications_desktop/pubspec.yaml @@ -0,0 +1,15 @@ +name: scheduler_notifications_desktop +description: Desktop notification adapter for the ADHD scheduler. +version: 0.1.0 +publish_to: none +resolution: workspace + +environment: + sdk: '>=3.6.0 <4.0.0' + +dependencies: + scheduler_notifications: any + +dev_dependencies: + lints: ^5.0.0 + test: ^1.25.0 diff --git a/packages/scheduler_notifications_desktop/test/desktop_notification_adapter_test.dart b/packages/scheduler_notifications_desktop/test/desktop_notification_adapter_test.dart new file mode 100644 index 0000000..b3bf2df --- /dev/null +++ b/packages/scheduler_notifications_desktop/test/desktop_notification_adapter_test.dart @@ -0,0 +1,152 @@ +import 'dart:io'; + +import 'package:scheduler_notifications/notifications.dart'; +import 'package:test/test.dart'; + +import '../../scheduler_notifications/test/notification_adapter_contract.dart'; + +// ignore: avoid_relative_lib_imports +import '../lib/desktop_notifications.dart'; + +void main() { + runNotificationAdapterContractTests('desktop', () { + final backend = _RecordingBackend(); + return NotificationAdapterContractHarness( + adapter: DesktopNotificationAdapter(backend: backend), + scheduledRequests: () => backend.delivered, + cancelledIds: () => backend.cancelled, + ); + }); + + test('desktop adapter delegates schedule and cancel to backend', () async { + final backend = _RecordingBackend(); + final adapter = DesktopNotificationAdapter(backend: backend); + final request = _request('reminder-1'); + + await adapter.schedule(request); + await adapter.cancel(' reminder-1 '); + + expect(backend.delivered, [request]); + expect(backend.cancelled, ['reminder-1']); + expect(adapter, isA()); + }); + + test('linux backend invokes notify-send', () async { + final calls = <_ProcessCall>[]; + final logs = []; + final backend = createDefaultDesktopNotificationBackend( + platform: DesktopNotificationPlatform.linux, + processRunner: (executable, arguments) async { + calls.add(_ProcessCall(executable, arguments)); + return ProcessResult(1, 0, '', ''); + }, + logSink: logs.add, + ); + + await backend.deliver(_request('reminder-1')); + + expect(backend.isSupported, isTrue); + expect(calls.single.executable, 'notify-send'); + expect(calls.single.arguments, contains('Start task')); + expect(logs, isEmpty); + }); + + test('macOS backend invokes osascript', () async { + final calls = <_ProcessCall>[]; + final backend = createDefaultDesktopNotificationBackend( + platform: DesktopNotificationPlatform.macos, + processRunner: (executable, arguments) async { + calls.add(_ProcessCall(executable, arguments)); + return ProcessResult(1, 0, '', ''); + }, + ); + + await backend.deliver(_request('reminder-1')); + + expect(backend.isSupported, isTrue); + expect(calls.single.executable, 'osascript'); + expect(calls.single.arguments, contains('-e')); + }); + + test('unsupported and Windows platforms fall back to log output', () async { + final logs = []; + final windows = createDefaultDesktopNotificationBackend( + platform: DesktopNotificationPlatform.windows, + logSink: logs.add, + ); + final unsupported = createDefaultDesktopNotificationBackend( + platform: DesktopNotificationPlatform.unsupported, + logSink: logs.add, + ); + + await windows.deliver(_request('windows-reminder')); + await unsupported.cancel(' unsupported-reminder '); + + expect(windows.isSupported, isFalse); + expect(unsupported.isSupported, isFalse); + expect(logs.first, contains('windows-reminder')); + expect(logs.last, contains('unsupported-reminder')); + }); + + test('fake adapter can stand in for scheduling workflows', () async { + final adapter = FakeNotificationAdapter(); + addTearDown(adapter.close); + await _scheduleAndCancelReminder(adapter, _request('reminder-1')); + + expect(adapter.scheduledRequests, isEmpty); + expect(adapter.cancelledIds, ['reminder-1']); + }); + + test('default desktop adapter can be created on supported test platforms', + () { + if (!Platform.isLinux && !Platform.isMacOS && !Platform.isWindows) { + markTestSkipped( + 'Desktop notifications are unsupported on this platform.'); + } + + expect(DesktopNotificationAdapter.defaultInstance(), + isA()); + }); +} + +Future _scheduleAndCancelReminder( + NotificationAdapter adapter, + NotificationRequest request, +) async { + await adapter.schedule(request); + await adapter.cancel(request.id); +} + +NotificationRequest _request(String id) { + return NotificationRequest( + id: id, + title: 'Start task', + body: 'Time to begin.', + scheduledDateTimeUtc: DateTime.utc(2026, 1, 1, 9), + ); +} + +final class _RecordingBackend implements DesktopNotificationBackend { + final List delivered = []; + final List cancelled = []; + + @override + bool get isSupported => true; + + @override + Future deliver(NotificationRequest request) async { + delivered.add(request); + } + + @override + Future cancel(String id) async { + cancelled.add(id); + } +} + +final class _ProcessCall { + const _ProcessCall(this.executable, this.arguments); + + final String executable; + final List arguments; +} diff --git a/packages/scheduler_persistence/analysis_options.yaml b/packages/scheduler_persistence/analysis_options.yaml new file mode 100644 index 0000000..572dd23 --- /dev/null +++ b/packages/scheduler_persistence/analysis_options.yaml @@ -0,0 +1 @@ +include: package:lints/recommended.yaml diff --git a/packages/scheduler_persistence/lib/persistence.dart b/packages/scheduler_persistence/lib/persistence.dart new file mode 100644 index 0000000..55ebd67 --- /dev/null +++ b/packages/scheduler_persistence/lib/persistence.dart @@ -0,0 +1,325 @@ +/// Domain-only repository contracts for scheduler persistence adapters. +/// +/// This package defines the stable boundary that SQLite, memory, and future +/// adapters must implement. Interfaces accept and return domain objects from +/// `scheduler_core`; storage rows, Drift classes, SQL clients, and platform +/// details stay behind adapter packages. +library; + +import 'package:scheduler_core/scheduler_core.dart' as core; + +/// Adapter-neutral repository failure. +sealed class RepositoryFailure { + const RepositoryFailure({ + this.entityId, + this.expectedRevision, + this.actualRevision, + this.message, + }); + + /// Stable id of the entity related to the failure, when available. + final String? entityId; + + /// Revision the caller expected for a compare-and-set operation. + final core.Revision? expectedRevision; + + /// Revision currently stored by the adapter. + final core.Revision? actualRevision; + + /// Optional non-localized diagnostic text for logs and tests. + final String? message; +} + +/// The requested entity was not found. +final class RepositoryNotFound extends RepositoryFailure { + /// Creates a not-found failure for [entityId]. + const RepositoryNotFound({super.entityId, super.message}); +} + +/// A record with the requested id already exists. +final class RepositoryDuplicateId extends RepositoryFailure { + /// Creates a duplicate-id failure for [entityId]. + const RepositoryDuplicateId({super.entityId, super.message}); +} + +/// The caller supplied an invalid revision value. +final class RepositoryInvalidRevision extends RepositoryFailure { + /// Creates an invalid-revision failure. + const RepositoryInvalidRevision({ + super.entityId, + super.expectedRevision, + super.message, + }); +} + +/// The caller attempted to write using a stale revision. +final class RepositoryStaleRevision extends RepositoryFailure { + /// Creates a stale-revision failure. + const RepositoryStaleRevision({ + super.entityId, + required super.expectedRevision, + required super.actualRevision, + super.message, + }); +} + +/// The requested record exists under a different owner scope. +final class RepositoryOwnerMismatch extends RepositoryFailure { + /// Creates an owner-mismatch failure for [entityId]. + const RepositoryOwnerMismatch({super.entityId, super.message}); +} + +/// The adapter could not complete the operation due to storage failure. +final class RepositoryStorageFailure extends RepositoryFailure { + /// Creates a storage failure with optional [message]. + const RepositoryStorageFailure({super.entityId, super.message}); +} + +/// Minimal Either type for expected repository success/failure paths. +sealed class Either { + const Either(); + + /// Whether this value is a left/failure value. + bool get isLeft => this is Left; + + /// Whether this value is a right/success value. + bool get isRight => this is Right; + + /// Return the left value or throw when this is right. + L get left => switch (this) { + Left(:final value) => value, + Right() => throw StateError('Either does not contain left.'), + }; + + /// Return the right value or throw when this is left. + R get right => switch (this) { + Left() => throw StateError('Either does not contain right.'), + Right(:final value) => value, + }; +} + +/// Failure side of [Either]. +final class Left extends Either { + /// Creates a left/failure value. + const Left(this.value); + + /// Wrapped failure value. + final L value; +} + +/// Success side of [Either]. +final class Right extends Either { + /// Creates a right/success value. + const Right(this.value); + + /// Wrapped success value. + final R value; +} + +/// Repository result wrapper for expected adapter outcomes. +typedef RepoResult = Either; + +/// Repository contract for task records. +abstract interface class TaskRepository { + /// Return the task with [taskId] in [ownerId], or null when it is absent. + Future> findById({ + required core.OwnerId ownerId, + required String taskId, + }); + + /// Return owner-scoped tasks in deterministic adapter order. + Future>> findByOwner({ + required core.OwnerId ownerId, + core.PageRequest page = const core.PageRequest(), + }); + + /// Return owner-scoped tasks assigned to [projectId]. + Future>> findByProjectId({ + required core.OwnerId ownerId, + required String projectId, + core.PageRequest page = const core.PageRequest(), + }); + + /// Return owner-scoped direct child tasks for [parentTaskId]. + Future>> findByParentTaskId({ + required core.OwnerId ownerId, + required String parentTaskId, + core.PageRequest page = const core.PageRequest(), + }); + + /// Return owner-scoped tasks with [status]. + Future>> findByStatus({ + required core.OwnerId ownerId, + required core.TaskStatus status, + core.PageRequest page = const core.PageRequest(), + }); + + /// Insert [task] for [ownerId] and return the assigned revision. + Future> insert({ + required core.OwnerId ownerId, + required core.Task task, + }); + + /// Save [task] if [expectedRevision] still matches the stored record. + Future> save({ + required core.OwnerId ownerId, + required core.Task task, + required core.Revision expectedRevision, + }); + + /// Remove [taskId] if [expectedRevision] still matches the stored record. + Future> delete({ + required core.OwnerId ownerId, + required String taskId, + required core.Revision expectedRevision, + }); +} + +/// Repository contract for project profile records. +abstract interface class ProjectRepository { + /// Return the project with [projectId] in [ownerId], or null when absent. + Future> findById({ + required core.OwnerId ownerId, + required String projectId, + }); + + /// Return owner-scoped projects, optionally including archived profiles. + Future>> findByOwner({ + required core.OwnerId ownerId, + bool includeArchived = false, + core.PageRequest page = const core.PageRequest(), + }); + + /// Insert [project] for [ownerId] and return the assigned revision. + Future> insert({ + required core.OwnerId ownerId, + required core.ProjectProfile project, + }); + + /// Save [project] if [expectedRevision] still matches the stored record. + Future> save({ + required core.OwnerId ownerId, + required core.ProjectProfile project, + required core.Revision expectedRevision, + }); + + /// Archive [projectId] without deleting task history. + Future> archive({ + required core.OwnerId ownerId, + required String projectId, + required core.Revision expectedRevision, + required DateTime archivedAtUtc, + }); +} + +/// Repository contract for locked blocks and date-scoped overrides. +abstract interface class LockedBlockRepository { + /// Return the locked block with [blockId] in [ownerId], or null when absent. + Future> findBlockById({ + required core.OwnerId ownerId, + required String blockId, + }); + + /// Return owner-scoped locked blocks. + Future>> findBlocksByOwner({ + required core.OwnerId ownerId, + bool includeArchived = false, + core.PageRequest page = const core.PageRequest(), + }); + + /// Return the override with [overrideId] in [ownerId], or null when absent. + Future> findOverrideById({ + required core.OwnerId ownerId, + required String overrideId, + }); + + /// Return owner-scoped overrides for [date]. + Future>> findOverridesForDate({ + required core.OwnerId ownerId, + required core.CivilDate date, + core.PageRequest page = const core.PageRequest(), + }); + + /// Insert [block] for [ownerId] and return the assigned revision. + Future> insertBlock({ + required core.OwnerId ownerId, + required core.LockedBlock block, + }); + + /// Save [block] if [expectedRevision] still matches the stored record. + Future> saveBlock({ + required core.OwnerId ownerId, + required core.LockedBlock block, + required core.Revision expectedRevision, + }); + + /// Archive [blockId] without deleting historical schedule context. + Future> archiveBlock({ + required core.OwnerId ownerId, + required String blockId, + required core.Revision expectedRevision, + required DateTime archivedAtUtc, + }); + + /// Insert [override] for [ownerId] and return the assigned revision. + Future> insertOverride({ + required core.OwnerId ownerId, + required core.LockedBlockOverride override, + }); + + /// Save [override] if [expectedRevision] still matches the stored record. + Future> saveOverride({ + required core.OwnerId ownerId, + required core.LockedBlockOverride override, + required core.Revision expectedRevision, + }); +} + +/// Repository contract for owner settings. +abstract interface class SettingsRepository { + /// Return settings for [ownerId], or null when no settings have been saved. + Future> findByOwner({ + required core.OwnerId ownerId, + }); + + /// Insert [settings] for [ownerId] and return the assigned revision. + Future> insert({ + required core.OwnerId ownerId, + required core.OwnerSettings settings, + }); + + /// Save [settings] if [expectedRevision] still matches the stored record. + Future> save({ + required core.OwnerId ownerId, + required core.OwnerSettings settings, + required core.Revision expectedRevision, + }); +} + +/// Repository contract for diagnostic schedule snapshots. +abstract interface class ScheduleSnapshotRepository { + /// Return the snapshot with [snapshotId] in [ownerId], or null when absent. + Future> findById({ + required core.OwnerId ownerId, + required String snapshotId, + }); + + /// Return snapshots captured for [ownerId] that overlap [window]. + Future>> findInWindow({ + required core.OwnerId ownerId, + required core.SchedulingWindow window, + core.PageRequest page = const core.PageRequest(), + }); + + /// Insert [snapshot] for [ownerId] and return the assigned revision. + Future> insert({ + required core.OwnerId ownerId, + required core.SchedulingStateSnapshot snapshot, + }); + + /// Delete snapshots whose retention expiry is before [nowUtc]. + Future> deleteExpired({ + required core.OwnerId ownerId, + required DateTime nowUtc, + }); +} diff --git a/packages/scheduler_persistence/pubspec.yaml b/packages/scheduler_persistence/pubspec.yaml new file mode 100644 index 0000000..3d050ef --- /dev/null +++ b/packages/scheduler_persistence/pubspec.yaml @@ -0,0 +1,15 @@ +name: scheduler_persistence +description: Domain-only repository contracts for the ADHD scheduler. +version: 0.1.0 +publish_to: none +resolution: workspace + +environment: + sdk: '>=3.6.0 <4.0.0' + +dependencies: + scheduler_core: any + +dev_dependencies: + lints: ^5.0.0 + test: ^1.25.0 diff --git a/packages/scheduler_persistence/test/repo_conformance.dart b/packages/scheduler_persistence/test/repo_conformance.dart new file mode 100644 index 0000000..0b1477e --- /dev/null +++ b/packages/scheduler_persistence/test/repo_conformance.dart @@ -0,0 +1,462 @@ +import 'package:scheduler_core/scheduler_core.dart' as core; +import 'package:scheduler_persistence/persistence.dart'; +import 'package:test/test.dart'; + +/// Factory set used by adapter packages to run the shared repository contract. +final class RepositoryComplianceFactories { + const RepositoryComplianceFactories({ + required this.taskRepository, + required this.projectRepository, + required this.lockedBlockRepository, + required this.settingsRepository, + required this.scheduleSnapshotRepository, + }); + + final TaskRepository Function() taskRepository; + final ProjectRepository Function() projectRepository; + final LockedBlockRepository Function() lockedBlockRepository; + final SettingsRepository Function() settingsRepository; + final ScheduleSnapshotRepository Function() scheduleSnapshotRepository; +} + +/// Runs the cross-adapter behavioral checks expected of repository packages. +void runRepositoryComplianceTests(RepositoryComplianceFactories factories) { + group('task repository compliance', () { + test('supports CRUD, owner isolation, and JSON-cloned returns', () async { + final repo = factories.taskRepository(); + final owner = core.OwnerId('owner-a'); + final otherOwner = core.OwnerId('owner-b'); + final task = _task('task-a'); + + final inserted = await repo.insert(ownerId: owner, task: task); + expect(inserted.right, core.Revision.initial); + + final found = + (await repo.findById(ownerId: owner, taskId: task.id)).right; + expect(found, isNotNull); + expect(found, isNot(same(task))); + expect(found!.id, task.id); + + final hiddenFromOtherOwner = + (await repo.findById(ownerId: otherOwner, taskId: task.id)).right; + expect(hiddenFromOtherOwner, isNull); + + final updated = task.copyWith( + title: 'Updated task', + updatedAt: _instant(2), + ); + final saved = await repo.save( + ownerId: owner, + task: updated, + expectedRevision: inserted.right, + ); + expect(saved.right, const core.Revision(2)); + + final afterSave = + (await repo.findById(ownerId: owner, taskId: task.id)).right; + expect(afterSave!.title, 'Updated task'); + + final deleted = await repo.delete( + ownerId: owner, + taskId: task.id, + expectedRevision: saved.right, + ); + expect(deleted.right, const core.Revision(3)); + expect( + (await repo.findById(ownerId: owner, taskId: task.id)).right, isNull); + }); + + test('enforces duplicate ids and optimistic revision conflicts', () async { + final repo = factories.taskRepository(); + final owner = core.OwnerId('owner-a'); + final otherOwner = core.OwnerId('owner-b'); + final task = _task('task-a'); + + final inserted = await repo.insert(ownerId: owner, task: task); + expect( + (await repo.insert(ownerId: owner, task: task)).left, + isA(), + ); + expect( + (await repo.save( + ownerId: owner, + task: task.copyWith(title: 'Invalid'), + expectedRevision: const core.Revision(0), + )) + .left, + isA(), + ); + expect( + (await repo.save( + ownerId: otherOwner, + task: task.copyWith(title: 'Other owner'), + expectedRevision: inserted.right, + )) + .left, + isA(), + ); + + final saved = await repo.save( + ownerId: owner, + task: task.copyWith(title: 'Fresh'), + expectedRevision: inserted.right, + ); + final stale = (await repo.save( + ownerId: owner, + task: task.copyWith(title: 'Stale'), + expectedRevision: inserted.right, + )) + .left; + expect(stale, isA()); + expect(stale.actualRevision, saved.right); + }); + + test('returns deterministic pages and filtered task queries', () async { + final repo = factories.taskRepository(); + final owner = core.OwnerId('owner-a'); + await repo.insert(ownerId: owner, task: _task('task-c')); + await repo.insert( + ownerId: owner, + task: _task( + 'task-a', + projectId: 'project-filter', + status: core.TaskStatus.completed, + ), + ); + await repo.insert( + ownerId: owner, + task: _task('task-b', parentTaskId: 'parent-1'), + ); + + final firstPage = (await repo.findByOwner( + ownerId: owner, + page: const core.PageRequest(limit: 2), + )) + .right; + expect(firstPage.items.map((task) => task.id), ['task-a', 'task-b']); + expect(firstPage.nextCursor, isNotNull); + + final secondPage = (await repo.findByOwner( + ownerId: owner, + page: core.PageRequest(cursor: firstPage.nextCursor, limit: 2), + )) + .right; + expect(secondPage.items.map((task) => task.id), ['task-c']); + expect(secondPage.nextCursor, isNull); + + expect( + (await repo.findByProjectId( + ownerId: owner, + projectId: 'project-filter', + )) + .right + .items + .map((task) => task.id), + ['task-a'], + ); + expect( + (await repo.findByParentTaskId( + ownerId: owner, + parentTaskId: 'parent-1', + )) + .right + .items + .map((task) => task.id), + ['task-b'], + ); + expect( + (await repo.findByStatus( + ownerId: owner, + status: core.TaskStatus.completed, + )) + .right + .items + .map((task) => task.id), + ['task-a'], + ); + }); + }); + + group('project repository compliance', () { + test('supports save, archive filters, and stale conflicts', () async { + final repo = factories.projectRepository(); + final owner = core.OwnerId('owner-a'); + final project = _project('project-a'); + + final inserted = await repo.insert(ownerId: owner, project: project); + expect(inserted.right, core.Revision.initial); + expect( + (await repo.insert(ownerId: owner, project: project)).left, + isA(), + ); + + final found = + (await repo.findById(ownerId: owner, projectId: project.id)).right; + expect(found, isNot(same(project))); + expect(found!.name, project.name); + + final saved = await repo.save( + ownerId: owner, + project: project.copyWith(name: 'Updated project'), + expectedRevision: inserted.right, + ); + expect(saved.right, const core.Revision(2)); + expect( + (await repo.save( + ownerId: owner, + project: project.copyWith(name: 'Stale project'), + expectedRevision: inserted.right, + )) + .left, + isA(), + ); + + final archived = await repo.archive( + ownerId: owner, + projectId: project.id, + expectedRevision: saved.right, + archivedAtUtc: _instant(3), + ); + expect(archived.right, const core.Revision(3)); + expect((await repo.findByOwner(ownerId: owner)).right.items, isEmpty); + expect( + (await repo.findByOwner(ownerId: owner, includeArchived: true)) + .right + .items + .single + .isArchived, + isTrue, + ); + }); + }); + + group('locked block repository compliance', () { + test('supports block archive and override date lookup', () async { + final repo = factories.lockedBlockRepository(); + final owner = core.OwnerId('owner-a'); + final block = _lockedBlock('block-a'); + final override = _override('override-a'); + + final insertedBlock = + await repo.insertBlock(ownerId: owner, block: block); + expect(insertedBlock.right, core.Revision.initial); + expect( + (await repo.insertBlock(ownerId: owner, block: block)).left, + isA(), + ); + + final foundBlock = + (await repo.findBlockById(ownerId: owner, blockId: block.id)).right; + expect(foundBlock, isNot(same(block))); + expect(foundBlock!.name, block.name); + + final savedBlock = await repo.saveBlock( + ownerId: owner, + block: block.copyWith(name: 'Updated block', updatedAt: _instant(2)), + expectedRevision: insertedBlock.right, + ); + expect(savedBlock.right, const core.Revision(2)); + expect( + (await repo.saveBlock( + ownerId: owner, + block: block, + expectedRevision: insertedBlock.right, + )) + .left, + isA(), + ); + + final archived = await repo.archiveBlock( + ownerId: owner, + blockId: block.id, + expectedRevision: savedBlock.right, + archivedAtUtc: _instant(3), + ); + expect(archived.right, const core.Revision(3)); + expect( + (await repo.findBlocksByOwner(ownerId: owner)).right.items, isEmpty); + + final insertedOverride = + await repo.insertOverride(ownerId: owner, override: override); + expect(insertedOverride.right, core.Revision.initial); + expect( + (await repo.findOverridesForDate(ownerId: owner, date: override.date)) + .right + .items + .map((item) => item.id), + [override.id], + ); + + final savedOverride = await repo.saveOverride( + ownerId: owner, + override: _override('override-a', name: 'Replacement block'), + expectedRevision: insertedOverride.right, + ); + expect(savedOverride.right, const core.Revision(2)); + }); + }); + + group('settings repository compliance', () { + test('supports owner singleton save and stale conflict', () async { + final repo = factories.settingsRepository(); + final owner = core.OwnerId('owner-a'); + final settings = _settings(owner.value); + + final inserted = await repo.insert(ownerId: owner, settings: settings); + expect(inserted.right, core.Revision.initial); + expect( + (await repo.insert(ownerId: owner, settings: settings)).left, + isA(), + ); + + final found = (await repo.findByOwner(ownerId: owner)).right; + expect(found, isNot(same(settings))); + expect(found!.timeZoneId, 'America/Los_Angeles'); + + final saved = await repo.save( + ownerId: owner, + settings: settings.copyWith(compactModeEnabled: true), + expectedRevision: inserted.right, + ); + expect(saved.right, const core.Revision(2)); + expect((await repo.findByOwner(ownerId: owner)).right!.compactModeEnabled, + isTrue); + expect( + (await repo.save( + ownerId: owner, + settings: settings, + expectedRevision: inserted.right, + )) + .left, + isA(), + ); + }); + }); + + group('schedule snapshot repository compliance', () { + test('supports window lookup, owner isolation, duplicates, and expiry', + () async { + final repo = factories.scheduleSnapshotRepository(); + final owner = core.OwnerId('owner-a'); + final otherOwner = core.OwnerId('owner-b'); + final snapshot = _snapshot('snapshot-a'); + + final inserted = await repo.insert(ownerId: owner, snapshot: snapshot); + expect(inserted.right, core.Revision.initial); + expect( + (await repo.insert(ownerId: owner, snapshot: snapshot)).left, + isA(), + ); + + final found = + (await repo.findById(ownerId: owner, snapshotId: snapshot.id)).right; + expect(found, isNot(same(snapshot))); + expect(found!.tasks.single.id, 'task-a'); + expect( + (await repo.findById(ownerId: otherOwner, snapshotId: snapshot.id)) + .right, + isNull, + ); + + final overlapping = (await repo.findInWindow( + ownerId: owner, + window: core.SchedulingWindow( + start: _instant(0, hour: 12), + end: _instant(0, hour: 13), + ), + )) + .right; + expect(overlapping.items.map((item) => item.id), [snapshot.id]); + + final deleted = await repo.deleteExpired( + ownerId: owner, + nowUtc: snapshot.capturedAt.add(const Duration(days: 31)), + ); + expect(deleted.right, 1); + expect( + (await repo.findById(ownerId: owner, snapshotId: snapshot.id)).right, + isNull, + ); + }); + }); +} + +DateTime _instant(int day, {int hour = 9}) { + return DateTime.utc(2026, 1, 1 + day, hour); +} + +core.Task _task( + String id, { + String projectId = 'project-a', + String? parentTaskId, + core.TaskStatus status = core.TaskStatus.backlog, +}) { + return core.Task( + id: id, + title: 'Task $id', + projectId: projectId, + type: core.TaskType.flexible, + status: status, + durationMinutes: 30, + parentTaskId: parentTaskId, + createdAt: _instant(0), + updatedAt: _instant(0), + ); +} + +core.ProjectProfile _project(String id) { + return core.ProjectProfile( + id: id, + name: 'Project $id', + colorKey: 'blue', + defaultDurationMinutes: 30, + ); +} + +core.LockedBlock _lockedBlock(String id) { + return core.LockedBlock( + id: id, + name: 'Block $id', + date: core.CivilDate(2026, 1, 1), + startTime: core.WallTime(hour: 10, minute: 0), + endTime: core.WallTime(hour: 11, minute: 0), + createdAt: _instant(0), + updatedAt: _instant(0), + ); +} + +core.LockedBlockOverride _override( + String id, { + String name = 'Added block', +}) { + return core.LockedBlockOverride.add( + id: id, + date: core.CivilDate(2026, 1, 2), + name: name, + startTime: core.WallTime(hour: 14, minute: 0), + endTime: core.WallTime(hour: 15, minute: 0), + createdAt: _instant(1), + ); +} + +core.OwnerSettings _settings(String ownerId) { + return core.OwnerSettings( + ownerId: ownerId, + timeZoneId: 'America/Los_Angeles', + dayStart: core.WallTime(hour: 8, minute: 0), + dayEnd: core.WallTime(hour: 22, minute: 0), + ); +} + +core.SchedulingStateSnapshot _snapshot(String id) { + return core.SchedulingStateSnapshot( + id: id, + capturedAt: _instant(0), + window: core.SchedulingWindow( + start: _instant(0, hour: 8), + end: _instant(0, hour: 17), + ), + tasks: [_task('task-a')], + operationName: 'conformance', + ); +} diff --git a/packages/scheduler_persistence/test/repo_contract_smoke_test.dart b/packages/scheduler_persistence/test/repo_contract_smoke_test.dart new file mode 100644 index 0000000..817cfaa --- /dev/null +++ b/packages/scheduler_persistence/test/repo_contract_smoke_test.dart @@ -0,0 +1,40 @@ +import 'package:scheduler_core/scheduler_core.dart' as core; +import 'package:scheduler_persistence/persistence.dart'; +import 'package:test/test.dart'; + +void main() { + group('repository contract smoke test', () { + test('exports required repository interfaces', () { + expect(TaskRepository, isNotNull); + expect(ProjectRepository, isNotNull); + expect(LockedBlockRepository, isNotNull); + expect(SettingsRepository, isNotNull); + expect(ScheduleSnapshotRepository, isNotNull); + }); + + test('repo result carries typed success and failure values', () { + const success = Right( + core.Revision.initial, + ); + const failure = Left( + RepositoryNotFound(entityId: 'missing'), + ); + + expect(success.isRight, isTrue); + expect(success.right, core.Revision.initial); + expect(failure.isLeft, isTrue); + expect(failure.left, isA()); + }); + + test('shared core value objects are available to adapters', () { + final ownerId = core.OwnerId(' owner-1 '); + final page = core.Page(items: const ['a'], nextCursor: 'next'); + + expect(ownerId.value, 'owner-1'); + expect(core.Revision.initial.next(), const core.Revision(2)); + expect(const core.PageRequest(limit: 10).limit, 10); + expect(page.items, const ['a']); + expect(page.hasMore, isTrue); + }); + }); +} diff --git a/packages/scheduler_persistence_memory/analysis_options.yaml b/packages/scheduler_persistence_memory/analysis_options.yaml new file mode 100644 index 0000000..572dd23 --- /dev/null +++ b/packages/scheduler_persistence_memory/analysis_options.yaml @@ -0,0 +1 @@ +include: package:lints/recommended.yaml diff --git a/packages/scheduler_persistence_memory/lib/memory.dart b/packages/scheduler_persistence_memory/lib/memory.dart new file mode 100644 index 0000000..3b2621a --- /dev/null +++ b/packages/scheduler_persistence_memory/lib/memory.dart @@ -0,0 +1,4 @@ +/// Compatibility entry point for in-memory scheduler persistence adapters. +library; + +export 'persistence_memory.dart'; diff --git a/packages/scheduler_persistence_memory/lib/persistence_memory.dart b/packages/scheduler_persistence_memory/lib/persistence_memory.dart new file mode 100644 index 0000000..53ecd8e --- /dev/null +++ b/packages/scheduler_persistence_memory/lib/persistence_memory.dart @@ -0,0 +1,852 @@ +/// In-memory implementations of the scheduler persistence repository contracts. +/// +/// These repositories are intended for core tests, local integration tests, and +/// adapter conformance checks. They model optimistic compare-and-set behavior +/// but do not provide durable storage. +library; + +import 'dart:convert'; + +import 'package:scheduler_core/scheduler_core.dart' as core; +import 'package:scheduler_persistence/persistence.dart'; + +const _snapshotRetention = Duration(days: 30); + +final _epoch = DateTime.fromMillisecondsSinceEpoch(0, isUtc: true); + +/// In-memory task repository with owner scoping and revision checks. +final class InMemoryTaskRepository implements TaskRepository { + final Map> _records = + >{}; + + @override + Future> findById({ + required core.OwnerId ownerId, + required String taskId, + }) async { + final record = _records[taskId]; + if (record == null || record.ownerId != ownerId) { + return const Right(null); + } + return Right(_cloneTask(record.value, record.ownerId, record.revision)); + } + + @override + Future>> findByOwner({ + required core.OwnerId ownerId, + core.PageRequest page = const core.PageRequest(), + }) async { + return Right(_pageRecords( + _records.values.where((record) => record.ownerId == ownerId), + page, + (record) => _cloneTask(record.value, record.ownerId, record.revision), + )); + } + + @override + Future>> findByParentTaskId({ + required core.OwnerId ownerId, + required String parentTaskId, + core.PageRequest page = const core.PageRequest(), + }) async { + return Right(_pageRecords( + _records.values.where( + (record) => + record.ownerId == ownerId && + record.value.parentTaskId == parentTaskId, + ), + page, + (record) => _cloneTask(record.value, record.ownerId, record.revision), + )); + } + + @override + Future>> findByProjectId({ + required core.OwnerId ownerId, + required String projectId, + core.PageRequest page = const core.PageRequest(), + }) async { + return Right(_pageRecords( + _records.values.where( + (record) => + record.ownerId == ownerId && record.value.projectId == projectId, + ), + page, + (record) => _cloneTask(record.value, record.ownerId, record.revision), + )); + } + + @override + Future>> findByStatus({ + required core.OwnerId ownerId, + required core.TaskStatus status, + core.PageRequest page = const core.PageRequest(), + }) async { + return Right(_pageRecords( + _records.values.where( + (record) => record.ownerId == ownerId && record.value.status == status, + ), + page, + (record) => _cloneTask(record.value, record.ownerId, record.revision), + )); + } + + @override + Future> insert({ + required core.OwnerId ownerId, + required core.Task task, + }) async { + if (_records.containsKey(task.id)) { + return Left(RepositoryDuplicateId(entityId: task.id)); + } + _records[task.id] = _Record( + id: task.id, + ownerId: ownerId, + value: _cloneTask(task, ownerId, core.Revision.initial), + revision: core.Revision.initial, + ); + return const Right(core.Revision.initial); + } + + @override + Future> save({ + required core.OwnerId ownerId, + required core.Task task, + required core.Revision expectedRevision, + }) async { + final checked = _checkWrite( + records: _records, + id: task.id, + ownerId: ownerId, + expectedRevision: expectedRevision, + ); + if (checked is Left>) { + return Left(checked.value); + } + final record = checked.right; + final nextRevision = record.revision.next(); + _records[task.id] = record.copyWith( + value: _cloneTask(task, ownerId, nextRevision), + revision: nextRevision, + updatedAt: task.updatedAt, + ); + return Right(nextRevision); + } + + @override + Future> delete({ + required core.OwnerId ownerId, + required String taskId, + required core.Revision expectedRevision, + }) async { + final checked = _checkWrite( + records: _records, + id: taskId, + ownerId: ownerId, + expectedRevision: expectedRevision, + ); + if (checked is Left>) { + return Left(checked.value); + } + final nextRevision = checked.right.revision.next(); + _records.remove(taskId); + return Right(nextRevision); + } +} + +/// In-memory project repository with owner scoping and revision checks. +final class InMemoryProjectRepository implements ProjectRepository { + final Map> _records = + >{}; + + @override + Future> findById({ + required core.OwnerId ownerId, + required String projectId, + }) async { + final record = _records[projectId]; + if (record == null || record.ownerId != ownerId) { + return const Right(null); + } + return Right(_cloneProject(record)); + } + + @override + Future>> findByOwner({ + required core.OwnerId ownerId, + bool includeArchived = false, + core.PageRequest page = const core.PageRequest(), + }) async { + return Right(_pageRecords( + _records.values.where( + (record) => + record.ownerId == ownerId && + (includeArchived || !record.value.isArchived), + ), + page, + _cloneProject, + )); + } + + @override + Future> insert({ + required core.OwnerId ownerId, + required core.ProjectProfile project, + }) async { + if (_records.containsKey(project.id)) { + return Left(RepositoryDuplicateId(entityId: project.id)); + } + _records[project.id] = _Record( + id: project.id, + ownerId: ownerId, + value: _cloneProjectValue( + project, + ownerId: ownerId, + revision: core.Revision.initial, + createdAt: _epoch, + updatedAt: _epoch, + ), + revision: core.Revision.initial, + createdAt: _epoch, + updatedAt: _epoch, + ); + return const Right(core.Revision.initial); + } + + @override + Future> save({ + required core.OwnerId ownerId, + required core.ProjectProfile project, + required core.Revision expectedRevision, + }) async { + final checked = _checkWrite( + records: _records, + id: project.id, + ownerId: ownerId, + expectedRevision: expectedRevision, + ); + if (checked is Left>) { + return Left(checked.value); + } + final record = checked.right; + final nextRevision = record.revision.next(); + _records[project.id] = record.copyWith( + value: _cloneProjectValue( + project, + ownerId: ownerId, + revision: nextRevision, + createdAt: record.createdAt, + updatedAt: _epoch, + ), + revision: nextRevision, + updatedAt: _epoch, + ); + return Right(nextRevision); + } + + @override + Future> archive({ + required core.OwnerId ownerId, + required String projectId, + required core.Revision expectedRevision, + required DateTime archivedAtUtc, + }) async { + final checked = _checkWrite( + records: _records, + id: projectId, + ownerId: ownerId, + expectedRevision: expectedRevision, + ); + if (checked is Left>) { + return Left(checked.value); + } + final record = checked.right; + final nextRevision = record.revision.next(); + final archived = record.value.copyWith(archivedAt: archivedAtUtc); + _records[projectId] = record.copyWith( + value: _cloneProjectValue( + archived, + ownerId: ownerId, + revision: nextRevision, + createdAt: record.createdAt, + updatedAt: _epoch, + ), + revision: nextRevision, + updatedAt: _epoch, + ); + return Right(nextRevision); + } +} + +/// In-memory locked-block repository with owner scoping and revision checks. +final class InMemoryLockedBlockRepository implements LockedBlockRepository { + final Map> _blocks = + >{}; + final Map> _overrides = + >{}; + + @override + Future> findBlockById({ + required core.OwnerId ownerId, + required String blockId, + }) async { + final record = _blocks[blockId]; + if (record == null || record.ownerId != ownerId) { + return const Right(null); + } + return Right( + _cloneLockedBlock(record.value, record.ownerId, record.revision)); + } + + @override + Future>> findBlocksByOwner({ + required core.OwnerId ownerId, + bool includeArchived = false, + core.PageRequest page = const core.PageRequest(), + }) async { + return Right(_pageRecords( + _blocks.values.where( + (record) => + record.ownerId == ownerId && + (includeArchived || !record.value.isArchived), + ), + page, + (record) => _cloneLockedBlock( + record.value, + record.ownerId, + record.revision, + ), + )); + } + + @override + Future> findOverrideById({ + required core.OwnerId ownerId, + required String overrideId, + }) async { + final record = _overrides[overrideId]; + if (record == null || record.ownerId != ownerId) { + return const Right(null); + } + return Right( + _cloneLockedBlockOverride(record.value, record.ownerId, record.revision), + ); + } + + @override + Future>> findOverridesForDate({ + required core.OwnerId ownerId, + required core.CivilDate date, + core.PageRequest page = const core.PageRequest(), + }) async { + return Right(_pageRecords( + _overrides.values.where( + (record) => record.ownerId == ownerId && record.value.date == date, + ), + page, + (record) => _cloneLockedBlockOverride( + record.value, + record.ownerId, + record.revision, + ), + )); + } + + @override + Future> insertBlock({ + required core.OwnerId ownerId, + required core.LockedBlock block, + }) async { + if (_blocks.containsKey(block.id)) { + return Left(RepositoryDuplicateId(entityId: block.id)); + } + _blocks[block.id] = _Record( + id: block.id, + ownerId: ownerId, + value: _cloneLockedBlock(block, ownerId, core.Revision.initial), + revision: core.Revision.initial, + ); + return const Right(core.Revision.initial); + } + + @override + Future> saveBlock({ + required core.OwnerId ownerId, + required core.LockedBlock block, + required core.Revision expectedRevision, + }) async { + final checked = _checkWrite( + records: _blocks, + id: block.id, + ownerId: ownerId, + expectedRevision: expectedRevision, + ); + if (checked is Left>) { + return Left(checked.value); + } + final record = checked.right; + final nextRevision = record.revision.next(); + _blocks[block.id] = record.copyWith( + value: _cloneLockedBlock(block, ownerId, nextRevision), + revision: nextRevision, + updatedAt: block.updatedAt, + ); + return Right(nextRevision); + } + + @override + Future> archiveBlock({ + required core.OwnerId ownerId, + required String blockId, + required core.Revision expectedRevision, + required DateTime archivedAtUtc, + }) async { + final checked = _checkWrite( + records: _blocks, + id: blockId, + ownerId: ownerId, + expectedRevision: expectedRevision, + ); + if (checked is Left>) { + return Left(checked.value); + } + final record = checked.right; + final nextRevision = record.revision.next(); + final archived = record.value.copyWith(archivedAt: archivedAtUtc); + _blocks[blockId] = record.copyWith( + value: _cloneLockedBlock(archived, ownerId, nextRevision), + revision: nextRevision, + updatedAt: archived.updatedAt, + ); + return Right(nextRevision); + } + + @override + Future> insertOverride({ + required core.OwnerId ownerId, + required core.LockedBlockOverride override, + }) async { + if (_overrides.containsKey(override.id)) { + return Left(RepositoryDuplicateId(entityId: override.id)); + } + _overrides[override.id] = _Record( + id: override.id, + ownerId: ownerId, + value: _cloneLockedBlockOverride( + override, + ownerId, + core.Revision.initial, + ), + revision: core.Revision.initial, + ); + return const Right(core.Revision.initial); + } + + @override + Future> saveOverride({ + required core.OwnerId ownerId, + required core.LockedBlockOverride override, + required core.Revision expectedRevision, + }) async { + final checked = _checkWrite( + records: _overrides, + id: override.id, + ownerId: ownerId, + expectedRevision: expectedRevision, + ); + if (checked is Left>) { + return Left(checked.value); + } + final record = checked.right; + final nextRevision = record.revision.next(); + _overrides[override.id] = record.copyWith( + value: _cloneLockedBlockOverride(override, ownerId, nextRevision), + revision: nextRevision, + updatedAt: override.updatedAt, + ); + return Right(nextRevision); + } +} + +/// In-memory owner settings repository with owner scoping and revision checks. +final class InMemorySettingsRepository implements SettingsRepository { + final Map> _records = + >{}; + + @override + Future> findByOwner({ + required core.OwnerId ownerId, + }) async { + final record = _records[ownerId.value]; + if (record == null || record.ownerId != ownerId) { + return const Right(null); + } + return Right(_cloneSettings(record)); + } + + @override + Future> insert({ + required core.OwnerId ownerId, + required core.OwnerSettings settings, + }) async { + if (_records.containsKey(ownerId.value)) { + return Left(RepositoryDuplicateId(entityId: ownerId.value)); + } + final ownedSettings = settings.ownerId == ownerId.value + ? settings + : settings.copyWith(ownerId: ownerId.value); + _records[ownerId.value] = _Record( + id: ownerId.value, + ownerId: ownerId, + value: _cloneSettingsValue( + ownedSettings, + revision: core.Revision.initial, + createdAt: _epoch, + updatedAt: _epoch, + ), + revision: core.Revision.initial, + createdAt: _epoch, + updatedAt: _epoch, + ); + return const Right(core.Revision.initial); + } + + @override + Future> save({ + required core.OwnerId ownerId, + required core.OwnerSettings settings, + required core.Revision expectedRevision, + }) async { + final checked = _checkWrite( + records: _records, + id: ownerId.value, + ownerId: ownerId, + expectedRevision: expectedRevision, + ); + if (checked is Left>) { + return Left(checked.value); + } + final record = checked.right; + final nextRevision = record.revision.next(); + final ownedSettings = settings.ownerId == ownerId.value + ? settings + : settings.copyWith(ownerId: ownerId.value); + _records[ownerId.value] = record.copyWith( + value: _cloneSettingsValue( + ownedSettings, + revision: nextRevision, + createdAt: record.createdAt, + updatedAt: _epoch, + ), + revision: nextRevision, + updatedAt: _epoch, + ); + return Right(nextRevision); + } +} + +/// In-memory diagnostic schedule snapshot repository. +final class InMemoryScheduleSnapshotRepository + implements ScheduleSnapshotRepository { + final Map _records = {}; + + @override + Future> findById({ + required core.OwnerId ownerId, + required String snapshotId, + }) async { + final record = _records[snapshotId]; + if (record == null || record.ownerId != ownerId) { + return const Right(null); + } + return Right(_cloneSnapshot(record.value, record.ownerId, record.revision)); + } + + @override + Future>> findInWindow({ + required core.OwnerId ownerId, + required core.SchedulingWindow window, + core.PageRequest page = const core.PageRequest(), + }) async { + return Right(_pageRecords( + _records.values.where( + (record) => + record.ownerId == ownerId && + _windowsOverlap(record.value.window, window), + ), + page, + (record) => _cloneSnapshot(record.value, record.ownerId, record.revision), + )); + } + + @override + Future> insert({ + required core.OwnerId ownerId, + required core.SchedulingStateSnapshot snapshot, + }) async { + if (_records.containsKey(snapshot.id)) { + return Left(RepositoryDuplicateId(entityId: snapshot.id)); + } + _records[snapshot.id] = _SnapshotRecord( + id: snapshot.id, + ownerId: ownerId, + value: _cloneSnapshot(snapshot, ownerId, core.Revision.initial), + revision: core.Revision.initial, + retentionExpiresAt: snapshot.capturedAt.add(_snapshotRetention), + ); + return const Right(core.Revision.initial); + } + + @override + Future> deleteExpired({ + required core.OwnerId ownerId, + required DateTime nowUtc, + }) async { + final expiredIds = _records.values + .where( + (record) => + record.ownerId == ownerId && + record.retentionExpiresAt.isBefore(nowUtc), + ) + .map((record) => record.id) + .toList(growable: false); + for (final id in expiredIds) { + _records.remove(id); + } + return Right(expiredIds.length); + } +} + +Either> _checkWrite({ + required Map> records, + required String id, + required core.OwnerId ownerId, + required core.Revision expectedRevision, +}) { + if (expectedRevision.value <= 0) { + return Left( + RepositoryInvalidRevision( + entityId: id, + expectedRevision: expectedRevision, + ), + ); + } + final record = records[id]; + if (record == null) { + return Left(RepositoryNotFound(entityId: id)); + } + if (record.ownerId != ownerId) { + return Left(RepositoryOwnerMismatch(entityId: id)); + } + if (record.revision != expectedRevision) { + return Left( + RepositoryStaleRevision( + entityId: id, + expectedRevision: expectedRevision, + actualRevision: record.revision, + ), + ); + } + return Right(record); +} + +core.Page _pageRecords( + Iterable records, + core.PageRequest page, + R Function(T record) clone, +) { + final ordered = records.toList(growable: false) + ..sort((left, right) => left.id.compareTo(right.id)); + final start = _cursorOffset(page.cursor); + final end = (start + page.limit).clamp(0, ordered.length); + final items = ordered.sublist(start, end).map(clone).toList(growable: false); + final nextCursor = end < ordered.length ? end.toString() : null; + return core.Page(items: items, nextCursor: nextCursor); +} + +int _cursorOffset(String? cursor) { + if (cursor == null) { + return 0; + } + final parsed = int.tryParse(cursor); + if (parsed == null || parsed < 0) { + return 0; + } + return parsed; +} + +bool _windowsOverlap(core.SchedulingWindow left, core.SchedulingWindow right) { + return left.start.isBefore(right.end) && right.start.isBefore(left.end); +} + +core.Task _cloneTask( + core.Task task, + core.OwnerId ownerId, + core.Revision revision, +) { + return core.TaskDocumentMapping.fromDocument(_roundTrip( + core.TaskDocumentMapping.toDocument( + task, + ownerId: ownerId.value, + revision: revision.value, + ), + )); +} + +core.ProjectProfile _cloneProject(_Record record) { + return _cloneProjectValue( + record.value, + ownerId: record.ownerId, + revision: record.revision, + createdAt: record.createdAt, + updatedAt: record.updatedAt, + ); +} + +core.ProjectProfile _cloneProjectValue( + core.ProjectProfile project, { + required core.OwnerId ownerId, + required core.Revision revision, + required DateTime createdAt, + required DateTime updatedAt, +}) { + return core.ProjectDocumentMapping.fromDocument(_roundTrip( + core.ProjectDocumentMapping.toDocument( + project, + ownerId: ownerId.value, + revision: revision.value, + createdAt: createdAt, + updatedAt: updatedAt, + ), + )); +} + +core.LockedBlock _cloneLockedBlock( + core.LockedBlock block, + core.OwnerId ownerId, + core.Revision revision, +) { + return core.LockedBlockDocumentMapping.fromDocument(_roundTrip( + core.LockedBlockDocumentMapping.toDocument( + block, + ownerId: ownerId.value, + revision: revision.value, + ), + )); +} + +core.LockedBlockOverride _cloneLockedBlockOverride( + core.LockedBlockOverride override, + core.OwnerId ownerId, + core.Revision revision, +) { + return core.LockedBlockOverrideDocumentMapping.fromDocument(_roundTrip( + core.LockedBlockOverrideDocumentMapping.toDocument( + override, + ownerId: ownerId.value, + revision: revision.value, + ), + )); +} + +core.OwnerSettings _cloneSettings(_Record record) { + return _cloneSettingsValue( + record.value, + revision: record.revision, + createdAt: record.createdAt, + updatedAt: record.updatedAt, + ); +} + +core.OwnerSettings _cloneSettingsValue( + core.OwnerSettings settings, { + required core.Revision revision, + required DateTime createdAt, + required DateTime updatedAt, +}) { + return core.OwnerSettingsDocumentMapping.fromDocument(_roundTrip( + core.OwnerSettingsDocumentMapping.toDocument( + settings, + revision: revision.value, + createdAt: createdAt, + updatedAt: updatedAt, + ), + )); +} + +core.SchedulingStateSnapshot _cloneSnapshot( + core.SchedulingStateSnapshot snapshot, + core.OwnerId ownerId, + core.Revision revision, +) { + return core.SchedulingSnapshotDocumentMapping.fromDocument(_roundTrip( + core.SchedulingSnapshotDocumentMapping.toDocument( + snapshot, + ownerId: ownerId.value, + revision: revision.value, + retentionExpiresAt: snapshot.capturedAt.add(_snapshotRetention), + ), + )); +} + +Map _roundTrip(Map document) { + return Map.from( + jsonDecode(jsonEncode(document)) as Map, + ); +} + +abstract interface class _IdentifiedRecord { + String get id; +} + +final class _Record implements _IdentifiedRecord { + _Record({ + required this.id, + required this.ownerId, + required this.value, + required this.revision, + DateTime? createdAt, + DateTime? updatedAt, + }) : createdAt = createdAt ?? _epoch, + updatedAt = updatedAt ?? _epoch; + + @override + final String id; + final core.OwnerId ownerId; + final T value; + final core.Revision revision; + final DateTime createdAt; + final DateTime updatedAt; + + _Record copyWith({ + T? value, + core.Revision? revision, + DateTime? updatedAt, + }) { + return _Record( + id: id, + ownerId: ownerId, + value: value ?? this.value, + revision: revision ?? this.revision, + createdAt: createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } +} + +final class _SnapshotRecord implements _IdentifiedRecord { + _SnapshotRecord({ + required this.id, + required this.ownerId, + required this.value, + required this.revision, + required this.retentionExpiresAt, + }); + + @override + final String id; + final core.OwnerId ownerId; + final core.SchedulingStateSnapshot value; + final core.Revision revision; + final DateTime retentionExpiresAt; +} diff --git a/packages/scheduler_persistence_memory/pubspec.yaml b/packages/scheduler_persistence_memory/pubspec.yaml new file mode 100644 index 0000000..add5b9a --- /dev/null +++ b/packages/scheduler_persistence_memory/pubspec.yaml @@ -0,0 +1,15 @@ +name: scheduler_persistence_memory +publish_to: none + +environment: + sdk: '>=3.6.0 <4.0.0' + +resolution: workspace + +dependencies: + scheduler_core: any + scheduler_persistence: any + +dev_dependencies: + lints: ^5.0.0 + test: ^1.25.0 diff --git a/packages/scheduler_persistence_memory/test/memory_repository_conformance_test.dart b/packages/scheduler_persistence_memory/test/memory_repository_conformance_test.dart new file mode 100644 index 0000000..62b82ea --- /dev/null +++ b/packages/scheduler_persistence_memory/test/memory_repository_conformance_test.dart @@ -0,0 +1,15 @@ +import 'package:scheduler_persistence_memory/persistence_memory.dart'; + +import '../../scheduler_persistence/test/repo_conformance.dart'; + +void main() { + runRepositoryComplianceTests( + RepositoryComplianceFactories( + taskRepository: InMemoryTaskRepository.new, + projectRepository: InMemoryProjectRepository.new, + lockedBlockRepository: InMemoryLockedBlockRepository.new, + settingsRepository: InMemorySettingsRepository.new, + scheduleSnapshotRepository: InMemoryScheduleSnapshotRepository.new, + ), + ); +} diff --git a/packages/scheduler_persistence_sqlite/analysis_options.yaml b/packages/scheduler_persistence_sqlite/analysis_options.yaml new file mode 100644 index 0000000..572dd23 --- /dev/null +++ b/packages/scheduler_persistence_sqlite/analysis_options.yaml @@ -0,0 +1 @@ +include: package:lints/recommended.yaml diff --git a/packages/scheduler_persistence_sqlite/lib/sqlite.dart b/packages/scheduler_persistence_sqlite/lib/sqlite.dart new file mode 100644 index 0000000..be88c1d --- /dev/null +++ b/packages/scheduler_persistence_sqlite/lib/sqlite.dart @@ -0,0 +1,5 @@ +/// Drift-backed SQLite persistence surface for the scheduler. +library; + +export 'src/scheduler_db.dart'; +export 'src/sqlite_repositories.dart'; diff --git a/packages/scheduler_persistence_sqlite/lib/src/scheduler_db.dart b/packages/scheduler_persistence_sqlite/lib/src/scheduler_db.dart new file mode 100644 index 0000000..1e063c6 --- /dev/null +++ b/packages/scheduler_persistence_sqlite/lib/src/scheduler_db.dart @@ -0,0 +1,231 @@ +/// Drift schema for the scheduler's local SQLite database. +/// +/// This package owns SQLite and Drift details. The scheduler core continues to +/// depend only on domain objects and repository contracts. +library; + +import 'package:drift/drift.dart'; + +part 'scheduler_db.g.dart'; + +/// Authoritative task rows. +@DataClassName('TaskRow') +class Tasks extends Table { + TextColumn get id => text()(); + TextColumn get ownerId => text().named('owner_id')(); + TextColumn get title => text()(); + TextColumn get projectId => text().named('project_id')(); + TextColumn get parentId => text().named('parent_id').nullable()(); + TextColumn get type => text()(); + TextColumn get status => text()(); + TextColumn get priority => text().nullable()(); + TextColumn get reward => text()(); + TextColumn get difficulty => text()(); + IntColumn get durationMinutes => + integer().named('duration_minutes').nullable()(); + DateTimeColumn get scheduledStartUtc => + dateTime().named('scheduled_start_utc').nullable()(); + DateTimeColumn get scheduledEndUtc => + dateTime().named('scheduled_end_utc').nullable()(); + DateTimeColumn get actualStartUtc => + dateTime().named('actual_start_utc').nullable()(); + DateTimeColumn get actualEndUtc => + dateTime().named('actual_end_utc').nullable()(); + DateTimeColumn get completedAtUtc => + dateTime().named('completed_at_utc').nullable()(); + TextColumn get backlogTagsJson => text().named('backlog_tags_json')(); + TextColumn get reminderOverride => + text().named('reminder_override').nullable()(); + TextColumn get statsJson => text().named('stats_json')(); + DateTimeColumn get backlogEnteredAtUtc => + dateTime().named('backlog_entered_at_utc').nullable()(); + TextColumn get backlogEnteredProvenance => + text().named('backlog_entered_provenance').nullable()(); + IntColumn get revision => integer()(); + DateTimeColumn get createdAtUtc => dateTime().named('created_at_utc')(); + DateTimeColumn get updatedAtUtc => dateTime().named('updated_at_utc')(); + + @override + Set> get primaryKey => {id}; +} + +/// Project configuration rows. +@DataClassName('ProjectRow') +class Projects extends Table { + TextColumn get id => text()(); + TextColumn get ownerId => text().named('owner_id')(); + TextColumn get name => text()(); + TextColumn get colorKey => text().named('color_key')(); + TextColumn get defaultPriority => text().named('default_priority')(); + TextColumn get defaultReward => text().named('default_reward')(); + TextColumn get defaultDifficulty => text().named('default_difficulty')(); + TextColumn get defaultReminderProfile => + text().named('default_reminder_profile')(); + IntColumn get defaultDurationMinutes => + integer().named('default_duration_minutes').nullable()(); + DateTimeColumn get archivedAtUtc => + dateTime().named('archived_at_utc').nullable()(); + IntColumn get revision => integer()(); + DateTimeColumn get createdAtUtc => dateTime().named('created_at_utc')(); + DateTimeColumn get updatedAtUtc => dateTime().named('updated_at_utc')(); + + @override + Set> get primaryKey => {id}; +} + +/// Recurring and one-off locked-time rows. +@DataClassName('LockedBlockRow') +class LockedBlocks extends Table { + TextColumn get id => text()(); + TextColumn get ownerId => text().named('owner_id')(); + TextColumn get name => text()(); + TextColumn get date => text().nullable()(); + TextColumn get startTime => text().named('start_time')(); + TextColumn get endTime => text().named('end_time')(); + TextColumn get recurrenceJson => text().named('recurrence_json').nullable()(); + BoolColumn get hiddenByDefault => boolean().named('hidden_by_default')(); + TextColumn get projectId => text().named('project_id').nullable()(); + DateTimeColumn get archivedAtUtc => + dateTime().named('archived_at_utc').nullable()(); + IntColumn get revision => integer()(); + DateTimeColumn get createdAtUtc => dateTime().named('created_at_utc')(); + DateTimeColumn get updatedAtUtc => dateTime().named('updated_at_utc')(); + + @override + Set> get primaryKey => {id}; +} + +/// Date-scoped overrides for recurring locked blocks. +@DataClassName('LockedOverrideRow') +class LockedOverrides extends Table { + TextColumn get id => text()(); + TextColumn get ownerId => text().named('owner_id')(); + TextColumn get lockedBlockId => text().named('locked_block_id').nullable()(); + TextColumn get date => text()(); + TextColumn get type => text()(); + TextColumn get name => text().nullable()(); + TextColumn get startTime => text().named('start_time').nullable()(); + TextColumn get endTime => text().named('end_time').nullable()(); + BoolColumn get hiddenByDefault => boolean().named('hidden_by_default')(); + TextColumn get projectId => text().named('project_id').nullable()(); + IntColumn get revision => integer()(); + DateTimeColumn get createdAtUtc => dateTime().named('created_at_utc')(); + DateTimeColumn get updatedAtUtc => dateTime().named('updated_at_utc')(); + + @override + Set> get primaryKey => {id}; +} + +/// Owner-level settings rows. +@DataClassName('SettingsRow') +class SettingsTable extends Table { + @override + String get tableName => 'settings'; + + TextColumn get ownerId => text().named('owner_id')(); + TextColumn get timeZoneId => text().named('timezone_id')(); + IntColumn get dayStartMinutes => integer().named('day_start_minutes')(); + IntColumn get dayEndMinutes => integer().named('day_end_minutes')(); + BoolColumn get compactMode => boolean().named('compact_mode')(); + TextColumn get backlogStalenessJson => + text().named('backlog_staleness_json')(); + IntColumn get revision => integer()(); + DateTimeColumn get createdAtUtc => dateTime().named('created_at_utc')(); + DateTimeColumn get updatedAtUtc => dateTime().named('updated_at_utc')(); + + @override + Set> get primaryKey => {ownerId}; +} + +/// Bounded diagnostic schedule snapshots. +@DataClassName('SnapshotRow') +class Snapshots extends Table { + TextColumn get id => text()(); + TextColumn get ownerId => text().named('owner_id')(); + DateTimeColumn get capturedAtUtc => dateTime().named('captured_at_utc')(); + TextColumn get operationName => text().named('operation_name').nullable()(); + TextColumn get sourceDate => text().named('source_date').nullable()(); + TextColumn get targetDate => text().named('target_date').nullable()(); + TextColumn get windowJson => text().named('window_json')(); + TextColumn get tasksJson => text().named('tasks_json')(); + TextColumn get lockedIntervalsJson => text().named('locked_intervals_json')(); + TextColumn get requiredVisibleIntervalsJson => + text().named('required_visible_intervals_json')(); + TextColumn get noticesJson => text().named('notices_json')(); + TextColumn get changesJson => text().named('changes_json')(); + TextColumn get overlapsJson => text().named('overlaps_json')(); + DateTimeColumn get retentionExpiresUtc => + dateTime().named('retention_expires_utc')(); + BoolColumn get truncated => boolean()(); + IntColumn get revision => integer()(); + DateTimeColumn get createdAtUtc => dateTime().named('created_at_utc')(); + DateTimeColumn get updatedAtUtc => dateTime().named('updated_at_utc')(); + + @override + Set> get primaryKey => {id}; +} + +/// Generated task-row accessor. +@DriftAccessor(tables: [Tasks]) +class TaskDao extends DatabaseAccessor with _$TaskDaoMixin { + TaskDao(super.db); +} + +/// Generated project-row accessor. +@DriftAccessor(tables: [Projects]) +class ProjectDao extends DatabaseAccessor with _$ProjectDaoMixin { + ProjectDao(super.db); +} + +/// Generated locked-time accessors for blocks and date overrides. +@DriftAccessor(tables: [LockedBlocks, LockedOverrides]) +class LockedBlockDao extends DatabaseAccessor + with _$LockedBlockDaoMixin { + LockedBlockDao(super.db); +} + +/// Generated owner-settings accessor. +@DriftAccessor(tables: [SettingsTable]) +class SettingsDao extends DatabaseAccessor + with _$SettingsDaoMixin { + SettingsDao(super.db); +} + +/// Generated diagnostic snapshot accessor. +@DriftAccessor(tables: [Snapshots]) +class SnapshotDao extends DatabaseAccessor + with _$SnapshotDaoMixin { + SnapshotDao(super.db); +} + +/// Drift database for scheduler persistence. +@DriftDatabase( + tables: [ + Tasks, + Projects, + LockedBlocks, + LockedOverrides, + SettingsTable, + Snapshots, + ], + daos: [ + TaskDao, + ProjectDao, + LockedBlockDao, + SettingsDao, + SnapshotDao, + ], +) +class SchedulerDb extends _$SchedulerDb { + SchedulerDb(super.executor); + + @override + int get schemaVersion => 1; + + @override + MigrationStrategy get migration => MigrationStrategy( + onCreate: (Migrator migrator) async { + await migrator.createAll(); + }, + ); +} diff --git a/packages/scheduler_persistence_sqlite/lib/src/scheduler_db.g.dart b/packages/scheduler_persistence_sqlite/lib/src/scheduler_db.g.dart new file mode 100644 index 0000000..b597c1f --- /dev/null +++ b/packages/scheduler_persistence_sqlite/lib/src/scheduler_db.g.dart @@ -0,0 +1,6639 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'scheduler_db.dart'; + +// ignore_for_file: type=lint +mixin _$TaskDaoMixin on DatabaseAccessor { + $TasksTable get tasks => attachedDatabase.tasks; + TaskDaoManager get managers => TaskDaoManager(this); +} + +class TaskDaoManager { + final _$TaskDaoMixin _db; + TaskDaoManager(this._db); + $$TasksTableTableManager get tasks => + $$TasksTableTableManager(_db.attachedDatabase, _db.tasks); +} + +mixin _$ProjectDaoMixin on DatabaseAccessor { + $ProjectsTable get projects => attachedDatabase.projects; + ProjectDaoManager get managers => ProjectDaoManager(this); +} + +class ProjectDaoManager { + final _$ProjectDaoMixin _db; + ProjectDaoManager(this._db); + $$ProjectsTableTableManager get projects => + $$ProjectsTableTableManager(_db.attachedDatabase, _db.projects); +} + +mixin _$LockedBlockDaoMixin on DatabaseAccessor { + $LockedBlocksTable get lockedBlocks => attachedDatabase.lockedBlocks; + $LockedOverridesTable get lockedOverrides => attachedDatabase.lockedOverrides; + LockedBlockDaoManager get managers => LockedBlockDaoManager(this); +} + +class LockedBlockDaoManager { + final _$LockedBlockDaoMixin _db; + LockedBlockDaoManager(this._db); + $$LockedBlocksTableTableManager get lockedBlocks => + $$LockedBlocksTableTableManager(_db.attachedDatabase, _db.lockedBlocks); + $$LockedOverridesTableTableManager get lockedOverrides => + $$LockedOverridesTableTableManager( + _db.attachedDatabase, _db.lockedOverrides); +} + +mixin _$SettingsDaoMixin on DatabaseAccessor { + $SettingsTableTable get settingsTable => attachedDatabase.settingsTable; + SettingsDaoManager get managers => SettingsDaoManager(this); +} + +class SettingsDaoManager { + final _$SettingsDaoMixin _db; + SettingsDaoManager(this._db); + $$SettingsTableTableTableManager get settingsTable => + $$SettingsTableTableTableManager(_db.attachedDatabase, _db.settingsTable); +} + +mixin _$SnapshotDaoMixin on DatabaseAccessor { + $SnapshotsTable get snapshots => attachedDatabase.snapshots; + SnapshotDaoManager get managers => SnapshotDaoManager(this); +} + +class SnapshotDaoManager { + final _$SnapshotDaoMixin _db; + SnapshotDaoManager(this._db); + $$SnapshotsTableTableManager get snapshots => + $$SnapshotsTableTableManager(_db.attachedDatabase, _db.snapshots); +} + +class $TasksTable extends Tasks with TableInfo<$TasksTable, TaskRow> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $TasksTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _ownerIdMeta = + const VerificationMeta('ownerId'); + @override + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _titleMeta = const VerificationMeta('title'); + @override + late final GeneratedColumn title = GeneratedColumn( + 'title', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _projectIdMeta = + const VerificationMeta('projectId'); + @override + late final GeneratedColumn projectId = GeneratedColumn( + 'project_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _parentIdMeta = + const VerificationMeta('parentId'); + @override + late final GeneratedColumn parentId = GeneratedColumn( + 'parent_id', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _typeMeta = const VerificationMeta('type'); + @override + late final GeneratedColumn type = GeneratedColumn( + 'type', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _statusMeta = const VerificationMeta('status'); + @override + late final GeneratedColumn status = GeneratedColumn( + 'status', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _priorityMeta = + const VerificationMeta('priority'); + @override + late final GeneratedColumn priority = GeneratedColumn( + 'priority', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _rewardMeta = const VerificationMeta('reward'); + @override + late final GeneratedColumn reward = GeneratedColumn( + 'reward', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _difficultyMeta = + const VerificationMeta('difficulty'); + @override + late final GeneratedColumn difficulty = GeneratedColumn( + 'difficulty', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _durationMinutesMeta = + const VerificationMeta('durationMinutes'); + @override + late final GeneratedColumn durationMinutes = GeneratedColumn( + 'duration_minutes', aliasedName, true, + type: DriftSqlType.int, requiredDuringInsert: false); + static const VerificationMeta _scheduledStartUtcMeta = + const VerificationMeta('scheduledStartUtc'); + @override + late final GeneratedColumn scheduledStartUtc = + GeneratedColumn('scheduled_start_utc', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + static const VerificationMeta _scheduledEndUtcMeta = + const VerificationMeta('scheduledEndUtc'); + @override + late final GeneratedColumn scheduledEndUtc = + GeneratedColumn('scheduled_end_utc', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + static const VerificationMeta _actualStartUtcMeta = + const VerificationMeta('actualStartUtc'); + @override + late final GeneratedColumn actualStartUtc = + GeneratedColumn('actual_start_utc', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + static const VerificationMeta _actualEndUtcMeta = + const VerificationMeta('actualEndUtc'); + @override + late final GeneratedColumn actualEndUtc = GeneratedColumn( + 'actual_end_utc', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + static const VerificationMeta _completedAtUtcMeta = + const VerificationMeta('completedAtUtc'); + @override + late final GeneratedColumn completedAtUtc = + GeneratedColumn('completed_at_utc', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + static const VerificationMeta _backlogTagsJsonMeta = + const VerificationMeta('backlogTagsJson'); + @override + late final GeneratedColumn backlogTagsJson = GeneratedColumn( + 'backlog_tags_json', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _reminderOverrideMeta = + const VerificationMeta('reminderOverride'); + @override + late final GeneratedColumn reminderOverride = GeneratedColumn( + 'reminder_override', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _statsJsonMeta = + const VerificationMeta('statsJson'); + @override + late final GeneratedColumn statsJson = GeneratedColumn( + 'stats_json', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _backlogEnteredAtUtcMeta = + const VerificationMeta('backlogEnteredAtUtc'); + @override + late final GeneratedColumn backlogEnteredAtUtc = + GeneratedColumn('backlog_entered_at_utc', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + static const VerificationMeta _backlogEnteredProvenanceMeta = + const VerificationMeta('backlogEnteredProvenance'); + @override + late final GeneratedColumn backlogEnteredProvenance = + GeneratedColumn('backlog_entered_provenance', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _revisionMeta = + const VerificationMeta('revision'); + @override + late final GeneratedColumn revision = GeneratedColumn( + 'revision', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _createdAtUtcMeta = + const VerificationMeta('createdAtUtc'); + @override + late final GeneratedColumn createdAtUtc = GeneratedColumn( + 'created_at_utc', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + static const VerificationMeta _updatedAtUtcMeta = + const VerificationMeta('updatedAtUtc'); + @override + late final GeneratedColumn updatedAtUtc = GeneratedColumn( + 'updated_at_utc', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + @override + List get $columns => [ + id, + ownerId, + title, + projectId, + parentId, + type, + status, + priority, + reward, + difficulty, + durationMinutes, + scheduledStartUtc, + scheduledEndUtc, + actualStartUtc, + actualEndUtc, + completedAtUtc, + backlogTagsJson, + reminderOverride, + statsJson, + backlogEnteredAtUtc, + backlogEnteredProvenance, + revision, + createdAtUtc, + updatedAtUtc + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'tasks'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('owner_id')) { + context.handle(_ownerIdMeta, + ownerId.isAcceptableOrUnknown(data['owner_id']!, _ownerIdMeta)); + } else if (isInserting) { + context.missing(_ownerIdMeta); + } + if (data.containsKey('title')) { + context.handle( + _titleMeta, title.isAcceptableOrUnknown(data['title']!, _titleMeta)); + } else if (isInserting) { + context.missing(_titleMeta); + } + if (data.containsKey('project_id')) { + context.handle(_projectIdMeta, + projectId.isAcceptableOrUnknown(data['project_id']!, _projectIdMeta)); + } else if (isInserting) { + context.missing(_projectIdMeta); + } + if (data.containsKey('parent_id')) { + context.handle(_parentIdMeta, + parentId.isAcceptableOrUnknown(data['parent_id']!, _parentIdMeta)); + } + if (data.containsKey('type')) { + context.handle( + _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); + } else if (isInserting) { + context.missing(_typeMeta); + } + if (data.containsKey('status')) { + context.handle(_statusMeta, + status.isAcceptableOrUnknown(data['status']!, _statusMeta)); + } else if (isInserting) { + context.missing(_statusMeta); + } + if (data.containsKey('priority')) { + context.handle(_priorityMeta, + priority.isAcceptableOrUnknown(data['priority']!, _priorityMeta)); + } + if (data.containsKey('reward')) { + context.handle(_rewardMeta, + reward.isAcceptableOrUnknown(data['reward']!, _rewardMeta)); + } else if (isInserting) { + context.missing(_rewardMeta); + } + if (data.containsKey('difficulty')) { + context.handle( + _difficultyMeta, + difficulty.isAcceptableOrUnknown( + data['difficulty']!, _difficultyMeta)); + } else if (isInserting) { + context.missing(_difficultyMeta); + } + if (data.containsKey('duration_minutes')) { + context.handle( + _durationMinutesMeta, + durationMinutes.isAcceptableOrUnknown( + data['duration_minutes']!, _durationMinutesMeta)); + } + if (data.containsKey('scheduled_start_utc')) { + context.handle( + _scheduledStartUtcMeta, + scheduledStartUtc.isAcceptableOrUnknown( + data['scheduled_start_utc']!, _scheduledStartUtcMeta)); + } + if (data.containsKey('scheduled_end_utc')) { + context.handle( + _scheduledEndUtcMeta, + scheduledEndUtc.isAcceptableOrUnknown( + data['scheduled_end_utc']!, _scheduledEndUtcMeta)); + } + if (data.containsKey('actual_start_utc')) { + context.handle( + _actualStartUtcMeta, + actualStartUtc.isAcceptableOrUnknown( + data['actual_start_utc']!, _actualStartUtcMeta)); + } + if (data.containsKey('actual_end_utc')) { + context.handle( + _actualEndUtcMeta, + actualEndUtc.isAcceptableOrUnknown( + data['actual_end_utc']!, _actualEndUtcMeta)); + } + if (data.containsKey('completed_at_utc')) { + context.handle( + _completedAtUtcMeta, + completedAtUtc.isAcceptableOrUnknown( + data['completed_at_utc']!, _completedAtUtcMeta)); + } + if (data.containsKey('backlog_tags_json')) { + context.handle( + _backlogTagsJsonMeta, + backlogTagsJson.isAcceptableOrUnknown( + data['backlog_tags_json']!, _backlogTagsJsonMeta)); + } else if (isInserting) { + context.missing(_backlogTagsJsonMeta); + } + if (data.containsKey('reminder_override')) { + context.handle( + _reminderOverrideMeta, + reminderOverride.isAcceptableOrUnknown( + data['reminder_override']!, _reminderOverrideMeta)); + } + if (data.containsKey('stats_json')) { + context.handle(_statsJsonMeta, + statsJson.isAcceptableOrUnknown(data['stats_json']!, _statsJsonMeta)); + } else if (isInserting) { + context.missing(_statsJsonMeta); + } + if (data.containsKey('backlog_entered_at_utc')) { + context.handle( + _backlogEnteredAtUtcMeta, + backlogEnteredAtUtc.isAcceptableOrUnknown( + data['backlog_entered_at_utc']!, _backlogEnteredAtUtcMeta)); + } + if (data.containsKey('backlog_entered_provenance')) { + context.handle( + _backlogEnteredProvenanceMeta, + backlogEnteredProvenance.isAcceptableOrUnknown( + data['backlog_entered_provenance']!, + _backlogEnteredProvenanceMeta)); + } + if (data.containsKey('revision')) { + context.handle(_revisionMeta, + revision.isAcceptableOrUnknown(data['revision']!, _revisionMeta)); + } else if (isInserting) { + context.missing(_revisionMeta); + } + if (data.containsKey('created_at_utc')) { + context.handle( + _createdAtUtcMeta, + createdAtUtc.isAcceptableOrUnknown( + data['created_at_utc']!, _createdAtUtcMeta)); + } else if (isInserting) { + context.missing(_createdAtUtcMeta); + } + if (data.containsKey('updated_at_utc')) { + context.handle( + _updatedAtUtcMeta, + updatedAtUtc.isAcceptableOrUnknown( + data['updated_at_utc']!, _updatedAtUtcMeta)); + } else if (isInserting) { + context.missing(_updatedAtUtcMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + TaskRow map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TaskRow( + id: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}id'])!, + ownerId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}owner_id'])!, + title: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}title'])!, + projectId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}project_id'])!, + parentId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}parent_id']), + type: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}type'])!, + status: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}status'])!, + priority: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}priority']), + reward: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}reward'])!, + difficulty: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}difficulty'])!, + durationMinutes: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}duration_minutes']), + scheduledStartUtc: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}scheduled_start_utc']), + scheduledEndUtc: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}scheduled_end_utc']), + actualStartUtc: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}actual_start_utc']), + actualEndUtc: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}actual_end_utc']), + completedAtUtc: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}completed_at_utc']), + backlogTagsJson: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}backlog_tags_json'])!, + reminderOverride: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}reminder_override']), + statsJson: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}stats_json'])!, + backlogEnteredAtUtc: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}backlog_entered_at_utc']), + backlogEnteredProvenance: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}backlog_entered_provenance']), + revision: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}revision'])!, + createdAtUtc: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}created_at_utc'])!, + updatedAtUtc: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}updated_at_utc'])!, + ); + } + + @override + $TasksTable createAlias(String alias) { + return $TasksTable(attachedDatabase, alias); + } +} + +class TaskRow extends DataClass implements Insertable { + final String id; + final String ownerId; + final String title; + final String projectId; + final String? parentId; + final String type; + final String status; + final String? priority; + final String reward; + final String difficulty; + final int? durationMinutes; + final DateTime? scheduledStartUtc; + final DateTime? scheduledEndUtc; + final DateTime? actualStartUtc; + final DateTime? actualEndUtc; + final DateTime? completedAtUtc; + final String backlogTagsJson; + final String? reminderOverride; + final String statsJson; + final DateTime? backlogEnteredAtUtc; + final String? backlogEnteredProvenance; + final int revision; + final DateTime createdAtUtc; + final DateTime updatedAtUtc; + const TaskRow( + {required this.id, + required this.ownerId, + required this.title, + required this.projectId, + this.parentId, + required this.type, + required this.status, + this.priority, + required this.reward, + required this.difficulty, + this.durationMinutes, + this.scheduledStartUtc, + this.scheduledEndUtc, + this.actualStartUtc, + this.actualEndUtc, + this.completedAtUtc, + required this.backlogTagsJson, + this.reminderOverride, + required this.statsJson, + this.backlogEnteredAtUtc, + this.backlogEnteredProvenance, + required this.revision, + required this.createdAtUtc, + required this.updatedAtUtc}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['owner_id'] = Variable(ownerId); + map['title'] = Variable(title); + map['project_id'] = Variable(projectId); + if (!nullToAbsent || parentId != null) { + map['parent_id'] = Variable(parentId); + } + map['type'] = Variable(type); + map['status'] = Variable(status); + if (!nullToAbsent || priority != null) { + map['priority'] = Variable(priority); + } + map['reward'] = Variable(reward); + map['difficulty'] = Variable(difficulty); + if (!nullToAbsent || durationMinutes != null) { + map['duration_minutes'] = Variable(durationMinutes); + } + if (!nullToAbsent || scheduledStartUtc != null) { + map['scheduled_start_utc'] = Variable(scheduledStartUtc); + } + if (!nullToAbsent || scheduledEndUtc != null) { + map['scheduled_end_utc'] = Variable(scheduledEndUtc); + } + if (!nullToAbsent || actualStartUtc != null) { + map['actual_start_utc'] = Variable(actualStartUtc); + } + if (!nullToAbsent || actualEndUtc != null) { + map['actual_end_utc'] = Variable(actualEndUtc); + } + if (!nullToAbsent || completedAtUtc != null) { + map['completed_at_utc'] = Variable(completedAtUtc); + } + map['backlog_tags_json'] = Variable(backlogTagsJson); + if (!nullToAbsent || reminderOverride != null) { + map['reminder_override'] = Variable(reminderOverride); + } + map['stats_json'] = Variable(statsJson); + if (!nullToAbsent || backlogEnteredAtUtc != null) { + map['backlog_entered_at_utc'] = Variable(backlogEnteredAtUtc); + } + if (!nullToAbsent || backlogEnteredProvenance != null) { + map['backlog_entered_provenance'] = + Variable(backlogEnteredProvenance); + } + map['revision'] = Variable(revision); + map['created_at_utc'] = Variable(createdAtUtc); + map['updated_at_utc'] = Variable(updatedAtUtc); + return map; + } + + TasksCompanion toCompanion(bool nullToAbsent) { + return TasksCompanion( + id: Value(id), + ownerId: Value(ownerId), + title: Value(title), + projectId: Value(projectId), + parentId: parentId == null && nullToAbsent + ? const Value.absent() + : Value(parentId), + type: Value(type), + status: Value(status), + priority: priority == null && nullToAbsent + ? const Value.absent() + : Value(priority), + reward: Value(reward), + difficulty: Value(difficulty), + durationMinutes: durationMinutes == null && nullToAbsent + ? const Value.absent() + : Value(durationMinutes), + scheduledStartUtc: scheduledStartUtc == null && nullToAbsent + ? const Value.absent() + : Value(scheduledStartUtc), + scheduledEndUtc: scheduledEndUtc == null && nullToAbsent + ? const Value.absent() + : Value(scheduledEndUtc), + actualStartUtc: actualStartUtc == null && nullToAbsent + ? const Value.absent() + : Value(actualStartUtc), + actualEndUtc: actualEndUtc == null && nullToAbsent + ? const Value.absent() + : Value(actualEndUtc), + completedAtUtc: completedAtUtc == null && nullToAbsent + ? const Value.absent() + : Value(completedAtUtc), + backlogTagsJson: Value(backlogTagsJson), + reminderOverride: reminderOverride == null && nullToAbsent + ? const Value.absent() + : Value(reminderOverride), + statsJson: Value(statsJson), + backlogEnteredAtUtc: backlogEnteredAtUtc == null && nullToAbsent + ? const Value.absent() + : Value(backlogEnteredAtUtc), + backlogEnteredProvenance: backlogEnteredProvenance == null && nullToAbsent + ? const Value.absent() + : Value(backlogEnteredProvenance), + revision: Value(revision), + createdAtUtc: Value(createdAtUtc), + updatedAtUtc: Value(updatedAtUtc), + ); + } + + factory TaskRow.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TaskRow( + id: serializer.fromJson(json['id']), + ownerId: serializer.fromJson(json['ownerId']), + title: serializer.fromJson(json['title']), + projectId: serializer.fromJson(json['projectId']), + parentId: serializer.fromJson(json['parentId']), + type: serializer.fromJson(json['type']), + status: serializer.fromJson(json['status']), + priority: serializer.fromJson(json['priority']), + reward: serializer.fromJson(json['reward']), + difficulty: serializer.fromJson(json['difficulty']), + durationMinutes: serializer.fromJson(json['durationMinutes']), + scheduledStartUtc: + serializer.fromJson(json['scheduledStartUtc']), + scheduledEndUtc: serializer.fromJson(json['scheduledEndUtc']), + actualStartUtc: serializer.fromJson(json['actualStartUtc']), + actualEndUtc: serializer.fromJson(json['actualEndUtc']), + completedAtUtc: serializer.fromJson(json['completedAtUtc']), + backlogTagsJson: serializer.fromJson(json['backlogTagsJson']), + reminderOverride: serializer.fromJson(json['reminderOverride']), + statsJson: serializer.fromJson(json['statsJson']), + backlogEnteredAtUtc: + serializer.fromJson(json['backlogEnteredAtUtc']), + backlogEnteredProvenance: + serializer.fromJson(json['backlogEnteredProvenance']), + revision: serializer.fromJson(json['revision']), + createdAtUtc: serializer.fromJson(json['createdAtUtc']), + updatedAtUtc: serializer.fromJson(json['updatedAtUtc']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'ownerId': serializer.toJson(ownerId), + 'title': serializer.toJson(title), + 'projectId': serializer.toJson(projectId), + 'parentId': serializer.toJson(parentId), + 'type': serializer.toJson(type), + 'status': serializer.toJson(status), + 'priority': serializer.toJson(priority), + 'reward': serializer.toJson(reward), + 'difficulty': serializer.toJson(difficulty), + 'durationMinutes': serializer.toJson(durationMinutes), + 'scheduledStartUtc': serializer.toJson(scheduledStartUtc), + 'scheduledEndUtc': serializer.toJson(scheduledEndUtc), + 'actualStartUtc': serializer.toJson(actualStartUtc), + 'actualEndUtc': serializer.toJson(actualEndUtc), + 'completedAtUtc': serializer.toJson(completedAtUtc), + 'backlogTagsJson': serializer.toJson(backlogTagsJson), + 'reminderOverride': serializer.toJson(reminderOverride), + 'statsJson': serializer.toJson(statsJson), + 'backlogEnteredAtUtc': serializer.toJson(backlogEnteredAtUtc), + 'backlogEnteredProvenance': + serializer.toJson(backlogEnteredProvenance), + 'revision': serializer.toJson(revision), + 'createdAtUtc': serializer.toJson(createdAtUtc), + 'updatedAtUtc': serializer.toJson(updatedAtUtc), + }; + } + + TaskRow copyWith( + {String? id, + String? ownerId, + String? title, + String? projectId, + Value parentId = const Value.absent(), + String? type, + String? status, + Value priority = const Value.absent(), + String? reward, + String? difficulty, + Value durationMinutes = const Value.absent(), + Value scheduledStartUtc = const Value.absent(), + Value scheduledEndUtc = const Value.absent(), + Value actualStartUtc = const Value.absent(), + Value actualEndUtc = const Value.absent(), + Value completedAtUtc = const Value.absent(), + String? backlogTagsJson, + Value reminderOverride = const Value.absent(), + String? statsJson, + Value backlogEnteredAtUtc = const Value.absent(), + Value backlogEnteredProvenance = const Value.absent(), + int? revision, + DateTime? createdAtUtc, + DateTime? updatedAtUtc}) => + TaskRow( + id: id ?? this.id, + ownerId: ownerId ?? this.ownerId, + title: title ?? this.title, + projectId: projectId ?? this.projectId, + parentId: parentId.present ? parentId.value : this.parentId, + type: type ?? this.type, + status: status ?? this.status, + priority: priority.present ? priority.value : this.priority, + reward: reward ?? this.reward, + difficulty: difficulty ?? this.difficulty, + durationMinutes: durationMinutes.present + ? durationMinutes.value + : this.durationMinutes, + scheduledStartUtc: scheduledStartUtc.present + ? scheduledStartUtc.value + : this.scheduledStartUtc, + scheduledEndUtc: scheduledEndUtc.present + ? scheduledEndUtc.value + : this.scheduledEndUtc, + actualStartUtc: + actualStartUtc.present ? actualStartUtc.value : this.actualStartUtc, + actualEndUtc: + actualEndUtc.present ? actualEndUtc.value : this.actualEndUtc, + completedAtUtc: + completedAtUtc.present ? completedAtUtc.value : this.completedAtUtc, + backlogTagsJson: backlogTagsJson ?? this.backlogTagsJson, + reminderOverride: reminderOverride.present + ? reminderOverride.value + : this.reminderOverride, + statsJson: statsJson ?? this.statsJson, + backlogEnteredAtUtc: backlogEnteredAtUtc.present + ? backlogEnteredAtUtc.value + : this.backlogEnteredAtUtc, + backlogEnteredProvenance: backlogEnteredProvenance.present + ? backlogEnteredProvenance.value + : this.backlogEnteredProvenance, + revision: revision ?? this.revision, + createdAtUtc: createdAtUtc ?? this.createdAtUtc, + updatedAtUtc: updatedAtUtc ?? this.updatedAtUtc, + ); + TaskRow copyWithCompanion(TasksCompanion data) { + return TaskRow( + id: data.id.present ? data.id.value : this.id, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + title: data.title.present ? data.title.value : this.title, + projectId: data.projectId.present ? data.projectId.value : this.projectId, + parentId: data.parentId.present ? data.parentId.value : this.parentId, + type: data.type.present ? data.type.value : this.type, + status: data.status.present ? data.status.value : this.status, + priority: data.priority.present ? data.priority.value : this.priority, + reward: data.reward.present ? data.reward.value : this.reward, + difficulty: + data.difficulty.present ? data.difficulty.value : this.difficulty, + durationMinutes: data.durationMinutes.present + ? data.durationMinutes.value + : this.durationMinutes, + scheduledStartUtc: data.scheduledStartUtc.present + ? data.scheduledStartUtc.value + : this.scheduledStartUtc, + scheduledEndUtc: data.scheduledEndUtc.present + ? data.scheduledEndUtc.value + : this.scheduledEndUtc, + actualStartUtc: data.actualStartUtc.present + ? data.actualStartUtc.value + : this.actualStartUtc, + actualEndUtc: data.actualEndUtc.present + ? data.actualEndUtc.value + : this.actualEndUtc, + completedAtUtc: data.completedAtUtc.present + ? data.completedAtUtc.value + : this.completedAtUtc, + backlogTagsJson: data.backlogTagsJson.present + ? data.backlogTagsJson.value + : this.backlogTagsJson, + reminderOverride: data.reminderOverride.present + ? data.reminderOverride.value + : this.reminderOverride, + statsJson: data.statsJson.present ? data.statsJson.value : this.statsJson, + backlogEnteredAtUtc: data.backlogEnteredAtUtc.present + ? data.backlogEnteredAtUtc.value + : this.backlogEnteredAtUtc, + backlogEnteredProvenance: data.backlogEnteredProvenance.present + ? data.backlogEnteredProvenance.value + : this.backlogEnteredProvenance, + revision: data.revision.present ? data.revision.value : this.revision, + createdAtUtc: data.createdAtUtc.present + ? data.createdAtUtc.value + : this.createdAtUtc, + updatedAtUtc: data.updatedAtUtc.present + ? data.updatedAtUtc.value + : this.updatedAtUtc, + ); + } + + @override + String toString() { + return (StringBuffer('TaskRow(') + ..write('id: $id, ') + ..write('ownerId: $ownerId, ') + ..write('title: $title, ') + ..write('projectId: $projectId, ') + ..write('parentId: $parentId, ') + ..write('type: $type, ') + ..write('status: $status, ') + ..write('priority: $priority, ') + ..write('reward: $reward, ') + ..write('difficulty: $difficulty, ') + ..write('durationMinutes: $durationMinutes, ') + ..write('scheduledStartUtc: $scheduledStartUtc, ') + ..write('scheduledEndUtc: $scheduledEndUtc, ') + ..write('actualStartUtc: $actualStartUtc, ') + ..write('actualEndUtc: $actualEndUtc, ') + ..write('completedAtUtc: $completedAtUtc, ') + ..write('backlogTagsJson: $backlogTagsJson, ') + ..write('reminderOverride: $reminderOverride, ') + ..write('statsJson: $statsJson, ') + ..write('backlogEnteredAtUtc: $backlogEnteredAtUtc, ') + ..write('backlogEnteredProvenance: $backlogEnteredProvenance, ') + ..write('revision: $revision, ') + ..write('createdAtUtc: $createdAtUtc, ') + ..write('updatedAtUtc: $updatedAtUtc') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + id, + ownerId, + title, + projectId, + parentId, + type, + status, + priority, + reward, + difficulty, + durationMinutes, + scheduledStartUtc, + scheduledEndUtc, + actualStartUtc, + actualEndUtc, + completedAtUtc, + backlogTagsJson, + reminderOverride, + statsJson, + backlogEnteredAtUtc, + backlogEnteredProvenance, + revision, + createdAtUtc, + updatedAtUtc + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TaskRow && + other.id == this.id && + other.ownerId == this.ownerId && + other.title == this.title && + other.projectId == this.projectId && + other.parentId == this.parentId && + other.type == this.type && + other.status == this.status && + other.priority == this.priority && + other.reward == this.reward && + other.difficulty == this.difficulty && + other.durationMinutes == this.durationMinutes && + other.scheduledStartUtc == this.scheduledStartUtc && + other.scheduledEndUtc == this.scheduledEndUtc && + other.actualStartUtc == this.actualStartUtc && + other.actualEndUtc == this.actualEndUtc && + other.completedAtUtc == this.completedAtUtc && + other.backlogTagsJson == this.backlogTagsJson && + other.reminderOverride == this.reminderOverride && + other.statsJson == this.statsJson && + other.backlogEnteredAtUtc == this.backlogEnteredAtUtc && + other.backlogEnteredProvenance == this.backlogEnteredProvenance && + other.revision == this.revision && + other.createdAtUtc == this.createdAtUtc && + other.updatedAtUtc == this.updatedAtUtc); +} + +class TasksCompanion extends UpdateCompanion { + final Value id; + final Value ownerId; + final Value title; + final Value projectId; + final Value parentId; + final Value type; + final Value status; + final Value priority; + final Value reward; + final Value difficulty; + final Value durationMinutes; + final Value scheduledStartUtc; + final Value scheduledEndUtc; + final Value actualStartUtc; + final Value actualEndUtc; + final Value completedAtUtc; + final Value backlogTagsJson; + final Value reminderOverride; + final Value statsJson; + final Value backlogEnteredAtUtc; + final Value backlogEnteredProvenance; + final Value revision; + final Value createdAtUtc; + final Value updatedAtUtc; + final Value rowid; + const TasksCompanion({ + this.id = const Value.absent(), + this.ownerId = const Value.absent(), + this.title = const Value.absent(), + this.projectId = const Value.absent(), + this.parentId = const Value.absent(), + this.type = const Value.absent(), + this.status = const Value.absent(), + this.priority = const Value.absent(), + this.reward = const Value.absent(), + this.difficulty = const Value.absent(), + this.durationMinutes = const Value.absent(), + this.scheduledStartUtc = const Value.absent(), + this.scheduledEndUtc = const Value.absent(), + this.actualStartUtc = const Value.absent(), + this.actualEndUtc = const Value.absent(), + this.completedAtUtc = const Value.absent(), + this.backlogTagsJson = const Value.absent(), + this.reminderOverride = const Value.absent(), + this.statsJson = const Value.absent(), + this.backlogEnteredAtUtc = const Value.absent(), + this.backlogEnteredProvenance = const Value.absent(), + this.revision = const Value.absent(), + this.createdAtUtc = const Value.absent(), + this.updatedAtUtc = const Value.absent(), + this.rowid = const Value.absent(), + }); + TasksCompanion.insert({ + required String id, + required String ownerId, + required String title, + required String projectId, + this.parentId = const Value.absent(), + required String type, + required String status, + this.priority = const Value.absent(), + required String reward, + required String difficulty, + this.durationMinutes = const Value.absent(), + this.scheduledStartUtc = const Value.absent(), + this.scheduledEndUtc = const Value.absent(), + this.actualStartUtc = const Value.absent(), + this.actualEndUtc = const Value.absent(), + this.completedAtUtc = const Value.absent(), + required String backlogTagsJson, + this.reminderOverride = const Value.absent(), + required String statsJson, + this.backlogEnteredAtUtc = const Value.absent(), + this.backlogEnteredProvenance = const Value.absent(), + required int revision, + required DateTime createdAtUtc, + required DateTime updatedAtUtc, + this.rowid = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + title = Value(title), + projectId = Value(projectId), + type = Value(type), + status = Value(status), + reward = Value(reward), + difficulty = Value(difficulty), + backlogTagsJson = Value(backlogTagsJson), + statsJson = Value(statsJson), + revision = Value(revision), + createdAtUtc = Value(createdAtUtc), + updatedAtUtc = Value(updatedAtUtc); + static Insertable custom({ + Expression? id, + Expression? ownerId, + Expression? title, + Expression? projectId, + Expression? parentId, + Expression? type, + Expression? status, + Expression? priority, + Expression? reward, + Expression? difficulty, + Expression? durationMinutes, + Expression? scheduledStartUtc, + Expression? scheduledEndUtc, + Expression? actualStartUtc, + Expression? actualEndUtc, + Expression? completedAtUtc, + Expression? backlogTagsJson, + Expression? reminderOverride, + Expression? statsJson, + Expression? backlogEnteredAtUtc, + Expression? backlogEnteredProvenance, + Expression? revision, + Expression? createdAtUtc, + Expression? updatedAtUtc, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (ownerId != null) 'owner_id': ownerId, + if (title != null) 'title': title, + if (projectId != null) 'project_id': projectId, + if (parentId != null) 'parent_id': parentId, + if (type != null) 'type': type, + if (status != null) 'status': status, + if (priority != null) 'priority': priority, + if (reward != null) 'reward': reward, + if (difficulty != null) 'difficulty': difficulty, + if (durationMinutes != null) 'duration_minutes': durationMinutes, + if (scheduledStartUtc != null) 'scheduled_start_utc': scheduledStartUtc, + if (scheduledEndUtc != null) 'scheduled_end_utc': scheduledEndUtc, + if (actualStartUtc != null) 'actual_start_utc': actualStartUtc, + if (actualEndUtc != null) 'actual_end_utc': actualEndUtc, + if (completedAtUtc != null) 'completed_at_utc': completedAtUtc, + if (backlogTagsJson != null) 'backlog_tags_json': backlogTagsJson, + if (reminderOverride != null) 'reminder_override': reminderOverride, + if (statsJson != null) 'stats_json': statsJson, + if (backlogEnteredAtUtc != null) + 'backlog_entered_at_utc': backlogEnteredAtUtc, + if (backlogEnteredProvenance != null) + 'backlog_entered_provenance': backlogEnteredProvenance, + if (revision != null) 'revision': revision, + if (createdAtUtc != null) 'created_at_utc': createdAtUtc, + if (updatedAtUtc != null) 'updated_at_utc': updatedAtUtc, + if (rowid != null) 'rowid': rowid, + }); + } + + TasksCompanion copyWith( + {Value? id, + Value? ownerId, + Value? title, + Value? projectId, + Value? parentId, + Value? type, + Value? status, + Value? priority, + Value? reward, + Value? difficulty, + Value? durationMinutes, + Value? scheduledStartUtc, + Value? scheduledEndUtc, + Value? actualStartUtc, + Value? actualEndUtc, + Value? completedAtUtc, + Value? backlogTagsJson, + Value? reminderOverride, + Value? statsJson, + Value? backlogEnteredAtUtc, + Value? backlogEnteredProvenance, + Value? revision, + Value? createdAtUtc, + Value? updatedAtUtc, + Value? rowid}) { + return TasksCompanion( + id: id ?? this.id, + ownerId: ownerId ?? this.ownerId, + title: title ?? this.title, + projectId: projectId ?? this.projectId, + parentId: parentId ?? this.parentId, + type: type ?? this.type, + status: status ?? this.status, + priority: priority ?? this.priority, + reward: reward ?? this.reward, + difficulty: difficulty ?? this.difficulty, + durationMinutes: durationMinutes ?? this.durationMinutes, + scheduledStartUtc: scheduledStartUtc ?? this.scheduledStartUtc, + scheduledEndUtc: scheduledEndUtc ?? this.scheduledEndUtc, + actualStartUtc: actualStartUtc ?? this.actualStartUtc, + actualEndUtc: actualEndUtc ?? this.actualEndUtc, + completedAtUtc: completedAtUtc ?? this.completedAtUtc, + backlogTagsJson: backlogTagsJson ?? this.backlogTagsJson, + reminderOverride: reminderOverride ?? this.reminderOverride, + statsJson: statsJson ?? this.statsJson, + backlogEnteredAtUtc: backlogEnteredAtUtc ?? this.backlogEnteredAtUtc, + backlogEnteredProvenance: + backlogEnteredProvenance ?? this.backlogEnteredProvenance, + revision: revision ?? this.revision, + createdAtUtc: createdAtUtc ?? this.createdAtUtc, + updatedAtUtc: updatedAtUtc ?? this.updatedAtUtc, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (title.present) { + map['title'] = Variable(title.value); + } + if (projectId.present) { + map['project_id'] = Variable(projectId.value); + } + if (parentId.present) { + map['parent_id'] = Variable(parentId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (status.present) { + map['status'] = Variable(status.value); + } + if (priority.present) { + map['priority'] = Variable(priority.value); + } + if (reward.present) { + map['reward'] = Variable(reward.value); + } + if (difficulty.present) { + map['difficulty'] = Variable(difficulty.value); + } + if (durationMinutes.present) { + map['duration_minutes'] = Variable(durationMinutes.value); + } + if (scheduledStartUtc.present) { + map['scheduled_start_utc'] = Variable(scheduledStartUtc.value); + } + if (scheduledEndUtc.present) { + map['scheduled_end_utc'] = Variable(scheduledEndUtc.value); + } + if (actualStartUtc.present) { + map['actual_start_utc'] = Variable(actualStartUtc.value); + } + if (actualEndUtc.present) { + map['actual_end_utc'] = Variable(actualEndUtc.value); + } + if (completedAtUtc.present) { + map['completed_at_utc'] = Variable(completedAtUtc.value); + } + if (backlogTagsJson.present) { + map['backlog_tags_json'] = Variable(backlogTagsJson.value); + } + if (reminderOverride.present) { + map['reminder_override'] = Variable(reminderOverride.value); + } + if (statsJson.present) { + map['stats_json'] = Variable(statsJson.value); + } + if (backlogEnteredAtUtc.present) { + map['backlog_entered_at_utc'] = + Variable(backlogEnteredAtUtc.value); + } + if (backlogEnteredProvenance.present) { + map['backlog_entered_provenance'] = + Variable(backlogEnteredProvenance.value); + } + if (revision.present) { + map['revision'] = Variable(revision.value); + } + if (createdAtUtc.present) { + map['created_at_utc'] = Variable(createdAtUtc.value); + } + if (updatedAtUtc.present) { + map['updated_at_utc'] = Variable(updatedAtUtc.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TasksCompanion(') + ..write('id: $id, ') + ..write('ownerId: $ownerId, ') + ..write('title: $title, ') + ..write('projectId: $projectId, ') + ..write('parentId: $parentId, ') + ..write('type: $type, ') + ..write('status: $status, ') + ..write('priority: $priority, ') + ..write('reward: $reward, ') + ..write('difficulty: $difficulty, ') + ..write('durationMinutes: $durationMinutes, ') + ..write('scheduledStartUtc: $scheduledStartUtc, ') + ..write('scheduledEndUtc: $scheduledEndUtc, ') + ..write('actualStartUtc: $actualStartUtc, ') + ..write('actualEndUtc: $actualEndUtc, ') + ..write('completedAtUtc: $completedAtUtc, ') + ..write('backlogTagsJson: $backlogTagsJson, ') + ..write('reminderOverride: $reminderOverride, ') + ..write('statsJson: $statsJson, ') + ..write('backlogEnteredAtUtc: $backlogEnteredAtUtc, ') + ..write('backlogEnteredProvenance: $backlogEnteredProvenance, ') + ..write('revision: $revision, ') + ..write('createdAtUtc: $createdAtUtc, ') + ..write('updatedAtUtc: $updatedAtUtc, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $ProjectsTable extends Projects + with TableInfo<$ProjectsTable, ProjectRow> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $ProjectsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _ownerIdMeta = + const VerificationMeta('ownerId'); + @override + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _colorKeyMeta = + const VerificationMeta('colorKey'); + @override + late final GeneratedColumn colorKey = GeneratedColumn( + 'color_key', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _defaultPriorityMeta = + const VerificationMeta('defaultPriority'); + @override + late final GeneratedColumn defaultPriority = GeneratedColumn( + 'default_priority', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _defaultRewardMeta = + const VerificationMeta('defaultReward'); + @override + late final GeneratedColumn defaultReward = GeneratedColumn( + 'default_reward', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _defaultDifficultyMeta = + const VerificationMeta('defaultDifficulty'); + @override + late final GeneratedColumn defaultDifficulty = + GeneratedColumn('default_difficulty', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _defaultReminderProfileMeta = + const VerificationMeta('defaultReminderProfile'); + @override + late final GeneratedColumn defaultReminderProfile = + GeneratedColumn('default_reminder_profile', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _defaultDurationMinutesMeta = + const VerificationMeta('defaultDurationMinutes'); + @override + late final GeneratedColumn defaultDurationMinutes = GeneratedColumn( + 'default_duration_minutes', aliasedName, true, + type: DriftSqlType.int, requiredDuringInsert: false); + static const VerificationMeta _archivedAtUtcMeta = + const VerificationMeta('archivedAtUtc'); + @override + late final GeneratedColumn archivedAtUtc = + GeneratedColumn('archived_at_utc', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + static const VerificationMeta _revisionMeta = + const VerificationMeta('revision'); + @override + late final GeneratedColumn revision = GeneratedColumn( + 'revision', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _createdAtUtcMeta = + const VerificationMeta('createdAtUtc'); + @override + late final GeneratedColumn createdAtUtc = GeneratedColumn( + 'created_at_utc', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + static const VerificationMeta _updatedAtUtcMeta = + const VerificationMeta('updatedAtUtc'); + @override + late final GeneratedColumn updatedAtUtc = GeneratedColumn( + 'updated_at_utc', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + @override + List get $columns => [ + id, + ownerId, + name, + colorKey, + defaultPriority, + defaultReward, + defaultDifficulty, + defaultReminderProfile, + defaultDurationMinutes, + archivedAtUtc, + revision, + createdAtUtc, + updatedAtUtc + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'projects'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('owner_id')) { + context.handle(_ownerIdMeta, + ownerId.isAcceptableOrUnknown(data['owner_id']!, _ownerIdMeta)); + } else if (isInserting) { + context.missing(_ownerIdMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta)); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('color_key')) { + context.handle(_colorKeyMeta, + colorKey.isAcceptableOrUnknown(data['color_key']!, _colorKeyMeta)); + } else if (isInserting) { + context.missing(_colorKeyMeta); + } + if (data.containsKey('default_priority')) { + context.handle( + _defaultPriorityMeta, + defaultPriority.isAcceptableOrUnknown( + data['default_priority']!, _defaultPriorityMeta)); + } else if (isInserting) { + context.missing(_defaultPriorityMeta); + } + if (data.containsKey('default_reward')) { + context.handle( + _defaultRewardMeta, + defaultReward.isAcceptableOrUnknown( + data['default_reward']!, _defaultRewardMeta)); + } else if (isInserting) { + context.missing(_defaultRewardMeta); + } + if (data.containsKey('default_difficulty')) { + context.handle( + _defaultDifficultyMeta, + defaultDifficulty.isAcceptableOrUnknown( + data['default_difficulty']!, _defaultDifficultyMeta)); + } else if (isInserting) { + context.missing(_defaultDifficultyMeta); + } + if (data.containsKey('default_reminder_profile')) { + context.handle( + _defaultReminderProfileMeta, + defaultReminderProfile.isAcceptableOrUnknown( + data['default_reminder_profile']!, _defaultReminderProfileMeta)); + } else if (isInserting) { + context.missing(_defaultReminderProfileMeta); + } + if (data.containsKey('default_duration_minutes')) { + context.handle( + _defaultDurationMinutesMeta, + defaultDurationMinutes.isAcceptableOrUnknown( + data['default_duration_minutes']!, _defaultDurationMinutesMeta)); + } + if (data.containsKey('archived_at_utc')) { + context.handle( + _archivedAtUtcMeta, + archivedAtUtc.isAcceptableOrUnknown( + data['archived_at_utc']!, _archivedAtUtcMeta)); + } + if (data.containsKey('revision')) { + context.handle(_revisionMeta, + revision.isAcceptableOrUnknown(data['revision']!, _revisionMeta)); + } else if (isInserting) { + context.missing(_revisionMeta); + } + if (data.containsKey('created_at_utc')) { + context.handle( + _createdAtUtcMeta, + createdAtUtc.isAcceptableOrUnknown( + data['created_at_utc']!, _createdAtUtcMeta)); + } else if (isInserting) { + context.missing(_createdAtUtcMeta); + } + if (data.containsKey('updated_at_utc')) { + context.handle( + _updatedAtUtcMeta, + updatedAtUtc.isAcceptableOrUnknown( + data['updated_at_utc']!, _updatedAtUtcMeta)); + } else if (isInserting) { + context.missing(_updatedAtUtcMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + ProjectRow map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ProjectRow( + id: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}id'])!, + ownerId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}owner_id'])!, + name: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}name'])!, + colorKey: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}color_key'])!, + defaultPriority: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}default_priority'])!, + defaultReward: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}default_reward'])!, + defaultDifficulty: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}default_difficulty'])!, + defaultReminderProfile: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}default_reminder_profile'])!, + defaultDurationMinutes: attachedDatabase.typeMapping.read( + DriftSqlType.int, data['${effectivePrefix}default_duration_minutes']), + archivedAtUtc: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}archived_at_utc']), + revision: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}revision'])!, + createdAtUtc: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}created_at_utc'])!, + updatedAtUtc: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}updated_at_utc'])!, + ); + } + + @override + $ProjectsTable createAlias(String alias) { + return $ProjectsTable(attachedDatabase, alias); + } +} + +class ProjectRow extends DataClass implements Insertable { + final String id; + final String ownerId; + final String name; + final String colorKey; + final String defaultPriority; + final String defaultReward; + final String defaultDifficulty; + final String defaultReminderProfile; + final int? defaultDurationMinutes; + final DateTime? archivedAtUtc; + final int revision; + final DateTime createdAtUtc; + final DateTime updatedAtUtc; + const ProjectRow( + {required this.id, + required this.ownerId, + required this.name, + required this.colorKey, + required this.defaultPriority, + required this.defaultReward, + required this.defaultDifficulty, + required this.defaultReminderProfile, + this.defaultDurationMinutes, + this.archivedAtUtc, + required this.revision, + required this.createdAtUtc, + required this.updatedAtUtc}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + map['color_key'] = Variable(colorKey); + map['default_priority'] = Variable(defaultPriority); + map['default_reward'] = Variable(defaultReward); + map['default_difficulty'] = Variable(defaultDifficulty); + map['default_reminder_profile'] = Variable(defaultReminderProfile); + if (!nullToAbsent || defaultDurationMinutes != null) { + map['default_duration_minutes'] = Variable(defaultDurationMinutes); + } + if (!nullToAbsent || archivedAtUtc != null) { + map['archived_at_utc'] = Variable(archivedAtUtc); + } + map['revision'] = Variable(revision); + map['created_at_utc'] = Variable(createdAtUtc); + map['updated_at_utc'] = Variable(updatedAtUtc); + return map; + } + + ProjectsCompanion toCompanion(bool nullToAbsent) { + return ProjectsCompanion( + id: Value(id), + ownerId: Value(ownerId), + name: Value(name), + colorKey: Value(colorKey), + defaultPriority: Value(defaultPriority), + defaultReward: Value(defaultReward), + defaultDifficulty: Value(defaultDifficulty), + defaultReminderProfile: Value(defaultReminderProfile), + defaultDurationMinutes: defaultDurationMinutes == null && nullToAbsent + ? const Value.absent() + : Value(defaultDurationMinutes), + archivedAtUtc: archivedAtUtc == null && nullToAbsent + ? const Value.absent() + : Value(archivedAtUtc), + revision: Value(revision), + createdAtUtc: Value(createdAtUtc), + updatedAtUtc: Value(updatedAtUtc), + ); + } + + factory ProjectRow.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ProjectRow( + id: serializer.fromJson(json['id']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + colorKey: serializer.fromJson(json['colorKey']), + defaultPriority: serializer.fromJson(json['defaultPriority']), + defaultReward: serializer.fromJson(json['defaultReward']), + defaultDifficulty: serializer.fromJson(json['defaultDifficulty']), + defaultReminderProfile: + serializer.fromJson(json['defaultReminderProfile']), + defaultDurationMinutes: + serializer.fromJson(json['defaultDurationMinutes']), + archivedAtUtc: serializer.fromJson(json['archivedAtUtc']), + revision: serializer.fromJson(json['revision']), + createdAtUtc: serializer.fromJson(json['createdAtUtc']), + updatedAtUtc: serializer.fromJson(json['updatedAtUtc']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'colorKey': serializer.toJson(colorKey), + 'defaultPriority': serializer.toJson(defaultPriority), + 'defaultReward': serializer.toJson(defaultReward), + 'defaultDifficulty': serializer.toJson(defaultDifficulty), + 'defaultReminderProfile': + serializer.toJson(defaultReminderProfile), + 'defaultDurationMinutes': serializer.toJson(defaultDurationMinutes), + 'archivedAtUtc': serializer.toJson(archivedAtUtc), + 'revision': serializer.toJson(revision), + 'createdAtUtc': serializer.toJson(createdAtUtc), + 'updatedAtUtc': serializer.toJson(updatedAtUtc), + }; + } + + ProjectRow copyWith( + {String? id, + String? ownerId, + String? name, + String? colorKey, + String? defaultPriority, + String? defaultReward, + String? defaultDifficulty, + String? defaultReminderProfile, + Value defaultDurationMinutes = const Value.absent(), + Value archivedAtUtc = const Value.absent(), + int? revision, + DateTime? createdAtUtc, + DateTime? updatedAtUtc}) => + ProjectRow( + id: id ?? this.id, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + colorKey: colorKey ?? this.colorKey, + defaultPriority: defaultPriority ?? this.defaultPriority, + defaultReward: defaultReward ?? this.defaultReward, + defaultDifficulty: defaultDifficulty ?? this.defaultDifficulty, + defaultReminderProfile: + defaultReminderProfile ?? this.defaultReminderProfile, + defaultDurationMinutes: defaultDurationMinutes.present + ? defaultDurationMinutes.value + : this.defaultDurationMinutes, + archivedAtUtc: + archivedAtUtc.present ? archivedAtUtc.value : this.archivedAtUtc, + revision: revision ?? this.revision, + createdAtUtc: createdAtUtc ?? this.createdAtUtc, + updatedAtUtc: updatedAtUtc ?? this.updatedAtUtc, + ); + ProjectRow copyWithCompanion(ProjectsCompanion data) { + return ProjectRow( + id: data.id.present ? data.id.value : this.id, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + colorKey: data.colorKey.present ? data.colorKey.value : this.colorKey, + defaultPriority: data.defaultPriority.present + ? data.defaultPriority.value + : this.defaultPriority, + defaultReward: data.defaultReward.present + ? data.defaultReward.value + : this.defaultReward, + defaultDifficulty: data.defaultDifficulty.present + ? data.defaultDifficulty.value + : this.defaultDifficulty, + defaultReminderProfile: data.defaultReminderProfile.present + ? data.defaultReminderProfile.value + : this.defaultReminderProfile, + defaultDurationMinutes: data.defaultDurationMinutes.present + ? data.defaultDurationMinutes.value + : this.defaultDurationMinutes, + archivedAtUtc: data.archivedAtUtc.present + ? data.archivedAtUtc.value + : this.archivedAtUtc, + revision: data.revision.present ? data.revision.value : this.revision, + createdAtUtc: data.createdAtUtc.present + ? data.createdAtUtc.value + : this.createdAtUtc, + updatedAtUtc: data.updatedAtUtc.present + ? data.updatedAtUtc.value + : this.updatedAtUtc, + ); + } + + @override + String toString() { + return (StringBuffer('ProjectRow(') + ..write('id: $id, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('colorKey: $colorKey, ') + ..write('defaultPriority: $defaultPriority, ') + ..write('defaultReward: $defaultReward, ') + ..write('defaultDifficulty: $defaultDifficulty, ') + ..write('defaultReminderProfile: $defaultReminderProfile, ') + ..write('defaultDurationMinutes: $defaultDurationMinutes, ') + ..write('archivedAtUtc: $archivedAtUtc, ') + ..write('revision: $revision, ') + ..write('createdAtUtc: $createdAtUtc, ') + ..write('updatedAtUtc: $updatedAtUtc') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + ownerId, + name, + colorKey, + defaultPriority, + defaultReward, + defaultDifficulty, + defaultReminderProfile, + defaultDurationMinutes, + archivedAtUtc, + revision, + createdAtUtc, + updatedAtUtc); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ProjectRow && + other.id == this.id && + other.ownerId == this.ownerId && + other.name == this.name && + other.colorKey == this.colorKey && + other.defaultPriority == this.defaultPriority && + other.defaultReward == this.defaultReward && + other.defaultDifficulty == this.defaultDifficulty && + other.defaultReminderProfile == this.defaultReminderProfile && + other.defaultDurationMinutes == this.defaultDurationMinutes && + other.archivedAtUtc == this.archivedAtUtc && + other.revision == this.revision && + other.createdAtUtc == this.createdAtUtc && + other.updatedAtUtc == this.updatedAtUtc); +} + +class ProjectsCompanion extends UpdateCompanion { + final Value id; + final Value ownerId; + final Value name; + final Value colorKey; + final Value defaultPriority; + final Value defaultReward; + final Value defaultDifficulty; + final Value defaultReminderProfile; + final Value defaultDurationMinutes; + final Value archivedAtUtc; + final Value revision; + final Value createdAtUtc; + final Value updatedAtUtc; + final Value rowid; + const ProjectsCompanion({ + this.id = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.colorKey = const Value.absent(), + this.defaultPriority = const Value.absent(), + this.defaultReward = const Value.absent(), + this.defaultDifficulty = const Value.absent(), + this.defaultReminderProfile = const Value.absent(), + this.defaultDurationMinutes = const Value.absent(), + this.archivedAtUtc = const Value.absent(), + this.revision = const Value.absent(), + this.createdAtUtc = const Value.absent(), + this.updatedAtUtc = const Value.absent(), + this.rowid = const Value.absent(), + }); + ProjectsCompanion.insert({ + required String id, + required String ownerId, + required String name, + required String colorKey, + required String defaultPriority, + required String defaultReward, + required String defaultDifficulty, + required String defaultReminderProfile, + this.defaultDurationMinutes = const Value.absent(), + this.archivedAtUtc = const Value.absent(), + required int revision, + required DateTime createdAtUtc, + required DateTime updatedAtUtc, + this.rowid = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + colorKey = Value(colorKey), + defaultPriority = Value(defaultPriority), + defaultReward = Value(defaultReward), + defaultDifficulty = Value(defaultDifficulty), + defaultReminderProfile = Value(defaultReminderProfile), + revision = Value(revision), + createdAtUtc = Value(createdAtUtc), + updatedAtUtc = Value(updatedAtUtc); + static Insertable custom({ + Expression? id, + Expression? ownerId, + Expression? name, + Expression? colorKey, + Expression? defaultPriority, + Expression? defaultReward, + Expression? defaultDifficulty, + Expression? defaultReminderProfile, + Expression? defaultDurationMinutes, + Expression? archivedAtUtc, + Expression? revision, + Expression? createdAtUtc, + Expression? updatedAtUtc, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (colorKey != null) 'color_key': colorKey, + if (defaultPriority != null) 'default_priority': defaultPriority, + if (defaultReward != null) 'default_reward': defaultReward, + if (defaultDifficulty != null) 'default_difficulty': defaultDifficulty, + if (defaultReminderProfile != null) + 'default_reminder_profile': defaultReminderProfile, + if (defaultDurationMinutes != null) + 'default_duration_minutes': defaultDurationMinutes, + if (archivedAtUtc != null) 'archived_at_utc': archivedAtUtc, + if (revision != null) 'revision': revision, + if (createdAtUtc != null) 'created_at_utc': createdAtUtc, + if (updatedAtUtc != null) 'updated_at_utc': updatedAtUtc, + if (rowid != null) 'rowid': rowid, + }); + } + + ProjectsCompanion copyWith( + {Value? id, + Value? ownerId, + Value? name, + Value? colorKey, + Value? defaultPriority, + Value? defaultReward, + Value? defaultDifficulty, + Value? defaultReminderProfile, + Value? defaultDurationMinutes, + Value? archivedAtUtc, + Value? revision, + Value? createdAtUtc, + Value? updatedAtUtc, + Value? rowid}) { + return ProjectsCompanion( + id: id ?? this.id, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + colorKey: colorKey ?? this.colorKey, + defaultPriority: defaultPriority ?? this.defaultPriority, + defaultReward: defaultReward ?? this.defaultReward, + defaultDifficulty: defaultDifficulty ?? this.defaultDifficulty, + defaultReminderProfile: + defaultReminderProfile ?? this.defaultReminderProfile, + defaultDurationMinutes: + defaultDurationMinutes ?? this.defaultDurationMinutes, + archivedAtUtc: archivedAtUtc ?? this.archivedAtUtc, + revision: revision ?? this.revision, + createdAtUtc: createdAtUtc ?? this.createdAtUtc, + updatedAtUtc: updatedAtUtc ?? this.updatedAtUtc, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (colorKey.present) { + map['color_key'] = Variable(colorKey.value); + } + if (defaultPriority.present) { + map['default_priority'] = Variable(defaultPriority.value); + } + if (defaultReward.present) { + map['default_reward'] = Variable(defaultReward.value); + } + if (defaultDifficulty.present) { + map['default_difficulty'] = Variable(defaultDifficulty.value); + } + if (defaultReminderProfile.present) { + map['default_reminder_profile'] = + Variable(defaultReminderProfile.value); + } + if (defaultDurationMinutes.present) { + map['default_duration_minutes'] = + Variable(defaultDurationMinutes.value); + } + if (archivedAtUtc.present) { + map['archived_at_utc'] = Variable(archivedAtUtc.value); + } + if (revision.present) { + map['revision'] = Variable(revision.value); + } + if (createdAtUtc.present) { + map['created_at_utc'] = Variable(createdAtUtc.value); + } + if (updatedAtUtc.present) { + map['updated_at_utc'] = Variable(updatedAtUtc.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ProjectsCompanion(') + ..write('id: $id, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('colorKey: $colorKey, ') + ..write('defaultPriority: $defaultPriority, ') + ..write('defaultReward: $defaultReward, ') + ..write('defaultDifficulty: $defaultDifficulty, ') + ..write('defaultReminderProfile: $defaultReminderProfile, ') + ..write('defaultDurationMinutes: $defaultDurationMinutes, ') + ..write('archivedAtUtc: $archivedAtUtc, ') + ..write('revision: $revision, ') + ..write('createdAtUtc: $createdAtUtc, ') + ..write('updatedAtUtc: $updatedAtUtc, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $LockedBlocksTable extends LockedBlocks + with TableInfo<$LockedBlocksTable, LockedBlockRow> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $LockedBlocksTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _ownerIdMeta = + const VerificationMeta('ownerId'); + @override + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _dateMeta = const VerificationMeta('date'); + @override + late final GeneratedColumn date = GeneratedColumn( + 'date', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _startTimeMeta = + const VerificationMeta('startTime'); + @override + late final GeneratedColumn startTime = GeneratedColumn( + 'start_time', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _endTimeMeta = + const VerificationMeta('endTime'); + @override + late final GeneratedColumn endTime = GeneratedColumn( + 'end_time', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _recurrenceJsonMeta = + const VerificationMeta('recurrenceJson'); + @override + late final GeneratedColumn recurrenceJson = GeneratedColumn( + 'recurrence_json', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _hiddenByDefaultMeta = + const VerificationMeta('hiddenByDefault'); + @override + late final GeneratedColumn hiddenByDefault = GeneratedColumn( + 'hidden_by_default', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("hidden_by_default" IN (0, 1))')); + static const VerificationMeta _projectIdMeta = + const VerificationMeta('projectId'); + @override + late final GeneratedColumn projectId = GeneratedColumn( + 'project_id', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _archivedAtUtcMeta = + const VerificationMeta('archivedAtUtc'); + @override + late final GeneratedColumn archivedAtUtc = + GeneratedColumn('archived_at_utc', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + static const VerificationMeta _revisionMeta = + const VerificationMeta('revision'); + @override + late final GeneratedColumn revision = GeneratedColumn( + 'revision', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _createdAtUtcMeta = + const VerificationMeta('createdAtUtc'); + @override + late final GeneratedColumn createdAtUtc = GeneratedColumn( + 'created_at_utc', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + static const VerificationMeta _updatedAtUtcMeta = + const VerificationMeta('updatedAtUtc'); + @override + late final GeneratedColumn updatedAtUtc = GeneratedColumn( + 'updated_at_utc', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + @override + List get $columns => [ + id, + ownerId, + name, + date, + startTime, + endTime, + recurrenceJson, + hiddenByDefault, + projectId, + archivedAtUtc, + revision, + createdAtUtc, + updatedAtUtc + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'locked_blocks'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('owner_id')) { + context.handle(_ownerIdMeta, + ownerId.isAcceptableOrUnknown(data['owner_id']!, _ownerIdMeta)); + } else if (isInserting) { + context.missing(_ownerIdMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta)); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('date')) { + context.handle( + _dateMeta, date.isAcceptableOrUnknown(data['date']!, _dateMeta)); + } + if (data.containsKey('start_time')) { + context.handle(_startTimeMeta, + startTime.isAcceptableOrUnknown(data['start_time']!, _startTimeMeta)); + } else if (isInserting) { + context.missing(_startTimeMeta); + } + if (data.containsKey('end_time')) { + context.handle(_endTimeMeta, + endTime.isAcceptableOrUnknown(data['end_time']!, _endTimeMeta)); + } else if (isInserting) { + context.missing(_endTimeMeta); + } + if (data.containsKey('recurrence_json')) { + context.handle( + _recurrenceJsonMeta, + recurrenceJson.isAcceptableOrUnknown( + data['recurrence_json']!, _recurrenceJsonMeta)); + } + if (data.containsKey('hidden_by_default')) { + context.handle( + _hiddenByDefaultMeta, + hiddenByDefault.isAcceptableOrUnknown( + data['hidden_by_default']!, _hiddenByDefaultMeta)); + } else if (isInserting) { + context.missing(_hiddenByDefaultMeta); + } + if (data.containsKey('project_id')) { + context.handle(_projectIdMeta, + projectId.isAcceptableOrUnknown(data['project_id']!, _projectIdMeta)); + } + if (data.containsKey('archived_at_utc')) { + context.handle( + _archivedAtUtcMeta, + archivedAtUtc.isAcceptableOrUnknown( + data['archived_at_utc']!, _archivedAtUtcMeta)); + } + if (data.containsKey('revision')) { + context.handle(_revisionMeta, + revision.isAcceptableOrUnknown(data['revision']!, _revisionMeta)); + } else if (isInserting) { + context.missing(_revisionMeta); + } + if (data.containsKey('created_at_utc')) { + context.handle( + _createdAtUtcMeta, + createdAtUtc.isAcceptableOrUnknown( + data['created_at_utc']!, _createdAtUtcMeta)); + } else if (isInserting) { + context.missing(_createdAtUtcMeta); + } + if (data.containsKey('updated_at_utc')) { + context.handle( + _updatedAtUtcMeta, + updatedAtUtc.isAcceptableOrUnknown( + data['updated_at_utc']!, _updatedAtUtcMeta)); + } else if (isInserting) { + context.missing(_updatedAtUtcMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + LockedBlockRow map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LockedBlockRow( + id: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}id'])!, + ownerId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}owner_id'])!, + name: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}name'])!, + date: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}date']), + startTime: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}start_time'])!, + endTime: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}end_time'])!, + recurrenceJson: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}recurrence_json']), + hiddenByDefault: attachedDatabase.typeMapping.read( + DriftSqlType.bool, data['${effectivePrefix}hidden_by_default'])!, + projectId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}project_id']), + archivedAtUtc: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}archived_at_utc']), + revision: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}revision'])!, + createdAtUtc: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}created_at_utc'])!, + updatedAtUtc: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}updated_at_utc'])!, + ); + } + + @override + $LockedBlocksTable createAlias(String alias) { + return $LockedBlocksTable(attachedDatabase, alias); + } +} + +class LockedBlockRow extends DataClass implements Insertable { + final String id; + final String ownerId; + final String name; + final String? date; + final String startTime; + final String endTime; + final String? recurrenceJson; + final bool hiddenByDefault; + final String? projectId; + final DateTime? archivedAtUtc; + final int revision; + final DateTime createdAtUtc; + final DateTime updatedAtUtc; + const LockedBlockRow( + {required this.id, + required this.ownerId, + required this.name, + this.date, + required this.startTime, + required this.endTime, + this.recurrenceJson, + required this.hiddenByDefault, + this.projectId, + this.archivedAtUtc, + required this.revision, + required this.createdAtUtc, + required this.updatedAtUtc}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || date != null) { + map['date'] = Variable(date); + } + map['start_time'] = Variable(startTime); + map['end_time'] = Variable(endTime); + if (!nullToAbsent || recurrenceJson != null) { + map['recurrence_json'] = Variable(recurrenceJson); + } + map['hidden_by_default'] = Variable(hiddenByDefault); + if (!nullToAbsent || projectId != null) { + map['project_id'] = Variable(projectId); + } + if (!nullToAbsent || archivedAtUtc != null) { + map['archived_at_utc'] = Variable(archivedAtUtc); + } + map['revision'] = Variable(revision); + map['created_at_utc'] = Variable(createdAtUtc); + map['updated_at_utc'] = Variable(updatedAtUtc); + return map; + } + + LockedBlocksCompanion toCompanion(bool nullToAbsent) { + return LockedBlocksCompanion( + id: Value(id), + ownerId: Value(ownerId), + name: Value(name), + date: date == null && nullToAbsent ? const Value.absent() : Value(date), + startTime: Value(startTime), + endTime: Value(endTime), + recurrenceJson: recurrenceJson == null && nullToAbsent + ? const Value.absent() + : Value(recurrenceJson), + hiddenByDefault: Value(hiddenByDefault), + projectId: projectId == null && nullToAbsent + ? const Value.absent() + : Value(projectId), + archivedAtUtc: archivedAtUtc == null && nullToAbsent + ? const Value.absent() + : Value(archivedAtUtc), + revision: Value(revision), + createdAtUtc: Value(createdAtUtc), + updatedAtUtc: Value(updatedAtUtc), + ); + } + + factory LockedBlockRow.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LockedBlockRow( + id: serializer.fromJson(json['id']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + date: serializer.fromJson(json['date']), + startTime: serializer.fromJson(json['startTime']), + endTime: serializer.fromJson(json['endTime']), + recurrenceJson: serializer.fromJson(json['recurrenceJson']), + hiddenByDefault: serializer.fromJson(json['hiddenByDefault']), + projectId: serializer.fromJson(json['projectId']), + archivedAtUtc: serializer.fromJson(json['archivedAtUtc']), + revision: serializer.fromJson(json['revision']), + createdAtUtc: serializer.fromJson(json['createdAtUtc']), + updatedAtUtc: serializer.fromJson(json['updatedAtUtc']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'date': serializer.toJson(date), + 'startTime': serializer.toJson(startTime), + 'endTime': serializer.toJson(endTime), + 'recurrenceJson': serializer.toJson(recurrenceJson), + 'hiddenByDefault': serializer.toJson(hiddenByDefault), + 'projectId': serializer.toJson(projectId), + 'archivedAtUtc': serializer.toJson(archivedAtUtc), + 'revision': serializer.toJson(revision), + 'createdAtUtc': serializer.toJson(createdAtUtc), + 'updatedAtUtc': serializer.toJson(updatedAtUtc), + }; + } + + LockedBlockRow copyWith( + {String? id, + String? ownerId, + String? name, + Value date = const Value.absent(), + String? startTime, + String? endTime, + Value recurrenceJson = const Value.absent(), + bool? hiddenByDefault, + Value projectId = const Value.absent(), + Value archivedAtUtc = const Value.absent(), + int? revision, + DateTime? createdAtUtc, + DateTime? updatedAtUtc}) => + LockedBlockRow( + id: id ?? this.id, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + date: date.present ? date.value : this.date, + startTime: startTime ?? this.startTime, + endTime: endTime ?? this.endTime, + recurrenceJson: + recurrenceJson.present ? recurrenceJson.value : this.recurrenceJson, + hiddenByDefault: hiddenByDefault ?? this.hiddenByDefault, + projectId: projectId.present ? projectId.value : this.projectId, + archivedAtUtc: + archivedAtUtc.present ? archivedAtUtc.value : this.archivedAtUtc, + revision: revision ?? this.revision, + createdAtUtc: createdAtUtc ?? this.createdAtUtc, + updatedAtUtc: updatedAtUtc ?? this.updatedAtUtc, + ); + LockedBlockRow copyWithCompanion(LockedBlocksCompanion data) { + return LockedBlockRow( + id: data.id.present ? data.id.value : this.id, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + date: data.date.present ? data.date.value : this.date, + startTime: data.startTime.present ? data.startTime.value : this.startTime, + endTime: data.endTime.present ? data.endTime.value : this.endTime, + recurrenceJson: data.recurrenceJson.present + ? data.recurrenceJson.value + : this.recurrenceJson, + hiddenByDefault: data.hiddenByDefault.present + ? data.hiddenByDefault.value + : this.hiddenByDefault, + projectId: data.projectId.present ? data.projectId.value : this.projectId, + archivedAtUtc: data.archivedAtUtc.present + ? data.archivedAtUtc.value + : this.archivedAtUtc, + revision: data.revision.present ? data.revision.value : this.revision, + createdAtUtc: data.createdAtUtc.present + ? data.createdAtUtc.value + : this.createdAtUtc, + updatedAtUtc: data.updatedAtUtc.present + ? data.updatedAtUtc.value + : this.updatedAtUtc, + ); + } + + @override + String toString() { + return (StringBuffer('LockedBlockRow(') + ..write('id: $id, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('date: $date, ') + ..write('startTime: $startTime, ') + ..write('endTime: $endTime, ') + ..write('recurrenceJson: $recurrenceJson, ') + ..write('hiddenByDefault: $hiddenByDefault, ') + ..write('projectId: $projectId, ') + ..write('archivedAtUtc: $archivedAtUtc, ') + ..write('revision: $revision, ') + ..write('createdAtUtc: $createdAtUtc, ') + ..write('updatedAtUtc: $updatedAtUtc') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + ownerId, + name, + date, + startTime, + endTime, + recurrenceJson, + hiddenByDefault, + projectId, + archivedAtUtc, + revision, + createdAtUtc, + updatedAtUtc); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LockedBlockRow && + other.id == this.id && + other.ownerId == this.ownerId && + other.name == this.name && + other.date == this.date && + other.startTime == this.startTime && + other.endTime == this.endTime && + other.recurrenceJson == this.recurrenceJson && + other.hiddenByDefault == this.hiddenByDefault && + other.projectId == this.projectId && + other.archivedAtUtc == this.archivedAtUtc && + other.revision == this.revision && + other.createdAtUtc == this.createdAtUtc && + other.updatedAtUtc == this.updatedAtUtc); +} + +class LockedBlocksCompanion extends UpdateCompanion { + final Value id; + final Value ownerId; + final Value name; + final Value date; + final Value startTime; + final Value endTime; + final Value recurrenceJson; + final Value hiddenByDefault; + final Value projectId; + final Value archivedAtUtc; + final Value revision; + final Value createdAtUtc; + final Value updatedAtUtc; + final Value rowid; + const LockedBlocksCompanion({ + this.id = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.date = const Value.absent(), + this.startTime = const Value.absent(), + this.endTime = const Value.absent(), + this.recurrenceJson = const Value.absent(), + this.hiddenByDefault = const Value.absent(), + this.projectId = const Value.absent(), + this.archivedAtUtc = const Value.absent(), + this.revision = const Value.absent(), + this.createdAtUtc = const Value.absent(), + this.updatedAtUtc = const Value.absent(), + this.rowid = const Value.absent(), + }); + LockedBlocksCompanion.insert({ + required String id, + required String ownerId, + required String name, + this.date = const Value.absent(), + required String startTime, + required String endTime, + this.recurrenceJson = const Value.absent(), + required bool hiddenByDefault, + this.projectId = const Value.absent(), + this.archivedAtUtc = const Value.absent(), + required int revision, + required DateTime createdAtUtc, + required DateTime updatedAtUtc, + this.rowid = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + startTime = Value(startTime), + endTime = Value(endTime), + hiddenByDefault = Value(hiddenByDefault), + revision = Value(revision), + createdAtUtc = Value(createdAtUtc), + updatedAtUtc = Value(updatedAtUtc); + static Insertable custom({ + Expression? id, + Expression? ownerId, + Expression? name, + Expression? date, + Expression? startTime, + Expression? endTime, + Expression? recurrenceJson, + Expression? hiddenByDefault, + Expression? projectId, + Expression? archivedAtUtc, + Expression? revision, + Expression? createdAtUtc, + Expression? updatedAtUtc, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (date != null) 'date': date, + if (startTime != null) 'start_time': startTime, + if (endTime != null) 'end_time': endTime, + if (recurrenceJson != null) 'recurrence_json': recurrenceJson, + if (hiddenByDefault != null) 'hidden_by_default': hiddenByDefault, + if (projectId != null) 'project_id': projectId, + if (archivedAtUtc != null) 'archived_at_utc': archivedAtUtc, + if (revision != null) 'revision': revision, + if (createdAtUtc != null) 'created_at_utc': createdAtUtc, + if (updatedAtUtc != null) 'updated_at_utc': updatedAtUtc, + if (rowid != null) 'rowid': rowid, + }); + } + + LockedBlocksCompanion copyWith( + {Value? id, + Value? ownerId, + Value? name, + Value? date, + Value? startTime, + Value? endTime, + Value? recurrenceJson, + Value? hiddenByDefault, + Value? projectId, + Value? archivedAtUtc, + Value? revision, + Value? createdAtUtc, + Value? updatedAtUtc, + Value? rowid}) { + return LockedBlocksCompanion( + id: id ?? this.id, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + date: date ?? this.date, + startTime: startTime ?? this.startTime, + endTime: endTime ?? this.endTime, + recurrenceJson: recurrenceJson ?? this.recurrenceJson, + hiddenByDefault: hiddenByDefault ?? this.hiddenByDefault, + projectId: projectId ?? this.projectId, + archivedAtUtc: archivedAtUtc ?? this.archivedAtUtc, + revision: revision ?? this.revision, + createdAtUtc: createdAtUtc ?? this.createdAtUtc, + updatedAtUtc: updatedAtUtc ?? this.updatedAtUtc, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (date.present) { + map['date'] = Variable(date.value); + } + if (startTime.present) { + map['start_time'] = Variable(startTime.value); + } + if (endTime.present) { + map['end_time'] = Variable(endTime.value); + } + if (recurrenceJson.present) { + map['recurrence_json'] = Variable(recurrenceJson.value); + } + if (hiddenByDefault.present) { + map['hidden_by_default'] = Variable(hiddenByDefault.value); + } + if (projectId.present) { + map['project_id'] = Variable(projectId.value); + } + if (archivedAtUtc.present) { + map['archived_at_utc'] = Variable(archivedAtUtc.value); + } + if (revision.present) { + map['revision'] = Variable(revision.value); + } + if (createdAtUtc.present) { + map['created_at_utc'] = Variable(createdAtUtc.value); + } + if (updatedAtUtc.present) { + map['updated_at_utc'] = Variable(updatedAtUtc.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LockedBlocksCompanion(') + ..write('id: $id, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('date: $date, ') + ..write('startTime: $startTime, ') + ..write('endTime: $endTime, ') + ..write('recurrenceJson: $recurrenceJson, ') + ..write('hiddenByDefault: $hiddenByDefault, ') + ..write('projectId: $projectId, ') + ..write('archivedAtUtc: $archivedAtUtc, ') + ..write('revision: $revision, ') + ..write('createdAtUtc: $createdAtUtc, ') + ..write('updatedAtUtc: $updatedAtUtc, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $LockedOverridesTable extends LockedOverrides + with TableInfo<$LockedOverridesTable, LockedOverrideRow> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $LockedOverridesTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _ownerIdMeta = + const VerificationMeta('ownerId'); + @override + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _lockedBlockIdMeta = + const VerificationMeta('lockedBlockId'); + @override + late final GeneratedColumn lockedBlockId = GeneratedColumn( + 'locked_block_id', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _dateMeta = const VerificationMeta('date'); + @override + late final GeneratedColumn date = GeneratedColumn( + 'date', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _typeMeta = const VerificationMeta('type'); + @override + late final GeneratedColumn type = GeneratedColumn( + 'type', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _startTimeMeta = + const VerificationMeta('startTime'); + @override + late final GeneratedColumn startTime = GeneratedColumn( + 'start_time', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _endTimeMeta = + const VerificationMeta('endTime'); + @override + late final GeneratedColumn endTime = GeneratedColumn( + 'end_time', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _hiddenByDefaultMeta = + const VerificationMeta('hiddenByDefault'); + @override + late final GeneratedColumn hiddenByDefault = GeneratedColumn( + 'hidden_by_default', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("hidden_by_default" IN (0, 1))')); + static const VerificationMeta _projectIdMeta = + const VerificationMeta('projectId'); + @override + late final GeneratedColumn projectId = GeneratedColumn( + 'project_id', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _revisionMeta = + const VerificationMeta('revision'); + @override + late final GeneratedColumn revision = GeneratedColumn( + 'revision', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _createdAtUtcMeta = + const VerificationMeta('createdAtUtc'); + @override + late final GeneratedColumn createdAtUtc = GeneratedColumn( + 'created_at_utc', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + static const VerificationMeta _updatedAtUtcMeta = + const VerificationMeta('updatedAtUtc'); + @override + late final GeneratedColumn updatedAtUtc = GeneratedColumn( + 'updated_at_utc', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + @override + List get $columns => [ + id, + ownerId, + lockedBlockId, + date, + type, + name, + startTime, + endTime, + hiddenByDefault, + projectId, + revision, + createdAtUtc, + updatedAtUtc + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'locked_overrides'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('owner_id')) { + context.handle(_ownerIdMeta, + ownerId.isAcceptableOrUnknown(data['owner_id']!, _ownerIdMeta)); + } else if (isInserting) { + context.missing(_ownerIdMeta); + } + if (data.containsKey('locked_block_id')) { + context.handle( + _lockedBlockIdMeta, + lockedBlockId.isAcceptableOrUnknown( + data['locked_block_id']!, _lockedBlockIdMeta)); + } + if (data.containsKey('date')) { + context.handle( + _dateMeta, date.isAcceptableOrUnknown(data['date']!, _dateMeta)); + } else if (isInserting) { + context.missing(_dateMeta); + } + if (data.containsKey('type')) { + context.handle( + _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); + } else if (isInserting) { + context.missing(_typeMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta)); + } + if (data.containsKey('start_time')) { + context.handle(_startTimeMeta, + startTime.isAcceptableOrUnknown(data['start_time']!, _startTimeMeta)); + } + if (data.containsKey('end_time')) { + context.handle(_endTimeMeta, + endTime.isAcceptableOrUnknown(data['end_time']!, _endTimeMeta)); + } + if (data.containsKey('hidden_by_default')) { + context.handle( + _hiddenByDefaultMeta, + hiddenByDefault.isAcceptableOrUnknown( + data['hidden_by_default']!, _hiddenByDefaultMeta)); + } else if (isInserting) { + context.missing(_hiddenByDefaultMeta); + } + if (data.containsKey('project_id')) { + context.handle(_projectIdMeta, + projectId.isAcceptableOrUnknown(data['project_id']!, _projectIdMeta)); + } + if (data.containsKey('revision')) { + context.handle(_revisionMeta, + revision.isAcceptableOrUnknown(data['revision']!, _revisionMeta)); + } else if (isInserting) { + context.missing(_revisionMeta); + } + if (data.containsKey('created_at_utc')) { + context.handle( + _createdAtUtcMeta, + createdAtUtc.isAcceptableOrUnknown( + data['created_at_utc']!, _createdAtUtcMeta)); + } else if (isInserting) { + context.missing(_createdAtUtcMeta); + } + if (data.containsKey('updated_at_utc')) { + context.handle( + _updatedAtUtcMeta, + updatedAtUtc.isAcceptableOrUnknown( + data['updated_at_utc']!, _updatedAtUtcMeta)); + } else if (isInserting) { + context.missing(_updatedAtUtcMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + LockedOverrideRow map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LockedOverrideRow( + id: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}id'])!, + ownerId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}owner_id'])!, + lockedBlockId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}locked_block_id']), + date: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}date'])!, + type: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}type'])!, + name: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}name']), + startTime: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}start_time']), + endTime: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}end_time']), + hiddenByDefault: attachedDatabase.typeMapping.read( + DriftSqlType.bool, data['${effectivePrefix}hidden_by_default'])!, + projectId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}project_id']), + revision: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}revision'])!, + createdAtUtc: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}created_at_utc'])!, + updatedAtUtc: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}updated_at_utc'])!, + ); + } + + @override + $LockedOverridesTable createAlias(String alias) { + return $LockedOverridesTable(attachedDatabase, alias); + } +} + +class LockedOverrideRow extends DataClass + implements Insertable { + final String id; + final String ownerId; + final String? lockedBlockId; + final String date; + final String type; + final String? name; + final String? startTime; + final String? endTime; + final bool hiddenByDefault; + final String? projectId; + final int revision; + final DateTime createdAtUtc; + final DateTime updatedAtUtc; + const LockedOverrideRow( + {required this.id, + required this.ownerId, + this.lockedBlockId, + required this.date, + required this.type, + this.name, + this.startTime, + this.endTime, + required this.hiddenByDefault, + this.projectId, + required this.revision, + required this.createdAtUtc, + required this.updatedAtUtc}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || lockedBlockId != null) { + map['locked_block_id'] = Variable(lockedBlockId); + } + map['date'] = Variable(date); + map['type'] = Variable(type); + if (!nullToAbsent || name != null) { + map['name'] = Variable(name); + } + if (!nullToAbsent || startTime != null) { + map['start_time'] = Variable(startTime); + } + if (!nullToAbsent || endTime != null) { + map['end_time'] = Variable(endTime); + } + map['hidden_by_default'] = Variable(hiddenByDefault); + if (!nullToAbsent || projectId != null) { + map['project_id'] = Variable(projectId); + } + map['revision'] = Variable(revision); + map['created_at_utc'] = Variable(createdAtUtc); + map['updated_at_utc'] = Variable(updatedAtUtc); + return map; + } + + LockedOverridesCompanion toCompanion(bool nullToAbsent) { + return LockedOverridesCompanion( + id: Value(id), + ownerId: Value(ownerId), + lockedBlockId: lockedBlockId == null && nullToAbsent + ? const Value.absent() + : Value(lockedBlockId), + date: Value(date), + type: Value(type), + name: name == null && nullToAbsent ? const Value.absent() : Value(name), + startTime: startTime == null && nullToAbsent + ? const Value.absent() + : Value(startTime), + endTime: endTime == null && nullToAbsent + ? const Value.absent() + : Value(endTime), + hiddenByDefault: Value(hiddenByDefault), + projectId: projectId == null && nullToAbsent + ? const Value.absent() + : Value(projectId), + revision: Value(revision), + createdAtUtc: Value(createdAtUtc), + updatedAtUtc: Value(updatedAtUtc), + ); + } + + factory LockedOverrideRow.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LockedOverrideRow( + id: serializer.fromJson(json['id']), + ownerId: serializer.fromJson(json['ownerId']), + lockedBlockId: serializer.fromJson(json['lockedBlockId']), + date: serializer.fromJson(json['date']), + type: serializer.fromJson(json['type']), + name: serializer.fromJson(json['name']), + startTime: serializer.fromJson(json['startTime']), + endTime: serializer.fromJson(json['endTime']), + hiddenByDefault: serializer.fromJson(json['hiddenByDefault']), + projectId: serializer.fromJson(json['projectId']), + revision: serializer.fromJson(json['revision']), + createdAtUtc: serializer.fromJson(json['createdAtUtc']), + updatedAtUtc: serializer.fromJson(json['updatedAtUtc']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'ownerId': serializer.toJson(ownerId), + 'lockedBlockId': serializer.toJson(lockedBlockId), + 'date': serializer.toJson(date), + 'type': serializer.toJson(type), + 'name': serializer.toJson(name), + 'startTime': serializer.toJson(startTime), + 'endTime': serializer.toJson(endTime), + 'hiddenByDefault': serializer.toJson(hiddenByDefault), + 'projectId': serializer.toJson(projectId), + 'revision': serializer.toJson(revision), + 'createdAtUtc': serializer.toJson(createdAtUtc), + 'updatedAtUtc': serializer.toJson(updatedAtUtc), + }; + } + + LockedOverrideRow copyWith( + {String? id, + String? ownerId, + Value lockedBlockId = const Value.absent(), + String? date, + String? type, + Value name = const Value.absent(), + Value startTime = const Value.absent(), + Value endTime = const Value.absent(), + bool? hiddenByDefault, + Value projectId = const Value.absent(), + int? revision, + DateTime? createdAtUtc, + DateTime? updatedAtUtc}) => + LockedOverrideRow( + id: id ?? this.id, + ownerId: ownerId ?? this.ownerId, + lockedBlockId: + lockedBlockId.present ? lockedBlockId.value : this.lockedBlockId, + date: date ?? this.date, + type: type ?? this.type, + name: name.present ? name.value : this.name, + startTime: startTime.present ? startTime.value : this.startTime, + endTime: endTime.present ? endTime.value : this.endTime, + hiddenByDefault: hiddenByDefault ?? this.hiddenByDefault, + projectId: projectId.present ? projectId.value : this.projectId, + revision: revision ?? this.revision, + createdAtUtc: createdAtUtc ?? this.createdAtUtc, + updatedAtUtc: updatedAtUtc ?? this.updatedAtUtc, + ); + LockedOverrideRow copyWithCompanion(LockedOverridesCompanion data) { + return LockedOverrideRow( + id: data.id.present ? data.id.value : this.id, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + lockedBlockId: data.lockedBlockId.present + ? data.lockedBlockId.value + : this.lockedBlockId, + date: data.date.present ? data.date.value : this.date, + type: data.type.present ? data.type.value : this.type, + name: data.name.present ? data.name.value : this.name, + startTime: data.startTime.present ? data.startTime.value : this.startTime, + endTime: data.endTime.present ? data.endTime.value : this.endTime, + hiddenByDefault: data.hiddenByDefault.present + ? data.hiddenByDefault.value + : this.hiddenByDefault, + projectId: data.projectId.present ? data.projectId.value : this.projectId, + revision: data.revision.present ? data.revision.value : this.revision, + createdAtUtc: data.createdAtUtc.present + ? data.createdAtUtc.value + : this.createdAtUtc, + updatedAtUtc: data.updatedAtUtc.present + ? data.updatedAtUtc.value + : this.updatedAtUtc, + ); + } + + @override + String toString() { + return (StringBuffer('LockedOverrideRow(') + ..write('id: $id, ') + ..write('ownerId: $ownerId, ') + ..write('lockedBlockId: $lockedBlockId, ') + ..write('date: $date, ') + ..write('type: $type, ') + ..write('name: $name, ') + ..write('startTime: $startTime, ') + ..write('endTime: $endTime, ') + ..write('hiddenByDefault: $hiddenByDefault, ') + ..write('projectId: $projectId, ') + ..write('revision: $revision, ') + ..write('createdAtUtc: $createdAtUtc, ') + ..write('updatedAtUtc: $updatedAtUtc') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + ownerId, + lockedBlockId, + date, + type, + name, + startTime, + endTime, + hiddenByDefault, + projectId, + revision, + createdAtUtc, + updatedAtUtc); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LockedOverrideRow && + other.id == this.id && + other.ownerId == this.ownerId && + other.lockedBlockId == this.lockedBlockId && + other.date == this.date && + other.type == this.type && + other.name == this.name && + other.startTime == this.startTime && + other.endTime == this.endTime && + other.hiddenByDefault == this.hiddenByDefault && + other.projectId == this.projectId && + other.revision == this.revision && + other.createdAtUtc == this.createdAtUtc && + other.updatedAtUtc == this.updatedAtUtc); +} + +class LockedOverridesCompanion extends UpdateCompanion { + final Value id; + final Value ownerId; + final Value lockedBlockId; + final Value date; + final Value type; + final Value name; + final Value startTime; + final Value endTime; + final Value hiddenByDefault; + final Value projectId; + final Value revision; + final Value createdAtUtc; + final Value updatedAtUtc; + final Value rowid; + const LockedOverridesCompanion({ + this.id = const Value.absent(), + this.ownerId = const Value.absent(), + this.lockedBlockId = const Value.absent(), + this.date = const Value.absent(), + this.type = const Value.absent(), + this.name = const Value.absent(), + this.startTime = const Value.absent(), + this.endTime = const Value.absent(), + this.hiddenByDefault = const Value.absent(), + this.projectId = const Value.absent(), + this.revision = const Value.absent(), + this.createdAtUtc = const Value.absent(), + this.updatedAtUtc = const Value.absent(), + this.rowid = const Value.absent(), + }); + LockedOverridesCompanion.insert({ + required String id, + required String ownerId, + this.lockedBlockId = const Value.absent(), + required String date, + required String type, + this.name = const Value.absent(), + this.startTime = const Value.absent(), + this.endTime = const Value.absent(), + required bool hiddenByDefault, + this.projectId = const Value.absent(), + required int revision, + required DateTime createdAtUtc, + required DateTime updatedAtUtc, + this.rowid = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + date = Value(date), + type = Value(type), + hiddenByDefault = Value(hiddenByDefault), + revision = Value(revision), + createdAtUtc = Value(createdAtUtc), + updatedAtUtc = Value(updatedAtUtc); + static Insertable custom({ + Expression? id, + Expression? ownerId, + Expression? lockedBlockId, + Expression? date, + Expression? type, + Expression? name, + Expression? startTime, + Expression? endTime, + Expression? hiddenByDefault, + Expression? projectId, + Expression? revision, + Expression? createdAtUtc, + Expression? updatedAtUtc, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (ownerId != null) 'owner_id': ownerId, + if (lockedBlockId != null) 'locked_block_id': lockedBlockId, + if (date != null) 'date': date, + if (type != null) 'type': type, + if (name != null) 'name': name, + if (startTime != null) 'start_time': startTime, + if (endTime != null) 'end_time': endTime, + if (hiddenByDefault != null) 'hidden_by_default': hiddenByDefault, + if (projectId != null) 'project_id': projectId, + if (revision != null) 'revision': revision, + if (createdAtUtc != null) 'created_at_utc': createdAtUtc, + if (updatedAtUtc != null) 'updated_at_utc': updatedAtUtc, + if (rowid != null) 'rowid': rowid, + }); + } + + LockedOverridesCompanion copyWith( + {Value? id, + Value? ownerId, + Value? lockedBlockId, + Value? date, + Value? type, + Value? name, + Value? startTime, + Value? endTime, + Value? hiddenByDefault, + Value? projectId, + Value? revision, + Value? createdAtUtc, + Value? updatedAtUtc, + Value? rowid}) { + return LockedOverridesCompanion( + id: id ?? this.id, + ownerId: ownerId ?? this.ownerId, + lockedBlockId: lockedBlockId ?? this.lockedBlockId, + date: date ?? this.date, + type: type ?? this.type, + name: name ?? this.name, + startTime: startTime ?? this.startTime, + endTime: endTime ?? this.endTime, + hiddenByDefault: hiddenByDefault ?? this.hiddenByDefault, + projectId: projectId ?? this.projectId, + revision: revision ?? this.revision, + createdAtUtc: createdAtUtc ?? this.createdAtUtc, + updatedAtUtc: updatedAtUtc ?? this.updatedAtUtc, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (lockedBlockId.present) { + map['locked_block_id'] = Variable(lockedBlockId.value); + } + if (date.present) { + map['date'] = Variable(date.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (startTime.present) { + map['start_time'] = Variable(startTime.value); + } + if (endTime.present) { + map['end_time'] = Variable(endTime.value); + } + if (hiddenByDefault.present) { + map['hidden_by_default'] = Variable(hiddenByDefault.value); + } + if (projectId.present) { + map['project_id'] = Variable(projectId.value); + } + if (revision.present) { + map['revision'] = Variable(revision.value); + } + if (createdAtUtc.present) { + map['created_at_utc'] = Variable(createdAtUtc.value); + } + if (updatedAtUtc.present) { + map['updated_at_utc'] = Variable(updatedAtUtc.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LockedOverridesCompanion(') + ..write('id: $id, ') + ..write('ownerId: $ownerId, ') + ..write('lockedBlockId: $lockedBlockId, ') + ..write('date: $date, ') + ..write('type: $type, ') + ..write('name: $name, ') + ..write('startTime: $startTime, ') + ..write('endTime: $endTime, ') + ..write('hiddenByDefault: $hiddenByDefault, ') + ..write('projectId: $projectId, ') + ..write('revision: $revision, ') + ..write('createdAtUtc: $createdAtUtc, ') + ..write('updatedAtUtc: $updatedAtUtc, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $SettingsTableTable extends SettingsTable + with TableInfo<$SettingsTableTable, SettingsRow> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $SettingsTableTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _ownerIdMeta = + const VerificationMeta('ownerId'); + @override + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _timeZoneIdMeta = + const VerificationMeta('timeZoneId'); + @override + late final GeneratedColumn timeZoneId = GeneratedColumn( + 'timezone_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _dayStartMinutesMeta = + const VerificationMeta('dayStartMinutes'); + @override + late final GeneratedColumn dayStartMinutes = GeneratedColumn( + 'day_start_minutes', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _dayEndMinutesMeta = + const VerificationMeta('dayEndMinutes'); + @override + late final GeneratedColumn dayEndMinutes = GeneratedColumn( + 'day_end_minutes', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _compactModeMeta = + const VerificationMeta('compactMode'); + @override + late final GeneratedColumn compactMode = GeneratedColumn( + 'compact_mode', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("compact_mode" IN (0, 1))')); + static const VerificationMeta _backlogStalenessJsonMeta = + const VerificationMeta('backlogStalenessJson'); + @override + late final GeneratedColumn backlogStalenessJson = + GeneratedColumn('backlog_staleness_json', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _revisionMeta = + const VerificationMeta('revision'); + @override + late final GeneratedColumn revision = GeneratedColumn( + 'revision', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _createdAtUtcMeta = + const VerificationMeta('createdAtUtc'); + @override + late final GeneratedColumn createdAtUtc = GeneratedColumn( + 'created_at_utc', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + static const VerificationMeta _updatedAtUtcMeta = + const VerificationMeta('updatedAtUtc'); + @override + late final GeneratedColumn updatedAtUtc = GeneratedColumn( + 'updated_at_utc', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + @override + List get $columns => [ + ownerId, + timeZoneId, + dayStartMinutes, + dayEndMinutes, + compactMode, + backlogStalenessJson, + revision, + createdAtUtc, + updatedAtUtc + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'settings'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('owner_id')) { + context.handle(_ownerIdMeta, + ownerId.isAcceptableOrUnknown(data['owner_id']!, _ownerIdMeta)); + } else if (isInserting) { + context.missing(_ownerIdMeta); + } + if (data.containsKey('timezone_id')) { + context.handle( + _timeZoneIdMeta, + timeZoneId.isAcceptableOrUnknown( + data['timezone_id']!, _timeZoneIdMeta)); + } else if (isInserting) { + context.missing(_timeZoneIdMeta); + } + if (data.containsKey('day_start_minutes')) { + context.handle( + _dayStartMinutesMeta, + dayStartMinutes.isAcceptableOrUnknown( + data['day_start_minutes']!, _dayStartMinutesMeta)); + } else if (isInserting) { + context.missing(_dayStartMinutesMeta); + } + if (data.containsKey('day_end_minutes')) { + context.handle( + _dayEndMinutesMeta, + dayEndMinutes.isAcceptableOrUnknown( + data['day_end_minutes']!, _dayEndMinutesMeta)); + } else if (isInserting) { + context.missing(_dayEndMinutesMeta); + } + if (data.containsKey('compact_mode')) { + context.handle( + _compactModeMeta, + compactMode.isAcceptableOrUnknown( + data['compact_mode']!, _compactModeMeta)); + } else if (isInserting) { + context.missing(_compactModeMeta); + } + if (data.containsKey('backlog_staleness_json')) { + context.handle( + _backlogStalenessJsonMeta, + backlogStalenessJson.isAcceptableOrUnknown( + data['backlog_staleness_json']!, _backlogStalenessJsonMeta)); + } else if (isInserting) { + context.missing(_backlogStalenessJsonMeta); + } + if (data.containsKey('revision')) { + context.handle(_revisionMeta, + revision.isAcceptableOrUnknown(data['revision']!, _revisionMeta)); + } else if (isInserting) { + context.missing(_revisionMeta); + } + if (data.containsKey('created_at_utc')) { + context.handle( + _createdAtUtcMeta, + createdAtUtc.isAcceptableOrUnknown( + data['created_at_utc']!, _createdAtUtcMeta)); + } else if (isInserting) { + context.missing(_createdAtUtcMeta); + } + if (data.containsKey('updated_at_utc')) { + context.handle( + _updatedAtUtcMeta, + updatedAtUtc.isAcceptableOrUnknown( + data['updated_at_utc']!, _updatedAtUtcMeta)); + } else if (isInserting) { + context.missing(_updatedAtUtcMeta); + } + return context; + } + + @override + Set get $primaryKey => {ownerId}; + @override + SettingsRow map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SettingsRow( + ownerId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}owner_id'])!, + timeZoneId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}timezone_id'])!, + dayStartMinutes: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}day_start_minutes'])!, + dayEndMinutes: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}day_end_minutes'])!, + compactMode: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}compact_mode'])!, + backlogStalenessJson: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}backlog_staleness_json'])!, + revision: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}revision'])!, + createdAtUtc: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}created_at_utc'])!, + updatedAtUtc: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}updated_at_utc'])!, + ); + } + + @override + $SettingsTableTable createAlias(String alias) { + return $SettingsTableTable(attachedDatabase, alias); + } +} + +class SettingsRow extends DataClass implements Insertable { + final String ownerId; + final String timeZoneId; + final int dayStartMinutes; + final int dayEndMinutes; + final bool compactMode; + final String backlogStalenessJson; + final int revision; + final DateTime createdAtUtc; + final DateTime updatedAtUtc; + const SettingsRow( + {required this.ownerId, + required this.timeZoneId, + required this.dayStartMinutes, + required this.dayEndMinutes, + required this.compactMode, + required this.backlogStalenessJson, + required this.revision, + required this.createdAtUtc, + required this.updatedAtUtc}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['owner_id'] = Variable(ownerId); + map['timezone_id'] = Variable(timeZoneId); + map['day_start_minutes'] = Variable(dayStartMinutes); + map['day_end_minutes'] = Variable(dayEndMinutes); + map['compact_mode'] = Variable(compactMode); + map['backlog_staleness_json'] = Variable(backlogStalenessJson); + map['revision'] = Variable(revision); + map['created_at_utc'] = Variable(createdAtUtc); + map['updated_at_utc'] = Variable(updatedAtUtc); + return map; + } + + SettingsTableCompanion toCompanion(bool nullToAbsent) { + return SettingsTableCompanion( + ownerId: Value(ownerId), + timeZoneId: Value(timeZoneId), + dayStartMinutes: Value(dayStartMinutes), + dayEndMinutes: Value(dayEndMinutes), + compactMode: Value(compactMode), + backlogStalenessJson: Value(backlogStalenessJson), + revision: Value(revision), + createdAtUtc: Value(createdAtUtc), + updatedAtUtc: Value(updatedAtUtc), + ); + } + + factory SettingsRow.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SettingsRow( + ownerId: serializer.fromJson(json['ownerId']), + timeZoneId: serializer.fromJson(json['timeZoneId']), + dayStartMinutes: serializer.fromJson(json['dayStartMinutes']), + dayEndMinutes: serializer.fromJson(json['dayEndMinutes']), + compactMode: serializer.fromJson(json['compactMode']), + backlogStalenessJson: + serializer.fromJson(json['backlogStalenessJson']), + revision: serializer.fromJson(json['revision']), + createdAtUtc: serializer.fromJson(json['createdAtUtc']), + updatedAtUtc: serializer.fromJson(json['updatedAtUtc']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'ownerId': serializer.toJson(ownerId), + 'timeZoneId': serializer.toJson(timeZoneId), + 'dayStartMinutes': serializer.toJson(dayStartMinutes), + 'dayEndMinutes': serializer.toJson(dayEndMinutes), + 'compactMode': serializer.toJson(compactMode), + 'backlogStalenessJson': serializer.toJson(backlogStalenessJson), + 'revision': serializer.toJson(revision), + 'createdAtUtc': serializer.toJson(createdAtUtc), + 'updatedAtUtc': serializer.toJson(updatedAtUtc), + }; + } + + SettingsRow copyWith( + {String? ownerId, + String? timeZoneId, + int? dayStartMinutes, + int? dayEndMinutes, + bool? compactMode, + String? backlogStalenessJson, + int? revision, + DateTime? createdAtUtc, + DateTime? updatedAtUtc}) => + SettingsRow( + ownerId: ownerId ?? this.ownerId, + timeZoneId: timeZoneId ?? this.timeZoneId, + dayStartMinutes: dayStartMinutes ?? this.dayStartMinutes, + dayEndMinutes: dayEndMinutes ?? this.dayEndMinutes, + compactMode: compactMode ?? this.compactMode, + backlogStalenessJson: backlogStalenessJson ?? this.backlogStalenessJson, + revision: revision ?? this.revision, + createdAtUtc: createdAtUtc ?? this.createdAtUtc, + updatedAtUtc: updatedAtUtc ?? this.updatedAtUtc, + ); + SettingsRow copyWithCompanion(SettingsTableCompanion data) { + return SettingsRow( + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + timeZoneId: + data.timeZoneId.present ? data.timeZoneId.value : this.timeZoneId, + dayStartMinutes: data.dayStartMinutes.present + ? data.dayStartMinutes.value + : this.dayStartMinutes, + dayEndMinutes: data.dayEndMinutes.present + ? data.dayEndMinutes.value + : this.dayEndMinutes, + compactMode: + data.compactMode.present ? data.compactMode.value : this.compactMode, + backlogStalenessJson: data.backlogStalenessJson.present + ? data.backlogStalenessJson.value + : this.backlogStalenessJson, + revision: data.revision.present ? data.revision.value : this.revision, + createdAtUtc: data.createdAtUtc.present + ? data.createdAtUtc.value + : this.createdAtUtc, + updatedAtUtc: data.updatedAtUtc.present + ? data.updatedAtUtc.value + : this.updatedAtUtc, + ); + } + + @override + String toString() { + return (StringBuffer('SettingsRow(') + ..write('ownerId: $ownerId, ') + ..write('timeZoneId: $timeZoneId, ') + ..write('dayStartMinutes: $dayStartMinutes, ') + ..write('dayEndMinutes: $dayEndMinutes, ') + ..write('compactMode: $compactMode, ') + ..write('backlogStalenessJson: $backlogStalenessJson, ') + ..write('revision: $revision, ') + ..write('createdAtUtc: $createdAtUtc, ') + ..write('updatedAtUtc: $updatedAtUtc') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + ownerId, + timeZoneId, + dayStartMinutes, + dayEndMinutes, + compactMode, + backlogStalenessJson, + revision, + createdAtUtc, + updatedAtUtc); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SettingsRow && + other.ownerId == this.ownerId && + other.timeZoneId == this.timeZoneId && + other.dayStartMinutes == this.dayStartMinutes && + other.dayEndMinutes == this.dayEndMinutes && + other.compactMode == this.compactMode && + other.backlogStalenessJson == this.backlogStalenessJson && + other.revision == this.revision && + other.createdAtUtc == this.createdAtUtc && + other.updatedAtUtc == this.updatedAtUtc); +} + +class SettingsTableCompanion extends UpdateCompanion { + final Value ownerId; + final Value timeZoneId; + final Value dayStartMinutes; + final Value dayEndMinutes; + final Value compactMode; + final Value backlogStalenessJson; + final Value revision; + final Value createdAtUtc; + final Value updatedAtUtc; + final Value rowid; + const SettingsTableCompanion({ + this.ownerId = const Value.absent(), + this.timeZoneId = const Value.absent(), + this.dayStartMinutes = const Value.absent(), + this.dayEndMinutes = const Value.absent(), + this.compactMode = const Value.absent(), + this.backlogStalenessJson = const Value.absent(), + this.revision = const Value.absent(), + this.createdAtUtc = const Value.absent(), + this.updatedAtUtc = const Value.absent(), + this.rowid = const Value.absent(), + }); + SettingsTableCompanion.insert({ + required String ownerId, + required String timeZoneId, + required int dayStartMinutes, + required int dayEndMinutes, + required bool compactMode, + required String backlogStalenessJson, + required int revision, + required DateTime createdAtUtc, + required DateTime updatedAtUtc, + this.rowid = const Value.absent(), + }) : ownerId = Value(ownerId), + timeZoneId = Value(timeZoneId), + dayStartMinutes = Value(dayStartMinutes), + dayEndMinutes = Value(dayEndMinutes), + compactMode = Value(compactMode), + backlogStalenessJson = Value(backlogStalenessJson), + revision = Value(revision), + createdAtUtc = Value(createdAtUtc), + updatedAtUtc = Value(updatedAtUtc); + static Insertable custom({ + Expression? ownerId, + Expression? timeZoneId, + Expression? dayStartMinutes, + Expression? dayEndMinutes, + Expression? compactMode, + Expression? backlogStalenessJson, + Expression? revision, + Expression? createdAtUtc, + Expression? updatedAtUtc, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (ownerId != null) 'owner_id': ownerId, + if (timeZoneId != null) 'timezone_id': timeZoneId, + if (dayStartMinutes != null) 'day_start_minutes': dayStartMinutes, + if (dayEndMinutes != null) 'day_end_minutes': dayEndMinutes, + if (compactMode != null) 'compact_mode': compactMode, + if (backlogStalenessJson != null) + 'backlog_staleness_json': backlogStalenessJson, + if (revision != null) 'revision': revision, + if (createdAtUtc != null) 'created_at_utc': createdAtUtc, + if (updatedAtUtc != null) 'updated_at_utc': updatedAtUtc, + if (rowid != null) 'rowid': rowid, + }); + } + + SettingsTableCompanion copyWith( + {Value? ownerId, + Value? timeZoneId, + Value? dayStartMinutes, + Value? dayEndMinutes, + Value? compactMode, + Value? backlogStalenessJson, + Value? revision, + Value? createdAtUtc, + Value? updatedAtUtc, + Value? rowid}) { + return SettingsTableCompanion( + ownerId: ownerId ?? this.ownerId, + timeZoneId: timeZoneId ?? this.timeZoneId, + dayStartMinutes: dayStartMinutes ?? this.dayStartMinutes, + dayEndMinutes: dayEndMinutes ?? this.dayEndMinutes, + compactMode: compactMode ?? this.compactMode, + backlogStalenessJson: backlogStalenessJson ?? this.backlogStalenessJson, + revision: revision ?? this.revision, + createdAtUtc: createdAtUtc ?? this.createdAtUtc, + updatedAtUtc: updatedAtUtc ?? this.updatedAtUtc, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (timeZoneId.present) { + map['timezone_id'] = Variable(timeZoneId.value); + } + if (dayStartMinutes.present) { + map['day_start_minutes'] = Variable(dayStartMinutes.value); + } + if (dayEndMinutes.present) { + map['day_end_minutes'] = Variable(dayEndMinutes.value); + } + if (compactMode.present) { + map['compact_mode'] = Variable(compactMode.value); + } + if (backlogStalenessJson.present) { + map['backlog_staleness_json'] = + Variable(backlogStalenessJson.value); + } + if (revision.present) { + map['revision'] = Variable(revision.value); + } + if (createdAtUtc.present) { + map['created_at_utc'] = Variable(createdAtUtc.value); + } + if (updatedAtUtc.present) { + map['updated_at_utc'] = Variable(updatedAtUtc.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SettingsTableCompanion(') + ..write('ownerId: $ownerId, ') + ..write('timeZoneId: $timeZoneId, ') + ..write('dayStartMinutes: $dayStartMinutes, ') + ..write('dayEndMinutes: $dayEndMinutes, ') + ..write('compactMode: $compactMode, ') + ..write('backlogStalenessJson: $backlogStalenessJson, ') + ..write('revision: $revision, ') + ..write('createdAtUtc: $createdAtUtc, ') + ..write('updatedAtUtc: $updatedAtUtc, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $SnapshotsTable extends Snapshots + with TableInfo<$SnapshotsTable, SnapshotRow> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $SnapshotsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _ownerIdMeta = + const VerificationMeta('ownerId'); + @override + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _capturedAtUtcMeta = + const VerificationMeta('capturedAtUtc'); + @override + late final GeneratedColumn capturedAtUtc = + GeneratedColumn('captured_at_utc', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + static const VerificationMeta _operationNameMeta = + const VerificationMeta('operationName'); + @override + late final GeneratedColumn operationName = GeneratedColumn( + 'operation_name', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _sourceDateMeta = + const VerificationMeta('sourceDate'); + @override + late final GeneratedColumn sourceDate = GeneratedColumn( + 'source_date', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _targetDateMeta = + const VerificationMeta('targetDate'); + @override + late final GeneratedColumn targetDate = GeneratedColumn( + 'target_date', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _windowJsonMeta = + const VerificationMeta('windowJson'); + @override + late final GeneratedColumn windowJson = GeneratedColumn( + 'window_json', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _tasksJsonMeta = + const VerificationMeta('tasksJson'); + @override + late final GeneratedColumn tasksJson = GeneratedColumn( + 'tasks_json', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _lockedIntervalsJsonMeta = + const VerificationMeta('lockedIntervalsJson'); + @override + late final GeneratedColumn lockedIntervalsJson = + GeneratedColumn('locked_intervals_json', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _requiredVisibleIntervalsJsonMeta = + const VerificationMeta('requiredVisibleIntervalsJson'); + @override + late final GeneratedColumn requiredVisibleIntervalsJson = + GeneratedColumn( + 'required_visible_intervals_json', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _noticesJsonMeta = + const VerificationMeta('noticesJson'); + @override + late final GeneratedColumn noticesJson = GeneratedColumn( + 'notices_json', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _changesJsonMeta = + const VerificationMeta('changesJson'); + @override + late final GeneratedColumn changesJson = GeneratedColumn( + 'changes_json', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _overlapsJsonMeta = + const VerificationMeta('overlapsJson'); + @override + late final GeneratedColumn overlapsJson = GeneratedColumn( + 'overlaps_json', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _retentionExpiresUtcMeta = + const VerificationMeta('retentionExpiresUtc'); + @override + late final GeneratedColumn retentionExpiresUtc = + GeneratedColumn('retention_expires_utc', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + static const VerificationMeta _truncatedMeta = + const VerificationMeta('truncated'); + @override + late final GeneratedColumn truncated = GeneratedColumn( + 'truncated', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("truncated" IN (0, 1))')); + static const VerificationMeta _revisionMeta = + const VerificationMeta('revision'); + @override + late final GeneratedColumn revision = GeneratedColumn( + 'revision', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _createdAtUtcMeta = + const VerificationMeta('createdAtUtc'); + @override + late final GeneratedColumn createdAtUtc = GeneratedColumn( + 'created_at_utc', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + static const VerificationMeta _updatedAtUtcMeta = + const VerificationMeta('updatedAtUtc'); + @override + late final GeneratedColumn updatedAtUtc = GeneratedColumn( + 'updated_at_utc', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + @override + List get $columns => [ + id, + ownerId, + capturedAtUtc, + operationName, + sourceDate, + targetDate, + windowJson, + tasksJson, + lockedIntervalsJson, + requiredVisibleIntervalsJson, + noticesJson, + changesJson, + overlapsJson, + retentionExpiresUtc, + truncated, + revision, + createdAtUtc, + updatedAtUtc + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'snapshots'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('owner_id')) { + context.handle(_ownerIdMeta, + ownerId.isAcceptableOrUnknown(data['owner_id']!, _ownerIdMeta)); + } else if (isInserting) { + context.missing(_ownerIdMeta); + } + if (data.containsKey('captured_at_utc')) { + context.handle( + _capturedAtUtcMeta, + capturedAtUtc.isAcceptableOrUnknown( + data['captured_at_utc']!, _capturedAtUtcMeta)); + } else if (isInserting) { + context.missing(_capturedAtUtcMeta); + } + if (data.containsKey('operation_name')) { + context.handle( + _operationNameMeta, + operationName.isAcceptableOrUnknown( + data['operation_name']!, _operationNameMeta)); + } + if (data.containsKey('source_date')) { + context.handle( + _sourceDateMeta, + sourceDate.isAcceptableOrUnknown( + data['source_date']!, _sourceDateMeta)); + } + if (data.containsKey('target_date')) { + context.handle( + _targetDateMeta, + targetDate.isAcceptableOrUnknown( + data['target_date']!, _targetDateMeta)); + } + if (data.containsKey('window_json')) { + context.handle( + _windowJsonMeta, + windowJson.isAcceptableOrUnknown( + data['window_json']!, _windowJsonMeta)); + } else if (isInserting) { + context.missing(_windowJsonMeta); + } + if (data.containsKey('tasks_json')) { + context.handle(_tasksJsonMeta, + tasksJson.isAcceptableOrUnknown(data['tasks_json']!, _tasksJsonMeta)); + } else if (isInserting) { + context.missing(_tasksJsonMeta); + } + if (data.containsKey('locked_intervals_json')) { + context.handle( + _lockedIntervalsJsonMeta, + lockedIntervalsJson.isAcceptableOrUnknown( + data['locked_intervals_json']!, _lockedIntervalsJsonMeta)); + } else if (isInserting) { + context.missing(_lockedIntervalsJsonMeta); + } + if (data.containsKey('required_visible_intervals_json')) { + context.handle( + _requiredVisibleIntervalsJsonMeta, + requiredVisibleIntervalsJson.isAcceptableOrUnknown( + data['required_visible_intervals_json']!, + _requiredVisibleIntervalsJsonMeta)); + } else if (isInserting) { + context.missing(_requiredVisibleIntervalsJsonMeta); + } + if (data.containsKey('notices_json')) { + context.handle( + _noticesJsonMeta, + noticesJson.isAcceptableOrUnknown( + data['notices_json']!, _noticesJsonMeta)); + } else if (isInserting) { + context.missing(_noticesJsonMeta); + } + if (data.containsKey('changes_json')) { + context.handle( + _changesJsonMeta, + changesJson.isAcceptableOrUnknown( + data['changes_json']!, _changesJsonMeta)); + } else if (isInserting) { + context.missing(_changesJsonMeta); + } + if (data.containsKey('overlaps_json')) { + context.handle( + _overlapsJsonMeta, + overlapsJson.isAcceptableOrUnknown( + data['overlaps_json']!, _overlapsJsonMeta)); + } else if (isInserting) { + context.missing(_overlapsJsonMeta); + } + if (data.containsKey('retention_expires_utc')) { + context.handle( + _retentionExpiresUtcMeta, + retentionExpiresUtc.isAcceptableOrUnknown( + data['retention_expires_utc']!, _retentionExpiresUtcMeta)); + } else if (isInserting) { + context.missing(_retentionExpiresUtcMeta); + } + if (data.containsKey('truncated')) { + context.handle(_truncatedMeta, + truncated.isAcceptableOrUnknown(data['truncated']!, _truncatedMeta)); + } else if (isInserting) { + context.missing(_truncatedMeta); + } + if (data.containsKey('revision')) { + context.handle(_revisionMeta, + revision.isAcceptableOrUnknown(data['revision']!, _revisionMeta)); + } else if (isInserting) { + context.missing(_revisionMeta); + } + if (data.containsKey('created_at_utc')) { + context.handle( + _createdAtUtcMeta, + createdAtUtc.isAcceptableOrUnknown( + data['created_at_utc']!, _createdAtUtcMeta)); + } else if (isInserting) { + context.missing(_createdAtUtcMeta); + } + if (data.containsKey('updated_at_utc')) { + context.handle( + _updatedAtUtcMeta, + updatedAtUtc.isAcceptableOrUnknown( + data['updated_at_utc']!, _updatedAtUtcMeta)); + } else if (isInserting) { + context.missing(_updatedAtUtcMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + SnapshotRow map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SnapshotRow( + id: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}id'])!, + ownerId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}owner_id'])!, + capturedAtUtc: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}captured_at_utc'])!, + operationName: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}operation_name']), + sourceDate: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}source_date']), + targetDate: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}target_date']), + windowJson: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}window_json'])!, + tasksJson: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}tasks_json'])!, + lockedIntervalsJson: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}locked_intervals_json'])!, + requiredVisibleIntervalsJson: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}required_visible_intervals_json'])!, + noticesJson: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}notices_json'])!, + changesJson: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}changes_json'])!, + overlapsJson: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}overlaps_json'])!, + retentionExpiresUtc: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}retention_expires_utc'])!, + truncated: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}truncated'])!, + revision: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}revision'])!, + createdAtUtc: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}created_at_utc'])!, + updatedAtUtc: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}updated_at_utc'])!, + ); + } + + @override + $SnapshotsTable createAlias(String alias) { + return $SnapshotsTable(attachedDatabase, alias); + } +} + +class SnapshotRow extends DataClass implements Insertable { + final String id; + final String ownerId; + final DateTime capturedAtUtc; + final String? operationName; + final String? sourceDate; + final String? targetDate; + final String windowJson; + final String tasksJson; + final String lockedIntervalsJson; + final String requiredVisibleIntervalsJson; + final String noticesJson; + final String changesJson; + final String overlapsJson; + final DateTime retentionExpiresUtc; + final bool truncated; + final int revision; + final DateTime createdAtUtc; + final DateTime updatedAtUtc; + const SnapshotRow( + {required this.id, + required this.ownerId, + required this.capturedAtUtc, + this.operationName, + this.sourceDate, + this.targetDate, + required this.windowJson, + required this.tasksJson, + required this.lockedIntervalsJson, + required this.requiredVisibleIntervalsJson, + required this.noticesJson, + required this.changesJson, + required this.overlapsJson, + required this.retentionExpiresUtc, + required this.truncated, + required this.revision, + required this.createdAtUtc, + required this.updatedAtUtc}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['owner_id'] = Variable(ownerId); + map['captured_at_utc'] = Variable(capturedAtUtc); + if (!nullToAbsent || operationName != null) { + map['operation_name'] = Variable(operationName); + } + if (!nullToAbsent || sourceDate != null) { + map['source_date'] = Variable(sourceDate); + } + if (!nullToAbsent || targetDate != null) { + map['target_date'] = Variable(targetDate); + } + map['window_json'] = Variable(windowJson); + map['tasks_json'] = Variable(tasksJson); + map['locked_intervals_json'] = Variable(lockedIntervalsJson); + map['required_visible_intervals_json'] = + Variable(requiredVisibleIntervalsJson); + map['notices_json'] = Variable(noticesJson); + map['changes_json'] = Variable(changesJson); + map['overlaps_json'] = Variable(overlapsJson); + map['retention_expires_utc'] = Variable(retentionExpiresUtc); + map['truncated'] = Variable(truncated); + map['revision'] = Variable(revision); + map['created_at_utc'] = Variable(createdAtUtc); + map['updated_at_utc'] = Variable(updatedAtUtc); + return map; + } + + SnapshotsCompanion toCompanion(bool nullToAbsent) { + return SnapshotsCompanion( + id: Value(id), + ownerId: Value(ownerId), + capturedAtUtc: Value(capturedAtUtc), + operationName: operationName == null && nullToAbsent + ? const Value.absent() + : Value(operationName), + sourceDate: sourceDate == null && nullToAbsent + ? const Value.absent() + : Value(sourceDate), + targetDate: targetDate == null && nullToAbsent + ? const Value.absent() + : Value(targetDate), + windowJson: Value(windowJson), + tasksJson: Value(tasksJson), + lockedIntervalsJson: Value(lockedIntervalsJson), + requiredVisibleIntervalsJson: Value(requiredVisibleIntervalsJson), + noticesJson: Value(noticesJson), + changesJson: Value(changesJson), + overlapsJson: Value(overlapsJson), + retentionExpiresUtc: Value(retentionExpiresUtc), + truncated: Value(truncated), + revision: Value(revision), + createdAtUtc: Value(createdAtUtc), + updatedAtUtc: Value(updatedAtUtc), + ); + } + + factory SnapshotRow.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SnapshotRow( + id: serializer.fromJson(json['id']), + ownerId: serializer.fromJson(json['ownerId']), + capturedAtUtc: serializer.fromJson(json['capturedAtUtc']), + operationName: serializer.fromJson(json['operationName']), + sourceDate: serializer.fromJson(json['sourceDate']), + targetDate: serializer.fromJson(json['targetDate']), + windowJson: serializer.fromJson(json['windowJson']), + tasksJson: serializer.fromJson(json['tasksJson']), + lockedIntervalsJson: + serializer.fromJson(json['lockedIntervalsJson']), + requiredVisibleIntervalsJson: + serializer.fromJson(json['requiredVisibleIntervalsJson']), + noticesJson: serializer.fromJson(json['noticesJson']), + changesJson: serializer.fromJson(json['changesJson']), + overlapsJson: serializer.fromJson(json['overlapsJson']), + retentionExpiresUtc: + serializer.fromJson(json['retentionExpiresUtc']), + truncated: serializer.fromJson(json['truncated']), + revision: serializer.fromJson(json['revision']), + createdAtUtc: serializer.fromJson(json['createdAtUtc']), + updatedAtUtc: serializer.fromJson(json['updatedAtUtc']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'ownerId': serializer.toJson(ownerId), + 'capturedAtUtc': serializer.toJson(capturedAtUtc), + 'operationName': serializer.toJson(operationName), + 'sourceDate': serializer.toJson(sourceDate), + 'targetDate': serializer.toJson(targetDate), + 'windowJson': serializer.toJson(windowJson), + 'tasksJson': serializer.toJson(tasksJson), + 'lockedIntervalsJson': serializer.toJson(lockedIntervalsJson), + 'requiredVisibleIntervalsJson': + serializer.toJson(requiredVisibleIntervalsJson), + 'noticesJson': serializer.toJson(noticesJson), + 'changesJson': serializer.toJson(changesJson), + 'overlapsJson': serializer.toJson(overlapsJson), + 'retentionExpiresUtc': serializer.toJson(retentionExpiresUtc), + 'truncated': serializer.toJson(truncated), + 'revision': serializer.toJson(revision), + 'createdAtUtc': serializer.toJson(createdAtUtc), + 'updatedAtUtc': serializer.toJson(updatedAtUtc), + }; + } + + SnapshotRow copyWith( + {String? id, + String? ownerId, + DateTime? capturedAtUtc, + Value operationName = const Value.absent(), + Value sourceDate = const Value.absent(), + Value targetDate = const Value.absent(), + String? windowJson, + String? tasksJson, + String? lockedIntervalsJson, + String? requiredVisibleIntervalsJson, + String? noticesJson, + String? changesJson, + String? overlapsJson, + DateTime? retentionExpiresUtc, + bool? truncated, + int? revision, + DateTime? createdAtUtc, + DateTime? updatedAtUtc}) => + SnapshotRow( + id: id ?? this.id, + ownerId: ownerId ?? this.ownerId, + capturedAtUtc: capturedAtUtc ?? this.capturedAtUtc, + operationName: + operationName.present ? operationName.value : this.operationName, + sourceDate: sourceDate.present ? sourceDate.value : this.sourceDate, + targetDate: targetDate.present ? targetDate.value : this.targetDate, + windowJson: windowJson ?? this.windowJson, + tasksJson: tasksJson ?? this.tasksJson, + lockedIntervalsJson: lockedIntervalsJson ?? this.lockedIntervalsJson, + requiredVisibleIntervalsJson: + requiredVisibleIntervalsJson ?? this.requiredVisibleIntervalsJson, + noticesJson: noticesJson ?? this.noticesJson, + changesJson: changesJson ?? this.changesJson, + overlapsJson: overlapsJson ?? this.overlapsJson, + retentionExpiresUtc: retentionExpiresUtc ?? this.retentionExpiresUtc, + truncated: truncated ?? this.truncated, + revision: revision ?? this.revision, + createdAtUtc: createdAtUtc ?? this.createdAtUtc, + updatedAtUtc: updatedAtUtc ?? this.updatedAtUtc, + ); + SnapshotRow copyWithCompanion(SnapshotsCompanion data) { + return SnapshotRow( + id: data.id.present ? data.id.value : this.id, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + capturedAtUtc: data.capturedAtUtc.present + ? data.capturedAtUtc.value + : this.capturedAtUtc, + operationName: data.operationName.present + ? data.operationName.value + : this.operationName, + sourceDate: + data.sourceDate.present ? data.sourceDate.value : this.sourceDate, + targetDate: + data.targetDate.present ? data.targetDate.value : this.targetDate, + windowJson: + data.windowJson.present ? data.windowJson.value : this.windowJson, + tasksJson: data.tasksJson.present ? data.tasksJson.value : this.tasksJson, + lockedIntervalsJson: data.lockedIntervalsJson.present + ? data.lockedIntervalsJson.value + : this.lockedIntervalsJson, + requiredVisibleIntervalsJson: data.requiredVisibleIntervalsJson.present + ? data.requiredVisibleIntervalsJson.value + : this.requiredVisibleIntervalsJson, + noticesJson: + data.noticesJson.present ? data.noticesJson.value : this.noticesJson, + changesJson: + data.changesJson.present ? data.changesJson.value : this.changesJson, + overlapsJson: data.overlapsJson.present + ? data.overlapsJson.value + : this.overlapsJson, + retentionExpiresUtc: data.retentionExpiresUtc.present + ? data.retentionExpiresUtc.value + : this.retentionExpiresUtc, + truncated: data.truncated.present ? data.truncated.value : this.truncated, + revision: data.revision.present ? data.revision.value : this.revision, + createdAtUtc: data.createdAtUtc.present + ? data.createdAtUtc.value + : this.createdAtUtc, + updatedAtUtc: data.updatedAtUtc.present + ? data.updatedAtUtc.value + : this.updatedAtUtc, + ); + } + + @override + String toString() { + return (StringBuffer('SnapshotRow(') + ..write('id: $id, ') + ..write('ownerId: $ownerId, ') + ..write('capturedAtUtc: $capturedAtUtc, ') + ..write('operationName: $operationName, ') + ..write('sourceDate: $sourceDate, ') + ..write('targetDate: $targetDate, ') + ..write('windowJson: $windowJson, ') + ..write('tasksJson: $tasksJson, ') + ..write('lockedIntervalsJson: $lockedIntervalsJson, ') + ..write( + 'requiredVisibleIntervalsJson: $requiredVisibleIntervalsJson, ') + ..write('noticesJson: $noticesJson, ') + ..write('changesJson: $changesJson, ') + ..write('overlapsJson: $overlapsJson, ') + ..write('retentionExpiresUtc: $retentionExpiresUtc, ') + ..write('truncated: $truncated, ') + ..write('revision: $revision, ') + ..write('createdAtUtc: $createdAtUtc, ') + ..write('updatedAtUtc: $updatedAtUtc') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + ownerId, + capturedAtUtc, + operationName, + sourceDate, + targetDate, + windowJson, + tasksJson, + lockedIntervalsJson, + requiredVisibleIntervalsJson, + noticesJson, + changesJson, + overlapsJson, + retentionExpiresUtc, + truncated, + revision, + createdAtUtc, + updatedAtUtc); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SnapshotRow && + other.id == this.id && + other.ownerId == this.ownerId && + other.capturedAtUtc == this.capturedAtUtc && + other.operationName == this.operationName && + other.sourceDate == this.sourceDate && + other.targetDate == this.targetDate && + other.windowJson == this.windowJson && + other.tasksJson == this.tasksJson && + other.lockedIntervalsJson == this.lockedIntervalsJson && + other.requiredVisibleIntervalsJson == + this.requiredVisibleIntervalsJson && + other.noticesJson == this.noticesJson && + other.changesJson == this.changesJson && + other.overlapsJson == this.overlapsJson && + other.retentionExpiresUtc == this.retentionExpiresUtc && + other.truncated == this.truncated && + other.revision == this.revision && + other.createdAtUtc == this.createdAtUtc && + other.updatedAtUtc == this.updatedAtUtc); +} + +class SnapshotsCompanion extends UpdateCompanion { + final Value id; + final Value ownerId; + final Value capturedAtUtc; + final Value operationName; + final Value sourceDate; + final Value targetDate; + final Value windowJson; + final Value tasksJson; + final Value lockedIntervalsJson; + final Value requiredVisibleIntervalsJson; + final Value noticesJson; + final Value changesJson; + final Value overlapsJson; + final Value retentionExpiresUtc; + final Value truncated; + final Value revision; + final Value createdAtUtc; + final Value updatedAtUtc; + final Value rowid; + const SnapshotsCompanion({ + this.id = const Value.absent(), + this.ownerId = const Value.absent(), + this.capturedAtUtc = const Value.absent(), + this.operationName = const Value.absent(), + this.sourceDate = const Value.absent(), + this.targetDate = const Value.absent(), + this.windowJson = const Value.absent(), + this.tasksJson = const Value.absent(), + this.lockedIntervalsJson = const Value.absent(), + this.requiredVisibleIntervalsJson = const Value.absent(), + this.noticesJson = const Value.absent(), + this.changesJson = const Value.absent(), + this.overlapsJson = const Value.absent(), + this.retentionExpiresUtc = const Value.absent(), + this.truncated = const Value.absent(), + this.revision = const Value.absent(), + this.createdAtUtc = const Value.absent(), + this.updatedAtUtc = const Value.absent(), + this.rowid = const Value.absent(), + }); + SnapshotsCompanion.insert({ + required String id, + required String ownerId, + required DateTime capturedAtUtc, + this.operationName = const Value.absent(), + this.sourceDate = const Value.absent(), + this.targetDate = const Value.absent(), + required String windowJson, + required String tasksJson, + required String lockedIntervalsJson, + required String requiredVisibleIntervalsJson, + required String noticesJson, + required String changesJson, + required String overlapsJson, + required DateTime retentionExpiresUtc, + required bool truncated, + required int revision, + required DateTime createdAtUtc, + required DateTime updatedAtUtc, + this.rowid = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + capturedAtUtc = Value(capturedAtUtc), + windowJson = Value(windowJson), + tasksJson = Value(tasksJson), + lockedIntervalsJson = Value(lockedIntervalsJson), + requiredVisibleIntervalsJson = Value(requiredVisibleIntervalsJson), + noticesJson = Value(noticesJson), + changesJson = Value(changesJson), + overlapsJson = Value(overlapsJson), + retentionExpiresUtc = Value(retentionExpiresUtc), + truncated = Value(truncated), + revision = Value(revision), + createdAtUtc = Value(createdAtUtc), + updatedAtUtc = Value(updatedAtUtc); + static Insertable custom({ + Expression? id, + Expression? ownerId, + Expression? capturedAtUtc, + Expression? operationName, + Expression? sourceDate, + Expression? targetDate, + Expression? windowJson, + Expression? tasksJson, + Expression? lockedIntervalsJson, + Expression? requiredVisibleIntervalsJson, + Expression? noticesJson, + Expression? changesJson, + Expression? overlapsJson, + Expression? retentionExpiresUtc, + Expression? truncated, + Expression? revision, + Expression? createdAtUtc, + Expression? updatedAtUtc, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (ownerId != null) 'owner_id': ownerId, + if (capturedAtUtc != null) 'captured_at_utc': capturedAtUtc, + if (operationName != null) 'operation_name': operationName, + if (sourceDate != null) 'source_date': sourceDate, + if (targetDate != null) 'target_date': targetDate, + if (windowJson != null) 'window_json': windowJson, + if (tasksJson != null) 'tasks_json': tasksJson, + if (lockedIntervalsJson != null) + 'locked_intervals_json': lockedIntervalsJson, + if (requiredVisibleIntervalsJson != null) + 'required_visible_intervals_json': requiredVisibleIntervalsJson, + if (noticesJson != null) 'notices_json': noticesJson, + if (changesJson != null) 'changes_json': changesJson, + if (overlapsJson != null) 'overlaps_json': overlapsJson, + if (retentionExpiresUtc != null) + 'retention_expires_utc': retentionExpiresUtc, + if (truncated != null) 'truncated': truncated, + if (revision != null) 'revision': revision, + if (createdAtUtc != null) 'created_at_utc': createdAtUtc, + if (updatedAtUtc != null) 'updated_at_utc': updatedAtUtc, + if (rowid != null) 'rowid': rowid, + }); + } + + SnapshotsCompanion copyWith( + {Value? id, + Value? ownerId, + Value? capturedAtUtc, + Value? operationName, + Value? sourceDate, + Value? targetDate, + Value? windowJson, + Value? tasksJson, + Value? lockedIntervalsJson, + Value? requiredVisibleIntervalsJson, + Value? noticesJson, + Value? changesJson, + Value? overlapsJson, + Value? retentionExpiresUtc, + Value? truncated, + Value? revision, + Value? createdAtUtc, + Value? updatedAtUtc, + Value? rowid}) { + return SnapshotsCompanion( + id: id ?? this.id, + ownerId: ownerId ?? this.ownerId, + capturedAtUtc: capturedAtUtc ?? this.capturedAtUtc, + operationName: operationName ?? this.operationName, + sourceDate: sourceDate ?? this.sourceDate, + targetDate: targetDate ?? this.targetDate, + windowJson: windowJson ?? this.windowJson, + tasksJson: tasksJson ?? this.tasksJson, + lockedIntervalsJson: lockedIntervalsJson ?? this.lockedIntervalsJson, + requiredVisibleIntervalsJson: + requiredVisibleIntervalsJson ?? this.requiredVisibleIntervalsJson, + noticesJson: noticesJson ?? this.noticesJson, + changesJson: changesJson ?? this.changesJson, + overlapsJson: overlapsJson ?? this.overlapsJson, + retentionExpiresUtc: retentionExpiresUtc ?? this.retentionExpiresUtc, + truncated: truncated ?? this.truncated, + revision: revision ?? this.revision, + createdAtUtc: createdAtUtc ?? this.createdAtUtc, + updatedAtUtc: updatedAtUtc ?? this.updatedAtUtc, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (capturedAtUtc.present) { + map['captured_at_utc'] = Variable(capturedAtUtc.value); + } + if (operationName.present) { + map['operation_name'] = Variable(operationName.value); + } + if (sourceDate.present) { + map['source_date'] = Variable(sourceDate.value); + } + if (targetDate.present) { + map['target_date'] = Variable(targetDate.value); + } + if (windowJson.present) { + map['window_json'] = Variable(windowJson.value); + } + if (tasksJson.present) { + map['tasks_json'] = Variable(tasksJson.value); + } + if (lockedIntervalsJson.present) { + map['locked_intervals_json'] = + Variable(lockedIntervalsJson.value); + } + if (requiredVisibleIntervalsJson.present) { + map['required_visible_intervals_json'] = + Variable(requiredVisibleIntervalsJson.value); + } + if (noticesJson.present) { + map['notices_json'] = Variable(noticesJson.value); + } + if (changesJson.present) { + map['changes_json'] = Variable(changesJson.value); + } + if (overlapsJson.present) { + map['overlaps_json'] = Variable(overlapsJson.value); + } + if (retentionExpiresUtc.present) { + map['retention_expires_utc'] = + Variable(retentionExpiresUtc.value); + } + if (truncated.present) { + map['truncated'] = Variable(truncated.value); + } + if (revision.present) { + map['revision'] = Variable(revision.value); + } + if (createdAtUtc.present) { + map['created_at_utc'] = Variable(createdAtUtc.value); + } + if (updatedAtUtc.present) { + map['updated_at_utc'] = Variable(updatedAtUtc.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SnapshotsCompanion(') + ..write('id: $id, ') + ..write('ownerId: $ownerId, ') + ..write('capturedAtUtc: $capturedAtUtc, ') + ..write('operationName: $operationName, ') + ..write('sourceDate: $sourceDate, ') + ..write('targetDate: $targetDate, ') + ..write('windowJson: $windowJson, ') + ..write('tasksJson: $tasksJson, ') + ..write('lockedIntervalsJson: $lockedIntervalsJson, ') + ..write( + 'requiredVisibleIntervalsJson: $requiredVisibleIntervalsJson, ') + ..write('noticesJson: $noticesJson, ') + ..write('changesJson: $changesJson, ') + ..write('overlapsJson: $overlapsJson, ') + ..write('retentionExpiresUtc: $retentionExpiresUtc, ') + ..write('truncated: $truncated, ') + ..write('revision: $revision, ') + ..write('createdAtUtc: $createdAtUtc, ') + ..write('updatedAtUtc: $updatedAtUtc, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +abstract class _$SchedulerDb extends GeneratedDatabase { + _$SchedulerDb(QueryExecutor e) : super(e); + $SchedulerDbManager get managers => $SchedulerDbManager(this); + late final $TasksTable tasks = $TasksTable(this); + late final $ProjectsTable projects = $ProjectsTable(this); + late final $LockedBlocksTable lockedBlocks = $LockedBlocksTable(this); + late final $LockedOverridesTable lockedOverrides = + $LockedOverridesTable(this); + late final $SettingsTableTable settingsTable = $SettingsTableTable(this); + late final $SnapshotsTable snapshots = $SnapshotsTable(this); + late final TaskDao taskDao = TaskDao(this as SchedulerDb); + late final ProjectDao projectDao = ProjectDao(this as SchedulerDb); + late final LockedBlockDao lockedBlockDao = + LockedBlockDao(this as SchedulerDb); + late final SettingsDao settingsDao = SettingsDao(this as SchedulerDb); + late final SnapshotDao snapshotDao = SnapshotDao(this as SchedulerDb); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + tasks, + projects, + lockedBlocks, + lockedOverrides, + settingsTable, + snapshots + ]; +} + +typedef $$TasksTableCreateCompanionBuilder = TasksCompanion Function({ + required String id, + required String ownerId, + required String title, + required String projectId, + Value parentId, + required String type, + required String status, + Value priority, + required String reward, + required String difficulty, + Value durationMinutes, + Value scheduledStartUtc, + Value scheduledEndUtc, + Value actualStartUtc, + Value actualEndUtc, + Value completedAtUtc, + required String backlogTagsJson, + Value reminderOverride, + required String statsJson, + Value backlogEnteredAtUtc, + Value backlogEnteredProvenance, + required int revision, + required DateTime createdAtUtc, + required DateTime updatedAtUtc, + Value rowid, +}); +typedef $$TasksTableUpdateCompanionBuilder = TasksCompanion Function({ + Value id, + Value ownerId, + Value title, + Value projectId, + Value parentId, + Value type, + Value status, + Value priority, + Value reward, + Value difficulty, + Value durationMinutes, + Value scheduledStartUtc, + Value scheduledEndUtc, + Value actualStartUtc, + Value actualEndUtc, + Value completedAtUtc, + Value backlogTagsJson, + Value reminderOverride, + Value statsJson, + Value backlogEnteredAtUtc, + Value backlogEnteredProvenance, + Value revision, + Value createdAtUtc, + Value updatedAtUtc, + Value rowid, +}); + +class $$TasksTableFilterComposer extends Composer<_$SchedulerDb, $TasksTable> { + $$TasksTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnFilters(column)); + + ColumnFilters get ownerId => $composableBuilder( + column: $table.ownerId, builder: (column) => ColumnFilters(column)); + + ColumnFilters get title => $composableBuilder( + column: $table.title, builder: (column) => ColumnFilters(column)); + + ColumnFilters get projectId => $composableBuilder( + column: $table.projectId, builder: (column) => ColumnFilters(column)); + + ColumnFilters get parentId => $composableBuilder( + column: $table.parentId, builder: (column) => ColumnFilters(column)); + + ColumnFilters get type => $composableBuilder( + column: $table.type, builder: (column) => ColumnFilters(column)); + + ColumnFilters get status => $composableBuilder( + column: $table.status, builder: (column) => ColumnFilters(column)); + + ColumnFilters get priority => $composableBuilder( + column: $table.priority, builder: (column) => ColumnFilters(column)); + + ColumnFilters get reward => $composableBuilder( + column: $table.reward, builder: (column) => ColumnFilters(column)); + + ColumnFilters get difficulty => $composableBuilder( + column: $table.difficulty, builder: (column) => ColumnFilters(column)); + + ColumnFilters get durationMinutes => $composableBuilder( + column: $table.durationMinutes, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get scheduledStartUtc => $composableBuilder( + column: $table.scheduledStartUtc, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get scheduledEndUtc => $composableBuilder( + column: $table.scheduledEndUtc, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get actualStartUtc => $composableBuilder( + column: $table.actualStartUtc, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get actualEndUtc => $composableBuilder( + column: $table.actualEndUtc, builder: (column) => ColumnFilters(column)); + + ColumnFilters get completedAtUtc => $composableBuilder( + column: $table.completedAtUtc, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get backlogTagsJson => $composableBuilder( + column: $table.backlogTagsJson, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get reminderOverride => $composableBuilder( + column: $table.reminderOverride, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get statsJson => $composableBuilder( + column: $table.statsJson, builder: (column) => ColumnFilters(column)); + + ColumnFilters get backlogEnteredAtUtc => $composableBuilder( + column: $table.backlogEnteredAtUtc, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get backlogEnteredProvenance => $composableBuilder( + column: $table.backlogEnteredProvenance, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get revision => $composableBuilder( + column: $table.revision, builder: (column) => ColumnFilters(column)); + + ColumnFilters get createdAtUtc => $composableBuilder( + column: $table.createdAtUtc, builder: (column) => ColumnFilters(column)); + + ColumnFilters get updatedAtUtc => $composableBuilder( + column: $table.updatedAtUtc, builder: (column) => ColumnFilters(column)); +} + +class $$TasksTableOrderingComposer + extends Composer<_$SchedulerDb, $TasksTable> { + $$TasksTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get ownerId => $composableBuilder( + column: $table.ownerId, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get title => $composableBuilder( + column: $table.title, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get projectId => $composableBuilder( + column: $table.projectId, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get parentId => $composableBuilder( + column: $table.parentId, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get type => $composableBuilder( + column: $table.type, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get status => $composableBuilder( + column: $table.status, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get priority => $composableBuilder( + column: $table.priority, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get reward => $composableBuilder( + column: $table.reward, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get difficulty => $composableBuilder( + column: $table.difficulty, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get durationMinutes => $composableBuilder( + column: $table.durationMinutes, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get scheduledStartUtc => $composableBuilder( + column: $table.scheduledStartUtc, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get scheduledEndUtc => $composableBuilder( + column: $table.scheduledEndUtc, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get actualStartUtc => $composableBuilder( + column: $table.actualStartUtc, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get actualEndUtc => $composableBuilder( + column: $table.actualEndUtc, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get completedAtUtc => $composableBuilder( + column: $table.completedAtUtc, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get backlogTagsJson => $composableBuilder( + column: $table.backlogTagsJson, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get reminderOverride => $composableBuilder( + column: $table.reminderOverride, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get statsJson => $composableBuilder( + column: $table.statsJson, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get backlogEnteredAtUtc => $composableBuilder( + column: $table.backlogEnteredAtUtc, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get backlogEnteredProvenance => $composableBuilder( + column: $table.backlogEnteredProvenance, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get revision => $composableBuilder( + column: $table.revision, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get createdAtUtc => $composableBuilder( + column: $table.createdAtUtc, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get updatedAtUtc => $composableBuilder( + column: $table.updatedAtUtc, + builder: (column) => ColumnOrderings(column)); +} + +class $$TasksTableAnnotationComposer + extends Composer<_$SchedulerDb, $TasksTable> { + $$TasksTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get ownerId => + $composableBuilder(column: $table.ownerId, builder: (column) => column); + + GeneratedColumn get title => + $composableBuilder(column: $table.title, builder: (column) => column); + + GeneratedColumn get projectId => + $composableBuilder(column: $table.projectId, builder: (column) => column); + + GeneratedColumn get parentId => + $composableBuilder(column: $table.parentId, builder: (column) => column); + + GeneratedColumn get type => + $composableBuilder(column: $table.type, builder: (column) => column); + + GeneratedColumn get status => + $composableBuilder(column: $table.status, builder: (column) => column); + + GeneratedColumn get priority => + $composableBuilder(column: $table.priority, builder: (column) => column); + + GeneratedColumn get reward => + $composableBuilder(column: $table.reward, builder: (column) => column); + + GeneratedColumn get difficulty => $composableBuilder( + column: $table.difficulty, builder: (column) => column); + + GeneratedColumn get durationMinutes => $composableBuilder( + column: $table.durationMinutes, builder: (column) => column); + + GeneratedColumn get scheduledStartUtc => $composableBuilder( + column: $table.scheduledStartUtc, builder: (column) => column); + + GeneratedColumn get scheduledEndUtc => $composableBuilder( + column: $table.scheduledEndUtc, builder: (column) => column); + + GeneratedColumn get actualStartUtc => $composableBuilder( + column: $table.actualStartUtc, builder: (column) => column); + + GeneratedColumn get actualEndUtc => $composableBuilder( + column: $table.actualEndUtc, builder: (column) => column); + + GeneratedColumn get completedAtUtc => $composableBuilder( + column: $table.completedAtUtc, builder: (column) => column); + + GeneratedColumn get backlogTagsJson => $composableBuilder( + column: $table.backlogTagsJson, builder: (column) => column); + + GeneratedColumn get reminderOverride => $composableBuilder( + column: $table.reminderOverride, builder: (column) => column); + + GeneratedColumn get statsJson => + $composableBuilder(column: $table.statsJson, builder: (column) => column); + + GeneratedColumn get backlogEnteredAtUtc => $composableBuilder( + column: $table.backlogEnteredAtUtc, builder: (column) => column); + + GeneratedColumn get backlogEnteredProvenance => $composableBuilder( + column: $table.backlogEnteredProvenance, builder: (column) => column); + + GeneratedColumn get revision => + $composableBuilder(column: $table.revision, builder: (column) => column); + + GeneratedColumn get createdAtUtc => $composableBuilder( + column: $table.createdAtUtc, builder: (column) => column); + + GeneratedColumn get updatedAtUtc => $composableBuilder( + column: $table.updatedAtUtc, builder: (column) => column); +} + +class $$TasksTableTableManager extends RootTableManager< + _$SchedulerDb, + $TasksTable, + TaskRow, + $$TasksTableFilterComposer, + $$TasksTableOrderingComposer, + $$TasksTableAnnotationComposer, + $$TasksTableCreateCompanionBuilder, + $$TasksTableUpdateCompanionBuilder, + (TaskRow, BaseReferences<_$SchedulerDb, $TasksTable, TaskRow>), + TaskRow, + PrefetchHooks Function()> { + $$TasksTableTableManager(_$SchedulerDb db, $TasksTable table) + : super(TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$TasksTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$TasksTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$TasksTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + Value id = const Value.absent(), + Value ownerId = const Value.absent(), + Value title = const Value.absent(), + Value projectId = const Value.absent(), + Value parentId = const Value.absent(), + Value type = const Value.absent(), + Value status = const Value.absent(), + Value priority = const Value.absent(), + Value reward = const Value.absent(), + Value difficulty = const Value.absent(), + Value durationMinutes = const Value.absent(), + Value scheduledStartUtc = const Value.absent(), + Value scheduledEndUtc = const Value.absent(), + Value actualStartUtc = const Value.absent(), + Value actualEndUtc = const Value.absent(), + Value completedAtUtc = const Value.absent(), + Value backlogTagsJson = const Value.absent(), + Value reminderOverride = const Value.absent(), + Value statsJson = const Value.absent(), + Value backlogEnteredAtUtc = const Value.absent(), + Value backlogEnteredProvenance = const Value.absent(), + Value revision = const Value.absent(), + Value createdAtUtc = const Value.absent(), + Value updatedAtUtc = const Value.absent(), + Value rowid = const Value.absent(), + }) => + TasksCompanion( + id: id, + ownerId: ownerId, + title: title, + projectId: projectId, + parentId: parentId, + type: type, + status: status, + priority: priority, + reward: reward, + difficulty: difficulty, + durationMinutes: durationMinutes, + scheduledStartUtc: scheduledStartUtc, + scheduledEndUtc: scheduledEndUtc, + actualStartUtc: actualStartUtc, + actualEndUtc: actualEndUtc, + completedAtUtc: completedAtUtc, + backlogTagsJson: backlogTagsJson, + reminderOverride: reminderOverride, + statsJson: statsJson, + backlogEnteredAtUtc: backlogEnteredAtUtc, + backlogEnteredProvenance: backlogEnteredProvenance, + revision: revision, + createdAtUtc: createdAtUtc, + updatedAtUtc: updatedAtUtc, + rowid: rowid, + ), + createCompanionCallback: ({ + required String id, + required String ownerId, + required String title, + required String projectId, + Value parentId = const Value.absent(), + required String type, + required String status, + Value priority = const Value.absent(), + required String reward, + required String difficulty, + Value durationMinutes = const Value.absent(), + Value scheduledStartUtc = const Value.absent(), + Value scheduledEndUtc = const Value.absent(), + Value actualStartUtc = const Value.absent(), + Value actualEndUtc = const Value.absent(), + Value completedAtUtc = const Value.absent(), + required String backlogTagsJson, + Value reminderOverride = const Value.absent(), + required String statsJson, + Value backlogEnteredAtUtc = const Value.absent(), + Value backlogEnteredProvenance = const Value.absent(), + required int revision, + required DateTime createdAtUtc, + required DateTime updatedAtUtc, + Value rowid = const Value.absent(), + }) => + TasksCompanion.insert( + id: id, + ownerId: ownerId, + title: title, + projectId: projectId, + parentId: parentId, + type: type, + status: status, + priority: priority, + reward: reward, + difficulty: difficulty, + durationMinutes: durationMinutes, + scheduledStartUtc: scheduledStartUtc, + scheduledEndUtc: scheduledEndUtc, + actualStartUtc: actualStartUtc, + actualEndUtc: actualEndUtc, + completedAtUtc: completedAtUtc, + backlogTagsJson: backlogTagsJson, + reminderOverride: reminderOverride, + statsJson: statsJson, + backlogEnteredAtUtc: backlogEnteredAtUtc, + backlogEnteredProvenance: backlogEnteredProvenance, + revision: revision, + createdAtUtc: createdAtUtc, + updatedAtUtc: updatedAtUtc, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$TasksTableProcessedTableManager = ProcessedTableManager< + _$SchedulerDb, + $TasksTable, + TaskRow, + $$TasksTableFilterComposer, + $$TasksTableOrderingComposer, + $$TasksTableAnnotationComposer, + $$TasksTableCreateCompanionBuilder, + $$TasksTableUpdateCompanionBuilder, + (TaskRow, BaseReferences<_$SchedulerDb, $TasksTable, TaskRow>), + TaskRow, + PrefetchHooks Function()>; +typedef $$ProjectsTableCreateCompanionBuilder = ProjectsCompanion Function({ + required String id, + required String ownerId, + required String name, + required String colorKey, + required String defaultPriority, + required String defaultReward, + required String defaultDifficulty, + required String defaultReminderProfile, + Value defaultDurationMinutes, + Value archivedAtUtc, + required int revision, + required DateTime createdAtUtc, + required DateTime updatedAtUtc, + Value rowid, +}); +typedef $$ProjectsTableUpdateCompanionBuilder = ProjectsCompanion Function({ + Value id, + Value ownerId, + Value name, + Value colorKey, + Value defaultPriority, + Value defaultReward, + Value defaultDifficulty, + Value defaultReminderProfile, + Value defaultDurationMinutes, + Value archivedAtUtc, + Value revision, + Value createdAtUtc, + Value updatedAtUtc, + Value rowid, +}); + +class $$ProjectsTableFilterComposer + extends Composer<_$SchedulerDb, $ProjectsTable> { + $$ProjectsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnFilters(column)); + + ColumnFilters get ownerId => $composableBuilder( + column: $table.ownerId, builder: (column) => ColumnFilters(column)); + + ColumnFilters get name => $composableBuilder( + column: $table.name, builder: (column) => ColumnFilters(column)); + + ColumnFilters get colorKey => $composableBuilder( + column: $table.colorKey, builder: (column) => ColumnFilters(column)); + + ColumnFilters get defaultPriority => $composableBuilder( + column: $table.defaultPriority, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get defaultReward => $composableBuilder( + column: $table.defaultReward, builder: (column) => ColumnFilters(column)); + + ColumnFilters get defaultDifficulty => $composableBuilder( + column: $table.defaultDifficulty, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get defaultReminderProfile => $composableBuilder( + column: $table.defaultReminderProfile, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get defaultDurationMinutes => $composableBuilder( + column: $table.defaultDurationMinutes, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get archivedAtUtc => $composableBuilder( + column: $table.archivedAtUtc, builder: (column) => ColumnFilters(column)); + + ColumnFilters get revision => $composableBuilder( + column: $table.revision, builder: (column) => ColumnFilters(column)); + + ColumnFilters get createdAtUtc => $composableBuilder( + column: $table.createdAtUtc, builder: (column) => ColumnFilters(column)); + + ColumnFilters get updatedAtUtc => $composableBuilder( + column: $table.updatedAtUtc, builder: (column) => ColumnFilters(column)); +} + +class $$ProjectsTableOrderingComposer + extends Composer<_$SchedulerDb, $ProjectsTable> { + $$ProjectsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get ownerId => $composableBuilder( + column: $table.ownerId, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get colorKey => $composableBuilder( + column: $table.colorKey, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get defaultPriority => $composableBuilder( + column: $table.defaultPriority, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get defaultReward => $composableBuilder( + column: $table.defaultReward, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get defaultDifficulty => $composableBuilder( + column: $table.defaultDifficulty, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get defaultReminderProfile => $composableBuilder( + column: $table.defaultReminderProfile, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get defaultDurationMinutes => $composableBuilder( + column: $table.defaultDurationMinutes, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get archivedAtUtc => $composableBuilder( + column: $table.archivedAtUtc, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get revision => $composableBuilder( + column: $table.revision, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get createdAtUtc => $composableBuilder( + column: $table.createdAtUtc, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get updatedAtUtc => $composableBuilder( + column: $table.updatedAtUtc, + builder: (column) => ColumnOrderings(column)); +} + +class $$ProjectsTableAnnotationComposer + extends Composer<_$SchedulerDb, $ProjectsTable> { + $$ProjectsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get ownerId => + $composableBuilder(column: $table.ownerId, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + GeneratedColumn get colorKey => + $composableBuilder(column: $table.colorKey, builder: (column) => column); + + GeneratedColumn get defaultPriority => $composableBuilder( + column: $table.defaultPriority, builder: (column) => column); + + GeneratedColumn get defaultReward => $composableBuilder( + column: $table.defaultReward, builder: (column) => column); + + GeneratedColumn get defaultDifficulty => $composableBuilder( + column: $table.defaultDifficulty, builder: (column) => column); + + GeneratedColumn get defaultReminderProfile => $composableBuilder( + column: $table.defaultReminderProfile, builder: (column) => column); + + GeneratedColumn get defaultDurationMinutes => $composableBuilder( + column: $table.defaultDurationMinutes, builder: (column) => column); + + GeneratedColumn get archivedAtUtc => $composableBuilder( + column: $table.archivedAtUtc, builder: (column) => column); + + GeneratedColumn get revision => + $composableBuilder(column: $table.revision, builder: (column) => column); + + GeneratedColumn get createdAtUtc => $composableBuilder( + column: $table.createdAtUtc, builder: (column) => column); + + GeneratedColumn get updatedAtUtc => $composableBuilder( + column: $table.updatedAtUtc, builder: (column) => column); +} + +class $$ProjectsTableTableManager extends RootTableManager< + _$SchedulerDb, + $ProjectsTable, + ProjectRow, + $$ProjectsTableFilterComposer, + $$ProjectsTableOrderingComposer, + $$ProjectsTableAnnotationComposer, + $$ProjectsTableCreateCompanionBuilder, + $$ProjectsTableUpdateCompanionBuilder, + (ProjectRow, BaseReferences<_$SchedulerDb, $ProjectsTable, ProjectRow>), + ProjectRow, + PrefetchHooks Function()> { + $$ProjectsTableTableManager(_$SchedulerDb db, $ProjectsTable table) + : super(TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$ProjectsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$ProjectsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$ProjectsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + Value id = const Value.absent(), + Value ownerId = const Value.absent(), + Value name = const Value.absent(), + Value colorKey = const Value.absent(), + Value defaultPriority = const Value.absent(), + Value defaultReward = const Value.absent(), + Value defaultDifficulty = const Value.absent(), + Value defaultReminderProfile = const Value.absent(), + Value defaultDurationMinutes = const Value.absent(), + Value archivedAtUtc = const Value.absent(), + Value revision = const Value.absent(), + Value createdAtUtc = const Value.absent(), + Value updatedAtUtc = const Value.absent(), + Value rowid = const Value.absent(), + }) => + ProjectsCompanion( + id: id, + ownerId: ownerId, + name: name, + colorKey: colorKey, + defaultPriority: defaultPriority, + defaultReward: defaultReward, + defaultDifficulty: defaultDifficulty, + defaultReminderProfile: defaultReminderProfile, + defaultDurationMinutes: defaultDurationMinutes, + archivedAtUtc: archivedAtUtc, + revision: revision, + createdAtUtc: createdAtUtc, + updatedAtUtc: updatedAtUtc, + rowid: rowid, + ), + createCompanionCallback: ({ + required String id, + required String ownerId, + required String name, + required String colorKey, + required String defaultPriority, + required String defaultReward, + required String defaultDifficulty, + required String defaultReminderProfile, + Value defaultDurationMinutes = const Value.absent(), + Value archivedAtUtc = const Value.absent(), + required int revision, + required DateTime createdAtUtc, + required DateTime updatedAtUtc, + Value rowid = const Value.absent(), + }) => + ProjectsCompanion.insert( + id: id, + ownerId: ownerId, + name: name, + colorKey: colorKey, + defaultPriority: defaultPriority, + defaultReward: defaultReward, + defaultDifficulty: defaultDifficulty, + defaultReminderProfile: defaultReminderProfile, + defaultDurationMinutes: defaultDurationMinutes, + archivedAtUtc: archivedAtUtc, + revision: revision, + createdAtUtc: createdAtUtc, + updatedAtUtc: updatedAtUtc, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$ProjectsTableProcessedTableManager = ProcessedTableManager< + _$SchedulerDb, + $ProjectsTable, + ProjectRow, + $$ProjectsTableFilterComposer, + $$ProjectsTableOrderingComposer, + $$ProjectsTableAnnotationComposer, + $$ProjectsTableCreateCompanionBuilder, + $$ProjectsTableUpdateCompanionBuilder, + (ProjectRow, BaseReferences<_$SchedulerDb, $ProjectsTable, ProjectRow>), + ProjectRow, + PrefetchHooks Function()>; +typedef $$LockedBlocksTableCreateCompanionBuilder = LockedBlocksCompanion + Function({ + required String id, + required String ownerId, + required String name, + Value date, + required String startTime, + required String endTime, + Value recurrenceJson, + required bool hiddenByDefault, + Value projectId, + Value archivedAtUtc, + required int revision, + required DateTime createdAtUtc, + required DateTime updatedAtUtc, + Value rowid, +}); +typedef $$LockedBlocksTableUpdateCompanionBuilder = LockedBlocksCompanion + Function({ + Value id, + Value ownerId, + Value name, + Value date, + Value startTime, + Value endTime, + Value recurrenceJson, + Value hiddenByDefault, + Value projectId, + Value archivedAtUtc, + Value revision, + Value createdAtUtc, + Value updatedAtUtc, + Value rowid, +}); + +class $$LockedBlocksTableFilterComposer + extends Composer<_$SchedulerDb, $LockedBlocksTable> { + $$LockedBlocksTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnFilters(column)); + + ColumnFilters get ownerId => $composableBuilder( + column: $table.ownerId, builder: (column) => ColumnFilters(column)); + + ColumnFilters get name => $composableBuilder( + column: $table.name, builder: (column) => ColumnFilters(column)); + + ColumnFilters get date => $composableBuilder( + column: $table.date, builder: (column) => ColumnFilters(column)); + + ColumnFilters get startTime => $composableBuilder( + column: $table.startTime, builder: (column) => ColumnFilters(column)); + + ColumnFilters get endTime => $composableBuilder( + column: $table.endTime, builder: (column) => ColumnFilters(column)); + + ColumnFilters get recurrenceJson => $composableBuilder( + column: $table.recurrenceJson, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get hiddenByDefault => $composableBuilder( + column: $table.hiddenByDefault, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get projectId => $composableBuilder( + column: $table.projectId, builder: (column) => ColumnFilters(column)); + + ColumnFilters get archivedAtUtc => $composableBuilder( + column: $table.archivedAtUtc, builder: (column) => ColumnFilters(column)); + + ColumnFilters get revision => $composableBuilder( + column: $table.revision, builder: (column) => ColumnFilters(column)); + + ColumnFilters get createdAtUtc => $composableBuilder( + column: $table.createdAtUtc, builder: (column) => ColumnFilters(column)); + + ColumnFilters get updatedAtUtc => $composableBuilder( + column: $table.updatedAtUtc, builder: (column) => ColumnFilters(column)); +} + +class $$LockedBlocksTableOrderingComposer + extends Composer<_$SchedulerDb, $LockedBlocksTable> { + $$LockedBlocksTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get ownerId => $composableBuilder( + column: $table.ownerId, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get date => $composableBuilder( + column: $table.date, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get startTime => $composableBuilder( + column: $table.startTime, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get endTime => $composableBuilder( + column: $table.endTime, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get recurrenceJson => $composableBuilder( + column: $table.recurrenceJson, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get hiddenByDefault => $composableBuilder( + column: $table.hiddenByDefault, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get projectId => $composableBuilder( + column: $table.projectId, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get archivedAtUtc => $composableBuilder( + column: $table.archivedAtUtc, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get revision => $composableBuilder( + column: $table.revision, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get createdAtUtc => $composableBuilder( + column: $table.createdAtUtc, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get updatedAtUtc => $composableBuilder( + column: $table.updatedAtUtc, + builder: (column) => ColumnOrderings(column)); +} + +class $$LockedBlocksTableAnnotationComposer + extends Composer<_$SchedulerDb, $LockedBlocksTable> { + $$LockedBlocksTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get ownerId => + $composableBuilder(column: $table.ownerId, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + GeneratedColumn get date => + $composableBuilder(column: $table.date, builder: (column) => column); + + GeneratedColumn get startTime => + $composableBuilder(column: $table.startTime, builder: (column) => column); + + GeneratedColumn get endTime => + $composableBuilder(column: $table.endTime, builder: (column) => column); + + GeneratedColumn get recurrenceJson => $composableBuilder( + column: $table.recurrenceJson, builder: (column) => column); + + GeneratedColumn get hiddenByDefault => $composableBuilder( + column: $table.hiddenByDefault, builder: (column) => column); + + GeneratedColumn get projectId => + $composableBuilder(column: $table.projectId, builder: (column) => column); + + GeneratedColumn get archivedAtUtc => $composableBuilder( + column: $table.archivedAtUtc, builder: (column) => column); + + GeneratedColumn get revision => + $composableBuilder(column: $table.revision, builder: (column) => column); + + GeneratedColumn get createdAtUtc => $composableBuilder( + column: $table.createdAtUtc, builder: (column) => column); + + GeneratedColumn get updatedAtUtc => $composableBuilder( + column: $table.updatedAtUtc, builder: (column) => column); +} + +class $$LockedBlocksTableTableManager extends RootTableManager< + _$SchedulerDb, + $LockedBlocksTable, + LockedBlockRow, + $$LockedBlocksTableFilterComposer, + $$LockedBlocksTableOrderingComposer, + $$LockedBlocksTableAnnotationComposer, + $$LockedBlocksTableCreateCompanionBuilder, + $$LockedBlocksTableUpdateCompanionBuilder, + ( + LockedBlockRow, + BaseReferences<_$SchedulerDb, $LockedBlocksTable, LockedBlockRow> + ), + LockedBlockRow, + PrefetchHooks Function()> { + $$LockedBlocksTableTableManager(_$SchedulerDb db, $LockedBlocksTable table) + : super(TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$LockedBlocksTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$LockedBlocksTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$LockedBlocksTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + Value id = const Value.absent(), + Value ownerId = const Value.absent(), + Value name = const Value.absent(), + Value date = const Value.absent(), + Value startTime = const Value.absent(), + Value endTime = const Value.absent(), + Value recurrenceJson = const Value.absent(), + Value hiddenByDefault = const Value.absent(), + Value projectId = const Value.absent(), + Value archivedAtUtc = const Value.absent(), + Value revision = const Value.absent(), + Value createdAtUtc = const Value.absent(), + Value updatedAtUtc = const Value.absent(), + Value rowid = const Value.absent(), + }) => + LockedBlocksCompanion( + id: id, + ownerId: ownerId, + name: name, + date: date, + startTime: startTime, + endTime: endTime, + recurrenceJson: recurrenceJson, + hiddenByDefault: hiddenByDefault, + projectId: projectId, + archivedAtUtc: archivedAtUtc, + revision: revision, + createdAtUtc: createdAtUtc, + updatedAtUtc: updatedAtUtc, + rowid: rowid, + ), + createCompanionCallback: ({ + required String id, + required String ownerId, + required String name, + Value date = const Value.absent(), + required String startTime, + required String endTime, + Value recurrenceJson = const Value.absent(), + required bool hiddenByDefault, + Value projectId = const Value.absent(), + Value archivedAtUtc = const Value.absent(), + required int revision, + required DateTime createdAtUtc, + required DateTime updatedAtUtc, + Value rowid = const Value.absent(), + }) => + LockedBlocksCompanion.insert( + id: id, + ownerId: ownerId, + name: name, + date: date, + startTime: startTime, + endTime: endTime, + recurrenceJson: recurrenceJson, + hiddenByDefault: hiddenByDefault, + projectId: projectId, + archivedAtUtc: archivedAtUtc, + revision: revision, + createdAtUtc: createdAtUtc, + updatedAtUtc: updatedAtUtc, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$LockedBlocksTableProcessedTableManager = ProcessedTableManager< + _$SchedulerDb, + $LockedBlocksTable, + LockedBlockRow, + $$LockedBlocksTableFilterComposer, + $$LockedBlocksTableOrderingComposer, + $$LockedBlocksTableAnnotationComposer, + $$LockedBlocksTableCreateCompanionBuilder, + $$LockedBlocksTableUpdateCompanionBuilder, + ( + LockedBlockRow, + BaseReferences<_$SchedulerDb, $LockedBlocksTable, LockedBlockRow> + ), + LockedBlockRow, + PrefetchHooks Function()>; +typedef $$LockedOverridesTableCreateCompanionBuilder = LockedOverridesCompanion + Function({ + required String id, + required String ownerId, + Value lockedBlockId, + required String date, + required String type, + Value name, + Value startTime, + Value endTime, + required bool hiddenByDefault, + Value projectId, + required int revision, + required DateTime createdAtUtc, + required DateTime updatedAtUtc, + Value rowid, +}); +typedef $$LockedOverridesTableUpdateCompanionBuilder = LockedOverridesCompanion + Function({ + Value id, + Value ownerId, + Value lockedBlockId, + Value date, + Value type, + Value name, + Value startTime, + Value endTime, + Value hiddenByDefault, + Value projectId, + Value revision, + Value createdAtUtc, + Value updatedAtUtc, + Value rowid, +}); + +class $$LockedOverridesTableFilterComposer + extends Composer<_$SchedulerDb, $LockedOverridesTable> { + $$LockedOverridesTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnFilters(column)); + + ColumnFilters get ownerId => $composableBuilder( + column: $table.ownerId, builder: (column) => ColumnFilters(column)); + + ColumnFilters get lockedBlockId => $composableBuilder( + column: $table.lockedBlockId, builder: (column) => ColumnFilters(column)); + + ColumnFilters get date => $composableBuilder( + column: $table.date, builder: (column) => ColumnFilters(column)); + + ColumnFilters get type => $composableBuilder( + column: $table.type, builder: (column) => ColumnFilters(column)); + + ColumnFilters get name => $composableBuilder( + column: $table.name, builder: (column) => ColumnFilters(column)); + + ColumnFilters get startTime => $composableBuilder( + column: $table.startTime, builder: (column) => ColumnFilters(column)); + + ColumnFilters get endTime => $composableBuilder( + column: $table.endTime, builder: (column) => ColumnFilters(column)); + + ColumnFilters get hiddenByDefault => $composableBuilder( + column: $table.hiddenByDefault, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get projectId => $composableBuilder( + column: $table.projectId, builder: (column) => ColumnFilters(column)); + + ColumnFilters get revision => $composableBuilder( + column: $table.revision, builder: (column) => ColumnFilters(column)); + + ColumnFilters get createdAtUtc => $composableBuilder( + column: $table.createdAtUtc, builder: (column) => ColumnFilters(column)); + + ColumnFilters get updatedAtUtc => $composableBuilder( + column: $table.updatedAtUtc, builder: (column) => ColumnFilters(column)); +} + +class $$LockedOverridesTableOrderingComposer + extends Composer<_$SchedulerDb, $LockedOverridesTable> { + $$LockedOverridesTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get ownerId => $composableBuilder( + column: $table.ownerId, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get lockedBlockId => $composableBuilder( + column: $table.lockedBlockId, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get date => $composableBuilder( + column: $table.date, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get type => $composableBuilder( + column: $table.type, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get startTime => $composableBuilder( + column: $table.startTime, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get endTime => $composableBuilder( + column: $table.endTime, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get hiddenByDefault => $composableBuilder( + column: $table.hiddenByDefault, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get projectId => $composableBuilder( + column: $table.projectId, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get revision => $composableBuilder( + column: $table.revision, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get createdAtUtc => $composableBuilder( + column: $table.createdAtUtc, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get updatedAtUtc => $composableBuilder( + column: $table.updatedAtUtc, + builder: (column) => ColumnOrderings(column)); +} + +class $$LockedOverridesTableAnnotationComposer + extends Composer<_$SchedulerDb, $LockedOverridesTable> { + $$LockedOverridesTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get ownerId => + $composableBuilder(column: $table.ownerId, builder: (column) => column); + + GeneratedColumn get lockedBlockId => $composableBuilder( + column: $table.lockedBlockId, builder: (column) => column); + + GeneratedColumn get date => + $composableBuilder(column: $table.date, builder: (column) => column); + + GeneratedColumn get type => + $composableBuilder(column: $table.type, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + GeneratedColumn get startTime => + $composableBuilder(column: $table.startTime, builder: (column) => column); + + GeneratedColumn get endTime => + $composableBuilder(column: $table.endTime, builder: (column) => column); + + GeneratedColumn get hiddenByDefault => $composableBuilder( + column: $table.hiddenByDefault, builder: (column) => column); + + GeneratedColumn get projectId => + $composableBuilder(column: $table.projectId, builder: (column) => column); + + GeneratedColumn get revision => + $composableBuilder(column: $table.revision, builder: (column) => column); + + GeneratedColumn get createdAtUtc => $composableBuilder( + column: $table.createdAtUtc, builder: (column) => column); + + GeneratedColumn get updatedAtUtc => $composableBuilder( + column: $table.updatedAtUtc, builder: (column) => column); +} + +class $$LockedOverridesTableTableManager extends RootTableManager< + _$SchedulerDb, + $LockedOverridesTable, + LockedOverrideRow, + $$LockedOverridesTableFilterComposer, + $$LockedOverridesTableOrderingComposer, + $$LockedOverridesTableAnnotationComposer, + $$LockedOverridesTableCreateCompanionBuilder, + $$LockedOverridesTableUpdateCompanionBuilder, + ( + LockedOverrideRow, + BaseReferences<_$SchedulerDb, $LockedOverridesTable, LockedOverrideRow> + ), + LockedOverrideRow, + PrefetchHooks Function()> { + $$LockedOverridesTableTableManager( + _$SchedulerDb db, $LockedOverridesTable table) + : super(TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$LockedOverridesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$LockedOverridesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$LockedOverridesTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + Value id = const Value.absent(), + Value ownerId = const Value.absent(), + Value lockedBlockId = const Value.absent(), + Value date = const Value.absent(), + Value type = const Value.absent(), + Value name = const Value.absent(), + Value startTime = const Value.absent(), + Value endTime = const Value.absent(), + Value hiddenByDefault = const Value.absent(), + Value projectId = const Value.absent(), + Value revision = const Value.absent(), + Value createdAtUtc = const Value.absent(), + Value updatedAtUtc = const Value.absent(), + Value rowid = const Value.absent(), + }) => + LockedOverridesCompanion( + id: id, + ownerId: ownerId, + lockedBlockId: lockedBlockId, + date: date, + type: type, + name: name, + startTime: startTime, + endTime: endTime, + hiddenByDefault: hiddenByDefault, + projectId: projectId, + revision: revision, + createdAtUtc: createdAtUtc, + updatedAtUtc: updatedAtUtc, + rowid: rowid, + ), + createCompanionCallback: ({ + required String id, + required String ownerId, + Value lockedBlockId = const Value.absent(), + required String date, + required String type, + Value name = const Value.absent(), + Value startTime = const Value.absent(), + Value endTime = const Value.absent(), + required bool hiddenByDefault, + Value projectId = const Value.absent(), + required int revision, + required DateTime createdAtUtc, + required DateTime updatedAtUtc, + Value rowid = const Value.absent(), + }) => + LockedOverridesCompanion.insert( + id: id, + ownerId: ownerId, + lockedBlockId: lockedBlockId, + date: date, + type: type, + name: name, + startTime: startTime, + endTime: endTime, + hiddenByDefault: hiddenByDefault, + projectId: projectId, + revision: revision, + createdAtUtc: createdAtUtc, + updatedAtUtc: updatedAtUtc, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$LockedOverridesTableProcessedTableManager = ProcessedTableManager< + _$SchedulerDb, + $LockedOverridesTable, + LockedOverrideRow, + $$LockedOverridesTableFilterComposer, + $$LockedOverridesTableOrderingComposer, + $$LockedOverridesTableAnnotationComposer, + $$LockedOverridesTableCreateCompanionBuilder, + $$LockedOverridesTableUpdateCompanionBuilder, + ( + LockedOverrideRow, + BaseReferences<_$SchedulerDb, $LockedOverridesTable, LockedOverrideRow> + ), + LockedOverrideRow, + PrefetchHooks Function()>; +typedef $$SettingsTableTableCreateCompanionBuilder = SettingsTableCompanion + Function({ + required String ownerId, + required String timeZoneId, + required int dayStartMinutes, + required int dayEndMinutes, + required bool compactMode, + required String backlogStalenessJson, + required int revision, + required DateTime createdAtUtc, + required DateTime updatedAtUtc, + Value rowid, +}); +typedef $$SettingsTableTableUpdateCompanionBuilder = SettingsTableCompanion + Function({ + Value ownerId, + Value timeZoneId, + Value dayStartMinutes, + Value dayEndMinutes, + Value compactMode, + Value backlogStalenessJson, + Value revision, + Value createdAtUtc, + Value updatedAtUtc, + Value rowid, +}); + +class $$SettingsTableTableFilterComposer + extends Composer<_$SchedulerDb, $SettingsTableTable> { + $$SettingsTableTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get ownerId => $composableBuilder( + column: $table.ownerId, builder: (column) => ColumnFilters(column)); + + ColumnFilters get timeZoneId => $composableBuilder( + column: $table.timeZoneId, builder: (column) => ColumnFilters(column)); + + ColumnFilters get dayStartMinutes => $composableBuilder( + column: $table.dayStartMinutes, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get dayEndMinutes => $composableBuilder( + column: $table.dayEndMinutes, builder: (column) => ColumnFilters(column)); + + ColumnFilters get compactMode => $composableBuilder( + column: $table.compactMode, builder: (column) => ColumnFilters(column)); + + ColumnFilters get backlogStalenessJson => $composableBuilder( + column: $table.backlogStalenessJson, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get revision => $composableBuilder( + column: $table.revision, builder: (column) => ColumnFilters(column)); + + ColumnFilters get createdAtUtc => $composableBuilder( + column: $table.createdAtUtc, builder: (column) => ColumnFilters(column)); + + ColumnFilters get updatedAtUtc => $composableBuilder( + column: $table.updatedAtUtc, builder: (column) => ColumnFilters(column)); +} + +class $$SettingsTableTableOrderingComposer + extends Composer<_$SchedulerDb, $SettingsTableTable> { + $$SettingsTableTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get ownerId => $composableBuilder( + column: $table.ownerId, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get timeZoneId => $composableBuilder( + column: $table.timeZoneId, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get dayStartMinutes => $composableBuilder( + column: $table.dayStartMinutes, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get dayEndMinutes => $composableBuilder( + column: $table.dayEndMinutes, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get compactMode => $composableBuilder( + column: $table.compactMode, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get backlogStalenessJson => $composableBuilder( + column: $table.backlogStalenessJson, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get revision => $composableBuilder( + column: $table.revision, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get createdAtUtc => $composableBuilder( + column: $table.createdAtUtc, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get updatedAtUtc => $composableBuilder( + column: $table.updatedAtUtc, + builder: (column) => ColumnOrderings(column)); +} + +class $$SettingsTableTableAnnotationComposer + extends Composer<_$SchedulerDb, $SettingsTableTable> { + $$SettingsTableTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get ownerId => + $composableBuilder(column: $table.ownerId, builder: (column) => column); + + GeneratedColumn get timeZoneId => $composableBuilder( + column: $table.timeZoneId, builder: (column) => column); + + GeneratedColumn get dayStartMinutes => $composableBuilder( + column: $table.dayStartMinutes, builder: (column) => column); + + GeneratedColumn get dayEndMinutes => $composableBuilder( + column: $table.dayEndMinutes, builder: (column) => column); + + GeneratedColumn get compactMode => $composableBuilder( + column: $table.compactMode, builder: (column) => column); + + GeneratedColumn get backlogStalenessJson => $composableBuilder( + column: $table.backlogStalenessJson, builder: (column) => column); + + GeneratedColumn get revision => + $composableBuilder(column: $table.revision, builder: (column) => column); + + GeneratedColumn get createdAtUtc => $composableBuilder( + column: $table.createdAtUtc, builder: (column) => column); + + GeneratedColumn get updatedAtUtc => $composableBuilder( + column: $table.updatedAtUtc, builder: (column) => column); +} + +class $$SettingsTableTableTableManager extends RootTableManager< + _$SchedulerDb, + $SettingsTableTable, + SettingsRow, + $$SettingsTableTableFilterComposer, + $$SettingsTableTableOrderingComposer, + $$SettingsTableTableAnnotationComposer, + $$SettingsTableTableCreateCompanionBuilder, + $$SettingsTableTableUpdateCompanionBuilder, + ( + SettingsRow, + BaseReferences<_$SchedulerDb, $SettingsTableTable, SettingsRow> + ), + SettingsRow, + PrefetchHooks Function()> { + $$SettingsTableTableTableManager(_$SchedulerDb db, $SettingsTableTable table) + : super(TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$SettingsTableTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$SettingsTableTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$SettingsTableTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + Value ownerId = const Value.absent(), + Value timeZoneId = const Value.absent(), + Value dayStartMinutes = const Value.absent(), + Value dayEndMinutes = const Value.absent(), + Value compactMode = const Value.absent(), + Value backlogStalenessJson = const Value.absent(), + Value revision = const Value.absent(), + Value createdAtUtc = const Value.absent(), + Value updatedAtUtc = const Value.absent(), + Value rowid = const Value.absent(), + }) => + SettingsTableCompanion( + ownerId: ownerId, + timeZoneId: timeZoneId, + dayStartMinutes: dayStartMinutes, + dayEndMinutes: dayEndMinutes, + compactMode: compactMode, + backlogStalenessJson: backlogStalenessJson, + revision: revision, + createdAtUtc: createdAtUtc, + updatedAtUtc: updatedAtUtc, + rowid: rowid, + ), + createCompanionCallback: ({ + required String ownerId, + required String timeZoneId, + required int dayStartMinutes, + required int dayEndMinutes, + required bool compactMode, + required String backlogStalenessJson, + required int revision, + required DateTime createdAtUtc, + required DateTime updatedAtUtc, + Value rowid = const Value.absent(), + }) => + SettingsTableCompanion.insert( + ownerId: ownerId, + timeZoneId: timeZoneId, + dayStartMinutes: dayStartMinutes, + dayEndMinutes: dayEndMinutes, + compactMode: compactMode, + backlogStalenessJson: backlogStalenessJson, + revision: revision, + createdAtUtc: createdAtUtc, + updatedAtUtc: updatedAtUtc, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$SettingsTableTableProcessedTableManager = ProcessedTableManager< + _$SchedulerDb, + $SettingsTableTable, + SettingsRow, + $$SettingsTableTableFilterComposer, + $$SettingsTableTableOrderingComposer, + $$SettingsTableTableAnnotationComposer, + $$SettingsTableTableCreateCompanionBuilder, + $$SettingsTableTableUpdateCompanionBuilder, + ( + SettingsRow, + BaseReferences<_$SchedulerDb, $SettingsTableTable, SettingsRow> + ), + SettingsRow, + PrefetchHooks Function()>; +typedef $$SnapshotsTableCreateCompanionBuilder = SnapshotsCompanion Function({ + required String id, + required String ownerId, + required DateTime capturedAtUtc, + Value operationName, + Value sourceDate, + Value targetDate, + required String windowJson, + required String tasksJson, + required String lockedIntervalsJson, + required String requiredVisibleIntervalsJson, + required String noticesJson, + required String changesJson, + required String overlapsJson, + required DateTime retentionExpiresUtc, + required bool truncated, + required int revision, + required DateTime createdAtUtc, + required DateTime updatedAtUtc, + Value rowid, +}); +typedef $$SnapshotsTableUpdateCompanionBuilder = SnapshotsCompanion Function({ + Value id, + Value ownerId, + Value capturedAtUtc, + Value operationName, + Value sourceDate, + Value targetDate, + Value windowJson, + Value tasksJson, + Value lockedIntervalsJson, + Value requiredVisibleIntervalsJson, + Value noticesJson, + Value changesJson, + Value overlapsJson, + Value retentionExpiresUtc, + Value truncated, + Value revision, + Value createdAtUtc, + Value updatedAtUtc, + Value rowid, +}); + +class $$SnapshotsTableFilterComposer + extends Composer<_$SchedulerDb, $SnapshotsTable> { + $$SnapshotsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnFilters(column)); + + ColumnFilters get ownerId => $composableBuilder( + column: $table.ownerId, builder: (column) => ColumnFilters(column)); + + ColumnFilters get capturedAtUtc => $composableBuilder( + column: $table.capturedAtUtc, builder: (column) => ColumnFilters(column)); + + ColumnFilters get operationName => $composableBuilder( + column: $table.operationName, builder: (column) => ColumnFilters(column)); + + ColumnFilters get sourceDate => $composableBuilder( + column: $table.sourceDate, builder: (column) => ColumnFilters(column)); + + ColumnFilters get targetDate => $composableBuilder( + column: $table.targetDate, builder: (column) => ColumnFilters(column)); + + ColumnFilters get windowJson => $composableBuilder( + column: $table.windowJson, builder: (column) => ColumnFilters(column)); + + ColumnFilters get tasksJson => $composableBuilder( + column: $table.tasksJson, builder: (column) => ColumnFilters(column)); + + ColumnFilters get lockedIntervalsJson => $composableBuilder( + column: $table.lockedIntervalsJson, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get requiredVisibleIntervalsJson => $composableBuilder( + column: $table.requiredVisibleIntervalsJson, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get noticesJson => $composableBuilder( + column: $table.noticesJson, builder: (column) => ColumnFilters(column)); + + ColumnFilters get changesJson => $composableBuilder( + column: $table.changesJson, builder: (column) => ColumnFilters(column)); + + ColumnFilters get overlapsJson => $composableBuilder( + column: $table.overlapsJson, builder: (column) => ColumnFilters(column)); + + ColumnFilters get retentionExpiresUtc => $composableBuilder( + column: $table.retentionExpiresUtc, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get truncated => $composableBuilder( + column: $table.truncated, builder: (column) => ColumnFilters(column)); + + ColumnFilters get revision => $composableBuilder( + column: $table.revision, builder: (column) => ColumnFilters(column)); + + ColumnFilters get createdAtUtc => $composableBuilder( + column: $table.createdAtUtc, builder: (column) => ColumnFilters(column)); + + ColumnFilters get updatedAtUtc => $composableBuilder( + column: $table.updatedAtUtc, builder: (column) => ColumnFilters(column)); +} + +class $$SnapshotsTableOrderingComposer + extends Composer<_$SchedulerDb, $SnapshotsTable> { + $$SnapshotsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get ownerId => $composableBuilder( + column: $table.ownerId, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get capturedAtUtc => $composableBuilder( + column: $table.capturedAtUtc, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get operationName => $composableBuilder( + column: $table.operationName, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get sourceDate => $composableBuilder( + column: $table.sourceDate, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get targetDate => $composableBuilder( + column: $table.targetDate, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get windowJson => $composableBuilder( + column: $table.windowJson, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get tasksJson => $composableBuilder( + column: $table.tasksJson, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get lockedIntervalsJson => $composableBuilder( + column: $table.lockedIntervalsJson, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get requiredVisibleIntervalsJson => + $composableBuilder( + column: $table.requiredVisibleIntervalsJson, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get noticesJson => $composableBuilder( + column: $table.noticesJson, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get changesJson => $composableBuilder( + column: $table.changesJson, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get overlapsJson => $composableBuilder( + column: $table.overlapsJson, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get retentionExpiresUtc => $composableBuilder( + column: $table.retentionExpiresUtc, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get truncated => $composableBuilder( + column: $table.truncated, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get revision => $composableBuilder( + column: $table.revision, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get createdAtUtc => $composableBuilder( + column: $table.createdAtUtc, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get updatedAtUtc => $composableBuilder( + column: $table.updatedAtUtc, + builder: (column) => ColumnOrderings(column)); +} + +class $$SnapshotsTableAnnotationComposer + extends Composer<_$SchedulerDb, $SnapshotsTable> { + $$SnapshotsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get ownerId => + $composableBuilder(column: $table.ownerId, builder: (column) => column); + + GeneratedColumn get capturedAtUtc => $composableBuilder( + column: $table.capturedAtUtc, builder: (column) => column); + + GeneratedColumn get operationName => $composableBuilder( + column: $table.operationName, builder: (column) => column); + + GeneratedColumn get sourceDate => $composableBuilder( + column: $table.sourceDate, builder: (column) => column); + + GeneratedColumn get targetDate => $composableBuilder( + column: $table.targetDate, builder: (column) => column); + + GeneratedColumn get windowJson => $composableBuilder( + column: $table.windowJson, builder: (column) => column); + + GeneratedColumn get tasksJson => + $composableBuilder(column: $table.tasksJson, builder: (column) => column); + + GeneratedColumn get lockedIntervalsJson => $composableBuilder( + column: $table.lockedIntervalsJson, builder: (column) => column); + + GeneratedColumn get requiredVisibleIntervalsJson => + $composableBuilder( + column: $table.requiredVisibleIntervalsJson, + builder: (column) => column); + + GeneratedColumn get noticesJson => $composableBuilder( + column: $table.noticesJson, builder: (column) => column); + + GeneratedColumn get changesJson => $composableBuilder( + column: $table.changesJson, builder: (column) => column); + + GeneratedColumn get overlapsJson => $composableBuilder( + column: $table.overlapsJson, builder: (column) => column); + + GeneratedColumn get retentionExpiresUtc => $composableBuilder( + column: $table.retentionExpiresUtc, builder: (column) => column); + + GeneratedColumn get truncated => + $composableBuilder(column: $table.truncated, builder: (column) => column); + + GeneratedColumn get revision => + $composableBuilder(column: $table.revision, builder: (column) => column); + + GeneratedColumn get createdAtUtc => $composableBuilder( + column: $table.createdAtUtc, builder: (column) => column); + + GeneratedColumn get updatedAtUtc => $composableBuilder( + column: $table.updatedAtUtc, builder: (column) => column); +} + +class $$SnapshotsTableTableManager extends RootTableManager< + _$SchedulerDb, + $SnapshotsTable, + SnapshotRow, + $$SnapshotsTableFilterComposer, + $$SnapshotsTableOrderingComposer, + $$SnapshotsTableAnnotationComposer, + $$SnapshotsTableCreateCompanionBuilder, + $$SnapshotsTableUpdateCompanionBuilder, + (SnapshotRow, BaseReferences<_$SchedulerDb, $SnapshotsTable, SnapshotRow>), + SnapshotRow, + PrefetchHooks Function()> { + $$SnapshotsTableTableManager(_$SchedulerDb db, $SnapshotsTable table) + : super(TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$SnapshotsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$SnapshotsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$SnapshotsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + Value id = const Value.absent(), + Value ownerId = const Value.absent(), + Value capturedAtUtc = const Value.absent(), + Value operationName = const Value.absent(), + Value sourceDate = const Value.absent(), + Value targetDate = const Value.absent(), + Value windowJson = const Value.absent(), + Value tasksJson = const Value.absent(), + Value lockedIntervalsJson = const Value.absent(), + Value requiredVisibleIntervalsJson = const Value.absent(), + Value noticesJson = const Value.absent(), + Value changesJson = const Value.absent(), + Value overlapsJson = const Value.absent(), + Value retentionExpiresUtc = const Value.absent(), + Value truncated = const Value.absent(), + Value revision = const Value.absent(), + Value createdAtUtc = const Value.absent(), + Value updatedAtUtc = const Value.absent(), + Value rowid = const Value.absent(), + }) => + SnapshotsCompanion( + id: id, + ownerId: ownerId, + capturedAtUtc: capturedAtUtc, + operationName: operationName, + sourceDate: sourceDate, + targetDate: targetDate, + windowJson: windowJson, + tasksJson: tasksJson, + lockedIntervalsJson: lockedIntervalsJson, + requiredVisibleIntervalsJson: requiredVisibleIntervalsJson, + noticesJson: noticesJson, + changesJson: changesJson, + overlapsJson: overlapsJson, + retentionExpiresUtc: retentionExpiresUtc, + truncated: truncated, + revision: revision, + createdAtUtc: createdAtUtc, + updatedAtUtc: updatedAtUtc, + rowid: rowid, + ), + createCompanionCallback: ({ + required String id, + required String ownerId, + required DateTime capturedAtUtc, + Value operationName = const Value.absent(), + Value sourceDate = const Value.absent(), + Value targetDate = const Value.absent(), + required String windowJson, + required String tasksJson, + required String lockedIntervalsJson, + required String requiredVisibleIntervalsJson, + required String noticesJson, + required String changesJson, + required String overlapsJson, + required DateTime retentionExpiresUtc, + required bool truncated, + required int revision, + required DateTime createdAtUtc, + required DateTime updatedAtUtc, + Value rowid = const Value.absent(), + }) => + SnapshotsCompanion.insert( + id: id, + ownerId: ownerId, + capturedAtUtc: capturedAtUtc, + operationName: operationName, + sourceDate: sourceDate, + targetDate: targetDate, + windowJson: windowJson, + tasksJson: tasksJson, + lockedIntervalsJson: lockedIntervalsJson, + requiredVisibleIntervalsJson: requiredVisibleIntervalsJson, + noticesJson: noticesJson, + changesJson: changesJson, + overlapsJson: overlapsJson, + retentionExpiresUtc: retentionExpiresUtc, + truncated: truncated, + revision: revision, + createdAtUtc: createdAtUtc, + updatedAtUtc: updatedAtUtc, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$SnapshotsTableProcessedTableManager = ProcessedTableManager< + _$SchedulerDb, + $SnapshotsTable, + SnapshotRow, + $$SnapshotsTableFilterComposer, + $$SnapshotsTableOrderingComposer, + $$SnapshotsTableAnnotationComposer, + $$SnapshotsTableCreateCompanionBuilder, + $$SnapshotsTableUpdateCompanionBuilder, + (SnapshotRow, BaseReferences<_$SchedulerDb, $SnapshotsTable, SnapshotRow>), + SnapshotRow, + PrefetchHooks Function()>; + +class $SchedulerDbManager { + final _$SchedulerDb _db; + $SchedulerDbManager(this._db); + $$TasksTableTableManager get tasks => + $$TasksTableTableManager(_db, _db.tasks); + $$ProjectsTableTableManager get projects => + $$ProjectsTableTableManager(_db, _db.projects); + $$LockedBlocksTableTableManager get lockedBlocks => + $$LockedBlocksTableTableManager(_db, _db.lockedBlocks); + $$LockedOverridesTableTableManager get lockedOverrides => + $$LockedOverridesTableTableManager(_db, _db.lockedOverrides); + $$SettingsTableTableTableManager get settingsTable => + $$SettingsTableTableTableManager(_db, _db.settingsTable); + $$SnapshotsTableTableManager get snapshots => + $$SnapshotsTableTableManager(_db, _db.snapshots); +} diff --git a/packages/scheduler_persistence_sqlite/lib/src/sqlite_repositories.dart b/packages/scheduler_persistence_sqlite/lib/src/sqlite_repositories.dart new file mode 100644 index 0000000..574f213 --- /dev/null +++ b/packages/scheduler_persistence_sqlite/lib/src/sqlite_repositories.dart @@ -0,0 +1,1374 @@ +/// SQLite repository implementations backed by [SchedulerDb]. +library; + +import 'dart:convert'; + +import 'package:drift/drift.dart' as drift; +import 'package:scheduler_core/scheduler_core.dart' as core; +import 'package:scheduler_persistence/persistence.dart'; + +import 'scheduler_db.dart'; + +const _snapshotRetention = Duration(days: 30); + +final _epoch = DateTime.fromMillisecondsSinceEpoch(0, isUtc: true); + +/// Drift-backed task repository. +final class SqliteTaskRepository implements TaskRepository { + /// Creates a task repository using [db]. + SqliteTaskRepository(this.db); + + /// Shared SQLite database handle. + final SchedulerDb db; + + @override + Future> findById({ + required core.OwnerId ownerId, + required String taskId, + }) async { + final row = await _taskById(taskId); + if (row == null || row.ownerId != ownerId.value) { + return const Right(null); + } + return Right(_taskFromRow(row)); + } + + @override + Future>> findByOwner({ + required core.OwnerId ownerId, + core.PageRequest page = const core.PageRequest(), + }) async { + return Right(await _pageTaskRows( + page: page, + where: (table) => table.ownerId.equals(ownerId.value), + )); + } + + @override + Future>> findByParentTaskId({ + required core.OwnerId ownerId, + required String parentTaskId, + core.PageRequest page = const core.PageRequest(), + }) async { + return Right(await _pageTaskRows( + page: page, + where: (table) => + table.ownerId.equals(ownerId.value) & + table.parentId.equals(parentTaskId), + )); + } + + @override + Future>> findByProjectId({ + required core.OwnerId ownerId, + required String projectId, + core.PageRequest page = const core.PageRequest(), + }) async { + return Right(await _pageTaskRows( + page: page, + where: (table) => + table.ownerId.equals(ownerId.value) & + table.projectId.equals(projectId), + )); + } + + @override + Future>> findByStatus({ + required core.OwnerId ownerId, + required core.TaskStatus status, + core.PageRequest page = const core.PageRequest(), + }) async { + return Right(await _pageTaskRows( + page: page, + where: (table) => + table.ownerId.equals(ownerId.value) & + table.status.equals(core.PersistenceEnumMapping.encodeTaskStatus( + status, + )), + )); + } + + @override + Future> insert({ + required core.OwnerId ownerId, + required core.Task task, + }) async { + if (await _taskById(task.id) != null) { + return Left(RepositoryDuplicateId(entityId: task.id)); + } + await db.into(db.tasks).insert(_taskCompanion( + task, + ownerId: ownerId, + revision: core.Revision.initial, + )); + return const Right(core.Revision.initial); + } + + @override + Future> save({ + required core.OwnerId ownerId, + required core.Task task, + required core.Revision expectedRevision, + }) async { + final checked = await _checkTaskWrite( + task.id, + ownerId, + expectedRevision, + ); + if (checked is Left) { + return Left(checked.value); + } + final nextRevision = checked.right.revision.next(); + final updates = _taskCompanion( + task, + ownerId: ownerId, + revision: nextRevision, + ); + final updated = await (db.update(db.tasks) + ..where( + (table) => + table.id.equals(task.id) & + table.ownerId.equals(ownerId.value) & + table.revision.equals(expectedRevision.value), + )) + .write(updates); + if (updated != 1) { + return Left(await _staleTaskFailure(task.id, expectedRevision)); + } + return Right(nextRevision); + } + + @override + Future> delete({ + required core.OwnerId ownerId, + required String taskId, + required core.Revision expectedRevision, + }) async { + final checked = await _checkTaskWrite(taskId, ownerId, expectedRevision); + if (checked is Left) { + return Left(checked.value); + } + final nextRevision = checked.right.revision.next(); + final deleted = await (db.delete(db.tasks) + ..where( + (table) => + table.id.equals(taskId) & + table.ownerId.equals(ownerId.value) & + table.revision.equals(expectedRevision.value), + )) + .go(); + if (deleted != 1) { + return Left(await _staleTaskFailure(taskId, expectedRevision)); + } + return Right(nextRevision); + } + + Future> _pageTaskRows({ + required core.PageRequest page, + required drift.Expression Function($TasksTable table) where, + }) async { + final offset = _cursorOffset(page.cursor); + final query = db.select(db.tasks) + ..where(where) + ..orderBy([(table) => drift.OrderingTerm.asc(table.id)]) + ..limit(page.limit + 1, offset: offset); + final rows = await query.get(); + final pageRows = rows.take(page.limit).toList(growable: false); + return core.Page( + items: pageRows.map(_taskFromRow), + nextCursor: rows.length > page.limit ? '${offset + page.limit}' : null, + ); + } + + Future _taskById(String id) { + return (db.select(db.tasks)..where((table) => table.id.equals(id))) + .getSingleOrNull(); + } + + Future> _checkTaskWrite( + String id, + core.OwnerId ownerId, + core.Revision expectedRevision, + ) async { + if (expectedRevision.value <= 0) { + return Left(RepositoryInvalidRevision( + entityId: id, + expectedRevision: expectedRevision, + )); + } + final row = await _taskById(id); + if (row == null) { + return Left(RepositoryNotFound(entityId: id)); + } + if (row.ownerId != ownerId.value) { + return Left(RepositoryOwnerMismatch(entityId: id)); + } + if (row.revision != expectedRevision.value) { + return Left(RepositoryStaleRevision( + entityId: id, + expectedRevision: expectedRevision, + actualRevision: core.Revision(row.revision), + )); + } + return Right(row); + } + + Future _staleTaskFailure( + String id, + core.Revision expectedRevision, + ) async { + final row = await _taskById(id); + return RepositoryStaleRevision( + entityId: id, + expectedRevision: expectedRevision, + actualRevision: + row == null ? expectedRevision.next() : core.Revision(row.revision), + ); + } +} + +/// Drift-backed project repository. +final class SqliteProjectRepository implements ProjectRepository { + /// Creates a project repository using [db]. + SqliteProjectRepository(this.db); + + /// Shared SQLite database handle. + final SchedulerDb db; + + @override + Future> findById({ + required core.OwnerId ownerId, + required String projectId, + }) async { + final row = await _projectById(projectId); + if (row == null || row.ownerId != ownerId.value) { + return const Right(null); + } + return Right(_projectFromRow(row)); + } + + @override + Future>> findByOwner({ + required core.OwnerId ownerId, + bool includeArchived = false, + core.PageRequest page = const core.PageRequest(), + }) async { + final offset = _cursorOffset(page.cursor); + final query = db.select(db.projects) + ..where((table) { + final ownerClause = table.ownerId.equals(ownerId.value); + return includeArchived + ? ownerClause + : ownerClause & table.archivedAtUtc.isNull(); + }) + ..orderBy([(table) => drift.OrderingTerm.asc(table.id)]) + ..limit(page.limit + 1, offset: offset); + final rows = await query.get(); + final pageRows = rows.take(page.limit).toList(growable: false); + return Right(core.Page( + items: pageRows.map(_projectFromRow), + nextCursor: rows.length > page.limit ? '${offset + page.limit}' : null, + )); + } + + @override + Future> insert({ + required core.OwnerId ownerId, + required core.ProjectProfile project, + }) async { + if (await _projectById(project.id) != null) { + return Left(RepositoryDuplicateId(entityId: project.id)); + } + await db.into(db.projects).insert(_projectCompanion( + project, + ownerId: ownerId, + revision: core.Revision.initial, + createdAt: _epoch, + updatedAt: _epoch, + )); + return const Right(core.Revision.initial); + } + + @override + Future> save({ + required core.OwnerId ownerId, + required core.ProjectProfile project, + required core.Revision expectedRevision, + }) async { + final checked = await _checkProjectWrite( + project.id, + ownerId, + expectedRevision, + ); + if (checked is Left) { + return Left(checked.value); + } + final row = checked.right; + final nextRevision = core.Revision(row.revision).next(); + final updated = await (db.update(db.projects) + ..where( + (table) => + table.id.equals(project.id) & + table.ownerId.equals(ownerId.value) & + table.revision.equals(expectedRevision.value), + )) + .write(_projectCompanion( + project, + ownerId: ownerId, + revision: nextRevision, + createdAt: row.createdAtUtc, + updatedAt: _epoch, + )); + if (updated != 1) { + return Left(await _staleProjectFailure(project.id, expectedRevision)); + } + return Right(nextRevision); + } + + @override + Future> archive({ + required core.OwnerId ownerId, + required String projectId, + required core.Revision expectedRevision, + required DateTime archivedAtUtc, + }) async { + final checked = await _checkProjectWrite( + projectId, + ownerId, + expectedRevision, + ); + if (checked is Left) { + return Left(checked.value); + } + final row = checked.right; + final nextRevision = core.Revision(row.revision).next(); + final archived = _projectFromRow(row).copyWith(archivedAt: archivedAtUtc); + final updated = await (db.update(db.projects) + ..where( + (table) => + table.id.equals(projectId) & + table.ownerId.equals(ownerId.value) & + table.revision.equals(expectedRevision.value), + )) + .write(_projectCompanion( + archived, + ownerId: ownerId, + revision: nextRevision, + createdAt: row.createdAtUtc, + updatedAt: archivedAtUtc, + )); + if (updated != 1) { + return Left(await _staleProjectFailure(projectId, expectedRevision)); + } + return Right(nextRevision); + } + + Future _projectById(String id) { + return (db.select(db.projects)..where((table) => table.id.equals(id))) + .getSingleOrNull(); + } + + Future> _checkProjectWrite( + String id, + core.OwnerId ownerId, + core.Revision expectedRevision, + ) async { + if (expectedRevision.value <= 0) { + return Left(RepositoryInvalidRevision( + entityId: id, + expectedRevision: expectedRevision, + )); + } + final row = await _projectById(id); + if (row == null) { + return Left(RepositoryNotFound(entityId: id)); + } + if (row.ownerId != ownerId.value) { + return Left(RepositoryOwnerMismatch(entityId: id)); + } + if (row.revision != expectedRevision.value) { + return Left(RepositoryStaleRevision( + entityId: id, + expectedRevision: expectedRevision, + actualRevision: core.Revision(row.revision), + )); + } + return Right(row); + } + + Future _staleProjectFailure( + String id, + core.Revision expectedRevision, + ) async { + final row = await _projectById(id); + return RepositoryStaleRevision( + entityId: id, + expectedRevision: expectedRevision, + actualRevision: + row == null ? expectedRevision.next() : core.Revision(row.revision), + ); + } +} + +/// Drift-backed locked-block and override repository. +final class SqliteLockedBlockRepository implements LockedBlockRepository { + /// Creates a locked-time repository using [db]. + SqliteLockedBlockRepository(this.db); + + /// Shared SQLite database handle. + final SchedulerDb db; + + @override + Future> findBlockById({ + required core.OwnerId ownerId, + required String blockId, + }) async { + final row = await _blockById(blockId); + if (row == null || row.ownerId != ownerId.value) { + return const Right(null); + } + return Right(_lockedBlockFromRow(row)); + } + + @override + Future>> findBlocksByOwner({ + required core.OwnerId ownerId, + bool includeArchived = false, + core.PageRequest page = const core.PageRequest(), + }) async { + final offset = _cursorOffset(page.cursor); + final query = db.select(db.lockedBlocks) + ..where((table) { + final ownerClause = table.ownerId.equals(ownerId.value); + return includeArchived + ? ownerClause + : ownerClause & table.archivedAtUtc.isNull(); + }) + ..orderBy([(table) => drift.OrderingTerm.asc(table.id)]) + ..limit(page.limit + 1, offset: offset); + final rows = await query.get(); + final pageRows = rows.take(page.limit).toList(growable: false); + return Right(core.Page( + items: pageRows.map(_lockedBlockFromRow), + nextCursor: rows.length > page.limit ? '${offset + page.limit}' : null, + )); + } + + @override + Future> findOverrideById({ + required core.OwnerId ownerId, + required String overrideId, + }) async { + final row = await _overrideById(overrideId); + if (row == null || row.ownerId != ownerId.value) { + return const Right(null); + } + return Right(_lockedOverrideFromRow(row)); + } + + @override + Future>> findOverridesForDate({ + required core.OwnerId ownerId, + required core.CivilDate date, + core.PageRequest page = const core.PageRequest(), + }) async { + final offset = _cursorOffset(page.cursor); + final query = db.select(db.lockedOverrides) + ..where( + (table) => + table.ownerId.equals(ownerId.value) & + table.date.equals(date.toIsoString()), + ) + ..orderBy([(table) => drift.OrderingTerm.asc(table.id)]) + ..limit(page.limit + 1, offset: offset); + final rows = await query.get(); + final pageRows = rows.take(page.limit).toList(growable: false); + return Right(core.Page( + items: pageRows.map(_lockedOverrideFromRow), + nextCursor: rows.length > page.limit ? '${offset + page.limit}' : null, + )); + } + + @override + Future> insertBlock({ + required core.OwnerId ownerId, + required core.LockedBlock block, + }) async { + if (await _blockById(block.id) != null) { + return Left(RepositoryDuplicateId(entityId: block.id)); + } + await db.into(db.lockedBlocks).insert(_lockedBlockCompanion( + block, + ownerId: ownerId, + revision: core.Revision.initial, + )); + return const Right(core.Revision.initial); + } + + @override + Future> saveBlock({ + required core.OwnerId ownerId, + required core.LockedBlock block, + required core.Revision expectedRevision, + }) async { + final checked = await _checkBlockWrite( + block.id, + ownerId, + expectedRevision, + ); + if (checked is Left) { + return Left(checked.value); + } + final nextRevision = core.Revision(checked.right.revision).next(); + final updated = await (db.update(db.lockedBlocks) + ..where( + (table) => + table.id.equals(block.id) & + table.ownerId.equals(ownerId.value) & + table.revision.equals(expectedRevision.value), + )) + .write(_lockedBlockCompanion( + block, + ownerId: ownerId, + revision: nextRevision, + )); + if (updated != 1) { + return Left(await _staleBlockFailure(block.id, expectedRevision)); + } + return Right(nextRevision); + } + + @override + Future> archiveBlock({ + required core.OwnerId ownerId, + required String blockId, + required core.Revision expectedRevision, + required DateTime archivedAtUtc, + }) async { + final checked = await _checkBlockWrite(blockId, ownerId, expectedRevision); + if (checked is Left) { + return Left(checked.value); + } + final row = checked.right; + final nextRevision = core.Revision(row.revision).next(); + final archived = + _lockedBlockFromRow(row).copyWith(archivedAt: archivedAtUtc); + final updated = await (db.update(db.lockedBlocks) + ..where( + (table) => + table.id.equals(blockId) & + table.ownerId.equals(ownerId.value) & + table.revision.equals(expectedRevision.value), + )) + .write(_lockedBlockCompanion( + archived, + ownerId: ownerId, + revision: nextRevision, + )); + if (updated != 1) { + return Left(await _staleBlockFailure(blockId, expectedRevision)); + } + return Right(nextRevision); + } + + @override + Future> insertOverride({ + required core.OwnerId ownerId, + required core.LockedBlockOverride override, + }) async { + if (await _overrideById(override.id) != null) { + return Left(RepositoryDuplicateId(entityId: override.id)); + } + await db.into(db.lockedOverrides).insert(_lockedOverrideCompanion( + override, + ownerId: ownerId, + revision: core.Revision.initial, + )); + return const Right(core.Revision.initial); + } + + @override + Future> saveOverride({ + required core.OwnerId ownerId, + required core.LockedBlockOverride override, + required core.Revision expectedRevision, + }) async { + final checked = await _checkOverrideWrite( + override.id, + ownerId, + expectedRevision, + ); + if (checked is Left) { + return Left(checked.value); + } + final nextRevision = core.Revision(checked.right.revision).next(); + final updated = await (db.update(db.lockedOverrides) + ..where( + (table) => + table.id.equals(override.id) & + table.ownerId.equals(ownerId.value) & + table.revision.equals(expectedRevision.value), + )) + .write(_lockedOverrideCompanion( + override, + ownerId: ownerId, + revision: nextRevision, + )); + if (updated != 1) { + return Left(await _staleOverrideFailure( + override.id, + expectedRevision, + )); + } + return Right(nextRevision); + } + + Future _blockById(String id) { + return (db.select(db.lockedBlocks)..where((table) => table.id.equals(id))) + .getSingleOrNull(); + } + + Future _overrideById(String id) { + return (db.select(db.lockedOverrides) + ..where((table) => table.id.equals(id))) + .getSingleOrNull(); + } + + Future> _checkBlockWrite( + String id, + core.OwnerId ownerId, + core.Revision expectedRevision, + ) async { + if (expectedRevision.value <= 0) { + return Left(RepositoryInvalidRevision( + entityId: id, + expectedRevision: expectedRevision, + )); + } + final row = await _blockById(id); + if (row == null) return Left(RepositoryNotFound(entityId: id)); + if (row.ownerId != ownerId.value) { + return Left(RepositoryOwnerMismatch(entityId: id)); + } + if (row.revision != expectedRevision.value) { + return Left(RepositoryStaleRevision( + entityId: id, + expectedRevision: expectedRevision, + actualRevision: core.Revision(row.revision), + )); + } + return Right(row); + } + + Future> _checkOverrideWrite( + String id, + core.OwnerId ownerId, + core.Revision expectedRevision, + ) async { + if (expectedRevision.value <= 0) { + return Left(RepositoryInvalidRevision( + entityId: id, + expectedRevision: expectedRevision, + )); + } + final row = await _overrideById(id); + if (row == null) return Left(RepositoryNotFound(entityId: id)); + if (row.ownerId != ownerId.value) { + return Left(RepositoryOwnerMismatch(entityId: id)); + } + if (row.revision != expectedRevision.value) { + return Left(RepositoryStaleRevision( + entityId: id, + expectedRevision: expectedRevision, + actualRevision: core.Revision(row.revision), + )); + } + return Right(row); + } + + Future _staleBlockFailure( + String id, + core.Revision expectedRevision, + ) async { + final row = await _blockById(id); + return RepositoryStaleRevision( + entityId: id, + expectedRevision: expectedRevision, + actualRevision: + row == null ? expectedRevision.next() : core.Revision(row.revision), + ); + } + + Future _staleOverrideFailure( + String id, + core.Revision expectedRevision, + ) async { + final row = await _overrideById(id); + return RepositoryStaleRevision( + entityId: id, + expectedRevision: expectedRevision, + actualRevision: + row == null ? expectedRevision.next() : core.Revision(row.revision), + ); + } +} + +/// Drift-backed owner settings repository. +final class SqliteSettingsRepository implements SettingsRepository { + /// Creates a settings repository using [db]. + SqliteSettingsRepository(this.db); + + /// Shared SQLite database handle. + final SchedulerDb db; + + @override + Future> findByOwner({ + required core.OwnerId ownerId, + }) async { + final row = await _settingsByOwner(ownerId.value); + if (row == null) return const Right(null); + return Right(_settingsFromRow(row)); + } + + @override + Future> insert({ + required core.OwnerId ownerId, + required core.OwnerSettings settings, + }) async { + if (await _settingsByOwner(ownerId.value) != null) { + return Left(RepositoryDuplicateId(entityId: ownerId.value)); + } + final ownedSettings = settings.ownerId == ownerId.value + ? settings + : settings.copyWith(ownerId: ownerId.value); + await db.into(db.settingsTable).insert(_settingsCompanion( + ownedSettings, + revision: core.Revision.initial, + createdAt: _epoch, + updatedAt: _epoch, + )); + return const Right(core.Revision.initial); + } + + @override + Future> save({ + required core.OwnerId ownerId, + required core.OwnerSettings settings, + required core.Revision expectedRevision, + }) async { + final checked = await _checkSettingsWrite(ownerId, expectedRevision); + if (checked is Left) { + return Left(checked.value); + } + final row = checked.right; + final nextRevision = core.Revision(row.revision).next(); + final ownedSettings = settings.ownerId == ownerId.value + ? settings + : settings.copyWith(ownerId: ownerId.value); + final updated = await (db.update(db.settingsTable) + ..where( + (table) => + table.ownerId.equals(ownerId.value) & + table.revision.equals(expectedRevision.value), + )) + .write(_settingsCompanion( + ownedSettings, + revision: nextRevision, + createdAt: row.createdAtUtc, + updatedAt: _epoch, + )); + if (updated != 1) { + return Left(await _staleSettingsFailure(ownerId, expectedRevision)); + } + return Right(nextRevision); + } + + Future _settingsByOwner(String ownerId) { + return (db.select(db.settingsTable) + ..where((table) => table.ownerId.equals(ownerId))) + .getSingleOrNull(); + } + + Future> _checkSettingsWrite( + core.OwnerId ownerId, + core.Revision expectedRevision, + ) async { + if (expectedRevision.value <= 0) { + return Left(RepositoryInvalidRevision( + entityId: ownerId.value, + expectedRevision: expectedRevision, + )); + } + final row = await _settingsByOwner(ownerId.value); + if (row == null) return Left(RepositoryNotFound(entityId: ownerId.value)); + if (row.revision != expectedRevision.value) { + return Left(RepositoryStaleRevision( + entityId: ownerId.value, + expectedRevision: expectedRevision, + actualRevision: core.Revision(row.revision), + )); + } + return Right(row); + } + + Future _staleSettingsFailure( + core.OwnerId ownerId, + core.Revision expectedRevision, + ) async { + final row = await _settingsByOwner(ownerId.value); + return RepositoryStaleRevision( + entityId: ownerId.value, + expectedRevision: expectedRevision, + actualRevision: + row == null ? expectedRevision.next() : core.Revision(row.revision), + ); + } +} + +/// Drift-backed schedule snapshot repository. +final class SqliteScheduleSnapshotRepository + implements ScheduleSnapshotRepository { + /// Creates a snapshot repository using [db]. + SqliteScheduleSnapshotRepository(this.db); + + /// Shared SQLite database handle. + final SchedulerDb db; + + @override + Future> findById({ + required core.OwnerId ownerId, + required String snapshotId, + }) async { + final row = await _snapshotById(snapshotId); + if (row == null || row.ownerId != ownerId.value) { + return const Right(null); + } + return Right(_snapshotFromRow(row)); + } + + @override + Future>> findInWindow({ + required core.OwnerId ownerId, + required core.SchedulingWindow window, + core.PageRequest page = const core.PageRequest(), + }) async { + final rows = await (db.select(db.snapshots) + ..where((table) => table.ownerId.equals(ownerId.value)) + ..orderBy([(table) => drift.OrderingTerm.asc(table.id)])) + .get(); + final matching = rows + .map(_snapshotFromRow) + .where((snapshot) => _windowsOverlap(snapshot.window, window)) + .toList(growable: false); + final offset = _cursorOffset(page.cursor); + final pageItems = matching.skip(offset).take(page.limit).toList(); + final nextOffset = offset + page.limit; + return Right(core.Page( + items: pageItems, + nextCursor: nextOffset < matching.length ? '$nextOffset' : null, + )); + } + + @override + Future> insert({ + required core.OwnerId ownerId, + required core.SchedulingStateSnapshot snapshot, + }) async { + if (await _snapshotById(snapshot.id) != null) { + return Left(RepositoryDuplicateId(entityId: snapshot.id)); + } + await db.into(db.snapshots).insert(_snapshotCompanion( + snapshot, + ownerId: ownerId, + revision: core.Revision.initial, + )); + return const Right(core.Revision.initial); + } + + @override + Future> deleteExpired({ + required core.OwnerId ownerId, + required DateTime nowUtc, + }) async { + final deleted = await (db.delete(db.snapshots) + ..where( + (table) => + table.ownerId.equals(ownerId.value) & + table.retentionExpiresUtc.isSmallerThanValue(nowUtc.toUtc()), + )) + .go(); + return Right(deleted); + } + + Future _snapshotById(String id) { + return (db.select(db.snapshots)..where((table) => table.id.equals(id))) + .getSingleOrNull(); + } +} + +TasksCompanion _taskCompanion( + core.Task task, { + required core.OwnerId ownerId, + required core.Revision revision, +}) { + final doc = core.TaskDocumentMapping.toDocument( + task, + ownerId: ownerId.value, + revision: revision.value, + ); + return TasksCompanion.insert( + id: task.id, + ownerId: ownerId.value, + title: _string(doc, core.TaskDocumentFields.title), + projectId: _string(doc, core.TaskDocumentFields.projectId), + parentId: drift.Value(_nullableString( + doc, + core.TaskDocumentFields.parentTaskId, + )), + type: _string(doc, core.TaskDocumentFields.type), + status: _string(doc, core.TaskDocumentFields.status), + priority: drift.Value(_nullableString( + doc, + core.TaskDocumentFields.priority, + )), + reward: _string(doc, core.TaskDocumentFields.reward), + difficulty: _string(doc, core.TaskDocumentFields.difficulty), + durationMinutes: drift.Value(_nullableInt( + doc, + core.TaskDocumentFields.durationMinutes, + )), + scheduledStartUtc: drift.Value(_nullableDateTime( + doc, + core.TaskDocumentFields.scheduledStart, + )), + scheduledEndUtc: drift.Value(_nullableDateTime( + doc, + core.TaskDocumentFields.scheduledEnd, + )), + actualStartUtc: drift.Value(_nullableDateTime( + doc, + core.TaskDocumentFields.actualStart, + )), + actualEndUtc: drift.Value(_nullableDateTime( + doc, + core.TaskDocumentFields.actualEnd, + )), + completedAtUtc: drift.Value(_nullableDateTime( + doc, + core.TaskDocumentFields.completedAt, + )), + backlogTagsJson: jsonEncode(doc[core.TaskDocumentFields.backlogTags]), + reminderOverride: drift.Value(_nullableString( + doc, + core.TaskDocumentFields.reminderOverride, + )), + statsJson: jsonEncode(doc[core.TaskDocumentFields.stats]), + backlogEnteredAtUtc: drift.Value(_nullableDateTime( + doc, + core.TaskDocumentFields.backlogEnteredAt, + )), + backlogEnteredProvenance: drift.Value(_nullableString( + doc, + core.TaskDocumentFields.backlogEnteredAtProvenance, + )), + revision: revision.value, + createdAtUtc: task.createdAt.toUtc(), + updatedAtUtc: task.updatedAt.toUtc(), + ); +} + +core.Task _taskFromRow(TaskRow row) { + return core.TaskDocumentMapping.fromDocument({ + ..._commonDocumentFields( + row.id, row.ownerId, row.revision, row.createdAtUtc, row.updatedAtUtc), + core.TaskDocumentFields.title: row.title, + core.TaskDocumentFields.projectId: row.projectId, + core.TaskDocumentFields.type: row.type, + core.TaskDocumentFields.status: row.status, + core.TaskDocumentFields.priority: row.priority, + core.TaskDocumentFields.reward: row.reward, + core.TaskDocumentFields.difficulty: row.difficulty, + core.TaskDocumentFields.durationMinutes: row.durationMinutes, + core.TaskDocumentFields.scheduledStart: + _storedDateTimeOrNull(row.scheduledStartUtc), + core.TaskDocumentFields.scheduledEnd: + _storedDateTimeOrNull(row.scheduledEndUtc), + core.TaskDocumentFields.actualStart: + _storedDateTimeOrNull(row.actualStartUtc), + core.TaskDocumentFields.actualEnd: _storedDateTimeOrNull(row.actualEndUtc), + core.TaskDocumentFields.completedAt: + _storedDateTimeOrNull(row.completedAtUtc), + core.TaskDocumentFields.parentTaskId: row.parentId, + core.TaskDocumentFields.backlogTags: _jsonList(row.backlogTagsJson), + core.TaskDocumentFields.reminderOverride: row.reminderOverride, + core.TaskDocumentFields.stats: _jsonMap(row.statsJson), + core.TaskDocumentFields.backlogEnteredAt: + _storedDateTimeOrNull(row.backlogEnteredAtUtc), + core.TaskDocumentFields.backlogEnteredAtProvenance: + row.backlogEnteredProvenance, + }); +} + +ProjectsCompanion _projectCompanion( + core.ProjectProfile project, { + required core.OwnerId ownerId, + required core.Revision revision, + required DateTime createdAt, + required DateTime updatedAt, +}) { + final doc = core.ProjectDocumentMapping.toDocument( + project, + ownerId: ownerId.value, + revision: revision.value, + createdAt: createdAt, + updatedAt: updatedAt, + ); + return ProjectsCompanion.insert( + id: project.id, + ownerId: ownerId.value, + name: _string(doc, core.ProjectDocumentFields.name), + colorKey: _string(doc, core.ProjectDocumentFields.colorKey), + defaultPriority: _string(doc, core.ProjectDocumentFields.defaultPriority), + defaultReward: _string(doc, core.ProjectDocumentFields.defaultReward), + defaultDifficulty: + _string(doc, core.ProjectDocumentFields.defaultDifficulty), + defaultReminderProfile: + _string(doc, core.ProjectDocumentFields.defaultReminderProfile), + defaultDurationMinutes: drift.Value(_nullableInt( + doc, + core.ProjectDocumentFields.defaultDurationMinutes, + )), + archivedAtUtc: drift.Value(_nullableDateTime( + doc, + core.ProjectDocumentFields.archivedAt, + )), + revision: revision.value, + createdAtUtc: createdAt.toUtc(), + updatedAtUtc: updatedAt.toUtc(), + ); +} + +core.ProjectProfile _projectFromRow(ProjectRow row) { + return core.ProjectDocumentMapping.fromDocument({ + ..._commonDocumentFields( + row.id, row.ownerId, row.revision, row.createdAtUtc, row.updatedAtUtc), + core.ProjectDocumentFields.name: row.name, + core.ProjectDocumentFields.colorKey: row.colorKey, + core.ProjectDocumentFields.defaultPriority: row.defaultPriority, + core.ProjectDocumentFields.defaultReward: row.defaultReward, + core.ProjectDocumentFields.defaultDifficulty: row.defaultDifficulty, + core.ProjectDocumentFields.defaultReminderProfile: + row.defaultReminderProfile, + core.ProjectDocumentFields.defaultDurationMinutes: + row.defaultDurationMinutes, + core.ProjectDocumentFields.archivedAt: _storedDateTimeOrNull( + row.archivedAtUtc, + ), + }); +} + +LockedBlocksCompanion _lockedBlockCompanion( + core.LockedBlock block, { + required core.OwnerId ownerId, + required core.Revision revision, +}) { + final doc = core.LockedBlockDocumentMapping.toDocument( + block, + ownerId: ownerId.value, + revision: revision.value, + ); + return LockedBlocksCompanion.insert( + id: block.id, + ownerId: ownerId.value, + name: _string(doc, core.LockedBlockDocumentFields.name), + date: + drift.Value(_nullableString(doc, core.LockedBlockDocumentFields.date)), + startTime: _string(doc, core.LockedBlockDocumentFields.startTime), + endTime: _string(doc, core.LockedBlockDocumentFields.endTime), + recurrenceJson: drift.Value(_jsonEncodeNullable( + doc[core.LockedBlockDocumentFields.recurrence], + )), + hiddenByDefault: _bool(doc, core.LockedBlockDocumentFields.hiddenByDefault), + projectId: drift.Value(_nullableString( + doc, + core.LockedBlockDocumentFields.projectId, + )), + archivedAtUtc: drift.Value(_nullableDateTime( + doc, + core.LockedBlockDocumentFields.archivedAt, + )), + revision: revision.value, + createdAtUtc: block.createdAt.toUtc(), + updatedAtUtc: block.updatedAt.toUtc(), + ); +} + +core.LockedBlock _lockedBlockFromRow(LockedBlockRow row) { + return core.LockedBlockDocumentMapping.fromDocument({ + ..._commonDocumentFields( + row.id, row.ownerId, row.revision, row.createdAtUtc, row.updatedAtUtc), + core.LockedBlockDocumentFields.name: row.name, + core.LockedBlockDocumentFields.startTime: row.startTime, + core.LockedBlockDocumentFields.endTime: row.endTime, + core.LockedBlockDocumentFields.date: row.date, + core.LockedBlockDocumentFields.recurrence: + row.recurrenceJson == null ? null : _jsonMap(row.recurrenceJson!), + core.LockedBlockDocumentFields.hiddenByDefault: row.hiddenByDefault, + core.LockedBlockDocumentFields.projectId: row.projectId, + core.LockedBlockDocumentFields.archivedAt: + _storedDateTimeOrNull(row.archivedAtUtc), + }); +} + +LockedOverridesCompanion _lockedOverrideCompanion( + core.LockedBlockOverride override, { + required core.OwnerId ownerId, + required core.Revision revision, +}) { + final doc = core.LockedBlockOverrideDocumentMapping.toDocument( + override, + ownerId: ownerId.value, + revision: revision.value, + ); + return LockedOverridesCompanion.insert( + id: override.id, + ownerId: ownerId.value, + lockedBlockId: drift.Value(_nullableString( + doc, + core.LockedBlockOverrideDocumentFields.lockedBlockId, + )), + date: _string(doc, core.LockedBlockOverrideDocumentFields.date), + type: _string(doc, core.LockedBlockOverrideDocumentFields.type), + name: drift.Value( + _nullableString(doc, core.LockedBlockOverrideDocumentFields.name), + ), + startTime: drift.Value(_nullableString( + doc, + core.LockedBlockOverrideDocumentFields.startTime, + )), + endTime: drift.Value(_nullableString( + doc, + core.LockedBlockOverrideDocumentFields.endTime, + )), + hiddenByDefault: + _bool(doc, core.LockedBlockOverrideDocumentFields.hiddenByDefault), + projectId: drift.Value(_nullableString( + doc, + core.LockedBlockOverrideDocumentFields.projectId, + )), + revision: revision.value, + createdAtUtc: override.createdAt.toUtc(), + updatedAtUtc: override.updatedAt.toUtc(), + ); +} + +core.LockedBlockOverride _lockedOverrideFromRow(LockedOverrideRow row) { + return core.LockedBlockOverrideDocumentMapping.fromDocument({ + ..._commonDocumentFields( + row.id, row.ownerId, row.revision, row.createdAtUtc, row.updatedAtUtc), + core.LockedBlockOverrideDocumentFields.lockedBlockId: row.lockedBlockId, + core.LockedBlockOverrideDocumentFields.date: row.date, + core.LockedBlockOverrideDocumentFields.type: row.type, + core.LockedBlockOverrideDocumentFields.name: row.name, + core.LockedBlockOverrideDocumentFields.startTime: row.startTime, + core.LockedBlockOverrideDocumentFields.endTime: row.endTime, + core.LockedBlockOverrideDocumentFields.hiddenByDefault: row.hiddenByDefault, + core.LockedBlockOverrideDocumentFields.projectId: row.projectId, + }); +} + +SettingsTableCompanion _settingsCompanion( + core.OwnerSettings settings, { + required core.Revision revision, + required DateTime createdAt, + required DateTime updatedAt, +}) { + final doc = core.OwnerSettingsDocumentMapping.toDocument( + settings, + revision: revision.value, + createdAt: createdAt, + updatedAt: updatedAt, + ); + return SettingsTableCompanion.insert( + ownerId: settings.ownerId, + timeZoneId: _string(doc, core.OwnerSettingsDocumentFields.timeZoneId), + dayStartMinutes: settings.dayStart.minutesSinceMidnight, + dayEndMinutes: settings.dayEnd.minutesSinceMidnight, + compactMode: + _bool(doc, core.OwnerSettingsDocumentFields.compactModeEnabled), + backlogStalenessJson: + jsonEncode(doc[core.OwnerSettingsDocumentFields.backlogStaleness]), + revision: revision.value, + createdAtUtc: createdAt.toUtc(), + updatedAtUtc: updatedAt.toUtc(), + ); +} + +core.OwnerSettings _settingsFromRow(SettingsRow row) { + return core.OwnerSettingsDocumentMapping.fromDocument({ + ..._commonDocumentFields(row.ownerId, row.ownerId, row.revision, + row.createdAtUtc, row.updatedAtUtc), + core.OwnerSettingsDocumentFields.timeZoneId: row.timeZoneId, + core.OwnerSettingsDocumentFields.dayStart: + _wallTimeFromMinutes(row.dayStartMinutes).toIsoString(), + core.OwnerSettingsDocumentFields.dayEnd: + _wallTimeFromMinutes(row.dayEndMinutes).toIsoString(), + core.OwnerSettingsDocumentFields.compactModeEnabled: row.compactMode, + core.OwnerSettingsDocumentFields.backlogStaleness: + _jsonMap(row.backlogStalenessJson), + }); +} + +SnapshotsCompanion _snapshotCompanion( + core.SchedulingStateSnapshot snapshot, { + required core.OwnerId ownerId, + required core.Revision revision, +}) { + final retentionExpiresAt = snapshot.capturedAt.add(_snapshotRetention); + final doc = core.SchedulingSnapshotDocumentMapping.toDocument( + snapshot, + ownerId: ownerId.value, + revision: revision.value, + retentionExpiresAt: retentionExpiresAt, + ); + return SnapshotsCompanion.insert( + id: snapshot.id, + ownerId: ownerId.value, + capturedAtUtc: snapshot.capturedAt.toUtc(), + operationName: drift.Value(_nullableString( + doc, + core.SchedulingSnapshotDocumentFields.operationName, + )), + sourceDate: drift.Value(_nullableString( + doc, + core.SchedulingSnapshotDocumentFields.sourceDate, + )), + targetDate: drift.Value(_nullableString( + doc, + core.SchedulingSnapshotDocumentFields.targetDate, + )), + windowJson: jsonEncode(doc[core.SchedulingSnapshotDocumentFields.window]), + tasksJson: jsonEncode(doc[core.SchedulingSnapshotDocumentFields.tasks]), + lockedIntervalsJson: + jsonEncode(doc[core.SchedulingSnapshotDocumentFields.lockedIntervals]), + requiredVisibleIntervalsJson: jsonEncode( + doc[core.SchedulingSnapshotDocumentFields.requiredVisibleIntervals], + ), + noticesJson: jsonEncode(doc[core.SchedulingSnapshotDocumentFields.notices]), + changesJson: jsonEncode(doc[core.SchedulingSnapshotDocumentFields.changes]), + overlapsJson: + jsonEncode(doc[core.SchedulingSnapshotDocumentFields.overlaps]), + retentionExpiresUtc: retentionExpiresAt.toUtc(), + truncated: _bool(doc, core.SchedulingSnapshotDocumentFields.truncated), + revision: revision.value, + createdAtUtc: snapshot.capturedAt.toUtc(), + updatedAtUtc: snapshot.capturedAt.toUtc(), + ); +} + +core.SchedulingStateSnapshot _snapshotFromRow(SnapshotRow row) { + return core.SchedulingSnapshotDocumentMapping.fromDocument({ + ..._commonDocumentFields( + row.id, row.ownerId, row.revision, row.createdAtUtc, row.updatedAtUtc), + core.SchedulingSnapshotDocumentFields.capturedAt: + core.PersistenceDateTimeConvention.toStoredString(row.capturedAtUtc), + core.SchedulingSnapshotDocumentFields.sourceDate: row.sourceDate, + core.SchedulingSnapshotDocumentFields.targetDate: row.targetDate, + core.SchedulingSnapshotDocumentFields.window: _jsonMap(row.windowJson), + core.SchedulingSnapshotDocumentFields.tasks: _jsonList(row.tasksJson), + core.SchedulingSnapshotDocumentFields.lockedIntervals: + _jsonList(row.lockedIntervalsJson), + core.SchedulingSnapshotDocumentFields.requiredVisibleIntervals: + _jsonList(row.requiredVisibleIntervalsJson), + core.SchedulingSnapshotDocumentFields.notices: _jsonList(row.noticesJson), + core.SchedulingSnapshotDocumentFields.changes: _jsonList(row.changesJson), + core.SchedulingSnapshotDocumentFields.overlaps: _jsonList(row.overlapsJson), + core.SchedulingSnapshotDocumentFields.operationName: row.operationName, + core.SchedulingSnapshotDocumentFields.retentionExpiresAt: + core.PersistenceDateTimeConvention.toStoredString( + row.retentionExpiresUtc, + ), + core.SchedulingSnapshotDocumentFields.truncated: row.truncated, + }); +} + +Map _commonDocumentFields( + String id, + String ownerId, + int revision, + DateTime createdAt, + DateTime updatedAt, +) { + return { + core.DocumentFields.schemaVersion: core.v1SchemaVersion, + core.DocumentFields.id: id, + core.DocumentFields.ownerId: ownerId, + core.DocumentFields.revision: revision, + core.DocumentFields.createdAt: + core.PersistenceDateTimeConvention.toStoredString(createdAt), + core.DocumentFields.updatedAt: + core.PersistenceDateTimeConvention.toStoredString(updatedAt), + }; +} + +String _string(Map doc, String fieldName) { + return doc[fieldName] as String; +} + +String? _nullableString(Map doc, String fieldName) { + return doc[fieldName] as String?; +} + +int? _nullableInt(Map doc, String fieldName) { + return doc[fieldName] as int?; +} + +bool _bool(Map doc, String fieldName) { + return doc[fieldName] as bool; +} + +DateTime? _nullableDateTime(Map doc, String fieldName) { + final value = doc[fieldName] as String?; + return value == null + ? null + : core.PersistenceDateTimeConvention.fromStoredString(value); +} + +String? _storedDateTimeOrNull(DateTime? value) { + return value == null + ? null + : core.PersistenceDateTimeConvention.toStoredString(value); +} + +String? _jsonEncodeNullable(Object? value) { + return value == null ? null : jsonEncode(value); +} + +Map _jsonMap(String value) { + return Map.from( + jsonDecode(value) as Map, + ); +} + +List _jsonList(String value) { + return List.from(jsonDecode(value) as List); +} + +core.WallTime _wallTimeFromMinutes(int minutes) { + return core.WallTime(hour: minutes ~/ 60, minute: minutes % 60); +} + +int _cursorOffset(String? cursor) { + if (cursor == null) return 0; + final parsed = int.tryParse(cursor); + if (parsed == null || parsed < 0) return 0; + return parsed; +} + +extension on int { + core.Revision next() => core.Revision(this).next(); +} + +bool _windowsOverlap(core.SchedulingWindow left, core.SchedulingWindow right) { + return left.start.isBefore(right.end) && right.start.isBefore(left.end); +} diff --git a/packages/scheduler_persistence_sqlite/pubspec.yaml b/packages/scheduler_persistence_sqlite/pubspec.yaml new file mode 100644 index 0000000..54d5d7a --- /dev/null +++ b/packages/scheduler_persistence_sqlite/pubspec.yaml @@ -0,0 +1,19 @@ +name: scheduler_persistence_sqlite +description: Drift-backed SQLite persistence adapter for the ADHD scheduler. +version: 0.1.0 +publish_to: none +resolution: workspace + +environment: + sdk: '>=3.6.0 <4.0.0' + +dependencies: + drift: ^2.20.0 + scheduler_core: any + scheduler_persistence: any + +dev_dependencies: + build_runner: ^2.4.13 + drift_dev: ^2.20.0 + lints: ^5.0.0 + test: ^1.25.0 diff --git a/packages/scheduler_persistence_sqlite/test/scheduler_db_test.dart b/packages/scheduler_persistence_sqlite/test/scheduler_db_test.dart new file mode 100644 index 0000000..6139ead --- /dev/null +++ b/packages/scheduler_persistence_sqlite/test/scheduler_db_test.dart @@ -0,0 +1,33 @@ +import 'package:drift/native.dart'; +import 'package:scheduler_persistence_sqlite/sqlite.dart'; +import 'package:test/test.dart'; + +void main() { + test('opens schema version 1 with expected tables', () async { + final db = SchedulerDb(NativeDatabase.memory()); + addTearDown(db.close); + + expect(db.schemaVersion, 1); + + final rows = await db + .customSelect( + "SELECT name FROM sqlite_master " + "WHERE type = 'table' AND name NOT LIKE 'sqlite_%' " + 'ORDER BY name', + ) + .get(); + final tableNames = rows.map((row) => row.data['name']).toList(); + + expect( + tableNames, + containsAll([ + 'locked_blocks', + 'locked_overrides', + 'projects', + 'settings', + 'snapshots', + 'tasks', + ]), + ); + }); +} diff --git a/packages/scheduler_persistence_sqlite/test/sqlite_repository_conformance_test.dart b/packages/scheduler_persistence_sqlite/test/sqlite_repository_conformance_test.dart new file mode 100644 index 0000000..7ff8825 --- /dev/null +++ b/packages/scheduler_persistence_sqlite/test/sqlite_repository_conformance_test.dart @@ -0,0 +1,57 @@ +import 'package:drift/native.dart'; +import 'package:scheduler_core/scheduler_core.dart' as core; +import 'package:scheduler_persistence_sqlite/sqlite.dart'; +import 'package:test/test.dart'; + +import '../../scheduler_persistence/test/repo_conformance.dart'; + +void main() { + runRepositoryComplianceTests( + RepositoryComplianceFactories( + taskRepository: () => _repo(SqliteTaskRepository.new), + projectRepository: () => _repo(SqliteProjectRepository.new), + lockedBlockRepository: () => _repo(SqliteLockedBlockRepository.new), + settingsRepository: () => _repo(SqliteSettingsRepository.new), + scheduleSnapshotRepository: () => + _repo(SqliteScheduleSnapshotRepository.new), + ), + ); + + test('average task write latency stays below 50ms', () async { + final db = SchedulerDb(NativeDatabase.memory()); + addTearDown(db.close); + final repo = SqliteTaskRepository(db); + final ownerId = core.OwnerId('latency-owner'); + const writes = 50; + final stopwatch = Stopwatch()..start(); + + for (var index = 0; index < writes; index += 1) { + final result = await repo.insert( + ownerId: ownerId, + task: core.Task( + id: 'task-$index', + title: 'Task $index', + projectId: 'project-a', + type: core.TaskType.flexible, + status: core.TaskStatus.backlog, + durationMinutes: 30, + createdAt: DateTime.utc(2026, 1, 1), + updatedAt: DateTime.utc(2026, 1, 1), + ), + ); + expect(result.isRight, isTrue); + } + + stopwatch.stop(); + final averageWriteMs = stopwatch.elapsedMicroseconds / + Duration.microsecondsPerMillisecond / + writes; + expect(averageWriteMs, lessThanOrEqualTo(50)); + }); +} + +T _repo(T Function(SchedulerDb db) create) { + final db = SchedulerDb(NativeDatabase.memory()); + addTearDown(db.close); + return create(db); +} diff --git a/pubspec.yaml b/pubspec.yaml index e315334..6bdf50a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,11 +1,34 @@ -name: adhd_scheduler_core -description: A pure Dart scheduling core starter for an ADHD-focused scheduling app. -version: 0.1.0 +name: adhd_scheduling_workspace publish_to: none environment: - sdk: '>=3.4.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' + +workspace: + - packages/scheduler_backup + - packages/scheduler_core + - packages/scheduler_export + - packages/scheduler_export_json + - packages/scheduler_integration_tests + - packages/scheduler_notifications + - packages/scheduler_notifications_desktop + - packages/scheduler_persistence + - packages/scheduler_persistence_memory + - packages/scheduler_persistence_sqlite + +dependencies: + scheduler_backup: any + scheduler_core: any + scheduler_export: any + scheduler_export_json: any + scheduler_integration_tests: any + scheduler_notifications: any + scheduler_notifications_desktop: any + scheduler_persistence: any + scheduler_persistence_memory: any + scheduler_persistence_sqlite: any dev_dependencies: - lints: ^5.0.0 + build_runner: ^2.4.13 + coverage: ^1.11.0 test: ^1.25.0 diff --git a/test/scheduler_core_test.dart b/test/scheduler_core_test.dart new file mode 100644 index 0000000..818de52 --- /dev/null +++ b/test/scheduler_core_test.dart @@ -0,0 +1,105 @@ +import '../packages/scheduler_backup/test/encrypted_backup_test.dart' + as encrypted_backup_test; +import '../packages/scheduler_core/test/application_commands_test.dart' + as application_commands_test; +import '../packages/scheduler_core/test/application_layer_test.dart' + as application_layer_test; +import '../packages/scheduler_core/test/application_management_test.dart' + as application_management_test; +import '../packages/scheduler_core/test/application_recovery_test.dart' + as application_recovery_test; +import '../packages/scheduler_core/test/child_tasks_test.dart' + as child_tasks_test; +import '../packages/scheduler_core/test/document_mapping_test.dart' + as document_mapping_test; +import '../packages/scheduler_core/test/document_migration_test.dart' + as document_migration_test; +import '../packages/scheduler_core/test/domain_contracts_test.dart' + as domain_contracts_test; +import '../packages/scheduler_core/test/domain_invariants_test.dart' + as domain_invariants_test; +import '../packages/scheduler_core/test/edge_case_regression_test.dart' + as edge_case_regression_test; +import '../packages/scheduler_core/test/free_slots_test.dart' + as free_slots_test; +import '../packages/scheduler_core/test/occupancy_policy_test.dart' + as occupancy_policy_test; +import '../packages/scheduler_core/test/persistence_edge_cases_test.dart' + as persistence_edge_cases_test; +import '../packages/scheduler_core/test/persistence_index_contract_test.dart' + as persistence_index_contract_test; +import '../packages/scheduler_core/test/project_statistics_test.dart' + as project_statistics_test; +import '../packages/scheduler_core/test/reminder_policy_test.dart' + as reminder_policy_test; +import '../packages/scheduler_core/test/repositories_test.dart' + as repositories_test; +import '../packages/scheduler_core/test/required_task_actions_test.dart' + as required_task_actions_test; +import '../packages/scheduler_core/test/scheduling_engine_test.dart' + as scheduling_engine_test; +import '../packages/scheduler_core/test/scheduling_invariants_test.dart' + as scheduling_invariants_test; +import '../packages/scheduler_core/test/surprise_task_logging_test.dart' + as surprise_task_logging_test; +import '../packages/scheduler_core/test/task_lifecycle_test.dart' + as task_lifecycle_test; +import '../packages/scheduler_core/test/time_contracts_test.dart' + as time_contracts_test; +import '../packages/scheduler_core/test/timeline_state_test.dart' + as timeline_state_test; +import '../packages/scheduler_core/test/today_state_test.dart' + as today_state_test; +import '../packages/scheduler_notifications/test/fake_adapter_test.dart' + as fake_adapter_test; +import '../packages/scheduler_notifications_desktop/test/desktop_notification_adapter_test.dart' + as desktop_notification_adapter_test; +import '../packages/scheduler_export/test/export_controller_test.dart' + as export_controller_test; +import '../packages/scheduler_export_json/test/export_json_test.dart' + as export_json_test; +import '../packages/scheduler_integration_tests/integration/scheduler_flow_test.dart' + as scheduler_flow_test; +import '../packages/scheduler_persistence/test/repo_contract_smoke_test.dart' + as repo_contract_smoke_test; +import '../packages/scheduler_persistence_memory/test/memory_repository_conformance_test.dart' + as memory_repository_conformance_test; +import '../packages/scheduler_persistence_sqlite/test/sqlite_repository_conformance_test.dart' + as sqlite_repository_conformance_test; + +void main() { + encrypted_backup_test.main(); + application_commands_test.main(); + application_layer_test.main(); + application_management_test.main(); + application_recovery_test.main(); + child_tasks_test.main(); + document_mapping_test.main(); + document_migration_test.main(); + domain_contracts_test.main(); + domain_invariants_test.main(); + edge_case_regression_test.main(); + free_slots_test.main(); + occupancy_policy_test.main(); + persistence_edge_cases_test.main(); + persistence_index_contract_test.main(); + project_statistics_test.main(); + reminder_policy_test.main(); + repositories_test.main(); + required_task_actions_test.main(); + scheduling_engine_test.main(); + scheduling_invariants_test.main(); + surprise_task_logging_test.main(); + task_lifecycle_test.main(); + time_contracts_test.main(); + timeline_state_test.main(); + today_state_test.main(); + fake_adapter_test.main(); + desktop_notification_adapter_test.main(); + export_controller_test.main(); + export_json_test.main(); + scheduler_flow_test.main(); + repo_contract_smoke_test.main(); + memory_repository_conformance_test.main(); + sqlite_repository_conformance_test.main(); +}