소스 정보
- 저장소
- ffsshhttiikk/opencode-agents-skills
- 최근 소스 활동
- 2026년 2월 28일 22:53
- 감지된 SKILL.md 언어
- 영어
- 스타
- 2
- 포크
- 2
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
메뉴
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
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.) + }
}
}
}
}