omgkit 2.13.0 → 2.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +129 -10
- package/package.json +2 -2
- package/plugin/agents/api-designer.md +5 -0
- package/plugin/agents/architect.md +8 -0
- package/plugin/agents/brainstormer.md +4 -0
- package/plugin/agents/cicd-manager.md +6 -0
- package/plugin/agents/code-reviewer.md +6 -0
- package/plugin/agents/copywriter.md +2 -0
- package/plugin/agents/data-engineer.md +255 -0
- package/plugin/agents/database-admin.md +10 -0
- package/plugin/agents/debugger.md +10 -0
- package/plugin/agents/devsecops.md +314 -0
- package/plugin/agents/docs-manager.md +4 -0
- package/plugin/agents/domain-decomposer.md +181 -0
- package/plugin/agents/embedded-systems.md +397 -0
- package/plugin/agents/fullstack-developer.md +12 -0
- package/plugin/agents/game-systems-designer.md +375 -0
- package/plugin/agents/git-manager.md +10 -0
- package/plugin/agents/journal-writer.md +2 -0
- package/plugin/agents/ml-engineer.md +284 -0
- package/plugin/agents/observability-engineer.md +353 -0
- package/plugin/agents/oracle.md +9 -0
- package/plugin/agents/performance-engineer.md +290 -0
- package/plugin/agents/pipeline-architect.md +6 -0
- package/plugin/agents/planner.md +12 -0
- package/plugin/agents/platform-engineer.md +325 -0
- package/plugin/agents/project-manager.md +3 -0
- package/plugin/agents/researcher.md +5 -0
- package/plugin/agents/scientific-computing.md +426 -0
- package/plugin/agents/scout.md +3 -0
- package/plugin/agents/security-auditor.md +7 -0
- package/plugin/agents/sprint-master.md +17 -0
- package/plugin/agents/tester.md +10 -0
- package/plugin/agents/ui-ux-designer.md +12 -0
- package/plugin/agents/vulnerability-scanner.md +6 -0
- package/plugin/commands/data/pipeline.md +47 -0
- package/plugin/commands/data/quality.md +49 -0
- package/plugin/commands/domain/analyze.md +34 -0
- package/plugin/commands/domain/map.md +41 -0
- package/plugin/commands/game/balance.md +56 -0
- package/plugin/commands/game/optimize.md +62 -0
- package/plugin/commands/iot/provision.md +58 -0
- package/plugin/commands/ml/evaluate.md +47 -0
- package/plugin/commands/ml/train.md +48 -0
- package/plugin/commands/perf/benchmark.md +54 -0
- package/plugin/commands/perf/profile.md +49 -0
- package/plugin/commands/platform/blueprint.md +56 -0
- package/plugin/commands/security/audit.md +54 -0
- package/plugin/commands/security/scan.md +55 -0
- package/plugin/commands/sre/dashboard.md +53 -0
- package/plugin/registry.yaml +787 -0
- package/plugin/skills/ai-ml/experiment-tracking/SKILL.md +338 -0
- package/plugin/skills/ai-ml/feature-stores/SKILL.md +340 -0
- package/plugin/skills/ai-ml/llm-ops/SKILL.md +454 -0
- package/plugin/skills/ai-ml/ml-pipelines/SKILL.md +390 -0
- package/plugin/skills/ai-ml/model-monitoring/SKILL.md +398 -0
- package/plugin/skills/ai-ml/model-serving/SKILL.md +386 -0
- package/plugin/skills/event-driven/cqrs-patterns/SKILL.md +348 -0
- package/plugin/skills/event-driven/event-sourcing/SKILL.md +334 -0
- package/plugin/skills/event-driven/kafka-deep/SKILL.md +252 -0
- package/plugin/skills/event-driven/saga-orchestration/SKILL.md +335 -0
- package/plugin/skills/event-driven/schema-registry/SKILL.md +328 -0
- package/plugin/skills/event-driven/stream-processing/SKILL.md +313 -0
- package/plugin/skills/game/game-audio/SKILL.md +446 -0
- package/plugin/skills/game/game-networking/SKILL.md +490 -0
- package/plugin/skills/game/godot-patterns/SKILL.md +413 -0
- package/plugin/skills/game/shader-programming/SKILL.md +492 -0
- package/plugin/skills/game/unity-patterns/SKILL.md +488 -0
- package/plugin/skills/iot/device-provisioning/SKILL.md +405 -0
- package/plugin/skills/iot/edge-computing/SKILL.md +369 -0
- package/plugin/skills/iot/industrial-protocols/SKILL.md +438 -0
- package/plugin/skills/iot/mqtt-deep/SKILL.md +418 -0
- package/plugin/skills/iot/ota-updates/SKILL.md +426 -0
- package/plugin/skills/microservices/api-gateway-patterns/SKILL.md +201 -0
- package/plugin/skills/microservices/circuit-breaker-patterns/SKILL.md +246 -0
- package/plugin/skills/microservices/contract-testing/SKILL.md +284 -0
- package/plugin/skills/microservices/distributed-tracing/SKILL.md +246 -0
- package/plugin/skills/microservices/service-discovery/SKILL.md +304 -0
- package/plugin/skills/microservices/service-mesh/SKILL.md +181 -0
- package/plugin/skills/mobile-advanced/mobile-ci-cd/SKILL.md +407 -0
- package/plugin/skills/mobile-advanced/mobile-security/SKILL.md +403 -0
- package/plugin/skills/mobile-advanced/offline-first/SKILL.md +473 -0
- package/plugin/skills/mobile-advanced/push-notifications/SKILL.md +494 -0
- package/plugin/skills/mobile-advanced/react-native-deep/SKILL.md +374 -0
- package/plugin/skills/simulation/numerical-methods/SKILL.md +434 -0
- package/plugin/skills/simulation/parallel-computing/SKILL.md +382 -0
- package/plugin/skills/simulation/physics-engines/SKILL.md +377 -0
- package/plugin/skills/simulation/validation-verification/SKILL.md +479 -0
- package/plugin/skills/simulation/visualization-scientific/SKILL.md +365 -0
- package/plugin/stdrules/ALIGNMENT_PRINCIPLE.md +240 -0
- package/plugin/workflows/ai-engineering/agent-development.md +3 -3
- package/plugin/workflows/ai-engineering/fine-tuning.md +3 -3
- package/plugin/workflows/ai-engineering/model-evaluation.md +3 -3
- package/plugin/workflows/ai-engineering/prompt-engineering.md +2 -2
- package/plugin/workflows/ai-engineering/rag-development.md +4 -4
- package/plugin/workflows/ai-ml/data-pipeline.md +188 -0
- package/plugin/workflows/ai-ml/experiment-cycle.md +203 -0
- package/plugin/workflows/ai-ml/feature-engineering.md +208 -0
- package/plugin/workflows/ai-ml/model-deployment.md +199 -0
- package/plugin/workflows/ai-ml/monitoring-setup.md +227 -0
- package/plugin/workflows/api/api-design.md +1 -1
- package/plugin/workflows/api/api-testing.md +2 -2
- package/plugin/workflows/content/technical-docs.md +1 -1
- package/plugin/workflows/database/migration.md +1 -1
- package/plugin/workflows/database/optimization.md +1 -1
- package/plugin/workflows/database/schema-design.md +3 -3
- package/plugin/workflows/development/bug-fix.md +3 -3
- package/plugin/workflows/development/code-review.md +2 -1
- package/plugin/workflows/development/feature.md +3 -3
- package/plugin/workflows/development/refactor.md +2 -2
- package/plugin/workflows/event-driven/consumer-groups.md +190 -0
- package/plugin/workflows/event-driven/event-storming.md +172 -0
- package/plugin/workflows/event-driven/replay-testing.md +186 -0
- package/plugin/workflows/event-driven/saga-implementation.md +206 -0
- package/plugin/workflows/event-driven/schema-evolution.md +173 -0
- package/plugin/workflows/fullstack/authentication.md +4 -4
- package/plugin/workflows/fullstack/full-feature.md +4 -4
- package/plugin/workflows/game-dev/content-pipeline.md +218 -0
- package/plugin/workflows/game-dev/platform-submission.md +263 -0
- package/plugin/workflows/game-dev/playtesting.md +237 -0
- package/plugin/workflows/game-dev/prototype-to-production.md +205 -0
- package/plugin/workflows/microservices/contract-first.md +151 -0
- package/plugin/workflows/microservices/distributed-tracing.md +166 -0
- package/plugin/workflows/microservices/domain-decomposition.md +123 -0
- package/plugin/workflows/microservices/integration-testing.md +149 -0
- package/plugin/workflows/microservices/service-mesh-setup.md +153 -0
- package/plugin/workflows/microservices/service-scaffolding.md +151 -0
- package/plugin/workflows/omega/1000x-innovation.md +2 -2
- package/plugin/workflows/omega/100x-architecture.md +2 -2
- package/plugin/workflows/omega/10x-improvement.md +2 -2
- package/plugin/workflows/quality/performance-optimization.md +2 -2
- package/plugin/workflows/research/best-practices.md +1 -1
- package/plugin/workflows/research/technology-research.md +1 -1
- package/plugin/workflows/security/penetration-testing.md +3 -3
- package/plugin/workflows/security/security-audit.md +3 -3
- package/plugin/workflows/sprint/sprint-execution.md +2 -2
- package/plugin/workflows/sprint/sprint-retrospective.md +1 -1
- package/plugin/workflows/sprint/sprint-setup.md +1 -1
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
# Offline-First
|
|
2
|
+
|
|
3
|
+
Local-first architecture, data synchronization, conflict resolution, and offline data persistence strategies.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Offline-first design ensures applications work seamlessly without network connectivity, syncing data when connections are restored.
|
|
8
|
+
|
|
9
|
+
## Core Concepts
|
|
10
|
+
|
|
11
|
+
### Offline-First Principles
|
|
12
|
+
- **Local First**: Data stored locally before remote
|
|
13
|
+
- **Background Sync**: Automatic synchronization
|
|
14
|
+
- **Conflict Resolution**: Handle concurrent edits
|
|
15
|
+
- **Optimistic UI**: Assume operations succeed
|
|
16
|
+
|
|
17
|
+
### Sync Strategies
|
|
18
|
+
- **Full Sync**: Download complete dataset
|
|
19
|
+
- **Incremental Sync**: Only changes since last sync
|
|
20
|
+
- **Real-time Sync**: Continuous bidirectional
|
|
21
|
+
- **Manual Sync**: User-triggered
|
|
22
|
+
|
|
23
|
+
## Local Database
|
|
24
|
+
|
|
25
|
+
### WatermelonDB (React Native)
|
|
26
|
+
```typescript
|
|
27
|
+
import { Database, Model, Q } from '@nozbe/watermelondb';
|
|
28
|
+
import { field, date, children, relation } from '@nozbe/watermelondb/decorators';
|
|
29
|
+
import SQLiteAdapter from '@nozbe/watermelondb/adapters/sqlite';
|
|
30
|
+
|
|
31
|
+
// Schema
|
|
32
|
+
const schema = appSchema({
|
|
33
|
+
version: 1,
|
|
34
|
+
tables: [
|
|
35
|
+
tableSchema({
|
|
36
|
+
name: 'tasks',
|
|
37
|
+
columns: [
|
|
38
|
+
{ name: 'title', type: 'string' },
|
|
39
|
+
{ name: 'description', type: 'string', isOptional: true },
|
|
40
|
+
{ name: 'is_completed', type: 'boolean' },
|
|
41
|
+
{ name: 'project_id', type: 'string', isIndexed: true },
|
|
42
|
+
{ name: 'created_at', type: 'number' },
|
|
43
|
+
{ name: 'updated_at', type: 'number' },
|
|
44
|
+
{ name: 'synced_at', type: 'number', isOptional: true },
|
|
45
|
+
{ name: 'is_dirty', type: 'boolean' }
|
|
46
|
+
]
|
|
47
|
+
}),
|
|
48
|
+
tableSchema({
|
|
49
|
+
name: 'projects',
|
|
50
|
+
columns: [
|
|
51
|
+
{ name: 'name', type: 'string' },
|
|
52
|
+
{ name: 'color', type: 'string' },
|
|
53
|
+
{ name: 'synced_at', type: 'number', isOptional: true }
|
|
54
|
+
]
|
|
55
|
+
})
|
|
56
|
+
]
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Models
|
|
60
|
+
class Task extends Model {
|
|
61
|
+
static table = 'tasks';
|
|
62
|
+
static associations = {
|
|
63
|
+
projects: { type: 'belongs_to', key: 'project_id' }
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
@field('title') title!: string;
|
|
67
|
+
@field('description') description?: string;
|
|
68
|
+
@field('is_completed') isCompleted!: boolean;
|
|
69
|
+
@field('is_dirty') isDirty!: boolean;
|
|
70
|
+
@date('created_at') createdAt!: Date;
|
|
71
|
+
@date('updated_at') updatedAt!: Date;
|
|
72
|
+
@date('synced_at') syncedAt?: Date;
|
|
73
|
+
@relation('projects', 'project_id') project!: Project;
|
|
74
|
+
|
|
75
|
+
async markComplete() {
|
|
76
|
+
await this.update(task => {
|
|
77
|
+
task.isCompleted = true;
|
|
78
|
+
task.isDirty = true;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Database setup
|
|
84
|
+
const adapter = new SQLiteAdapter({
|
|
85
|
+
schema,
|
|
86
|
+
migrations: [],
|
|
87
|
+
jsi: true,
|
|
88
|
+
onSetUpError: error => {
|
|
89
|
+
console.error('Database setup failed:', error);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const database = new Database({
|
|
94
|
+
adapter,
|
|
95
|
+
modelClasses: [Task, Project]
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Queries and Operations
|
|
100
|
+
```typescript
|
|
101
|
+
class TaskRepository {
|
|
102
|
+
private collection = database.get<Task>('tasks');
|
|
103
|
+
|
|
104
|
+
async create(data: TaskInput): Promise<Task> {
|
|
105
|
+
return await database.write(async () => {
|
|
106
|
+
return await this.collection.create(task => {
|
|
107
|
+
task.title = data.title;
|
|
108
|
+
task.description = data.description;
|
|
109
|
+
task.isCompleted = false;
|
|
110
|
+
task.isDirty = true;
|
|
111
|
+
task.createdAt = new Date();
|
|
112
|
+
task.updatedAt = new Date();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async getByProject(projectId: string): Promise<Task[]> {
|
|
118
|
+
return await this.collection
|
|
119
|
+
.query(Q.where('project_id', projectId))
|
|
120
|
+
.fetch();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async getPendingSync(): Promise<Task[]> {
|
|
124
|
+
return await this.collection
|
|
125
|
+
.query(Q.where('is_dirty', true))
|
|
126
|
+
.fetch();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async markSynced(task: Task): Promise<void> {
|
|
130
|
+
await database.write(async () => {
|
|
131
|
+
await task.update(t => {
|
|
132
|
+
t.isDirty = false;
|
|
133
|
+
t.syncedAt = new Date();
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Sync Engine
|
|
141
|
+
|
|
142
|
+
### Bidirectional Sync
|
|
143
|
+
```typescript
|
|
144
|
+
interface SyncResult {
|
|
145
|
+
pushed: number;
|
|
146
|
+
pulled: number;
|
|
147
|
+
conflicts: ConflictRecord[];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
class SyncEngine {
|
|
151
|
+
private api: ApiClient;
|
|
152
|
+
private db: Database;
|
|
153
|
+
private lastSyncTimestamp: number = 0;
|
|
154
|
+
|
|
155
|
+
async sync(): Promise<SyncResult> {
|
|
156
|
+
const result: SyncResult = { pushed: 0, pulled: 0, conflicts: [] };
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
// 1. Push local changes
|
|
160
|
+
const localChanges = await this.getLocalChanges();
|
|
161
|
+
const pushResult = await this.pushChanges(localChanges);
|
|
162
|
+
result.pushed = pushResult.success.length;
|
|
163
|
+
result.conflicts.push(...pushResult.conflicts);
|
|
164
|
+
|
|
165
|
+
// 2. Pull remote changes
|
|
166
|
+
const remoteChanges = await this.api.getChanges(this.lastSyncTimestamp);
|
|
167
|
+
const pullResult = await this.applyRemoteChanges(remoteChanges);
|
|
168
|
+
result.pulled = pullResult.applied;
|
|
169
|
+
result.conflicts.push(...pullResult.conflicts);
|
|
170
|
+
|
|
171
|
+
// 3. Update sync timestamp
|
|
172
|
+
this.lastSyncTimestamp = Date.now();
|
|
173
|
+
await this.saveSyncState();
|
|
174
|
+
|
|
175
|
+
return result;
|
|
176
|
+
} catch (error) {
|
|
177
|
+
console.error('Sync failed:', error);
|
|
178
|
+
throw new SyncError('Sync failed', error);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private async getLocalChanges(): Promise<Change[]> {
|
|
183
|
+
const changes: Change[] = [];
|
|
184
|
+
|
|
185
|
+
for (const table of ['tasks', 'projects']) {
|
|
186
|
+
const dirtyRecords = await this.db
|
|
187
|
+
.get(table)
|
|
188
|
+
.query(Q.where('is_dirty', true))
|
|
189
|
+
.fetch();
|
|
190
|
+
|
|
191
|
+
for (const record of dirtyRecords) {
|
|
192
|
+
changes.push({
|
|
193
|
+
table,
|
|
194
|
+
id: record.id,
|
|
195
|
+
operation: record._raw._status === 'created' ? 'create' : 'update',
|
|
196
|
+
data: record._raw,
|
|
197
|
+
timestamp: record.updatedAt.getTime()
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return changes;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private async pushChanges(changes: Change[]): Promise<PushResult> {
|
|
206
|
+
const result = await this.api.pushChanges(changes);
|
|
207
|
+
|
|
208
|
+
// Mark successfully synced records
|
|
209
|
+
await this.db.write(async () => {
|
|
210
|
+
for (const success of result.success) {
|
|
211
|
+
const record = await this.db.get(success.table).find(success.id);
|
|
212
|
+
await record.update(r => {
|
|
213
|
+
r.isDirty = false;
|
|
214
|
+
r.syncedAt = new Date();
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private async applyRemoteChanges(changes: RemoteChange[]): Promise<PullResult> {
|
|
223
|
+
const conflicts: ConflictRecord[] = [];
|
|
224
|
+
let applied = 0;
|
|
225
|
+
|
|
226
|
+
await this.db.write(async () => {
|
|
227
|
+
for (const change of changes) {
|
|
228
|
+
const collection = this.db.get(change.table);
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
const existing = await collection.find(change.id).catch(() => null);
|
|
232
|
+
|
|
233
|
+
if (existing && existing.isDirty) {
|
|
234
|
+
// Conflict: local and remote both modified
|
|
235
|
+
const resolution = await this.resolveConflict(existing, change);
|
|
236
|
+
conflicts.push({ local: existing._raw, remote: change, resolution });
|
|
237
|
+
|
|
238
|
+
if (resolution === 'remote') {
|
|
239
|
+
await existing.update(r => Object.assign(r, change.data));
|
|
240
|
+
}
|
|
241
|
+
} else if (existing) {
|
|
242
|
+
// Update existing
|
|
243
|
+
await existing.update(r => Object.assign(r, change.data));
|
|
244
|
+
} else {
|
|
245
|
+
// Create new
|
|
246
|
+
await collection.create(r => Object.assign(r, change.data));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
applied++;
|
|
250
|
+
} catch (error) {
|
|
251
|
+
console.error(`Failed to apply change ${change.id}:`, error);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
return { applied, conflicts };
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Conflict Resolution
|
|
262
|
+
|
|
263
|
+
### Resolution Strategies
|
|
264
|
+
```typescript
|
|
265
|
+
type ConflictStrategy = 'last-write-wins' | 'client-wins' | 'server-wins' | 'merge' | 'manual';
|
|
266
|
+
|
|
267
|
+
class ConflictResolver {
|
|
268
|
+
private strategy: ConflictStrategy;
|
|
269
|
+
|
|
270
|
+
constructor(strategy: ConflictStrategy = 'last-write-wins') {
|
|
271
|
+
this.strategy = strategy;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
resolve<T extends Record<string, any>>(
|
|
275
|
+
local: T,
|
|
276
|
+
remote: T,
|
|
277
|
+
base?: T
|
|
278
|
+
): { result: T; strategy: string } {
|
|
279
|
+
switch (this.strategy) {
|
|
280
|
+
case 'last-write-wins':
|
|
281
|
+
return this.lastWriteWins(local, remote);
|
|
282
|
+
|
|
283
|
+
case 'client-wins':
|
|
284
|
+
return { result: local, strategy: 'client-wins' };
|
|
285
|
+
|
|
286
|
+
case 'server-wins':
|
|
287
|
+
return { result: remote, strategy: 'server-wins' };
|
|
288
|
+
|
|
289
|
+
case 'merge':
|
|
290
|
+
return this.mergeFields(local, remote, base);
|
|
291
|
+
|
|
292
|
+
case 'manual':
|
|
293
|
+
throw new ManualResolutionRequired(local, remote);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private lastWriteWins<T extends { updatedAt: number }>(
|
|
298
|
+
local: T,
|
|
299
|
+
remote: T
|
|
300
|
+
): { result: T; strategy: string } {
|
|
301
|
+
return {
|
|
302
|
+
result: local.updatedAt > remote.updatedAt ? local : remote,
|
|
303
|
+
strategy: 'last-write-wins'
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private mergeFields<T extends Record<string, any>>(
|
|
308
|
+
local: T,
|
|
309
|
+
remote: T,
|
|
310
|
+
base?: T
|
|
311
|
+
): { result: T; strategy: string } {
|
|
312
|
+
const result = { ...remote };
|
|
313
|
+
|
|
314
|
+
for (const key of Object.keys(local)) {
|
|
315
|
+
const localValue = local[key];
|
|
316
|
+
const remoteValue = remote[key];
|
|
317
|
+
const baseValue = base?.[key];
|
|
318
|
+
|
|
319
|
+
// If local changed and remote didn't, use local
|
|
320
|
+
if (localValue !== baseValue && remoteValue === baseValue) {
|
|
321
|
+
result[key] = localValue;
|
|
322
|
+
}
|
|
323
|
+
// If both changed to same value, use either
|
|
324
|
+
else if (localValue === remoteValue) {
|
|
325
|
+
result[key] = localValue;
|
|
326
|
+
}
|
|
327
|
+
// If both changed to different values, use remote (or mark conflict)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return { result: result as T, strategy: 'merge' };
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## Network Detection
|
|
336
|
+
|
|
337
|
+
### Connection Monitoring
|
|
338
|
+
```typescript
|
|
339
|
+
import NetInfo, { NetInfoState } from '@react-native-community/netinfo';
|
|
340
|
+
|
|
341
|
+
class NetworkMonitor {
|
|
342
|
+
private listeners: Set<(isConnected: boolean) => void> = new Set();
|
|
343
|
+
private isConnected: boolean = true;
|
|
344
|
+
private unsubscribe?: () => void;
|
|
345
|
+
|
|
346
|
+
start(): void {
|
|
347
|
+
this.unsubscribe = NetInfo.addEventListener(state => {
|
|
348
|
+
this.handleStateChange(state);
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
stop(): void {
|
|
353
|
+
this.unsubscribe?.();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
private handleStateChange(state: NetInfoState): void {
|
|
357
|
+
const wasConnected = this.isConnected;
|
|
358
|
+
this.isConnected = state.isConnected ?? false;
|
|
359
|
+
|
|
360
|
+
if (!wasConnected && this.isConnected) {
|
|
361
|
+
// Back online - trigger sync
|
|
362
|
+
this.notifyListeners(true);
|
|
363
|
+
this.triggerSync();
|
|
364
|
+
} else if (wasConnected && !this.isConnected) {
|
|
365
|
+
this.notifyListeners(false);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
private async triggerSync(): Promise<void> {
|
|
370
|
+
try {
|
|
371
|
+
await syncEngine.sync();
|
|
372
|
+
} catch (error) {
|
|
373
|
+
console.error('Auto-sync failed:', error);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
onConnectionChange(callback: (isConnected: boolean) => void): () => void {
|
|
378
|
+
this.listeners.add(callback);
|
|
379
|
+
return () => this.listeners.delete(callback);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
private notifyListeners(isConnected: boolean): void {
|
|
383
|
+
this.listeners.forEach(listener => listener(isConnected));
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
## Optimistic Updates
|
|
389
|
+
|
|
390
|
+
### React Hook
|
|
391
|
+
```typescript
|
|
392
|
+
function useOptimisticMutation<T, R>(
|
|
393
|
+
mutationFn: (data: T) => Promise<R>,
|
|
394
|
+
options: {
|
|
395
|
+
onOptimisticUpdate: (data: T) => void;
|
|
396
|
+
onSuccess?: (result: R) => void;
|
|
397
|
+
onError?: (error: Error, data: T) => void;
|
|
398
|
+
onRollback: (data: T) => void;
|
|
399
|
+
}
|
|
400
|
+
) {
|
|
401
|
+
const [isPending, setIsPending] = useState(false);
|
|
402
|
+
|
|
403
|
+
const mutate = useCallback(async (data: T) => {
|
|
404
|
+
setIsPending(true);
|
|
405
|
+
|
|
406
|
+
// Optimistic update
|
|
407
|
+
options.onOptimisticUpdate(data);
|
|
408
|
+
|
|
409
|
+
try {
|
|
410
|
+
const result = await mutationFn(data);
|
|
411
|
+
options.onSuccess?.(result);
|
|
412
|
+
return result;
|
|
413
|
+
} catch (error) {
|
|
414
|
+
// Rollback on failure
|
|
415
|
+
options.onRollback(data);
|
|
416
|
+
options.onError?.(error as Error, data);
|
|
417
|
+
throw error;
|
|
418
|
+
} finally {
|
|
419
|
+
setIsPending(false);
|
|
420
|
+
}
|
|
421
|
+
}, [mutationFn, options]);
|
|
422
|
+
|
|
423
|
+
return { mutate, isPending };
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Usage
|
|
427
|
+
const { mutate: completeTask } = useOptimisticMutation(
|
|
428
|
+
(task: Task) => api.updateTask(task.id, { completed: true }),
|
|
429
|
+
{
|
|
430
|
+
onOptimisticUpdate: (task) => {
|
|
431
|
+
setTasks(prev => prev.map(t =>
|
|
432
|
+
t.id === task.id ? { ...t, completed: true } : t
|
|
433
|
+
));
|
|
434
|
+
},
|
|
435
|
+
onRollback: (task) => {
|
|
436
|
+
setTasks(prev => prev.map(t =>
|
|
437
|
+
t.id === task.id ? { ...t, completed: false } : t
|
|
438
|
+
));
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
);
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
## Best Practices
|
|
445
|
+
|
|
446
|
+
1. **Queue Operations**: Store pending ops for retry
|
|
447
|
+
2. **Idempotent Sync**: Handle duplicate syncs safely
|
|
448
|
+
3. **Conflict UI**: Show conflicts to users clearly
|
|
449
|
+
4. **Partial Sync**: Don't require full dataset
|
|
450
|
+
5. **Background Sync**: Sync when app not active
|
|
451
|
+
|
|
452
|
+
## Anti-Patterns
|
|
453
|
+
|
|
454
|
+
- Blocking UI during sync
|
|
455
|
+
- No conflict handling
|
|
456
|
+
- Full dataset sync every time
|
|
457
|
+
- Ignoring sync failures
|
|
458
|
+
- No offline indicator
|
|
459
|
+
|
|
460
|
+
## When to Use
|
|
461
|
+
|
|
462
|
+
- Field/remote work apps
|
|
463
|
+
- Note-taking apps
|
|
464
|
+
- Collaborative tools
|
|
465
|
+
- Apps for unreliable networks
|
|
466
|
+
- Real-time collaboration
|
|
467
|
+
|
|
468
|
+
## When NOT to Use
|
|
469
|
+
|
|
470
|
+
- Real-time only features
|
|
471
|
+
- Always-online requirements
|
|
472
|
+
- Simple read-only apps
|
|
473
|
+
- No data persistence needed
|