@adobe/helix-google-support 1.0.0 → 1.2.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 CHANGED
@@ -1,3 +1,24 @@
1
+ # [1.2.0](https://github.com/adobe/helix-google-support/compare/v1.1.0...v1.2.0) (2022-04-28)
2
+
3
+
4
+ ### Features
5
+
6
+ * add get file|doc from path ([#9](https://github.com/adobe/helix-google-support/issues/9)) ([4a2e927](https://github.com/adobe/helix-google-support/commit/4a2e9272f4328e936e65b7144d04e516ac602625))
7
+
8
+ # [1.1.0](https://github.com/adobe/helix-google-support/compare/v1.0.1...v1.1.0) (2022-04-28)
9
+
10
+
11
+ ### Features
12
+
13
+ * 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))
14
+
15
+ ## [1.0.1](https://github.com/adobe/helix-google-support/compare/v1.0.0...v1.0.1) (2022-04-20)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * minor update ([#6](https://github.com/adobe/helix-google-support/issues/6)) ([8d7cd50](https://github.com/adobe/helix-google-support/commit/8d7cd50c8162da12c4d4430833416fac0f2eda93))
21
+
1
22
  # 1.0.0 (2022-04-13)
2
23
 
3
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/helix-google-support",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Helix Google Support",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -40,16 +40,17 @@
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.0",
43
+ "c8": "7.11.2",
44
44
  "codecov": "3.8.3",
45
- "eslint": "8.13.0",
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.3.7",
50
+ "lint-staged": "12.4.0",
51
51
  "mocha": "9.2.2",
52
52
  "mocha-multi-reporters": "1.5.1",
53
+ "nock": "13.2.4",
53
54
  "semantic-release": "19.0.2"
54
55
  },
55
56
  "lint-staged": {
@@ -0,0 +1,140 @@
1
+ /*
2
+ * Copyright 2022 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
+ declare interface ICachePlugin {}
13
+
14
+ declare interface DriveItemInfo {}
15
+
16
+ declare interface GoogleClientOptions {
17
+ log:Console;
18
+ clientId:string;
19
+ clientSecret:string;
20
+ redirectUri:string;
21
+ cachePlugin?:ICachePlugin;
22
+ }
23
+
24
+ /**
25
+ * Google client
26
+ */
27
+ declare class GoogleClient {
28
+ /**
29
+ * Sets the global item cache options. It internally creates a new LRU
30
+ * with the given options
31
+ *
32
+ * @param opts
33
+ */
34
+ static setItemCacheOptions(opts:object);
35
+
36
+ /**
37
+ * Returns a url for a google drive id.
38
+ * @param {string} id
39
+ * @returns {string}
40
+ */
41
+ static id2Url(id:string):string;
42
+
43
+ constructor(opts:GoogleClientOptions);
44
+
45
+ init():Promise<GoogleClient>;
46
+
47
+ generateAuthUrl(...args):Promise<string>;
48
+
49
+ /**
50
+ * Sets the credentials
51
+ * @param tokens
52
+ * @returns {Promise<void>}
53
+ */
54
+ setCredentials(tokens:object):Promise<void>;
55
+
56
+ /**
57
+ * Returns the token for the given code
58
+ * @param {string} code
59
+ * @returns {Promise<*>}
60
+ */
61
+ getToken(code:string):Promise<object>;
62
+
63
+ /**
64
+ * @param {string} parentId
65
+ * @param {string[]} pathSegments
66
+ * @param {string} parentPath
67
+ * @returns {Promise<DriveItemInfo[]>|null}
68
+ */
69
+ getUncachedItemsFromSegments(parentId:string, pathSegments:string[], parentPath:string):Promise<DriveItemInfo[]>;
70
+
71
+ /**
72
+ * @param {string} parentId
73
+ * @param {string} pathSegments
74
+ * @param {string} parentPath
75
+ * @returns {Promise<DriveItemInfo[]>|null}
76
+ */
77
+ getDriveItemsFromSegments(parentId:string, pathSegments:string[], parentPath:string):Promise<DriveItemInfo[]>;
78
+
79
+ /**
80
+ * returns the items hierarchy for the given path and root id, starting with the given path.
81
+ * @param {string} parentId
82
+ * @param {string} path
83
+ * @return {DriveItemInfo[]}
84
+ */
85
+ getDriveItemsFromSegments(parentId:string, path:string):Promise<DriveItemInfo[]>;
86
+
87
+ /**
88
+ * returns the items hierarchy for the given item, starting with the given id
89
+ * @param {string} fileId
90
+ * @param {object} roots
91
+ * @returns {Promise<DriveItemInfo[]>}
92
+ */
93
+ getItemsFromId(fileId:string, roots:object):Promise<DriveItemInfo[]>;
94
+
95
+ /**
96
+ * Returns the (cached) item for the given path or {@code null} if the item cannot be found.
97
+ * The item will contains a `invalidate()` method which can be used to remove it from the cache.
98
+ *
99
+ * @param {string} parentId
100
+ * @param {string} path
101
+ * @returns {Promise<DriveItemInfo>}
102
+ */
103
+ getItemFromPath(parentId:string, path:string):Promise<DriveItemInfo>;
104
+
105
+ /**
106
+ * Returns an (uncached) file directly via the google api
107
+ * @param {string} fileId
108
+ * @returns {string} file data
109
+ */
110
+ getFile(fileId:string):Promise<string>;
111
+
112
+ /**
113
+ * Fetches the file data from the give path. If the file with the internal id could not be
114
+ * fetched, the item cache is invalidated and the operation is retried. this is to support
115
+ * moved items.
116
+ * @param {string} parentId
117
+ * @param {string} path
118
+ * @param {boolean} noRetry {@code true} to avoid retry
119
+ * @returns {Promise<string>|null} The data of the file or {@code null} if the file does not exist
120
+ */
121
+ getFileFromPath(parentId:string, path:string, noRetry:boolean):Promise<string>;
122
+
123
+ /**
124
+ * Returns an (uncached) document directly via the google api
125
+ * @param {string} documentId
126
+ * @returns {object} document
127
+ */
128
+ getDocument(documentId:string):Promise<object>;
129
+
130
+ /**
131
+ * Fetches the document from the give path. If the document with the internal id could not be
132
+ * fetched, the item cache is invalidated and the operation is retried. this is to support
133
+ * moved items.
134
+ * @param {string} parentId
135
+ * @param {string} path
136
+ * @param {boolean} noRetry {@code true} to avoid retry
137
+ * @returns {Promise<object>|null} The document or {@code null} if the document does not exist
138
+ */
139
+ getDocumentFromPath(parentId:string, path:string, noRetry:boolean):Promise<object>;
140
+ }
@@ -9,15 +9,65 @@
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
- import { editDistance, sanitizeName, splitByExtension } from '@adobe/helix-onedrive-support/utils';
14
+ import {
15
+ editDistance, sanitizeName, splitByExtension,
16
+ } from '@adobe/helix-onedrive-support/utils';
17
+ import { StatusCodeError } from '@adobe/helix-onedrive-support';
14
18
  import { GoogleTokenCache } from './GoogleTokenCache.js';
15
- import cache from './cache.js';
19
+
20
+ let lru = new LRU({ max: 1000, ttl: 60000 });
21
+
22
+ /**
23
+ * @typedef DriveItemInfo {
24
+ * @property {string} id
25
+ * @property {string} name
26
+ * @property {string} url
27
+ * @property {string} path
28
+ */
29
+
30
+ /**
31
+ * Adds the last modified property if defined in item
32
+ * @param {DriveItemInfo} itemInfo
33
+ * @param item
34
+ * @returns {DriveItemInfo}
35
+ */
36
+ function addLastModified(itemInfo, item) {
37
+ if (item.modifiedTime) {
38
+ // eslint-disable-next-line no-param-reassign
39
+ itemInfo.lastModified = Date.parse(item.modifiedTime);
40
+ }
41
+ return itemInfo;
42
+ }
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
+ }
16
56
 
17
57
  /**
18
58
  * Google auth client
19
59
  */
20
60
  export class GoogleClient {
61
+ /**
62
+ * Sets the global item cache options. It internally creates a new LRU
63
+ * with the given options
64
+ *
65
+ * @param opts
66
+ */
67
+ static setItemCacheOptions(opts) {
68
+ lru = new LRU(opts);
69
+ }
70
+
21
71
  /**
22
72
  * Returns a url for a google drive id.
23
73
  * @param {string} id
@@ -35,39 +85,38 @@ export class GoogleClient {
35
85
  * @param {ICachePlugin} plugin
36
86
  */
37
87
  constructor(opts) {
38
- this.log = opts.log;
39
- this.auth = new google.auth.OAuth2(
40
- opts.clientId,
41
- opts.clientSecret,
42
- opts.redirectUri,
43
- );
44
- this.cachePlugin = opts.cachePlugin;
45
- this.cache = new GoogleTokenCache(opts.cachePlugin).withLog(opts.log);
46
-
47
- /// hack to capture tokens, since the emit handler is not awaited in the google client
48
- const originalRefreshTokenNoCache = this.auth.refreshTokenNoCache.bind(this.auth);
49
- this.auth.refreshTokenNoCache = async (...args) => {
50
- const ret = await originalRefreshTokenNoCache(...args);
51
- await this.cache.store(ret.tokens);
52
- return ret;
53
- };
88
+ Object.assign(this, {
89
+ log: opts.log,
90
+ auth: new google.auth.OAuth2(
91
+ opts.clientId,
92
+ opts.clientSecret,
93
+ opts.redirectUri,
94
+ ),
95
+ });
96
+
97
+ if (opts.cachePlugin) {
98
+ this.cachePlugin = opts.cachePlugin;
99
+ this.cache = new GoogleTokenCache(opts.cachePlugin).withLog(opts.log);
100
+ /// hack to capture tokens, since the emit handler is not awaited in the google client
101
+ const originalRefreshTokenNoCache = this.auth.refreshTokenNoCache.bind(this.auth);
102
+ this.auth.refreshTokenNoCache = async (...args) => {
103
+ const ret = await originalRefreshTokenNoCache(...args);
104
+ await this.cache.store(ret.tokens);
105
+ return ret;
106
+ };
107
+ }
54
108
 
55
109
  this.drive = google.drive({
56
110
  version: 'v3',
57
111
  auth: this.auth,
58
112
  });
59
-
60
- /**
61
- * Cached version of `getUncachedItemsFromPath`
62
- */
63
- this.getDriveItemsFromPath = cache(this.getUncachedItemsFromPath.bind(this), {
64
- hash: (fn, path, parentId) => `${parentId}:${path}`,
65
- });
66
113
  }
67
114
 
68
115
  async init() {
69
- await this.cache.load();
70
- this.auth.setCredentials(this.cache.tokens);
116
+ if (this.cache) {
117
+ await this.cache.load();
118
+ this.auth.setCredentials(this.cache.tokens);
119
+ }
71
120
  return this;
72
121
  }
73
122
 
@@ -75,28 +124,40 @@ export class GoogleClient {
75
124
  return this.auth.generateAuthUrl(...args);
76
125
  }
77
126
 
127
+ /**
128
+ * Sets the credentials
129
+ * @param tokens
130
+ * @returns {Promise<void>}
131
+ */
78
132
  async setCredentials(tokens) {
79
- await this.cache.store(tokens);
133
+ if (this.cache) {
134
+ await this.cache.store(tokens);
135
+ }
80
136
  this.auth.setCredentials(tokens);
81
137
  }
82
138
 
139
+ /**
140
+ * Returns the token for the given code
141
+ * @param {string} code
142
+ * @returns {Promise<*>}
143
+ */
83
144
  async getToken(code) {
84
- const resp = await this.auth.getToken(code);
85
- await this.cache.store(resp.tokens);
86
- return resp;
145
+ const { tokens } = await this.auth.getToken(code);
146
+ if (this.cache) {
147
+ await this.cache.store(tokens);
148
+ }
149
+ return tokens;
87
150
  }
88
151
 
89
152
  /**
90
- * @param {Drive} drive
91
- * @param {AdminContext} context
92
- * @param {string} path
93
153
  * @param {string} parentId
154
+ * @param {string[]} pathSegments
94
155
  * @param {string} parentPath
95
- * @returns {Promise<EditFolderInfo[]>}
156
+ * @returns {Promise<DriveItemInfo[]>|null}
96
157
  */
97
- async getUncachedItemsFromPath(path, parentId, parentPath) {
158
+ async getUncachedItemsFromSegments(parentId, pathSegments, parentPath) {
98
159
  const { log, drive } = this;
99
- const [name, ...rest] = path.split('/');
160
+ const name = pathSegments.shift();
100
161
  const [baseName, ext] = splitByExtension(name);
101
162
  const sanitizedName = sanitizeName(baseName);
102
163
 
@@ -106,7 +167,7 @@ export class GoogleClient {
106
167
  `'${parentId}' in parents`,
107
168
  'and trashed=false',
108
169
  // folder if path continues, sheet otherwise
109
- `and mimeType ${rest.length ? '=' : '!='} 'application/vnd.google-apps.folder'`,
170
+ `and mimeType ${pathSegments.length ? '=' : '!='} 'application/vnd.google-apps.folder'`,
110
171
  ].join(' '),
111
172
  fields: 'nextPageToken, files(id, name, modifiedTime)',
112
173
  includeItemsFromAllDrives: true,
@@ -163,50 +224,83 @@ export class GoogleClient {
163
224
  }
164
225
 
165
226
  const itemPath = `${parentPath}/${sanitizedName}`;
166
- const children = rest.length
227
+ const children = pathSegments.length
167
228
  // eslint-disable-next-line no-use-before-define
168
- ? await this.getDriveItemsFromPath(rest.join('/'), item.id, itemPath)
229
+ ? await this.getDriveItemsFromSegments(item.id, pathSegments, itemPath)
169
230
  : [];
170
231
 
171
232
  if (!children) {
172
233
  return null;
173
234
  }
174
-
175
- const pathItem = {
235
+ const pathItem = addLastModified({
176
236
  name,
177
237
  path: itemPath,
178
238
  id: item.id,
179
- lastModified: Date.parse(item.modifiedTime),
180
- };
239
+ }, item);
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
+ }
181
248
 
182
249
  return [...children, pathItem];
183
250
  }
184
251
 
252
+ /**
253
+ * @param {string} parentId
254
+ * @param {string} pathSegments
255
+ * @param {string} parentPath
256
+ * @returns {Promise<DriveItemInfo[]>|null}
257
+ */
258
+ async getDriveItemsFromSegments(parentId, pathSegments, 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(parentId, pathSegments, 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
+
185
279
  /**
186
280
  * returns the items hierarchy for the given path and root id, starting with the given path.
187
- * @param context
188
- * @param path
189
- * @param rootId
281
+ * @param {string} parentId
282
+ * @param {string} path
283
+ * @return {DriveItemInfo[]}
190
284
  */
191
- async getItemsFromPath(path, rootId) {
192
- const result = await this.getDriveItemsFromPath(path, rootId, '');
285
+ async getItemsFromPath(parentId, path) {
286
+ const segs = createPathSegments(path);
287
+ const result = await this.getDriveItemsFromSegments(parentId, segs, '');
193
288
  if (!result) {
194
- return null;
289
+ return [];
195
290
  }
196
291
  return [...result, {
197
292
  name: '',
198
293
  path: '/',
199
- id: rootId,
294
+ id: parentId,
200
295
  }];
201
296
  }
202
297
 
203
298
  /**
204
299
  * returns the items hierarchy for the given item, starting with the given id
205
- * @param {AdminContext} context
206
- * @param {Drive} drive
207
300
  * @param {string} fileId
208
301
  * @param {object} roots
209
- * @returns {Promise<EditFolderInfo[]>}
302
+ * @private
303
+ * @returns {Promise<DriveItemInfo[]>}
210
304
  */
211
305
  async getItems(fileId, roots) {
212
306
  const { log } = this;
@@ -223,62 +317,170 @@ export class GoogleClient {
223
317
  const root = roots[fileId];
224
318
  if (root) {
225
319
  // stop at mount root
226
- return [{
320
+ return [addLastModified({
227
321
  id: fileId,
228
322
  name: '',
229
323
  path: root,
230
- lastModified: Date.parse(data.modifiedTime),
231
- }];
324
+ }, data)];
232
325
  }
233
326
 
234
327
  const parentId = data.parents ? data.parents[0] : '';
235
328
  if (!parentId) {
236
329
  // outside mountpoint
237
- return [{
330
+ return [addLastModified({
238
331
  id: fileId,
239
332
  name: data.name,
240
333
  path: `/root:/${data.name}`,
241
- lastModified: Date.parse(data.modifiedTime),
242
- }];
334
+ }, data)];
243
335
  }
244
336
 
245
337
  const ancestors = await this.getItems(data.parents[0], roots);
246
338
  const parentPath = ancestors[0].path.replace(/\/+$/, '');
247
- ancestors.unshift({
339
+ ancestors.unshift(addLastModified({
248
340
  id: fileId,
249
341
  name: data.name,
250
342
  path: `${parentPath}/${data.name}`,
251
- lastModified: Date.parse(data.modifiedTime),
252
- });
343
+ }, data));
253
344
  return ancestors;
254
345
  }
255
346
 
256
347
  /**
257
- * @param {AdminContext} context
258
348
  * @param {string} fileId
259
349
  * @param {object} roots
260
- * @returns {Promise<EditFolderInfo[]>}
350
+ * @returns {Promise<DriveItemInfo[]>}
261
351
  */
262
352
  async getItemsFromId(fileId, roots) {
263
353
  const { log } = this;
264
354
  try {
265
355
  return await this.getItems(fileId, roots);
266
356
  } catch (e) {
357
+ if (e.response && e.response.status === 404) {
358
+ log.warn(`unable to get items for ${fileId}. Not found`);
359
+ return [];
360
+ }
267
361
  log.warn(`unable to get items for ${fileId}. ${e}`);
268
- return [];
362
+ throw e;
269
363
  }
270
364
  }
271
365
 
272
366
  /**
273
- * @param {AdminContext} context
367
+ * Returns the (cached) item for the given path or {@code null} if the item cannot be found.
368
+ * The item will contains a `invalidate()` method which can be used to remove it from the cache.
369
+ *
370
+ * @param {string} parentId
371
+ * @param {string} path
372
+ * @returns {Promise<DriveItemInfo>}
373
+ */
374
+ async getItemFromPath(parentId, path) {
375
+ const segs = createPathSegments(path);
376
+ const items = await this.getDriveItemsFromSegments(parentId, segs, '');
377
+ const item = items?.[0];
378
+ if (!item) {
379
+ return null;
380
+ }
381
+ return item;
382
+ }
383
+
384
+ /**
385
+ * Returns an (uncached) file directly via the google api
274
386
  * @param {string} fileId
275
387
  * @returns {string} file data
276
388
  */
277
389
  async getFile(fileId) {
278
- const res = await this.drive.files.get({
279
- fileId,
280
- alt: 'media',
390
+ try {
391
+ const res = await this.drive.files.get({
392
+ fileId,
393
+ alt: 'media',
394
+ });
395
+ return res.data;
396
+ } catch (e) {
397
+ if (e.response && e.response.status === 404) {
398
+ throw new StatusCodeError(`Not Found: ${fileId}`, 404);
399
+ }
400
+ throw e;
401
+ }
402
+ }
403
+
404
+ /**
405
+ * Fetches the file data from the give path. If the file with the internal id could not be
406
+ * fetched, the item cache is invalidated and the operation is retried. this is to support
407
+ * moved items.
408
+ * @param {string} parentId
409
+ * @param {string} path
410
+ * @param {boolean} noRetry {@code true} to avoid retry
411
+ * @returns {Promise<string>|null} The data of the file or {@code null} if the file does not exist
412
+ */
413
+ async getFileFromPath(parentId, path, noRetry) {
414
+ const item = await this.getItemFromPath(parentId, path);
415
+ if (!item) {
416
+ return null;
417
+ }
418
+ try {
419
+ // noinspection ES6RedundantAwait (need to catch exception)
420
+ return await this.getFile(item.id);
421
+ } catch (e) {
422
+ if (e.statusCode === 404) {
423
+ if (noRetry) {
424
+ return null;
425
+ }
426
+ this.log.info(`file ${item.id} does not exist - invalidating cache and retry`);
427
+ item.invalidate();
428
+ return this.getFileFromPath(parentId, path, true);
429
+ } else {
430
+ throw e;
431
+ }
432
+ }
433
+ }
434
+
435
+ /**
436
+ * Returns an (uncached) document directly via the google api
437
+ * @param {string} documentId
438
+ * @returns {object} document
439
+ */
440
+ async getDocument(documentId) {
441
+ const docs = google.docs({
442
+ version: 'v1',
443
+ auth: this.auth,
281
444
  });
282
- return res.data;
445
+ try {
446
+ const res = await docs.documents.get({ documentId });
447
+ return res.data;
448
+ } catch (e) {
449
+ if (e.response && e.response.status === 404) {
450
+ throw new StatusCodeError(`Not Found: ${documentId}`, 404);
451
+ }
452
+ throw e;
453
+ }
454
+ }
455
+
456
+ /**
457
+ * Fetches the document from the give path. If the document with the internal id could not be
458
+ * fetched, the item cache is invalidated and the operation is retried. this is to support
459
+ * moved items.
460
+ * @param {string} parentId
461
+ * @param {string} path
462
+ * @param {boolean} noRetry {@code true} to avoid retry
463
+ * @returns {Promise<object>|null} The document or {@code null} if the document does not exist
464
+ */
465
+ async getDocumentFromPath(parentId, path, noRetry) {
466
+ const item = await this.getItemFromPath(parentId, path);
467
+ if (!item) {
468
+ return null;
469
+ }
470
+ try {
471
+ // noinspection ES6RedundantAwait (need to catch exception)
472
+ return await this.getDocument(item.id);
473
+ } catch (e) {
474
+ if (e.statusCode === 404) {
475
+ if (noRetry) {
476
+ return null;
477
+ }
478
+ this.log.info(`document ${item.id} does not exist - invalidating cache and retry`);
479
+ item.invalidate();
480
+ return this.getDocumentFromPath(parentId, path, true);
481
+ } else {
482
+ throw e;
483
+ }
484
+ }
283
485
  }
284
486
  }
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
- };