@docstack/pouchdb-adapter-googledrive 0.0.9 → 0.1.0

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.
@@ -1,481 +0,0 @@
1
- {
2
- "cells": [
3
- {
4
- "cell_type": "markdown",
5
- "id": "60e24526",
6
- "metadata": {},
7
- "source": [
8
- "# Debug: Replica Bidirezionale Google Drive PouchDB Adapter\n",
9
- "\n",
10
- "## Problema\n",
11
- "La replica `localDB.replicate.to(googleDriveDB)` funziona ✅ \n",
12
- "Ma `localDB.replicate.from(googleDriveDB)` non sincronizza i dati ❌\n",
13
- "\n",
14
- "Questo notebook diagnostica il flusso di replicazione per identificare dove il blocco si verifica."
15
- ]
16
- },
17
- {
18
- "cell_type": "markdown",
19
- "id": "3796436e",
20
- "metadata": {},
21
- "source": [
22
- "## 1️⃣ Section: Setup e Configurazione Replica\n",
23
- "\n",
24
- "### Aggiungi questo codice al tuo componente per diagnosticare il problema:"
25
- ]
26
- },
27
- {
28
- "cell_type": "code",
29
- "execution_count": null,
30
- "id": "5a4749c9",
31
- "metadata": {
32
- "vscode": {
33
- "languageId": "javascript"
34
- }
35
- },
36
- "outputs": [],
37
- "source": [
38
- "// Setup Google Drive Adapter con debug logging\n",
39
- "const plugin = GoogleDriveAdapter({\n",
40
- " accessToken: async () => driveConfig.accessToken,\n",
41
- " folderName: 'my-db',\n",
42
- " pollingIntervalMs: 5000,\n",
43
- " debug: true // 🔍 Abilita logging dettagliato\n",
44
- "});\n",
45
- "\n",
46
- "PouchDB.plugin(plugin);\n",
47
- "\n",
48
- "const googleDriveDB = new PouchDB('paper-drive-db', {\n",
49
- " adapter: 'googledrive'\n",
50
- "});\n",
51
- "\n",
52
- "const localDB = stack?.db;\n",
53
- "\n",
54
- "// 🔍 Aggiungi listener per monitorare gli eventi di replicazione\n",
55
- "console.log('=== SETUP REPLICA BIDIREZIONALE ===');\n",
56
- "\n",
57
- "// Replica TO (localDB → googleDriveDB) - Funzionante ✅\n",
58
- "const repTo = localDB.replicate.to(googleDriveDB, { live: true, retry: true });\n",
59
- "repTo.on('change', (info) => {\n",
60
- " console.log('📤 TO: change event', { \n",
61
- " direction: 'local→gdrive',\n",
62
- " docs: info.docs?.length,\n",
63
- " ok: info.ok\n",
64
- " });\n",
65
- "});\n",
66
- "repTo.on('complete', (info) => {\n",
67
- " console.log('✅ TO: replication complete', info);\n",
68
- "});\n",
69
- "repTo.on('error', (err) => {\n",
70
- " console.error('❌ TO: replication error', err);\n",
71
- "});\n",
72
- "\n",
73
- "// Replica FROM (googleDriveDB → localDB) - Problema ❌\n",
74
- "const repFrom = localDB.replicate.from(googleDriveDB, { live: true, retry: true });\n",
75
- "repFrom.on('change', (info) => {\n",
76
- " console.log('📥 FROM: change event', { \n",
77
- " direction: 'gdrive→local',\n",
78
- " docs: info.docs?.length,\n",
79
- " ok: info.ok\n",
80
- " });\n",
81
- "});\n",
82
- "repFrom.on('complete', (info) => {\n",
83
- " console.log('✅ FROM: replication complete', info);\n",
84
- "});\n",
85
- "repFrom.on('error', (err) => {\n",
86
- " console.error('❌ FROM: replication error', err);\n",
87
- "});"
88
- ]
89
- },
90
- {
91
- "cell_type": "markdown",
92
- "id": "56686c46",
93
- "metadata": {},
94
- "source": [
95
- "## 2️⃣ Section: Diagnosi - Verifica Flusso di Replicazione\n",
96
- "\n",
97
- "### Controlla le sequenze e i metadati del database"
98
- ]
99
- },
100
- {
101
- "cell_type": "code",
102
- "execution_count": null,
103
- "id": "fb620ba8",
104
- "metadata": {
105
- "vscode": {
106
- "languageId": "javascript"
107
- }
108
- },
109
- "outputs": [],
110
- "source": [
111
- "// Confronta lo stato dei due database\n",
112
- "async function compareDatabases() {\n",
113
- " const localInfo = await localDB.info();\n",
114
- " const googleDriveInfo = await googleDriveDB.info();\n",
115
- " \n",
116
- " console.log('📊 DATABASE INFO COMPARISON:');\n",
117
- " console.log('Local DB:', {\n",
118
- " doc_count: localInfo.doc_count,\n",
119
- " update_seq: localInfo.update_seq,\n",
120
- " db_name: localInfo.db_name\n",
121
- " });\n",
122
- " console.log('Google Drive DB:', {\n",
123
- " doc_count: googleDriveInfo.doc_count,\n",
124
- " update_seq: googleDriveInfo.update_seq,\n",
125
- " db_name: googleDriveInfo.db_name\n",
126
- " });\n",
127
- " \n",
128
- " return { localInfo, googleDriveInfo };\n",
129
- "}\n",
130
- "\n",
131
- "// Esegui la diagnosi dopo 25 secondi (dopo che il polling ha avuto tempo di rilevare)\n",
132
- "setTimeout(async () => {\n",
133
- " console.log('\\n=== DIAGNOSI DOPO 25 SEC ===\\n');\n",
134
- " \n",
135
- " const { localInfo, googleDriveInfo } = await compareDatabases();\n",
136
- " \n",
137
- " // 🔍 Verifica se le sequenze sono sincronizzate\n",
138
- " if (localInfo.update_seq === googleDriveInfo.update_seq) {\n",
139
- " console.log('✅ Sequenze sincronizzate');\n",
140
- " } else {\n",
141
- " console.warn('⚠️ Sequenze NON sincronizzate:', {\n",
142
- " local: localInfo.update_seq,\n",
143
- " googleDrive: googleDriveInfo.update_seq,\n",
144
- " diff: googleDriveInfo.update_seq - localInfo.update_seq\n",
145
- " });\n",
146
- " }\n",
147
- " \n",
148
- " // 🔍 Verifica il documento specifico su Google Drive\n",
149
- " try {\n",
150
- " const docOnDrive = await googleDriveDB.get('Notebook-0');\n",
151
- " console.log('✅ Notebook-0 trovato su Google Drive:', {\n",
152
- " _id: docOnDrive._id,\n",
153
- " _rev: docOnDrive._rev\n",
154
- " });\n",
155
- " } catch (err) {\n",
156
- " console.error('❌ Notebook-0 NON trovato su Google Drive:', err.message);\n",
157
- " }\n",
158
- " \n",
159
- " // 🔍 Verifica il documento su Local DB\n",
160
- " try {\n",
161
- " const docLocal = await localDB.get('Notebook-0');\n",
162
- " console.log('✅ Notebook-0 trovato su Local DB:', {\n",
163
- " _id: docLocal._id,\n",
164
- " _rev: docLocal._rev\n",
165
- " });\n",
166
- " } catch (err) {\n",
167
- " console.error('❌ Notebook-0 NON trovato su Local DB:', err.message);\n",
168
- " }\n",
169
- " \n",
170
- "}, 25000);"
171
- ]
172
- },
173
- {
174
- "cell_type": "markdown",
175
- "id": "5015299b",
176
- "metadata": {},
177
- "source": [
178
- "## 3️⃣ Section: Debug - Polling e Change Detection\n",
179
- "\n",
180
- "### Il problema probabilmente è nel meccanismo di Polling\n",
181
- "\n",
182
- "Verifica se il polling sta effettivamente rilevando i cambiamenti da Google Drive:"
183
- ]
184
- },
185
- {
186
- "cell_type": "code",
187
- "execution_count": null,
188
- "id": "5d57b557",
189
- "metadata": {
190
- "vscode": {
191
- "languageId": "javascript"
192
- }
193
- },
194
- "outputs": [],
195
- "source": [
196
- "// 🔍 Monitora i cambiamenti rilevati tramite _changes API\n",
197
- "async function debugChangesAPI() {\n",
198
- " console.log('\\n=== TESTING _CHANGES API ===\\n');\n",
199
- " \n",
200
- " // Ottieni gli ultimi 10 cambiamenti dal Google Drive DB\n",
201
- " try {\n",
202
- " const changes = await googleDriveDB.changes({\n",
203
- " include_docs: true,\n",
204
- " descending: true,\n",
205
- " limit: 10\n",
206
- " });\n",
207
- " \n",
208
- " console.log('Recent changes from Google Drive DB:', {\n",
209
- " results: changes.results.length,\n",
210
- " last_seq: changes.last_seq,\n",
211
- " results: changes.results.map(r => ({\n",
212
- " id: r.id,\n",
213
- " seq: r.seq,\n",
214
- " changes: r.changes,\n",
215
- " deleted: r.deleted,\n",
216
- " hasDoc: !!r.doc\n",
217
- " }))\n",
218
- " });\n",
219
- " \n",
220
- " return changes;\n",
221
- " } catch (err) {\n",
222
- " console.error('❌ Error fetching changes from Google Drive DB:', err);\n",
223
- " }\n",
224
- "}\n",
225
- "\n",
226
- "// Testa la _changes API subito\n",
227
- "await debugChangesAPI();\n",
228
- "\n",
229
- "// Testa di nuovo dopo 10 secondi per vedere se il polling ha rilevato nuovi cambiamenti\n",
230
- "setTimeout(async () => {\n",
231
- " console.log('\\n=== TESTING _CHANGES API AFTER POLLING ===\\n');\n",
232
- " await debugChangesAPI();\n",
233
- "}, 10000);"
234
- ]
235
- },
236
- {
237
- "cell_type": "markdown",
238
- "id": "39d29701",
239
- "metadata": {},
240
- "source": [
241
- "## 🚨 POSSIBILI CULPRIT IDENTIFICATI\n",
242
- "\n",
243
- "### Problema 1: _changes feed con `include_docs` potrebbe essere lento\n",
244
- "\n",
245
- "Nel tuo `adapter.ts`, quando `opts.include_docs` è vero, il metodo `_changes` deve:\n",
246
- "- Scaricare il CORPO di ogni documento (lento)\n",
247
- "- Questo blocca la replica\n",
248
- "\n",
249
- "**Diagnosi:** \n",
250
- "Controlla la console del browser. Se vedi molti `fetchFile` calls per ogni change, questo è il collo di bottiglia.\n",
251
- "\n",
252
- "### Problema 2: Change detection non rispetta il `since` parameter\n",
253
- "\n",
254
- "La replica PouchDB invia `since: lastSeq` per ricevere solo i cambiamenti NUOVI. \n",
255
- "Se `_changes` non filtra correttamente, la replica non rileva i nuovi documenti.\n",
256
- "\n",
257
- "### Problema 3: Live listener potrebbe non essere chiamato\n",
258
- "\n",
259
- "Nel `DriveHandler`, il polling rileva modifiche a `_meta.json`, ma:\n",
260
- "- Il polling ha un delay di 5 secondi\n",
261
- "- La replicazione potrebbe timeout prima di ricevere notifiche"
262
- ]
263
- },
264
- {
265
- "cell_type": "markdown",
266
- "id": "de439ea3",
267
- "metadata": {},
268
- "source": [
269
- "## 4️⃣ Section: Troubleshooting - Conflitti di Revisione\n",
270
- "\n",
271
- "### Verifica validazione revisioni"
272
- ]
273
- },
274
- {
275
- "cell_type": "code",
276
- "execution_count": null,
277
- "id": "08da678c",
278
- "metadata": {
279
- "vscode": {
280
- "languageId": "javascript"
281
- }
282
- },
283
- "outputs": [],
284
- "source": [
285
- "// Verifica i conflitti di revisione durante la replica\n",
286
- "async function checkReplicationConflicts() {\n",
287
- " console.log('\\n=== CHECKING REPLICATION CONFLICTS ===\\n');\n",
288
- " \n",
289
- " // Leggi lo stesso documento da entrambi i database\n",
290
- " const docId = 'Notebook-0';\n",
291
- " \n",
292
- " try {\n",
293
- " const localDoc = await localDB.get(docId);\n",
294
- " console.log('Local Doc:', { _id: localDoc._id, _rev: localDoc._rev });\n",
295
- " } catch (e) {\n",
296
- " console.warn('Local doc not found');\n",
297
- " }\n",
298
- " \n",
299
- " try {\n",
300
- " const remoteDoc = await googleDriveDB.get(docId);\n",
301
- " console.log('Remote Doc:', { _id: remoteDoc._id, _rev: remoteDoc._rev });\n",
302
- " } catch (e) {\n",
303
- " console.warn('Remote doc not found');\n",
304
- " }\n",
305
- " \n",
306
- " // 🔍 Verifica _conflicts (documenti con conflitti di merge)\n",
307
- " try {\n",
308
- " const allDocs = await localDB.allDocs({ conflicts: true });\n",
309
- " const conflictedDocs = allDocs.rows.filter(r => r.value.conflicts && r.value.conflicts.length > 0);\n",
310
- " if (conflictedDocs.length > 0) {\n",
311
- " console.warn('⚠️ Found documents with conflicts:', conflictedDocs);\n",
312
- " } else {\n",
313
- " console.log('✅ No conflicts found in local DB');\n",
314
- " }\n",
315
- " } catch (e) {\n",
316
- " console.error('Error checking conflicts:', e);\n",
317
- " }\n",
318
- "}\n",
319
- "\n",
320
- "// Esegui il check dopo 30 secondi\n",
321
- "setTimeout(async () => {\n",
322
- " await checkReplicationConflicts();\n",
323
- "}, 30000);"
324
- ]
325
- },
326
- {
327
- "cell_type": "markdown",
328
- "id": "52a770f6",
329
- "metadata": {},
330
- "source": [
331
- "## 5️⃣ Section: Validazione - Test della Replica Bidirezionale\n",
332
- "\n",
333
- "### Test Manuale: Crea un documento su Google Drive e verifica la sincronizzazione"
334
- ]
335
- },
336
- {
337
- "cell_type": "code",
338
- "execution_count": null,
339
- "id": "0f47da2f",
340
- "metadata": {
341
- "vscode": {
342
- "languageId": "javascript"
343
- }
344
- },
345
- "outputs": [],
346
- "source": [
347
- "// 🧪 Test Completo della Replica Bidirezionale\n",
348
- "\n",
349
- "async function runFullReplicationTest() {\n",
350
- " console.log('\\n=== FULL REPLICATION TEST ===\\n');\n",
351
- " \n",
352
- " // Step 1: Leggi lo stato iniziale\n",
353
- " const localInfoBefore = await localDB.info();\n",
354
- " const googleDriveInfoBefore = await googleDriveDB.info();\n",
355
- " \n",
356
- " console.log('Initial State:');\n",
357
- " console.log(' Local:', { doc_count: localInfoBefore.doc_count, seq: localInfoBefore.update_seq });\n",
358
- " console.log(' Google Drive:', { doc_count: googleDriveInfoBefore.doc_count, seq: googleDriveInfoBefore.update_seq });\n",
359
- " \n",
360
- " // Step 2: Crea un nuovo documento SU GOOGLE DRIVE\n",
361
- " const testDocId = `test-sync-${Date.now()}`;\n",
362
- " const testDoc = {\n",
363
- " _id: testDocId,\n",
364
- " title: 'Test Document for Replication',\n",
365
- " createdAt: new Date().toISOString(),\n",
366
- " source: 'google-drive-db'\n",
367
- " };\n",
368
- " \n",
369
- " try {\n",
370
- " const saveResult = await googleDriveDB.put(testDoc);\n",
371
- " console.log('\\n✅ Created document on Google Drive:', { id: saveResult.id, rev: saveResult.rev });\n",
372
- " } catch (err) {\n",
373
- " console.error('❌ Failed to create document on Google Drive:', err);\n",
374
- " return;\n",
375
- " }\n",
376
- " \n",
377
- " // Step 3: Attendi il polling + replicazione (20 secondi dovrebbe essere sufficiente)\n",
378
- " console.log('\\n⏳ Waiting for polling and replication (20 seconds)...');\n",
379
- " \n",
380
- " await new Promise(resolve => setTimeout(resolve, 20000));\n",
381
- " \n",
382
- " // Step 4: Verifica se il documento è arrivato al Local DB\n",
383
- " try {\n",
384
- " const docOnLocal = await localDB.get(testDocId);\n",
385
- " console.log('\\n✅✅✅ SUCCESS! Document replicated to local DB:', {\n",
386
- " id: docOnLocal._id,\n",
387
- " rev: docOnLocal._rev,\n",
388
- " title: docOnLocal.title\n",
389
- " });\n",
390
- " } catch (err) {\n",
391
- " console.error('\\n❌ FAILED! Document NOT found on local DB:', err.message);\n",
392
- " \n",
393
- " // Diagnosi aggiuntiva\n",
394
- " console.log('\\n🔍 DIAGNOSTIC INFO:');\n",
395
- " const localInfoAfter = await localDB.info();\n",
396
- " const googleDriveInfoAfter = await googleDriveDB.info();\n",
397
- " \n",
398
- " console.log('After Test:');\n",
399
- " console.log(' Local:', { doc_count: localInfoAfter.doc_count, seq: localInfoAfter.update_seq });\n",
400
- " console.log(' Google Drive:', { doc_count: googleDriveInfoAfter.doc_count, seq: googleDriveInfoAfter.update_seq });\n",
401
- " \n",
402
- " // Verifica che il doc sia effettivamente su Google Drive\n",
403
- " try {\n",
404
- " const verifyDoc = await googleDriveDB.get(testDocId);\n",
405
- " console.log('✅ Document IS on Google Drive:', {\n",
406
- " id: verifyDoc._id,\n",
407
- " rev: verifyDoc._rev\n",
408
- " });\n",
409
- " console.log('❌ BUT NOT replicated to local. Issue: REPLICATION FROM REMOTE NOT WORKING');\n",
410
- " } catch (e) {\n",
411
- " console.error('❌ Document not even on Google Drive. Save failed?');\n",
412
- " }\n",
413
- " }\n",
414
- "}\n",
415
- "\n",
416
- "// Esegui il test\n",
417
- "await runFullReplicationTest();"
418
- ]
419
- },
420
- {
421
- "cell_type": "markdown",
422
- "id": "051a4d4b",
423
- "metadata": {},
424
- "source": [
425
- "---\n",
426
- "\n",
427
- "## 🔧 SOLUZIONI CONSIGLIATE\n",
428
- "\n",
429
- "Basandoti sui risultati del test sopra, ecco i passaggi per risolvere il problema:\n",
430
- "\n",
431
- "### Se il test fallisce (documento non replica from remote):\n",
432
- "\n",
433
- "**Soluzione 1: Aumenta il pollingIntervalMs**\n",
434
- "```javascript\n",
435
- "pollingIntervalMs: 2000 // Invece di 5000 - controlla più frequentemente\n",
436
- "```\n",
437
- "\n",
438
- "**Soluzione 2: Riduci il timeout di replicazione**\n",
439
- "```javascript\n",
440
- "const repFrom = localDB.replicate.from(googleDriveDB, { \n",
441
- " live: true, \n",
442
- " retry: true,\n",
443
- " timeout: 15000 // Aumenta timeout da default 30s\n",
444
- "});\n",
445
- "```\n",
446
- "\n",
447
- "**Soluzione 3: Aggiungi log nel adapter._changes in adapter.ts**\n",
448
- "\n",
449
- "Modifica `src/adapter.ts` linea ~428 per aggiungere debug:\n",
450
- "```typescript\n",
451
- "api._changes = function (opts: any): { cancel: () => void } {\n",
452
- " console.log('[_changes] Called with opts:', {\n",
453
- " since: opts.since,\n",
454
- " limit: opts.limit,\n",
455
- " live: opts.live,\n",
456
- " include_docs: !!opts.include_docs\n",
457
- " });\n",
458
- " // ...\n",
459
- "```\n",
460
- "\n",
461
- "**Soluzione 4: Verifica che il live listener sia correttamente configurato**\n",
462
- "\n",
463
- "Nel `DriveHandler.load()`, assicurati che il polling chiami `notifyListeners()` ogni volta che rileva cambiamenti:\n",
464
- "```typescript\n",
465
- "if (metaFile.modifiedTime !== this.metaModifiedTime) {\n",
466
- " this.log('Polling detected change!', metaFile.modifiedTime);\n",
467
- " await this.load();\n",
468
- " this.notifyListeners(); // <-- DEVE ESSERE CHIAMATO\n",
469
- "}\n",
470
- "```"
471
- ]
472
- }
473
- ],
474
- "metadata": {
475
- "language_info": {
476
- "name": "python"
477
- }
478
- },
479
- "nbformat": 4,
480
- "nbformat_minor": 5
481
- }
@@ -1,115 +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.
55
-
56
- ## 3. Testing with Local Express Server
57
-
58
- The adapter includes a built-in test mode that emulates the Google Drive API using a local Express server. This allows for full integration testing without needing real Google Cloud credentials or network calls.
59
-
60
- ### Configuration
61
- To enable the test emulator:
62
-
63
- ```typescript
64
- const db = new PouchDB('testdb', {
65
- adapter: 'googledrive',
66
- testMode: true,
67
- // testServerUrl: 'http://localhost:3000' // Optional, defaults to localhost:3000
68
- });
69
- ```
70
-
71
- ### Test Server
72
- The `TestServer` class (exported in `test-src/server.ts`) provides:
73
- - In-memory metadata storage
74
- - Local file system storage for content (defaults to `.test-drive-root` directory)
75
- - Emulation of `files.list`, `files.get`, `files.create` (multipart), `files.update`, and `files.delete`.
76
-
77
- ### Example Usage
78
- ```typescript
79
- import { TestServer } from './tests-src/server';
80
-
81
- const server = new TestServer(3000);
82
- await server.start();
83
-
84
- // Run PouchDB operations...
85
-
86
-
87
- ## 4. Production Testing (Real Google Drive API)
88
-
89
- To verify the adapter against the actual Google Drive service, you can run the production test suite. This requires a valid Google OAuth2 Access Token.
90
-
91
- ### A. Environment Setup
92
- Create a `.env` file in the project root (see `.env.example`):
93
- ```env
94
- GOOGLE_ACCESS_TOKEN=your_temporary_access_token_here
95
- ```
96
-
97
- ### B. Obtaining a Token via OAuth1/2 Playground
98
- The easiest way to get a temporary token for manual testing:
99
- 1. Go to the [Google OAuth2 Playground](https://developers.google.com/oauthplayground/).
100
- 2. **Step 1 (Select & authorize APIs)**:
101
- * Find "Drive API v3" in the list.
102
- * Select the scope: `https://www.googleapis.com/auth/drive.file` (this is the recommended scope as it only allows the app to see files it creates).
103
- * Click **Authorize APIs** and sign in with your Google account.
104
- 3. **Step 2 (Exchange authorization code for tokens)**:
105
- * Click **Exchange authorization code for tokens**.
106
- 4. **Step 3 (Configure request to API)**:
107
- * Copy the **Access Token** string.
108
- * Paste it into your `.env` file as `GOOGLE_ACCESS_TOKEN`.
109
-
110
- ### C. Running the Tests
111
- Execute the following command to run the tests in production mode:
112
- ```bash
113
- npm run test:prod
114
- ```
115
- This command sets `TEST_ENV=production`, which tells the test runner to skip the local Express emulator and use the real Google Drive endpoints with your provided token.
@@ -1,75 +0,0 @@
1
- # Promise Support Documentation
2
-
3
- The adapter now fully supports all three PouchDB call patterns:
4
-
5
- ## 1. Callback Pattern
6
- ```javascript
7
- db.get('mydoc', function(err, doc) {
8
- if (err) { return console.log(err); }
9
- // handle doc
10
- });
11
- ```
12
-
13
- ## 2. Promise Pattern
14
- ```javascript
15
- db.get('mydoc').then(function (doc) {
16
- // handle doc
17
- }).catch(function (err) {
18
- console.log(err);
19
- });
20
- ```
21
-
22
- ## 3. Async/Await Pattern
23
- ```javascript
24
- try {
25
- const doc = await db.get('mydoc');
26
- // handle doc
27
- } catch (err) {
28
- console.log(err);
29
- }
30
- ```
31
-
32
- ## Methods Updated
33
-
34
- The following adapter methods now support all three patterns:
35
-
36
- ### Query Methods
37
- - `_get()` / `get()` - Get a single document
38
- - `_allDocs()` / `allDocs()` - Get all documents
39
- - `_getLocal()` - Get a local document
40
- - `_getRevisionTree()` - Get revision tree (internal)
41
-
42
- ### Mutation Methods
43
- - `_bulkDocs()` / `bulkDocs()` - Bulk document operations
44
- - `_bulkGet()` / `bulkGet()` - Bulk get operation
45
- - `_putLocal()` - Put a local document
46
- - `_removeLocal()` - Remove a local document
47
-
48
- ### Maintenance Methods
49
- - `_compact()` - Compact the database
50
-
51
- ## Implementation Details
52
-
53
- Each method now:
54
- 1. **Always returns a Promise** (even when a callback is provided)
55
- 2. **Calls the callback if provided** for backward compatibility
56
- 3. **Properly propagates errors** through both callbacks and rejections
57
- 4. **Handles callback overloading** (when `opts` is actually the callback)
58
-
59
- This allows PouchDB and its replication engine to use the adapter with promises and async/await, fixing the error:
60
- ```
61
- TypeError: can't access property "then", db.get(...) is undefined
62
- ```
63
-
64
- ## Migration Path
65
-
66
- Your existing callback-based code continues to work:
67
- ```javascript
68
- // Old callback pattern - still works!
69
- db.get('doc1', (err, doc) => {
70
- if (err) console.error(err);
71
- else console.log(doc);
72
- });
73
- ```
74
-
75
- You can gradually migrate to promises/async-await as needed.