Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 66 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,74 @@
{
// Dart/Flutter specific settings
"dart.lineLength": 100,
"dart.previewFlutterUiGuides": true,
"dart.previewFlutterUiGuidesCustomTracking": true,
"dart.debugSdkLibraries": false,
// File handling
"files.insertFinalNewline": true,
"files.trimTrailingWhitespace": true,
"files.exclude": {
"**/.dart_tool/**": true,
"**/build/**": true,
"**/*.g.dart": true,
"**/*.iml": true
},
// Explorer settings for better project navigation
"explorer.fileNesting.enabled": true,
"explorer.fileNesting.expand": false,
"explorer.fileNesting.patterns": {
"pubspec.yaml": "pubspec.lock,analysis_options.yaml,.metadata",
"*.dart": "*.g.dart",
"README.md": "CHANGELOG.md,LICENSE,CODEOWNERS"
},
// Editor formatting
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.organizeImports": "explicit"
},
"explorer.fileNesting.enabled": true,
"explorer.fileNesting.expand": false,
"files.insertFinalNewline": true
"editor.rulers": [
100
],
"editor.tabSize": 2,
"editor.insertSpaces": true,
"editor.detectIndentation": false,
// Language specific settings
"[dart]": {
"editor.selectionHighlight": false,
"editor.suggest.snippetsPreventQuickSuggestions": false,
"editor.suggestSelection": "first",
"editor.tabCompletion": "onlySnippets",
"editor.wordBasedSuggestions": "off"
},
"[yaml]": {
"editor.insertSpaces": true,
"editor.tabSize": 2,
"editor.autoIndent": "advanced"
},
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[markdown]": {
"editor.wordWrap": "wordWrapColumn",
"editor.wordWrapColumn": 80,
"editor.quickSuggestions": {
"comments": "off",
"strings": "off",
"other": "off"
},
"editor.suggest.showWords": false,
"editor.formatOnSave": false
},
// Search settings
"search.exclude": {
"**/.dart_tool/**": true,
"**/build/**": true,
"**/node_modules/**": true,
"**/.git/**": true
},
// Workbench settings
"workbench.editorAssociations": {
"*.md": "default"
}
}
8 changes: 8 additions & 0 deletions docs/offline_first/policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ Delete local results before waiting for the remote provider to respond.

Delete local results after remote responds; local results are not deleted if remote responds with any exception.

### `localOnly`

Delete local results only; do not send any request to the remote provider.

## OfflineFirstGetPolicy

### `alwaysHydrate`
Expand Down Expand Up @@ -39,3 +43,7 @@ Save results to local before waiting for the remote provider to respond.
### `requireRemote`

Save results to local after remote responds; local results are not saved if remote responds with any exception.

### `localOnly`

Save results to local only; do not send any request to the remote provider.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ enum OfflineFirstDeletePolicy {

/// Delete local results after remote responds; local results are not deleted if remote responds with any exception
requireRemote,

/// Delete local results only; do not send any request to the remote provider
localOnly,
}

/// Data will **always** be returned from local providers and never directly
Expand Down Expand Up @@ -35,4 +38,7 @@ enum OfflineFirstUpsertPolicy {
/// Save results to local after remote responds;
/// local results are not saved if remote responds with any exception
requireRemote,

/// Save results to local only; do not send any request to the remote provider
localOnly,
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,14 +114,19 @@ abstract class OfflineFirstRepository<TRepositoryModel extends OfflineFirstModel

final optimisticLocal = policy == OfflineFirstDeletePolicy.optimisticLocal;
final requireRemote = policy == OfflineFirstDeletePolicy.requireRemote;
final localOnly = policy == OfflineFirstDeletePolicy.localOnly;

var rowsDeleted = 0;

if (optimisticLocal) {
if (optimisticLocal || localOnly) {
rowsDeleted = await _deleteLocal<TModel>(instance, query: query);
await notifySubscriptionsWithLocalData<TModel>();
}

if (localOnly) {
return rowsDeleted > 0;
}

try {
await remoteProvider.delete<TModel>(instance, query: query, repository: this);
if (requireRemote) {
Expand All @@ -135,7 +140,6 @@ abstract class OfflineFirstRepository<TRepositoryModel extends OfflineFirstModel
logger.warning('#delete socket failure: $e');
if (requireRemote) rethrow;
}

// ignore: unawaited_futures
if (autoHydrate) hydrate<TModel>(query: query);

Expand Down Expand Up @@ -416,12 +420,17 @@ abstract class OfflineFirstRepository<TRepositoryModel extends OfflineFirstModel

final optimisticLocal = policy == OfflineFirstUpsertPolicy.optimisticLocal;
final requireRemote = policy == OfflineFirstUpsertPolicy.requireRemote;
final localOnly = policy == OfflineFirstUpsertPolicy.localOnly;

if (optimisticLocal) {
if (optimisticLocal || localOnly) {
instance.primaryKey = await _upsertLocal<TModel>(instance, query: query);
await notifySubscriptionsWithLocalData<TModel>();
}

if (localOnly) {
return instance;
}

try {
await remoteProvider.upsert<TModel>(instance, query: query, repository: this);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@ void main() {
throwsA(const TypeMatcher<SocketException>()),
);
});

test('OfflineFirstDeletePolicy.localOnly', () async {
final instance = Mounty(name: 'SqliteName');
final upserted = await TestRepository().upsert<Mounty>(instance);
expect(await TestRepository().sqliteProvider.get<Mounty>(), hasLength(1));
(TestRepository().remoteProvider as TestProvider).methodsCalled.clear();

await TestRepository().delete<Mounty>(upserted, policy: OfflineFirstDeletePolicy.localOnly);

expect(await TestRepository().sqliteProvider.get<Mounty>(), isEmpty);
expect((TestRepository().remoteProvider as TestProvider).methodsCalled, isEmpty);
});
});

group('#get', () {
Expand Down Expand Up @@ -185,6 +197,22 @@ void main() {
throwsA(const TypeMatcher<SocketException>()),
);
});

test('OfflineFirstUpsertPolicy.localOnly', () async {
final instance = Mounty(name: 'LocalOnlyMounty');
(TestRepository().remoteProvider as TestProvider).methodsCalled.clear();

final result = await TestRepository()
.upsert<Mounty>(instance, policy: OfflineFirstUpsertPolicy.localOnly);

expect(result.name, 'LocalOnlyMounty');
expect(result.primaryKey, greaterThanOrEqualTo(1));

final sqliteResults = await TestRepository().sqliteProvider.get<Mounty>();
expect(sqliteResults.where((m) => m.name == 'LocalOnlyMounty'), hasLength(1));

expect((TestRepository().remoteProvider as TestProvider).methodsCalled, isEmpty);
});
});

test('#reset', () async {
Expand Down