@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/.env.example +3 -0
- package/README.md +72 -72
- package/compaction_test.log +725 -0
- package/docs/DEBUG_BIDIRECTIONAL_REPLICATION.ipynb +481 -0
- package/{DOCUMENTATION.md → docs/DOCUMENTATION.md} +61 -0
- package/docs/PROMISE_SUPPORT.md +75 -0
- package/docs/REPLICATION_ISSUE.md +63 -0
- package/emulation-test.log +163 -0
- package/failing_test.log +31 -0
- package/final_test.log +2620 -0
- package/jest.config.js +8 -8
- package/lib/adapter.d.ts +3 -0
- package/lib/adapter.js +373 -163
- package/lib/cache.d.ts +0 -0
- package/lib/cache.js +0 -0
- package/lib/client.d.ts +5 -0
- package/lib/client.js +43 -20
- package/lib/drive.d.ts +12 -2
- package/lib/drive.js +373 -189
- package/lib/index.d.ts +0 -0
- package/lib/index.js +4 -3
- package/lib/types.d.ts +6 -0
- package/lib/types.js +0 -0
- package/live_test.log +1209 -0
- package/package.json +47 -40
- package/test_output.log +563 -0
- package/test_output_2.log +1408 -0
- package/test_output_3.log +2445 -0
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
|
|
5
|
-
const
|
|
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
|
|
20
|
-
|
|
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
|
-
|
|
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(`${
|
|
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(`${
|
|
97
|
-
|
|
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(`${
|
|
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:
|
|
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(`${
|
|
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:
|
|
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(`${
|
|
153
|
+
const res = await this.fetch(`${this.uploadUrl}/${fileId}?uploadType=media&fields=id,modifiedTime`, {
|
|
138
154
|
method: 'PATCH',
|
|
139
|
-
headers: expectedEtag ? { 'If-Match': expectedEtag
|
|
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:
|
|
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(`${
|
|
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>;
|