@docstack/pouchdb-adapter-googledrive 0.0.6 → 0.0.9
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/.env.example +3 -0
- package/README.md +72 -72
- package/compaction_test.log +725 -0
- package/docs/DEBUG_BIDIRECTIONAL_REPLICATION.ipynb +481 -0
- package/{DOCUMENTATION.md → docs/DOCUMENTATION.md} +61 -0
- package/docs/PROMISE_SUPPORT.md +75 -0
- package/docs/REPLICATION_ISSUE.md +63 -0
- package/emulation-test.log +163 -0
- package/failing_test.log +31 -0
- package/final_test.log +2620 -0
- package/jest.config.js +8 -8
- package/lib/adapter.d.ts +3 -0
- package/lib/adapter.js +373 -163
- package/lib/cache.d.ts +0 -0
- package/lib/cache.js +0 -0
- package/lib/client.d.ts +5 -0
- package/lib/client.js +43 -20
- package/lib/drive.d.ts +12 -2
- package/lib/drive.js +373 -189
- package/lib/index.d.ts +0 -0
- package/lib/index.js +4 -3
- package/lib/types.d.ts +6 -0
- package/lib/types.js +0 -0
- package/live_test.log +1209 -0
- package/package.json +47 -40
- package/test_output.log +563 -0
- package/test_output_2.log +1408 -0
- package/test_output_3.log +2445 -0
package/lib/adapter.js
CHANGED
|
@@ -6,8 +6,13 @@ const drive_1 = require("./drive");
|
|
|
6
6
|
* Schedule a function to run asynchronously.
|
|
7
7
|
*/
|
|
8
8
|
function nextTick(fn) {
|
|
9
|
-
|
|
9
|
+
if (typeof fn === 'function') {
|
|
10
|
+
queueMicrotask(fn);
|
|
11
|
+
}
|
|
10
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* TODO: Implement dynamic method signature, since PouchDB CRUD methods can be called as promise, callback or (?)
|
|
15
|
+
*/
|
|
11
16
|
/**
|
|
12
17
|
* GoogleDriveAdapter - PouchDB adapter for Google Drive storage.
|
|
13
18
|
* Updated for Lazy Loading (Async Access).
|
|
@@ -22,20 +27,73 @@ function GoogleDriveAdapter(PouchDB) {
|
|
|
22
27
|
let instanceId;
|
|
23
28
|
let db;
|
|
24
29
|
// Initialize DriveHandler
|
|
30
|
+
console.log('Initializing DriveHandler with options:', adapterOpts);
|
|
25
31
|
db = new drive_1.DriveHandler(adapterOpts, name);
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
// Initialize instanceId synchronously to avoid race conditions during replication setup
|
|
33
|
+
instanceId = 'gdrive-' + name + '-' + Date.now().toString(36);
|
|
34
|
+
// Polyfill activeTasks to ensure replication works even if PouchDB core doesn't attach it
|
|
35
|
+
if (!api.activeTasks) {
|
|
36
|
+
class ActiveTasks {
|
|
37
|
+
constructor() {
|
|
38
|
+
this._tasks = {};
|
|
39
|
+
}
|
|
40
|
+
add(task) {
|
|
41
|
+
const id = Date.now().toString(36) + Math.random().toString(36).substring(2, 5);
|
|
42
|
+
this._tasks[id] = task;
|
|
43
|
+
this._tasks[id].created_at = new Date().toISOString();
|
|
44
|
+
this._tasks[id].updated_at = new Date().toISOString();
|
|
45
|
+
return id;
|
|
46
|
+
}
|
|
47
|
+
remove(id, err) {
|
|
48
|
+
delete this._tasks[id];
|
|
49
|
+
}
|
|
50
|
+
update(id, update) {
|
|
51
|
+
if (this._tasks[id]) {
|
|
52
|
+
Object.assign(this._tasks[id], update);
|
|
53
|
+
this._tasks[id].updated_at = new Date().toISOString();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
get(id) {
|
|
57
|
+
return this._tasks[id];
|
|
58
|
+
}
|
|
59
|
+
list() {
|
|
60
|
+
return Object.values(this._tasks);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// @ts-ignore
|
|
64
|
+
api.activeTasks = new ActiveTasks();
|
|
32
65
|
}
|
|
66
|
+
// Wrap callback to ensure it's only called once
|
|
67
|
+
let callbackCalled = false;
|
|
68
|
+
const onceCallback = (err, api) => {
|
|
69
|
+
if (callbackCalled)
|
|
70
|
+
return;
|
|
71
|
+
callbackCalled = true;
|
|
72
|
+
callback(err, api);
|
|
73
|
+
};
|
|
74
|
+
const debug = adapterOpts.debug;
|
|
75
|
+
const log = (...args) => { if (debug)
|
|
76
|
+
console.log(`[googledrive-adapter] [${name}]`, ...args); };
|
|
77
|
+
log('Initializing with options:', {
|
|
78
|
+
folderId: adapterOpts.folderId,
|
|
79
|
+
folderName: adapterOpts.folderName,
|
|
80
|
+
pollingIntervalMs: adapterOpts.pollingIntervalMs,
|
|
81
|
+
debug: adapterOpts.debug
|
|
82
|
+
});
|
|
33
83
|
// Load data from Drive and initialize
|
|
34
84
|
db.load().then(() => {
|
|
85
|
+
log('Database loaded');
|
|
35
86
|
afterDBCreated();
|
|
36
87
|
}).catch((err) => {
|
|
37
|
-
|
|
88
|
+
log('Load error', err);
|
|
89
|
+
onceCallback(err);
|
|
38
90
|
});
|
|
91
|
+
// After database is initialized
|
|
92
|
+
function afterDBCreated() {
|
|
93
|
+
nextTick(function () {
|
|
94
|
+
onceCallback(null, api);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
39
97
|
// ============ PouchDB Adapter API Methods ============
|
|
40
98
|
api._remote = false;
|
|
41
99
|
api.type = function () {
|
|
@@ -46,77 +104,115 @@ function GoogleDriveAdapter(PouchDB) {
|
|
|
46
104
|
};
|
|
47
105
|
// Info now must be async-ish (calculated from Index)
|
|
48
106
|
api._info = function (callback) {
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
107
|
+
// Need to handle promise if it returns one
|
|
108
|
+
const promise = Promise.resolve().then(async () => {
|
|
109
|
+
const keys = await db.getIndexKeys();
|
|
110
|
+
const docCount = keys.length; // Approximate (doesn't account for deleted unless filtered)
|
|
111
|
+
// Filter deleted for accurate count
|
|
112
|
+
let alive = 0;
|
|
113
|
+
for (const k of keys) {
|
|
114
|
+
const entry = db.getIndexEntry(k);
|
|
115
|
+
if (entry && !entry.deleted)
|
|
116
|
+
alive++;
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
db_name: name,
|
|
120
|
+
doc_count: alive,
|
|
121
|
+
update_seq: db.seq,
|
|
122
|
+
backend_adapter: 'googledrive'
|
|
123
|
+
};
|
|
65
124
|
});
|
|
125
|
+
if (callback) {
|
|
126
|
+
promise.then(res => callback(null, res)).catch(err => callback(err));
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
return promise;
|
|
130
|
+
}
|
|
66
131
|
};
|
|
132
|
+
api.info = api._info;
|
|
67
133
|
// Get a single document by ID (Async fetch)
|
|
68
134
|
api._get = function (id, opts, callback) {
|
|
69
135
|
if (typeof opts === 'function') {
|
|
70
136
|
callback = opts;
|
|
71
137
|
opts = {};
|
|
72
138
|
}
|
|
73
|
-
|
|
139
|
+
// Ensure opts is always an object
|
|
140
|
+
if (!opts || typeof opts !== 'object') {
|
|
141
|
+
opts = {};
|
|
142
|
+
}
|
|
143
|
+
// PouchDB sometimes asks for metadata only (revs, revs_info)
|
|
144
|
+
log('_get id:', id, 'opts:', JSON.stringify(opts));
|
|
145
|
+
const promise = db.get(id).then(doc => {
|
|
74
146
|
if (!doc) {
|
|
75
|
-
|
|
147
|
+
log('_get missing', id);
|
|
148
|
+
const err = {
|
|
76
149
|
status: 404,
|
|
77
150
|
error: true,
|
|
78
151
|
name: 'not_found',
|
|
79
|
-
message: 'missing'
|
|
80
|
-
|
|
81
|
-
|
|
152
|
+
message: 'missing'
|
|
153
|
+
};
|
|
154
|
+
if (callback)
|
|
155
|
+
callback(err);
|
|
156
|
+
throw err;
|
|
82
157
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
158
|
+
// Support open_revs for bulkGet shim compatibility
|
|
159
|
+
if (opts.open_revs) {
|
|
160
|
+
log('_get handling open_revs for:', id);
|
|
161
|
+
// We currently only store the winning revision.
|
|
162
|
+
// If open_revs='all' or includes our rev, return it.
|
|
163
|
+
// TODO: Robust conflict support would check exact rev matching.
|
|
164
|
+
const result = [{ ok: doc }];
|
|
165
|
+
if (callback)
|
|
166
|
+
callback(null, result);
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
log('_get returning standard doc for:', id);
|
|
170
|
+
// If only rev was requested? (Internal optimization)
|
|
171
|
+
// PouchDB core handles this if we return the full doc.
|
|
172
|
+
const response = doc;
|
|
173
|
+
if (callback)
|
|
174
|
+
callback(null, response);
|
|
175
|
+
return response;
|
|
176
|
+
}).catch(e => {
|
|
177
|
+
log('_get error', e);
|
|
178
|
+
if (callback)
|
|
179
|
+
callback(e);
|
|
180
|
+
throw e;
|
|
92
181
|
});
|
|
182
|
+
return promise;
|
|
93
183
|
};
|
|
184
|
+
api.get = api._get;
|
|
94
185
|
// Get all documents (Lazy stream or fetch)
|
|
95
186
|
api._allDocs = function (opts, callback) {
|
|
96
187
|
if (typeof opts === 'function') {
|
|
97
188
|
callback = opts;
|
|
98
189
|
opts = {};
|
|
99
190
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
191
|
+
// Ensure opts is always an object
|
|
192
|
+
if (!opts || typeof opts !== 'object') {
|
|
193
|
+
opts = {};
|
|
194
|
+
}
|
|
195
|
+
const promise = (async () => {
|
|
196
|
+
const keys = await db.getIndexKeys();
|
|
197
|
+
const total = keys.length; // Total keys (including deleted?)
|
|
198
|
+
let startIndex = opts.skip || 0;
|
|
199
|
+
let limit = typeof opts.limit === 'number' ? opts.limit : keys.length;
|
|
200
|
+
let filteredKeys = keys;
|
|
201
|
+
if (opts.startkey)
|
|
202
|
+
filteredKeys = filteredKeys.filter(k => k >= opts.startkey);
|
|
203
|
+
if (opts.endkey)
|
|
204
|
+
filteredKeys = filteredKeys.filter(k => k <= opts.endkey);
|
|
205
|
+
if (opts.key)
|
|
206
|
+
filteredKeys = filteredKeys.filter(k => k === opts.key);
|
|
207
|
+
if (opts.keys)
|
|
208
|
+
filteredKeys = opts.keys;
|
|
209
|
+
filteredKeys.sort();
|
|
210
|
+
if (opts.descending)
|
|
211
|
+
filteredKeys.reverse();
|
|
212
|
+
const sliced = filteredKeys.slice(startIndex, startIndex + limit);
|
|
213
|
+
// Fetch actual docs if needed
|
|
214
|
+
if (opts.include_docs) {
|
|
215
|
+
const docs = await db.getMulti(sliced);
|
|
120
216
|
const rows = sliced.map((id, i) => {
|
|
121
217
|
const doc = docs[i];
|
|
122
218
|
const entry = db.getIndexEntry(id);
|
|
@@ -142,31 +238,78 @@ function GoogleDriveAdapter(PouchDB) {
|
|
|
142
238
|
};
|
|
143
239
|
if (opts.update_seq)
|
|
144
240
|
result.update_seq = db.seq;
|
|
145
|
-
|
|
146
|
-
}
|
|
241
|
+
return result;
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
// Index only (Fast!)
|
|
245
|
+
const rows = sliced.map(id => {
|
|
246
|
+
const entry = db.getIndexEntry(id);
|
|
247
|
+
if (!entry || entry.deleted)
|
|
248
|
+
return { key: id, error: 'not_found' };
|
|
249
|
+
return {
|
|
250
|
+
id,
|
|
251
|
+
key: id,
|
|
252
|
+
value: { rev: entry.rev }
|
|
253
|
+
};
|
|
254
|
+
});
|
|
255
|
+
const result = {
|
|
256
|
+
total_rows: total,
|
|
257
|
+
offset: startIndex,
|
|
258
|
+
rows
|
|
259
|
+
};
|
|
260
|
+
if (opts.update_seq)
|
|
261
|
+
result.update_seq = db.seq;
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
})();
|
|
265
|
+
if (callback) {
|
|
266
|
+
promise.then(res => callback(null, res)).catch(err => callback(err));
|
|
147
267
|
}
|
|
148
268
|
else {
|
|
149
|
-
|
|
150
|
-
const rows = sliced.map(id => {
|
|
151
|
-
const entry = db.getIndexEntry(id);
|
|
152
|
-
if (!entry || entry.deleted)
|
|
153
|
-
return { key: id, error: 'not_found' };
|
|
154
|
-
return {
|
|
155
|
-
id,
|
|
156
|
-
key: id,
|
|
157
|
-
value: { rev: entry.rev }
|
|
158
|
-
};
|
|
159
|
-
});
|
|
160
|
-
const result = {
|
|
161
|
-
total_rows: total,
|
|
162
|
-
offset: startIndex,
|
|
163
|
-
rows
|
|
164
|
-
};
|
|
165
|
-
if (opts.update_seq)
|
|
166
|
-
result.update_seq = db.seq;
|
|
167
|
-
nextTick(() => callback(null, result));
|
|
269
|
+
return promise;
|
|
168
270
|
}
|
|
169
271
|
};
|
|
272
|
+
api.allDocs = api._allDocs;
|
|
273
|
+
// Bulk Get optimization for Replication
|
|
274
|
+
api._bulkGet = function (opts, callback) {
|
|
275
|
+
const docs = opts.docs;
|
|
276
|
+
const ids = docs.map((d) => d.id);
|
|
277
|
+
return db.getMulti(ids).then(results => {
|
|
278
|
+
const response = {
|
|
279
|
+
results: ids.map((id, i) => {
|
|
280
|
+
const doc = results[i];
|
|
281
|
+
const requestedRev = docs[i].rev;
|
|
282
|
+
const entry = db.getIndexEntry(id);
|
|
283
|
+
let docResult;
|
|
284
|
+
if (!doc || (requestedRev && doc._rev !== requestedRev)) {
|
|
285
|
+
docResult = {
|
|
286
|
+
error: {
|
|
287
|
+
status: 404,
|
|
288
|
+
error: true,
|
|
289
|
+
name: 'not_found',
|
|
290
|
+
message: 'missing'
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
docResult = { ok: doc };
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
id,
|
|
299
|
+
docs: [docResult]
|
|
300
|
+
};
|
|
301
|
+
})
|
|
302
|
+
};
|
|
303
|
+
if (callback)
|
|
304
|
+
callback(null, response);
|
|
305
|
+
return response;
|
|
306
|
+
}).catch((err) => {
|
|
307
|
+
if (callback)
|
|
308
|
+
callback(err);
|
|
309
|
+
throw err;
|
|
310
|
+
});
|
|
311
|
+
};
|
|
312
|
+
api.bulkGet = api._bulkGet;
|
|
170
313
|
// Bulk document operations
|
|
171
314
|
api._bulkDocs = function (req, opts, callback) {
|
|
172
315
|
const docs = req.docs;
|
|
@@ -226,13 +369,21 @@ function GoogleDriveAdapter(PouchDB) {
|
|
|
226
369
|
results.push({ ok: true, id, rev: newRev });
|
|
227
370
|
}
|
|
228
371
|
}
|
|
372
|
+
log('_bulkDocs flushing', changes.length, 'changes');
|
|
229
373
|
// Append changes to log
|
|
230
|
-
db.appendChanges(changes).then(() => {
|
|
231
|
-
|
|
374
|
+
const promise = db.appendChanges(changes).then(() => {
|
|
375
|
+
if (callback)
|
|
376
|
+
nextTick(() => callback(null, results));
|
|
377
|
+
return results;
|
|
232
378
|
}).catch((err) => {
|
|
233
|
-
|
|
379
|
+
log('_bulkDocs error', err);
|
|
380
|
+
if (callback)
|
|
381
|
+
callback(err);
|
|
382
|
+
throw err;
|
|
234
383
|
});
|
|
384
|
+
return promise;
|
|
235
385
|
};
|
|
386
|
+
api.bulkDocs = api._bulkDocs;
|
|
236
387
|
// Changes feed
|
|
237
388
|
api._changes = function (opts) {
|
|
238
389
|
opts = Object.assign({}, opts);
|
|
@@ -242,50 +393,52 @@ function GoogleDriveAdapter(PouchDB) {
|
|
|
242
393
|
const results = [];
|
|
243
394
|
let lastSeq = since;
|
|
244
395
|
let complete = false;
|
|
245
|
-
//
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
396
|
+
// Setup live listener FIRST (before emitting changes)
|
|
397
|
+
let cancelLive;
|
|
398
|
+
let liveListener;
|
|
399
|
+
if (opts.live) {
|
|
400
|
+
log('_changes setting up live listener');
|
|
401
|
+
liveListener = (changedDocs) => {
|
|
402
|
+
if (complete)
|
|
403
|
+
return;
|
|
404
|
+
for (const id of Object.keys(changedDocs)) {
|
|
405
|
+
if (id.startsWith('_local/'))
|
|
406
|
+
continue;
|
|
407
|
+
const entry = db.getIndexEntry(id);
|
|
408
|
+
if (entry && entry.seq > lastSeq) {
|
|
409
|
+
const change = {
|
|
410
|
+
id: id,
|
|
411
|
+
seq: entry.seq,
|
|
412
|
+
changes: [{ rev: entry.rev }]
|
|
413
|
+
};
|
|
414
|
+
if (opts.include_docs) {
|
|
415
|
+
db.get(id).then(doc => {
|
|
416
|
+
change.doc = doc;
|
|
417
|
+
if (opts.onChange)
|
|
418
|
+
opts.onChange(change);
|
|
419
|
+
lastSeq = Math.max(lastSeq, change.seq);
|
|
420
|
+
}).catch(e => log('Live change body fetch error', e));
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
if (opts.onChange)
|
|
424
|
+
opts.onChange(change);
|
|
425
|
+
lastSeq = Math.max(lastSeq, change.seq);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
277
428
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
// ... This requires rewrite for async iteration ...
|
|
429
|
+
};
|
|
430
|
+
cancelLive = db.onChange(liveListener);
|
|
281
431
|
}
|
|
282
|
-
//
|
|
432
|
+
// Process initial changes
|
|
283
433
|
async function processChangesAsync() {
|
|
284
|
-
|
|
434
|
+
log('_changes processing since', since, 'limit', limit, 'live', !!opts.live);
|
|
435
|
+
const keys = await db.getIndexKeys();
|
|
285
436
|
let processed = 0;
|
|
286
437
|
for (const id of keys) {
|
|
287
438
|
if (complete || processed >= limit)
|
|
288
439
|
break;
|
|
440
|
+
if (id.startsWith('_local/'))
|
|
441
|
+
continue;
|
|
289
442
|
const entry = db.getIndexEntry(id);
|
|
290
443
|
if (!entry || entry.seq <= since)
|
|
291
444
|
continue;
|
|
@@ -295,7 +448,12 @@ function GoogleDriveAdapter(PouchDB) {
|
|
|
295
448
|
changes: [{ rev: entry.rev }]
|
|
296
449
|
};
|
|
297
450
|
if (opts.include_docs) {
|
|
298
|
-
|
|
451
|
+
try {
|
|
452
|
+
change.doc = await db.get(id);
|
|
453
|
+
}
|
|
454
|
+
catch (e) {
|
|
455
|
+
log('_changes include_docs error', e);
|
|
456
|
+
}
|
|
299
457
|
}
|
|
300
458
|
if (opts.onChange)
|
|
301
459
|
opts.onChange(change);
|
|
@@ -304,53 +462,67 @@ function GoogleDriveAdapter(PouchDB) {
|
|
|
304
462
|
processed++;
|
|
305
463
|
lastSeq = Math.max(lastSeq, entry.seq);
|
|
306
464
|
}
|
|
307
|
-
|
|
465
|
+
// ✅ Call opts.complete() ONLY for non-live modes
|
|
466
|
+
// PouchDB replication will infinite-loop reconnect if we call complete() on a live feed
|
|
467
|
+
if (opts.complete && !complete && !opts.live) {
|
|
468
|
+
log('_changes calling complete callback with', { results_count: results.length, last_seq: lastSeq });
|
|
308
469
|
opts.complete(null, { results, last_seq: lastSeq });
|
|
309
470
|
}
|
|
310
471
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
nextTick(() => {
|
|
318
|
-
processChangesAsync().catch(err => {
|
|
319
|
-
console.error('Changes feed error', err);
|
|
320
|
-
if (opts.complete)
|
|
321
|
-
opts.complete(err);
|
|
322
|
-
});
|
|
472
|
+
// Start processing (async) which will wait for load() via getIndexKeys()
|
|
473
|
+
processChangesAsync().catch(err => {
|
|
474
|
+
log('_changes error', err);
|
|
475
|
+
complete = true;
|
|
476
|
+
if (opts.complete)
|
|
477
|
+
opts.complete(err);
|
|
323
478
|
});
|
|
324
479
|
return {
|
|
325
480
|
cancel() {
|
|
481
|
+
log('_changes cancel');
|
|
326
482
|
complete = true;
|
|
483
|
+
if (cancelLive)
|
|
484
|
+
cancelLive();
|
|
327
485
|
}
|
|
328
486
|
};
|
|
329
487
|
};
|
|
330
488
|
// Manual compaction trigger
|
|
331
489
|
api._compact = function (callback) {
|
|
332
|
-
db.compact().then(() => {
|
|
333
|
-
|
|
490
|
+
const promise = db.compact().then(() => {
|
|
491
|
+
const result = { ok: true };
|
|
492
|
+
if (callback)
|
|
493
|
+
callback(null, result);
|
|
494
|
+
return result;
|
|
334
495
|
}).catch((err) => {
|
|
335
|
-
callback
|
|
496
|
+
if (callback)
|
|
497
|
+
callback(err);
|
|
498
|
+
throw err;
|
|
336
499
|
});
|
|
500
|
+
return promise;
|
|
337
501
|
};
|
|
338
502
|
api._getRevisionTree = function (docId, callback) {
|
|
339
|
-
db.
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
503
|
+
const entry = db.getIndexEntry(docId);
|
|
504
|
+
if (!entry) {
|
|
505
|
+
return callback({ status: 404, error: true, name: 'not_found', message: 'missing' });
|
|
506
|
+
}
|
|
507
|
+
// Return a minimal tree based on the known winning revision
|
|
508
|
+
const revNum = parseInt(entry.rev.split('-')[0], 10);
|
|
509
|
+
const revHash = entry.rev.split('-')[1];
|
|
510
|
+
const revTree = [{
|
|
511
|
+
pos: revNum,
|
|
512
|
+
ids: [revHash, { status: 'available' }, []]
|
|
513
|
+
}];
|
|
514
|
+
callback(null, revTree);
|
|
349
515
|
};
|
|
350
516
|
api._close = function (callback) {
|
|
351
517
|
db.stopPolling();
|
|
352
|
-
|
|
518
|
+
if (typeof callback === 'function') {
|
|
519
|
+
nextTick(callback);
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
return Promise.resolve();
|
|
523
|
+
}
|
|
353
524
|
};
|
|
525
|
+
api.close = api._close;
|
|
354
526
|
api._destroy = function (opts, callback) {
|
|
355
527
|
if (typeof opts === 'function') {
|
|
356
528
|
callback = opts;
|
|
@@ -358,16 +530,28 @@ function GoogleDriveAdapter(PouchDB) {
|
|
|
358
530
|
}
|
|
359
531
|
db.stopPolling();
|
|
360
532
|
if (opts.deleteFolder) {
|
|
361
|
-
db.deleteFolder().then(() => {
|
|
362
|
-
|
|
533
|
+
return db.deleteFolder().then(() => {
|
|
534
|
+
if (typeof callback === 'function') {
|
|
535
|
+
callback(null, { ok: true });
|
|
536
|
+
}
|
|
537
|
+
return { ok: true };
|
|
363
538
|
}).catch((err) => {
|
|
364
|
-
callback
|
|
539
|
+
if (typeof callback === 'function') {
|
|
540
|
+
callback(err);
|
|
541
|
+
}
|
|
542
|
+
throw err;
|
|
365
543
|
});
|
|
366
544
|
}
|
|
367
545
|
else {
|
|
368
|
-
|
|
546
|
+
if (typeof callback === 'function') {
|
|
547
|
+
nextTick(() => callback(null, { ok: true }));
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
return Promise.resolve({ ok: true });
|
|
551
|
+
}
|
|
369
552
|
}
|
|
370
553
|
};
|
|
554
|
+
api.destroy = api._destroy;
|
|
371
555
|
api._putLocal = function (doc, opts, callback) {
|
|
372
556
|
if (typeof opts === 'function') {
|
|
373
557
|
callback = opts;
|
|
@@ -383,18 +567,35 @@ function GoogleDriveAdapter(PouchDB) {
|
|
|
383
567
|
doc: savedDoc,
|
|
384
568
|
timestamp: Date.now()
|
|
385
569
|
};
|
|
386
|
-
db.appendChanges([change]).then(() => {
|
|
387
|
-
|
|
570
|
+
const promise = db.appendChanges([change]).then(() => {
|
|
571
|
+
const result = { ok: true, id, rev };
|
|
572
|
+
if (callback)
|
|
573
|
+
callback(null, result);
|
|
574
|
+
return result;
|
|
388
575
|
}).catch((err) => {
|
|
389
|
-
callback
|
|
576
|
+
if (callback)
|
|
577
|
+
callback(err);
|
|
578
|
+
throw err;
|
|
390
579
|
});
|
|
580
|
+
return promise;
|
|
391
581
|
};
|
|
392
582
|
api._getLocal = function (id, callback) {
|
|
393
|
-
db.get(id).then(doc => {
|
|
394
|
-
if (!doc)
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
583
|
+
const promise = db.get(id).then(doc => {
|
|
584
|
+
if (!doc) {
|
|
585
|
+
const err = { status: 404, error: true, name: 'not_found' };
|
|
586
|
+
if (callback)
|
|
587
|
+
callback(err);
|
|
588
|
+
throw err;
|
|
589
|
+
}
|
|
590
|
+
if (callback)
|
|
591
|
+
callback(null, doc);
|
|
592
|
+
return doc;
|
|
593
|
+
}).catch(err => {
|
|
594
|
+
if (callback)
|
|
595
|
+
callback(err);
|
|
596
|
+
throw err;
|
|
597
|
+
});
|
|
598
|
+
return promise;
|
|
398
599
|
};
|
|
399
600
|
api._removeLocal = function (doc, opts, callback) {
|
|
400
601
|
// ... Similar async update ...
|
|
@@ -405,7 +606,10 @@ function GoogleDriveAdapter(PouchDB) {
|
|
|
405
606
|
const id = doc._id;
|
|
406
607
|
// Check existence async if we want to be strict, but index check is ok
|
|
407
608
|
if (!db.getIndexEntry(id)) {
|
|
408
|
-
|
|
609
|
+
const err = { status: 404, error: true, name: 'not_found' };
|
|
610
|
+
if (callback)
|
|
611
|
+
callback(err);
|
|
612
|
+
return Promise.reject(err);
|
|
409
613
|
}
|
|
410
614
|
// ...
|
|
411
615
|
// Simplified removeLocal
|
|
@@ -416,11 +620,17 @@ function GoogleDriveAdapter(PouchDB) {
|
|
|
416
620
|
deleted: true,
|
|
417
621
|
timestamp: Date.now()
|
|
418
622
|
};
|
|
419
|
-
db.appendChanges([change]).then(() => {
|
|
420
|
-
|
|
623
|
+
const promise = db.appendChanges([change]).then(() => {
|
|
624
|
+
const result = { ok: true, id, rev: '0-0' };
|
|
625
|
+
if (callback)
|
|
626
|
+
callback(null, result);
|
|
627
|
+
return result;
|
|
421
628
|
}).catch((err) => {
|
|
422
|
-
callback
|
|
629
|
+
if (callback)
|
|
630
|
+
callback(err);
|
|
631
|
+
throw err;
|
|
423
632
|
});
|
|
633
|
+
return promise;
|
|
424
634
|
};
|
|
425
635
|
}
|
|
426
636
|
// Static properties
|