@adobe/helix-google-support 1.0.1 → 1.1.0
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/CHANGELOG.md +7 -0
- package/package.json +4 -4
- package/src/GoogleClient.js +79 -17
- package/src/cache.js +0 -78
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [1.1.0](https://github.com/adobe/helix-google-support/compare/v1.0.1...v1.1.0) (2022-04-28)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* add cache invalidate for drive items ([#8](https://github.com/adobe/helix-google-support/issues/8)) ([7aa2c80](https://github.com/adobe/helix-google-support/commit/7aa2c8061facb2d3c45929356c23370d83686f55))
|
|
7
|
+
|
|
1
8
|
## [1.0.1](https://github.com/adobe/helix-google-support/compare/v1.0.0...v1.0.1) (2022-04-20)
|
|
2
9
|
|
|
3
10
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/helix-google-support",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Helix Google Support",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -40,14 +40,14 @@
|
|
|
40
40
|
"@semantic-release/changelog": "6.0.1",
|
|
41
41
|
"@semantic-release/git": "10.0.1",
|
|
42
42
|
"@semantic-release/npm": "9.0.1",
|
|
43
|
-
"c8": "7.11.
|
|
43
|
+
"c8": "7.11.2",
|
|
44
44
|
"codecov": "3.8.3",
|
|
45
|
-
"eslint": "8.
|
|
45
|
+
"eslint": "8.14.0",
|
|
46
46
|
"eslint-plugin-header": "3.1.1",
|
|
47
47
|
"eslint-plugin-import": "2.26.0",
|
|
48
48
|
"husky": "7.0.4",
|
|
49
49
|
"junit-report-builder": "3.0.0",
|
|
50
|
-
"lint-staged": "12.
|
|
50
|
+
"lint-staged": "12.4.0",
|
|
51
51
|
"mocha": "9.2.2",
|
|
52
52
|
"mocha-multi-reporters": "1.5.1",
|
|
53
53
|
"nock": "13.2.4",
|
package/src/GoogleClient.js
CHANGED
|
@@ -9,13 +9,15 @@
|
|
|
9
9
|
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
|
+
import LRU from 'lru-cache';
|
|
12
13
|
import { google } from 'googleapis';
|
|
13
14
|
import {
|
|
14
15
|
editDistance, sanitizeName, splitByExtension,
|
|
15
16
|
} from '@adobe/helix-onedrive-support/utils';
|
|
16
17
|
import { StatusCodeError } from '@adobe/helix-onedrive-support';
|
|
17
18
|
import { GoogleTokenCache } from './GoogleTokenCache.js';
|
|
18
|
-
|
|
19
|
+
|
|
20
|
+
let lru = new LRU({ max: 1000, ttl: 60000 });
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
23
|
* @typedef DriveItemInfo {
|
|
@@ -39,6 +41,19 @@ function addLastModified(itemInfo, item) {
|
|
|
39
41
|
return itemInfo;
|
|
40
42
|
}
|
|
41
43
|
|
|
44
|
+
function createPathSegments(path) {
|
|
45
|
+
const pathSegments = path.split('/');
|
|
46
|
+
if (!pathSegments[0]) {
|
|
47
|
+
// if path starts with '/' the first segment is empty
|
|
48
|
+
pathSegments.shift();
|
|
49
|
+
}
|
|
50
|
+
return pathSegments;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getCacheKey(parentId, pathSegments) {
|
|
54
|
+
return `${parentId}:${pathSegments.join('/')}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
42
57
|
/**
|
|
43
58
|
* Google auth client
|
|
44
59
|
*/
|
|
@@ -50,7 +65,7 @@ export class GoogleClient {
|
|
|
50
65
|
* @param opts
|
|
51
66
|
*/
|
|
52
67
|
static setItemCacheOptions(opts) {
|
|
53
|
-
|
|
68
|
+
lru = new LRU(opts);
|
|
54
69
|
}
|
|
55
70
|
|
|
56
71
|
/**
|
|
@@ -95,13 +110,6 @@ export class GoogleClient {
|
|
|
95
110
|
version: 'v3',
|
|
96
111
|
auth: this.auth,
|
|
97
112
|
});
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Cached version of `getUncachedItemsFromSegments`
|
|
101
|
-
*/
|
|
102
|
-
this.getDriveItemsFromSegments = cache(this.getUncachedItemsFromSegments.bind(this), {
|
|
103
|
-
hash: (fn, segs, parentId) => `${parentId}:${segs.join('/')}`,
|
|
104
|
-
});
|
|
105
113
|
}
|
|
106
114
|
|
|
107
115
|
async init() {
|
|
@@ -142,10 +150,10 @@ export class GoogleClient {
|
|
|
142
150
|
}
|
|
143
151
|
|
|
144
152
|
/**
|
|
145
|
-
* @param {string}
|
|
153
|
+
* @param {string[]} pathSegments
|
|
146
154
|
* @param {string} parentId
|
|
147
155
|
* @param {string} parentPath
|
|
148
|
-
* @returns {Promise<DriveItemInfo[]
|
|
156
|
+
* @returns {Promise<DriveItemInfo[]>|null}
|
|
149
157
|
*/
|
|
150
158
|
async getUncachedItemsFromSegments(pathSegments, parentId, parentPath) {
|
|
151
159
|
const { log, drive } = this;
|
|
@@ -224,16 +232,50 @@ export class GoogleClient {
|
|
|
224
232
|
if (!children) {
|
|
225
233
|
return null;
|
|
226
234
|
}
|
|
227
|
-
|
|
228
235
|
const pathItem = addLastModified({
|
|
229
236
|
name,
|
|
230
237
|
path: itemPath,
|
|
231
238
|
id: item.id,
|
|
232
239
|
}, item);
|
|
233
240
|
|
|
241
|
+
if (children.length) {
|
|
242
|
+
// add parent references not-enumerable to avoid deep structures during serialization
|
|
243
|
+
Object.defineProperty(children[children.length - 1], 'parent', {
|
|
244
|
+
enumerable: false,
|
|
245
|
+
value: pathItem,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
234
249
|
return [...children, pathItem];
|
|
235
250
|
}
|
|
236
251
|
|
|
252
|
+
/**
|
|
253
|
+
* @param {string} pathSegments
|
|
254
|
+
* @param {string} parentId
|
|
255
|
+
* @param {string} parentPath
|
|
256
|
+
* @returns {Promise<DriveItemInfo[]>|null}
|
|
257
|
+
*/
|
|
258
|
+
async getDriveItemsFromSegments(pathSegments, parentId, parentPath) {
|
|
259
|
+
const key = getCacheKey(parentId, pathSegments);
|
|
260
|
+
let items = lru.get(key);
|
|
261
|
+
if (items) {
|
|
262
|
+
return items;
|
|
263
|
+
}
|
|
264
|
+
items = await this.getUncachedItemsFromSegments(pathSegments, parentId, parentPath);
|
|
265
|
+
if (items) {
|
|
266
|
+
lru.set(key, items);
|
|
267
|
+
// add invalidation function as not-enumerable to keep compatible objects
|
|
268
|
+
Object.defineProperty(items[items.length - 1], 'invalidate', {
|
|
269
|
+
enumerable: false,
|
|
270
|
+
value() {
|
|
271
|
+
lru.delete(key);
|
|
272
|
+
this.parent?.invalidate();
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
return items;
|
|
277
|
+
}
|
|
278
|
+
|
|
237
279
|
/**
|
|
238
280
|
* returns the items hierarchy for the given path and root id, starting with the given path.
|
|
239
281
|
* @param path
|
|
@@ -241,11 +283,7 @@ export class GoogleClient {
|
|
|
241
283
|
* @return {DriveItemInfo[]}
|
|
242
284
|
*/
|
|
243
285
|
async getItemsFromPath(path, rootId) {
|
|
244
|
-
const segs = path
|
|
245
|
-
if (!segs[0]) {
|
|
246
|
-
// if path starts with '/' the first segment is empty
|
|
247
|
-
segs.shift();
|
|
248
|
-
}
|
|
286
|
+
const segs = createPathSegments(path);
|
|
249
287
|
const result = await this.getDriveItemsFromSegments(segs, rootId, '');
|
|
250
288
|
if (!result) {
|
|
251
289
|
return [];
|
|
@@ -325,6 +363,25 @@ export class GoogleClient {
|
|
|
325
363
|
}
|
|
326
364
|
|
|
327
365
|
/**
|
|
366
|
+
* Returns the (cached) item for the given path or {@code null} if the item cannot be found.
|
|
367
|
+
* The item will contains a `invalidate()` method which can be used to remove it from the cache.
|
|
368
|
+
*
|
|
369
|
+
* @param {string} rootId
|
|
370
|
+
* @param {string} path
|
|
371
|
+
* @returns {Promise<DriveItemInfo>}
|
|
372
|
+
*/
|
|
373
|
+
async getItemFromPath(rootId, path) {
|
|
374
|
+
const segs = createPathSegments(path);
|
|
375
|
+
const items = await this.getDriveItemsFromSegments(segs, rootId, '');
|
|
376
|
+
const item = items?.[0];
|
|
377
|
+
if (!item) {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
return item;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Returns an (uncached) file directly via the google api
|
|
328
385
|
* @param {string} fileId
|
|
329
386
|
* @returns {string} file data
|
|
330
387
|
*/
|
|
@@ -343,6 +400,11 @@ export class GoogleClient {
|
|
|
343
400
|
}
|
|
344
401
|
}
|
|
345
402
|
|
|
403
|
+
/**
|
|
404
|
+
* Returns an (uncached) document directly via the google api
|
|
405
|
+
* @param {string} documentId
|
|
406
|
+
* @returns {object} document
|
|
407
|
+
*/
|
|
346
408
|
async getDocument(documentId) {
|
|
347
409
|
const docs = google.docs({
|
|
348
410
|
version: 'v1',
|
package/src/cache.js
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright 2020 Adobe. All rights reserved.
|
|
3
|
-
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
-
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
-
*
|
|
7
|
-
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
-
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
-
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
-
* governing permissions and limitations under the License.
|
|
11
|
-
*/
|
|
12
|
-
import LRU from 'lru-cache';
|
|
13
|
-
|
|
14
|
-
let lru = new LRU({ max: 1000, ttl: 60000 });
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Returns a memoized version of the function `fn`.
|
|
18
|
-
* @param {function} fn a function to memoize
|
|
19
|
-
* @param {object} opts caching options
|
|
20
|
-
* @param {function} opts.hash a hash function to build a cache key.
|
|
21
|
-
* The hash function will be called with the `fn` and a list of
|
|
22
|
-
* it's arguments. A good hashing function will discard irrelevant
|
|
23
|
-
* arguments and join the resulting arguments
|
|
24
|
-
* @param {function} opts.cacheresult a predicate function that will be called
|
|
25
|
-
* with the result of the function execution. Only when this function returns
|
|
26
|
-
* `true` will the result be cached. The default is that all results are
|
|
27
|
-
* cached.
|
|
28
|
-
* @param {function} opts.cacheerror a predicate function that will be called
|
|
29
|
-
* with the error that the function execution throws. Only when this
|
|
30
|
-
* function returns `true` will the error be cached. The default is that
|
|
31
|
-
* errors are never cached, so that each new invocation will be uncached.
|
|
32
|
-
*/
|
|
33
|
-
export default function cache(fn, opts = {}) {
|
|
34
|
-
const {
|
|
35
|
-
hash = (...args) => args.join(),
|
|
36
|
-
cacheresult = () => true,
|
|
37
|
-
cacheerror = () => false,
|
|
38
|
-
} = opts;
|
|
39
|
-
return async function cached(...args) {
|
|
40
|
-
const key = hash(fn, ...args);
|
|
41
|
-
const entry = lru.get(key);
|
|
42
|
-
if (entry) {
|
|
43
|
-
if (entry.err) {
|
|
44
|
-
throw entry.err;
|
|
45
|
-
}
|
|
46
|
-
return entry.ok;
|
|
47
|
-
}
|
|
48
|
-
try {
|
|
49
|
-
// invoke the function
|
|
50
|
-
const result = await fn(...args);
|
|
51
|
-
if (cacheresult(result)) {
|
|
52
|
-
// store the result under ok if permitted
|
|
53
|
-
lru.set(key, { ok: result });
|
|
54
|
-
}
|
|
55
|
-
// and return the result
|
|
56
|
-
return result;
|
|
57
|
-
} catch (err) {
|
|
58
|
-
if (cacheerror(err)) {
|
|
59
|
-
// store the error under err if permitted
|
|
60
|
-
lru.set(key, { err });
|
|
61
|
-
}
|
|
62
|
-
// and throw the error
|
|
63
|
-
throw err;
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Resets the QuickLRU cache with new options. Existing cache entries
|
|
70
|
-
* will be cleared.
|
|
71
|
-
* @param {object} opts options
|
|
72
|
-
* @param {integer} opts.maxSize maximum size of the cache
|
|
73
|
-
*/
|
|
74
|
-
cache.options = (opts) => {
|
|
75
|
-
lru = new LRU(opts);
|
|
76
|
-
|
|
77
|
-
return cache;
|
|
78
|
-
};
|