{"_id":"@aldian/drivedb","_rev":"3-c1988dc739c09381df39011019359b01","name":"@aldian/drivedb","dist-tags":{"latest":"0.2.1"},"versions":{"0.1.0":{"name":"@aldian/drivedb","version":"0.1.0","keywords":["indexeddb","google-drive","local-first","offline-first","database","gdrive","wal","write-ahead-log","caching","sync","crdt","byod"],"author":{"name":"Aldian Fazrihady","email":"mobile@aldian.net"},"license":"MIT","_id":"@aldian/drivedb@0.1.0","maintainers":[{"name":"aldian","email":"mobile@aldian.net"}],"dist":{"shasum":"a9943ec5f1a20928655ca56d116d9b9d4fb1ba3d","tarball":"https://registry.npmjs.org/@aldian/drivedb/-/drivedb-0.1.0.tgz","fileCount":7,"integrity":"sha512-mQ5qgKpNCW9AM0usjKTEDLNY2IMSe9yHBLvPoD0vsmDN3KI4Bnet9ZfQqFpfklPOJVrZEpP+pMmHKfE8X7DpaA==","signatures":[{"sig":"MEUCIQDqRX/X/kNqFZn61FAyZ2T+yO96CTQ10eDbrhKzrOL4NwIgdFkZu63knKG9P34ZL4UXMqSBpjJ/ibJJkJptjYy09NQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":97637},"main":"./dist/index.cjs","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"470c4755fae3ee89ad083f0c84eeb1bbe53a9760","scripts":{"lint":"eslint \"src/**/*.ts\" \"tests/**/*.ts\"","test":"vitest run --coverage","build":"tsup src/index.ts --format cjs,esm --dts --clean","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm run typecheck && npm run test && npm run build"},"_npmUser":{"name":"aldian","email":"mobile@aldian.net"},"_npmVersion":"10.9.2","description":"Lightweight, local-first, zero-backend database for single-user web applications with IndexedDB caching and Append-Only Delta Log (WAL) Google Drive cloud synchronization.","directories":{},"_nodeVersion":"22.13.1","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.6","eslint":"^9.20.0","vitest":"^3.0.5","typescript":"^5.7.3","@types/node":"^22.13.1","fake-indexeddb":"^6.0.0","@vitest/coverage-v8":"^3.2.7","@typescript-eslint/parser":"^8.24.0","@typescript-eslint/eslint-plugin":"^8.24.0"},"_npmOperationalInternal":{"tmp":"tmp/drivedb_0.1.0_1788443366605_0.4633889949553276","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@aldian/drivedb","version":"0.2.0","keywords":["indexeddb","google-drive","local-first","offline-first","database","gdrive","wal","write-ahead-log","caching","sync","crdt","byod"],"author":{"name":"Aldian Fazrihady","email":"mobile@aldian.net"},"license":"MIT","_id":"@aldian/drivedb@0.2.0","maintainers":[{"name":"aldian","email":"mobile@aldian.net"}],"dist":{"shasum":"3018dc5a8b45468861eb48e5c63acd08cdc4e08e","tarball":"https://registry.npmjs.org/@aldian/drivedb/-/drivedb-0.2.0.tgz","fileCount":7,"integrity":"sha512-9kLVXdckzlviReH7k3cpVeEUoSGZLJGGSsgN4tVbJ385DPr4Ju/eXZwdU+iO08doam2VQX+ykV2mkXQkF5n9hg==","signatures":[{"sig":"MEUCIEk5vqhamGOgECfQSzgu1+zwG28I6DX+xw1Wury8AJbDAiEAnlXgMRv+ravf2qvp1oW1+K/eTOcGenl7H0omx45hpgs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":106123},"main":"./dist/index.cjs","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"627df28f6427079a0f0efcbfa9b191ef8a063370","scripts":{"lint":"eslint \"src/**/*.ts\" \"tests/**/*.ts\"","test":"vitest run --coverage","build":"tsup src/index.ts --format cjs,esm --dts --clean","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm run typecheck && npm run test && npm run build"},"_npmUser":{"name":"aldian","email":"mobile@aldian.net"},"_npmVersion":"10.9.2","description":"Lightweight, local-first, zero-backend database for single-user web applications with IndexedDB caching and Append-Only Delta Log (WAL) Google Drive cloud synchronization.","directories":{},"_nodeVersion":"22.13.1","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.6","eslint":"^9.20.0","vitest":"^3.0.5","typescript":"^5.7.3","@types/node":"^22.13.1","fake-indexeddb":"^6.0.0","@vitest/coverage-v8":"^3.2.7","@typescript-eslint/parser":"^8.24.0","@typescript-eslint/eslint-plugin":"^8.24.0"},"_npmOperationalInternal":{"tmp":"tmp/drivedb_0.2.0_1788446644355_0.09757604401615905","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@aldian/drivedb","version":"0.2.1","description":"Lightweight, local-first, zero-backend database for single-user web applications with IndexedDB caching and Append-Only Delta Log (WAL) Google Drive cloud synchronization.","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"scripts":{"build":"tsup src/index.ts --format cjs,esm --dts --clean","test":"vitest run --coverage","test:watch":"vitest","lint":"eslint \"src/**/*.ts\" \"tests/**/*.ts\"","typecheck":"tsc --noEmit","prepublishOnly":"npm run typecheck && npm run test && npm run build"},"keywords":["indexeddb","google-drive","local-first","offline-first","database","gdrive","wal","write-ahead-log","caching","sync","crdt","byod"],"author":{"name":"Aldian Fazrihady","email":"mobile@aldian.net"},"license":"MIT","devDependencies":{"@types/node":"^22.13.1","@typescript-eslint/eslint-plugin":"^8.24.0","@typescript-eslint/parser":"^8.24.0","@vitest/coverage-v8":"^3.2.7","eslint":"^9.20.0","fake-indexeddb":"^6.0.0","tsup":"^8.3.6","typescript":"^5.7.3","vitest":"^3.0.5"},"_id":"@aldian/drivedb@0.2.1","gitHead":"4e418887d95a251f58a0a4effc665d6ae78f7003","_nodeVersion":"22.13.1","_npmVersion":"10.9.2","dist":{"integrity":"sha512-D8BoWR1GGbG2vVDyISMFeCjQvMEDfGJf9mZ0zU38WSbeLrX7VqYRd1NatqX/sDJ/SvC/Un+TpY0Md20Ws5tCxA==","shasum":"59360b56ab1d06bf0ba5a47a9ddf8217dedf60fd","tarball":"https://registry.npmjs.org/@aldian/drivedb/-/drivedb-0.2.1.tgz","fileCount":7,"unpackedSize":111125,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIHOdpnFnYiGDwx9HiM38jRZe9YTS86+lON1UQBoXn7nRAiB7a0bulhI9zYm8zE/nuq8cJFmsIRBJxD4CitIIwo4NsQ=="}]},"_npmUser":{"name":"aldian","email":"mobile@aldian.net"},"directories":{},"maintainers":[{"name":"aldian","email":"mobile@aldian.net"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/drivedb_0.2.1_1788450160015_0.02901199090160622"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-03T13:49:26.442Z","modified":"2026-09-03T15:42:40.295Z","0.1.0":"2026-09-03T13:49:26.761Z","0.2.0":"2026-09-03T14:44:04.492Z","0.2.1":"2026-09-03T15:42:40.165Z"},"author":{"name":"Aldian Fazrihady","email":"mobile@aldian.net"},"license":"MIT","keywords":["indexeddb","google-drive","local-first","offline-first","database","gdrive","wal","write-ahead-log","caching","sync","crdt","byod"],"description":"Lightweight, local-first, zero-backend database for single-user web applications with IndexedDB caching and Append-Only Delta Log (WAL) Google Drive cloud synchronization.","maintainers":[{"name":"aldian","email":"mobile@aldian.net"}],"readme":"# 🚀 DriveDB\n\n> **Lightweight, local-first, zero-backend database for single-user web applications with IndexedDB caching and Append-Only Delta Log (WAL) Google Drive cloud synchronization.**\n\n[![npm version](https://img.shields.io/badge/npm-v0.1.0-blue.svg)](https://www.npmjs.com/package/@aldian/drivedb)\n[![License: MIT](https://img.shields.io/badge/License-MIT-emerald.svg)](https://opensource.org/licenses/MIT)\n[![TypeScript](https://img.shields.io/badge/TypeScript-Strict-blue.svg)](https://www.typescriptlang.org/)\n[![Tests](https://img.shields.io/badge/tests-passing-brightgreen.svg)]()\n\n---\n\n## 💡 Why DriveDB?\n\nModern web apps and single-user PWAs face a dilemma:\n1. **Set up backend databases** (PostgreSQL, Supabase, Firebase) $\\rightarrow$ server costs, maintenance overhead, and storing user personal data on third-party servers.\n2. **Browser-only storage** (LocalStorage, IndexedDB) $\\rightarrow$ fast and private, but changes are trapped on a single device and vulnerable to cache eviction.\n3. **Monolithic cloud file sync** (e.g. uploading a single 10MB `database.json`) $\\rightarrow$ severe network bottlenecks, race conditions, and heavy mobile battery drain.\n\n### The DriveDB Solution: **Append-Only Delta Log (WAL)**\nDriveDB implements a true database **Write-Ahead Log (WAL)** directly on Google Drive (**Bring Your Own Drive / BYOD**):\n* **Zero Latency ($0\\text{ms}$)**: Reads and queries resolve synchronously from an in-memory cache.\n* **Durable IndexedDB Persistence**: Writes persist asynchronously to local IndexedDB with browser eviction protection (`navigator.storage.persist()`).\n* **Append-Only Cloud Sync**: Instead of rewriting a giant file, DriveDB pushes small, immutable delta log batches (`wal/{timestamp}_{clientId}_{batchId}.json`) to Google Drive.\n* **Zero Overwrite Conflicts**: Because cloud sync *appends* new immutable files instead of modifying existing files, simultaneous edits on phone and laptop **never collide or overwrite each other**.\n* **Automatic Compaction**: Older WAL files are periodically folded into a consolidated `snapshot.json`.\n\n---\n\n## 🏗️ Architecture\n\n```mermaid\nflowchart TD\n    subgraph Client[\"🖥️ Client Browser\"]\n        Action[\"User Action in UI\"] -->|\"Instant (0ms)\"| MemCache[\"In-Memory Cache<br/>(Synchronous get / query / list)\"]\n        MemCache -->|\"Async Persistence\"| IDB[(\"Materialized IndexedDB Store<br/>(Fast offline view)\")]\n        MemCache -->|\"Non-Blocking Mutation\"| Outbox[(\"Local WAL Outbox<br/>(Queued SET & DELETE mutations)\")]\n        Outbox -->|\"Debounced Flush (e.g. 1000ms)\"| Worker[\"Sync Worker\"]\n    end\n\n    subgraph GDrive[\"☁️ Google Drive (Cloud Truth)\"]\n        direction TB\n        AppFolder[\"📁 DriveDB Data/\"]\n        Snapshot[\"📄 snapshot.json<br/>(Compacted Base State)\"]\n        WalFolder[\"📁 wal/\"]\n        BatchA[\"📄 wal_1725350100_devicePhone_b1.json<br/>(Immutable Delta Batch)\"]\n        BatchB[\"📄 wal_1725350200_deviceLaptop_b2.json<br/>(Immutable Delta Batch)\"]\n\n        AppFolder --> Snapshot\n        AppFolder --> WalFolder\n        WalFolder --> BatchA\n        WalFolder --> BatchB\n    end\n\n    Worker -->|\"Google Drive v3 REST API<br/>(Immutable Multipart Upload)\"| WalFolder\n    Snapshot -.->|\"Periodic Compaction\"| WalFolder\n```\n\n---\n\n## ✨ Features\n\n- ⚡ **Zero-Latency ($0\\text{ms}$)**: Reads and queries resolve directly from in-memory state.\n- 📜 **Append-Only Delta Log (WAL)**: Only sends tiny mutation deltas over the wire instead of re-uploading the entire database.\n- 🛡️ **Zero Overwrite Conflicts**: Client syncs append immutable log files. HTTP 412/409 conflicts and race conditions are eliminated.\n- 🔄 **Deterministic Replay & LWW**: New remote WAL batches replay in timestamp order with Last-Write-Wins (LWW) conflict convergence.\n- 🪦 **Tombstone Deletion**: Deleting records registers a `DELETE` mutation in the WAL, ensuring deletions propagate cleanly across devices.\n- 🗜️ **Automatic Snapshot Compaction**: Periodically merges accumulated WAL logs into a single `snapshot.json` and archives old logs.\n- 📡 **Cross-Tab Synchronization**: Native `BroadcastChannel` support keeps multiple open tabs in sync without redundant network calls.\n- 📦 **1-Click JSON Backup & Restore**: Direct export and import for complete user data sovereignty.\n- 🔒 **Direct Client-to-Google Communication**: Zero intermediary servers. 100% of data flows directly between the browser and Google's official API.\n\n---\n\n## 📦 Installation\n\n```bash\nnpm install @aldian/drivedb\n# or\npnpm add @aldian/drivedb\n# or\nyarn add @aldian/drivedb\n```\n\n---\n\n## 🚀 Quick Start\n\n### 1. Initialize DriveDB\n```typescript\nimport { DriveDB } from \"@aldian/drivedb\";\n\ninterface Note {\n  title: string;\n  content: string;\n  tags: string[];\n}\n\n// Create and initialize collection\nconst db = new DriveDB<Note>({\n  dbName: \"my_notes_app\",\n  tableName: \"notes\",\n  syncDebounceMs: 1000,\n  gdriveFolderName: \"My Notes App\",\n  // Optional: Supply Google OAuth Access Token (or token getter function)\n  accessToken: () => localStorage.getItem(\"gdrive_token\"),\n});\n\nawait db.init();\n```\n\n### 2. Fast CRUD Operations\n```typescript\n// Create or Update (0ms in-memory update + async IndexedDB durability + WAL log)\nawait db.set(\"note_1\", {\n  title: \"Meeting Notes\",\n  content: \"Discuss roadmap and launch timeline.\",\n  tags: [\"work\", \"planning\"],\n});\n\n// Synchronous 0ms retrieval\nconst note = db.get(\"note_1\");\nconsole.log(note?.data.title); // \"Meeting Notes\"\n\n// Query with predicate filters\nconst workNotes = db.query((doc) => doc.data.tags.includes(\"work\"));\n\n// List all active documents\nconst allNotes = db.list();\n\n// Delete (registers DELETE mutation in WAL for cloud propagation)\nawait db.delete(\"note_1\");\n```\n\n### 3. Google Drive Sync & Status Listeners\n```typescript\n// Subscribe to sync status events\nconst unsubscribe = db.onSyncChange(({ status, error, mutationsSynced }) => {\n  console.log(`Sync status: ${status}`); // \"pending\" | \"syncing\" | \"synced\" | \"error\"\n});\n\n// Update Google OAuth token dynamically upon user sign-in\ndb.setAccessToken(userOAuthAccessToken);\n\n// Trigger manual two-way sync (downloads remote WAL logs & flushes local outbox)\nawait db.sync();\n```\n\n---\n\n## ⚛️ React / Next.js Hook Example\n\n```typescript\nimport { useState, useEffect } from \"react\";\nimport { DriveDB, Document, SyncStatus } from \"@aldian/drivedb\";\n\ninterface Habit {\n  name: string;\n  streak: number;\n}\n\nconst habitsDb = new DriveDB<Habit>({\n  dbName: \"habits_app\",\n  tableName: \"habits\",\n  accessToken: () => localStorage.getItem(\"google_access_token\"),\n});\n\nexport function useHabits() {\n  const [habits, setHabits] = useState<Document<Habit>[]>([]);\n  const [syncStatus, setSyncStatus] = useState<SyncStatus>(\"synced\");\n\n  useEffect(() => {\n    async function setup() {\n      await habitsDb.init();\n      setHabits(habitsDb.list());\n    }\n    setup();\n\n    return habitsDb.onSyncChange((event) => {\n      setSyncStatus(event.status);\n      setHabits(habitsDb.list());\n    });\n  }, []);\n\n  const addHabit = async (id: string, name: string) => {\n    await habitsDb.set(id, { name, streak: 0 });\n    setHabits(habitsDb.list());\n  };\n\n  const deleteHabit = async (id: string) => {\n    await habitsDb.delete(id);\n    setHabits(habitsDb.list());\n  };\n\n  return { habits, addHabit, deleteHabit, syncStatus };\n}\n```\n\n---\n\n## 📖 API Reference\n\n### `new DriveDB<T>(options?: DriveDbOptions)`\n\n| Option | Type | Default | Description |\n| :--- | :--- | :--- | :--- |\n| `dbName` | `string` | `\"drivedb_store\"` | IndexedDB database name |\n| `tableName` | `string` | `\"documents\"` | IndexedDB object store table name |\n| `syncDebounceMs` | `number` | `1000` | Milliseconds to debounce before uploading WAL to Google Drive |\n| `autoSync` | `boolean` | `true` | Automatically trigger cloud sync on local writes |\n| `gdriveFolderName` | `string` | `\"${dbName}_drivedb_<uuid>\"` | Custom target folder name in Google Drive (decided by client) |\n| `gdriveFolderId` | `string` | `undefined` | Optional explicit Google Drive folder ID (e.g. from Drive Picker) |\n| `appendFolderUuid` | `boolean` | `false` | Append short UUID suffix to folder name to guarantee uniqueness |\n| `walFolderName` | `string` | `\"wal\"` | Subfolder name for immutable WAL batches |\n| `snapshotFileName` | `string` | `\"snapshot.json\"` | Consolidated snapshot filename |\n| `maxUncompactedLogs` | `number` | `50` | Max WAL batches before auto-compaction |\n| `enableBroadcastChannel` | `boolean` | `true` | Enable real-time cross-tab updates |\n| `requestPersistence` | `boolean` | `true` | Request `navigator.storage.persist()` on initialization |\n| `clientId` | `string` | *Auto-generated UUID* | Unique client/device identifier |\n| `accessToken` | `string \\| (() => string \\| null)` | `undefined` | Google OAuth access token or getter function |\n\n---\n\n## 🚢 Release & Publishing Guide\n\nReleases are published directly and securely from your local machine.\n\n### Prerequisites (One-Time)\nEnsure you are logged into your npm account in your local terminal:\n```bash\nnpm login\n```\nVerify authentication:\n```bash\nnpm whoami\n# Should output your npm username\n```\n\n> **💡 For Forks & Custom Builds**: If you are forking this repository to customize or publish your own variation, update the `\"name\"` field in `package.json` to your own npm scope (e.g. `\"@<your-username>/drivedb\"`).\n\n---\n\n### Step-by-Step Publishing Workflow\n\n#### 1. (Initial Release) Publish Version 0.1.0\nIf publishing for the first time:\n```bash\nnpm publish --access public\n```\n\n#### 2. (Subsequent Updates) Bump Version & Release\nFor subsequent updates, use semantic versioning:\n```bash\n# 1. Bump the version in package.json and generate a git tag\nnpm version patch   # 0.1.0 -> 0.1.1 (Bug fixes / minor adjustments)\n# or: npm version minor  # 0.1.0 -> 0.2.0 (New backward-compatible features)\n# or: npm version major  # 0.1.0 -> 1.0.0 (Breaking API changes)\n\n# 2. Publish to the public npm registry\nnpm publish --access public\n\n# 3. Push the version bump commit and git tag to GitHub\ngit push origin main --follow-tags\n```\n\n> **🛡️ Built-in Safety Check (`prepublishOnly`)**:\n> The `package.json` includes a `prepublishOnly` lifecycle hook. Whenever you run `npm publish`, npm automatically executes:\n> 1. `tsc --noEmit` (TypeScript typecheck)\n> 2. `vitest run --coverage` (All 31 tests & 92% coverage threshold)\n> 3. `tsup build` (Bundling clean ESM, CJS, and `.d.ts` outputs)\n>\n> If any test or type error occurs, the publish is aborted immediately, preventing broken builds from reaching npm.\n\n---\n\n## 📄 License\n\nMIT © [Aldian Fazrihady](https://github.com/aldian)\n","readmeFilename":"README.md"}