用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/ffsshhttiikk/opencode-agents-skills --skill offline-first命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
基于 SOC 职业分类
| name | offline-first |
| description | Mobile architecture designed for offline functionality |
| category | mobile-development |
| difficulty | advanced |
| tags | ["mobile","offline","sync","local-storage"] |
| author | OpenCode Community |
| version | 1 |
| last_updated | 2024-01-15T00:00:00.000Z |
I am Offline-First Architecture, a design approach that prioritizes application functionality regardless of network connectivity. I assume network availability is the exception rather than the norm, designing applications to work seamlessly offline with data stored locally. I synchronize local changes with remote servers when connectivity is restored using conflict resolution strategies. I implement various storage mechanisms including SQLite, Realm, IndexedDB, and file systems. I handle background sync, push notifications for sync completion, and merge strategies for concurrent edits. I optimize for low-bandwidth scenarios, progressive data loading, and graceful degradation. I enable users to be productive during network outages while ensuring data consistency upon reconnection.
Local Storage: Primary data store that persists data locally on the device.
Sync Queue: FIFO queue managing pending changes awaiting synchronization.
Conflict Resolution: Strategies for merging concurrent local and remote edits.
Background Sync: Automatic synchronization when app is not active.
Data Versioning: Tracking data states with timestamps or version numbers.
Optimistic UI: Immediate user feedback assuming successful operations.
Delta Sync: Transferring only changed data rather than full datasets.
Connectivity Awareness: Real-time detection of network state changes.
// offline-store.ts
abstract class OfflineStore<T> {
protected abstract createLocalStorage(): LocalStorage
protected abstract createSyncQueue(): SyncQueue
protected abstract createConflictResolver(): ConflictResolver<T>
async save(entity: T): Promise<void> {
const localStorage = this.createLocalStorage()
const syncQueue = this.createSyncQueue()
// Optimistically save to local storage
await localStorage.save(entity)
// Queue for sync
await syncQueue.enqueue({
type: 'UPSERT',
entity,
timestamp: Date.now(),
version: entity.version || 0
})
}
async delete(id: string): Promise<void> {
= .()
syncQueue = .()
.(id)
syncQueue.({
: ,
: id,
: .()
})
}
(: ): <T | > {
= .()
.(id)
}
(): <T[]> {
= .()
.()
}
(): <> {
= .()
syncQueue = .()
conflictResolver = .()
pendingChanges = syncQueue.()
: = { : , : , : [] }
( change pendingChanges) {
{
remoteEntity = .(change.)
(remoteEntity && change. === ) {
conflict = conflictResolver.(
change.,
remoteEntity
)
(conflict. === ) {
mergedEntity = conflict.!
.(mergedEntity)
.(mergedEntity)
} (conflict. === ) {
.(remoteEntity)
syncQueue.(change.)
}
syncResult..(conflict)
} {
.(change.)
syncQueue.(change.)
}
syncResult.++
} (error) {
syncResult.++
syncQueue.(change., error)
}
}
syncResult
}
(: ): <T | >
(: T): <>
}
{
:
: |
?: T
?:
:
:
:
: | |
}
{
:
:
: <T>[]
}
<T> {
: T
: T
: | |
?: T
}
// database.ts
import SQLite from 'react-native-sqlite-storage'
const DATABASE_NAME = 'offline_app.db'
interface Database {
open(): Promise<void>
close(): Promise<void>
execute(sql: string, params?: any[]): Promise<any>
transaction(fn: (tx: Transaction) => Promise<void>): Promise<void>
}
interface Transaction {
executeSql(sql: string, params?: any[]): Promise<{ rows: any[], insertId: number }>
}
class SQLiteDatabase implements Database {
private db: SQLite.SQLiteDatabase | null =
(): <> {
. = .({
: ,
: ,
: ,
})
.()
}
(): <> {
.()
.()
.()
}
(: , ?: []): <> {
(!.) ()
( {
.!.(sql, params).( {
({ : results., : results. })
}).(reject)
})
}
(: <>): <> {
(!.) ()
( {
.!.( {
({
: {
( {
tx.(sql, params, {
({ : results., : results. })
}).(reject)
})
}
})
}).(resolve).(reject)
})
}
(): <> {
(.) {
.(.)
. =
}
}
}
{
:
() {
. = db
}
(: ): <> {
..(
,
[user., user., user.]
)
}
(: ): <> {
..(
,
[user., user., user.]
)
}
(: ): <> {
..(
,
[id]
)
}
(: ): < | > {
result = ..(
,
[id]
)
result.. > ? result..() :
}
(): <[]> {
result = ..(
)
result..()
}
(): <[]> {
result = ..(
,
[]
)
result..()
}
(: ): <> {
..(
,
[, id]
)
}
}
// connectivity.ts
import { NetInfoState, NetInfoSubscription, addEventListener } from '@react-native-netinfo/ios'
type ConnectivityChangeListener = (isConnected: boolean, details: NetInfoState) => void
class ConnectivityMonitor {
private listeners: Set<ConnectivityChangeListener> = new Set()
private subscription: NetInfoSubscription | null = null
private currentState: NetInfoState | null = null
startMonitoring(): void {
this.subscription = addEventListener((state) => {
this.currentState = state
const isConnected = state.isConnected
this.listeners.forEach(listener => listener(isConnected, state))
})
}
stopMonitoring(): {
(.) {
.()
. =
}
}
(: ): {
..(listener)
..(listener)
}
(): | {
.
}
(): {
.?. ||
}
(): {
.?. ||
}
(): {
.?. ===
}
(): {
.?. ===
}
}
(): { : ; : } {
[connectivity, setConnectivity] = ({ : , : })
( {
monitor = ()
unsubscribe = monitor.( {
({
isConnected,
: state.
})
})
monitor.()
{
()
monitor.()
}
}, [])
connectivity
}
// background-sync.ts
import { AppState } from 'react-native'
import BackgroundTask from 'react-native-background-fetch'
class BackgroundSyncManager {
private isSyncing = false
private pendingSync = false
async initialize(): Promise<void> {
const status = await BackgroundTask.configure({
minimumFetchInterval: 15,
stopOnTerminate: false,
startOnBoot: true,
requiredNetworkType: BackgroundTask.NETWORK_TYPE_ANY,
})
if (status === BackgroundTask.STATUS_AVAILABLE) {
await BackgroundTask.registerBackgroundTask(
'sync-task',
this.performSync.bind(this)
)
}
}
async performSync(): Promise<void> {
if (this.) {
. =
}
. =
{
connectivity = ()
(!connectivity.()) {
}
syncManager = ()
result = syncManager.()
.(, result)
(result. > ) {
.(result.)
}
} (error) {
.(, error)
} {
. =
(.) {
. =
.()
}
}
.()
}
(: ): <> {
delay = .( * .(, failedCount), )
( (resolve, delay))
.(
,
..()
)
}
}
{
:
:
:
(): <> {
: = { : , : , : }
userResult = .()
result. += userResult.
result. += userResult.
result. += userResult.
postResult = .()
result. += postResult.
result. += postResult.
result. += postResult.
result
}
(): <> {
pendingUsers = ..()
: = { : , : , : }
( user pendingUsers) {
{
remoteUser = .(user.)
(remoteUser) {
conflict = ..(user, remoteUser)
(conflict. === ) {
..(conflict.!)
.(conflict.!)
} (conflict. === ) {
..(remoteUser)
}
result.++
} {
.(user)
}
..(user.)
result.++
} (error) {
..(user.)
result.++
}
}
result
}
}
// conflict-resolver.ts
// Timestamp-based resolution
class LastWriteWinsResolver<T extends { updatedAt: number }> {
resolve(local: T, remote: T): ConflictResolution<T> {
if (local.updatedAt >= remote.updatedAt) {
return { resolution: 'LOCAL', entity: local }
} else {
return { resolution: 'REMOTE', entity: remote }
}
}
}
// Field-level merge resolver
class FieldMergeResolver<T extends { version: number }> {
resolve(local: T, remote: T, mergeFields: (local: T, remote: T) => T): ConflictResolution<T> {
const localVersion = local.version || 0
const remoteVersion = remote.version || 0
if (localVersion > remoteVersion) {
return { resolution: 'LOCAL', entity: local }
} else if (remoteVersion > localVersion) {
{ : , : remote }
} {
merged = (local, remote)
merged. = .(localVersion, remoteVersion) +
{ : , : merged }
}
}
}
{
(: , : ): <> {
mergeFields = (: , : ): {
{
: local.,
: local. >= remote. ? local. : remote.,
: local. || remote.,
: .(local., remote.) + ,
: .()
}
}
resolver = <>()
resolver.(local, remote, mergeFields)
}
}
<T { : ; : }> {
(: T, : T, : T | ): <T> {
hasBase = base !==
baseVersion = base?. ||
localChanged = local. !== baseVersion
remoteChanged = remote. !== baseVersion
(!localChanged) {
{ : , : remote }
} (!remoteChanged) {
{ : , : local }
} (hasBase && .(local) === .(base)) {
{ : , : remote }
} (hasBase && .(remote) === .(base)) {
{ : , : local }
} {
{
: ,
: { ...remote, : .(local., remote.) + }
}
}
}
}