@docstack/pouchdb-adapter-googledrive 0.1.1 → 0.1.2

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/lib/adapter.js CHANGED
@@ -350,15 +350,27 @@ function GoogleDriveAdapter(PouchDB) {
350
350
  }
351
351
  else {
352
352
  let newRev;
353
+ let savedDoc;
353
354
  if (newEdits) {
354
355
  const oldRev = entry?.rev || '0-0';
355
356
  const revNum = parseInt(oldRev.split('-')[0], 10) + 1;
356
- newRev = revNum + '-' + generateRevId();
357
+ const revHash = generateRevId();
358
+ newRev = revNum + '-' + revHash;
359
+ savedDoc = Object.assign({}, doc, { _rev: newRev });
360
+ if (doc._revisions) {
361
+ savedDoc._revisions = {
362
+ start: revNum,
363
+ ids: [revHash, ...(doc._revisions.ids || [])]
364
+ };
365
+ if (savedDoc._revisions.ids.length > 500) {
366
+ savedDoc._revisions.ids = savedDoc._revisions.ids.slice(0, 500);
367
+ }
368
+ }
357
369
  }
358
370
  else {
359
371
  newRev = doc._rev;
372
+ savedDoc = Object.assign({}, doc, { _rev: newRev });
360
373
  }
361
- const savedDoc = Object.assign({}, doc, { _rev: newRev });
362
374
  changes.push({
363
375
  seq,
364
376
  id,
@@ -558,8 +570,16 @@ function GoogleDriveAdapter(PouchDB) {
558
570
  opts = {};
559
571
  }
560
572
  const id = doc._id;
561
- const rev = '0-1';
573
+ const revNum = 1;
574
+ const revHash = generateRevId();
575
+ const rev = revNum + '-' + revHash;
562
576
  const savedDoc = Object.assign({}, doc, { _rev: rev });
577
+ if (doc._revisions) {
578
+ savedDoc._revisions = {
579
+ start: revNum,
580
+ ids: [revHash, ...(doc._revisions.ids || [])]
581
+ };
582
+ }
563
583
  const change = {
564
584
  seq: db.getNextSeq(),
565
585
  id,
package/lib/drive.d.ts CHANGED
@@ -35,6 +35,8 @@ export declare class DriveHandler {
35
35
  private currentSnapshotIndexId;
36
36
  private debug;
37
37
  private isCompacting;
38
+ private pendingDownloads;
39
+ private pendingFinds;
38
40
  private log;
39
41
  constructor(options: GoogleDriveAdapterOptions, dbName: string);
40
42
  get seq(): number;
package/lib/drive.js CHANGED
@@ -45,6 +45,8 @@ class DriveHandler {
45
45
  this.currentSnapshotIndexId = null;
46
46
  this.debug = false;
47
47
  this.isCompacting = false;
48
+ this.pendingDownloads = new Map();
49
+ this.pendingFinds = new Map();
48
50
  const clientOptions = { ...options };
49
51
  if (options.testMode) {
50
52
  const serverUrl = options.testServerUrl || 'http://localhost:3000';
@@ -97,8 +99,8 @@ class DriveHandler {
97
99
  }
98
100
  const metaFile = await this.findFile('_meta.json');
99
101
  if (metaFile) {
100
- this.log('Retrieved meta file', { fileId: metaFile.id });
101
- this.meta = await this.downloadJson(metaFile.id, true); // No cache for meta
102
+ this.log('Retrieved meta file', { fileId: metaFile.fileId });
103
+ this.meta = await this.downloadJson(metaFile.fileId, true); // No cache for meta
102
104
  this.metaEtag = metaFile.etag || null;
103
105
  this.metaMd5 = metaFile.md5Checksum || null;
104
106
  this.metaModifiedTime = metaFile.modifiedTime || null;
@@ -290,45 +292,61 @@ class DriveHandler {
290
292
  return cached;
291
293
  }
292
294
  }
293
- this.log('fetchFile downloading', fileId);
294
- const data = await this.client.getFile(fileId);
295
- let parsed;
296
- if (typeof data === 'string') {
297
- const trimmed = data.trim();
298
- if (trimmed.startsWith('{')) {
299
- // Could be JSON or NDJSON
300
- if (trimmed.includes('\n')) {
301
- // Definitely NDJSON (multiple lines)
302
- try {
303
- const lines = trimmed.split('\n').filter(l => l);
304
- parsed = lines.map(line => JSON.parse(line));
295
+ // Always check pending downloads. A download in progress is as fresh as it
296
+ // can be right now, so we can reuse it even if skipCache is true.
297
+ const pending = this.pendingDownloads.get(fileId);
298
+ if (pending) {
299
+ this.log('fetchFile reuse pending download', fileId);
300
+ return await pending;
301
+ }
302
+ const downloadPromise = (async () => {
303
+ try {
304
+ this.log('fetchFile downloading', fileId);
305
+ const data = await this.client.getFile(fileId);
306
+ let parsed;
307
+ if (typeof data === 'string') {
308
+ const trimmed = data.trim();
309
+ if (trimmed.startsWith('{')) {
310
+ // Could be JSON or NDJSON
311
+ if (trimmed.includes('\n')) {
312
+ // Definitely NDJSON (multiple lines)
313
+ try {
314
+ const lines = trimmed.split('\n').filter(l => l);
315
+ parsed = lines.map(line => JSON.parse(line));
316
+ }
317
+ catch (e) {
318
+ parsed = data;
319
+ }
320
+ }
321
+ else {
322
+ // Single line. Try regular JSON first.
323
+ try {
324
+ parsed = JSON.parse(trimmed);
325
+ }
326
+ catch (e) {
327
+ parsed = data;
328
+ }
329
+ }
305
330
  }
306
- catch (e) {
331
+ else {
307
332
  parsed = data;
308
333
  }
309
334
  }
310
335
  else {
311
- // Single line. Try regular JSON first.
312
- try {
313
- parsed = JSON.parse(trimmed);
314
- // Optional: if we know it was supposed to be NDJSON?
315
- // Our change files are always NDJSON.
316
- // But _meta.json and snapshot-*.json are regular JSON.
317
- }
318
- catch (e) {
319
- parsed = data;
320
- }
336
+ parsed = data;
321
337
  }
338
+ if (!skipCache)
339
+ this.fileCache.put(fileId, parsed);
340
+ return parsed;
322
341
  }
323
- else {
324
- parsed = data;
342
+ finally {
343
+ if (!skipCache)
344
+ this.pendingDownloads.delete(fileId);
325
345
  }
326
- }
327
- else {
328
- parsed = data;
329
- }
330
- this.fileCache.put(fileId, parsed);
331
- return parsed;
346
+ })();
347
+ if (!skipCache)
348
+ this.pendingDownloads.set(fileId, downloadPromise);
349
+ return await downloadPromise;
332
350
  }
333
351
  /** Get multiple docs (Atomic-ish) used for _allDocs */
334
352
  async getMulti(ids) {
@@ -652,7 +670,7 @@ class DriveHandler {
652
670
  const metaFile = await this.findFile('_meta.json');
653
671
  if (!metaFile)
654
672
  throw new Error('Meta missing');
655
- const validMeta = await this.downloadJson(metaFile.id, true); // No cache
673
+ const validMeta = await this.downloadJson(metaFile.fileId, true); // No cache
656
674
  const newMeta = modifier(validMeta);
657
675
  await this.saveMeta(newMeta, metaFile.etag);
658
676
  this.meta = newMeta;
@@ -679,29 +697,42 @@ class DriveHandler {
679
697
  return createRes.id;
680
698
  }
681
699
  async findFile(name) {
682
- if (!this.folderId)
683
- return null;
684
- const safeName = this.escapeQuery(name);
685
- const q = `name = '${safeName}' and '${this.folderId}' in parents and trashed = false`;
686
- const files = await this.client.listFiles(q);
687
- if (files.length > 0) {
688
- let file = files[0];
689
- if (!file.etag) {
690
- // Robustness: Fetch metadata for the file if etag is missing from list
691
- try {
692
- file = await this.client.getFileMetadata(file.id);
693
- }
694
- catch (e) {
695
- this.log('Failed to fetch file metadata for etag', file.id, e);
700
+ const pending = this.pendingFinds.get(name);
701
+ if (pending) {
702
+ this.log('findFile reuse pending search', name);
703
+ return await pending;
704
+ }
705
+ const findPromise = (async () => {
706
+ const safeName = this.escapeQuery(name);
707
+ const q = `name = '${safeName}' and '${this.folderId}' in parents and trashed = false`;
708
+ try {
709
+ const files = await this.client.listFiles(q);
710
+ if (files.length > 0) {
711
+ let file = files[0];
712
+ if (!file.etag) {
713
+ // Robustness: Fetch metadata for the file if etag is missing from list
714
+ try {
715
+ file = await this.client.getFileMetadata(file.id);
716
+ }
717
+ catch (e) {
718
+ this.log('Failed to fetch file metadata for etag', file.id, e);
719
+ }
720
+ }
721
+ return {
722
+ fileId: file.id,
723
+ etag: file.etag,
724
+ md5Checksum: file.md5Checksum,
725
+ modifiedTime: file.modifiedTime
726
+ };
696
727
  }
728
+ return null;
697
729
  }
698
- return {
699
- id: file.id,
700
- etag: file.etag,
701
- modifiedTime: file.modifiedTime
702
- };
703
- }
704
- return null;
730
+ finally {
731
+ this.pendingFinds.delete(name);
732
+ }
733
+ })();
734
+ this.pendingFinds.set(name, findPromise);
735
+ return await findPromise;
705
736
  }
706
737
  async downloadJson(fileId, skipCache = false) {
707
738
  return await this.fetchFile(fileId, skipCache);
@@ -724,11 +755,11 @@ class DriveHandler {
724
755
  const content = JSON.stringify(meta);
725
756
  const metaFile = await this.findFile('_meta.json');
726
757
  if (metaFile) {
727
- const res = await this.client.updateFile(metaFile.id, content, expectedEtag || undefined);
758
+ const res = await this.client.updateFile(metaFile.fileId, content, expectedEtag || undefined);
728
759
  this.metaEtag = res.etag;
729
760
  this.metaMd5 = res.md5Checksum || null;
730
761
  this.metaModifiedTime = res.modifiedTime;
731
- this.fileCache.remove(metaFile.id); // Invalidate cache
762
+ this.fileCache.remove(metaFile.fileId); // Invalidate cache
732
763
  }
733
764
  else {
734
765
  const res = await this.client.createFile('_meta.json', [this.folderId], 'application/json', content);
package/lib/types.d.ts CHANGED
@@ -40,6 +40,9 @@ export interface ChangeEntry {
40
40
  /** Location pointer for lazy loading */
41
41
  export interface FilePointer {
42
42
  fileId: string;
43
+ etag?: string;
44
+ md5Checksum?: string;
45
+ modifiedTime?: string;
43
46
  /** Optional offset/length for future optimization (packed files) */
44
47
  offset?: number;
45
48
  length?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docstack/pouchdb-adapter-googledrive",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "PouchDB adapter for Google Drive",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",