@docstack/pouchdb-adapter-googledrive 0.0.1 β†’ 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # PouchDB Adapter for Google Drive
2
2
 
3
- A PouchDB adapter that uses Google Drive as a backend storage.
3
+ A persistent, serverless PouchDB adapter that uses Google Drive as a backend storage. Designed for high concurrency, large datasets (via lazy loading), and offline resilience.
4
4
 
5
5
  ## Features
6
6
 
7
- - **Append-Only Log**: Uses an efficient append-only log pattern for fast writes.
8
- - **Auto-Compaction**: Automatically merges logs into a snapshot when thresholds are met.
9
- - **Offline/Sync**: Supports PouchDB's replication and sync capabilities.
10
- - **TypeScript**: Written in TypeScript with full type definitions.
7
+ - **πŸš€ Append-Only Log**: Uses an efficient append-only log pattern for fast, conflict-free writes.
8
+ - **⚑ Lazy Loading**: Optimizes memory and bandwidth by loading only the **Index** into memory. Document bodies are fetched on-demand.
9
+ - **πŸ›‘οΈ Optimistic Concurrency Control**: Uses ETag-based locking on metadata to prevent race conditions.
10
+ - **πŸ“¦ Auto-Compaction**: Automatically merges logs for performance.
11
11
 
12
12
  ## Installation
13
13
 
@@ -17,62 +17,46 @@ npm install @docstack/pouchdb-adapter-googledrive
17
17
 
18
18
  ## Usage
19
19
 
20
+ The adapter is initialized as a plugin with your Google Drive configuration.
21
+
20
22
  ```typescript
21
23
  import PouchDB from 'pouchdb-core';
22
24
  import GoogleDriveAdapter from '@docstack/pouchdb-adapter-googledrive';
23
25
  import { google } from 'googleapis';
24
26
 
25
- // Register the adapter
26
- PouchDB.plugin(GoogleDriveAdapter);
27
-
28
- // Setup Google Drive Client (Authenticated)
29
- const oauth2Client = new google.auth.OAuth2(
30
- YOUR_CLIENT_ID,
31
- YOUR_CLIENT_SECRET,
32
- YOUR_REDIRECT_URL
33
- );
27
+ // 1. Setup Google Drive Client
28
+ const oauth2Client = new google.auth.OAuth2(CLIENT_ID, SECRET, REDIRECT);
34
29
  oauth2Client.setCredentials({ access_token: '...' });
35
-
36
30
  const drive = google.drive({ version: 'v3', auth: oauth2Client });
37
31
 
38
- // Create Database
39
- const db = new PouchDB('my-drive-db', {
40
- adapter: 'googledrive',
41
- drive: drive, // valid googleapis Drive instance
42
- folderId: '...', // Optional: Folder ID to store database files
43
- folderName: 'my-db', // Optional: Folder name (created if not exists)
44
- pollingIntervalMs: 2000, // Optional: Check for remote changes
45
- compactionThreshold: 50 // Optional: Number of changes before auto-compaction
32
+ // 2. Initialize the Adapter Plugin with Config
33
+ const adapterPlugin = GoogleDriveAdapter({
34
+ drive: drive,
35
+ folderName: 'my-app-db-folder', // Root folder
36
+ pollingIntervalMs: 2000
46
37
  });
47
- ```
48
38
 
49
- ## How it works
39
+ // 3. Register Plugin
40
+ PouchDB.plugin(adapterPlugin);
41
+ // Also needs replication plugin if using replicate()
42
+ // PouchDB.plugin(require('pouchdb-replication'));
50
43
 
51
- The adapter implements an **append-only log** pattern for efficiency and reliability:
52
-
53
- 1. **Folder Structure**: Each database is a folder in Google Drive.
54
- 2. **`_meta.json`**: Tracks the current sequence number and active log files.
55
- 3. **`snapshot.json`**: Contains the full database state at a specific sequence point.
56
- 4. **`changes-{timestamp}.ndjson`**: New changes are appended to these newline-delimited JSON files.
57
-
58
- ### Compaction
44
+ // 4. Create Database
45
+ // No need to pass 'drive' here anymore!
46
+ const db = new PouchDB('user_db', {
47
+ adapter: 'googledrive'
48
+ });
59
49
 
60
- To prevent the change logs from growing indefinitely, the adapter performs auto-compaction:
61
- - When the number of pending changes exceeds `compactionThreshold` (default: 100).
62
- - Or when the log file size exceeds `compactionSizeThreshold` (default: 1MB).
50
+ await db.post({ title: 'Hello World' });
51
+ ```
63
52
 
64
- Compaction merges the snapshot and all change logs into a new `snapshot.json` and deletes old log files.
53
+ ## Architecture
65
54
 
66
- ## Testing
55
+ The adapter implements a **"Remote-First"** architecture:
56
+ - **Lazy Loading**: `db.get(id)` fetches data on-demand from Drive.
57
+ - **Caching**: Changes are indexed locally but bodies are cached in an LRU cache.
58
+ - **Resilience**: Writes use optimistic locking to handle multi-client concurrency safer.
67
59
 
68
- To run the tests, you need to provide Google Drive API credentials.
60
+ ## License
69
61
 
70
- 1. Copy `.env.example` to `.env`:
71
- ```bash
72
- cp .env.example .env
73
- ```
74
- 2. Fill in your Google Cloud credentials in `.env`.
75
- 3. Run the tests:
76
- ```bash
77
- npm test
78
- ```
62
+ CC-BY-SA-4.0
package/lib/index.d.ts CHANGED
@@ -1 +1,10 @@
1
- export default function (PouchDB: any): void;
1
+ import { GoogleDriveAdapterOptions } from './types';
2
+ export * from './types';
3
+ /**
4
+ * Google Drive Adapter Plugin Factory
5
+ *
6
+ * Usage:
7
+ * const plugin = GoogleDriveAdapter({ drive: myDriveClient, ... });
8
+ * PouchDB.plugin(plugin);
9
+ */
10
+ export default function GoogleDriveAdapter(config: GoogleDriveAdapterOptions): (PouchDB: any) => void;
package/lib/index.js CHANGED
@@ -1,7 +1,55 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
2
16
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = default_1;
17
+ exports.default = GoogleDriveAdapter;
4
18
  const adapter_1 = require("./adapter");
5
- function default_1(PouchDB) {
6
- PouchDB.adapter('googledrive', (0, adapter_1.GoogleDriveAdapter)(PouchDB), true);
19
+ // Export types
20
+ __exportStar(require("./types"), exports);
21
+ /**
22
+ * Google Drive Adapter Plugin Factory
23
+ *
24
+ * Usage:
25
+ * const plugin = GoogleDriveAdapter({ drive: myDriveClient, ... });
26
+ * PouchDB.plugin(plugin);
27
+ */
28
+ function GoogleDriveAdapter(config) {
29
+ return function (PouchDB) {
30
+ // Get the base adapter constructor (scoped to this PouchDB instance)
31
+ const BaseAdapter = (0, adapter_1.GoogleDriveAdapter)(PouchDB);
32
+ // Create a wrapper constructor that injects the config
33
+ function ConfiguredAdapter(opts, callback) {
34
+ // Merge factory config with constructor options
35
+ // Constructor options take precedence (overrides)
36
+ const mergedOpts = Object.assign({}, config, opts);
37
+ // Call the base adapter
38
+ BaseAdapter.call(this, mergedOpts, callback);
39
+ }
40
+ // Copy static properties required by PouchDB
41
+ // @ts-ignore
42
+ ConfiguredAdapter.valid = BaseAdapter.valid;
43
+ // @ts-ignore
44
+ ConfiguredAdapter.use_prefix = BaseAdapter.use_prefix;
45
+ // Register the adapter manually
46
+ // We use PouchDB.adapters object directly to avoid using the .adapter() method
47
+ if (PouchDB.adapters) {
48
+ PouchDB.adapters['googledrive'] = ConfiguredAdapter;
49
+ }
50
+ else {
51
+ // Fallback/Warning if adapters object is somehow missing (should not happen in core)
52
+ console.warn('PouchDB.adapters not found, unable to register googledrive adapter');
53
+ }
54
+ };
7
55
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docstack/pouchdb-adapter-googledrive",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "PouchDB adapter for Google Drive",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
package/DOCUMENTATION.md DELETED
@@ -1,54 +0,0 @@
1
- # Architecture & Design Documentation
2
-
3
- ## 1. Core Principles
4
- The `pouchdb-adapter-googledrive` implementation is built on three core pillars to ensure data integrity and performance on a file-based remote storage system.
5
-
6
- ### A. Append-Only Log (Storage)
7
- Instead of modifying a single database file (which is prone to conflicts), we use an **Append-Only** strategy.
8
- - **Changes**: Every write operation (or batch of writes) creates a **new, immutable file** (e.g., `changes-{seq}-{uuid}.ndjson`).
9
- - **Snapshots**: Periodically, the log is compacted into a `snapshot` file.
10
- - **Benefit**: Historical data is preserved until compaction, and file-write conflicts are minimized.
11
-
12
- ### B. Optimistic Concurrency Control (OCC)
13
- To prevent race conditions (two clients writing simultaneously), we use **ETag-based locking** on a single entry point: `_meta.json`.
14
- - **The Lock**: `_meta.json` holds the current Sequence Number and the list of active log files.
15
- - **The Protocol**:
16
- 1. Reader fetches `_meta.json` and its `ETag`.
17
- 2. Writer prepares a new change file and uploads it (orphaned initially).
18
- 3. Writer attempts to update `_meta.json` with the new file reference, sending `If-Match: <Old-ETag>`.
19
- 4. **Success**: The change is now officially part of the DB.
20
- 5. **Failure (412/409)**: Another client updated the DB. The writer deletes its orphaned file, pulls the new state, and retries the logical operation.
21
-
22
- ### C. Remote-First "Lazy" Loading (Memory Optimization)
23
- To support large databases without exhausting client memory, we separate **Metadata** from **Content**.
24
-
25
- #### Storage Structure
26
- - `_meta.json`: Root pointer. Small.
27
- - `snapshot-index.json`: A map of `{ docId: { rev, filePointer } }`. Medium size (~100 bytes/doc). Loaded at startup.
28
- - `snapshot-data.json`: The actual document bodies. Large. **Never fully loaded.**
29
- - `changes-*.ndjson`: Recent updates.
30
-
31
- #### Client Startup Sequence
32
- 1. **Fetch Meta**: Download `_meta.json` and get the `snapshotIndexId`.
33
- 2. **Fetch Index**: Download `snapshot-index.json`. This builds the "Revision Tree" in memory.
34
- 3. **Replay Logs**: Download and parse only the small `changes-*.ndjson` files created since the snapshot to update the in-memory Index.
35
- 4. **Ready**: The client is now ready to query keys. No document content has been downloaded yet.
36
-
37
- #### On-Demand Usage
38
- - **`db.get(id)`**:
39
- 1. Look up `id` in the **Memory Index** to find the `filePointer`.
40
- 2. Check **LRU Cache**.
41
- 3. If missing, fetch the specific file/range from Google Drive.
42
- - **`db.allDocs({ keys: [...] })`**: Efficiently looks up pointers and fetches only requested docs.
43
-
44
- ## 2. Technical Patterns
45
-
46
- ### Atomic Compaction
47
- Compaction is a critical maintenance task that merges the `snapshot-data` with recent `changes` to create a new baseline.
48
- - **Safe**: Limits memory usage by streaming/batching.
49
- - **Atomic**: Uploads the new snapshot as a new file. Swaps the pointer in `_meta.json` using OCC.
50
- - **Zero-Downtime**: Clients can continue reading/writing to the old logs while compaction runs. Writes that happen *during* compaction are detected via the ETag check, causing the compaction to abort/retry safeley.
51
-
52
- ### Conflict Handling
53
- - **PouchDB Level**: Standard CouchDB revision conflicts (409) are preserved. A "winner" is chosen deterministically, but conflicting revisions are kept in the tree (requires `snapshot-index` to store the full revision tree, not just the winner).
54
- - **Adapter Level**: Drive API 409s handling (retry logic) ensures the transport layer is reliable.
package/error.log DELETED
@@ -1,21 +0,0 @@
1
- FAIL tests/adapter.test.ts
2
- ΓùÅ Test suite failed to run
3
-
4
- tests/adapter.test.ts:51:13 - error TS2353: Object literal may only specify known properties, and 'drive' does not exist in type 'DatabaseConfiguration'.
5
-
6
- 51 drive: drive,
7
- ~~~~~
8
- tests/adapter.test.ts:57:21 - error TS2339: Property 'backend_adapter' does not exist on type 'DatabaseInfo'.
9
-
10
- 57 expect(info.backend_adapter).toBe('googledrive');
11
- ~~~~~~~~~~~~~~~
12
- tests/adapter.test.ts:65:24 - error TS2339: Property 'title' does not exist on type 'IdMeta & GetMeta'.
13
-
14
- 65 expect(fetched.title).toBe('Start Wars');
15
- ~~~~~
16
-
17
- Test Suites: 1 failed, 1 total
18
- Tests: 0 total
19
- Snapshots: 0 total
20
- Time: 14.401 s
21
- Ran all test suites matching tests/adapter.test.ts.