Skip to content

Release v2.0.0 - Major Breaking Changes#3

Closed
protheeuz wants to merge 15 commits into
mainfrom
release/v2.0.0
Closed

Release v2.0.0 - Major Breaking Changes#3
protheeuz wants to merge 15 commits into
mainfrom
release/v2.0.0

Conversation

@protheeuz

Copy link
Copy Markdown
Member

Release v2.0.0 - Major Architecture Overhaul

Overview

This PR introduces a complete rewrite of local_storage_cache with a comprehensive monorepo architecture, advanced features, and professional tooling. This is a major breaking change from v1.x.

Breaking Changes

Architecture Changes

  • Monorepo Structure: Migrated from single package to Melos-managed monorepo with 8 packages
    Platform-Specific Implementations: Separate packages for Android, iOS, macOS, Windows, Linux, and Web
    New API Surface: Complete API redesign with improved type safety and developer experience

API Changes

  • Removed simple key-value storage API from v1
  • Introduced schema-based storage with TableSchema and FieldSchema
  • New StorageEngine class replaces old LocalStorage class
  • Query builder pattern replaces direct SQL queries
  • Configuration moved to dedicated config classes

Migration Required

Users upgrading from v1.x will need to:

  1. Update import statements to new package structure
  2. Migrate from key-value storage to schema-based storage
  3. Update initialization code to use new StorageEngine API
  4. Review and update encryption configuration

New Features

Core Features

  • Multi-Space Architecture: Isolate data for different users or contexts
  • Advanced Query System: SQL-like queries with chaining, joins, and complex conditions
  • Schema Management: Automatic schema migration with intelligent field rename detection
  • Strong Encryption: AES-256-GCM and ChaCha20-Poly1305 with platform-native secure key storage

Performance Features

  • Multi-Level Caching: Memory and disk caching with configurable eviction policies
  • Connection Pooling: Efficient database connection management
  • Prepared Statements: Cached prepared statements for improved query performance
  • Batch Operations: Optimized bulk insert, update, and delete operations

Advanced Features

  • Backup and Restore: Full and selective backups with compression and encryption
  • Data Validation: Field-level validation with custom validators
  • Event System: Real-time event streams for storage operations
  • Performance Metrics: Built-in monitoring and performance tracking
  • Error Recovery: Automatic retry with exponential backoff

Documentation

  • Complete README rewrite with comprehensive examples
  • Professional CHANGELOG with semantic versioning
  • Platform-specific setup guides
  • API reference documentation
  • Example applications for all major features

Migration Guide

A detailed migration guide will be provided in the documentation. Key steps:

  1. Update pubspec.yaml to v2.0.0
  2. Replace old imports with new package structure
  3. Migrate data from v1 key-value storage to v2 schema-based storage
  4. Update initialization and configuration code
  5. Test thoroughly before deploying to production

@gitguardian

gitguardian Bot commented Jan 30, 2026

Copy link
Copy Markdown

️✅ There are no secrets present in this pull request anymore.

If these secrets were true positive and are still valid, we highly recommend you to revoke them.
While these secrets were previously flagged, we no longer have a reference to the
specific commits where they were detected. Once a secret has been leaked into a git
repository, you should consider it compromised, even if it was deleted immediately.
Find here more information about risks.


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@protheeuz protheeuz added enhancement New feature or request breaking-change labels Jan 30, 2026
@protheeuz

Copy link
Copy Markdown
Member Author

/gemini review

@protheeuz protheeuz closed this Jan 30, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This is an impressive and comprehensive rewrite for v2.0.0, introducing a professional monorepo architecture and a rich feature set. The documentation, including the README and SECURITY guides, is exceptionally detailed and well-structured. My review focuses on several critical and high-severity issues related to security, correctness, and performance that should be addressed before release. These include insecure key generation, non-deterministic hashing, incomplete migration logic, and performance bottlenecks in the caching layer. I've also noted some opportunities for improvement in the backup/restore logic and the query builder.

Comment on lines +74 to +78
String _generateKey() {
final timestamp = DateTime.now().microsecondsSinceEpoch;
final random = timestamp.toString();
final hash = sha256.convert(utf8.encode(random));
return base64Url.encode(hash.bytes);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-critical critical

The key generation method uses DateTime.now().microsecondsSinceEpoch, which is not a source of cryptographic randomness. Keys generated this way are predictable and can compromise the security of the entire encryption system. A cryptographically secure random number generator should be used instead.

import 'dart:math';

// ...

  String _generateKey() {
    final random = Random.secure();
    final values = List<int>.generate(32, (i) => random.nextInt(256));
    return base64Url.encode(values);
  }

Comment on lines +174 to +177
String _generateEncryptionKey() {
// Generate a 32-byte (256-bit) key
final random = DateTime.now().millisecondsSinceEpoch.toString();
return random.padRight(32, '0').substring(0, 32);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-critical critical

The encryption key generation is based on DateTime.now(), which is predictable and not cryptographically secure. This can lead to compromised encryption. It's crucial to use a source of true randomness for generating security keys.

import 'dart:math';

// ...

  String _generateEncryptionKey() {
    // Generate a 32-byte (256-bit) key
    final random = Random.secure();
    final values = List<int>.generate(32, (i) => random.nextInt(256));
    return base64Url.encode(values);
  }

Comment on lines +302 to +303

// Check for table rename

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The schema hash is calculated using schemaMap.toString().hashCode. The toString() representation of a Map does not guarantee a consistent key order across different Dart versions or runs. This can lead to non-deterministic hashes for the same schema, potentially causing unnecessary or incorrect migration triggers. To create a deterministic hash, the map should be converted to a canonical string representation, for example by using json.encode after sorting the map keys recursively.

Comment on lines +512 to +534
// SQLite doesn't support DROP COLUMN directly
// Need to use table recreation strategy
operations.add(
MigrationOperation.customSql(
sql:
'-- Field ${change.fieldName} removed from ${change.tableName} (requires table recreation)',
description:
'Remove field ${change.fieldName} from ${change.tableName}',
),
);

case SchemaChangeType.fieldTypeChanged:
case SchemaChangeType.fieldConstraintChanged:
// SQLite doesn't support ALTER COLUMN
// Need to use table recreation strategy
operations.add(
MigrationOperation.customSql(
sql:
'-- Field ${change.fieldName} modified in ${change.tableName} (requires table recreation)',
description:
'Modify field ${change.fieldName} in ${change.tableName}',
),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The migration generation for fieldRemoved, fieldTypeChanged, and fieldConstraintChanged is incomplete. It only adds a SQL comment indicating that table recreation is needed. For the feature "Automatic schema migration" to be fully realized, the system should handle this by implementing the shadow table migration pattern (create new table, copy data, drop old table, rename new table). The method migrateWithZeroDowntime seems to implement this pattern, and it should be integrated here to automate these complex migrations.

Comment on lines +392 to +395
String _buildConditionSQL(QueryCondition condition) {
// Simplified implementation - would need full condition parsing
return condition.toString();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The _buildConditionSQL method, which is supposed to handle nested query conditions, currently just returns condition.toString(). This is a placeholder and will not generate the correct SQL for nested WHERE clauses, breaking the functionality for complex queries. This method needs to be implemented to recursively build the SQL string for the nested QueryCondition.

# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The test coverage report directory (coverage/) is not ignored. Generated files like this should not be committed to version control as they can cause merge conflicts and unnecessarily increase repository size.

/android/app/release
/coverage/

Comment thread .gitignore
.flutter-plugins-dependencies

.kiro/
AGENTS.md No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The pubspec.lock file for the example app is included in version control. For library packages, it's a best practice to not commit the pubspec.lock file (even for examples) to avoid forcing specific dependency versions on consumers and contributors.

AGENTS.md
**/pubspec.lock

Comment on lines +258 to +268
Future<void> _evictOldest() async {
final allEntries = await entries;

if (allEntries.isEmpty) return;

// Sort by creation time
allEntries.sort((a, b) => a.createdAt.compareTo(b.createdAt));

// Remove oldest
await remove(allEntries.first.key);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The _evictOldest method implements a First-In, First-Out (FIFO) eviction policy by sorting entries based on createdAt. However, the CacheConfig allows specifying other policies like LRU or LFU. The DiskCache should respect the configured evictionPolicy instead of having a hardcoded behavior. The method should be updated to handle different eviction strategies, for example by sorting on lastAccessedAt for LRU or accessCount for LFU.

Comment on lines +347 to +376
Future<void> _checkExpirations() async {
if (!_initialized) return;

// Check memory cache
for (final entry in _memoryCache.entries) {
if (entry.isExpired) {
_expirationController.add(
CacheExpirationEvent(
key: entry.key,
expiredAt: DateTime.now(),
),
);
}
}

// Check disk cache
for (final entry in await _diskCache.entries) {
if (entry.isExpired) {
_expirationController.add(
CacheExpirationEvent(
key: entry.key,
expiredAt: DateTime.now(),
),
);
}
}

// Clear expired entries
await clearExpired();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The _checkExpirations method iterates through all cache entries to find expired items and emit events. Immediately after, clearExpired is called, which iterates through all entries again to remove them. This double iteration can be inefficient, especially for large caches. The logic could be combined into a single pass to both identify expired items for event emission and remove them.

Comment on lines +353 to +361
String _encodeMetadata(Map<String, dynamic> metadata) {
return metadata.toString(); // Simplified - in production use json.encode
}

/// Decodes metadata from JSON string.
Map<String, dynamic> _decodeMetadata(String json) {
// Simplified - in production use json.decode
return {};
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The _encodeMetadata and _decodeMetadata methods use placeholder implementations (toString() and returning an empty map). The comment indicates that json.encode/decode should be used. This should be fixed to correctly serialize and deserialize space metadata to and from JSON.

  String _encodeMetadata(Map<String, dynamic> metadata) {
    return json.encode(metadata);
  }

  /// Decodes metadata from JSON string.
  Map<String, dynamic> _decodeMetadata(String json) {
    return json.decode(json) as Map<String, dynamic>;
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-change enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant