{"_id":"@appboypov/turbo-firestore-api","_rev":"6-3f8e23a5cecb0a1ef69e6f9d9d90a452","name":"@appboypov/turbo-firestore-api","dist-tags":{"latest":"1.1.0"},"versions":{"0.1.2":{"name":"@appboypov/turbo-firestore-api","version":"0.1.2","keywords":["firebase","firestore","react","typescript","crud","streaming","hooks"],"license":"MIT","_id":"@appboypov/turbo-firestore-api@0.1.2","maintainers":[{"name":"appboypov","email":"info@codaveto.com"}],"dist":{"shasum":"4d34283142ac14a81e498160bf1af253fb984509","tarball":"https://registry.npmjs.org/@appboypov/turbo-firestore-api/-/turbo-firestore-api-0.1.2.tgz","fileCount":6,"integrity":"sha512-rxLlq3+Q5IPoMM0Z4e0QzcbMURG6OH0YoCKZYghtkmNrD8aCLF43vgthmXEfccn8juM1D1o/VM+ECEJh8kUB+w==","signatures":[{"sig":"MEUCIQCLaV98FAmqWDVSi1fLObWguJlmWFdi2hseOpKb+itwCQIgG5gohYqPu5Cw2sIlO6O2Ms5dd+qxM0vp57DU+f//1YQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":228693},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"gitHead":"347a3b10d39ae2ca1ad19c571bbd0587a06f7225","scripts":{"dev":"tsup src/index.ts --format cjs,esm --dts --watch","test":"vitest run","build":"tsup src/index.ts --format cjs,esm --dts","typecheck":"tsc --noEmit"},"_npmUser":{"name":"appboypov","email":"info@codaveto.com"},"_npmVersion":"11.3.0","description":"Type-safe Firestore API wrapper with CRUD, streaming, batch operations for React","directories":{},"_nodeVersion":"24.1.0","dependencies":{"informers":"^0.1.0","veto-mvvm":"^0.1.0","turbo-response":"^0.1.0"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","react":"^18.3.1","vitest":"^1.6.1","firebase":"^10.14.1","typescript":"^5.9.3","@types/react":"^18.3.27"},"peerDependencies":{"react":">=18.0.0","firebase":">=10.0.0"},"_npmOperationalInternal":{"tmp":"tmp/turbo-firestore-api_0.1.2_1764042772940_0.3168713088792525","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@appboypov/turbo-firestore-api","version":"0.1.3","keywords":["firebase","firestore","react","typescript","crud","streaming","hooks"],"license":"MIT","_id":"@appboypov/turbo-firestore-api@0.1.3","maintainers":[{"name":"appboypov","email":"info@codaveto.com"}],"dist":{"shasum":"b4d2ce32d541416fc2b1ac3ece1515a45d9acfa0","tarball":"https://registry.npmjs.org/@appboypov/turbo-firestore-api/-/turbo-firestore-api-0.1.3.tgz","fileCount":6,"integrity":"sha512-kmajPFGmcxVBa3Nyuai1glRS/JD9V+uA21Dn5CH2LWMuzqjSLZRo01nufinkLi0ArYoNZY0arqd0J83Sdr+92Q==","signatures":[{"sig":"MEYCIQCtRu7d+vPAYhSyPRxQ2yv3hvW6A1fagTtkyn91Y+ybGwIhAITZcvgOt1u82GS/76sgH5Z09tGqG0EwiGt4t907fFJS","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":228726},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"gitHead":"972a80b0373805607f40931d76d997559d3f536e","scripts":{"dev":"tsup src/index.ts --format cjs,esm --dts --watch","test":"vitest run","build":"tsup src/index.ts --format cjs,esm --dts","typecheck":"tsc --noEmit"},"_npmUser":{"name":"appboypov","email":"info@codaveto.com"},"_npmVersion":"11.3.0","description":"Type-safe Firestore API wrapper with CRUD, streaming, batch operations for React","directories":{},"_nodeVersion":"24.1.0","dependencies":{"@appboypov/informers":"^0.1.3","@appboypov/veto-mvvm":"^0.1.2","@appboypov/turbo-response":"^0.1.2"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","react":"^18.3.1","vitest":"^1.6.1","firebase":"^10.14.1","typescript":"^5.9.3","@types/react":"^18.3.27"},"peerDependencies":{"react":">=18.0.0","firebase":">=10.0.0"},"_npmOperationalInternal":{"tmp":"tmp/turbo-firestore-api_0.1.3_1764042837639_0.672344755628276","host":"s3://npm-registry-packages-npm-production"}},"0.1.4":{"name":"@appboypov/turbo-firestore-api","version":"0.1.4","keywords":["firebase","firestore","react","typescript","crud","streaming","hooks"],"license":"MIT","_id":"@appboypov/turbo-firestore-api@0.1.4","maintainers":[{"name":"appboypov","email":"info@codaveto.com"}],"dist":{"shasum":"5acba9706fd4038cea0c6523b02d2eca8de8d5b9","tarball":"https://registry.npmjs.org/@appboypov/turbo-firestore-api/-/turbo-firestore-api-0.1.4.tgz","fileCount":6,"integrity":"sha512-JdTnoo5e1RU1dzY8rst/QIhhSIaySd6JEL7zn4i2JNiDd3xwRK8xIn62vaIgIQp5oHFbL3+fGChxGsP4N9KrMg==","signatures":[{"sig":"MEUCIDndXr7FQ9WY7anWZiNjrK4FyYUmDTZWEepxkFMYqvRjAiEAyjDJ5Zpto1uH1XeZI07fZLCDfJEp1uDZZZOvMMvE5Ys=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":228770},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"gitHead":"d83e294fa826a278145784e20b5841d3dfa9a1f2","scripts":{"dev":"tsup src/index.ts --format cjs,esm --dts --watch","test":"vitest run","build":"tsup src/index.ts --format cjs,esm --dts","typecheck":"tsc --noEmit"},"_npmUser":{"name":"appboypov","email":"info@codaveto.com"},"_npmVersion":"11.3.0","description":"Type-safe Firestore API wrapper with CRUD, streaming, batch operations for React","directories":{},"_nodeVersion":"24.1.0","dependencies":{"@appboypov/informers":"^0.1.3","@appboypov/veto-mvvm":"^0.1.2","@appboypov/turbo-response":"^0.1.2"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","react":"^18.3.1","vitest":"^1.6.1","firebase":"^10.14.1","typescript":"^5.9.3","@types/react":"^18.3.27"},"peerDependencies":{"react":">=18.0.0","firebase":">=10.0.0"},"_npmOperationalInternal":{"tmp":"tmp/turbo-firestore-api_0.1.4_1764042984876_0.8385209004473753","host":"s3://npm-registry-packages-npm-production"}},"0.1.5":{"name":"@appboypov/turbo-firestore-api","version":"0.1.5","keywords":["firebase","firestore","react","typescript","crud","streaming","hooks"],"license":"MIT","_id":"@appboypov/turbo-firestore-api@0.1.5","maintainers":[{"name":"appboypov","email":"info@codaveto.com"}],"dist":{"shasum":"450a504a63e056acd242e517795a091fc2a1465c","tarball":"https://registry.npmjs.org/@appboypov/turbo-firestore-api/-/turbo-firestore-api-0.1.5.tgz","fileCount":6,"integrity":"sha512-KMc8jSbKJtEffsqvZqtaKSt1/PeQpK3r1I7jW4C0KUFdnuf5+oiZVncaH30uVceo9Hy/ZrKVge1xobC6Rfgb5g==","signatures":[{"sig":"MEQCIE1G8IYif5PDk55Pm0+zJHZSMdikw1cxT2Vix0V2YCU/AiANabDRhQ6k11q/ttCyxg6yaNSaTVKQZHGbOdrLowQ5TA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":229023},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"gitHead":"d83e294fa826a278145784e20b5841d3dfa9a1f2","scripts":{"dev":"tsup src/index.ts --format cjs,esm --dts --watch","test":"vitest run","build":"tsup src/index.ts --format cjs,esm --dts","typecheck":"tsc --noEmit"},"_npmUser":{"name":"appboypov","email":"info@codaveto.com"},"_npmVersion":"11.3.0","description":"Type-safe Firestore API wrapper with CRUD, streaming, batch operations for React","directories":{},"_nodeVersion":"24.1.0","dependencies":{"@appboypov/informers":"^1.0.0","@appboypov/veto-mvvm":"^1.0.0","@appboypov/turbo-response":"^0.1.2"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","react":"^18.3.1","vitest":"^1.6.1","firebase":"^10.14.1","typescript":"^5.9.3","@types/react":"^18.3.27"},"peerDependencies":{"react":">=18.0.0","firebase":">=10.0.0"},"_npmOperationalInternal":{"tmp":"tmp/turbo-firestore-api_0.1.5_1764635451398_0.20128148417632552","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"@appboypov/turbo-firestore-api","version":"1.0.0","keywords":["firebase","firestore","react","typescript","crud","streaming","hooks"],"license":"MIT","_id":"@appboypov/turbo-firestore-api@1.0.0","maintainers":[{"name":"appboypov","email":"info@codaveto.com"}],"dist":{"shasum":"247751867e766c3b045d422f2dbf4e6ee2a6b90e","tarball":"https://registry.npmjs.org/@appboypov/turbo-firestore-api/-/turbo-firestore-api-1.0.0.tgz","fileCount":6,"integrity":"sha512-T7CoqxH3EFPnRp4dC54HB8gnR3luAvdJBKEy8vMlX3Es78GYK8OZ3gr3QENoV6+waj7Ag84LUzvSOYPIloHmFA==","signatures":[{"sig":"MEYCIQCkE2ZCsJDy9+VDw/PzJPVbvIBSr2BCDl96O3rVhVh+bwIhAPiW+auVr431umX1XtOXGu9E7L/n6/EvWkoEkLB4X0SR","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":228225},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"gitHead":"5e2924689c90a67d40d48e2cc15568c2273f3cc7","scripts":{"dev":"tsup src/index.ts --format cjs,esm --dts --watch","test":"vitest run","build":"tsup src/index.ts --format cjs,esm --dts","typecheck":"tsc --noEmit"},"_npmUser":{"name":"appboypov","email":"info@codaveto.com"},"_npmVersion":"11.3.0","description":"Type-safe Firestore API wrapper with CRUD, streaming, batch operations for React","directories":{},"_nodeVersion":"24.1.0","dependencies":{"@appboypov/informers":"^1.0.0","@appboypov/veto-mvvm":"^1.0.0","@appboypov/turbo-response":"^0.1.2"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","react":"^18.3.1","vitest":"^1.6.1","firebase":"^10.14.1","typescript":"^5.9.3","@types/react":"^18.3.27"},"peerDependencies":{"react":">=18.0.0","firebase":">=10.0.0"},"_npmOperationalInternal":{"tmp":"tmp/turbo-firestore-api_1.0.0_1764682240336_0.3310116007032129","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@appboypov/turbo-firestore-api","version":"1.1.0","description":"Type-safe Firestore API wrapper with CRUD, streaming, batch operations for React","main":"dist/index.js","module":"dist/index.mjs","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"scripts":{"build":"tsup src/index.ts --format cjs,esm --dts","dev":"tsup src/index.ts --format cjs,esm --dts --watch","test":"vitest run","typecheck":"tsc --noEmit"},"keywords":["firebase","firestore","react","typescript","crud","streaming","hooks"],"license":"MIT","dependencies":{"@appboypov/informers":"^1.0.0","@appboypov/turbo-response":"^0.1.2","@appboypov/veto-mvvm":"^1.0.0"},"peerDependencies":{"firebase":">=10.0.0","react":">=18.0.0"},"devDependencies":{"@types/react":"^18.3.27","firebase":"^10.14.1","react":"^18.3.1","tsup":"^8.5.1","typescript":"^5.9.3","vitest":"^1.6.1"},"_id":"@appboypov/turbo-firestore-api@1.1.0","gitHead":"22c5193b631c45c256f8efe1fe8ca5683019ed6f","_nodeVersion":"24.1.0","_npmVersion":"11.3.0","dist":{"integrity":"sha512-vsnmiRo/O/8ePWDiaOMWWayck5KmTKv1qeED98nz/GCkQ0Bht3rUrbbI+j8tybETeW2Bmi+VWnys8dYw3lcdHw==","shasum":"4c43d3e17e1e4d5d6be2dfa810485594534ef283","tarball":"https://registry.npmjs.org/@appboypov/turbo-firestore-api/-/turbo-firestore-api-1.1.0.tgz","fileCount":6,"unpackedSize":283866,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIE07YvYcqVbutaCIXi977H+Rgq/WZRDOkRqsfiKNqjTEAiAUs6/fv/fRd28RJbFI6HLICGIaSORVknomsc36SVKBGA=="}]},"_npmUser":{"name":"appboypov","email":"info@codaveto.com"},"directories":{},"maintainers":[{"name":"appboypov","email":"info@codaveto.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/turbo-firestore-api_1.1.0_1771198515202_0.262091582625011"},"_hasShrinkwrap":false}},"time":{"created":"2025-11-25T03:52:52.836Z","modified":"2026-02-15T23:35:15.467Z","0.1.2":"2025-11-25T03:52:53.206Z","0.1.3":"2025-11-25T03:53:57.833Z","0.1.4":"2025-11-25T03:56:25.061Z","0.1.5":"2025-12-02T00:30:51.629Z","1.0.0":"2025-12-02T13:30:40.512Z","1.1.0":"2026-02-15T23:35:15.342Z"},"license":"MIT","keywords":["firebase","firestore","react","typescript","crud","streaming","hooks"],"description":"Type-safe Firestore API wrapper with CRUD, streaming, batch operations for React","maintainers":[{"name":"appboypov","email":"info@codaveto.com"}],"readme":"# turbo-firestore-api\n\nType-safe Firestore API wrapper with CRUD operations, real-time streaming, and optimistic updates for React.\n\n## Installation\n\n```bash\nnpm install @appboypov/turbo-firestore-api\n```\n\n### Peer Dependencies\n\n```bash\nnpm install firebase react\n```\n\n### Dependencies\n\nThis package uses:\n- `turbo-response` - Type-safe result handling\n- `informers` - Reactive state management\n- `veto-mvvm` - ViewModel pattern and DI\n\n## Features\n\n- **Type-safe CRUD operations** with full TypeScript support\n- **Real-time streaming** with automatic synchronization\n- **Optimistic updates** for instant UI feedback\n- **Automatic rollback** on API failures\n- **Batch operations** for multiple documents\n- **Auth-synced streams** that restart on user changes\n- **O(1) document lookup** with ID-based maps\n- **Local-only operations** for temporary state\n\n## Quick Start\n\n```tsx\nimport { useTurboCollection, useAuthSync } from '@appboypov/turbo-firestore-api';\nimport { TurboFirestoreApi } from '@appboypov/turbo-firestore-api';\nimport { isSuccess } from '@appboypov/turbo-response';\n\n// Define your document type\ninterface UserDto {\n  id: string;\n  name: string;\n  email: string;\n  createdAt: Date;\n  updatedAt: Date;\n  toJson(): Record<string, unknown>;\n}\n\n// Create API instance\nconst usersApi = new TurboFirestoreApi<UserDto>({\n  firestore,\n  collectionPath: () => 'users',\n  fromJson: (json) => ({\n    id: json.id as string,\n    name: json.name as string,\n    email: json.email as string,\n    createdAt: (json.createdAt as any).toDate(),\n    updatedAt: (json.updatedAt as any).toDate(),\n    toJson() {\n      return { name: this.name, email: this.email };\n    },\n  }),\n});\n\n// Use in component\nfunction UserList() {\n  const { docs, isReady, createDoc, updateDoc, deleteDoc } = useTurboCollection({\n    api: usersApi,\n    getUserId: () => auth.currentUser?.uid ?? null,\n  });\n\n  if (!isReady) return <Loading />;\n\n  return (\n    <ul>\n      {docs.map(user => (\n        <li key={user.id}>{user.name}</li>\n      ))}\n    </ul>\n  );\n}\n```\n\n## API\n\n### `useTurboCollection<T>(options): TurboCollectionHook<T>`\n\nHook for managing a Firestore collection with real-time updates and CRUD operations.\n\n#### Options\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `api` | `TurboFirestoreApi<T>` | The API instance for the collection |\n| `getUserId` | `() => string \\| null` | Function to get current user ID |\n| `onDocsChanged` | `(docs: T[]) => void` | Optional callback when documents change |\n| `onError` | `(error: TurboFirestoreException) => void` | Optional error callback |\n| `initialDocs` | `T[]` | Optional initial documents (for SSR) |\n\n#### Returns\n\n**State:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `docs` | `T[]` | Array of all documents |\n| `docsById` | `Map<string, T>` | Documents indexed by ID |\n| `isReady` | `boolean` | Whether initial load is complete |\n| `hasDocs` | `boolean` | Whether collection has any documents |\n\n**Lookup Methods:**\n\n| Method | Type | Description |\n|--------|------|-------------|\n| `findById` | `(id: string) => T` | Find document by ID (throws if not found) |\n| `tryFindById` | `(id: string \\| null) => T \\| null` | Safe lookup (returns null) |\n| `exists` | `(id: string) => boolean` | Check if document exists |\n\n**CRUD Operations (with optimistic updates):**\n\n| Method | Description |\n|--------|-------------|\n| `createDoc(def)` | Create a new document |\n| `updateDoc(id, def)` | Update existing document |\n| `upsertDoc(id, def)` | Create or update document |\n| `deleteDoc(id)` | Delete document |\n\n**Batch Operations:**\n\n| Method | Description |\n|--------|-------------|\n| `createDocs(defs)` | Create multiple documents |\n| `updateDocs(updates)` | Update multiple documents |\n| `deleteDocs(ids)` | Delete multiple documents |\n\n**Local Operations (no API call):**\n\n| Method | Description |\n|--------|-------------|\n| `createLocalDoc(def)` | Create document locally only |\n| `updateLocalDoc(id, def)` | Update document locally only |\n| `deleteLocalDoc(id)` | Delete document locally only |\n\n**Utilities:**\n\n| Method | Description |\n|--------|-------------|\n| `rebuild()` | Force refresh the collection |\n\n#### Example: CRUD Operations\n\n```tsx\nfunction TaskManager() {\n  const { docs, createDoc, updateDoc, deleteDoc, tryFindById } = useTurboCollection({\n    api: tasksApi,\n    getUserId: () => currentUserId,\n    onError: (error) => toast.error(error.message),\n  });\n\n  // Create\n  const handleCreate = async () => {\n    const response = await createDoc((vars) => ({\n      id: vars.id,\n      title: 'New Task',\n      completed: false,\n      createdAt: vars.now,\n      updatedAt: vars.now,\n      createdBy: vars.userId,\n      toJson() {\n        return { title: this.title, completed: this.completed };\n      },\n    }));\n\n    if (isSuccess(response)) {\n      console.log('Created:', response.result.id);\n    }\n  };\n\n  // Update with optimistic UI\n  const toggleComplete = async (taskId: string) => {\n    const task = tryFindById(taskId);\n    if (!task) return;\n\n    // UI updates immediately, rolls back if API fails\n    await updateDoc(taskId, (current, vars) => ({\n      ...current,\n      completed: !current.completed,\n      updatedAt: vars.now,\n    }));\n  };\n\n  // Delete\n  const handleDelete = async (taskId: string) => {\n    await deleteDoc(taskId);\n  };\n\n  return (\n    <ul>\n      {docs.map(task => (\n        <li key={task.id}>\n          <input\n            type=\"checkbox\"\n            checked={task.completed}\n            onChange={() => toggleComplete(task.id)}\n          />\n          {task.title}\n          <button onClick={() => handleDelete(task.id)}>Delete</button>\n        </li>\n      ))}\n      <button onClick={handleCreate}>Add Task</button>\n    </ul>\n  );\n}\n```\n\n#### Example: Batch Operations\n\n```tsx\nfunction BulkActions() {\n  const { docs, updateDocs, deleteDocs } = useTurboCollection({\n    api: itemsApi,\n    getUserId: () => currentUserId,\n  });\n\n  const markAllComplete = async () => {\n    const updates = docs\n      .filter(item => !item.completed)\n      .map(item => ({\n        id: item.id,\n        def: (current, vars) => ({\n          ...current,\n          completed: true,\n          updatedAt: vars.now,\n        }),\n      }));\n\n    await updateDocs(updates);\n  };\n\n  const deleteSelected = async (selectedIds: string[]) => {\n    await deleteDocs(selectedIds);\n  };\n\n  return (\n    <div>\n      <button onClick={markAllComplete}>Complete All</button>\n    </div>\n  );\n}\n```\n\n### `useAuthSync<T>(options): AuthSyncHook`\n\nHook for synchronizing data streams with Firebase Auth state. Automatically manages stream lifecycle based on user authentication.\n\n#### Options\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `auth` | `Auth` | Firebase Auth instance |\n| `stream` | `(user: User) => Unsubscribe` | Function to setup stream for user |\n| `onData` | `(value: T \\| null, user: User \\| null) => void` | Data callback |\n| `onAuth` | `(user: User) => void` | Optional callback when user signs in |\n| `onError` | `(error: TurboFirestoreException) => void` | Optional error callback |\n| `onDone` | `(retryCount, maxRetries) => void` | Optional completion callback |\n| `maxRetries` | `number` | Max retry attempts (default: 20) |\n| `retryDelayMs` | `number` | Delay between retries (default: 1000) |\n\n#### Returns\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `isReady` | `boolean` | Whether stream has completed initial load |\n| `cachedUserId` | `string \\| null` | Current user ID |\n| `isRetrying` | `boolean` | Whether currently retrying |\n| `retryCount` | `number` | Current retry count |\n| `resetAndReinitialize` | `() => Promise<void>` | Reset and restart stream |\n\n#### Example\n\n```tsx\nfunction useUserProfile() {\n  const [profile, setProfile] = useState<UserProfile | null>(null);\n\n  const { isReady, cachedUserId, resetAndReinitialize } = useAuthSync<UserProfile>({\n    auth: getAuth(),\n    stream: (user) => {\n      return profileApi.streamDocByIdWithConverter(\n        user.uid,\n        (data) => setProfile(data),\n        (error) => console.error(error)\n      );\n    },\n    onData: (data, user) => {\n      if (!user) {\n        setProfile(null);\n      }\n    },\n    onAuth: async (user) => {\n      console.log('User signed in:', user.uid);\n    },\n    maxRetries: 5,\n    retryDelayMs: 2000,\n  });\n\n  return { profile, isReady, cachedUserId, resetAndReinitialize };\n}\n\nfunction App() {\n  const { profile, isReady } = useUserProfile();\n\n  if (!isReady) return <Loading />;\n  if (!profile) return <LoginPage />;\n\n  return <Dashboard profile={profile} />;\n}\n```\n\n## Patterns\n\n### Optimistic Updates with Rollback\n\nAll CRUD operations in `useTurboCollection` are optimistic by default:\n\n1. UI updates immediately with the new state\n2. API call is made in the background\n3. If API fails, state is rolled back to previous value\n\n```tsx\nconst toggleTask = async (taskId: string) => {\n  // Immediately updates local state\n  const response = await updateDoc(taskId, (current, vars) => ({\n    ...current,\n    completed: !current.completed,\n    updatedAt: vars.now,\n  }));\n\n  // If API call failed, state was automatically rolled back\n  if (!isSuccess(response)) {\n    toast.error('Failed to update task');\n  }\n};\n```\n\n### Local-Only State for Drafts\n\nUse local operations for temporary state that shouldn't persist:\n\n```tsx\nfunction DraftEditor() {\n  const { createLocalDoc, updateLocalDoc, createDoc } = useTurboCollection({\n    api: draftsApi,\n    getUserId: () => currentUserId,\n  });\n\n  // Create local draft (no API call)\n  const draft = createLocalDoc((vars) => ({\n    id: vars.id,\n    content: '',\n    createdAt: vars.now,\n    updatedAt: vars.now,\n    toJson() { return { content: this.content }; },\n  }));\n\n  // Update locally while typing\n  const handleChange = (content: string) => {\n    updateLocalDoc(draft.id, (current, vars) => ({\n      ...current,\n      content,\n      updatedAt: vars.now,\n    }));\n  };\n\n  // Persist to API when saving\n  const handleSave = async () => {\n    await createDoc((vars) => draft);\n  };\n}\n```\n\n### Multiple Auth-Synced Streams\n\n```tsx\nfunction useAppData() {\n  const auth = getAuth();\n  const [profile, setProfile] = useState(null);\n  const [settings, setSettings] = useState(null);\n\n  const profileSync = useAuthSync({\n    auth,\n    stream: (user) => profileApi.stream(user.uid, setProfile),\n    onData: (data) => !data && setProfile(null),\n  });\n\n  const settingsSync = useAuthSync({\n    auth,\n    stream: (user) => settingsApi.stream(user.uid, setSettings),\n    onData: (data) => !data && setSettings(null),\n  });\n\n  return {\n    profile,\n    settings,\n    isReady: profileSync.isReady && settingsSync.isReady,\n  };\n}\n```\n\n## License\n\nMIT\n","readmeFilename":"README.md"}