@docstack/pouchdb-adapter-googledrive 0.1.0 → 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/client.js CHANGED
@@ -70,7 +70,7 @@ class GoogleDriveClient {
70
70
  async listFiles(q) {
71
71
  const params = new URLSearchParams({
72
72
  q,
73
- fields: 'files(id,name,mimeType,parents,modifiedTime)'
73
+ fields: 'files(id,name,mimeType,parents,modifiedTime,md5Checksum)'
74
74
  });
75
75
  // FIX: URLSearchParams uses '+', but Drive API is safer with '%20'
76
76
  const queryString = params.toString().replace(/\+/g, '%20');
@@ -103,7 +103,7 @@ class GoogleDriveClient {
103
103
  }
104
104
  // Single metadata get (for etag check)
105
105
  async getFileMetadata(fileId) {
106
- const params = new URLSearchParams({ fields: 'id,name,mimeType,parents,modifiedTime' });
106
+ const params = new URLSearchParams({ fields: 'id,name,mimeType,parents,modifiedTime,md5Checksum' });
107
107
  const queryString = params.toString().replace(/\+/g, '%20');
108
108
  const res = await this.fetch(`${this.baseUrl}/${fileId}?${queryString}`, { method: 'GET' });
109
109
  const data = await res.json();
package/lib/drive.d.ts CHANGED
@@ -19,7 +19,9 @@ export declare class DriveHandler {
19
19
  private compactionSizeThreshold;
20
20
  private meta;
21
21
  private metaEtag;
22
+ private metaMd5;
22
23
  private metaModifiedTime;
24
+ private localDocsEtag;
23
25
  private index;
24
26
  private docCache;
25
27
  private pendingChanges;
@@ -33,6 +35,8 @@ export declare class DriveHandler {
33
35
  private currentSnapshotIndexId;
34
36
  private debug;
35
37
  private isCompacting;
38
+ private pendingDownloads;
39
+ private pendingFinds;
36
40
  private log;
37
41
  constructor(options: GoogleDriveAdapterOptions, dbName: string);
38
42
  get seq(): number;
@@ -56,6 +60,7 @@ export declare class DriveHandler {
56
60
  appendChange(change: ChangeEntry): Promise<void>;
57
61
  /** Append changes with OCC */
58
62
  appendChanges(changes: ChangeEntry[]): Promise<void>;
63
+ private appendLocalDocs;
59
64
  private tryAppendChanges;
60
65
  /** Update Index with a new change */
61
66
  private updateIndex;
package/lib/drive.js CHANGED
@@ -30,7 +30,9 @@ class DriveHandler {
30
30
  dbName: ''
31
31
  };
32
32
  this.metaEtag = null;
33
+ this.metaMd5 = null;
33
34
  this.metaModifiedTime = null;
35
+ this.localDocsEtag = null;
34
36
  // In-Memory Index: ID -> Metadata/Pointer
35
37
  this.index = {};
36
38
  this.pendingChanges = [];
@@ -43,6 +45,8 @@ class DriveHandler {
43
45
  this.currentSnapshotIndexId = null;
44
46
  this.debug = false;
45
47
  this.isCompacting = false;
48
+ this.pendingDownloads = new Map();
49
+ this.pendingFinds = new Map();
46
50
  const clientOptions = { ...options };
47
51
  if (options.testMode) {
48
52
  const serverUrl = options.testServerUrl || 'http://localhost:3000';
@@ -95,9 +99,10 @@ class DriveHandler {
95
99
  }
96
100
  const metaFile = await this.findFile('_meta.json');
97
101
  if (metaFile) {
98
- this.log('Retrieved meta file', { fileId: metaFile.id });
99
- 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
100
104
  this.metaEtag = metaFile.etag || null;
105
+ this.metaMd5 = metaFile.md5Checksum || null;
101
106
  this.metaModifiedTime = metaFile.modifiedTime || null;
102
107
  }
103
108
  else {
@@ -142,36 +147,64 @@ class DriveHandler {
142
147
  }
143
148
  // 2. Replay NEW Change Logs (Metadata only updates)
144
149
  this.log('Replaying change logs');
145
- for (const logId of this.meta.changeLogIds) {
146
- if (this.processedLogIds.has(logId))
147
- continue;
148
- this.log('Replaying change log', logId);
149
- let changes = await this.downloadNdjson(logId);
150
- if (!Array.isArray(changes)) {
151
- this.log("Unexpected changes", { changes, logId });
152
- this.log('Downloaded changes not an array, wrapping/ignoring', typeof changes);
153
- changes = changes ? [changes] : []; // Fallback
154
- }
155
- this.currentLogSizeEstimate += 100 * changes.length;
156
- this.log('Replayed change log', logId);
157
- for (const change of changes) {
158
- this.log('Processing change, sequence', change.seq);
159
- this.updateIndex(change, logId);
160
- if (this.docCache.get(change.id)) {
161
- this.docCache.remove(change.id);
150
+ const pendingLogs = this.meta.changeLogIds.filter(id => !this.processedLogIds.has(id));
151
+ if (pendingLogs.length > 0) {
152
+ this.log(`Downloading ${pendingLogs.length} change logs in parallel`);
153
+ const logResults = await Promise.all(pendingLogs.map(async (id) => {
154
+ try {
155
+ const changes = await this.downloadNdjson(id);
156
+ return { id, changes };
157
+ }
158
+ catch (e) {
159
+ this.log(`Failed to download change log ${id}`, e);
160
+ return { id, changes: null };
161
+ }
162
+ }));
163
+ for (const { id, changes } of logResults) {
164
+ if (!changes) {
165
+ this.log(`Skipping failed log ${id}`);
166
+ continue;
167
+ }
168
+ let changesArray = Array.isArray(changes) ? changes : [changes];
169
+ this.currentLogSizeEstimate += 100 * changesArray.length;
170
+ for (const change of changesArray) {
171
+ this.log('Processing change, sequence', change.seq);
172
+ this.updateIndex(change, id);
173
+ if (this.docCache.get(change.id)) {
174
+ this.docCache.remove(change.id);
175
+ }
162
176
  }
177
+ this.processedLogIds.add(id);
178
+ this.log('Processed log', id);
163
179
  }
164
- this.processedLogIds.add(logId);
165
- this.log('Processed log', logId);
166
- }
167
- // 3. Start Polling (if enabled)
168
- if (this.options.pollingIntervalMs) {
169
- this.log('Starting polling with interval', this.options.pollingIntervalMs);
170
- this.startPolling(Number(this.options.pollingIntervalMs));
171
180
  }
172
- else {
173
- this.log('Polling disabled (no interval provided)');
181
+ // 2. Replay NEW Change Logs (Metadata only updates)
182
+ // ... (previous logic for change logs)
183
+ // (Already updated in previous turn, keep it)
184
+ // 2b. Load Local Documents Store (Pinned in meta)
185
+ if (this.meta.localDocsId) {
186
+ this.log('Loading local docs store', this.meta.localDocsId);
187
+ try {
188
+ const localStore = await this.client.getFileMetadata(this.meta.localDocsId);
189
+ this.localDocsEtag = localStore.etag || null;
190
+ const localDocsChunk = await this.downloadJson(this.meta.localDocsId, true);
191
+ if (localDocsChunk && localDocsChunk.docs) {
192
+ for (const [id, doc] of Object.entries(localDocsChunk.docs)) {
193
+ this.log('Merging local doc', id);
194
+ this.index[id] = {
195
+ rev: doc._rev,
196
+ seq: 0, // Local docs don't participate in shared sequences
197
+ location: { fileId: this.meta.localDocsId }
198
+ };
199
+ this.docCache.put(id, doc);
200
+ }
201
+ }
202
+ }
203
+ catch (e) {
204
+ this.log('Failed to load local docs store', e);
205
+ }
174
206
  }
207
+ // 3. Start Polling ...
175
208
  }
176
209
  catch (e) {
177
210
  console.error('Failed to load database', e);
@@ -259,45 +292,61 @@ class DriveHandler {
259
292
  return cached;
260
293
  }
261
294
  }
262
- this.log('fetchFile downloading', fileId);
263
- const data = await this.client.getFile(fileId);
264
- let parsed;
265
- if (typeof data === 'string') {
266
- const trimmed = data.trim();
267
- if (trimmed.startsWith('{')) {
268
- // Could be JSON or NDJSON
269
- if (trimmed.includes('\n')) {
270
- // Definitely NDJSON (multiple lines)
271
- try {
272
- const lines = trimmed.split('\n').filter(l => l);
273
- 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
+ }
274
330
  }
275
- catch (e) {
331
+ else {
276
332
  parsed = data;
277
333
  }
278
334
  }
279
335
  else {
280
- // Single line. Try regular JSON first.
281
- try {
282
- parsed = JSON.parse(trimmed);
283
- // Optional: if we know it was supposed to be NDJSON?
284
- // Our change files are always NDJSON.
285
- // But _meta.json and snapshot-*.json are regular JSON.
286
- }
287
- catch (e) {
288
- parsed = data;
289
- }
336
+ parsed = data;
290
337
  }
338
+ if (!skipCache)
339
+ this.fileCache.put(fileId, parsed);
340
+ return parsed;
291
341
  }
292
- else {
293
- parsed = data;
342
+ finally {
343
+ if (!skipCache)
344
+ this.pendingDownloads.delete(fileId);
294
345
  }
295
- }
296
- else {
297
- parsed = data;
298
- }
299
- this.fileCache.put(fileId, parsed);
300
- return parsed;
346
+ })();
347
+ if (!skipCache)
348
+ this.pendingDownloads.set(fileId, downloadPromise);
349
+ return await downloadPromise;
301
350
  }
302
351
  /** Get multiple docs (Atomic-ish) used for _allDocs */
303
352
  async getMulti(ids) {
@@ -383,24 +432,33 @@ class DriveHandler {
383
432
  /** Append changes with OCC */
384
433
  async appendChanges(changes) {
385
434
  const MAX_RETRIES = 5;
386
- let attempt = 0;
387
- while (attempt < MAX_RETRIES) {
435
+ let attemptNum = 0;
436
+ const local = changes.filter(c => c.id.startsWith('_local/'));
437
+ const remote = changes.filter(c => !c.id.startsWith('_local/'));
438
+ // Handle Local Docs (Pinned Store)
439
+ if (local.length > 0) {
440
+ await this.appendLocalDocs(local);
441
+ }
442
+ // Handle Remote Docs (App Log)
443
+ if (remote.length === 0)
444
+ return;
445
+ while (attemptNum < MAX_RETRIES) {
388
446
  try {
389
- return await this.tryAppendChanges(changes);
447
+ return await this.tryAppendChanges(remote);
390
448
  }
391
449
  catch (err) {
392
450
  if (err.status === 412 || err.status === 409) {
393
451
  // Reload and RETRY
394
452
  await this.load();
395
453
  // Check conflicts against Index (Metadata sufficient)
396
- this.checkConflicts(changes);
454
+ this.checkConflicts(remote);
397
455
  // Reseq
398
456
  let currentSeq = this.meta.seq;
399
- for (const change of changes) {
457
+ for (const change of remote) {
400
458
  currentSeq++;
401
459
  change.seq = currentSeq;
402
460
  }
403
- attempt++;
461
+ attemptNum++;
404
462
  await new Promise(r => setTimeout(r, Math.random() * 500 + 100));
405
463
  continue;
406
464
  }
@@ -409,6 +467,66 @@ class DriveHandler {
409
467
  }
410
468
  throw new Error('Failed to append changes');
411
469
  }
470
+ async appendLocalDocs(changes) {
471
+ const MAX_RETRIES = 5;
472
+ let attempt = 0;
473
+ while (attempt < MAX_RETRIES) {
474
+ try {
475
+ // 1. Download current local docs (no cache)
476
+ let store = { docs: {} };
477
+ let currentEtag = null;
478
+ if (this.meta.localDocsId) {
479
+ try {
480
+ const fileMeta = await this.client.getFileMetadata(this.meta.localDocsId);
481
+ currentEtag = fileMeta.etag || null;
482
+ store = await this.downloadJson(this.meta.localDocsId, true);
483
+ }
484
+ catch (e) {
485
+ if (e.status !== 404)
486
+ throw e;
487
+ }
488
+ }
489
+ // 2. Merge changes
490
+ for (const change of changes) {
491
+ if (change.deleted) {
492
+ delete store.docs[change.id];
493
+ }
494
+ else if (change.doc) {
495
+ store.docs[change.id] = change.doc;
496
+ }
497
+ }
498
+ // 3. Save back
499
+ const content = JSON.stringify(store);
500
+ let res;
501
+ if (this.meta.localDocsId) {
502
+ res = await this.client.updateFile(this.meta.localDocsId, content, currentEtag || undefined);
503
+ }
504
+ else {
505
+ res = await this.client.createFile('_local_docs.json', [this.folderId], 'application/json', content);
506
+ // Update Meta with new File ID
507
+ await this.atomicUpdateMeta((latest) => ({ ...latest, localDocsId: res.id }));
508
+ }
509
+ this.localDocsEtag = res.etag;
510
+ // Update Index
511
+ for (const change of changes) {
512
+ this.updateIndex(change, res.id);
513
+ if (change.doc)
514
+ this.docCache.put(change.id, change.doc);
515
+ else
516
+ this.docCache.remove(change.id);
517
+ }
518
+ return;
519
+ }
520
+ catch (err) {
521
+ if (err.status === 412 || err.status === 409) {
522
+ attempt++;
523
+ await new Promise(r => setTimeout(r, Math.random() * 500 + 100));
524
+ continue;
525
+ }
526
+ throw err;
527
+ }
528
+ }
529
+ }
412
530
  async tryAppendChanges(changes) {
413
531
  // 1. Write Log File (Upload Data)
414
532
  const fileId = await this.writeChangeFile(changes);
@@ -485,13 +603,22 @@ class DriveHandler {
485
603
  // This is the one time we download everything if not cached.
486
604
  // Optimization: We could reuse existing `snapshot-data` chunks and only append new data
487
605
  // to a new chunk, but for simplicity: Merge All.
488
- const allIds = Object.keys(this.index).filter(id => !this.index[id].deleted);
606
+ const allIds = Object.keys(this.index).filter(id => !this.index[id].deleted && !id.startsWith('_local/'));
489
607
  const allDocs = await this.getMulti(allIds);
490
608
  const snapshotData = { docs: {} };
609
+ const missingDocs = [];
491
610
  allIds.forEach((id, i) => {
492
- if (allDocs[i])
611
+ if (allDocs[i]) {
493
612
  snapshotData.docs[id] = allDocs[i];
613
+ }
614
+ else {
615
+ missingDocs.push(id);
616
+ }
494
617
  });
618
+ if (missingDocs.length > 0) {
619
+ this.log('Compaction ABORTED: Failed to fetch documents', missingDocs);
620
+ throw new Error(`Compaction failed: missing ${missingDocs.length} documents. Aborting to prevent data loss.`);
621
+ }
495
622
  // 2. Upload Data File
496
623
  const dataContent = JSON.stringify(snapshotData);
497
624
  const dataRes = await this.client.createFile(`snapshot-data-${Date.now()}.json`, [this.folderId], 'application/json', dataContent);
@@ -543,7 +670,7 @@ class DriveHandler {
543
670
  const metaFile = await this.findFile('_meta.json');
544
671
  if (!metaFile)
545
672
  throw new Error('Meta missing');
546
- const validMeta = await this.downloadJson(metaFile.id, true); // No cache
673
+ const validMeta = await this.downloadJson(metaFile.fileId, true); // No cache
547
674
  const newMeta = modifier(validMeta);
548
675
  await this.saveMeta(newMeta, metaFile.etag);
549
676
  this.meta = newMeta;
@@ -570,29 +697,42 @@ class DriveHandler {
570
697
  return createRes.id;
571
698
  }
572
699
  async findFile(name) {
573
- if (!this.folderId)
574
- return null;
575
- const safeName = this.escapeQuery(name);
576
- const q = `name = '${safeName}' and '${this.folderId}' in parents and trashed = false`;
577
- const files = await this.client.listFiles(q);
578
- if (files.length > 0) {
579
- let file = files[0];
580
- if (!file.etag) {
581
- // Robustness: Fetch metadata for the file if etag is missing from list
582
- try {
583
- file = await this.client.getFileMetadata(file.id);
584
- }
585
- catch (e) {
586
- 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
+ };
587
727
  }
728
+ return null;
588
729
  }
589
- return {
590
- id: file.id,
591
- etag: file.etag,
592
- modifiedTime: file.modifiedTime
593
- };
594
- }
595
- return null;
730
+ finally {
731
+ this.pendingFinds.delete(name);
732
+ }
733
+ })();
734
+ this.pendingFinds.set(name, findPromise);
735
+ return await findPromise;
596
736
  }
597
737
  async downloadJson(fileId, skipCache = false) {
598
738
  return await this.fetchFile(fileId, skipCache);
@@ -615,14 +755,16 @@ class DriveHandler {
615
755
  const content = JSON.stringify(meta);
616
756
  const metaFile = await this.findFile('_meta.json');
617
757
  if (metaFile) {
618
- const res = await this.client.updateFile(metaFile.id, content, expectedEtag || undefined);
758
+ const res = await this.client.updateFile(metaFile.fileId, content, expectedEtag || undefined);
619
759
  this.metaEtag = res.etag;
760
+ this.metaMd5 = res.md5Checksum || null;
620
761
  this.metaModifiedTime = res.modifiedTime;
621
- this.fileCache.remove(metaFile.id); // Invalidate cache
762
+ this.fileCache.remove(metaFile.fileId); // Invalidate cache
622
763
  }
623
764
  else {
624
765
  const res = await this.client.createFile('_meta.json', [this.folderId], 'application/json', content);
625
766
  this.metaEtag = res.etag;
767
+ this.metaMd5 = res.md5Checksum || null;
626
768
  this.metaModifiedTime = res.modifiedTime;
627
769
  }
628
770
  }
@@ -682,11 +824,25 @@ class DriveHandler {
682
824
  this.log('Polling: _meta.json not found');
683
825
  return;
684
826
  }
685
- // Compare etags, falling back to modifiedTime
686
- this.log('Polling: comparing etag', metaFile.etag, 'with', this.metaEtag);
687
- if ((metaFile.etag && this.metaEtag && metaFile.etag !== this.metaEtag) ||
688
- (!metaFile.etag && metaFile.modifiedTime !== this.metaModifiedTime)) {
689
- this.log('Polling detected change!', metaFile.etag || metaFile.modifiedTime);
827
+ // Compare etags, falling back to md5Checksum or modifiedTime
828
+ const remoteEtag = metaFile.etag;
829
+ const remoteMd5 = metaFile.md5Checksum;
830
+ const remoteModified = metaFile.modifiedTime;
831
+ this.log('Polling: comparing etag', remoteEtag, 'with', this.metaEtag, 'md5', remoteMd5, 'with', this.metaMd5);
832
+ let changed = false;
833
+ if (remoteEtag && this.metaEtag) {
834
+ if (remoteEtag !== this.metaEtag)
835
+ changed = true;
836
+ }
837
+ else if (remoteMd5 && this.metaMd5) {
838
+ if (remoteMd5 !== this.metaMd5)
839
+ changed = true;
840
+ }
841
+ else if (remoteModified !== this.metaModifiedTime) {
842
+ changed = true;
843
+ }
844
+ if (changed) {
845
+ this.log('Polling detected change!', remoteEtag || remoteMd5 || remoteModified);
690
846
  await this.load();
691
847
  this.notifyListeners();
692
848
  }
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;
@@ -87,6 +90,8 @@ export interface MetaData {
87
90
  lastCompaction: number | null;
88
91
  /** Database name */
89
92
  dbName: string;
93
+ /** File ID for _local documents store (optimizes by avoiding app-log writes) */
94
+ localDocsId?: string | null;
90
95
  /** Schema Version (for migration) */
91
96
  version?: number;
92
97
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docstack/pouchdb-adapter-googledrive",
3
- "version": "0.1.0",
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",
package/jest.config.js DELETED
@@ -1,8 +0,0 @@
1
- module.exports = {
2
- preset: 'ts-jest',
3
- testEnvironment: 'node',
4
- testMatch: ['**/tests/**/*.test.ts'],
5
- transform: {
6
- '^.+\\.ts$': 'ts-jest',
7
- },
8
- };