@fluidframework/driver-web-cache 2.0.0-internal.4.2.1 → 2.0.0-internal.4.4.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,5 +1,9 @@
1
1
  # @fluidframework/driver-web-cache
2
2
 
3
+ ## 2.0.0-internal.4.4.0
4
+
5
+ Dependency updates only.
6
+
3
7
  ## 2.0.0-internal.4.1.0
4
8
 
5
9
  Dependency updates only.
@@ -1,3 +1,7 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
1
5
  import { IPersistedCache, ICacheEntry, IFileEntry } from "@fluidframework/odsp-driver-definitions";
2
6
  import { ITelemetryBaseLogger } from "@fluidframework/common-definitions";
3
7
  export interface FluidCacheConfig {
@@ -18,6 +22,11 @@ export interface FluidCacheConfig {
18
22
  * If an entry exists in the cache, but is older than this value, the cached value will not be returned.
19
23
  */
20
24
  maxCacheItemAge: number;
25
+ /**
26
+ * Each time db is opened, it will remain open for this much time. To improve perf, if this property is set as
27
+ * any number greater than 0, then db will not be closed immediately after usage. This value is in milliseconds.
28
+ */
29
+ closeDbAfterMs?: number;
21
30
  }
22
31
  /**
23
32
  * A cache that can be used by the Fluid ODSP driver to cache data for faster performance
@@ -26,7 +35,14 @@ export declare class FluidCache implements IPersistedCache {
26
35
  private readonly logger;
27
36
  private readonly partitionKey;
28
37
  private readonly maxCacheItemAge;
38
+ private readonly closeDbImmediately;
39
+ private readonly closeDbAfterMs;
40
+ private db;
41
+ private dbCloseTimer;
42
+ private dbReuseCount;
29
43
  constructor(config: FluidCacheConfig);
44
+ private openDb;
45
+ private closeDb;
30
46
  removeEntries(file: IFileEntry): Promise<void>;
31
47
  get(cacheEntry: ICacheEntry): Promise<any>;
32
48
  private getItemFromCache;
@@ -1 +1 @@
1
- {"version":3,"file":"FluidCache.d.ts","sourceRoot":"","sources":["../src/FluidCache.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,yCAAyC,CAAC;AACnG,OAAO,EAAE,oBAAoB,EAAoB,MAAM,oCAAoC,CAAC;AAqB5F,MAAM,WAAW,gBAAgB;IAChC;;;;;;OAMG;IAEH,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAE5B;;OAEG;IACH,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAE9B;;;OAGG;IACH,eAAe,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,qBAAa,UAAW,YAAW,eAAe;IACjD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAE1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAgB;IAE7C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;gBAE7B,MAAM,EAAE,gBAAgB;IA2DvB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAwB9C,GAAG,CAAC,UAAU,EAAE,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC;YAmBzC,gBAAgB;IAkDjB,GAAG,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;CAiC/D"}
1
+ {"version":3,"file":"FluidCache.d.ts","sourceRoot":"","sources":["../src/FluidCache.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,yCAAyC,CAAC;AACnG,OAAO,EAAE,oBAAoB,EAAoB,MAAM,oCAAoC,CAAC;AAsB5F,MAAM,WAAW,gBAAgB;IAChC;;;;;;OAMG;IAEH,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAE5B;;OAEG;IACH,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAE9B;;;OAGG;IACH,eAAe,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,qBAAa,UAAW,YAAW,eAAe;IACjD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAE1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAgB;IAE7C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAiB;IACpD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,EAAE,CAA+C;IACzD,OAAO,CAAC,YAAY,CAA4C;IAChE,OAAO,CAAC,YAAY,CAAc;gBAEtB,MAAM,EAAE,gBAAgB;YAiEtB,MAAM;IAwCpB,OAAO,CAAC,OAAO;IAMF,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAyB9C,GAAG,CAAC,UAAU,EAAE,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC;YAoBzC,gBAAgB;IAiDjB,GAAG,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;CAgC/D"}
@@ -1,17 +1,30 @@
1
1
  "use strict";
2
+ /*!
3
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
4
+ * Licensed under the MIT License.
5
+ */
2
6
  Object.defineProperty(exports, "__esModule", { value: true });
3
7
  exports.FluidCache = void 0;
8
+ const common_utils_1 = require("@fluidframework/common-utils");
4
9
  const telemetry_utils_1 = require("@fluidframework/telemetry-utils");
5
10
  const scheduleIdleTask_1 = require("./scheduleIdleTask");
6
11
  const FluidCacheIndexedDb_1 = require("./FluidCacheIndexedDb");
12
+ const packageVersion_1 = require("./packageVersion");
7
13
  /**
8
14
  * A cache that can be used by the Fluid ODSP driver to cache data for faster performance
9
15
  */
10
16
  class FluidCache {
11
17
  constructor(config) {
18
+ var _a;
19
+ this.closeDbImmediately = true;
20
+ this.dbReuseCount = -1;
12
21
  this.logger = telemetry_utils_1.ChildLogger.create(config.logger);
13
22
  this.partitionKey = config.partitionKey;
14
23
  this.maxCacheItemAge = config.maxCacheItemAge;
24
+ this.closeDbAfterMs = (_a = config.closeDbAfterMs) !== null && _a !== void 0 ? _a : 0;
25
+ if (this.closeDbAfterMs > 0) {
26
+ this.closeDbImmediately = false;
27
+ }
15
28
  (0, scheduleIdleTask_1.scheduleIdleTask)(async () => {
16
29
  var _a;
17
30
  // Log how much storage space is currently being used by indexed db.
@@ -32,6 +45,7 @@ class FluidCache {
32
45
  quota: estimate.quota,
33
46
  usage: estimate.usage,
34
47
  indexedDBSize,
48
+ pkgVersion: packageVersion_1.pkgVersion,
35
49
  });
36
50
  }
37
51
  });
@@ -50,6 +64,7 @@ class FluidCache {
50
64
  catch (error) {
51
65
  this.logger.sendErrorEvent({
52
66
  eventName: "FluidCacheDeleteOldEntriesError" /* FluidCacheDeleteOldEntriesError */,
67
+ pkgVersion: packageVersion_1.pkgVersion,
53
68
  }, error);
54
69
  }
55
70
  finally {
@@ -57,10 +72,57 @@ class FluidCache {
57
72
  }
58
73
  });
59
74
  }
75
+ async openDb() {
76
+ if (this.closeDbImmediately) {
77
+ return (0, FluidCacheIndexedDb_1.getFluidCacheIndexedDbInstance)(this.logger);
78
+ }
79
+ if (this.db === undefined) {
80
+ const dbInstance = await (0, FluidCacheIndexedDb_1.getFluidCacheIndexedDbInstance)(this.logger);
81
+ if (this.db === undefined) {
82
+ // Reset the counter on first open.
83
+ this.dbReuseCount = -1;
84
+ this.db = dbInstance;
85
+ }
86
+ else {
87
+ dbInstance.close();
88
+ this.dbReuseCount += 1;
89
+ return this.db;
90
+ }
91
+ // Need to close the db on version change if opened.
92
+ this.db.onversionchange = (ev) => {
93
+ var _a;
94
+ (_a = this.db) === null || _a === void 0 ? void 0 : _a.close();
95
+ this.db = undefined;
96
+ clearTimeout(this.dbCloseTimer);
97
+ this.dbCloseTimer = undefined;
98
+ };
99
+ this.db.addEventListener("close", (ev) => {
100
+ clearTimeout(this.dbCloseTimer);
101
+ this.dbCloseTimer = undefined;
102
+ this.db = undefined;
103
+ });
104
+ // Schedule db close after this.closeDbAfterMs.
105
+ (0, common_utils_1.assert)(this.dbCloseTimer === undefined, 0x6c6 /* timer should not be set yet!! */);
106
+ this.dbCloseTimer = setTimeout(() => {
107
+ var _a;
108
+ (_a = this.db) === null || _a === void 0 ? void 0 : _a.close();
109
+ this.db = undefined;
110
+ this.dbCloseTimer = undefined;
111
+ }, this.closeDbAfterMs);
112
+ }
113
+ (0, common_utils_1.assert)(this.db !== undefined, 0x6c7 /* db should be intialized by now */);
114
+ this.dbReuseCount += 1;
115
+ return this.db;
116
+ }
117
+ closeDb(db) {
118
+ if (this.closeDbImmediately) {
119
+ db === null || db === void 0 ? void 0 : db.close();
120
+ }
121
+ }
60
122
  async removeEntries(file) {
61
123
  let db;
62
124
  try {
63
- db = await (0, FluidCacheIndexedDb_1.getFluidCacheIndexedDbInstance)(this.logger);
125
+ db = await this.openDb();
64
126
  const transaction = db.transaction(FluidCacheIndexedDb_1.FluidDriverObjectStoreName, "readwrite");
65
127
  const index = transaction.store.index("fileId");
66
128
  const keysToDelete = await index.getAllKeys(file.docId);
@@ -70,10 +132,11 @@ class FluidCache {
70
132
  catch (error) {
71
133
  this.logger.sendErrorEvent({
72
134
  eventName: "FluidCacheDeleteOldEntriesError" /* FluidCacheDeleteOldEntriesError */,
135
+ pkgVersion: packageVersion_1.pkgVersion,
73
136
  }, error);
74
137
  }
75
138
  finally {
76
- db === null || db === void 0 ? void 0 : db.close();
139
+ this.closeDb(db);
77
140
  }
78
141
  }
79
142
  async get(cacheEntry) {
@@ -85,7 +148,8 @@ class FluidCache {
85
148
  type: cacheEntry.type,
86
149
  duration: performance.now() - startTime,
87
150
  dbOpenPerf: cachedItem === null || cachedItem === void 0 ? void 0 : cachedItem.dbOpenPerf,
88
- dbClosePerf: cachedItem === null || cachedItem === void 0 ? void 0 : cachedItem.dbClosePerf,
151
+ dbReuseCount: this.dbReuseCount,
152
+ pkgVersion: packageVersion_1.pkgVersion,
89
153
  });
90
154
  // Value will contain metadata like the expiry time, we just want to return the object we were asked to cache
91
155
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
@@ -96,11 +160,11 @@ class FluidCache {
96
160
  try {
97
161
  const key = (0, FluidCacheIndexedDb_1.getKeyForCacheEntry)(cacheEntry);
98
162
  const dbOpenStartTime = performance.now();
99
- db = await (0, FluidCacheIndexedDb_1.getFluidCacheIndexedDbInstance)(this.logger);
163
+ db = await this.openDb();
100
164
  const dbOpenPerf = performance.now() - dbOpenStartTime;
101
165
  const value = await db.get(FluidCacheIndexedDb_1.FluidDriverObjectStoreName, key);
102
166
  if (!value) {
103
- db.close();
167
+ this.closeDb(db);
104
168
  return undefined;
105
169
  }
106
170
  // If the data does not come from the same partition, don't return it
@@ -108,33 +172,32 @@ class FluidCache {
108
172
  this.logger.sendTelemetryEvent({
109
173
  eventName: "FluidCachePartitionKeyMismatch" /* FluidCachePartitionKeyMismatch */,
110
174
  subCategory: "FluidCache" /* FluidCache */,
175
+ pkgVersion: packageVersion_1.pkgVersion,
111
176
  });
112
- db.close();
177
+ this.closeDb(db);
113
178
  return undefined;
114
179
  }
115
180
  const currentTime = new Date().getTime();
116
181
  // If too much time has passed since this cache entry was used, we will also return undefined
117
182
  if (currentTime - value.createdTimeMs > this.maxCacheItemAge) {
118
- db.close();
183
+ this.closeDb(db);
119
184
  return undefined;
120
185
  }
121
- const dbCloseStartTime = performance.now();
122
- db.close();
123
- const dbClosePerf = performance.now() - dbCloseStartTime;
124
- return Object.assign(Object.assign({}, value), { dbOpenPerf, dbClosePerf });
186
+ this.closeDb(db);
187
+ return Object.assign(Object.assign({}, value), { dbOpenPerf });
125
188
  }
126
189
  catch (error) {
127
190
  // We can fail to open the db for a variety of reasons,
128
191
  // such as the database version having upgraded underneath us. Return undefined in this case
129
- this.logger.sendErrorEvent({ eventName: "FluidCacheGetError" /* FluidCacheGetError */ }, error);
130
- db === null || db === void 0 ? void 0 : db.close();
192
+ this.logger.sendErrorEvent({ eventName: "FluidCacheGetError" /* FluidCacheGetError */, pkgVersion: packageVersion_1.pkgVersion }, error);
193
+ this.closeDb(db);
131
194
  return undefined;
132
195
  }
133
196
  }
134
197
  async put(entry, value) {
135
198
  let db;
136
199
  try {
137
- db = await (0, FluidCacheIndexedDb_1.getFluidCacheIndexedDbInstance)(this.logger);
200
+ db = await this.openDb();
138
201
  const currentTime = new Date().getTime();
139
202
  await db.put(FluidCacheIndexedDb_1.FluidDriverObjectStoreName, {
140
203
  cachedObject: value,
@@ -145,15 +208,15 @@ class FluidCache {
145
208
  createdTimeMs: currentTime,
146
209
  lastAccessTimeMs: currentTime,
147
210
  }, (0, FluidCacheIndexedDb_1.getKeyForCacheEntry)(entry));
148
- db.close();
211
+ this.closeDb(db);
149
212
  }
150
213
  catch (error) {
151
214
  // We can fail to open the db for a variety of reasons,
152
215
  // such as the database version having upgraded underneath us
153
- this.logger.sendErrorEvent({ eventName: "FluidCachePutError" /* FluidCachePutError */ }, error);
216
+ this.logger.sendErrorEvent({ eventName: "FluidCachePutError" /* FluidCachePutError */, pkgVersion: packageVersion_1.pkgVersion }, error);
154
217
  }
155
218
  finally {
156
- db === null || db === void 0 ? void 0 : db.close();
219
+ this.closeDb(db);
157
220
  }
158
221
  }
159
222
  }
@@ -1 +1 @@
1
- {"version":3,"file":"FluidCache.js","sourceRoot":"","sources":["../src/FluidCache.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\nimport { IDBPDatabase } from \"idb\";\nimport { IPersistedCache, ICacheEntry, IFileEntry } from \"@fluidframework/odsp-driver-definitions\";\nimport { ITelemetryBaseLogger, ITelemetryLogger } from \"@fluidframework/common-definitions\";\nimport { ChildLogger } from \"@fluidframework/telemetry-utils\";\nimport { scheduleIdleTask } from \"./scheduleIdleTask\";\nimport {\n\tgetFluidCacheIndexedDbInstance,\n\tFluidCacheDBSchema,\n\tFluidDriverObjectStoreName,\n\tgetKeyForCacheEntry,\n} from \"./FluidCacheIndexedDb\";\nimport {\n\tFluidCacheErrorEvent,\n\tFluidCacheEventSubCategories,\n\tFluidCacheGenericEvent,\n} from \"./fluidCacheTelemetry\";\n\n// Some browsers have a usageDetails property that will tell you more detailed information\n// on how the storage is being used\ninterface StorageQuotaUsageDetails {\n\tindexedDB: number | undefined;\n}\n\nexport interface FluidCacheConfig {\n\t/**\n\t * A string to specify what partition of the cache you wish to use (e.g. a user id).\n\t * Null can be used to explicity indicate no partitioning, and has been chosen\n\t * vs undefined so that it is clear this is an intentional choice by the caller.\n\t * A null value should only be used when the host can ensure that the cache is not able\n\t * to be shared with multiple users.\n\t */\n\t// eslint-disable-next-line @rushstack/no-new-null\n\tpartitionKey: string | null;\n\n\t/**\n\t * A logger that can be used to get insight into cache performance and errors\n\t */\n\tlogger?: ITelemetryBaseLogger;\n\n\t/**\n\t * A value in milliseconds that determines the maximum age of a cache entry to return.\n\t * If an entry exists in the cache, but is older than this value, the cached value will not be returned.\n\t */\n\tmaxCacheItemAge: number;\n}\n\n/**\n * A cache that can be used by the Fluid ODSP driver to cache data for faster performance\n */\nexport class FluidCache implements IPersistedCache {\n\tprivate readonly logger: ITelemetryLogger;\n\n\tprivate readonly partitionKey: string | null;\n\n\tprivate readonly maxCacheItemAge: number;\n\n\tconstructor(config: FluidCacheConfig) {\n\t\tthis.logger = ChildLogger.create(config.logger);\n\t\tthis.partitionKey = config.partitionKey;\n\t\tthis.maxCacheItemAge = config.maxCacheItemAge;\n\n\t\tscheduleIdleTask(async () => {\n\t\t\t// Log how much storage space is currently being used by indexed db.\n\t\t\t// NOTE: This API is not supported in all browsers and it doesn't let you see the size of a specific DB.\n\t\t\t// Exception added when eslint rule was added, this should be revisited when modifying this code\n\t\t\tif (navigator.storage?.estimate) {\n\t\t\t\tconst estimate = await navigator.storage.estimate();\n\n\t\t\t\t// Some browsers have a usageDetails property that will tell you\n\t\t\t\t// more detailed information on how the storage is being used\n\t\t\t\tlet indexedDBSize: number | undefined;\n\t\t\t\tif (\"usageDetails\" in estimate) {\n\t\t\t\t\tindexedDBSize = ((estimate as any).usageDetails as StorageQuotaUsageDetails)\n\t\t\t\t\t\t.indexedDB;\n\t\t\t\t}\n\n\t\t\t\tthis.logger.sendTelemetryEvent({\n\t\t\t\t\teventName: FluidCacheGenericEvent.FluidCacheStorageInfo,\n\t\t\t\t\tsubCategory: FluidCacheEventSubCategories.FluidCache,\n\t\t\t\t\tquota: estimate.quota,\n\t\t\t\t\tusage: estimate.usage,\n\t\t\t\t\tindexedDBSize,\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\tscheduleIdleTask(async () => {\n\t\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\n\t\t\t// Delete entries that have not been accessed recently to clean up space\n\t\t\ttry {\n\t\t\t\tdb = await getFluidCacheIndexedDbInstance(this.logger);\n\n\t\t\t\tconst transaction = db.transaction(FluidDriverObjectStoreName, \"readwrite\");\n\t\t\t\tconst index = transaction.store.index(\"createdTimeMs\");\n\t\t\t\t// Get items which were cached before the maxCacheItemAge.\n\t\t\t\tconst keysToDelete = await index.getAllKeys(\n\t\t\t\t\tIDBKeyRange.upperBound(new Date().getTime() - this.maxCacheItemAge),\n\t\t\t\t);\n\n\t\t\t\tawait Promise.all(keysToDelete.map((key) => transaction.store.delete(key)));\n\t\t\t\tawait transaction.done;\n\t\t\t} catch (error: any) {\n\t\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: FluidCacheErrorEvent.FluidCacheDeleteOldEntriesError,\n\t\t\t\t\t},\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t} finally {\n\t\t\t\tdb?.close();\n\t\t\t}\n\t\t});\n\t}\n\n\tpublic async removeEntries(file: IFileEntry): Promise<void> {\n\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\t\ttry {\n\t\t\tdb = await getFluidCacheIndexedDbInstance(this.logger);\n\n\t\t\tconst transaction = db.transaction(FluidDriverObjectStoreName, \"readwrite\");\n\t\t\tconst index = transaction.store.index(\"fileId\");\n\n\t\t\tconst keysToDelete = await index.getAllKeys(file.docId);\n\n\t\t\tawait Promise.all(keysToDelete.map((key) => transaction.store.delete(key)));\n\t\t\tawait transaction.done;\n\t\t} catch (error: any) {\n\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t{\n\t\t\t\t\teventName: FluidCacheErrorEvent.FluidCacheDeleteOldEntriesError,\n\t\t\t\t},\n\t\t\t\terror,\n\t\t\t);\n\t\t} finally {\n\t\t\tdb?.close();\n\t\t}\n\t}\n\n\tpublic async get(cacheEntry: ICacheEntry): Promise<any> {\n\t\tconst startTime = performance.now();\n\n\t\tconst cachedItem = await this.getItemFromCache(cacheEntry);\n\n\t\tthis.logger.sendPerformanceEvent({\n\t\t\teventName: \"FluidCacheAccess\",\n\t\t\tcacheHit: cachedItem !== undefined,\n\t\t\ttype: cacheEntry.type,\n\t\t\tduration: performance.now() - startTime,\n\t\t\tdbOpenPerf: cachedItem?.dbOpenPerf,\n\t\t\tdbClosePerf: cachedItem?.dbClosePerf,\n\t\t});\n\n\t\t// Value will contain metadata like the expiry time, we just want to return the object we were asked to cache\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-return\n\t\treturn cachedItem?.cachedObject;\n\t}\n\n\tprivate async getItemFromCache(cacheEntry: ICacheEntry) {\n\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\t\ttry {\n\t\t\tconst key = getKeyForCacheEntry(cacheEntry);\n\n\t\t\tconst dbOpenStartTime = performance.now();\n\t\t\tdb = await getFluidCacheIndexedDbInstance(this.logger);\n\t\t\tconst dbOpenPerf = performance.now() - dbOpenStartTime;\n\t\t\tconst value = await db.get(FluidDriverObjectStoreName, key);\n\n\t\t\tif (!value) {\n\t\t\t\tdb.close();\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\t// If the data does not come from the same partition, don't return it\n\t\t\tif (value.partitionKey !== this.partitionKey) {\n\t\t\t\tthis.logger.sendTelemetryEvent({\n\t\t\t\t\teventName: FluidCacheGenericEvent.FluidCachePartitionKeyMismatch,\n\t\t\t\t\tsubCategory: FluidCacheEventSubCategories.FluidCache,\n\t\t\t\t});\n\n\t\t\t\tdb.close();\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\tconst currentTime = new Date().getTime();\n\n\t\t\t// If too much time has passed since this cache entry was used, we will also return undefined\n\t\t\tif (currentTime - value.createdTimeMs > this.maxCacheItemAge) {\n\t\t\t\tdb.close();\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\tconst dbCloseStartTime = performance.now();\n\t\t\tdb.close();\n\t\t\tconst dbClosePerf = performance.now() - dbCloseStartTime;\n\t\t\treturn { ...value, dbOpenPerf, dbClosePerf };\n\t\t} catch (error: any) {\n\t\t\t// We can fail to open the db for a variety of reasons,\n\t\t\t// such as the database version having upgraded underneath us. Return undefined in this case\n\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t{ eventName: FluidCacheErrorEvent.FluidCacheGetError },\n\t\t\t\terror,\n\t\t\t);\n\t\t\tdb?.close();\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tpublic async put(entry: ICacheEntry, value: any): Promise<void> {\n\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\t\ttry {\n\t\t\tdb = await getFluidCacheIndexedDbInstance(this.logger);\n\n\t\t\tconst currentTime = new Date().getTime();\n\n\t\t\tawait db.put(\n\t\t\t\tFluidDriverObjectStoreName,\n\t\t\t\t{\n\t\t\t\t\tcachedObject: value,\n\t\t\t\t\tfileId: entry.file.docId,\n\t\t\t\t\ttype: entry.type,\n\t\t\t\t\tcacheItemId: entry.key,\n\t\t\t\t\tpartitionKey: this.partitionKey,\n\t\t\t\t\tcreatedTimeMs: currentTime,\n\t\t\t\t\tlastAccessTimeMs: currentTime,\n\t\t\t\t},\n\t\t\t\tgetKeyForCacheEntry(entry),\n\t\t\t);\n\n\t\t\tdb.close();\n\t\t} catch (error: any) {\n\t\t\t// We can fail to open the db for a variety of reasons,\n\t\t\t// such as the database version having upgraded underneath us\n\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t{ eventName: FluidCacheErrorEvent.FluidCachePutError },\n\t\t\t\terror,\n\t\t\t);\n\t\t} finally {\n\t\t\tdb?.close();\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"FluidCache.js","sourceRoot":"","sources":["../src/FluidCache.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { IDBPDatabase } from \"idb\";\nimport { assert } from \"@fluidframework/common-utils\";\nimport { IPersistedCache, ICacheEntry, IFileEntry } from \"@fluidframework/odsp-driver-definitions\";\nimport { ITelemetryBaseLogger, ITelemetryLogger } from \"@fluidframework/common-definitions\";\nimport { ChildLogger } from \"@fluidframework/telemetry-utils\";\nimport { scheduleIdleTask } from \"./scheduleIdleTask\";\nimport {\n\tgetFluidCacheIndexedDbInstance,\n\tFluidCacheDBSchema,\n\tFluidDriverObjectStoreName,\n\tgetKeyForCacheEntry,\n} from \"./FluidCacheIndexedDb\";\nimport {\n\tFluidCacheErrorEvent,\n\tFluidCacheEventSubCategories,\n\tFluidCacheGenericEvent,\n} from \"./fluidCacheTelemetry\";\nimport { pkgVersion } from \"./packageVersion\";\n\n// Some browsers have a usageDetails property that will tell you more detailed information\n// on how the storage is being used\ninterface StorageQuotaUsageDetails {\n\tindexedDB: number | undefined;\n}\n\nexport interface FluidCacheConfig {\n\t/**\n\t * A string to specify what partition of the cache you wish to use (e.g. a user id).\n\t * Null can be used to explicity indicate no partitioning, and has been chosen\n\t * vs undefined so that it is clear this is an intentional choice by the caller.\n\t * A null value should only be used when the host can ensure that the cache is not able\n\t * to be shared with multiple users.\n\t */\n\t// eslint-disable-next-line @rushstack/no-new-null\n\tpartitionKey: string | null;\n\n\t/**\n\t * A logger that can be used to get insight into cache performance and errors\n\t */\n\tlogger?: ITelemetryBaseLogger;\n\n\t/**\n\t * A value in milliseconds that determines the maximum age of a cache entry to return.\n\t * If an entry exists in the cache, but is older than this value, the cached value will not be returned.\n\t */\n\tmaxCacheItemAge: number;\n\n\t/**\n\t * Each time db is opened, it will remain open for this much time. To improve perf, if this property is set as\n\t * any number greater than 0, then db will not be closed immediately after usage. This value is in milliseconds.\n\t */\n\tcloseDbAfterMs?: number;\n}\n\n/**\n * A cache that can be used by the Fluid ODSP driver to cache data for faster performance\n */\nexport class FluidCache implements IPersistedCache {\n\tprivate readonly logger: ITelemetryLogger;\n\n\tprivate readonly partitionKey: string | null;\n\n\tprivate readonly maxCacheItemAge: number;\n\tprivate readonly closeDbImmediately: boolean = true;\n\tprivate readonly closeDbAfterMs: number;\n\tprivate db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\tprivate dbCloseTimer: ReturnType<typeof setTimeout> | undefined;\n\tprivate dbReuseCount: number = -1;\n\n\tconstructor(config: FluidCacheConfig) {\n\t\tthis.logger = ChildLogger.create(config.logger);\n\t\tthis.partitionKey = config.partitionKey;\n\t\tthis.maxCacheItemAge = config.maxCacheItemAge;\n\t\tthis.closeDbAfterMs = config.closeDbAfterMs ?? 0;\n\t\tif (this.closeDbAfterMs > 0) {\n\t\t\tthis.closeDbImmediately = false;\n\t\t}\n\n\t\tscheduleIdleTask(async () => {\n\t\t\t// Log how much storage space is currently being used by indexed db.\n\t\t\t// NOTE: This API is not supported in all browsers and it doesn't let you see the size of a specific DB.\n\t\t\t// Exception added when eslint rule was added, this should be revisited when modifying this code\n\t\t\tif (navigator.storage?.estimate) {\n\t\t\t\tconst estimate = await navigator.storage.estimate();\n\n\t\t\t\t// Some browsers have a usageDetails property that will tell you\n\t\t\t\t// more detailed information on how the storage is being used\n\t\t\t\tlet indexedDBSize: number | undefined;\n\t\t\t\tif (\"usageDetails\" in estimate) {\n\t\t\t\t\tindexedDBSize = ((estimate as any).usageDetails as StorageQuotaUsageDetails)\n\t\t\t\t\t\t.indexedDB;\n\t\t\t\t}\n\n\t\t\t\tthis.logger.sendTelemetryEvent({\n\t\t\t\t\teventName: FluidCacheGenericEvent.FluidCacheStorageInfo,\n\t\t\t\t\tsubCategory: FluidCacheEventSubCategories.FluidCache,\n\t\t\t\t\tquota: estimate.quota,\n\t\t\t\t\tusage: estimate.usage,\n\t\t\t\t\tindexedDBSize,\n\t\t\t\t\tpkgVersion,\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\tscheduleIdleTask(async () => {\n\t\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\n\t\t\t// Delete entries that have not been accessed recently to clean up space\n\t\t\ttry {\n\t\t\t\tdb = await getFluidCacheIndexedDbInstance(this.logger);\n\n\t\t\t\tconst transaction = db.transaction(FluidDriverObjectStoreName, \"readwrite\");\n\t\t\t\tconst index = transaction.store.index(\"createdTimeMs\");\n\t\t\t\t// Get items which were cached before the maxCacheItemAge.\n\t\t\t\tconst keysToDelete = await index.getAllKeys(\n\t\t\t\t\tIDBKeyRange.upperBound(new Date().getTime() - this.maxCacheItemAge),\n\t\t\t\t);\n\n\t\t\t\tawait Promise.all(keysToDelete.map((key) => transaction.store.delete(key)));\n\t\t\t\tawait transaction.done;\n\t\t\t} catch (error: any) {\n\t\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: FluidCacheErrorEvent.FluidCacheDeleteOldEntriesError,\n\t\t\t\t\t\tpkgVersion,\n\t\t\t\t\t},\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t} finally {\n\t\t\t\tdb?.close();\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate async openDb() {\n\t\tif (this.closeDbImmediately) {\n\t\t\treturn getFluidCacheIndexedDbInstance(this.logger);\n\t\t}\n\t\tif (this.db === undefined) {\n\t\t\tconst dbInstance = await getFluidCacheIndexedDbInstance(this.logger);\n\t\t\tif (this.db === undefined) {\n\t\t\t\t// Reset the counter on first open.\n\t\t\t\tthis.dbReuseCount = -1;\n\t\t\t\tthis.db = dbInstance;\n\t\t\t} else {\n\t\t\t\tdbInstance.close();\n\t\t\t\tthis.dbReuseCount += 1;\n\t\t\t\treturn this.db;\n\t\t\t}\n\t\t\t// Need to close the db on version change if opened.\n\t\t\tthis.db.onversionchange = (ev) => {\n\t\t\t\tthis.db?.close();\n\t\t\t\tthis.db = undefined;\n\t\t\t\tclearTimeout(this.dbCloseTimer);\n\t\t\t\tthis.dbCloseTimer = undefined;\n\t\t\t};\n\t\t\tthis.db.addEventListener(\"close\", (ev) => {\n\t\t\t\tclearTimeout(this.dbCloseTimer);\n\t\t\t\tthis.dbCloseTimer = undefined;\n\t\t\t\tthis.db = undefined;\n\t\t\t});\n\t\t\t// Schedule db close after this.closeDbAfterMs.\n\t\t\tassert(this.dbCloseTimer === undefined, 0x6c6 /* timer should not be set yet!! */);\n\t\t\tthis.dbCloseTimer = setTimeout(() => {\n\t\t\t\tthis.db?.close();\n\t\t\t\tthis.db = undefined;\n\t\t\t\tthis.dbCloseTimer = undefined;\n\t\t\t}, this.closeDbAfterMs);\n\t\t}\n\t\tassert(this.db !== undefined, 0x6c7 /* db should be intialized by now */);\n\t\tthis.dbReuseCount += 1;\n\t\treturn this.db;\n\t}\n\n\tprivate closeDb(db?: IDBPDatabase<FluidCacheDBSchema>) {\n\t\tif (this.closeDbImmediately) {\n\t\t\tdb?.close();\n\t\t}\n\t}\n\n\tpublic async removeEntries(file: IFileEntry): Promise<void> {\n\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\t\ttry {\n\t\t\tdb = await this.openDb();\n\n\t\t\tconst transaction = db.transaction(FluidDriverObjectStoreName, \"readwrite\");\n\t\t\tconst index = transaction.store.index(\"fileId\");\n\n\t\t\tconst keysToDelete = await index.getAllKeys(file.docId);\n\n\t\t\tawait Promise.all(keysToDelete.map((key) => transaction.store.delete(key)));\n\t\t\tawait transaction.done;\n\t\t} catch (error: any) {\n\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t{\n\t\t\t\t\teventName: FluidCacheErrorEvent.FluidCacheDeleteOldEntriesError,\n\t\t\t\t\tpkgVersion,\n\t\t\t\t},\n\t\t\t\terror,\n\t\t\t);\n\t\t} finally {\n\t\t\tthis.closeDb(db);\n\t\t}\n\t}\n\n\tpublic async get(cacheEntry: ICacheEntry): Promise<any> {\n\t\tconst startTime = performance.now();\n\n\t\tconst cachedItem = await this.getItemFromCache(cacheEntry);\n\n\t\tthis.logger.sendPerformanceEvent({\n\t\t\teventName: \"FluidCacheAccess\",\n\t\t\tcacheHit: cachedItem !== undefined,\n\t\t\ttype: cacheEntry.type,\n\t\t\tduration: performance.now() - startTime,\n\t\t\tdbOpenPerf: cachedItem?.dbOpenPerf,\n\t\t\tdbReuseCount: this.dbReuseCount,\n\t\t\tpkgVersion,\n\t\t});\n\n\t\t// Value will contain metadata like the expiry time, we just want to return the object we were asked to cache\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-return\n\t\treturn cachedItem?.cachedObject;\n\t}\n\n\tprivate async getItemFromCache(cacheEntry: ICacheEntry) {\n\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\t\ttry {\n\t\t\tconst key = getKeyForCacheEntry(cacheEntry);\n\n\t\t\tconst dbOpenStartTime = performance.now();\n\t\t\tdb = await this.openDb();\n\t\t\tconst dbOpenPerf = performance.now() - dbOpenStartTime;\n\t\t\tconst value = await db.get(FluidDriverObjectStoreName, key);\n\n\t\t\tif (!value) {\n\t\t\t\tthis.closeDb(db);\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\t// If the data does not come from the same partition, don't return it\n\t\t\tif (value.partitionKey !== this.partitionKey) {\n\t\t\t\tthis.logger.sendTelemetryEvent({\n\t\t\t\t\teventName: FluidCacheGenericEvent.FluidCachePartitionKeyMismatch,\n\t\t\t\t\tsubCategory: FluidCacheEventSubCategories.FluidCache,\n\t\t\t\t\tpkgVersion,\n\t\t\t\t});\n\n\t\t\t\tthis.closeDb(db);\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\tconst currentTime = new Date().getTime();\n\n\t\t\t// If too much time has passed since this cache entry was used, we will also return undefined\n\t\t\tif (currentTime - value.createdTimeMs > this.maxCacheItemAge) {\n\t\t\t\tthis.closeDb(db);\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\tthis.closeDb(db);\n\t\t\treturn { ...value, dbOpenPerf };\n\t\t} catch (error: any) {\n\t\t\t// We can fail to open the db for a variety of reasons,\n\t\t\t// such as the database version having upgraded underneath us. Return undefined in this case\n\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t{ eventName: FluidCacheErrorEvent.FluidCacheGetError, pkgVersion },\n\t\t\t\terror,\n\t\t\t);\n\t\t\tthis.closeDb(db);\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tpublic async put(entry: ICacheEntry, value: any): Promise<void> {\n\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\t\ttry {\n\t\t\tdb = await this.openDb();\n\n\t\t\tconst currentTime = new Date().getTime();\n\n\t\t\tawait db.put(\n\t\t\t\tFluidDriverObjectStoreName,\n\t\t\t\t{\n\t\t\t\t\tcachedObject: value,\n\t\t\t\t\tfileId: entry.file.docId,\n\t\t\t\t\ttype: entry.type,\n\t\t\t\t\tcacheItemId: entry.key,\n\t\t\t\t\tpartitionKey: this.partitionKey,\n\t\t\t\t\tcreatedTimeMs: currentTime,\n\t\t\t\t\tlastAccessTimeMs: currentTime,\n\t\t\t\t},\n\t\t\t\tgetKeyForCacheEntry(entry),\n\t\t\t);\n\t\t\tthis.closeDb(db);\n\t\t} catch (error: any) {\n\t\t\t// We can fail to open the db for a variety of reasons,\n\t\t\t// such as the database version having upgraded underneath us\n\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t{ eventName: FluidCacheErrorEvent.FluidCachePutError, pkgVersion },\n\t\t\t\terror,\n\t\t\t);\n\t\t} finally {\n\t\t\tthis.closeDb(db);\n\t\t}\n\t}\n}\n"]}
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export declare const pkgName = "@fluidframework/driver-web-cache";
8
- export declare const pkgVersion = "2.0.0-internal.4.2.1";
8
+ export declare const pkgVersion = "2.0.0-internal.4.4.0";
9
9
  //# sourceMappingURL=packageVersion.d.ts.map
@@ -8,5 +8,5 @@
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.pkgVersion = exports.pkgName = void 0;
10
10
  exports.pkgName = "@fluidframework/driver-web-cache";
11
- exports.pkgVersion = "2.0.0-internal.4.2.1";
11
+ exports.pkgVersion = "2.0.0-internal.4.4.0";
12
12
  //# sourceMappingURL=packageVersion.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEU,QAAA,OAAO,GAAG,kCAAkC,CAAC;AAC7C,QAAA,UAAU,GAAG,sBAAsB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/driver-web-cache\";\nexport const pkgVersion = \"2.0.0-internal.4.2.1\";\n"]}
1
+ {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEU,QAAA,OAAO,GAAG,kCAAkC,CAAC;AAC7C,QAAA,UAAU,GAAG,sBAAsB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/driver-web-cache\";\nexport const pkgVersion = \"2.0.0-internal.4.4.0\";\n"]}
@@ -18,24 +18,24 @@ function scheduleIdleTask(task) {
18
18
  taskQueue.push({
19
19
  task,
20
20
  });
21
- ensureIdleCallback();
21
+ ensureIdleCallback(2000);
22
22
  }
23
23
  exports.scheduleIdleTask = scheduleIdleTask;
24
24
  /**
25
25
  * Ensures an idle callback has been scheduled for the remaining tasks
26
26
  */
27
- function ensureIdleCallback() {
27
+ function ensureIdleCallback(timeout = 0) {
28
28
  if (!idleTaskScheduled) {
29
29
  // Exception added when eslint rule was added, this should be revisited when modifying this code
30
- if (window.requestIdleCallback) {
31
- window.requestIdleCallback(idleTaskCallback);
30
+ if (self.requestIdleCallback) {
31
+ self.requestIdleCallback(idleTaskCallback);
32
32
  }
33
33
  else {
34
34
  const deadline = Date.now() + 50;
35
- window.setTimeout(() => idleTaskCallback({
35
+ self.setTimeout(() => idleTaskCallback({
36
36
  timeRemaining: () => Math.max(deadline - Date.now(), 0),
37
37
  didTimeout: false,
38
- }), 0);
38
+ }), timeout);
39
39
  }
40
40
  idleTaskScheduled = true;
41
41
  }
@@ -1 +1 @@
1
- {"version":3,"file":"scheduleIdleTask.js","sourceRoot":"","sources":["../src/scheduleIdleTask.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAOH,2CAA2C;AAC3C,IAAI,SAAS,GAAoB,EAAE,CAAC;AAEpC,yDAAyD;AACzD,IAAI,iBAAiB,GAAG,KAAK,CAAC;AAE9B;;;;GAIG;AACH,SAAgB,gBAAgB,CAAC,IAAgB;IAChD,SAAS,CAAC,IAAI,CAAC;QACd,IAAI;KACJ,CAAC,CAAC;IAEH,kBAAkB,EAAE,CAAC;AACtB,CAAC;AAND,4CAMC;AAED;;GAEG;AACH,SAAS,kBAAkB;IAC1B,IAAI,CAAC,iBAAiB,EAAE;QACvB,gGAAgG;QAChG,IAAI,MAAM,CAAC,mBAAmB,EAAE;YAC/B,MAAM,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,CAAC;SAC7C;aAAM;YACN,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;YACjC,MAAM,CAAC,UAAU,CAChB,GAAG,EAAE,CACJ,gBAAgB,CAAC;gBAChB,aAAa,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;gBACvD,UAAU,EAAE,KAAK;aACjB,CAAC,EACH,CAAC,CACD,CAAC;SACF;QACD,iBAAiB,GAAG,IAAI,CAAC;KACzB;AACF,CAAC;AAED;;;;;;GAMG;AACH,SAAS,QAAQ,CAChB,MAAkD,EAClD,qBAAqC;IAErC,oCAAoC;IACpC,MAAM,YAAY,GAAoB,EAAE,CAAC;IAEzC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE;QACzD,IAAI,qBAAqB,IAAI,CAAC,qBAAqB,EAAE,EAAE;YACtD,kEAAkE;YAClE,YAAY,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;YAC7C,MAAM;SACN;QAED,MAAM,aAAa,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;QAEvC,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE;YACrC,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;SACjC;aAAM;YACN,aAAa,CAAC,IAAI,EAAE,CAAC;SACrB;KACD;IAED,SAAS,GAAG,YAAY,CAAC;AAC1B,CAAC;AAED,uCAAuC;AACvC,SAAS,gBAAgB,CAAC,QAAuE;IAChG,wEAAwE;IACxE,MAAM,WAAW,GAAG,EAAE,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,aAAa,EAAE,GAAG,WAAW,CAAC,CAAC;IAClE,iBAAiB,GAAG,KAAK,CAAC;IAE1B,4EAA4E;IAC5E,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE;QACzB,kBAAkB,EAAE,CAAC;KACrB;AACF,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\ninterface TaskQueueItem {\n\t/** The task to run */\n\ttask: () => void;\n}\n\n// A set of tasks that still have to be run\nlet taskQueue: TaskQueueItem[] = [];\n\n// Set to true when we have a pending idle task scheduled\nlet idleTaskScheduled = false;\n\n/**\n * A function that schedules a non critical task to be run when the browser has cycles available\n * @param task - The task to be executed\n * @param options - Optional configuration for the task execution\n */\nexport function scheduleIdleTask(task: () => void) {\n\ttaskQueue.push({\n\t\ttask,\n\t});\n\n\tensureIdleCallback();\n}\n\n/**\n * Ensures an idle callback has been scheduled for the remaining tasks\n */\nfunction ensureIdleCallback() {\n\tif (!idleTaskScheduled) {\n\t\t// Exception added when eslint rule was added, this should be revisited when modifying this code\n\t\tif (window.requestIdleCallback) {\n\t\t\twindow.requestIdleCallback(idleTaskCallback);\n\t\t} else {\n\t\t\tconst deadline = Date.now() + 50;\n\t\t\twindow.setTimeout(\n\t\t\t\t() =>\n\t\t\t\t\tidleTaskCallback({\n\t\t\t\t\t\ttimeRemaining: () => Math.max(deadline - Date.now(), 0),\n\t\t\t\t\t\tdidTimeout: false,\n\t\t\t\t\t}),\n\t\t\t\t0,\n\t\t\t);\n\t\t}\n\t\tidleTaskScheduled = true;\n\t}\n}\n\n/**\n * Runs tasks from the task queue\n * @param filter - An optional function that will be called for each task to see if it should run.\n * Returns false for tasks that should not run. If omitted all tasks run.\n * @param shouldContinueRunning - An optional function that will be called to determine if\n * we have enough time to continue running tasks. If omitted, we don't stop running tasks.\n */\nfunction runTasks(\n\tfilter?: (taskQueueItem: TaskQueueItem) => boolean,\n\tshouldContinueRunning?: () => boolean,\n) {\n\t// The next value for the task queue\n\tconst newTaskQueue: TaskQueueItem[] = [];\n\n\tfor (let index = 0; index < taskQueue.length; index += 1) {\n\t\tif (shouldContinueRunning && !shouldContinueRunning()) {\n\t\t\t// Add the tasks we didn't get to to the end of the new task queue\n\t\t\tnewTaskQueue.push(...taskQueue.slice(index));\n\t\t\tbreak;\n\t\t}\n\n\t\tconst taskQueueItem = taskQueue[index];\n\n\t\tif (filter && !filter(taskQueueItem)) {\n\t\t\tnewTaskQueue.push(taskQueueItem);\n\t\t} else {\n\t\t\ttaskQueueItem.task();\n\t\t}\n\t}\n\n\ttaskQueue = newTaskQueue;\n}\n\n// Runs all the tasks in the task queue\nfunction idleTaskCallback(deadline: { timeRemaining: () => number; readonly didTimeout: boolean }) {\n\t// Minimum time that must be available on deadline to run any more tasks\n\tconst minTaskTime = 10;\n\trunTasks(undefined, () => deadline.timeRemaining() > minTaskTime);\n\tidleTaskScheduled = false;\n\n\t// If we didn't run through the entire queue, schedule another idle callback\n\tif (taskQueue.length > 0) {\n\t\tensureIdleCallback();\n\t}\n}\n"]}
1
+ {"version":3,"file":"scheduleIdleTask.js","sourceRoot":"","sources":["../src/scheduleIdleTask.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAOH,2CAA2C;AAC3C,IAAI,SAAS,GAAoB,EAAE,CAAC;AAEpC,yDAAyD;AACzD,IAAI,iBAAiB,GAAG,KAAK,CAAC;AAE9B;;;;GAIG;AACH,SAAgB,gBAAgB,CAAC,IAAgB;IAChD,SAAS,CAAC,IAAI,CAAC;QACd,IAAI;KACJ,CAAC,CAAC;IAEH,kBAAkB,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAND,4CAMC;AAED;;GAEG;AACH,SAAS,kBAAkB,CAAC,UAAkB,CAAC;IAC9C,IAAI,CAAC,iBAAiB,EAAE;QACvB,gGAAgG;QAChG,IAAI,IAAI,CAAC,mBAAmB,EAAE;YAC7B,IAAI,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,CAAC;SAC3C;aAAM;YACN,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;YACjC,IAAI,CAAC,UAAU,CACd,GAAG,EAAE,CACJ,gBAAgB,CAAC;gBAChB,aAAa,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;gBACvD,UAAU,EAAE,KAAK;aACjB,CAAC,EACH,OAAO,CACP,CAAC;SACF;QACD,iBAAiB,GAAG,IAAI,CAAC;KACzB;AACF,CAAC;AAED;;;;;;GAMG;AACH,SAAS,QAAQ,CAChB,MAAkD,EAClD,qBAAqC;IAErC,oCAAoC;IACpC,MAAM,YAAY,GAAoB,EAAE,CAAC;IAEzC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE;QACzD,IAAI,qBAAqB,IAAI,CAAC,qBAAqB,EAAE,EAAE;YACtD,kEAAkE;YAClE,YAAY,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;YAC7C,MAAM;SACN;QAED,MAAM,aAAa,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;QAEvC,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE;YACrC,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;SACjC;aAAM;YACN,aAAa,CAAC,IAAI,EAAE,CAAC;SACrB;KACD;IAED,SAAS,GAAG,YAAY,CAAC;AAC1B,CAAC;AAED,uCAAuC;AACvC,SAAS,gBAAgB,CAAC,QAAuE;IAChG,wEAAwE;IACxE,MAAM,WAAW,GAAG,EAAE,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,aAAa,EAAE,GAAG,WAAW,CAAC,CAAC;IAClE,iBAAiB,GAAG,KAAK,CAAC;IAE1B,4EAA4E;IAC5E,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE;QACzB,kBAAkB,EAAE,CAAC;KACrB;AACF,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\ninterface TaskQueueItem {\n\t/** The task to run */\n\ttask: () => void;\n}\n\n// A set of tasks that still have to be run\nlet taskQueue: TaskQueueItem[] = [];\n\n// Set to true when we have a pending idle task scheduled\nlet idleTaskScheduled = false;\n\n/**\n * A function that schedules a non critical task to be run when the browser has cycles available\n * @param task - The task to be executed\n * @param options - Optional configuration for the task execution\n */\nexport function scheduleIdleTask(task: () => void) {\n\ttaskQueue.push({\n\t\ttask,\n\t});\n\n\tensureIdleCallback(2000);\n}\n\n/**\n * Ensures an idle callback has been scheduled for the remaining tasks\n */\nfunction ensureIdleCallback(timeout: number = 0) {\n\tif (!idleTaskScheduled) {\n\t\t// Exception added when eslint rule was added, this should be revisited when modifying this code\n\t\tif (self.requestIdleCallback) {\n\t\t\tself.requestIdleCallback(idleTaskCallback);\n\t\t} else {\n\t\t\tconst deadline = Date.now() + 50;\n\t\t\tself.setTimeout(\n\t\t\t\t() =>\n\t\t\t\t\tidleTaskCallback({\n\t\t\t\t\t\ttimeRemaining: () => Math.max(deadline - Date.now(), 0),\n\t\t\t\t\t\tdidTimeout: false,\n\t\t\t\t\t}),\n\t\t\t\ttimeout,\n\t\t\t);\n\t\t}\n\t\tidleTaskScheduled = true;\n\t}\n}\n\n/**\n * Runs tasks from the task queue\n * @param filter - An optional function that will be called for each task to see if it should run.\n * Returns false for tasks that should not run. If omitted all tasks run.\n * @param shouldContinueRunning - An optional function that will be called to determine if\n * we have enough time to continue running tasks. If omitted, we don't stop running tasks.\n */\nfunction runTasks(\n\tfilter?: (taskQueueItem: TaskQueueItem) => boolean,\n\tshouldContinueRunning?: () => boolean,\n) {\n\t// The next value for the task queue\n\tconst newTaskQueue: TaskQueueItem[] = [];\n\n\tfor (let index = 0; index < taskQueue.length; index += 1) {\n\t\tif (shouldContinueRunning && !shouldContinueRunning()) {\n\t\t\t// Add the tasks we didn't get to to the end of the new task queue\n\t\t\tnewTaskQueue.push(...taskQueue.slice(index));\n\t\t\tbreak;\n\t\t}\n\n\t\tconst taskQueueItem = taskQueue[index];\n\n\t\tif (filter && !filter(taskQueueItem)) {\n\t\t\tnewTaskQueue.push(taskQueueItem);\n\t\t} else {\n\t\t\ttaskQueueItem.task();\n\t\t}\n\t}\n\n\ttaskQueue = newTaskQueue;\n}\n\n// Runs all the tasks in the task queue\nfunction idleTaskCallback(deadline: { timeRemaining: () => number; readonly didTimeout: boolean }) {\n\t// Minimum time that must be available on deadline to run any more tasks\n\tconst minTaskTime = 10;\n\trunTasks(undefined, () => deadline.timeRemaining() > minTaskTime);\n\tidleTaskScheduled = false;\n\n\t// If we didn't run through the entire queue, schedule another idle callback\n\tif (taskQueue.length > 0) {\n\t\tensureIdleCallback();\n\t}\n}\n"]}
@@ -1,3 +1,7 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
1
5
  import { IPersistedCache, ICacheEntry, IFileEntry } from "@fluidframework/odsp-driver-definitions";
2
6
  import { ITelemetryBaseLogger } from "@fluidframework/common-definitions";
3
7
  export interface FluidCacheConfig {
@@ -18,6 +22,11 @@ export interface FluidCacheConfig {
18
22
  * If an entry exists in the cache, but is older than this value, the cached value will not be returned.
19
23
  */
20
24
  maxCacheItemAge: number;
25
+ /**
26
+ * Each time db is opened, it will remain open for this much time. To improve perf, if this property is set as
27
+ * any number greater than 0, then db will not be closed immediately after usage. This value is in milliseconds.
28
+ */
29
+ closeDbAfterMs?: number;
21
30
  }
22
31
  /**
23
32
  * A cache that can be used by the Fluid ODSP driver to cache data for faster performance
@@ -26,7 +35,14 @@ export declare class FluidCache implements IPersistedCache {
26
35
  private readonly logger;
27
36
  private readonly partitionKey;
28
37
  private readonly maxCacheItemAge;
38
+ private readonly closeDbImmediately;
39
+ private readonly closeDbAfterMs;
40
+ private db;
41
+ private dbCloseTimer;
42
+ private dbReuseCount;
29
43
  constructor(config: FluidCacheConfig);
44
+ private openDb;
45
+ private closeDb;
30
46
  removeEntries(file: IFileEntry): Promise<void>;
31
47
  get(cacheEntry: ICacheEntry): Promise<any>;
32
48
  private getItemFromCache;
@@ -1 +1 @@
1
- {"version":3,"file":"FluidCache.d.ts","sourceRoot":"","sources":["../src/FluidCache.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,yCAAyC,CAAC;AACnG,OAAO,EAAE,oBAAoB,EAAoB,MAAM,oCAAoC,CAAC;AAqB5F,MAAM,WAAW,gBAAgB;IAChC;;;;;;OAMG;IAEH,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAE5B;;OAEG;IACH,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAE9B;;;OAGG;IACH,eAAe,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,qBAAa,UAAW,YAAW,eAAe;IACjD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAE1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAgB;IAE7C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;gBAE7B,MAAM,EAAE,gBAAgB;IA2DvB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAwB9C,GAAG,CAAC,UAAU,EAAE,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC;YAmBzC,gBAAgB;IAkDjB,GAAG,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;CAiC/D"}
1
+ {"version":3,"file":"FluidCache.d.ts","sourceRoot":"","sources":["../src/FluidCache.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,yCAAyC,CAAC;AACnG,OAAO,EAAE,oBAAoB,EAAoB,MAAM,oCAAoC,CAAC;AAsB5F,MAAM,WAAW,gBAAgB;IAChC;;;;;;OAMG;IAEH,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAE5B;;OAEG;IACH,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAE9B;;;OAGG;IACH,eAAe,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,qBAAa,UAAW,YAAW,eAAe;IACjD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAE1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAgB;IAE7C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAiB;IACpD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,EAAE,CAA+C;IACzD,OAAO,CAAC,YAAY,CAA4C;IAChE,OAAO,CAAC,YAAY,CAAc;gBAEtB,MAAM,EAAE,gBAAgB;YAiEtB,MAAM;IAwCpB,OAAO,CAAC,OAAO;IAMF,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAyB9C,GAAG,CAAC,UAAU,EAAE,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC;YAoBzC,gBAAgB;IAiDjB,GAAG,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;CAgC/D"}
package/lib/FluidCache.js CHANGED
@@ -1,14 +1,27 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { assert } from "@fluidframework/common-utils";
1
6
  import { ChildLogger } from "@fluidframework/telemetry-utils";
2
7
  import { scheduleIdleTask } from "./scheduleIdleTask";
3
8
  import { getFluidCacheIndexedDbInstance, FluidDriverObjectStoreName, getKeyForCacheEntry, } from "./FluidCacheIndexedDb";
9
+ import { pkgVersion } from "./packageVersion";
4
10
  /**
5
11
  * A cache that can be used by the Fluid ODSP driver to cache data for faster performance
6
12
  */
7
13
  export class FluidCache {
8
14
  constructor(config) {
15
+ var _a;
16
+ this.closeDbImmediately = true;
17
+ this.dbReuseCount = -1;
9
18
  this.logger = ChildLogger.create(config.logger);
10
19
  this.partitionKey = config.partitionKey;
11
20
  this.maxCacheItemAge = config.maxCacheItemAge;
21
+ this.closeDbAfterMs = (_a = config.closeDbAfterMs) !== null && _a !== void 0 ? _a : 0;
22
+ if (this.closeDbAfterMs > 0) {
23
+ this.closeDbImmediately = false;
24
+ }
12
25
  scheduleIdleTask(async () => {
13
26
  var _a;
14
27
  // Log how much storage space is currently being used by indexed db.
@@ -29,6 +42,7 @@ export class FluidCache {
29
42
  quota: estimate.quota,
30
43
  usage: estimate.usage,
31
44
  indexedDBSize,
45
+ pkgVersion,
32
46
  });
33
47
  }
34
48
  });
@@ -47,6 +61,7 @@ export class FluidCache {
47
61
  catch (error) {
48
62
  this.logger.sendErrorEvent({
49
63
  eventName: "FluidCacheDeleteOldEntriesError" /* FluidCacheDeleteOldEntriesError */,
64
+ pkgVersion,
50
65
  }, error);
51
66
  }
52
67
  finally {
@@ -54,10 +69,57 @@ export class FluidCache {
54
69
  }
55
70
  });
56
71
  }
72
+ async openDb() {
73
+ if (this.closeDbImmediately) {
74
+ return getFluidCacheIndexedDbInstance(this.logger);
75
+ }
76
+ if (this.db === undefined) {
77
+ const dbInstance = await getFluidCacheIndexedDbInstance(this.logger);
78
+ if (this.db === undefined) {
79
+ // Reset the counter on first open.
80
+ this.dbReuseCount = -1;
81
+ this.db = dbInstance;
82
+ }
83
+ else {
84
+ dbInstance.close();
85
+ this.dbReuseCount += 1;
86
+ return this.db;
87
+ }
88
+ // Need to close the db on version change if opened.
89
+ this.db.onversionchange = (ev) => {
90
+ var _a;
91
+ (_a = this.db) === null || _a === void 0 ? void 0 : _a.close();
92
+ this.db = undefined;
93
+ clearTimeout(this.dbCloseTimer);
94
+ this.dbCloseTimer = undefined;
95
+ };
96
+ this.db.addEventListener("close", (ev) => {
97
+ clearTimeout(this.dbCloseTimer);
98
+ this.dbCloseTimer = undefined;
99
+ this.db = undefined;
100
+ });
101
+ // Schedule db close after this.closeDbAfterMs.
102
+ assert(this.dbCloseTimer === undefined, 0x6c6 /* timer should not be set yet!! */);
103
+ this.dbCloseTimer = setTimeout(() => {
104
+ var _a;
105
+ (_a = this.db) === null || _a === void 0 ? void 0 : _a.close();
106
+ this.db = undefined;
107
+ this.dbCloseTimer = undefined;
108
+ }, this.closeDbAfterMs);
109
+ }
110
+ assert(this.db !== undefined, 0x6c7 /* db should be intialized by now */);
111
+ this.dbReuseCount += 1;
112
+ return this.db;
113
+ }
114
+ closeDb(db) {
115
+ if (this.closeDbImmediately) {
116
+ db === null || db === void 0 ? void 0 : db.close();
117
+ }
118
+ }
57
119
  async removeEntries(file) {
58
120
  let db;
59
121
  try {
60
- db = await getFluidCacheIndexedDbInstance(this.logger);
122
+ db = await this.openDb();
61
123
  const transaction = db.transaction(FluidDriverObjectStoreName, "readwrite");
62
124
  const index = transaction.store.index("fileId");
63
125
  const keysToDelete = await index.getAllKeys(file.docId);
@@ -67,10 +129,11 @@ export class FluidCache {
67
129
  catch (error) {
68
130
  this.logger.sendErrorEvent({
69
131
  eventName: "FluidCacheDeleteOldEntriesError" /* FluidCacheDeleteOldEntriesError */,
132
+ pkgVersion,
70
133
  }, error);
71
134
  }
72
135
  finally {
73
- db === null || db === void 0 ? void 0 : db.close();
136
+ this.closeDb(db);
74
137
  }
75
138
  }
76
139
  async get(cacheEntry) {
@@ -82,7 +145,8 @@ export class FluidCache {
82
145
  type: cacheEntry.type,
83
146
  duration: performance.now() - startTime,
84
147
  dbOpenPerf: cachedItem === null || cachedItem === void 0 ? void 0 : cachedItem.dbOpenPerf,
85
- dbClosePerf: cachedItem === null || cachedItem === void 0 ? void 0 : cachedItem.dbClosePerf,
148
+ dbReuseCount: this.dbReuseCount,
149
+ pkgVersion,
86
150
  });
87
151
  // Value will contain metadata like the expiry time, we just want to return the object we were asked to cache
88
152
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
@@ -93,11 +157,11 @@ export class FluidCache {
93
157
  try {
94
158
  const key = getKeyForCacheEntry(cacheEntry);
95
159
  const dbOpenStartTime = performance.now();
96
- db = await getFluidCacheIndexedDbInstance(this.logger);
160
+ db = await this.openDb();
97
161
  const dbOpenPerf = performance.now() - dbOpenStartTime;
98
162
  const value = await db.get(FluidDriverObjectStoreName, key);
99
163
  if (!value) {
100
- db.close();
164
+ this.closeDb(db);
101
165
  return undefined;
102
166
  }
103
167
  // If the data does not come from the same partition, don't return it
@@ -105,33 +169,32 @@ export class FluidCache {
105
169
  this.logger.sendTelemetryEvent({
106
170
  eventName: "FluidCachePartitionKeyMismatch" /* FluidCachePartitionKeyMismatch */,
107
171
  subCategory: "FluidCache" /* FluidCache */,
172
+ pkgVersion,
108
173
  });
109
- db.close();
174
+ this.closeDb(db);
110
175
  return undefined;
111
176
  }
112
177
  const currentTime = new Date().getTime();
113
178
  // If too much time has passed since this cache entry was used, we will also return undefined
114
179
  if (currentTime - value.createdTimeMs > this.maxCacheItemAge) {
115
- db.close();
180
+ this.closeDb(db);
116
181
  return undefined;
117
182
  }
118
- const dbCloseStartTime = performance.now();
119
- db.close();
120
- const dbClosePerf = performance.now() - dbCloseStartTime;
121
- return Object.assign(Object.assign({}, value), { dbOpenPerf, dbClosePerf });
183
+ this.closeDb(db);
184
+ return Object.assign(Object.assign({}, value), { dbOpenPerf });
122
185
  }
123
186
  catch (error) {
124
187
  // We can fail to open the db for a variety of reasons,
125
188
  // such as the database version having upgraded underneath us. Return undefined in this case
126
- this.logger.sendErrorEvent({ eventName: "FluidCacheGetError" /* FluidCacheGetError */ }, error);
127
- db === null || db === void 0 ? void 0 : db.close();
189
+ this.logger.sendErrorEvent({ eventName: "FluidCacheGetError" /* FluidCacheGetError */, pkgVersion }, error);
190
+ this.closeDb(db);
128
191
  return undefined;
129
192
  }
130
193
  }
131
194
  async put(entry, value) {
132
195
  let db;
133
196
  try {
134
- db = await getFluidCacheIndexedDbInstance(this.logger);
197
+ db = await this.openDb();
135
198
  const currentTime = new Date().getTime();
136
199
  await db.put(FluidDriverObjectStoreName, {
137
200
  cachedObject: value,
@@ -142,15 +205,15 @@ export class FluidCache {
142
205
  createdTimeMs: currentTime,
143
206
  lastAccessTimeMs: currentTime,
144
207
  }, getKeyForCacheEntry(entry));
145
- db.close();
208
+ this.closeDb(db);
146
209
  }
147
210
  catch (error) {
148
211
  // We can fail to open the db for a variety of reasons,
149
212
  // such as the database version having upgraded underneath us
150
- this.logger.sendErrorEvent({ eventName: "FluidCachePutError" /* FluidCachePutError */ }, error);
213
+ this.logger.sendErrorEvent({ eventName: "FluidCachePutError" /* FluidCachePutError */, pkgVersion }, error);
151
214
  }
152
215
  finally {
153
- db === null || db === void 0 ? void 0 : db.close();
216
+ this.closeDb(db);
154
217
  }
155
218
  }
156
219
  }
@@ -1 +1 @@
1
- {"version":3,"file":"FluidCache.js","sourceRoot":"","sources":["../src/FluidCache.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\nimport { IDBPDatabase } from \"idb\";\nimport { IPersistedCache, ICacheEntry, IFileEntry } from \"@fluidframework/odsp-driver-definitions\";\nimport { ITelemetryBaseLogger, ITelemetryLogger } from \"@fluidframework/common-definitions\";\nimport { ChildLogger } from \"@fluidframework/telemetry-utils\";\nimport { scheduleIdleTask } from \"./scheduleIdleTask\";\nimport {\n\tgetFluidCacheIndexedDbInstance,\n\tFluidCacheDBSchema,\n\tFluidDriverObjectStoreName,\n\tgetKeyForCacheEntry,\n} from \"./FluidCacheIndexedDb\";\nimport {\n\tFluidCacheErrorEvent,\n\tFluidCacheEventSubCategories,\n\tFluidCacheGenericEvent,\n} from \"./fluidCacheTelemetry\";\n\n// Some browsers have a usageDetails property that will tell you more detailed information\n// on how the storage is being used\ninterface StorageQuotaUsageDetails {\n\tindexedDB: number | undefined;\n}\n\nexport interface FluidCacheConfig {\n\t/**\n\t * A string to specify what partition of the cache you wish to use (e.g. a user id).\n\t * Null can be used to explicity indicate no partitioning, and has been chosen\n\t * vs undefined so that it is clear this is an intentional choice by the caller.\n\t * A null value should only be used when the host can ensure that the cache is not able\n\t * to be shared with multiple users.\n\t */\n\t// eslint-disable-next-line @rushstack/no-new-null\n\tpartitionKey: string | null;\n\n\t/**\n\t * A logger that can be used to get insight into cache performance and errors\n\t */\n\tlogger?: ITelemetryBaseLogger;\n\n\t/**\n\t * A value in milliseconds that determines the maximum age of a cache entry to return.\n\t * If an entry exists in the cache, but is older than this value, the cached value will not be returned.\n\t */\n\tmaxCacheItemAge: number;\n}\n\n/**\n * A cache that can be used by the Fluid ODSP driver to cache data for faster performance\n */\nexport class FluidCache implements IPersistedCache {\n\tprivate readonly logger: ITelemetryLogger;\n\n\tprivate readonly partitionKey: string | null;\n\n\tprivate readonly maxCacheItemAge: number;\n\n\tconstructor(config: FluidCacheConfig) {\n\t\tthis.logger = ChildLogger.create(config.logger);\n\t\tthis.partitionKey = config.partitionKey;\n\t\tthis.maxCacheItemAge = config.maxCacheItemAge;\n\n\t\tscheduleIdleTask(async () => {\n\t\t\t// Log how much storage space is currently being used by indexed db.\n\t\t\t// NOTE: This API is not supported in all browsers and it doesn't let you see the size of a specific DB.\n\t\t\t// Exception added when eslint rule was added, this should be revisited when modifying this code\n\t\t\tif (navigator.storage?.estimate) {\n\t\t\t\tconst estimate = await navigator.storage.estimate();\n\n\t\t\t\t// Some browsers have a usageDetails property that will tell you\n\t\t\t\t// more detailed information on how the storage is being used\n\t\t\t\tlet indexedDBSize: number | undefined;\n\t\t\t\tif (\"usageDetails\" in estimate) {\n\t\t\t\t\tindexedDBSize = ((estimate as any).usageDetails as StorageQuotaUsageDetails)\n\t\t\t\t\t\t.indexedDB;\n\t\t\t\t}\n\n\t\t\t\tthis.logger.sendTelemetryEvent({\n\t\t\t\t\teventName: FluidCacheGenericEvent.FluidCacheStorageInfo,\n\t\t\t\t\tsubCategory: FluidCacheEventSubCategories.FluidCache,\n\t\t\t\t\tquota: estimate.quota,\n\t\t\t\t\tusage: estimate.usage,\n\t\t\t\t\tindexedDBSize,\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\tscheduleIdleTask(async () => {\n\t\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\n\t\t\t// Delete entries that have not been accessed recently to clean up space\n\t\t\ttry {\n\t\t\t\tdb = await getFluidCacheIndexedDbInstance(this.logger);\n\n\t\t\t\tconst transaction = db.transaction(FluidDriverObjectStoreName, \"readwrite\");\n\t\t\t\tconst index = transaction.store.index(\"createdTimeMs\");\n\t\t\t\t// Get items which were cached before the maxCacheItemAge.\n\t\t\t\tconst keysToDelete = await index.getAllKeys(\n\t\t\t\t\tIDBKeyRange.upperBound(new Date().getTime() - this.maxCacheItemAge),\n\t\t\t\t);\n\n\t\t\t\tawait Promise.all(keysToDelete.map((key) => transaction.store.delete(key)));\n\t\t\t\tawait transaction.done;\n\t\t\t} catch (error: any) {\n\t\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: FluidCacheErrorEvent.FluidCacheDeleteOldEntriesError,\n\t\t\t\t\t},\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t} finally {\n\t\t\t\tdb?.close();\n\t\t\t}\n\t\t});\n\t}\n\n\tpublic async removeEntries(file: IFileEntry): Promise<void> {\n\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\t\ttry {\n\t\t\tdb = await getFluidCacheIndexedDbInstance(this.logger);\n\n\t\t\tconst transaction = db.transaction(FluidDriverObjectStoreName, \"readwrite\");\n\t\t\tconst index = transaction.store.index(\"fileId\");\n\n\t\t\tconst keysToDelete = await index.getAllKeys(file.docId);\n\n\t\t\tawait Promise.all(keysToDelete.map((key) => transaction.store.delete(key)));\n\t\t\tawait transaction.done;\n\t\t} catch (error: any) {\n\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t{\n\t\t\t\t\teventName: FluidCacheErrorEvent.FluidCacheDeleteOldEntriesError,\n\t\t\t\t},\n\t\t\t\terror,\n\t\t\t);\n\t\t} finally {\n\t\t\tdb?.close();\n\t\t}\n\t}\n\n\tpublic async get(cacheEntry: ICacheEntry): Promise<any> {\n\t\tconst startTime = performance.now();\n\n\t\tconst cachedItem = await this.getItemFromCache(cacheEntry);\n\n\t\tthis.logger.sendPerformanceEvent({\n\t\t\teventName: \"FluidCacheAccess\",\n\t\t\tcacheHit: cachedItem !== undefined,\n\t\t\ttype: cacheEntry.type,\n\t\t\tduration: performance.now() - startTime,\n\t\t\tdbOpenPerf: cachedItem?.dbOpenPerf,\n\t\t\tdbClosePerf: cachedItem?.dbClosePerf,\n\t\t});\n\n\t\t// Value will contain metadata like the expiry time, we just want to return the object we were asked to cache\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-return\n\t\treturn cachedItem?.cachedObject;\n\t}\n\n\tprivate async getItemFromCache(cacheEntry: ICacheEntry) {\n\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\t\ttry {\n\t\t\tconst key = getKeyForCacheEntry(cacheEntry);\n\n\t\t\tconst dbOpenStartTime = performance.now();\n\t\t\tdb = await getFluidCacheIndexedDbInstance(this.logger);\n\t\t\tconst dbOpenPerf = performance.now() - dbOpenStartTime;\n\t\t\tconst value = await db.get(FluidDriverObjectStoreName, key);\n\n\t\t\tif (!value) {\n\t\t\t\tdb.close();\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\t// If the data does not come from the same partition, don't return it\n\t\t\tif (value.partitionKey !== this.partitionKey) {\n\t\t\t\tthis.logger.sendTelemetryEvent({\n\t\t\t\t\teventName: FluidCacheGenericEvent.FluidCachePartitionKeyMismatch,\n\t\t\t\t\tsubCategory: FluidCacheEventSubCategories.FluidCache,\n\t\t\t\t});\n\n\t\t\t\tdb.close();\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\tconst currentTime = new Date().getTime();\n\n\t\t\t// If too much time has passed since this cache entry was used, we will also return undefined\n\t\t\tif (currentTime - value.createdTimeMs > this.maxCacheItemAge) {\n\t\t\t\tdb.close();\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\tconst dbCloseStartTime = performance.now();\n\t\t\tdb.close();\n\t\t\tconst dbClosePerf = performance.now() - dbCloseStartTime;\n\t\t\treturn { ...value, dbOpenPerf, dbClosePerf };\n\t\t} catch (error: any) {\n\t\t\t// We can fail to open the db for a variety of reasons,\n\t\t\t// such as the database version having upgraded underneath us. Return undefined in this case\n\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t{ eventName: FluidCacheErrorEvent.FluidCacheGetError },\n\t\t\t\terror,\n\t\t\t);\n\t\t\tdb?.close();\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tpublic async put(entry: ICacheEntry, value: any): Promise<void> {\n\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\t\ttry {\n\t\t\tdb = await getFluidCacheIndexedDbInstance(this.logger);\n\n\t\t\tconst currentTime = new Date().getTime();\n\n\t\t\tawait db.put(\n\t\t\t\tFluidDriverObjectStoreName,\n\t\t\t\t{\n\t\t\t\t\tcachedObject: value,\n\t\t\t\t\tfileId: entry.file.docId,\n\t\t\t\t\ttype: entry.type,\n\t\t\t\t\tcacheItemId: entry.key,\n\t\t\t\t\tpartitionKey: this.partitionKey,\n\t\t\t\t\tcreatedTimeMs: currentTime,\n\t\t\t\t\tlastAccessTimeMs: currentTime,\n\t\t\t\t},\n\t\t\t\tgetKeyForCacheEntry(entry),\n\t\t\t);\n\n\t\t\tdb.close();\n\t\t} catch (error: any) {\n\t\t\t// We can fail to open the db for a variety of reasons,\n\t\t\t// such as the database version having upgraded underneath us\n\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t{ eventName: FluidCacheErrorEvent.FluidCachePutError },\n\t\t\t\terror,\n\t\t\t);\n\t\t} finally {\n\t\t\tdb?.close();\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"FluidCache.js","sourceRoot":"","sources":["../src/FluidCache.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAGtD,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EACN,8BAA8B,EAE9B,0BAA0B,EAC1B,mBAAmB,GACnB,MAAM,uBAAuB,CAAC;AAM/B,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAqC9C;;GAEG;AACH,MAAM,OAAO,UAAU;IAYtB,YAAY,MAAwB;;QANnB,uBAAkB,GAAY,IAAI,CAAC;QAI5C,iBAAY,GAAW,CAAC,CAAC,CAAC;QAGjC,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAChD,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,YAAY,CAAC;QACxC,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC,eAAe,CAAC;QAC9C,IAAI,CAAC,cAAc,GAAG,MAAA,MAAM,CAAC,cAAc,mCAAI,CAAC,CAAC;QACjD,IAAI,IAAI,CAAC,cAAc,GAAG,CAAC,EAAE;YAC5B,IAAI,CAAC,kBAAkB,GAAG,KAAK,CAAC;SAChC;QAED,gBAAgB,CAAC,KAAK,IAAI,EAAE;;YAC3B,oEAAoE;YACpE,wGAAwG;YACxG,gGAAgG;YAChG,IAAI,MAAA,SAAS,CAAC,OAAO,0CAAE,QAAQ,EAAE;gBAChC,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;gBAEpD,gEAAgE;gBAChE,6DAA6D;gBAC7D,IAAI,aAAiC,CAAC;gBACtC,IAAI,cAAc,IAAI,QAAQ,EAAE;oBAC/B,aAAa,GAAK,QAAgB,CAAC,YAAyC;yBAC1E,SAAS,CAAC;iBACZ;gBAED,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC;oBAC9B,SAAS,qDAA8C;oBACvD,WAAW,+BAAyC;oBACpD,KAAK,EAAE,QAAQ,CAAC,KAAK;oBACrB,KAAK,EAAE,QAAQ,CAAC,KAAK;oBACrB,aAAa;oBACb,UAAU;iBACV,CAAC,CAAC;aACH;QACF,CAAC,CAAC,CAAC;QAEH,gBAAgB,CAAC,KAAK,IAAI,EAAE;YAC3B,IAAI,EAAgD,CAAC;YAErD,wEAAwE;YACxE,IAAI;gBACH,EAAE,GAAG,MAAM,8BAA8B,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBAEvD,MAAM,WAAW,GAAG,EAAE,CAAC,WAAW,CAAC,0BAA0B,EAAE,WAAW,CAAC,CAAC;gBAC5E,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;gBACvD,0DAA0D;gBAC1D,MAAM,YAAY,GAAG,MAAM,KAAK,CAAC,UAAU,CAC1C,WAAW,CAAC,UAAU,CAAC,IAAI,IAAI,EAAE,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,eAAe,CAAC,CACnE,CAAC;gBAEF,MAAM,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBAC5E,MAAM,WAAW,CAAC,IAAI,CAAC;aACvB;YAAC,OAAO,KAAU,EAAE;gBACpB,IAAI,CAAC,MAAM,CAAC,cAAc,CACzB;oBACC,SAAS,yEAAsD;oBAC/D,UAAU;iBACV,EACD,KAAK,CACL,CAAC;aACF;oBAAS;gBACT,EAAE,aAAF,EAAE,uBAAF,EAAE,CAAE,KAAK,EAAE,CAAC;aACZ;QACF,CAAC,CAAC,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,MAAM;QACnB,IAAI,IAAI,CAAC,kBAAkB,EAAE;YAC5B,OAAO,8BAA8B,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;SACnD;QACD,IAAI,IAAI,CAAC,EAAE,KAAK,SAAS,EAAE;YAC1B,MAAM,UAAU,GAAG,MAAM,8BAA8B,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACrE,IAAI,IAAI,CAAC,EAAE,KAAK,SAAS,EAAE;gBAC1B,mCAAmC;gBACnC,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC;gBACvB,IAAI,CAAC,EAAE,GAAG,UAAU,CAAC;aACrB;iBAAM;gBACN,UAAU,CAAC,KAAK,EAAE,CAAC;gBACnB,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC;gBACvB,OAAO,IAAI,CAAC,EAAE,CAAC;aACf;YACD,oDAAoD;YACpD,IAAI,CAAC,EAAE,CAAC,eAAe,GAAG,CAAC,EAAE,EAAE,EAAE;;gBAChC,MAAA,IAAI,CAAC,EAAE,0CAAE,KAAK,EAAE,CAAC;gBACjB,IAAI,CAAC,EAAE,GAAG,SAAS,CAAC;gBACpB,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;gBAChC,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;YAC/B,CAAC,CAAC;YACF,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE;gBACxC,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;gBAChC,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;gBAC9B,IAAI,CAAC,EAAE,GAAG,SAAS,CAAC;YACrB,CAAC,CAAC,CAAC;YACH,+CAA+C;YAC/C,MAAM,CAAC,IAAI,CAAC,YAAY,KAAK,SAAS,EAAE,KAAK,CAAC,mCAAmC,CAAC,CAAC;YACnF,IAAI,CAAC,YAAY,GAAG,UAAU,CAAC,GAAG,EAAE;;gBACnC,MAAA,IAAI,CAAC,EAAE,0CAAE,KAAK,EAAE,CAAC;gBACjB,IAAI,CAAC,EAAE,GAAG,SAAS,CAAC;gBACpB,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;YAC/B,CAAC,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;SACxB;QACD,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,SAAS,EAAE,KAAK,CAAC,oCAAoC,CAAC,CAAC;QAC1E,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC;QACvB,OAAO,IAAI,CAAC,EAAE,CAAC;IAChB,CAAC;IAEO,OAAO,CAAC,EAAqC;QACpD,IAAI,IAAI,CAAC,kBAAkB,EAAE;YAC5B,EAAE,aAAF,EAAE,uBAAF,EAAE,CAAE,KAAK,EAAE,CAAC;SACZ;IACF,CAAC;IAEM,KAAK,CAAC,aAAa,CAAC,IAAgB;QAC1C,IAAI,EAAgD,CAAC;QACrD,IAAI;YACH,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;YAEzB,MAAM,WAAW,GAAG,EAAE,CAAC,WAAW,CAAC,0BAA0B,EAAE,WAAW,CAAC,CAAC;YAC5E,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YAEhD,MAAM,YAAY,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAExD,MAAM,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC5E,MAAM,WAAW,CAAC,IAAI,CAAC;SACvB;QAAC,OAAO,KAAU,EAAE;YACpB,IAAI,CAAC,MAAM,CAAC,cAAc,CACzB;gBACC,SAAS,yEAAsD;gBAC/D,UAAU;aACV,EACD,KAAK,CACL,CAAC;SACF;gBAAS;YACT,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;SACjB;IACF,CAAC;IAEM,KAAK,CAAC,GAAG,CAAC,UAAuB;QACvC,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAEpC,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;QAE3D,IAAI,CAAC,MAAM,CAAC,oBAAoB,CAAC;YAChC,SAAS,EAAE,kBAAkB;YAC7B,QAAQ,EAAE,UAAU,KAAK,SAAS;YAClC,IAAI,EAAE,UAAU,CAAC,IAAI;YACrB,QAAQ,EAAE,WAAW,CAAC,GAAG,EAAE,GAAG,SAAS;YACvC,UAAU,EAAE,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,UAAU;YAClC,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,UAAU;SACV,CAAC,CAAC;QAEH,6GAA6G;QAC7G,+DAA+D;QAC/D,OAAO,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,YAAY,CAAC;IACjC,CAAC;IAEO,KAAK,CAAC,gBAAgB,CAAC,UAAuB;QACrD,IAAI,EAAgD,CAAC;QACrD,IAAI;YACH,MAAM,GAAG,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC;YAE5C,MAAM,eAAe,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;YAC1C,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;YACzB,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,eAAe,CAAC;YACvD,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,GAAG,CAAC,0BAA0B,EAAE,GAAG,CAAC,CAAC;YAE5D,IAAI,CAAC,KAAK,EAAE;gBACX,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;gBACjB,OAAO,SAAS,CAAC;aACjB;YAED,qEAAqE;YACrE,IAAI,KAAK,CAAC,YAAY,KAAK,IAAI,CAAC,YAAY,EAAE;gBAC7C,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC;oBAC9B,SAAS,uEAAuD;oBAChE,WAAW,+BAAyC;oBACpD,UAAU;iBACV,CAAC,CAAC;gBAEH,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;gBACjB,OAAO,SAAS,CAAC;aACjB;YAED,MAAM,WAAW,GAAG,IAAI,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC;YAEzC,6FAA6F;YAC7F,IAAI,WAAW,GAAG,KAAK,CAAC,aAAa,GAAG,IAAI,CAAC,eAAe,EAAE;gBAC7D,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;gBACjB,OAAO,SAAS,CAAC;aACjB;YAED,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YACjB,uCAAY,KAAK,KAAE,UAAU,IAAG;SAChC;QAAC,OAAO,KAAU,EAAE;YACpB,uDAAuD;YACvD,4FAA4F;YAC5F,IAAI,CAAC,MAAM,CAAC,cAAc,CACzB,EAAE,SAAS,+CAAyC,EAAE,UAAU,EAAE,EAClE,KAAK,CACL,CAAC;YACF,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YACjB,OAAO,SAAS,CAAC;SACjB;IACF,CAAC;IAEM,KAAK,CAAC,GAAG,CAAC,KAAkB,EAAE,KAAU;QAC9C,IAAI,EAAgD,CAAC;QACrD,IAAI;YACH,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;YAEzB,MAAM,WAAW,GAAG,IAAI,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC;YAEzC,MAAM,EAAE,CAAC,GAAG,CACX,0BAA0B,EAC1B;gBACC,YAAY,EAAE,KAAK;gBACnB,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK;gBACxB,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,WAAW,EAAE,KAAK,CAAC,GAAG;gBACtB,YAAY,EAAE,IAAI,CAAC,YAAY;gBAC/B,aAAa,EAAE,WAAW;gBAC1B,gBAAgB,EAAE,WAAW;aAC7B,EACD,mBAAmB,CAAC,KAAK,CAAC,CAC1B,CAAC;YACF,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;SACjB;QAAC,OAAO,KAAU,EAAE;YACpB,uDAAuD;YACvD,6DAA6D;YAC7D,IAAI,CAAC,MAAM,CAAC,cAAc,CACzB,EAAE,SAAS,+CAAyC,EAAE,UAAU,EAAE,EAClE,KAAK,CACL,CAAC;SACF;gBAAS;YACT,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;SACjB;IACF,CAAC;CACD","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { IDBPDatabase } from \"idb\";\nimport { assert } from \"@fluidframework/common-utils\";\nimport { IPersistedCache, ICacheEntry, IFileEntry } from \"@fluidframework/odsp-driver-definitions\";\nimport { ITelemetryBaseLogger, ITelemetryLogger } from \"@fluidframework/common-definitions\";\nimport { ChildLogger } from \"@fluidframework/telemetry-utils\";\nimport { scheduleIdleTask } from \"./scheduleIdleTask\";\nimport {\n\tgetFluidCacheIndexedDbInstance,\n\tFluidCacheDBSchema,\n\tFluidDriverObjectStoreName,\n\tgetKeyForCacheEntry,\n} from \"./FluidCacheIndexedDb\";\nimport {\n\tFluidCacheErrorEvent,\n\tFluidCacheEventSubCategories,\n\tFluidCacheGenericEvent,\n} from \"./fluidCacheTelemetry\";\nimport { pkgVersion } from \"./packageVersion\";\n\n// Some browsers have a usageDetails property that will tell you more detailed information\n// on how the storage is being used\ninterface StorageQuotaUsageDetails {\n\tindexedDB: number | undefined;\n}\n\nexport interface FluidCacheConfig {\n\t/**\n\t * A string to specify what partition of the cache you wish to use (e.g. a user id).\n\t * Null can be used to explicity indicate no partitioning, and has been chosen\n\t * vs undefined so that it is clear this is an intentional choice by the caller.\n\t * A null value should only be used when the host can ensure that the cache is not able\n\t * to be shared with multiple users.\n\t */\n\t// eslint-disable-next-line @rushstack/no-new-null\n\tpartitionKey: string | null;\n\n\t/**\n\t * A logger that can be used to get insight into cache performance and errors\n\t */\n\tlogger?: ITelemetryBaseLogger;\n\n\t/**\n\t * A value in milliseconds that determines the maximum age of a cache entry to return.\n\t * If an entry exists in the cache, but is older than this value, the cached value will not be returned.\n\t */\n\tmaxCacheItemAge: number;\n\n\t/**\n\t * Each time db is opened, it will remain open for this much time. To improve perf, if this property is set as\n\t * any number greater than 0, then db will not be closed immediately after usage. This value is in milliseconds.\n\t */\n\tcloseDbAfterMs?: number;\n}\n\n/**\n * A cache that can be used by the Fluid ODSP driver to cache data for faster performance\n */\nexport class FluidCache implements IPersistedCache {\n\tprivate readonly logger: ITelemetryLogger;\n\n\tprivate readonly partitionKey: string | null;\n\n\tprivate readonly maxCacheItemAge: number;\n\tprivate readonly closeDbImmediately: boolean = true;\n\tprivate readonly closeDbAfterMs: number;\n\tprivate db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\tprivate dbCloseTimer: ReturnType<typeof setTimeout> | undefined;\n\tprivate dbReuseCount: number = -1;\n\n\tconstructor(config: FluidCacheConfig) {\n\t\tthis.logger = ChildLogger.create(config.logger);\n\t\tthis.partitionKey = config.partitionKey;\n\t\tthis.maxCacheItemAge = config.maxCacheItemAge;\n\t\tthis.closeDbAfterMs = config.closeDbAfterMs ?? 0;\n\t\tif (this.closeDbAfterMs > 0) {\n\t\t\tthis.closeDbImmediately = false;\n\t\t}\n\n\t\tscheduleIdleTask(async () => {\n\t\t\t// Log how much storage space is currently being used by indexed db.\n\t\t\t// NOTE: This API is not supported in all browsers and it doesn't let you see the size of a specific DB.\n\t\t\t// Exception added when eslint rule was added, this should be revisited when modifying this code\n\t\t\tif (navigator.storage?.estimate) {\n\t\t\t\tconst estimate = await navigator.storage.estimate();\n\n\t\t\t\t// Some browsers have a usageDetails property that will tell you\n\t\t\t\t// more detailed information on how the storage is being used\n\t\t\t\tlet indexedDBSize: number | undefined;\n\t\t\t\tif (\"usageDetails\" in estimate) {\n\t\t\t\t\tindexedDBSize = ((estimate as any).usageDetails as StorageQuotaUsageDetails)\n\t\t\t\t\t\t.indexedDB;\n\t\t\t\t}\n\n\t\t\t\tthis.logger.sendTelemetryEvent({\n\t\t\t\t\teventName: FluidCacheGenericEvent.FluidCacheStorageInfo,\n\t\t\t\t\tsubCategory: FluidCacheEventSubCategories.FluidCache,\n\t\t\t\t\tquota: estimate.quota,\n\t\t\t\t\tusage: estimate.usage,\n\t\t\t\t\tindexedDBSize,\n\t\t\t\t\tpkgVersion,\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\tscheduleIdleTask(async () => {\n\t\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\n\t\t\t// Delete entries that have not been accessed recently to clean up space\n\t\t\ttry {\n\t\t\t\tdb = await getFluidCacheIndexedDbInstance(this.logger);\n\n\t\t\t\tconst transaction = db.transaction(FluidDriverObjectStoreName, \"readwrite\");\n\t\t\t\tconst index = transaction.store.index(\"createdTimeMs\");\n\t\t\t\t// Get items which were cached before the maxCacheItemAge.\n\t\t\t\tconst keysToDelete = await index.getAllKeys(\n\t\t\t\t\tIDBKeyRange.upperBound(new Date().getTime() - this.maxCacheItemAge),\n\t\t\t\t);\n\n\t\t\t\tawait Promise.all(keysToDelete.map((key) => transaction.store.delete(key)));\n\t\t\t\tawait transaction.done;\n\t\t\t} catch (error: any) {\n\t\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: FluidCacheErrorEvent.FluidCacheDeleteOldEntriesError,\n\t\t\t\t\t\tpkgVersion,\n\t\t\t\t\t},\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t} finally {\n\t\t\t\tdb?.close();\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate async openDb() {\n\t\tif (this.closeDbImmediately) {\n\t\t\treturn getFluidCacheIndexedDbInstance(this.logger);\n\t\t}\n\t\tif (this.db === undefined) {\n\t\t\tconst dbInstance = await getFluidCacheIndexedDbInstance(this.logger);\n\t\t\tif (this.db === undefined) {\n\t\t\t\t// Reset the counter on first open.\n\t\t\t\tthis.dbReuseCount = -1;\n\t\t\t\tthis.db = dbInstance;\n\t\t\t} else {\n\t\t\t\tdbInstance.close();\n\t\t\t\tthis.dbReuseCount += 1;\n\t\t\t\treturn this.db;\n\t\t\t}\n\t\t\t// Need to close the db on version change if opened.\n\t\t\tthis.db.onversionchange = (ev) => {\n\t\t\t\tthis.db?.close();\n\t\t\t\tthis.db = undefined;\n\t\t\t\tclearTimeout(this.dbCloseTimer);\n\t\t\t\tthis.dbCloseTimer = undefined;\n\t\t\t};\n\t\t\tthis.db.addEventListener(\"close\", (ev) => {\n\t\t\t\tclearTimeout(this.dbCloseTimer);\n\t\t\t\tthis.dbCloseTimer = undefined;\n\t\t\t\tthis.db = undefined;\n\t\t\t});\n\t\t\t// Schedule db close after this.closeDbAfterMs.\n\t\t\tassert(this.dbCloseTimer === undefined, 0x6c6 /* timer should not be set yet!! */);\n\t\t\tthis.dbCloseTimer = setTimeout(() => {\n\t\t\t\tthis.db?.close();\n\t\t\t\tthis.db = undefined;\n\t\t\t\tthis.dbCloseTimer = undefined;\n\t\t\t}, this.closeDbAfterMs);\n\t\t}\n\t\tassert(this.db !== undefined, 0x6c7 /* db should be intialized by now */);\n\t\tthis.dbReuseCount += 1;\n\t\treturn this.db;\n\t}\n\n\tprivate closeDb(db?: IDBPDatabase<FluidCacheDBSchema>) {\n\t\tif (this.closeDbImmediately) {\n\t\t\tdb?.close();\n\t\t}\n\t}\n\n\tpublic async removeEntries(file: IFileEntry): Promise<void> {\n\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\t\ttry {\n\t\t\tdb = await this.openDb();\n\n\t\t\tconst transaction = db.transaction(FluidDriverObjectStoreName, \"readwrite\");\n\t\t\tconst index = transaction.store.index(\"fileId\");\n\n\t\t\tconst keysToDelete = await index.getAllKeys(file.docId);\n\n\t\t\tawait Promise.all(keysToDelete.map((key) => transaction.store.delete(key)));\n\t\t\tawait transaction.done;\n\t\t} catch (error: any) {\n\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t{\n\t\t\t\t\teventName: FluidCacheErrorEvent.FluidCacheDeleteOldEntriesError,\n\t\t\t\t\tpkgVersion,\n\t\t\t\t},\n\t\t\t\terror,\n\t\t\t);\n\t\t} finally {\n\t\t\tthis.closeDb(db);\n\t\t}\n\t}\n\n\tpublic async get(cacheEntry: ICacheEntry): Promise<any> {\n\t\tconst startTime = performance.now();\n\n\t\tconst cachedItem = await this.getItemFromCache(cacheEntry);\n\n\t\tthis.logger.sendPerformanceEvent({\n\t\t\teventName: \"FluidCacheAccess\",\n\t\t\tcacheHit: cachedItem !== undefined,\n\t\t\ttype: cacheEntry.type,\n\t\t\tduration: performance.now() - startTime,\n\t\t\tdbOpenPerf: cachedItem?.dbOpenPerf,\n\t\t\tdbReuseCount: this.dbReuseCount,\n\t\t\tpkgVersion,\n\t\t});\n\n\t\t// Value will contain metadata like the expiry time, we just want to return the object we were asked to cache\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-return\n\t\treturn cachedItem?.cachedObject;\n\t}\n\n\tprivate async getItemFromCache(cacheEntry: ICacheEntry) {\n\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\t\ttry {\n\t\t\tconst key = getKeyForCacheEntry(cacheEntry);\n\n\t\t\tconst dbOpenStartTime = performance.now();\n\t\t\tdb = await this.openDb();\n\t\t\tconst dbOpenPerf = performance.now() - dbOpenStartTime;\n\t\t\tconst value = await db.get(FluidDriverObjectStoreName, key);\n\n\t\t\tif (!value) {\n\t\t\t\tthis.closeDb(db);\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\t// If the data does not come from the same partition, don't return it\n\t\t\tif (value.partitionKey !== this.partitionKey) {\n\t\t\t\tthis.logger.sendTelemetryEvent({\n\t\t\t\t\teventName: FluidCacheGenericEvent.FluidCachePartitionKeyMismatch,\n\t\t\t\t\tsubCategory: FluidCacheEventSubCategories.FluidCache,\n\t\t\t\t\tpkgVersion,\n\t\t\t\t});\n\n\t\t\t\tthis.closeDb(db);\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\tconst currentTime = new Date().getTime();\n\n\t\t\t// If too much time has passed since this cache entry was used, we will also return undefined\n\t\t\tif (currentTime - value.createdTimeMs > this.maxCacheItemAge) {\n\t\t\t\tthis.closeDb(db);\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\tthis.closeDb(db);\n\t\t\treturn { ...value, dbOpenPerf };\n\t\t} catch (error: any) {\n\t\t\t// We can fail to open the db for a variety of reasons,\n\t\t\t// such as the database version having upgraded underneath us. Return undefined in this case\n\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t{ eventName: FluidCacheErrorEvent.FluidCacheGetError, pkgVersion },\n\t\t\t\terror,\n\t\t\t);\n\t\t\tthis.closeDb(db);\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tpublic async put(entry: ICacheEntry, value: any): Promise<void> {\n\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\t\ttry {\n\t\t\tdb = await this.openDb();\n\n\t\t\tconst currentTime = new Date().getTime();\n\n\t\t\tawait db.put(\n\t\t\t\tFluidDriverObjectStoreName,\n\t\t\t\t{\n\t\t\t\t\tcachedObject: value,\n\t\t\t\t\tfileId: entry.file.docId,\n\t\t\t\t\ttype: entry.type,\n\t\t\t\t\tcacheItemId: entry.key,\n\t\t\t\t\tpartitionKey: this.partitionKey,\n\t\t\t\t\tcreatedTimeMs: currentTime,\n\t\t\t\t\tlastAccessTimeMs: currentTime,\n\t\t\t\t},\n\t\t\t\tgetKeyForCacheEntry(entry),\n\t\t\t);\n\t\t\tthis.closeDb(db);\n\t\t} catch (error: any) {\n\t\t\t// We can fail to open the db for a variety of reasons,\n\t\t\t// such as the database version having upgraded underneath us\n\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t{ eventName: FluidCacheErrorEvent.FluidCachePutError, pkgVersion },\n\t\t\t\terror,\n\t\t\t);\n\t\t} finally {\n\t\t\tthis.closeDb(db);\n\t\t}\n\t}\n}\n"]}
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export declare const pkgName = "@fluidframework/driver-web-cache";
8
- export declare const pkgVersion = "2.0.0-internal.4.2.1";
8
+ export declare const pkgVersion = "2.0.0-internal.4.4.0";
9
9
  //# sourceMappingURL=packageVersion.d.ts.map
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export const pkgName = "@fluidframework/driver-web-cache";
8
- export const pkgVersion = "2.0.0-internal.4.2.1";
8
+ export const pkgVersion = "2.0.0-internal.4.4.0";
9
9
  //# sourceMappingURL=packageVersion.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,kCAAkC,CAAC;AAC1D,MAAM,CAAC,MAAM,UAAU,GAAG,sBAAsB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/driver-web-cache\";\nexport const pkgVersion = \"2.0.0-internal.4.2.1\";\n"]}
1
+ {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,kCAAkC,CAAC;AAC1D,MAAM,CAAC,MAAM,UAAU,GAAG,sBAAsB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/driver-web-cache\";\nexport const pkgVersion = \"2.0.0-internal.4.4.0\";\n"]}
@@ -15,23 +15,23 @@ export function scheduleIdleTask(task) {
15
15
  taskQueue.push({
16
16
  task,
17
17
  });
18
- ensureIdleCallback();
18
+ ensureIdleCallback(2000);
19
19
  }
20
20
  /**
21
21
  * Ensures an idle callback has been scheduled for the remaining tasks
22
22
  */
23
- function ensureIdleCallback() {
23
+ function ensureIdleCallback(timeout = 0) {
24
24
  if (!idleTaskScheduled) {
25
25
  // Exception added when eslint rule was added, this should be revisited when modifying this code
26
- if (window.requestIdleCallback) {
27
- window.requestIdleCallback(idleTaskCallback);
26
+ if (self.requestIdleCallback) {
27
+ self.requestIdleCallback(idleTaskCallback);
28
28
  }
29
29
  else {
30
30
  const deadline = Date.now() + 50;
31
- window.setTimeout(() => idleTaskCallback({
31
+ self.setTimeout(() => idleTaskCallback({
32
32
  timeRemaining: () => Math.max(deadline - Date.now(), 0),
33
33
  didTimeout: false,
34
- }), 0);
34
+ }), timeout);
35
35
  }
36
36
  idleTaskScheduled = true;
37
37
  }
@@ -1 +1 @@
1
- {"version":3,"file":"scheduleIdleTask.js","sourceRoot":"","sources":["../src/scheduleIdleTask.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,2CAA2C;AAC3C,IAAI,SAAS,GAAoB,EAAE,CAAC;AAEpC,yDAAyD;AACzD,IAAI,iBAAiB,GAAG,KAAK,CAAC;AAE9B;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAgB;IAChD,SAAS,CAAC,IAAI,CAAC;QACd,IAAI;KACJ,CAAC,CAAC;IAEH,kBAAkB,EAAE,CAAC;AACtB,CAAC;AAED;;GAEG;AACH,SAAS,kBAAkB;IAC1B,IAAI,CAAC,iBAAiB,EAAE;QACvB,gGAAgG;QAChG,IAAI,MAAM,CAAC,mBAAmB,EAAE;YAC/B,MAAM,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,CAAC;SAC7C;aAAM;YACN,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;YACjC,MAAM,CAAC,UAAU,CAChB,GAAG,EAAE,CACJ,gBAAgB,CAAC;gBAChB,aAAa,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;gBACvD,UAAU,EAAE,KAAK;aACjB,CAAC,EACH,CAAC,CACD,CAAC;SACF;QACD,iBAAiB,GAAG,IAAI,CAAC;KACzB;AACF,CAAC;AAED;;;;;;GAMG;AACH,SAAS,QAAQ,CAChB,MAAkD,EAClD,qBAAqC;IAErC,oCAAoC;IACpC,MAAM,YAAY,GAAoB,EAAE,CAAC;IAEzC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE;QACzD,IAAI,qBAAqB,IAAI,CAAC,qBAAqB,EAAE,EAAE;YACtD,kEAAkE;YAClE,YAAY,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;YAC7C,MAAM;SACN;QAED,MAAM,aAAa,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;QAEvC,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE;YACrC,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;SACjC;aAAM;YACN,aAAa,CAAC,IAAI,EAAE,CAAC;SACrB;KACD;IAED,SAAS,GAAG,YAAY,CAAC;AAC1B,CAAC;AAED,uCAAuC;AACvC,SAAS,gBAAgB,CAAC,QAAuE;IAChG,wEAAwE;IACxE,MAAM,WAAW,GAAG,EAAE,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,aAAa,EAAE,GAAG,WAAW,CAAC,CAAC;IAClE,iBAAiB,GAAG,KAAK,CAAC;IAE1B,4EAA4E;IAC5E,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE;QACzB,kBAAkB,EAAE,CAAC;KACrB;AACF,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\ninterface TaskQueueItem {\n\t/** The task to run */\n\ttask: () => void;\n}\n\n// A set of tasks that still have to be run\nlet taskQueue: TaskQueueItem[] = [];\n\n// Set to true when we have a pending idle task scheduled\nlet idleTaskScheduled = false;\n\n/**\n * A function that schedules a non critical task to be run when the browser has cycles available\n * @param task - The task to be executed\n * @param options - Optional configuration for the task execution\n */\nexport function scheduleIdleTask(task: () => void) {\n\ttaskQueue.push({\n\t\ttask,\n\t});\n\n\tensureIdleCallback();\n}\n\n/**\n * Ensures an idle callback has been scheduled for the remaining tasks\n */\nfunction ensureIdleCallback() {\n\tif (!idleTaskScheduled) {\n\t\t// Exception added when eslint rule was added, this should be revisited when modifying this code\n\t\tif (window.requestIdleCallback) {\n\t\t\twindow.requestIdleCallback(idleTaskCallback);\n\t\t} else {\n\t\t\tconst deadline = Date.now() + 50;\n\t\t\twindow.setTimeout(\n\t\t\t\t() =>\n\t\t\t\t\tidleTaskCallback({\n\t\t\t\t\t\ttimeRemaining: () => Math.max(deadline - Date.now(), 0),\n\t\t\t\t\t\tdidTimeout: false,\n\t\t\t\t\t}),\n\t\t\t\t0,\n\t\t\t);\n\t\t}\n\t\tidleTaskScheduled = true;\n\t}\n}\n\n/**\n * Runs tasks from the task queue\n * @param filter - An optional function that will be called for each task to see if it should run.\n * Returns false for tasks that should not run. If omitted all tasks run.\n * @param shouldContinueRunning - An optional function that will be called to determine if\n * we have enough time to continue running tasks. If omitted, we don't stop running tasks.\n */\nfunction runTasks(\n\tfilter?: (taskQueueItem: TaskQueueItem) => boolean,\n\tshouldContinueRunning?: () => boolean,\n) {\n\t// The next value for the task queue\n\tconst newTaskQueue: TaskQueueItem[] = [];\n\n\tfor (let index = 0; index < taskQueue.length; index += 1) {\n\t\tif (shouldContinueRunning && !shouldContinueRunning()) {\n\t\t\t// Add the tasks we didn't get to to the end of the new task queue\n\t\t\tnewTaskQueue.push(...taskQueue.slice(index));\n\t\t\tbreak;\n\t\t}\n\n\t\tconst taskQueueItem = taskQueue[index];\n\n\t\tif (filter && !filter(taskQueueItem)) {\n\t\t\tnewTaskQueue.push(taskQueueItem);\n\t\t} else {\n\t\t\ttaskQueueItem.task();\n\t\t}\n\t}\n\n\ttaskQueue = newTaskQueue;\n}\n\n// Runs all the tasks in the task queue\nfunction idleTaskCallback(deadline: { timeRemaining: () => number; readonly didTimeout: boolean }) {\n\t// Minimum time that must be available on deadline to run any more tasks\n\tconst minTaskTime = 10;\n\trunTasks(undefined, () => deadline.timeRemaining() > minTaskTime);\n\tidleTaskScheduled = false;\n\n\t// If we didn't run through the entire queue, schedule another idle callback\n\tif (taskQueue.length > 0) {\n\t\tensureIdleCallback();\n\t}\n}\n"]}
1
+ {"version":3,"file":"scheduleIdleTask.js","sourceRoot":"","sources":["../src/scheduleIdleTask.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,2CAA2C;AAC3C,IAAI,SAAS,GAAoB,EAAE,CAAC;AAEpC,yDAAyD;AACzD,IAAI,iBAAiB,GAAG,KAAK,CAAC;AAE9B;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAgB;IAChD,SAAS,CAAC,IAAI,CAAC;QACd,IAAI;KACJ,CAAC,CAAC;IAEH,kBAAkB,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;GAEG;AACH,SAAS,kBAAkB,CAAC,UAAkB,CAAC;IAC9C,IAAI,CAAC,iBAAiB,EAAE;QACvB,gGAAgG;QAChG,IAAI,IAAI,CAAC,mBAAmB,EAAE;YAC7B,IAAI,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,CAAC;SAC3C;aAAM;YACN,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;YACjC,IAAI,CAAC,UAAU,CACd,GAAG,EAAE,CACJ,gBAAgB,CAAC;gBAChB,aAAa,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;gBACvD,UAAU,EAAE,KAAK;aACjB,CAAC,EACH,OAAO,CACP,CAAC;SACF;QACD,iBAAiB,GAAG,IAAI,CAAC;KACzB;AACF,CAAC;AAED;;;;;;GAMG;AACH,SAAS,QAAQ,CAChB,MAAkD,EAClD,qBAAqC;IAErC,oCAAoC;IACpC,MAAM,YAAY,GAAoB,EAAE,CAAC;IAEzC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE;QACzD,IAAI,qBAAqB,IAAI,CAAC,qBAAqB,EAAE,EAAE;YACtD,kEAAkE;YAClE,YAAY,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;YAC7C,MAAM;SACN;QAED,MAAM,aAAa,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;QAEvC,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE;YACrC,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;SACjC;aAAM;YACN,aAAa,CAAC,IAAI,EAAE,CAAC;SACrB;KACD;IAED,SAAS,GAAG,YAAY,CAAC;AAC1B,CAAC;AAED,uCAAuC;AACvC,SAAS,gBAAgB,CAAC,QAAuE;IAChG,wEAAwE;IACxE,MAAM,WAAW,GAAG,EAAE,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,aAAa,EAAE,GAAG,WAAW,CAAC,CAAC;IAClE,iBAAiB,GAAG,KAAK,CAAC;IAE1B,4EAA4E;IAC5E,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE;QACzB,kBAAkB,EAAE,CAAC;KACrB;AACF,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\ninterface TaskQueueItem {\n\t/** The task to run */\n\ttask: () => void;\n}\n\n// A set of tasks that still have to be run\nlet taskQueue: TaskQueueItem[] = [];\n\n// Set to true when we have a pending idle task scheduled\nlet idleTaskScheduled = false;\n\n/**\n * A function that schedules a non critical task to be run when the browser has cycles available\n * @param task - The task to be executed\n * @param options - Optional configuration for the task execution\n */\nexport function scheduleIdleTask(task: () => void) {\n\ttaskQueue.push({\n\t\ttask,\n\t});\n\n\tensureIdleCallback(2000);\n}\n\n/**\n * Ensures an idle callback has been scheduled for the remaining tasks\n */\nfunction ensureIdleCallback(timeout: number = 0) {\n\tif (!idleTaskScheduled) {\n\t\t// Exception added when eslint rule was added, this should be revisited when modifying this code\n\t\tif (self.requestIdleCallback) {\n\t\t\tself.requestIdleCallback(idleTaskCallback);\n\t\t} else {\n\t\t\tconst deadline = Date.now() + 50;\n\t\t\tself.setTimeout(\n\t\t\t\t() =>\n\t\t\t\t\tidleTaskCallback({\n\t\t\t\t\t\ttimeRemaining: () => Math.max(deadline - Date.now(), 0),\n\t\t\t\t\t\tdidTimeout: false,\n\t\t\t\t\t}),\n\t\t\t\ttimeout,\n\t\t\t);\n\t\t}\n\t\tidleTaskScheduled = true;\n\t}\n}\n\n/**\n * Runs tasks from the task queue\n * @param filter - An optional function that will be called for each task to see if it should run.\n * Returns false for tasks that should not run. If omitted all tasks run.\n * @param shouldContinueRunning - An optional function that will be called to determine if\n * we have enough time to continue running tasks. If omitted, we don't stop running tasks.\n */\nfunction runTasks(\n\tfilter?: (taskQueueItem: TaskQueueItem) => boolean,\n\tshouldContinueRunning?: () => boolean,\n) {\n\t// The next value for the task queue\n\tconst newTaskQueue: TaskQueueItem[] = [];\n\n\tfor (let index = 0; index < taskQueue.length; index += 1) {\n\t\tif (shouldContinueRunning && !shouldContinueRunning()) {\n\t\t\t// Add the tasks we didn't get to to the end of the new task queue\n\t\t\tnewTaskQueue.push(...taskQueue.slice(index));\n\t\t\tbreak;\n\t\t}\n\n\t\tconst taskQueueItem = taskQueue[index];\n\n\t\tif (filter && !filter(taskQueueItem)) {\n\t\t\tnewTaskQueue.push(taskQueueItem);\n\t\t} else {\n\t\t\ttaskQueueItem.task();\n\t\t}\n\t}\n\n\ttaskQueue = newTaskQueue;\n}\n\n// Runs all the tasks in the task queue\nfunction idleTaskCallback(deadline: { timeRemaining: () => number; readonly didTimeout: boolean }) {\n\t// Minimum time that must be available on deadline to run any more tasks\n\tconst minTaskTime = 10;\n\trunTasks(undefined, () => deadline.timeRemaining() > minTaskTime);\n\tidleTaskScheduled = false;\n\n\t// If we didn't run through the entire queue, schedule another idle callback\n\tif (taskQueue.length > 0) {\n\t\tensureIdleCallback();\n\t}\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/driver-web-cache",
3
- "version": "2.0.0-internal.4.2.1",
3
+ "version": "2.0.0-internal.4.4.0",
4
4
  "description": "Implementation of the driver caching API for a web browser",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -16,8 +16,9 @@
16
16
  "types": "dist/index.d.ts",
17
17
  "dependencies": {
18
18
  "@fluidframework/common-definitions": "^0.20.1",
19
- "@fluidframework/odsp-driver-definitions": ">=2.0.0-internal.4.2.1 <2.0.0-internal.4.3.0",
20
- "@fluidframework/telemetry-utils": ">=2.0.0-internal.4.2.1 <2.0.0-internal.4.3.0",
19
+ "@fluidframework/common-utils": "^1.1.1",
20
+ "@fluidframework/odsp-driver-definitions": ">=2.0.0-internal.4.4.0 <2.0.0-internal.4.5.0",
21
+ "@fluidframework/telemetry-utils": ">=2.0.0-internal.4.4.0 <2.0.0-internal.4.5.0",
21
22
  "idb": "^6.1.2"
22
23
  },
23
24
  "devDependencies": {
package/src/FluidCache.ts CHANGED
@@ -2,7 +2,9 @@
2
2
  * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
3
  * Licensed under the MIT License.
4
4
  */
5
+
5
6
  import { IDBPDatabase } from "idb";
7
+ import { assert } from "@fluidframework/common-utils";
6
8
  import { IPersistedCache, ICacheEntry, IFileEntry } from "@fluidframework/odsp-driver-definitions";
7
9
  import { ITelemetryBaseLogger, ITelemetryLogger } from "@fluidframework/common-definitions";
8
10
  import { ChildLogger } from "@fluidframework/telemetry-utils";
@@ -18,6 +20,7 @@ import {
18
20
  FluidCacheEventSubCategories,
19
21
  FluidCacheGenericEvent,
20
22
  } from "./fluidCacheTelemetry";
23
+ import { pkgVersion } from "./packageVersion";
21
24
 
22
25
  // Some browsers have a usageDetails property that will tell you more detailed information
23
26
  // on how the storage is being used
@@ -46,6 +49,12 @@ export interface FluidCacheConfig {
46
49
  * If an entry exists in the cache, but is older than this value, the cached value will not be returned.
47
50
  */
48
51
  maxCacheItemAge: number;
52
+
53
+ /**
54
+ * Each time db is opened, it will remain open for this much time. To improve perf, if this property is set as
55
+ * any number greater than 0, then db will not be closed immediately after usage. This value is in milliseconds.
56
+ */
57
+ closeDbAfterMs?: number;
49
58
  }
50
59
 
51
60
  /**
@@ -57,11 +66,20 @@ export class FluidCache implements IPersistedCache {
57
66
  private readonly partitionKey: string | null;
58
67
 
59
68
  private readonly maxCacheItemAge: number;
69
+ private readonly closeDbImmediately: boolean = true;
70
+ private readonly closeDbAfterMs: number;
71
+ private db: IDBPDatabase<FluidCacheDBSchema> | undefined;
72
+ private dbCloseTimer: ReturnType<typeof setTimeout> | undefined;
73
+ private dbReuseCount: number = -1;
60
74
 
61
75
  constructor(config: FluidCacheConfig) {
62
76
  this.logger = ChildLogger.create(config.logger);
63
77
  this.partitionKey = config.partitionKey;
64
78
  this.maxCacheItemAge = config.maxCacheItemAge;
79
+ this.closeDbAfterMs = config.closeDbAfterMs ?? 0;
80
+ if (this.closeDbAfterMs > 0) {
81
+ this.closeDbImmediately = false;
82
+ }
65
83
 
66
84
  scheduleIdleTask(async () => {
67
85
  // Log how much storage space is currently being used by indexed db.
@@ -84,6 +102,7 @@ export class FluidCache implements IPersistedCache {
84
102
  quota: estimate.quota,
85
103
  usage: estimate.usage,
86
104
  indexedDBSize,
105
+ pkgVersion,
87
106
  });
88
107
  }
89
108
  });
@@ -108,6 +127,7 @@ export class FluidCache implements IPersistedCache {
108
127
  this.logger.sendErrorEvent(
109
128
  {
110
129
  eventName: FluidCacheErrorEvent.FluidCacheDeleteOldEntriesError,
130
+ pkgVersion,
111
131
  },
112
132
  error,
113
133
  );
@@ -117,10 +137,56 @@ export class FluidCache implements IPersistedCache {
117
137
  });
118
138
  }
119
139
 
140
+ private async openDb() {
141
+ if (this.closeDbImmediately) {
142
+ return getFluidCacheIndexedDbInstance(this.logger);
143
+ }
144
+ if (this.db === undefined) {
145
+ const dbInstance = await getFluidCacheIndexedDbInstance(this.logger);
146
+ if (this.db === undefined) {
147
+ // Reset the counter on first open.
148
+ this.dbReuseCount = -1;
149
+ this.db = dbInstance;
150
+ } else {
151
+ dbInstance.close();
152
+ this.dbReuseCount += 1;
153
+ return this.db;
154
+ }
155
+ // Need to close the db on version change if opened.
156
+ this.db.onversionchange = (ev) => {
157
+ this.db?.close();
158
+ this.db = undefined;
159
+ clearTimeout(this.dbCloseTimer);
160
+ this.dbCloseTimer = undefined;
161
+ };
162
+ this.db.addEventListener("close", (ev) => {
163
+ clearTimeout(this.dbCloseTimer);
164
+ this.dbCloseTimer = undefined;
165
+ this.db = undefined;
166
+ });
167
+ // Schedule db close after this.closeDbAfterMs.
168
+ assert(this.dbCloseTimer === undefined, 0x6c6 /* timer should not be set yet!! */);
169
+ this.dbCloseTimer = setTimeout(() => {
170
+ this.db?.close();
171
+ this.db = undefined;
172
+ this.dbCloseTimer = undefined;
173
+ }, this.closeDbAfterMs);
174
+ }
175
+ assert(this.db !== undefined, 0x6c7 /* db should be intialized by now */);
176
+ this.dbReuseCount += 1;
177
+ return this.db;
178
+ }
179
+
180
+ private closeDb(db?: IDBPDatabase<FluidCacheDBSchema>) {
181
+ if (this.closeDbImmediately) {
182
+ db?.close();
183
+ }
184
+ }
185
+
120
186
  public async removeEntries(file: IFileEntry): Promise<void> {
121
187
  let db: IDBPDatabase<FluidCacheDBSchema> | undefined;
122
188
  try {
123
- db = await getFluidCacheIndexedDbInstance(this.logger);
189
+ db = await this.openDb();
124
190
 
125
191
  const transaction = db.transaction(FluidDriverObjectStoreName, "readwrite");
126
192
  const index = transaction.store.index("fileId");
@@ -133,11 +199,12 @@ export class FluidCache implements IPersistedCache {
133
199
  this.logger.sendErrorEvent(
134
200
  {
135
201
  eventName: FluidCacheErrorEvent.FluidCacheDeleteOldEntriesError,
202
+ pkgVersion,
136
203
  },
137
204
  error,
138
205
  );
139
206
  } finally {
140
- db?.close();
207
+ this.closeDb(db);
141
208
  }
142
209
  }
143
210
 
@@ -152,7 +219,8 @@ export class FluidCache implements IPersistedCache {
152
219
  type: cacheEntry.type,
153
220
  duration: performance.now() - startTime,
154
221
  dbOpenPerf: cachedItem?.dbOpenPerf,
155
- dbClosePerf: cachedItem?.dbClosePerf,
222
+ dbReuseCount: this.dbReuseCount,
223
+ pkgVersion,
156
224
  });
157
225
 
158
226
  // Value will contain metadata like the expiry time, we just want to return the object we were asked to cache
@@ -166,12 +234,12 @@ export class FluidCache implements IPersistedCache {
166
234
  const key = getKeyForCacheEntry(cacheEntry);
167
235
 
168
236
  const dbOpenStartTime = performance.now();
169
- db = await getFluidCacheIndexedDbInstance(this.logger);
237
+ db = await this.openDb();
170
238
  const dbOpenPerf = performance.now() - dbOpenStartTime;
171
239
  const value = await db.get(FluidDriverObjectStoreName, key);
172
240
 
173
241
  if (!value) {
174
- db.close();
242
+ this.closeDb(db);
175
243
  return undefined;
176
244
  }
177
245
 
@@ -180,9 +248,10 @@ export class FluidCache implements IPersistedCache {
180
248
  this.logger.sendTelemetryEvent({
181
249
  eventName: FluidCacheGenericEvent.FluidCachePartitionKeyMismatch,
182
250
  subCategory: FluidCacheEventSubCategories.FluidCache,
251
+ pkgVersion,
183
252
  });
184
253
 
185
- db.close();
254
+ this.closeDb(db);
186
255
  return undefined;
187
256
  }
188
257
 
@@ -190,22 +259,20 @@ export class FluidCache implements IPersistedCache {
190
259
 
191
260
  // If too much time has passed since this cache entry was used, we will also return undefined
192
261
  if (currentTime - value.createdTimeMs > this.maxCacheItemAge) {
193
- db.close();
262
+ this.closeDb(db);
194
263
  return undefined;
195
264
  }
196
265
 
197
- const dbCloseStartTime = performance.now();
198
- db.close();
199
- const dbClosePerf = performance.now() - dbCloseStartTime;
200
- return { ...value, dbOpenPerf, dbClosePerf };
266
+ this.closeDb(db);
267
+ return { ...value, dbOpenPerf };
201
268
  } catch (error: any) {
202
269
  // We can fail to open the db for a variety of reasons,
203
270
  // such as the database version having upgraded underneath us. Return undefined in this case
204
271
  this.logger.sendErrorEvent(
205
- { eventName: FluidCacheErrorEvent.FluidCacheGetError },
272
+ { eventName: FluidCacheErrorEvent.FluidCacheGetError, pkgVersion },
206
273
  error,
207
274
  );
208
- db?.close();
275
+ this.closeDb(db);
209
276
  return undefined;
210
277
  }
211
278
  }
@@ -213,7 +280,7 @@ export class FluidCache implements IPersistedCache {
213
280
  public async put(entry: ICacheEntry, value: any): Promise<void> {
214
281
  let db: IDBPDatabase<FluidCacheDBSchema> | undefined;
215
282
  try {
216
- db = await getFluidCacheIndexedDbInstance(this.logger);
283
+ db = await this.openDb();
217
284
 
218
285
  const currentTime = new Date().getTime();
219
286
 
@@ -230,17 +297,16 @@ export class FluidCache implements IPersistedCache {
230
297
  },
231
298
  getKeyForCacheEntry(entry),
232
299
  );
233
-
234
- db.close();
300
+ this.closeDb(db);
235
301
  } catch (error: any) {
236
302
  // We can fail to open the db for a variety of reasons,
237
303
  // such as the database version having upgraded underneath us
238
304
  this.logger.sendErrorEvent(
239
- { eventName: FluidCacheErrorEvent.FluidCachePutError },
305
+ { eventName: FluidCacheErrorEvent.FluidCachePutError, pkgVersion },
240
306
  error,
241
307
  );
242
308
  } finally {
243
- db?.close();
309
+ this.closeDb(db);
244
310
  }
245
311
  }
246
312
  }
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/driver-web-cache";
9
- export const pkgVersion = "2.0.0-internal.4.2.1";
9
+ export const pkgVersion = "2.0.0-internal.4.4.0";
@@ -24,26 +24,26 @@ export function scheduleIdleTask(task: () => void) {
24
24
  task,
25
25
  });
26
26
 
27
- ensureIdleCallback();
27
+ ensureIdleCallback(2000);
28
28
  }
29
29
 
30
30
  /**
31
31
  * Ensures an idle callback has been scheduled for the remaining tasks
32
32
  */
33
- function ensureIdleCallback() {
33
+ function ensureIdleCallback(timeout: number = 0) {
34
34
  if (!idleTaskScheduled) {
35
35
  // Exception added when eslint rule was added, this should be revisited when modifying this code
36
- if (window.requestIdleCallback) {
37
- window.requestIdleCallback(idleTaskCallback);
36
+ if (self.requestIdleCallback) {
37
+ self.requestIdleCallback(idleTaskCallback);
38
38
  } else {
39
39
  const deadline = Date.now() + 50;
40
- window.setTimeout(
40
+ self.setTimeout(
41
41
  () =>
42
42
  idleTaskCallback({
43
43
  timeRemaining: () => Math.max(deadline - Date.now(), 0),
44
44
  didTimeout: false,
45
45
  }),
46
- 0,
46
+ timeout,
47
47
  );
48
48
  }
49
49
  idleTaskScheduled = true;