npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-cloud-sync

v0.4.0

Published

Sync React Native app data across iCloud key-value store, CloudKit, iCloud Drive and Google Drive behind one API - offline-first with automatic retry, real-time updates and optional end-to-end encryption. iOS, Android, Web, old and new architecture

Readme

Support my work

cryptoc - my crypto portfolio app. Your coins on the home screen, lock screen and watch face. iPhone, iPad, Mac, Apple Watch, Android, Android tablet and Wear OS.

  • Portfolio with average buy price and 24h / 180-day / all-time P&L
  • Widgets in three sizes, refreshed in the background - most days you never open the app
  • Price alerts on 5,000+ coins, delivered while the app is closed
  • No account, no email, no exchange API keys, no ads. Your holdings never reach a server - they sync through your own iCloud or Google Drive
  • Free for 3 holdings, and that is not a trial timer

This is the app the library was built for. Downloading it pays for the time that goes into maintaining it.

✨ Features

  • ☁️ Four providers behind one API: NSUbiquitousKeyValueStore (Apple's small key-value iCloud store), CloudKit records, iCloud Drive documents and Google Drive appDataFolder. Use them directly or through one facade.
  • 🔄 Auto-sync between iOS and Android in both directions: mirror writes to iCloud and Drive together, resolve reads back whichever copy is newest, so either platform can be where the user started.
  • 🍏 CloudKit reaches Android and web too, via CloudKit Web Services, against the same private database your iOS app uses.
  • 📂 icloudDocuments writes into the user's own iCloud Drive, so the files show up in Files.app.
  • 🚨 Every failure is a typed rejection (ERR_NOT_SIGNED_IN, ERR_QUOTA_EXCEEDED, ERR_RATE_LIMITED with retryAfterMs, ...); null means only "key doesn't exist".
  • 👤 All five CKAccountStatus values surface as-is, plus onAccountChange with identityChanged, which drops the previous account's caches and queued writes.
  • 🔔 onRemoteChange fires on every provider and the facade, including Google Drive via its change cursor.
  • 📦 Small values go to the key-value store, larger ones to a CKRecord field, binary to a CKAsset or a resumable Drive upload. The size check picks the target.
  • 🔁 Retryable failures queue into a durable outbox: backoff honours retry hints, auto-drains on foreground, bounded, never overwrites a newer write.
  • 🧺 multiGet/multiSet/multiRemove/clear batch for real, one request per provider.
  • 🪝 React hooks from /hooks: useCloudItem, useCloudItems, useCloudCollection, useAccountStatus, usePendingWrites. They drop stale responses and never setState after unmount.
  • 🔐 cloudKitEncrypted uses CloudKit's own encryptedValues, so only ciphertext leaves the device; every other provider has a codec seam for your own cipher.
  • 🧪 An in-memory provider with fault injection, plus the native mock, both exported (/testing, /jest-mock), so every failure path is testable in Jest.
  • ⚙️ React Native 0.71 through 0.86+, old and new architecture, with the #ifdef bridge for the legacy one.

💡 Why?

Cloud storage in React Native is fragmented into single-provider wrappers repeating the same defects: two ship a setItem that reports a failed write as a success (one checks the wrong error variable, the other discards the result entirely), and a third flattens five iCloud account states into one boolean.

A catch { return null } makes "not signed in", "offline", "out of storage" and "no such key" indistinguishable. The app can't tell the user anything useful, or decide whether to retry.

This library was built error contract first, providers second.

📚 Upstream documentation

This package is a wrapper. When something behaves unexpectedly, the answer is usually in Apple's or Google's docs.

Apple

| | | |---|---| | NSUbiquitousKeyValueStore | The iCloud key-value store; 1 MB / 1024-key limits | | CloudKit · CKDatabase · CKRecord | Records in the user's private database | | CKAsset · CKRecordZone | Binary assets and custom zones | | CKAccountStatus | The five account states this package surfaces verbatim | | CloudKit Web Services | The REST API behind the Android and web paths | | Authentication · Data size limits · Error codes | Worth reading before shipping CloudKit on Android | | iCloud entitlements | The keys the config plugin writes | | CloudKit Console | Where containers, schemas, API tokens and the sign-in callback live |

Google

| | | |---|---| | The appDataFolder | The hidden per-app folder this package stores into | | Drive files resource | The REST endpoints behind the provider | | Drive API scopes | Why the scope is drive.appdata |

⚖️ Comparison

| | this | kuatsu/cloud-storage | icloud-kit | expo-cloudkit | okwasniewski/icloud-storage | cloudkit-storage | |---|:---:|:---:|:---:|:---:|:---:|:---:| | iCloud key-value store | ✅ | ✅ | ✅ | - | ✅ | - | | CloudKit records | ✅ | - | ✅ | ✅ | - | ✅ | | Google Drive | ✅ | ✅ | - | - | - | - | | iOS | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Android | ✅ | ✅ | - | ❌ [1] | - | - | | Web | ✅ | partial [2] | - | - | - | - | | CloudKit on Android/web | ✅ | - | - | ❌ [1] | - | - | | New Architecture | ✅ | ✅ | ✅ | ✅ | ✅ | - | | Legacy Architecture | ✅ | ❌ | ✅ [3] | ✅ [3] | ✅ [3] | ✅ | | Typed error codes | ✅ | - | partial | ✅ | - | - | | 5-value account status | ✅ | boolean | boolean | ✅ | - | - | | Identity-change event | ✅ | ⚠️ [4] | - | ✅ | - | - | | Remote-change event | ✅ | ✅ | - | n/a | ❌ [5] | ✅ | | Offline write queue | ✅ | - | - | ✅ | - | - | | Size tiering | ✅ | - | - | - | - | - | | Binary / assets | ✅ [9] | ✅ | ❌ [6] | ✅ | - | - | | User-visible iCloud Drive files | ✅ | - | - | - | ✅ | - | | Native end-to-end encryption | ✅ | - | - | - | - | - | | Batch operations | ✅ | - | - | - | - | - | | React hooks | ✅ | ✅ | - | - | - | - | | Encryption seam | ✅ | - | - | - | - | - | | Exported test harness | ✅ | - | - | ⚠️ [7] | - | - | | Mac Catalyst | ✅ | - | - | ⚠️ | - | - | | Actively maintained | ✅ | ✅ | ✅ | ❌ [8] | ❌ [5] | ❌ |

[1] expo-cloudkit's README: Android throws CloudKitNotSupportedError on every call.
[2] Google Drive support is text-based only.
[3] Via the Expo Modules API; pulls in expo-modules-core.
[4] Fires before JS binds the emitter, crashing with std::bad_function_call (SIGABRT); hit three times.
[5] One version ever shipped; the change-listener PR has sat open since February.
[6] Field type is string | number | null, so it can't hold binary data.
[7] A mock factory exists, but isn't exported or documented.
[8] No commits since April; the last four releases shipped with Swift that didn't compile.
[9] CKAsset on Apple platforms; CloudKit Web Services' upload-token protocol on Android/web, capped at 15 MB by CloudKit. Use googleDriveFiles for anything bigger; it chunks and resumes the same way.

📖 Documentation

| | | |---|---| | Choosing a provider | Which one, what it costs, why to let the user pick | | iCloud key-value store | Small settings, zero friction, Apple only | | CloudKit | Records, zones, assets, and the Android/web path | | iCloud Drive | Files in the user's own Drive, visible in Files.app | | Google Drive | The always-on cross-platform backend | | The store facade | Tiering, outbox, migration, fallthrough | | Error handling | The typed contract | | Encryption | What's encrypted for you, and how to add your own | | React hooks | Binding cloud state to components | | Recipes | Backup/restore, migration, offline-first | | Testing | Fault injection without a device | | API reference | Every export | | Platform notes | Entitlements, architectures, build config | | Troubleshooting | Common problems and what they usually mean |

📋 Requirements

| | Minimum | |---|---| | React Native | 0.71 | | iOS | 15.1 | | Node | 20 |

Both architectures supported; RN 0.82 removed Legacy Architecture, so that half only matters on 0.81 and below.

Platform support per provider

| | iOS / macOS | Android | Web | |---|:---:|:---:|:---:| | icloudKV | native | - | - | | cloudKit | native | REST | REST | | cloudKitEncrypted | native | - | - | | icloudDocuments | native | - | - | | googleDrive | REST | REST | REST |

An unavailable provider rejects with ERR_UNSUPPORTED_PLATFORM instead of silently doing nothing. Choosing a provider covers the trade-offs.

📦 Installation

npx expo install react-native-cloud-sync

Add the config plugin, then rebuild:

{
  "expo": {
    "plugins": [
      ["react-native-cloud-sync", {
        "containerIdentifier": "iCloud.com.your.app"
      }]
    ]
  }
}

Bare React Native and manual ios/ entitlement keys: see Installation.

🚀 Quick start

iCloud key-value store

No sign-in and no UI; it uses the device's existing account.

import { icloudKV } from 'react-native-cloud-sync'

await icloudKV.setItem('settings/theme', 'dark')
const theme = await icloudKV.getItem('settings/theme')
// null means the key does not exist. Nothing else returns null.

icloudKV.onRemoteChange(({ keys }) => reload(keys))

Full guide

CloudKit

The same private database from iOS, Android and web.

import { cloudKit, cloudKitAssets } from 'react-native-cloud-sync'

await cloudKit.setItem('playlist', JSON.stringify(tracks))
const raw = await cloudKit.getItem('playlist')

// Anything above the 1 MB record limit goes in as a streamed CKAsset.
await cloudKitAssets.save({ recordName: 'avatar', fieldName: 'image', fileUri })

Android/web needs an Apple ID sign-in, and that token lasts up to two weeks, which suits an explicit import rather than background sync. Full guide

Sensitive data

CloudKit's own end-to-end encryption: Apple stores ciphertext and holds no key.

import { cloudKitEncrypted } from 'react-native-cloud-sync'

await cloudKitEncrypted.setItem('auth.refreshToken', token)

Apple-only: the key lives in the user's iCloud Keychain, so nothing server-side can decrypt it. Cross-platform needs the store's codec seam instead. Full guide

iCloud Drive

Files the user can open in Files.app.

import { icloudDocuments } from 'react-native-cloud-sync'

await icloudDocuments.save({ fileUri: localPath, name: 'Export 2024.csv' })

// A listed file may be a placeholder with no local bytes. fetch() downloads it.
const path = await icloudDocuments.fetch({ name: 'Export 2024.csv' })

Full guide

Google Drive

Identical behaviour on every platform, no periodic re-auth.

import { configureGoogleDrive, googleDrive } from 'react-native-cloud-sync'

configureGoogleDrive({
  getAccessToken: async () => (await GoogleSignin.getTokens()).accessToken,
})

await googleDrive.setItem('playlist.json', JSON.stringify(tracks))

Full guide

All of them at once

import { createCloudStore } from 'react-native-cloud-sync'

const store = createCloudStore({
  providers: ['icloudKV', 'googleDrive'],       // preference order
  writeMode: 'mirror',                          // write to both providers, not only the preferred one
  resolve: resolveByTimestamp('updatedAt'),     // read whichever copy is newest
  tiering: 'auto',                              // route by size
  outboxStorage: mmkvAdapter,                   // survive restarts
})

await store.setItem('playlist', json)
await store.flushOutbox()                       // on reconnect

Those two options make sync work in both directions across a mixed fleet: mirror copies to Drive so non-Apple devices can read it; resolve stops an Apple device serving a stale iCloud copy without checking Drive. Full guide

In a component

import { useCloudItem } from 'react-native-cloud-sync/hooks'

const { value, setValue, loading, error } = useCloudItem<Settings>(store, 'settings')

Re-reads on remote writes, drops stale responses, never calls setState after unmount. Full guide

Handling failures

import { isRetryable, requiresUserAction } from 'react-native-cloud-sync'

try {
  await store.setItem('k', 'v')
} catch (e) {
  if (requiresUserAction(e)) promptUser(e.code)      // signed out, out of storage
  else if (isRetryable(e)) scheduleRetry(e.retryAfterMs)
}

Full guide

🧪 Testing

import { ErrorCode } from 'react-native-cloud-sync'
import { createMemoryProvider } from 'react-native-cloud-sync/testing'

const provider = createMemoryProvider({
  faults: { setItem: { code: ErrorCode.QUOTA_EXCEEDED } },
})

provider.emitAccountChange({ status: 'available', identityChanged: true })

Signed-out, offline and account-switch paths, all in Jest without a device. Full guide

🧪 Example App

A playground covering every API, plus a live sync demo for side-by-side recording:

cd example
yarn installDevBuild:ios     # or :android
yarn start:web

Tabs: Sync (shared counter, one-tap mirror mode to iCloud+Drive), iCloud KV, CloudKit, Drive, Files (large-file backup/restore via GoogleDriveFileAdapter), Store, Faults.

🤝 Contributing

Issues and pull requests welcome. Run yarn lint, yarn typecheck and yarn test before opening one.

👥 Authors

I also build cryptoc - a crypto portfolio app with home screen, lock screen and Watch widgets, no account and no exchange API keys. It is where this library came from.

📄 License

MIT