@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/lib/cache.d.ts CHANGED
File without changes
package/lib/cache.js CHANGED
File without changes
package/lib/client.d.ts CHANGED
@@ -8,12 +8,16 @@ export interface DriveFile {
8
8
  }
9
9
  export interface DriveClientOptions {
10
10
  accessToken: string | (() => Promise<string>);
11
+ baseUrl?: string;
12
+ uploadUrl?: string;
11
13
  }
12
14
  export declare class GoogleDriveClient {
13
15
  private options;
14
16
  constructor(options: DriveClientOptions);
15
17
  private getToken;
16
18
  private fetch;
19
+ private get baseUrl();
20
+ private get uploadUrl();
17
21
  listFiles(q: string): Promise<DriveFile[]>;
18
22
  getFile(fileId: string): Promise<any>;
19
23
  getFileMetadata(fileId: string): Promise<DriveFile>;
@@ -27,6 +31,7 @@ export declare class GoogleDriveClient {
27
31
  etag: string;
28
32
  modifiedTime: string;
29
33
  }>;
34
+ private extractEtag;
30
35
  deleteFile(fileId: string): Promise<void>;
31
36
  private buildMultipart;
32
37
  }
package/lib/client.js CHANGED
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.GoogleDriveClient = void 0;
4
- const BASE_URL = 'https://www.googleapis.com/drive/v3/files';
5
- const UPLOAD_URL = 'https://www.googleapis.com/upload/drive/v3/files';
4
+ const DEFAULT_BASE_URL = 'https://www.googleapis.com/drive/v3/files';
5
+ const DEFAULT_UPLOAD_URL = 'https://www.googleapis.com/upload/drive/v3/files';
6
6
  class GoogleDriveClient {
7
7
  constructor(options) {
8
8
  this.options = options;
@@ -16,11 +16,16 @@ class GoogleDriveClient {
16
16
  async fetch(url, init) {
17
17
  const method = init.method || 'GET';
18
18
  const token = await this.getToken();
19
- const headers = new Headers(init.headers);
20
- headers.set('Authorization', `Bearer ${token}`);
19
+ const validHeaders = {
20
+ 'Authorization': `Bearer ${token}`
21
+ };
22
+ if (init.headers) {
23
+ // manual merge if needed, or just iterate
24
+ new Headers(init.headers).forEach((v, k) => validHeaders[k] = v);
25
+ }
21
26
  let res;
22
27
  try {
23
- res = await fetch(url, { ...init, headers });
28
+ res = await fetch(url, { ...init, headers: validHeaders });
24
29
  }
25
30
  catch (networkErr) {
26
31
  const err = new Error(`Network Error: ${networkErr.message} (${method} ${url})`);
@@ -56,6 +61,12 @@ class GoogleDriveClient {
56
61
  }
57
62
  return res;
58
63
  }
64
+ get baseUrl() {
65
+ return this.options.baseUrl || DEFAULT_BASE_URL;
66
+ }
67
+ get uploadUrl() {
68
+ return this.options.uploadUrl || DEFAULT_UPLOAD_URL;
69
+ }
59
70
  async listFiles(q) {
60
71
  const params = new URLSearchParams({
61
72
  q,
@@ -63,7 +74,8 @@ class GoogleDriveClient {
63
74
  });
64
75
  // FIX: URLSearchParams uses '+', but Drive API is safer with '%20'
65
76
  const queryString = params.toString().replace(/\+/g, '%20');
66
- const res = await this.fetch(`${BASE_URL}?${queryString}`, { method: 'GET' });
77
+ console.log(`[GoogleDriveClient] listFiles query: ${queryString}`);
78
+ const res = await this.fetch(`${this.baseUrl}?${queryString}`, { method: 'GET' });
67
79
  const data = await res.json();
68
80
  return data.files || [];
69
81
  }
@@ -72,7 +84,7 @@ class GoogleDriveClient {
72
84
  try {
73
85
  const params = new URLSearchParams({ alt: 'media' });
74
86
  const queryString = params.toString().replace(/\+/g, '%20');
75
- const res = await this.fetch(`${BASE_URL}/${fileId}?${queryString}`, { method: 'GET' });
87
+ const res = await this.fetch(`${this.baseUrl}/${fileId}?${queryString}`, { method: 'GET' });
76
88
  // Standard fetch handles JSON/Text transparency?
77
89
  // We expect JSON mostly, but sometimes we might want text.
78
90
  // PouchDB adapter flow: downloadJson, downloadNdjson
@@ -93,8 +105,12 @@ class GoogleDriveClient {
93
105
  async getFileMetadata(fileId) {
94
106
  const params = new URLSearchParams({ fields: 'id,name,mimeType,parents,modifiedTime' });
95
107
  const queryString = params.toString().replace(/\+/g, '%20');
96
- const res = await this.fetch(`${BASE_URL}/${fileId}?${queryString}`, { method: 'GET' });
97
- return await res.json();
108
+ const res = await this.fetch(`${this.baseUrl}/${fileId}?${queryString}`, { method: 'GET' });
109
+ const data = await res.json();
110
+ return {
111
+ ...data,
112
+ etag: this.extractEtag(res, data)
113
+ };
98
114
  }
99
115
  async createFile(name, parents, mimeType, content) {
100
116
  const metadata = {
@@ -104,7 +120,7 @@ class GoogleDriveClient {
104
120
  };
105
121
  // Folders or empty content can use simple metadata-only POST
106
122
  if (!content && mimeType === 'application/vnd.google-apps.folder') {
107
- const res = await this.fetch(`${BASE_URL}?fields=id,modifiedTime`, {
123
+ const res = await this.fetch(`${this.baseUrl}?fields=id,modifiedTime`, {
108
124
  method: 'POST',
109
125
  headers: { 'Content-Type': 'application/json' },
110
126
  body: JSON.stringify(metadata)
@@ -112,12 +128,12 @@ class GoogleDriveClient {
112
128
  const data = await res.json();
113
129
  return {
114
130
  id: data.id,
115
- etag: data.etag || '',
116
- modifiedTime: data.modifiedTime || ''
131
+ etag: this.extractEtag(res, data),
132
+ modifiedTime: data.modifiedTime || res.headers.get('Last-Modified') || ''
117
133
  };
118
134
  }
119
135
  const multipartBody = this.buildMultipart(metadata, content, mimeType);
120
- const res = await this.fetch(`${UPLOAD_URL}?uploadType=multipart&fields=id,modifiedTime`, {
136
+ const res = await this.fetch(`${this.uploadUrl}?uploadType=multipart&fields=id,modifiedTime`, {
121
137
  method: 'POST',
122
138
  headers: {
123
139
  'Content-Type': `multipart/related; boundary=${multipartBody.boundary}`
@@ -127,27 +143,34 @@ class GoogleDriveClient {
127
143
  const data = await res.json();
128
144
  return {
129
145
  id: data.id,
130
- etag: data.etag || '',
131
- modifiedTime: data.modifiedTime || ''
146
+ etag: this.extractEtag(res, data),
147
+ modifiedTime: data.modifiedTime || res.headers.get('Last-Modified') || ''
132
148
  };
133
149
  }
134
150
  async updateFile(fileId, content, expectedEtag) {
135
151
  // Update content (media) usually, but sometimes meta?
136
152
  // In our usage (saveMeta), we update body.
137
- const res = await this.fetch(`${UPLOAD_URL}/${fileId}?uploadType=media&fields=id,modifiedTime`, {
153
+ const res = await this.fetch(`${this.uploadUrl}/${fileId}?uploadType=media&fields=id,modifiedTime`, {
138
154
  method: 'PATCH',
139
- headers: expectedEtag ? { 'If-Match': expectedEtag, 'Content-Type': 'application/json' } : { 'Content-Type': 'application/json' },
155
+ headers: expectedEtag ? { 'If-Match': `"${expectedEtag}"`, 'Content-Type': 'application/json' } : { 'Content-Type': 'application/json' },
140
156
  body: content
141
157
  });
142
158
  const data = await res.json();
143
159
  return {
144
160
  id: data.id,
145
- etag: data.etag || '',
146
- modifiedTime: data.modifiedTime || ''
161
+ etag: this.extractEtag(res, data),
162
+ modifiedTime: data.modifiedTime || res.headers.get('Last-Modified') || ''
147
163
  };
148
164
  }
165
+ extractEtag(res, data) {
166
+ const headerEtag = res.headers.get('ETag');
167
+ if (headerEtag) {
168
+ return headerEtag.replace(/"/g, '');
169
+ }
170
+ return data.etag || '';
171
+ }
149
172
  async deleteFile(fileId) {
150
- await this.fetch(`${BASE_URL}/${fileId}`, { method: 'DELETE' });
173
+ await this.fetch(`${this.baseUrl}/${fileId}`, { method: 'DELETE' });
151
174
  }
152
175
  buildMultipart(metadata, content, contentType) {
153
176
  const boundary = '-------' + Math.random().toString(36).substring(2);
package/lib/drive.d.ts CHANGED
@@ -26,6 +26,14 @@ export declare class DriveHandler {
26
26
  private currentLogSizeEstimate;
27
27
  private listeners;
28
28
  private pollingInterval;
29
+ private loadingPromise;
30
+ private isPollingActive;
31
+ private fileCache;
32
+ private processedLogIds;
33
+ private currentSnapshotIndexId;
34
+ private debug;
35
+ private isCompacting;
36
+ private log;
29
37
  constructor(options: GoogleDriveAdapterOptions, dbName: string);
30
38
  get seq(): number;
31
39
  /** Load the database (Index Only) */
@@ -36,10 +44,12 @@ export declare class DriveHandler {
36
44
  * Index -> Cache -> Fetch
37
45
  */
38
46
  get(id: string): Promise<any | null>;
47
+ /** Generic Download with Caching and Parsing */
48
+ private fetchFile;
39
49
  /** Get multiple docs (Atomic-ish) used for _allDocs */
40
50
  getMulti(ids: string[]): Promise<any[]>;
41
51
  /** Return all keys in Index */
42
- getIndexKeys(): string[];
52
+ getIndexKeys(): Promise<string[]>;
43
53
  /** Get metadata for a specific ID from Index */
44
54
  getIndexEntry(id: string): IndexEntry | undefined;
45
55
  /** Single change wrapper */
@@ -64,7 +74,7 @@ export declare class DriveHandler {
64
74
  private cleanupOldFiles;
65
75
  private startPolling;
66
76
  private notifyListeners;
67
- onChange(cb: any): void;
77
+ onChange(cb: (changes: Record<string, any>) => void): () => void;
68
78
  stopPolling(): void;
69
79
  private escapeQuery;
70
80
  deleteFolder(): Promise<void>;