{"_id":"@aegiondynamic/async-storage-sync","name":"@aegiondynamic/async-storage-sync","dist-tags":{"latest":"1.0.13"},"versions":{"1.0.13":{"name":"@aegiondynamic/async-storage-sync","version":"1.0.13","description":"Offline-first data layer for React Native with local-first storage and automatic sync","main":"dist/index.js","module":"dist/index.mjs","types":"dist/index.d.ts","scripts":{"build":"tsup src/index.ts --format cjs,esm --dts --clean","test":"vitest run","prepublishOnly":"npm test && npm run build"},"keywords":["react-native","offline","sync","asyncstorage","queue","offline-first"],"maintainers":[{"name":"rkrishnasanka","email":"rkrishnasanka@gmail.com"},{"name":"nischalmv","email":"sainischalmv@gmail.com"}],"license":"MIT","peerDependencies":{"@react-native-async-storage/async-storage":">=1.0.0","@react-native-community/netinfo":">=11","react-native":">=0.70.0"},"devDependencies":{"@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.2","@testing-library/react-hooks":"^8.0.1","@types/react":"^18.0.0","jsdom":"^28.1.0","react":"^18.3.1","react-dom":"^18.3.1","tsup":"^8.5.1","typescript":"^5.9.3","vitest":"^4.0.18"},"_id":"@aegiondynamic/async-storage-sync@1.0.13","gitHead":"bab8454fe8c724a80ee5ab2b9e9b3377ba033d41","_nodeVersion":"20.20.0","_npmVersion":"10.8.2","dist":{"integrity":"sha512-+USTPmubOZ2MFxnm3mLqFExqlO67mLr2VdoCEWApYBiSkSYqTfKEFV9DYTEOP9T8ayjp4hFZI3M2c7MghVHaVA==","shasum":"8ef8f114adc7ad0a553b4ba222a1a4c9ccb38487","tarball":"https://registry.npmjs.org/@aegiondynamic/async-storage-sync/-/async-storage-sync-1.0.13.tgz","fileCount":6,"unpackedSize":66228,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIF2w7NXwUrJW7K3jnkbVw5TnpdWIqCY/vGjWtjtYquLFAiAZ9nPLPs16frTjYDwjJwd4O1V22qkQEXXGLgngl10o8w=="}]},"_npmUser":{"name":"nischalmv","email":"sainischalmv@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/async-storage-sync_1.0.13_1774266437219_0.11739466446863833"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-23T11:47:17.095Z","1.0.13":"2026-03-23T11:47:17.352Z","modified":"2026-03-23T11:47:17.639Z"},"maintainers":[{"name":"rkrishnasanka","email":"rkrishnasanka@gmail.com"},{"name":"nischalmv","email":"sainischalmv@gmail.com"}],"description":"Offline-first data layer for React Native with local-first storage and automatic sync","keywords":["react-native","offline","sync","asyncstorage","queue","offline-first"],"license":"MIT","readme":"# @aegiondynamic/async-storage-sync\n\nOffline-first queue for React Native with type-safe payloads and automatic sync.\n\n## Install\n\n```bash\nnpm install @aegiondynamic/async-storage-sync @react-native-async-storage/async-storage @react-native-community/netinfo\n```\n\n## Quick Start\n\n```ts\nimport { initSyncQueue, getSyncQueue } from '@aegiondynamic/async-storage-sync';\n\nawait initSyncQueue({\n  driver: 'asyncstorage',\n  serverUrl: 'https://api.example.com',\n  credentials: { Authorization: 'Token abc123' },\n  endpoint: '/submit',\n  idResolver: (item) => String(item.id ?? ''),\n  autoSync: true,\n  syncOnSave: true,\n});\n\ntype Submission = {\n  id: string;\n  formId: string;\n  name: string;\n};\n\nconst store = getSyncQueue().asType<Submission>();\n\nconst saved = await store.save('submissions', {\n  id: 'sub_1',\n  formId: 'form_a',\n  name: 'John',\n});\n\nconsole.log(saved.meta.id);     // internal id used by queue\nconsole.log(saved.data.name);   // your payload\n\nconst result = await store.flushWithResult();\nconsole.log(`Synced: ${result.synced}, Failed: ${result.failed}`);\n```\n\nIf queue access may happen during startup (for example from screens/services), use:\n\n```ts\nimport { ensureInitialized } from '@aegiondynamic/async-storage-sync';\n\nawait ensureInitialized({\n  driver: 'asyncstorage',\n  serverUrl: 'https://api.example.com',\n  credentials: { Authorization: 'Token abc123' },\n});\n```\n\n## What Changed\n\n- Generic API: `save<T>()`, `getAll<T>()`, `getById<T>()`, `flushWithResult<T>()`.\n- Metadata is separated from payload in storage and reads/writes.\n- Identity is configurable with `idResolver`.\n- `getSyncQueue()` is non-generic and safe on a singleton; use `asType<T>()` for typed access.\n\n## Configuration\n\n```ts\ninitSyncQueue({\n  driver: 'asyncstorage',\n  serverUrl: 'https://api.example.com',\n  credentials: {\n    Authorization: 'Token abc123',\n    'x-api-key': 'my-custom-key',\n  },\n\n  endpoint: '/submit',\n  autoSync: true,\n  syncOnSave: true,\n  autoSyncCollections: ['submissions'],\n\n  onSyncSuccess: 'delete',\n  ttl: 7 * 24 * 60 * 60 * 1000,\n  duplicateStrategy: 'append',\n\n  idResolver: (item) => String(item.id ?? ''),\n  payloadTransformer: (payload) => payload,\n});\n```\n\n## API\n\n### Setup\n\n```ts\ninitSyncQueue(config)\nisInitialized()\nensureInitialized(config?)\ngetSyncQueue()\ngetTypedSyncQueue<T>()\n```\n\n### Typed access\n\n```ts\ntype Invoice = { invoiceNo: string; amount: number };\n\nconst store = getSyncQueue().asType<Invoice>();\n\nawait store.save('invoices', { invoiceNo: 'INV-1', amount: 100 });\nconst all = await store.getAll('invoices');\nconst one = await store.getById('invoices', all[0].meta.id);\n```\n\nUsing the helper export:\n\n```ts\nimport { getTypedSyncQueue } from '@aegiondynamic/async-storage-sync';\n\ntype Invoice = { invoiceNo: string; amount: number };\nconst store = getTypedSyncQueue<Invoice>();\n```\n\n### Record shape\n\n```ts\ntype StoredRecord<T> = {\n  meta: {\n    id: string;\n    ts: number;\n    synced: 'pending' | 'synced' | 'failed';\n    type: string;\n    retries: number;\n  };\n  data: T;\n};\n```\n\n### Sync\n\n```ts\nconst store = getSyncQueue().asType<{ amount: number }>();\n\nawait store.flushWithResult();\nawait getSyncQueue().syncWithResult('invoices');\nawait getSyncQueue().syncManyWithResult(['invoices', 'receipts']);\nawait getSyncQueue().syncById('invoices', 'record-id');\n```\n\n### Events\n\n```ts\nconst queue = getSyncQueue();\n\nqueue.onSynced((item) => {\n  console.log('Synced queue item:', item.id);\n});\n\nqueue.onAuthError((statusCode, item) => {\n  console.log('Auth error:', statusCode, item.recordId);\n});\n\nqueue.onStorageFull(() => {\n  console.log('Storage full');\n});\n```\n\n## Notes\n\n- `payloadTransformer` receives only your payload (`record.data`), not metadata.\n- Queue payload and collection records are persisted across app restarts.\n- Max 5 retries per record with exponential backoff for 5xx errors.\n- `getSyncQueue()` throws if not initialized yet; use `ensureInitialized(config)` for boot-safe access.\n- `ensureInitialized()` without config works only after first init; otherwise pass config.\n\n## Sync Modes\n\n- `autoSync`: sync on connectivity/app-open signals (NetInfo-driven behavior).\n- `syncOnSave`: schedule immediate debounced best-effort sync after each `save()`.\n- Use both for most apps: `autoSync` handles reconnects, `syncOnSave` reduces delay when already online.","readmeFilename":"README.md","_rev":"1-09180f30552d05b78e3cef2fea2f5387"}