From e7bf5040b99096a287842fea45185bb7e665b822 Mon Sep 17 00:00:00 2001 From: Gabriel Santana Date: Sun, 17 May 2026 23:14:01 +0100 Subject: [PATCH 01/10] docs(website): scaffold Docusaurus site and wire workspace Add website/ workspace with 35 pages across getting-started, guides, api-reference, architecture, production, migration, development and troubleshooting. Wire docs:* scripts, ignore build outputs, and add lychee config for link checking. --- .gitignore | 5 + lychee.toml | 43 + package.json | 9 +- website/babel.config.js | 3 + website/docs/api-reference/context.md | 122 + website/docs/api-reference/enums.md | 218 + website/docs/api-reference/errors.md | 139 + website/docs/api-reference/functions.md | 567 + .../docs/api-reference/hooks/useAutoSync.md | 121 + .../docs/api-reference/hooks/useConnection.md | 76 + .../api-reference/hooks/useOfflineQueue.md | 105 + .../docs/api-reference/hooks/useSyncConfig.md | 113 + .../docs/api-reference/hooks/useSyncEvents.md | 108 + .../docs/api-reference/hooks/useSyncQueue.md | 126 + .../docs/api-reference/hooks/useSyncStatus.md | 111 + website/docs/api-reference/listeners.md | 155 + website/docs/api-reference/types.md | 361 + website/docs/architecture/android-native.md | 153 + website/docs/architecture/data-flow.md | 158 + website/docs/architecture/ios-native.md | 121 + website/docs/architecture/overview.md | 115 + website/docs/development/contributing.md | 145 + website/docs/development/debugging.md | 166 + website/docs/development/testing.md | 148 + website/docs/getting-started/android-setup.md | 157 + website/docs/getting-started/installation.md | 186 + website/docs/getting-started/introduction.md | 108 + website/docs/getting-started/ios-setup.md | 171 + website/docs/getting-started/quick-start.md | 227 + website/docs/guides/background-sync.md | 185 + website/docs/guides/connectivity-detection.md | 127 + website/docs/guides/error-handling.md | 145 + website/docs/guides/idempotency.md | 154 + website/docs/guides/offline-queue.md | 174 + website/docs/guides/priority-and-ordering.md | 167 + website/docs/guides/retry-policy.md | 172 + website/docs/migration/index.md | 46 + .../docs/production/android-permissions.md | 80 + .../docs/production/ios-background-modes.md | 79 + website/docs/production/privacy-manifest.md | 85 + .../docs/production/production-checklist.md | 82 + website/docs/troubleshooting.md | 164 + website/docusaurus.config.ts | 168 + website/package.json | 47 + website/sidebars.ts | 100 + website/src/css/custom.css | 124 + website/src/pages/index.tsx | 216 + website/static/img/social-card.svg | 9 + website/static/llms-full.txt | 499 + website/static/llms.txt | 168 + website/tsconfig.json | 6 + yarn.lock | 18895 ++++++++++++---- 52 files changed, 21259 insertions(+), 4870 deletions(-) create mode 100644 lychee.toml create mode 100644 website/babel.config.js create mode 100644 website/docs/api-reference/context.md create mode 100644 website/docs/api-reference/enums.md create mode 100644 website/docs/api-reference/errors.md create mode 100644 website/docs/api-reference/functions.md create mode 100644 website/docs/api-reference/hooks/useAutoSync.md create mode 100644 website/docs/api-reference/hooks/useConnection.md create mode 100644 website/docs/api-reference/hooks/useOfflineQueue.md create mode 100644 website/docs/api-reference/hooks/useSyncConfig.md create mode 100644 website/docs/api-reference/hooks/useSyncEvents.md create mode 100644 website/docs/api-reference/hooks/useSyncQueue.md create mode 100644 website/docs/api-reference/hooks/useSyncStatus.md create mode 100644 website/docs/api-reference/listeners.md create mode 100644 website/docs/api-reference/types.md create mode 100644 website/docs/architecture/android-native.md create mode 100644 website/docs/architecture/data-flow.md create mode 100644 website/docs/architecture/ios-native.md create mode 100644 website/docs/architecture/overview.md create mode 100644 website/docs/development/contributing.md create mode 100644 website/docs/development/debugging.md create mode 100644 website/docs/development/testing.md create mode 100644 website/docs/getting-started/android-setup.md create mode 100644 website/docs/getting-started/installation.md create mode 100644 website/docs/getting-started/introduction.md create mode 100644 website/docs/getting-started/ios-setup.md create mode 100644 website/docs/getting-started/quick-start.md create mode 100644 website/docs/guides/background-sync.md create mode 100644 website/docs/guides/connectivity-detection.md create mode 100644 website/docs/guides/error-handling.md create mode 100644 website/docs/guides/idempotency.md create mode 100644 website/docs/guides/offline-queue.md create mode 100644 website/docs/guides/priority-and-ordering.md create mode 100644 website/docs/guides/retry-policy.md create mode 100644 website/docs/migration/index.md create mode 100644 website/docs/production/android-permissions.md create mode 100644 website/docs/production/ios-background-modes.md create mode 100644 website/docs/production/privacy-manifest.md create mode 100644 website/docs/production/production-checklist.md create mode 100644 website/docs/troubleshooting.md create mode 100644 website/docusaurus.config.ts create mode 100644 website/package.json create mode 100644 website/sidebars.ts create mode 100644 website/src/css/custom.css create mode 100644 website/src/pages/index.tsx create mode 100644 website/static/img/social-card.svg create mode 100644 website/static/llms-full.txt create mode 100644 website/static/llms.txt create mode 100644 website/tsconfig.json diff --git a/.gitignore b/.gitignore index 41caa7a..ff7bb03 100644 --- a/.gitignore +++ b/.gitignore @@ -93,6 +93,11 @@ nitrogen/ !android/schemas/ !android/schemas/** +# Docusaurus +website/.docusaurus +website/build +website/node_modules + # Claude .claude/ CLAUDE.md diff --git a/lychee.toml b/lychee.toml new file mode 100644 index 0000000..b1cb811 --- /dev/null +++ b/lychee.toml @@ -0,0 +1,43 @@ +# Lychee link checker config +# Docs: https://lychee.cli.rs/usage/config/ +# Used by .github/workflows/docs-link-check.yml in two passes: +# 1) --offline + --include-fragments -> fails CI on broken internal links/anchors +# 2) default (online) -> warns on broken external links (non-blocking) + +# --- Paths to exclude (vendored / generated / build output) ------------------- +exclude_path = [ + "node_modules", + "lib", + "build", + "example/node_modules", + "example/ios/Pods", + "example/ios/build", + "example/android/build", + "example/android/.gradle", + "example/android/app/build", + "android/build", + "android/.gradle", + ".yarn", + ".turbo", + "coverage", + "website/node_modules", + "website/build", + "website/.docusaurus", +] + +# --- Network behavior (only applies to the online pass) ----------------------- +max_redirects = 5 +max_retries = 2 +timeout = 20 +# 200-299 = OK, 429 = rate-limited (treat as success to reduce flake). +accept = ["200..=299", "429"] + +# --- Hosts that are chronically flaky from GitHub runners --------------------- +# Add entries here (regex) if a specific host starts producing false positives. +exclude = [ + # Example: "^https?://(www\\.)?linkedin\\.com/", +] + +# --- Link scheme filters ------------------------------------------------------ +# mailto: and tel: links are not verifiable over HTTP. +# E-mail checking is disabled by default in lychee v0.18+; no flag needed. diff --git a/package.json b/package.json index 722a186..42adba5 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,11 @@ "nitrogen": "nitrogen", "typecheck": "tsc", "lint": "eslint \"**/*.{js,ts,tsx}\"", - "test": "jest" + "test": "jest", + "docs:dev": "yarn workspace react-native-sync-provider-website start", + "docs:build": "yarn workspace react-native-sync-provider-website build", + "docs:serve": "yarn workspace react-native-sync-provider-website serve", + "docs:clear": "yarn workspace react-native-sync-provider-website clear" }, "keywords": [ "react-native", @@ -145,7 +149,8 @@ } }, "workspaces": [ - "example" + "example", + "website" ], "packageManager": "yarn@4.11.0", "react-native-builder-bob": { diff --git a/website/babel.config.js b/website/babel.config.js new file mode 100644 index 0000000..e00595d --- /dev/null +++ b/website/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: [require.resolve('@docusaurus/core/lib/babel/preset')], +}; diff --git a/website/docs/api-reference/context.md b/website/docs/api-reference/context.md new file mode 100644 index 0000000..8c067bb --- /dev/null +++ b/website/docs/api-reference/context.md @@ -0,0 +1,122 @@ +--- +sidebar_position: 3 +title: SyncProvider Context +description: Optional React context provider that owns configureSync and background sync lifecycle, exposing the active SyncOptions to descendants. +keywords: + - react-native + - sync-provider + - context + - SyncProvider + - configureSync + - provider +--- + +# `SyncProvider` Context + +Optional React context provider. Mounting it lets descendant hooks read the active [`SyncOptions`](./types.md#syncoptions) without an extra native call, and centralizes the `configureSync` / `enableBackgroundSync` lifecycle in a single place. + +```tsx +import { SyncProvider } from '@gabriel-sisjr/react-native-sync-provider'; +``` + +The provider is **optional**: every hook works without it. Use it when you have multiple consumers in the tree that all need the config, or when you want a Provider-driven pattern for swapping configs at runtime (e.g. dev vs. prod). + +--- + +## Props + +| Prop | Type | Required | Description | +|------|------|----------|-------------| +| `options` | [`SyncOptions`](./types.md#syncoptions) | Yes | Configuration applied via `configureSync` on mount and on every prop change. | +| `backgroundSync` | [`BackgroundSyncOptions`](./types.md#backgroundsyncoptions) | No | When provided, the provider calls `enableBackgroundSync` on mount and `disableBackgroundSync` on unmount. Tracked via an internal ref so duplicate enable/disable calls are skipped. | +| `children` | `ReactNode` | Yes | Subtree that receives the context. | + +--- + +## Context Value + +```ts +interface SyncProviderContextValue { + config: SyncOptions | undefined; +} +``` + +| Property | Type | Description | +|----------|------|-------------| +| `config` | `SyncOptions \| undefined` | The most recent `options` prop, after `configureSync` has resolved. `undefined` between mount and first ack. | + +[`useSyncConfig`](./hooks/useSyncConfig.md) reads from this context when it is available. + +--- + +## Lifecycle + +| Phase | Behavior | +|-------|----------| +| Mount | Calls `configureSync(options)`. If `backgroundSync` is set, calls `enableBackgroundSync(backgroundSync)`. | +| `options` prop change | Calls `configureSync(newOptions)`. | +| `backgroundSync` prop change | If a previous `backgroundSync` was active, the provider re-runs `enableBackgroundSync` with the new options. The internal ref tracks whether ownership has been claimed so unmount cleanup runs exactly once. | +| Unmount | If the provider claimed background-sync ownership, calls `disableBackgroundSync()`. Does **not** revert `configureSync` -- the engine keeps running with the last applied config. | + +--- + +## Example + +### Single-tree configuration + +```tsx +import { + SyncProvider, + SyncStrategy, + BackoffStrategy, +} from '@gabriel-sisjr/react-native-sync-provider'; + +const SYNC_OPTIONS = { + strategy: SyncStrategy.AUTOMATIC, + retryPolicy: { + maxAttempts: 5, + backoff: BackoffStrategy.EXPONENTIAL, + baseDelayMs: 1000, + maxDelayMs: 60_000, + jitter: true, + retryOnStatusCodes: [408, 425, 429, 500, 502, 503, 504], + }, + batchSize: 10, +}; + +const BACKGROUND_OPTIONS = { + minimumIntervalMs: 15 * 60 * 1000, + requiresUnmeteredNetwork: false, +}; + +export default function App() { + return ( + + + + ); +} +``` + +### Runtime config swap + +```tsx +function ConfiguredApp({ env }: { env: 'dev' | 'prod' }) { + const options = env === 'prod' ? PROD_OPTIONS : DEV_OPTIONS; + return ( + + + + ); +} +``` + +The provider re-applies `configureSync` whenever `options` changes identity, so memoize the value if it is constructed inline. + +--- + +## See also + +- [`configureSync`](./functions.md#configuresync), [`enableBackgroundSync`](./functions.md#enablebackgroundsync) -- the underlying facade calls. +- [`useSyncConfig`](./hooks/useSyncConfig.md) -- consumes the context. +- [Background sync guide](../guides/background-sync.md). diff --git a/website/docs/api-reference/enums.md b/website/docs/api-reference/enums.md new file mode 100644 index 0000000..48e57b7 --- /dev/null +++ b/website/docs/api-reference/enums.md @@ -0,0 +1,218 @@ +--- +sidebar_position: 5 +title: Enums +description: All enum definitions for @gabriel-sisjr/react-native-sync-provider — ConnectionStatus, ConnectionType, SyncStrategy, SyncPriority, BackoffStrategy, HttpMethod, and SyncEventType. +keywords: + - react-native + - sync-provider + - enums + - ConnectionStatus + - SyncPriority + - BackoffStrategy + - SyncEventType +--- + +# Enums + +All runtime enum values exported by the library. Each is exported as both a value (for runtime use) and a type (for type annotations). + +```ts +import { + ConnectionStatus, + ConnectionType, + SyncStrategy, + SyncPriority, + BackoffStrategy, + HttpMethod, + SyncEventType, +} from '@gabriel-sisjr/react-native-sync-provider'; +``` + +--- + +## `ConnectionStatus` + +High-level network status reported by the native [`ConnectivityMonitor`](../architecture/overview.md). + +```ts +enum ConnectionStatus { + CONNECTED = 'CONNECTED', + DISCONNECTED = 'DISCONNECTED', + METERED = 'METERED', + UNKNOWN = 'UNKNOWN', +} +``` + +| Value | Description | Native mapping | +|-------|-------------|----------------| +| `CONNECTED` | Online on an unmetered connection. | iOS: `NWPath.status == .satisfied && !isExpensive` / Android: `NetworkCapabilities.NET_CAPABILITY_NOT_METERED` | +| `DISCONNECTED` | No network. | iOS: `NWPath.status != .satisfied` / Android: `NetworkCallback.onLost` | +| `METERED` | Online but the connection is metered (cellular, hotspot). | iOS: `NWPath.isExpensive == true` / Android: `!NET_CAPABILITY_NOT_METERED` | +| `UNKNOWN` | State has not been determined yet. | Initial value before the first emission. | + +--- + +## `ConnectionType` + +The transport behind the current connection. + +```ts +enum ConnectionType { + WIFI = 'WIFI', + CELLULAR = 'CELLULAR', + ETHERNET = 'ETHERNET', + BLUETOOTH = 'BLUETOOTH', + VPN = 'VPN', + OTHER = 'OTHER', + NONE = 'NONE', + UNKNOWN = 'UNKNOWN', +} +``` + +| Value | Description | Native mapping | +|-------|-------------|----------------| +| `WIFI` | 802.11 connection. | `NWInterface.InterfaceType.wifi` / `TRANSPORT_WIFI` | +| `CELLULAR` | Mobile network. | `.cellular` / `TRANSPORT_CELLULAR` | +| `ETHERNET` | Wired connection (typically tablets / dev kits). | `.wiredEthernet` / `TRANSPORT_ETHERNET` | +| `BLUETOOTH` | Tethered over Bluetooth. | `.other` (iOS) / `TRANSPORT_BLUETOOTH` | +| `VPN` | Tunneled. | `NWPath.usesInterfaceType(.other)` w/ VPN heuristic / `TRANSPORT_VPN` | +| `OTHER` | None of the above, but a transport is present. | Fallback for unrecognised transports. | +| `NONE` | No active transport. | Reported with `DISCONNECTED`. | +| `UNKNOWN` | Transport not yet known. | Initial value. | + +--- + +## `SyncStrategy` + +When the engine flushes the queue. + +```ts +enum SyncStrategy { + AUTOMATIC = 'AUTOMATIC', + MANUAL = 'MANUAL', + OPPORTUNISTIC = 'OPPORTUNISTIC', +} +``` + +| Value | Description | +|-------|-------------| +| `AUTOMATIC` | Flush whenever the queue is non-empty and connectivity allows. The default. | +| `MANUAL` | Never flush implicitly -- only when [`flush()`](./functions.md#flush) is called. | +| `OPPORTUNISTIC` | Flush only on opportunistic windows (foreground resume, charging, unmetered). | + +--- + +## `SyncPriority` + +Dispatch lane assigned to each item. Higher priorities drain before lower priorities of the same age. + +```ts +enum SyncPriority { + HIGH = 'HIGH', + NORMAL = 'NORMAL', + LOW = 'LOW', +} +``` + +| Value | Description | Native mapping | +|-------|-------------|----------------| +| `HIGH` | Drained first. Use for user-blocking actions (form submits). | Stored as integer `2` in Core Data / Room for ORDER BY. | +| `NORMAL` | Default lane. | `1` | +| `LOW` | Drained last. Use for telemetry, analytics. | `0` | + +--- + +## `BackoffStrategy` + +Curve used by [`RetryPolicy`](./types.md#retrypolicy) to compute the next attempt delay. + +```ts +enum BackoffStrategy { + LINEAR = 'LINEAR', + EXPONENTIAL = 'EXPONENTIAL', + FIBONACCI = 'FIBONACCI', +} +``` + +| Value | Description | Native mapping | +|-------|-------------|----------------| +| `LINEAR` | `delay = baseDelayMs * attempt`. Bounded by `maxDelayMs`. | `RetryBackoff.linear()` | +| `EXPONENTIAL` | `delay = baseDelayMs * 2^(attempt-1)`. Default. | `RetryBackoff.exponential()` | +| `FIBONACCI` | `delay = baseDelayMs * fib(attempt)`. Slower growth than exponential. | `RetryBackoff.fibonacci()` | + +See [Retry policy guide](../guides/retry-policy.md) for tuning advice. + +--- + +## `HttpMethod` + +HTTP verbs accepted by [`SyncItemInput.method`](./types.md#synciteminput). + +```ts +enum HttpMethod { + GET = 'GET', + POST = 'POST', + PUT = 'PUT', + PATCH = 'PATCH', + DELETE = 'DELETE', +} +``` + +| Value | Description | +|-------|-------------| +| `GET` | Read. Body is ignored. | +| `POST` | Create. | +| `PUT` | Replace. | +| `PATCH` | Partial update. | +| `DELETE` | Remove. | + +--- + +## `SyncEventType` + +Every event type emitted on the [`SYNC_EVENT_CHANNEL`](./listeners.md#sync_event_channel). + +```ts +enum SyncEventType { + ITEM_ENQUEUED = 'ITEM_ENQUEUED', + ITEM_REMOVED = 'ITEM_REMOVED', + SYNC_STARTED = 'SYNC_STARTED', + SYNC_PROGRESS = 'SYNC_PROGRESS', + SYNC_SUCCEEDED = 'SYNC_SUCCEEDED', + SYNC_FAILED = 'SYNC_FAILED', + ITEM_SUCCEEDED = 'ITEM_SUCCEEDED', + ITEM_FAILED = 'ITEM_FAILED', + ITEM_RETRYING = 'ITEM_RETRYING', + QUEUE_CLEARED = 'QUEUE_CLEARED', + CONNECTION_CHANGED = 'CONNECTION_CHANGED', + PAUSED = 'PAUSED', + RESUMED = 'RESUMED', + BACKGROUND_SYNC_STARTED = 'BACKGROUND_SYNC_STARTED', + BACKGROUND_SYNC_COMPLETED = 'BACKGROUND_SYNC_COMPLETED', +} +``` + +| Value | When it fires | Notable payload fields | +|-------|---------------|------------------------| +| `ITEM_ENQUEUED` | After `enqueue` / `enqueueBatch` persists an item. | `itemId`, `metadata` | +| `ITEM_REMOVED` | After `removeItem` succeeds. | `itemId` | +| `SYNC_STARTED` | A flush begins. | -- | +| `SYNC_PROGRESS` | Per-batch progress during a flush. | `progress` (0..1) | +| `SYNC_SUCCEEDED` | A flush finishes with no failures. | -- | +| `SYNC_FAILED` | A flush finishes with one or more failures. | -- | +| `ITEM_SUCCEEDED` | A single item dispatched successfully. | `itemId`, `statusCode`, `metadata` | +| `ITEM_FAILED` | A single item exhausted retries or hit a terminal status. | `itemId`, `errorCode`, `statusCode`, `attempt`, `metadata` | +| `ITEM_RETRYING` | A single item failed but will be retried. | `itemId`, `errorCode`, `attempt`, `metadata` | +| `QUEUE_CLEARED` | After `clearQueue`. | -- | +| `CONNECTION_CHANGED` | The native monitor reported a transition. | `connectionStatus` | +| `PAUSED` | After `pauseSync`. | -- | +| `RESUMED` | After `resumeSync`. | -- | +| `BACKGROUND_SYNC_STARTED` | A `BGTaskScheduler` / `WorkManager` run starts. | -- | +| `BACKGROUND_SYNC_COMPLETED` | The same run finishes. | -- | + +--- + +## See also + +- [`SyncEvent` type](./types.md#syncevent), [Listeners](./listeners.md), [`useSyncEvents`](./hooks/useSyncEvents.md). +- [Errors](./errors.md) for `SyncErrorCode`. diff --git a/website/docs/api-reference/errors.md b/website/docs/api-reference/errors.md new file mode 100644 index 0000000..5928bc1 --- /dev/null +++ b/website/docs/api-reference/errors.md @@ -0,0 +1,139 @@ +--- +sidebar_position: 6 +title: Errors +description: SyncError class, the SyncErrorCode discriminator, and the full retryability matrix for every error code emitted by react-native-sync-provider. +keywords: + - react-native + - sync-provider + - errors + - SyncError + - SyncErrorCode + - error-handling + - retryable +--- + +# Errors + +Structured error type and discriminator emitted by every facade function and reported through `SyncEvent.errorCode` / `SyncResult.errors`. + +```ts +import { + SyncError, + SyncErrorCode, +} from '@gabriel-sisjr/react-native-sync-provider'; +``` + +--- + +## `SyncError` + +A custom `Error` subclass that carries a structured [`SyncErrorCode`](#syncerrorcode) so callers can branch on the failure reason without parsing the message string. + +### Class definition + +```ts +class SyncError extends Error { + name: 'SyncError'; + code: SyncErrorCode; + cause?: unknown; + + constructor(code: SyncErrorCode, message: string, cause?: unknown); +} +``` + +### Properties + +| Property | Type | Description | +|----------|------|-------------| +| `name` | `'SyncError'` | Always the literal string. | +| `code` | [`SyncErrorCode`](#syncerrorcode) | Structured discriminator. | +| `message` | `string` | Human-readable description. | +| `cause` | `unknown` | Optional underlying error (native exception, fetch error). | +| `stack` | `string` | Standard stack trace, inherited from `Error`. | + +The constructor restores the prototype chain (`Object.setPrototypeOf(this, SyncError.prototype)`), so `instanceof SyncError` works reliably across bundlers and runtime targets. + +--- + +## `SyncErrorCode` + +```ts +type SyncErrorCode = + | 'NETWORK_ERROR' + | 'SERVER_ERROR' + | 'INVALID_PAYLOAD' + | 'QUEUE_FULL' + | 'NATIVE_MODULE_UNAVAILABLE' + | 'BACKGROUND_TASK_REGISTRATION_FAILED' + | 'INVALID_URL' + | 'UNAUTHORIZED' + | 'TIMEOUT' + | 'MAX_ATTEMPTS_EXCEEDED' + | 'DUPLICATE_ITEM'; +``` + +### Matrix + +| Code | Meaning | Retryable? | Typically thrown by | Suggested action | +|------|---------|------------|---------------------|------------------| +| `NETWORK_ERROR` | Transport failure (DNS, socket, TLS, no route). | Yes | Native dispatcher during `flush()`; surfaced on `ITEM_RETRYING` / `ITEM_FAILED` after exhaustion. | Wait for connectivity restoration; the engine will retry per `RetryPolicy`. | +| `SERVER_ERROR` | HTTP status in `RetryPolicy.retryOnStatusCodes` (typically 5xx, 408, 425, 429). | Yes | Dispatcher when the response status is in the retry list. | Retry handled automatically. Investigate server-side if it persists. | +| `INVALID_PAYLOAD` | The item is missing required fields (`method`, `url`) or `RetryPolicy` failed validation. | No | `enqueue`, `enqueueBatch`, `configureSync`. | Fix the call site; do not retry. | +| `QUEUE_FULL` | `enqueue` would exceed `SyncOptions.maxQueueSize`. | No | `enqueue`, `enqueueBatch`. | Drain via `flush()`, drop the item, or surface a "retry later" UX. | +| `NATIVE_MODULE_UNAVAILABLE` | Called on web / SSR or before native linking completed. | No | Every facade function. | Guard with `isNativeModuleAvailable()` and provide a JS fallback or no-op. | +| `BACKGROUND_TASK_REGISTRATION_FAILED` | iOS: missing `BGTaskSchedulerPermittedIdentifiers` entry or disabled Background Modes. Android: WorkManager rejected the request. | No | `enableBackgroundSync`. | See [Background sync guide](../guides/background-sync.md) and the per-platform [Production checklist](../production/production-checklist.md). | +| `INVALID_URL` | `url` does not parse as an absolute URL. | No | `enqueue`, `enqueueBatch`. | Validate URLs at the caller. | +| `UNAUTHORIZED` | HTTP 401 / 403 from the server. | No | Dispatcher; reported on `ITEM_FAILED`. | Refresh the auth token, mutate the item's `headers`, and re-enqueue. | +| `TIMEOUT` | Per-request `requestTimeoutMs` exceeded. | Yes | Dispatcher. | Retry handled automatically per policy; consider increasing `requestTimeoutMs` for large bodies. | +| `MAX_ATTEMPTS_EXCEEDED` | Item exhausted `RetryPolicy.maxAttempts`. | No | Dispatcher; reported on `ITEM_FAILED`. | Inspect the underlying `cause` (last error before exhaustion) and decide whether to manually re-enqueue. | +| `DUPLICATE_ITEM` | The native layer rejected an item whose id collided with an existing row. | No | `enqueue`, `enqueueBatch` (vanishingly rare with native ULIDs). | File a bug if reproducible. | + +Use this matrix when designing your error UI -- any code marked retryable will be retried by the engine; non-retryable codes are terminal and signal that the item will not advance without code changes. + +--- + +## Discrimination Pattern + +```ts +import { + enqueue, + SyncError, + SyncErrorCode, + HttpMethod, +} from '@gabriel-sisjr/react-native-sync-provider'; + +try { + await enqueue({ + method: HttpMethod.POST, + url: 'not-a-url', + }); +} catch (err) { + if (err instanceof SyncError) { + switch (err.code) { + case SyncErrorCode.INVALID_URL: + console.error('Bad URL -- fix the caller'); + break; + case SyncErrorCode.QUEUE_FULL: + // Drop or warn the user + break; + case SyncErrorCode.NATIVE_MODULE_UNAVAILABLE: + // Likely SSR / web; degrade gracefully + break; + default: + console.error(`SyncError [${err.code}]: ${err.message}`); + } + } else { + throw err; + } +} +``` + +For an end-to-end pattern (try/catch + event subscriptions + result inspection), see the [Error handling guide](../guides/error-handling.md). + +--- + +## See also + +- [`SyncEvent.errorCode` field](./types.md#syncevent). +- [`SyncResult.errors` map](./types.md#syncresult). +- [Retry policy guide](../guides/retry-policy.md), [Error handling guide](../guides/error-handling.md). diff --git a/website/docs/api-reference/functions.md b/website/docs/api-reference/functions.md new file mode 100644 index 0000000..e473efe --- /dev/null +++ b/website/docs/api-reference/functions.md @@ -0,0 +1,567 @@ +--- +sidebar_position: 1 +title: Functions +description: API reference for every public function in @gabriel-sisjr/react-native-sync-provider — queue, sync, configuration, history, connection, and background sync APIs. +keywords: + - react-native + - sync-provider + - api + - enqueue + - flush + - configureSync + - background-sync +--- + +# Functions + +The full list of facade functions exported from `@gabriel-sisjr/react-native-sync-provider`. Every function is async, returns a `Promise`, and throws a [`SyncError`](./errors.md) with a typed [`SyncErrorCode`](./errors.md#syncerrorcode) on failure. Calls made on web / SSR / before native linking throw `NATIVE_MODULE_UNAVAILABLE`. + +```ts +import { + // Queue + enqueue, + enqueueBatch, + removeItem, + clearQueue, + getQueueSize, + getPendingItems, + // Sync + flush, + pauseSync, + resumeSync, + isSyncing, + // Config + configureSync, + getSyncConfig, + // History + getLastSyncResult, + getSyncHistory, + clearSyncHistory, + // Connection + getConnectionStatus, + // Background + enableBackgroundSync, + disableBackgroundSync, + isBackgroundSyncEnabled, +} from '@gabriel-sisjr/react-native-sync-provider'; +``` + +--- + +## Queue + +### `enqueue` + +Persist a single HTTP request to the native queue. Returns the assigned ULID once the row hits disk. + +#### Signature + +```ts +function enqueue(item: SyncItemInput): Promise +``` + +#### Parameters + +| Name | Type | Required | Description | +|------|------|----------|-------------| +| `item` | [`SyncItemInput`](./types.md#synciteminput) | Yes | The HTTP request descriptor: `method`, `url`, optional `headers`, `body`, `contentType`, `priority`, `metadata`. | + +#### Returns + +`Promise` -- the item's ULID, generated natively for monotonic ordering and idempotency. + +#### Throws + +- `INVALID_PAYLOAD` -- `method` or `url` is missing. +- `INVALID_URL` -- `url` does not parse as an absolute URL. +- `QUEUE_FULL` -- queue size has reached `SyncOptions.maxQueueSize`. +- `DUPLICATE_ITEM` -- the auto-assigned id collided with an existing row (vanishingly rare). +- `NATIVE_MODULE_UNAVAILABLE` -- the native module is not linked. + +#### Example + +```ts +const id = await enqueue({ + method: HttpMethod.POST, + url: 'https://api.example.com/events', + contentType: 'application/json', + body: JSON.stringify({ event: 'page_view' }), + priority: SyncPriority.NORMAL, + metadata: { screen: 'Home' }, +}); +``` + +--- + +### `enqueueBatch` + +Atomically persist multiple items in a single transaction. Either every item is persisted or the call throws and none are. + +#### Signature + +```ts +function enqueueBatch(items: SyncItemInput[]): Promise +``` + +#### Parameters + +| Name | Type | Required | Description | +|------|------|----------|-------------| +| `items` | `SyncItemInput[]` | Yes | The items to persist. Empty arrays resolve to `[]` with no native call. | + +#### Returns + +`Promise` -- the ULIDs in the same order as the input. + +#### Throws + +Same codes as [`enqueue`](#enqueue), plus: + +- `QUEUE_FULL` -- adding the batch would exceed `maxQueueSize`. No items are persisted. + +#### Example + +```ts +const ids = await enqueueBatch([ + { method: HttpMethod.POST, url: 'https://api.example.com/a' }, + { method: HttpMethod.POST, url: 'https://api.example.com/b' }, +]); +``` + +--- + +### `removeItem` + +Remove a single pending item by id. + +#### Signature + +```ts +function removeItem(id: string): Promise +``` + +#### Parameters + +| Name | Type | Required | Description | +|------|------|----------|-------------| +| `id` | `string` | Yes | The ULID returned by `enqueue()` / `enqueueBatch()`. | + +#### Returns + +`Promise` -- `true` if the row existed and was removed, `false` if the id is unknown or the item already drained. + +#### Throws + +`NATIVE_MODULE_UNAVAILABLE`. + +#### Example + +```ts +const ok = await removeItem('01HZX9V4MAYQ2XJ7C2K6Z1Q3D8'); +if (!ok) console.warn('item already drained'); +``` + +--- + +### `clearQueue` + +Remove every pending item. Does not touch sync history. + +#### Signature + +```ts +function clearQueue(): Promise +``` + +#### Returns + +`Promise`. + +#### Throws + +`NATIVE_MODULE_UNAVAILABLE`. + +#### Example + +```ts +await clearQueue(); +``` + +--- + +### `getQueueSize` + +Read the current pending count. + +#### Signature + +```ts +function getQueueSize(): Promise +``` + +#### Returns + +`Promise`. + +#### Throws + +`NATIVE_MODULE_UNAVAILABLE`. + +#### Example + +```ts +const n = await getQueueSize(); +console.log(`${n} items pending`); +``` + +--- + +### `getPendingItems` + +Snapshot the pending queue, sorted by priority desc then `createdAt` asc. + +#### Signature + +```ts +function getPendingItems(): Promise +``` + +#### Returns + +`Promise<`[`SyncItem`](./types.md#syncitem)`[]>`. + +#### Throws + +`NATIVE_MODULE_UNAVAILABLE`. + +#### Example + +```ts +const items = await getPendingItems(); +items.forEach((item) => console.log(item.id, item.url)); +``` + +--- + +## Sync + +### `flush` + +Force an immediate sync attempt. Resolves with the [`SyncResult`](./types.md#syncresult) describing the run. + +#### Signature + +```ts +function flush(): Promise +``` + +#### Returns + +`Promise` -- `successCount`, `failureCount`, `succeededIds`, `failedIds`, `errors` map. + +#### Throws + +`NATIVE_MODULE_UNAVAILABLE`. Per-item failures are reported via `result.errors`, not thrown. + +:::warning `flush()` resolves successfully even with failures +A successful `Promise` resolution does **not** mean every item synced. Always inspect `result.failureCount` and `result.errors`. See the [Error handling guide](../guides/error-handling.md). +::: + +#### Example + +```ts +const result = await flush(); +if (result.failureCount > 0) { + console.warn('Some items failed:', result.errors); +} +``` + +--- + +### `pauseSync` + +Pause the engine without dropping the queue. Items persist and resume when `resumeSync()` is called. + +#### Signature + +```ts +function pauseSync(): Promise +``` + +#### Throws + +`NATIVE_MODULE_UNAVAILABLE`. + +--- + +### `resumeSync` + +Resume a previously paused engine. + +#### Signature + +```ts +function resumeSync(): Promise +``` + +#### Throws + +`NATIVE_MODULE_UNAVAILABLE`. + +--- + +### `isSyncing` + +Whether a flush is in flight. + +#### Signature + +```ts +function isSyncing(): Promise +``` + +#### Returns + +`Promise`. + +--- + +## Config + +### `configureSync` + +Apply a full [`SyncOptions`](./types.md#syncoptions) configuration. Replaces any prior call. + +#### Signature + +```ts +function configureSync(options: SyncOptions): Promise +``` + +#### Parameters + +| Name | Type | Required | Description | +|------|------|----------|-------------| +| `options` | [`SyncOptions`](./types.md#syncoptions) | Yes | Strategy, retry policy, batch size, timeout, queue cap, persistence flag, default headers. | + +#### Throws + +- `INVALID_PAYLOAD` -- malformed `retryPolicy` (e.g. `maxAttempts < 1`). +- `NATIVE_MODULE_UNAVAILABLE`. + +#### Example + +```ts +await configureSync({ + strategy: SyncStrategy.AUTOMATIC, + retryPolicy: { + maxAttempts: 5, + backoff: BackoffStrategy.EXPONENTIAL, + baseDelayMs: 1000, + maxDelayMs: 60_000, + jitter: true, + retryOnStatusCodes: [408, 425, 429, 500, 502, 503, 504], + }, + batchSize: 10, + defaultHeaders: { 'X-Client': 'mobile' }, +}); +``` + +--- + +### `getSyncConfig` + +Read the currently active configuration. + +#### Signature + +```ts +function getSyncConfig(): Promise +``` + +#### Returns + +`Promise` -- the merged config (defaults + last `configureSync()` call). + +--- + +## History + +### `getLastSyncResult` + +Return the most recent [`SyncResult`](./types.md#syncresult), or `undefined` if no flush has run yet. + +#### Signature + +```ts +function getLastSyncResult(): Promise +``` + +#### Returns + +`Promise`. + +:::note Sentinel translation +The native layer returns a result with `startedAt === 0 && finishedAt === 0` to mean "no run yet". The JS facade translates that sentinel into `undefined` so consumers can do a simple null check. +::: + +#### Example + +```ts +const last = await getLastSyncResult(); +if (!last) { + console.log('No sync has run yet'); +} else { + console.log(`Last run synced ${last.successCount} items`); +} +``` + +--- + +### `getSyncHistory` + +Return up to `limit` recent sync results, most recent first. + +#### Signature + +```ts +function getSyncHistory(limit?: number): Promise +``` + +#### Parameters + +| Name | Type | Required | Default | Description | +|------|------|----------|---------|-------------| +| `limit` | `number` | No | `0` | Max entries to return. `0` (the JS-side default when the argument is omitted) means "all history the native layer retains". | + +#### Returns + +`Promise`. + +#### Example + +```ts +const recent = await getSyncHistory(20); +const totalSucceeded = recent.reduce((sum, r) => sum + r.successCount, 0); +``` + +--- + +### `clearSyncHistory` + +Drop all stored history. + +#### Signature + +```ts +function clearSyncHistory(): Promise +``` + +#### Throws + +`NATIVE_MODULE_UNAVAILABLE`. + +--- + +## Connection + +### `getConnectionStatus` + +One-shot read of the current network state. + +#### Signature + +```ts +function getConnectionStatus(): Promise +``` + +#### Returns + +`Promise<`[`ConnectionState`](./types.md#connectionstate)`>` -- `status`, `type`, optional `isInternetReachable`, `isExpensive`. + +#### Example + +```ts +const conn = await getConnectionStatus(); +if (conn.status === ConnectionStatus.CONNECTED) { + await flush(); +} +``` + +For reactive updates, prefer the [`useConnection`](./hooks/useConnection.md) hook. + +--- + +## Background Sync + +### `enableBackgroundSync` + +Register the platform background scheduler so the queue keeps draining when the app is killed. + +#### Signature + +```ts +function enableBackgroundSync(options: BackgroundSyncOptions): Promise +``` + +#### Parameters + +| Name | Type | Required | Description | +|------|------|----------|-------------| +| `options` | [`BackgroundSyncOptions`](./types.md#backgroundsyncoptions) | Yes | Minimum interval, network / charging / idle constraints, optional task identifier. | + +#### Throws + +- `BACKGROUND_TASK_REGISTRATION_FAILED` -- iOS: the `BGTaskSchedulerPermittedIdentifiers` entry is missing or the Background Modes capability is disabled. Android: WorkManager rejected the request. +- `NATIVE_MODULE_UNAVAILABLE`. + +#### Example + +```ts +await enableBackgroundSync({ + minimumIntervalMs: 15 * 60 * 1000, // 15 min (Android floor) + requiresUnmeteredNetwork: false, +}); +``` + +See the [Background sync guide](../guides/background-sync.md) for full setup. + +--- + +### `disableBackgroundSync` + +Unregister the background scheduler. Pending in-flight work is allowed to finish. + +#### Signature + +```ts +function disableBackgroundSync(): Promise +``` + +#### Throws + +`NATIVE_MODULE_UNAVAILABLE`. + +--- + +### `isBackgroundSyncEnabled` + +Whether background sync is currently registered. + +#### Signature + +```ts +function isBackgroundSyncEnabled(): Promise +``` + +#### Returns + +`Promise`. + +--- + +## See also + +- [Listener helpers](./listeners.md) -- `addSyncEventListener`, `removeSyncEventListener`. +- [React hooks](./hooks/useSyncQueue.md) -- reactive wrappers around these functions. +- [Errors](./errors.md) -- `SyncError` and `SyncErrorCode`. diff --git a/website/docs/api-reference/hooks/useAutoSync.md b/website/docs/api-reference/hooks/useAutoSync.md new file mode 100644 index 0000000..5fca708 --- /dev/null +++ b/website/docs/api-reference/hooks/useAutoSync.md @@ -0,0 +1,121 @@ +--- +sidebar_position: 7 +title: useAutoSync +description: Orchestrator hook that triggers flushes on interval, reconnect, AppState foreground, and (optionally) registers background sync. +keywords: + - react-native + - sync-provider + - hook + - useAutoSync + - auto-sync + - flush + - background-sync +--- + +# `useAutoSync` + +Orchestrator hook that wires up the most common auto-flush triggers in one place: periodic interval, connectivity reconnect, metered-network gating, AppState foreground, and (optionally) registering background sync. Inert until `enabled` is `true`. Every trigger is wrapped in an `isSyncing()` guard so concurrent flushes do not stack. + +```ts +import { useAutoSync } from '@gabriel-sisjr/react-native-sync-provider'; +``` + +**Platform:** Android and iOS. + +--- + +## Signature + +```ts +function useAutoSync(options: { + enabled?: boolean; + flushOnReconnect?: boolean; + flushOnMetered?: boolean; + flushOnForeground?: boolean; + backgroundSync?: BackgroundSyncOptions; + intervalMs?: number; + flushOnAppForeground?: boolean; +}): void +``` + +--- + +## Parameters + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| +| `enabled` | `boolean` | No | `false` | Master switch. When `false`, no triggers fire. | +| `intervalMs` | `number` | No | `60_000` | Periodic interval between auto-flushes. Set to `0` to disable. | +| `flushOnReconnect` | `boolean` | No | `true` | Flush when `CONNECTION_CHANGED` reports a transition to `CONNECTED`. | +| `flushOnMetered` | `boolean` | No | `false` | Allow flushes triggered by reconnect / interval to run when the only available connection is `METERED`. | +| `flushOnForeground` | `boolean` | No | `false` | **Reserved for a future event-based foreground hook.** No-op today. Use `flushOnAppForeground` instead. | +| `flushOnAppForeground` | `boolean` | No | `false` | When `true`, the hook subscribes to React Native's `AppState` and flushes on every `active` transition. JS-only extension. | +| `backgroundSync` | [`BackgroundSyncOptions`](../types.md#backgroundsyncoptions) | No | -- | When provided, calls [`enableBackgroundSync`](../functions.md#enablebackgroundsync) on mount and [`disableBackgroundSync`](../functions.md#disablebackgroundsync) on unmount. | + +:::warning Conservative defaults +By default the hook is inert (`enabled: false`). Once enabled, it will not flush on metered networks unless you opt in with `flushOnMetered: true`. If your traffic is small (telemetry, analytics, control-plane events), turning it on is safe; if your queue carries large payloads, leave it off to avoid burning user data. +::: + +--- + +## Return Value + +`void`. The hook performs side effects only. + +--- + +## Behavior + +- **`enabled` toggle** -- when `enabled` flips to `false`, all subscriptions tear down. Flipping back recreates them. +- **Interval** -- a `setInterval(intervalMs)` calls `flush()` if the queue is non-empty and not already syncing. +- **Reconnect** -- subscribes to `CONNECTION_CHANGED` events. On transition to `CONNECTED` (or to `METERED` when `flushOnMetered` is `true`), fires a guarded flush. +- **AppState foreground** -- when `flushOnAppForeground` is `true`, subscribes to React Native `AppState` and flushes on every `'background' -> 'active'` transition. +- **Background sync** -- if `backgroundSync` is provided, the hook owns the lifecycle: `enableBackgroundSync(options)` on mount, `disableBackgroundSync()` on unmount. +- **Idempotent flushes** -- every trigger calls `isSyncing()` first; concurrent flush calls are skipped. + +--- + +## Example + +### Minimal opt-in + +```tsx +import { useAutoSync } from '@gabriel-sisjr/react-native-sync-provider'; + +function App() { + useAutoSync({ + enabled: true, + flushOnAppForeground: true, + }); + return ; +} +``` + +### Full orchestration with background sync + +```tsx +import { useAutoSync } from '@gabriel-sisjr/react-native-sync-provider'; + +function App() { + useAutoSync({ + enabled: true, + intervalMs: 30_000, + flushOnReconnect: true, + flushOnMetered: false, + flushOnAppForeground: true, + backgroundSync: { + minimumIntervalMs: 15 * 60 * 1000, + requiresUnmeteredNetwork: true, + }, + }); + return ; +} +``` + +--- + +## See also + +- [`flush`](../functions.md#flush), [`enableBackgroundSync`](../functions.md#enablebackgroundsync), [`disableBackgroundSync`](../functions.md#disablebackgroundsync). +- [`useConnection`](./useConnection.md) -- the underlying connection signal. +- [Background sync guide](../../guides/background-sync.md), [Connectivity detection guide](../../guides/connectivity-detection.md). diff --git a/website/docs/api-reference/hooks/useConnection.md b/website/docs/api-reference/hooks/useConnection.md new file mode 100644 index 0000000..15c922d --- /dev/null +++ b/website/docs/api-reference/hooks/useConnection.md @@ -0,0 +1,76 @@ +--- +sidebar_position: 1 +title: useConnection +description: React hook that subscribes to the native connectivity monitor and re-renders on every CONNECTION_CHANGED event. +keywords: + - react-native + - sync-provider + - hook + - useConnection + - connectivity + - online + - metered +--- + +# `useConnection` + +Reactive view of the device's network state. Subscribes to `CONNECTION_CHANGED` events from the native [`ConnectivityMonitor`](../../architecture/overview.md) and re-renders whenever the status, type, or metered flag changes. + +```ts +import { useConnection } from '@gabriel-sisjr/react-native-sync-provider'; +``` + +**Platform:** Android and iOS. On web / SSR returns `{ status: 'UNKNOWN', type: 'UNKNOWN', isOnline: false, isMetered: false }`. + +--- + +## Signature + +```ts +function useConnection(): UseConnectionResult +``` + +--- + +## Return Value + +| Property | Type | Description | +|----------|------|-------------| +| `status` | [`ConnectionStatus`](../enums.md#connectionstatus) | `CONNECTED`, `DISCONNECTED`, `METERED`, or `UNKNOWN`. | +| `type` | [`ConnectionType`](../enums.md#connectiontype) | `WIFI`, `CELLULAR`, `ETHERNET`, `BLUETOOTH`, `VPN`, `OTHER`, `NONE`, or `UNKNOWN`. | +| `isOnline` | `boolean` | Convenience: `status === CONNECTED \|\| status === METERED`. | +| `isMetered` | `boolean` | Convenience: `status === METERED`. | + +--- + +## Behavior + +- **Initial value** -- on mount, the hook calls [`getConnectionStatus()`](../functions.md#getconnectionstatus) once to seed state. +- **Subscription** -- registers a listener for `SyncEventType.CONNECTION_CHANGED` events via [`addSyncEventListener`](../listeners.md#addsynceventlistener) and updates state on every emission. +- **Cleanup** -- unsubscribes on unmount. +- **Web / SSR safety** -- when the native module is unavailable, the hook returns the `UNKNOWN` defaults without throwing. + +--- + +## Example + +```tsx +import { useConnection } from '@gabriel-sisjr/react-native-sync-provider'; +import { Text, View } from 'react-native'; + +function ConnectivityBadge() { + const { isOnline, isMetered, type } = useConnection(); + + if (!isOnline) return Offline; + if (isMetered) return On {type} (metered); + return Online via {type}; +} +``` + +--- + +## See also + +- [`getConnectionStatus`](../functions.md#getconnectionstatus) -- one-shot read. +- [`useOfflineQueue`](./useOfflineQueue.md) -- composes `useConnection` + queue state. +- [Connectivity detection guide](../../guides/connectivity-detection.md) -- how the native layer detects transitions. diff --git a/website/docs/api-reference/hooks/useOfflineQueue.md b/website/docs/api-reference/hooks/useOfflineQueue.md new file mode 100644 index 0000000..40ce55a --- /dev/null +++ b/website/docs/api-reference/hooks/useOfflineQueue.md @@ -0,0 +1,105 @@ +--- +sidebar_position: 4 +title: useOfflineQueue +description: Convenience hook composing useConnection, useSyncQueue, and a slice of useSyncStatus — ideal for offline-aware UI. +keywords: + - react-native + - sync-provider + - hook + - useOfflineQueue + - offline-first + - connection + - waiting-for-connection +--- + +# `useOfflineQueue` + +Convenience composition of [`useConnection`](./useConnection.md), [`useSyncQueue`](./useSyncQueue.md), and a slice of [`useSyncStatus`](./useSyncStatus.md). Use it when you want a single hook that powers an offline-first UI -- "X items pending, waiting for connection..." badges, retry buttons, etc. + +```ts +import { useOfflineQueue } from '@gabriel-sisjr/react-native-sync-provider'; +``` + +**Platform:** Android and iOS. + +--- + +## Signature + +```ts +function useOfflineQueue(): UseOfflineQueueResult +``` + +--- + +## Return Value + +### State + +| Property | Type | Description | +|----------|------|-------------| +| `connection` | [`ConnectionState`](../types.md#connectionstate) | Mirrored from `useConnection`. Includes `status`, `type`, `isInternetReachable`, `isExpensive`. | +| `size` | `number` | Pending queue size. | +| `items` | [`SyncItem[]`](../types.md#syncitem) | Pending items snapshot. | +| `isSyncing` | `boolean` | Whether a flush is in flight. | +| `isWaitingForConnection` | `boolean` | `size > 0 && connection.status !== CONNECTED && connection.status !== METERED`. The "Waiting for connection..." sentinel. | +| `error` | [`SyncError`](../errors.md) `\| null` | Last error from `enqueue` or `flush`. | + +### Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `enqueue` | `(item: SyncItemInput) => Promise` | Same as `useSyncQueue().enqueue`. | +| `flush` | `() => Promise` | Same as `useSyncStatus().flush`. | + +--- + +## Behavior + +This hook does not own any state -- it composes the three primitive hooks and projects a smaller, opinionated surface. Re-renders trigger whenever any of the underlying hooks would re-render. + +For finer-grained access (priority lanes, per-item removal, sync history), reach for the primitive hooks directly. + +--- + +## Example + +### Offline-aware status bar + +```tsx +import { useOfflineQueue } from '@gabriel-sisjr/react-native-sync-provider'; +import { Text, View, Button } from 'react-native'; + +function OfflineStatusBar() { + const { size, isSyncing, isWaitingForConnection, connection, flush } = + useOfflineQueue(); + + if (isWaitingForConnection) { + return ( + + {size} items pending -- waiting for connection + + ); + } + + if (size === 0) return All synced; + + return ( + + + {size} pending on {connection.type} + {isSyncing ? ' (syncing...)' : ''} + +