@docstack/pouchdb-adapter-googledrive 0.0.8 → 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.
Files changed (168) hide show
  1. package/compaction_test.log +725 -0
  2. package/docs/DEBUG_BIDIRECTIONAL_REPLICATION.ipynb +481 -0
  3. package/docs/DOCUMENTATION.md +115 -0
  4. package/docs/PROMISE_SUPPORT.md +75 -0
  5. package/docs/REPLICATION_ISSUE.md +63 -0
  6. package/emulation-test.log +163 -0
  7. package/failing_test.log +31 -0
  8. package/final_test.log +2620 -0
  9. package/lib/adapter.d.ts +20 -0
  10. package/lib/adapter.js +650 -0
  11. package/lib/cache.d.ts +12 -0
  12. package/lib/cache.js +42 -0
  13. package/lib/client.d.ts +37 -0
  14. package/lib/client.js +189 -0
  15. package/lib/drive.d.ts +82 -0
  16. package/lib/drive.js +737 -0
  17. package/lib/index.d.ts +10 -0
  18. package/lib/index.js +56 -0
  19. package/lib/types.d.ts +92 -0
  20. package/lib/types.js +2 -0
  21. package/live_test.log +1209 -0
  22. package/package.json +1 -1
  23. package/test_output.log +563 -0
  24. package/test_output_2.log +1408 -0
  25. package/test_output_3.log +2445 -0
  26. package/.test-drive-integration/1xssnxhy3akj +0 -1
  27. package/.test-drive-integration/8p8eymf8afm +0 -1
  28. package/.test-drive-integration/d1a4udbbuh +0 -1
  29. package/.test-drive-integration/usx93j46xtl +0 -1
  30. package/.test-drive-integration/vc0pq3g8vp +0 -1
  31. package/.test-drive-integration/zenra4v6i +0 -1
  32. package/.test-drive-root/05wzn5mtlrzt +0 -1
  33. package/.test-drive-root/0axnnh1gmn2d +0 -1
  34. package/.test-drive-root/0l2pw73vk4i +0 -1
  35. package/.test-drive-root/0tzush8sunaj +0 -1
  36. package/.test-drive-root/143bh2d29rnl +0 -1
  37. package/.test-drive-root/1synnvu59il +0 -1
  38. package/.test-drive-root/239cd47eh23i +0 -1
  39. package/.test-drive-root/275ocoaj6nq +0 -1
  40. package/.test-drive-root/29d78af16s4 +0 -1
  41. package/.test-drive-root/2gzvpzclnwu +0 -1
  42. package/.test-drive-root/2pmk0leotvp +0 -1
  43. package/.test-drive-root/38ijn3s7p33 +0 -1
  44. package/.test-drive-root/3jm64la13rp +0 -1
  45. package/.test-drive-root/3l4zrzi5bm2 +0 -1
  46. package/.test-drive-root/3of67xqpsfh +0 -1
  47. package/.test-drive-root/3pi1rk70we5 +0 -1
  48. package/.test-drive-root/3w3n7tpdn4n +0 -1
  49. package/.test-drive-root/3xg31irodim +0 -1
  50. package/.test-drive-root/50asm455hsa +0 -1
  51. package/.test-drive-root/51ldbder4dm +0 -1
  52. package/.test-drive-root/5l7dt9r491r +0 -1
  53. package/.test-drive-root/65mfhb9yuq5 +0 -1
  54. package/.test-drive-root/6iocm2rs118 +0 -1
  55. package/.test-drive-root/6o6vlpa6r6j +0 -1
  56. package/.test-drive-root/6rrcl4sdhoi +0 -1
  57. package/.test-drive-root/6ucq10yu9bx +0 -1
  58. package/.test-drive-root/7de4gtsch5b +0 -1
  59. package/.test-drive-root/7j4j3eq54q +0 -1
  60. package/.test-drive-root/7n3ai8wgsvy +0 -1
  61. package/.test-drive-root/7v4b3eyitw3 +0 -1
  62. package/.test-drive-root/8e15eu4xksq +0 -1
  63. package/.test-drive-root/8pu2c63yh1j +0 -1
  64. package/.test-drive-root/8ryxg058q8g +0 -1
  65. package/.test-drive-root/99l00ber08g +0 -1
  66. package/.test-drive-root/9gxcumnvepb +0 -1
  67. package/.test-drive-root/a4xxhwpe2cp +0 -1
  68. package/.test-drive-root/a57knn0ufon +0 -1
  69. package/.test-drive-root/a7gszl8y5ir +0 -1
  70. package/.test-drive-root/b0nddw4fdbu +0 -1
  71. package/.test-drive-root/b3b1d1yuolh +0 -1
  72. package/.test-drive-root/b4syq7nig1f +0 -1
  73. package/.test-drive-root/bcc8mhlffkg +0 -1
  74. package/.test-drive-root/bmngbpsk34 +0 -1
  75. package/.test-drive-root/c9i4hvcll2s +0 -1
  76. package/.test-drive-root/cao688yanw +0 -1
  77. package/.test-drive-root/cdtduwukrek +0 -1
  78. package/.test-drive-root/ckzeli8w7ej +0 -1
  79. package/.test-drive-root/ctywezu2fi5 +0 -1
  80. package/.test-drive-root/cyxiv5vgtle +0 -1
  81. package/.test-drive-root/d83g0suoz08 +0 -1
  82. package/.test-drive-root/d9tefhps5m6 +0 -1
  83. package/.test-drive-root/dac156l068 +0 -1
  84. package/.test-drive-root/daqsvyj5cu9 +0 -1
  85. package/.test-drive-root/df0wbnhhz1 +0 -1
  86. package/.test-drive-root/difkbp1duv +0 -1
  87. package/.test-drive-root/djrry1wk98v +0 -1
  88. package/.test-drive-root/dx42jrz7ty9 +0 -1
  89. package/.test-drive-root/e0s76kp3ena +0 -1
  90. package/.test-drive-root/e9xvdv0mqig +0 -1
  91. package/.test-drive-root/f3xo9hy1956 +0 -1
  92. package/.test-drive-root/f4scgrpgejw +0 -1
  93. package/.test-drive-root/f9kbt63p7xp +0 -1
  94. package/.test-drive-root/fe07yvs6p14 +0 -1
  95. package/.test-drive-root/ff5suiec8xv +0 -1
  96. package/.test-drive-root/fwc1qm37as +0 -1
  97. package/.test-drive-root/gh39v4lzxjp +0 -1
  98. package/.test-drive-root/gxvkr1qq21b +0 -1
  99. package/.test-drive-root/h4p64z7djk5 +0 -1
  100. package/.test-drive-root/habe7525tnl +0 -1
  101. package/.test-drive-root/hhzddxj31ar +0 -1
  102. package/.test-drive-root/hyj9crn9sb +0 -1
  103. package/.test-drive-root/igey5y5f3mi +0 -1
  104. package/.test-drive-root/isvxwzkgym +0 -1
  105. package/.test-drive-root/javyqnt07ws +0 -1
  106. package/.test-drive-root/jevlhmyaczl +0 -1
  107. package/.test-drive-root/jve7ypin7bb +0 -1
  108. package/.test-drive-root/koycie6jmyr +0 -1
  109. package/.test-drive-root/kvlad8ps1l +0 -1
  110. package/.test-drive-root/l13wzs51qi +0 -1
  111. package/.test-drive-root/lbwwk07et6i +0 -1
  112. package/.test-drive-root/m3uoxcsft2g +0 -1
  113. package/.test-drive-root/m7l8z61tnei +0 -1
  114. package/.test-drive-root/mmwpyz6aa2e +0 -1
  115. package/.test-drive-root/mqbyniatwu +0 -1
  116. package/.test-drive-root/mz3jbwwr12 +0 -1
  117. package/.test-drive-root/mza04oxze7 +0 -1
  118. package/.test-drive-root/ndqlafk0c0c +0 -1
  119. package/.test-drive-root/noojz8gst2 +0 -1
  120. package/.test-drive-root/o8ujk9otq7 +0 -1
  121. package/.test-drive-root/oa54zbip13m +0 -1
  122. package/.test-drive-root/ore7c15j64a +0 -1
  123. package/.test-drive-root/oxj3tt9q16 +0 -1
  124. package/.test-drive-root/pac4f4x1r9 +0 -1
  125. package/.test-drive-root/pqawj7k06i +0 -1
  126. package/.test-drive-root/qujzt0hpj6i +0 -1
  127. package/.test-drive-root/r5sdb4w0d3 +0 -1
  128. package/.test-drive-root/r8pybcnme1r +0 -1
  129. package/.test-drive-root/rpht4r0nk9 +0 -1
  130. package/.test-drive-root/rru3wcrfved +0 -1
  131. package/.test-drive-root/rtq2tifpvgt +0 -1
  132. package/.test-drive-root/ru00ubalaar +0 -1
  133. package/.test-drive-root/rx65wqybqlm +0 -1
  134. package/.test-drive-root/s585nksqc5 +0 -1
  135. package/.test-drive-root/scvyemdb9gh +0 -1
  136. package/.test-drive-root/si2b3ac0lve +0 -1
  137. package/.test-drive-root/sjbanmc5v5k +0 -1
  138. package/.test-drive-root/sl2coirs8zo +0 -1
  139. package/.test-drive-root/so3w2nrxm7 +0 -1
  140. package/.test-drive-root/t3y2vj69hz9 +0 -1
  141. package/.test-drive-root/t95zgk2fl8f +0 -1
  142. package/.test-drive-root/t9f08z6ayj +0 -1
  143. package/.test-drive-root/ta501uh71xi +0 -1
  144. package/.test-drive-root/taqhm9suyu +0 -1
  145. package/.test-drive-root/tya2ijynx5k +0 -1
  146. package/.test-drive-root/u4llyu4o7en +0 -1
  147. package/.test-drive-root/u5wco26a6lb +0 -1
  148. package/.test-drive-root/u6sricrto +0 -1
  149. package/.test-drive-root/ukcmmzglxa +0 -1
  150. package/.test-drive-root/uvsiktw52p +0 -1
  151. package/.test-drive-root/uylv1bn9swb +0 -1
  152. package/.test-drive-root/uzkzgxo3k5b +0 -1
  153. package/.test-drive-root/vf68w1pfrv +0 -1
  154. package/.test-drive-root/vp4g881bu7 +0 -1
  155. package/.test-drive-root/vuv8aavujb +0 -1
  156. package/.test-drive-root/wg24h3o6df +0 -1
  157. package/.test-drive-root/wyi5oftc8nh +0 -1
  158. package/.test-drive-root/x5zl3x2eh +0 -1
  159. package/.test-drive-root/xb3bpq9xtk +0 -1
  160. package/.test-drive-root/xfn089pthf9 +0 -1
  161. package/.test-drive-root/xrs7jul0mdl +0 -1
  162. package/.test-drive-root/y5qtilrbuzq +0 -1
  163. package/.test-drive-root/ysxlnyjuav +0 -1
  164. package/.test-drive-root/z2loakinuyk +0 -1
  165. package/.test-drive-root/z6lxmw8opjo +0 -1
  166. package/.test-drive-root/z74cnyignf9 +0 -1
  167. package/.test-drive-root/zi9agjhrfw +0 -1
  168. package/.test-drive-root/zspmm1wmo5l +0 -1
@@ -0,0 +1,20 @@
1
+ import { GoogleDriveAdapterOptions } from './types';
2
+ /** Combined options type for PouchDB adapter */
3
+ interface AdapterOptions extends GoogleDriveAdapterOptions {
4
+ name: string;
5
+ }
6
+ /** Callback type for adapter initialization */
7
+ type AdapterCallback = (err: Error | null, api?: any) => void;
8
+ /**
9
+ * TODO: Implement dynamic method signature, since PouchDB CRUD methods can be called as promise, callback or (?)
10
+ */
11
+ /**
12
+ * GoogleDriveAdapter - PouchDB adapter for Google Drive storage.
13
+ * Updated for Lazy Loading (Async Access).
14
+ */
15
+ export declare function GoogleDriveAdapter(PouchDB: any): {
16
+ (this: any, opts: AdapterOptions, callback: AdapterCallback): void;
17
+ valid(): boolean;
18
+ use_prefix: boolean;
19
+ };
20
+ export default GoogleDriveAdapter;
package/lib/adapter.js ADDED
@@ -0,0 +1,650 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GoogleDriveAdapter = GoogleDriveAdapter;
4
+ const drive_1 = require("./drive");
5
+ /**
6
+ * Schedule a function to run asynchronously.
7
+ */
8
+ function nextTick(fn) {
9
+ if (typeof fn === 'function') {
10
+ queueMicrotask(fn);
11
+ }
12
+ }
13
+ /**
14
+ * TODO: Implement dynamic method signature, since PouchDB CRUD methods can be called as promise, callback or (?)
15
+ */
16
+ /**
17
+ * GoogleDriveAdapter - PouchDB adapter for Google Drive storage.
18
+ * Updated for Lazy Loading (Async Access).
19
+ */
20
+ function GoogleDriveAdapter(PouchDB) {
21
+ function GoogleDrivePouch(opts, callback) {
22
+ const api = this;
23
+ const name = opts.name;
24
+ // Clone options to avoid mutation
25
+ const adapterOpts = Object.assign({}, opts);
26
+ // Internal state
27
+ let instanceId;
28
+ let db;
29
+ // Initialize DriveHandler
30
+ console.log('Initializing DriveHandler with options:', adapterOpts);
31
+ db = new drive_1.DriveHandler(adapterOpts, name);
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();
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
+ });
83
+ // Load data from Drive and initialize
84
+ db.load().then(() => {
85
+ log('Database loaded');
86
+ afterDBCreated();
87
+ }).catch((err) => {
88
+ log('Load error', err);
89
+ onceCallback(err);
90
+ });
91
+ // After database is initialized
92
+ function afterDBCreated() {
93
+ nextTick(function () {
94
+ onceCallback(null, api);
95
+ });
96
+ }
97
+ // ============ PouchDB Adapter API Methods ============
98
+ api._remote = false;
99
+ api.type = function () {
100
+ return 'googledrive';
101
+ };
102
+ api._id = function (callback) {
103
+ callback(null, instanceId);
104
+ };
105
+ // Info now must be async-ish (calculated from Index)
106
+ api._info = function (callback) {
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
+ };
124
+ });
125
+ if (callback) {
126
+ promise.then(res => callback(null, res)).catch(err => callback(err));
127
+ }
128
+ else {
129
+ return promise;
130
+ }
131
+ };
132
+ api.info = api._info;
133
+ // Get a single document by ID (Async fetch)
134
+ api._get = function (id, opts, callback) {
135
+ if (typeof opts === 'function') {
136
+ callback = opts;
137
+ opts = {};
138
+ }
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 => {
146
+ if (!doc) {
147
+ log('_get missing', id);
148
+ const err = {
149
+ status: 404,
150
+ error: true,
151
+ name: 'not_found',
152
+ message: 'missing'
153
+ };
154
+ if (callback)
155
+ callback(err);
156
+ throw err;
157
+ }
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;
181
+ });
182
+ return promise;
183
+ };
184
+ api.get = api._get;
185
+ // Get all documents (Lazy stream or fetch)
186
+ api._allDocs = function (opts, callback) {
187
+ if (typeof opts === 'function') {
188
+ callback = opts;
189
+ opts = {};
190
+ }
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);
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;
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));
267
+ }
268
+ else {
269
+ return promise;
270
+ }
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;
313
+ // Bulk document operations
314
+ api._bulkDocs = function (req, opts, callback) {
315
+ const docs = req.docs;
316
+ const results = [];
317
+ const newEdits = opts.new_edits !== false;
318
+ const changes = [];
319
+ // We need to validate revisions against Index
320
+ // This does NOT require fetching bodies usually
321
+ for (const doc of docs) {
322
+ const id = doc._id;
323
+ const seq = db.getNextSeq() + changes.length;
324
+ const entry = db.getIndexEntry(id);
325
+ if (doc._deleted) {
326
+ if (!entry || entry.deleted) {
327
+ results.push({
328
+ ok: false,
329
+ id,
330
+ error: 'not_found',
331
+ reason: 'missing'
332
+ });
333
+ continue;
334
+ }
335
+ // Check rev
336
+ const oldRev = entry.rev || '0-0'; // Index has latest
337
+ // If mismatch? PouchDB handles conflict logic before calling us sometimes?
338
+ // But we should verify.
339
+ // If doc._rev matches entry.rev, we are good.
340
+ const revNum = parseInt(oldRev.split('-')[0], 10) + 1;
341
+ const newRev = revNum + '-' + generateRevId();
342
+ changes.push({
343
+ seq,
344
+ id,
345
+ rev: newRev,
346
+ deleted: true,
347
+ timestamp: Date.now()
348
+ });
349
+ results.push({ ok: true, id, rev: newRev });
350
+ }
351
+ else {
352
+ let newRev;
353
+ if (newEdits) {
354
+ const oldRev = entry?.rev || '0-0';
355
+ const revNum = parseInt(oldRev.split('-')[0], 10) + 1;
356
+ newRev = revNum + '-' + generateRevId();
357
+ }
358
+ else {
359
+ newRev = doc._rev;
360
+ }
361
+ const savedDoc = Object.assign({}, doc, { _rev: newRev });
362
+ changes.push({
363
+ seq,
364
+ id,
365
+ rev: newRev,
366
+ doc: savedDoc,
367
+ timestamp: Date.now()
368
+ });
369
+ results.push({ ok: true, id, rev: newRev });
370
+ }
371
+ }
372
+ log('_bulkDocs flushing', changes.length, 'changes');
373
+ // Append changes to log
374
+ const promise = db.appendChanges(changes).then(() => {
375
+ if (callback)
376
+ nextTick(() => callback(null, results));
377
+ return results;
378
+ }).catch((err) => {
379
+ log('_bulkDocs error', err);
380
+ if (callback)
381
+ callback(err);
382
+ throw err;
383
+ });
384
+ return promise;
385
+ };
386
+ api.bulkDocs = api._bulkDocs;
387
+ // Changes feed
388
+ api._changes = function (opts) {
389
+ opts = Object.assign({}, opts);
390
+ const since = opts.since || 0;
391
+ const limit = typeof opts.limit === 'number' ? opts.limit : Infinity;
392
+ const returnDocs = opts.return_docs !== false;
393
+ const results = [];
394
+ let lastSeq = since;
395
+ let complete = false;
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
+ }
428
+ }
429
+ };
430
+ cancelLive = db.onChange(liveListener);
431
+ }
432
+ // Process initial changes
433
+ async function processChangesAsync() {
434
+ log('_changes processing since', since, 'limit', limit, 'live', !!opts.live);
435
+ const keys = await db.getIndexKeys();
436
+ let processed = 0;
437
+ for (const id of keys) {
438
+ if (complete || processed >= limit)
439
+ break;
440
+ if (id.startsWith('_local/'))
441
+ continue;
442
+ const entry = db.getIndexEntry(id);
443
+ if (!entry || entry.seq <= since)
444
+ continue;
445
+ const change = {
446
+ id: id,
447
+ seq: entry.seq,
448
+ changes: [{ rev: entry.rev }]
449
+ };
450
+ if (opts.include_docs) {
451
+ try {
452
+ change.doc = await db.get(id);
453
+ }
454
+ catch (e) {
455
+ log('_changes include_docs error', e);
456
+ }
457
+ }
458
+ if (opts.onChange)
459
+ opts.onChange(change);
460
+ if (returnDocs)
461
+ results.push(change);
462
+ processed++;
463
+ lastSeq = Math.max(lastSeq, entry.seq);
464
+ }
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 });
469
+ opts.complete(null, { results, last_seq: lastSeq });
470
+ }
471
+ }
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);
478
+ });
479
+ return {
480
+ cancel() {
481
+ log('_changes cancel');
482
+ complete = true;
483
+ if (cancelLive)
484
+ cancelLive();
485
+ }
486
+ };
487
+ };
488
+ // Manual compaction trigger
489
+ api._compact = function (callback) {
490
+ const promise = db.compact().then(() => {
491
+ const result = { ok: true };
492
+ if (callback)
493
+ callback(null, result);
494
+ return result;
495
+ }).catch((err) => {
496
+ if (callback)
497
+ callback(err);
498
+ throw err;
499
+ });
500
+ return promise;
501
+ };
502
+ api._getRevisionTree = function (docId, callback) {
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);
515
+ };
516
+ api._close = function (callback) {
517
+ db.stopPolling();
518
+ if (typeof callback === 'function') {
519
+ nextTick(callback);
520
+ }
521
+ else {
522
+ return Promise.resolve();
523
+ }
524
+ };
525
+ api.close = api._close;
526
+ api._destroy = function (opts, callback) {
527
+ if (typeof opts === 'function') {
528
+ callback = opts;
529
+ opts = {};
530
+ }
531
+ db.stopPolling();
532
+ if (opts.deleteFolder) {
533
+ return db.deleteFolder().then(() => {
534
+ if (typeof callback === 'function') {
535
+ callback(null, { ok: true });
536
+ }
537
+ return { ok: true };
538
+ }).catch((err) => {
539
+ if (typeof callback === 'function') {
540
+ callback(err);
541
+ }
542
+ throw err;
543
+ });
544
+ }
545
+ else {
546
+ if (typeof callback === 'function') {
547
+ nextTick(() => callback(null, { ok: true }));
548
+ }
549
+ else {
550
+ return Promise.resolve({ ok: true });
551
+ }
552
+ }
553
+ };
554
+ api.destroy = api._destroy;
555
+ api._putLocal = function (doc, opts, callback) {
556
+ if (typeof opts === 'function') {
557
+ callback = opts;
558
+ opts = {};
559
+ }
560
+ const id = doc._id;
561
+ const rev = '0-1';
562
+ const savedDoc = Object.assign({}, doc, { _rev: rev });
563
+ const change = {
564
+ seq: db.getNextSeq(),
565
+ id,
566
+ rev,
567
+ doc: savedDoc,
568
+ timestamp: Date.now()
569
+ };
570
+ const promise = db.appendChanges([change]).then(() => {
571
+ const result = { ok: true, id, rev };
572
+ if (callback)
573
+ callback(null, result);
574
+ return result;
575
+ }).catch((err) => {
576
+ if (callback)
577
+ callback(err);
578
+ throw err;
579
+ });
580
+ return promise;
581
+ };
582
+ api._getLocal = function (id, callback) {
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;
599
+ };
600
+ api._removeLocal = function (doc, opts, callback) {
601
+ // ... Similar async update ...
602
+ if (typeof opts === 'function') {
603
+ callback = opts;
604
+ opts = {};
605
+ }
606
+ const id = doc._id;
607
+ // Check existence async if we want to be strict, but index check is ok
608
+ if (!db.getIndexEntry(id)) {
609
+ const err = { status: 404, error: true, name: 'not_found' };
610
+ if (callback)
611
+ callback(err);
612
+ return Promise.reject(err);
613
+ }
614
+ // ...
615
+ // Simplified removeLocal
616
+ const change = {
617
+ seq: db.getNextSeq(),
618
+ id,
619
+ rev: '0-0',
620
+ deleted: true,
621
+ timestamp: Date.now()
622
+ };
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;
628
+ }).catch((err) => {
629
+ if (callback)
630
+ callback(err);
631
+ throw err;
632
+ });
633
+ return promise;
634
+ };
635
+ }
636
+ // Static properties
637
+ GoogleDrivePouch.valid = function () {
638
+ return true;
639
+ };
640
+ GoogleDrivePouch.use_prefix = false;
641
+ return GoogleDrivePouch;
642
+ }
643
+ /**
644
+ * Generate a random revision ID
645
+ */
646
+ function generateRevId() {
647
+ return Math.random().toString(36).substring(2, 11) +
648
+ Math.random().toString(36).substring(2, 11);
649
+ }
650
+ exports.default = GoogleDriveAdapter;
package/lib/cache.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Simple Least Recently Used (LRU) Cache
3
+ */
4
+ export declare class LRUCache<K, V> {
5
+ private capacity;
6
+ private map;
7
+ constructor(capacity: number);
8
+ get(key: K): V | undefined;
9
+ put(key: K, value: V): void;
10
+ remove(key: K): void;
11
+ clear(): void;
12
+ }