@docstack/pouchdb-adapter-googledrive 0.1.3 → 0.1.5

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 CHANGED
File without changes
package/README.md CHANGED
File without changes
package/lib/adapter.d.ts CHANGED
File without changes
package/lib/adapter.js CHANGED
@@ -193,74 +193,72 @@ function GoogleDriveAdapter(PouchDB) {
193
193
  opts = {};
194
194
  }
195
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);
216
- const rows = sliced.map((id, i) => {
217
- const doc = docs[i];
218
- const entry = db.getIndexEntry(id);
219
- if (!doc && (!entry || entry.deleted))
220
- return { key: id, error: 'not_found' };
221
- if (!doc && entry) {
222
- // This implies fetch failed but exists in index? Or null result.
223
- return { key: id, error: 'not_found' };
224
- }
225
- const row = {
226
- id,
227
- key: id,
228
- value: { rev: entry?.rev || doc._rev }
229
- };
230
- row.doc = doc;
231
- return row;
232
- });
233
- const result = {
234
- total_rows: total,
235
- offset: startIndex,
236
- rows: rows.filter(r => !r.error || !opts.keys) // Filter errored unless specifically asked via keys?
237
- // CouchDB usually returns error row if distinct keys requested.
238
- };
239
- if (opts.update_seq)
240
- result.update_seq = db.seq;
241
- return result;
196
+ // _local/* docs live in a separate namespace and never appear in allDocs,
197
+ // same as CouchDB - they aren't part of the B-tree of regular documents.
198
+ const indexKeys = (await db.getIndexKeys()).filter(k => !k.startsWith('_local/'));
199
+ // total_rows reflects live (non-deleted) documents, matching db.info().doc_count -
200
+ // deleted entries stay in the index forever but were never meant to count here.
201
+ let total = 0;
202
+ for (const k of indexKeys) {
203
+ const entry = db.getIndexEntry(k);
204
+ if (entry && !entry.deleted)
205
+ total++;
206
+ }
207
+ const startIndex = opts.skip || 0;
208
+ let candidateKeys;
209
+ if (opts.keys) {
210
+ // Explicit keys: return exactly these, in the given order. Deleted docs are
211
+ // included here (flagged via value.deleted), not treated as errors - only
212
+ // keys absent from the index entirely are 'not_found'.
213
+ candidateKeys = opts.keys;
242
214
  }
243
215
  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
- };
216
+ // Default listing: deleted docs never appear, same as a real CouchDB/PouchDB
217
+ // allDocs() with no keys specified.
218
+ candidateKeys = indexKeys.filter(k => {
219
+ const entry = db.getIndexEntry(k);
220
+ return entry && !entry.deleted;
254
221
  });
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;
222
+ if (opts.startkey)
223
+ candidateKeys = candidateKeys.filter(k => k >= opts.startkey);
224
+ if (opts.endkey)
225
+ candidateKeys = candidateKeys.filter(k => k <= opts.endkey);
226
+ if (opts.key)
227
+ candidateKeys = candidateKeys.filter(k => k === opts.key);
228
+ candidateKeys.sort();
229
+ if (opts.descending)
230
+ candidateKeys.reverse();
263
231
  }
232
+ const limit = typeof opts.limit === 'number' ? opts.limit : candidateKeys.length;
233
+ const sliced = opts.keys ? candidateKeys : candidateKeys.slice(startIndex, startIndex + limit);
234
+ const docs = opts.include_docs ? await db.getMulti(sliced) : null;
235
+ const rows = sliced.map((id, i) => {
236
+ const entry = db.getIndexEntry(id);
237
+ if (!entry)
238
+ return { key: id, error: 'not_found' };
239
+ if (entry.deleted) {
240
+ const row = { id, key: id, value: { rev: entry.rev, deleted: true } };
241
+ if (opts.include_docs)
242
+ row.doc = null;
243
+ return row;
244
+ }
245
+ const row = { id, key: id, value: { rev: entry.rev } };
246
+ if (opts.include_docs) {
247
+ const doc = docs[i];
248
+ if (!doc)
249
+ return { key: id, error: 'not_found' };
250
+ row.doc = doc;
251
+ }
252
+ return row;
253
+ });
254
+ const result = {
255
+ total_rows: total,
256
+ offset: startIndex,
257
+ rows
258
+ };
259
+ if (opts.update_seq)
260
+ result.update_seq = db.seq;
261
+ return result;
264
262
  })();
265
263
  if (callback) {
266
264
  promise.then(res => callback(null, res)).catch(err => callback(err));
@@ -314,7 +312,10 @@ function GoogleDriveAdapter(PouchDB) {
314
312
  api._bulkDocs = function (req, opts, callback) {
315
313
  const docs = req.docs;
316
314
  const results = [];
317
- const newEdits = opts.new_edits !== false;
315
+ // new_edits lives on `req` (the CouchDB _bulk_docs request body shape), not `opts`.
316
+ // api.bulkDocs = api._bulkDocs below means callers reach this directly, bypassing
317
+ // AbstractPouchDB.prototype.bulkDocs's req->opts normalization - so read it from req.
318
+ const newEdits = req.new_edits !== false;
318
319
  const changes = [];
319
320
  // We need to validate revisions against Index
320
321
  // This does NOT require fetching bodies usually
@@ -498,7 +499,11 @@ function GoogleDriveAdapter(PouchDB) {
498
499
  };
499
500
  };
500
501
  // Manual compaction trigger
501
- api._compact = function (callback) {
502
+ api._compact = function (opts, callback) {
503
+ if (typeof opts === 'function') {
504
+ callback = opts;
505
+ opts = {};
506
+ }
502
507
  const promise = db.compact().then(() => {
503
508
  const result = { ok: true };
504
509
  if (callback)
package/lib/cache.d.ts CHANGED
File without changes
package/lib/cache.js CHANGED
File without changes
package/lib/client.d.ts CHANGED
File without changes
package/lib/client.js CHANGED
File without changes
package/lib/drive.d.ts CHANGED
File without changes
package/lib/drive.js CHANGED
@@ -287,6 +287,12 @@ class DriveHandler {
287
287
  doc = content;
288
288
  }
289
289
  if (doc) {
290
+ // HEAL: Ensure PouchDB core validation doesn't fail due to history corruption
291
+ // This prevents the infinite replicator retry loop when start < ids.length
292
+ if (doc._revisions && doc._revisions.start < doc._revisions.ids.length) {
293
+ const start = doc._revisions.start || 1;
294
+ doc._revisions.ids = doc._revisions.ids.slice(0, start);
295
+ }
290
296
  this.docCache.put(id, doc);
291
297
  doc._rev = entry.rev;
292
298
  }
@@ -622,6 +628,27 @@ class DriveHandler {
622
628
  const snapshotSeq = this.meta.seq;
623
629
  const oldLogIds = [...this.meta.changeLogIds];
624
630
  const oldIndexId = this.meta.snapshotIndexId;
631
+ // Resolve which snapshot-data file(s) the OLD index points to, so we can
632
+ // delete them too once the new snapshot is safely in place. Without this,
633
+ // every compaction leaves its predecessor's data file behind as orphaned
634
+ // clutter (the index gets cleaned up, but the (often much larger) data
635
+ // file it pointed to never does).
636
+ let oldDataFileIds = [];
637
+ if (oldIndexId) {
638
+ try {
639
+ const oldIndex = await this.downloadJson(oldIndexId, true);
640
+ const ids = new Set();
641
+ for (const entry of Object.values(oldIndex.entries || {})) {
642
+ const fid = entry.location?.fileId;
643
+ if (fid && fid !== 'LEGACY_MEMORY')
644
+ ids.add(fid);
645
+ }
646
+ oldDataFileIds = [...ids];
647
+ }
648
+ catch (e) {
649
+ this.log('Failed to load old snapshot index for data-file cleanup', oldIndexId, e);
650
+ }
651
+ }
625
652
  // 1. Fetch ALL active documents
626
653
  // We need them to build the new large snapshot-data file
627
654
  // This is the one time we download everything if not cached.
@@ -677,8 +704,11 @@ class DriveHandler {
677
704
  lastCompaction: Date.now()
678
705
  };
679
706
  });
680
- // 5. Cleanup - Only delete files that were confirmed removed from metadata
681
- await this.cleanupOldFiles(oldIndexId, filesToDelete);
707
+ // 5. Cleanup - Only delete files that were confirmed removed from metadata.
708
+ // Also delete the old snapshot-data file(s) the old index pointed to (excluding
709
+ // the freshly-created one, which can't appear here since it's brand new).
710
+ const staleDataFileIds = oldDataFileIds.filter(id => id !== dataFileId);
711
+ await this.cleanupOldFiles(oldIndexId, [...filesToDelete, ...staleDataFileIds]);
682
712
  this.currentLogSizeEstimate = 0;
683
713
  }
684
714
  finally {
package/lib/index.d.ts CHANGED
File without changes
package/lib/index.js CHANGED
File without changes
package/lib/types.d.ts CHANGED
File without changes
package/lib/types.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@docstack/pouchdb-adapter-googledrive",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "PouchDB adapter for Google Drive",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
7
7
  "scripts": {
8
8
  "build": "tsc",
9
9
  "test": "jest",
10
- "test:prod": "TEST_ENV=production jest"
10
+ "test:prod": "TEST_ENV=production jest",
11
+ "test:prod:explore": "TEST_ENV=production jest tests/production.explore.test.ts",
12
+ "test:prod:replication": "TEST_ENV=production jest tests/production.replication.test.ts"
11
13
  },
12
14
  "keywords": [
13
15
  "pouchdb",