@fluidframework/driver-web-cache 2.0.0-internal.4.3.0 → 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;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;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;IA6DvB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAyB9C,GAAG,CAAC,UAAU,EAAE,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC;YAoBzC,gBAAgB;IAmDjB,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,6 +1,11 @@
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");
@@ -10,9 +15,16 @@ const packageVersion_1 = require("./packageVersion");
10
15
  */
11
16
  class FluidCache {
12
17
  constructor(config) {
18
+ var _a;
19
+ this.closeDbImmediately = true;
20
+ this.dbReuseCount = -1;
13
21
  this.logger = telemetry_utils_1.ChildLogger.create(config.logger);
14
22
  this.partitionKey = config.partitionKey;
15
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
+ }
16
28
  (0, scheduleIdleTask_1.scheduleIdleTask)(async () => {
17
29
  var _a;
18
30
  // Log how much storage space is currently being used by indexed db.
@@ -60,10 +72,57 @@ class FluidCache {
60
72
  }
61
73
  });
62
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
+ }
63
122
  async removeEntries(file) {
64
123
  let db;
65
124
  try {
66
- db = await (0, FluidCacheIndexedDb_1.getFluidCacheIndexedDbInstance)(this.logger);
125
+ db = await this.openDb();
67
126
  const transaction = db.transaction(FluidCacheIndexedDb_1.FluidDriverObjectStoreName, "readwrite");
68
127
  const index = transaction.store.index("fileId");
69
128
  const keysToDelete = await index.getAllKeys(file.docId);
@@ -77,7 +136,7 @@ class FluidCache {
77
136
  }, error);
78
137
  }
79
138
  finally {
80
- db === null || db === void 0 ? void 0 : db.close();
139
+ this.closeDb(db);
81
140
  }
82
141
  }
83
142
  async get(cacheEntry) {
@@ -89,7 +148,7 @@ class FluidCache {
89
148
  type: cacheEntry.type,
90
149
  duration: performance.now() - startTime,
91
150
  dbOpenPerf: cachedItem === null || cachedItem === void 0 ? void 0 : cachedItem.dbOpenPerf,
92
- dbClosePerf: cachedItem === null || cachedItem === void 0 ? void 0 : cachedItem.dbClosePerf,
151
+ dbReuseCount: this.dbReuseCount,
93
152
  pkgVersion: packageVersion_1.pkgVersion,
94
153
  });
95
154
  // Value will contain metadata like the expiry time, we just want to return the object we were asked to cache
@@ -101,11 +160,11 @@ class FluidCache {
101
160
  try {
102
161
  const key = (0, FluidCacheIndexedDb_1.getKeyForCacheEntry)(cacheEntry);
103
162
  const dbOpenStartTime = performance.now();
104
- db = await (0, FluidCacheIndexedDb_1.getFluidCacheIndexedDbInstance)(this.logger);
163
+ db = await this.openDb();
105
164
  const dbOpenPerf = performance.now() - dbOpenStartTime;
106
165
  const value = await db.get(FluidCacheIndexedDb_1.FluidDriverObjectStoreName, key);
107
166
  if (!value) {
108
- db.close();
167
+ this.closeDb(db);
109
168
  return undefined;
110
169
  }
111
170
  // If the data does not come from the same partition, don't return it
@@ -115,32 +174,30 @@ class FluidCache {
115
174
  subCategory: "FluidCache" /* FluidCache */,
116
175
  pkgVersion: packageVersion_1.pkgVersion,
117
176
  });
118
- db.close();
177
+ this.closeDb(db);
119
178
  return undefined;
120
179
  }
121
180
  const currentTime = new Date().getTime();
122
181
  // If too much time has passed since this cache entry was used, we will also return undefined
123
182
  if (currentTime - value.createdTimeMs > this.maxCacheItemAge) {
124
- db.close();
183
+ this.closeDb(db);
125
184
  return undefined;
126
185
  }
127
- const dbCloseStartTime = performance.now();
128
- db.close();
129
- const dbClosePerf = performance.now() - dbCloseStartTime;
130
- return Object.assign(Object.assign({}, value), { dbOpenPerf, dbClosePerf });
186
+ this.closeDb(db);
187
+ return Object.assign(Object.assign({}, value), { dbOpenPerf });
131
188
  }
132
189
  catch (error) {
133
190
  // We can fail to open the db for a variety of reasons,
134
191
  // such as the database version having upgraded underneath us. Return undefined in this case
135
192
  this.logger.sendErrorEvent({ eventName: "FluidCacheGetError" /* FluidCacheGetError */, pkgVersion: packageVersion_1.pkgVersion }, error);
136
- db === null || db === void 0 ? void 0 : db.close();
193
+ this.closeDb(db);
137
194
  return undefined;
138
195
  }
139
196
  }
140
197
  async put(entry, value) {
141
198
  let db;
142
199
  try {
143
- db = await (0, FluidCacheIndexedDb_1.getFluidCacheIndexedDbInstance)(this.logger);
200
+ db = await this.openDb();
144
201
  const currentTime = new Date().getTime();
145
202
  await db.put(FluidCacheIndexedDb_1.FluidDriverObjectStoreName, {
146
203
  cachedObject: value,
@@ -151,7 +208,7 @@ class FluidCache {
151
208
  createdTimeMs: currentTime,
152
209
  lastAccessTimeMs: currentTime,
153
210
  }, (0, FluidCacheIndexedDb_1.getKeyForCacheEntry)(entry));
154
- db.close();
211
+ this.closeDb(db);
155
212
  }
156
213
  catch (error) {
157
214
  // We can fail to open the db for a variety of reasons,
@@ -159,7 +216,7 @@ class FluidCache {
159
216
  this.logger.sendErrorEvent({ eventName: "FluidCachePutError" /* FluidCachePutError */, pkgVersion: packageVersion_1.pkgVersion }, error);
160
217
  }
161
218
  finally {
162
- db === null || db === void 0 ? void 0 : db.close();
219
+ this.closeDb(db);
163
220
  }
164
221
  }
165
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\";\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\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\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\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\tpkgVersion,\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\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 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\tpkgVersion,\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, pkgVersion },\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, pkgVersion },\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,+DAAsD;AAGtD,qEAA8D;AAC9D,yDAAsD;AACtD,+DAK+B;AAM/B,qDAA8C;AAqC9C;;GAEG;AACH,MAAa,UAAU;IAYtB,YAAY,MAAwB;;QANnB,uBAAkB,GAAY,IAAI,CAAC;QAI5C,iBAAY,GAAW,CAAC,CAAC,CAAC;QAGjC,IAAI,CAAC,MAAM,GAAG,6BAAW,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,IAAA,mCAAgB,EAAC,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,EAAV,2BAAU;iBACV,CAAC,CAAC;aACH;QACF,CAAC,CAAC,CAAC;QAEH,IAAA,mCAAgB,EAAC,KAAK,IAAI,EAAE;YAC3B,IAAI,EAAgD,CAAC;YAErD,wEAAwE;YACxE,IAAI;gBACH,EAAE,GAAG,MAAM,IAAA,oDAA8B,EAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBAEvD,MAAM,WAAW,GAAG,EAAE,CAAC,WAAW,CAAC,gDAA0B,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,EAAV,2BAAU;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,IAAA,oDAA8B,EAAC,IAAI,CAAC,MAAM,CAAC,CAAC;SACnD;QACD,IAAI,IAAI,CAAC,EAAE,KAAK,SAAS,EAAE;YAC1B,MAAM,UAAU,GAAG,MAAM,IAAA,oDAA8B,EAAC,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,IAAA,qBAAM,EAAC,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,IAAA,qBAAM,EAAC,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,gDAA0B,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,EAAV,2BAAU;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,EAAV,2BAAU;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,IAAA,yCAAmB,EAAC,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,gDAA0B,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,EAAV,2BAAU;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,EAAV,2BAAU,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,gDAA0B,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,IAAA,yCAAmB,EAAC,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,EAAV,2BAAU,EAAE,EAClE,KAAK,CACL,CAAC;SACF;gBAAS;YACT,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;SACjB;IACF,CAAC;CACD;AAzPD,gCAyPC","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.3.0";
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.3.0";
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.3.0\";\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;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;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;IA6DvB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAyB9C,GAAG,CAAC,UAAU,EAAE,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC;YAoBzC,gBAAgB;IAmDjB,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,3 +1,8 @@
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";
@@ -7,9 +12,16 @@ import { pkgVersion } from "./packageVersion";
7
12
  */
8
13
  export class FluidCache {
9
14
  constructor(config) {
15
+ var _a;
16
+ this.closeDbImmediately = true;
17
+ this.dbReuseCount = -1;
10
18
  this.logger = ChildLogger.create(config.logger);
11
19
  this.partitionKey = config.partitionKey;
12
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
+ }
13
25
  scheduleIdleTask(async () => {
14
26
  var _a;
15
27
  // Log how much storage space is currently being used by indexed db.
@@ -57,10 +69,57 @@ export class FluidCache {
57
69
  }
58
70
  });
59
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
+ }
60
119
  async removeEntries(file) {
61
120
  let db;
62
121
  try {
63
- db = await getFluidCacheIndexedDbInstance(this.logger);
122
+ db = await this.openDb();
64
123
  const transaction = db.transaction(FluidDriverObjectStoreName, "readwrite");
65
124
  const index = transaction.store.index("fileId");
66
125
  const keysToDelete = await index.getAllKeys(file.docId);
@@ -74,7 +133,7 @@ export class FluidCache {
74
133
  }, error);
75
134
  }
76
135
  finally {
77
- db === null || db === void 0 ? void 0 : db.close();
136
+ this.closeDb(db);
78
137
  }
79
138
  }
80
139
  async get(cacheEntry) {
@@ -86,7 +145,7 @@ export class FluidCache {
86
145
  type: cacheEntry.type,
87
146
  duration: performance.now() - startTime,
88
147
  dbOpenPerf: cachedItem === null || cachedItem === void 0 ? void 0 : cachedItem.dbOpenPerf,
89
- dbClosePerf: cachedItem === null || cachedItem === void 0 ? void 0 : cachedItem.dbClosePerf,
148
+ dbReuseCount: this.dbReuseCount,
90
149
  pkgVersion,
91
150
  });
92
151
  // Value will contain metadata like the expiry time, we just want to return the object we were asked to cache
@@ -98,11 +157,11 @@ export class FluidCache {
98
157
  try {
99
158
  const key = getKeyForCacheEntry(cacheEntry);
100
159
  const dbOpenStartTime = performance.now();
101
- db = await getFluidCacheIndexedDbInstance(this.logger);
160
+ db = await this.openDb();
102
161
  const dbOpenPerf = performance.now() - dbOpenStartTime;
103
162
  const value = await db.get(FluidDriverObjectStoreName, key);
104
163
  if (!value) {
105
- db.close();
164
+ this.closeDb(db);
106
165
  return undefined;
107
166
  }
108
167
  // If the data does not come from the same partition, don't return it
@@ -112,32 +171,30 @@ export class FluidCache {
112
171
  subCategory: "FluidCache" /* FluidCache */,
113
172
  pkgVersion,
114
173
  });
115
- db.close();
174
+ this.closeDb(db);
116
175
  return undefined;
117
176
  }
118
177
  const currentTime = new Date().getTime();
119
178
  // If too much time has passed since this cache entry was used, we will also return undefined
120
179
  if (currentTime - value.createdTimeMs > this.maxCacheItemAge) {
121
- db.close();
180
+ this.closeDb(db);
122
181
  return undefined;
123
182
  }
124
- const dbCloseStartTime = performance.now();
125
- db.close();
126
- const dbClosePerf = performance.now() - dbCloseStartTime;
127
- return Object.assign(Object.assign({}, value), { dbOpenPerf, dbClosePerf });
183
+ this.closeDb(db);
184
+ return Object.assign(Object.assign({}, value), { dbOpenPerf });
128
185
  }
129
186
  catch (error) {
130
187
  // We can fail to open the db for a variety of reasons,
131
188
  // such as the database version having upgraded underneath us. Return undefined in this case
132
189
  this.logger.sendErrorEvent({ eventName: "FluidCacheGetError" /* FluidCacheGetError */, pkgVersion }, error);
133
- db === null || db === void 0 ? void 0 : db.close();
190
+ this.closeDb(db);
134
191
  return undefined;
135
192
  }
136
193
  }
137
194
  async put(entry, value) {
138
195
  let db;
139
196
  try {
140
- db = await getFluidCacheIndexedDbInstance(this.logger);
197
+ db = await this.openDb();
141
198
  const currentTime = new Date().getTime();
142
199
  await db.put(FluidDriverObjectStoreName, {
143
200
  cachedObject: value,
@@ -148,7 +205,7 @@ export class FluidCache {
148
205
  createdTimeMs: currentTime,
149
206
  lastAccessTimeMs: currentTime,
150
207
  }, getKeyForCacheEntry(entry));
151
- db.close();
208
+ this.closeDb(db);
152
209
  }
153
210
  catch (error) {
154
211
  // We can fail to open the db for a variety of reasons,
@@ -156,7 +213,7 @@ export class FluidCache {
156
213
  this.logger.sendErrorEvent({ eventName: "FluidCachePutError" /* FluidCachePutError */, pkgVersion }, error);
157
214
  }
158
215
  finally {
159
- db === null || db === void 0 ? void 0 : db.close();
216
+ this.closeDb(db);
160
217
  }
161
218
  }
162
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\";\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\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\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\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\tpkgVersion,\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\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 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\tpkgVersion,\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, pkgVersion },\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, pkgVersion },\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.3.0";
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.3.0";
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.3.0\";\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.3.0",
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.3.0 <2.0.0-internal.4.4.0",
20
- "@fluidframework/telemetry-utils": ">=2.0.0-internal.4.3.0 <2.0.0-internal.4.4.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";
@@ -47,6 +49,12 @@ export interface FluidCacheConfig {
47
49
  * If an entry exists in the cache, but is older than this value, the cached value will not be returned.
48
50
  */
49
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;
50
58
  }
51
59
 
52
60
  /**
@@ -58,11 +66,20 @@ export class FluidCache implements IPersistedCache {
58
66
  private readonly partitionKey: string | null;
59
67
 
60
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;
61
74
 
62
75
  constructor(config: FluidCacheConfig) {
63
76
  this.logger = ChildLogger.create(config.logger);
64
77
  this.partitionKey = config.partitionKey;
65
78
  this.maxCacheItemAge = config.maxCacheItemAge;
79
+ this.closeDbAfterMs = config.closeDbAfterMs ?? 0;
80
+ if (this.closeDbAfterMs > 0) {
81
+ this.closeDbImmediately = false;
82
+ }
66
83
 
67
84
  scheduleIdleTask(async () => {
68
85
  // Log how much storage space is currently being used by indexed db.
@@ -120,10 +137,56 @@ export class FluidCache implements IPersistedCache {
120
137
  });
121
138
  }
122
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
+
123
186
  public async removeEntries(file: IFileEntry): Promise<void> {
124
187
  let db: IDBPDatabase<FluidCacheDBSchema> | undefined;
125
188
  try {
126
- db = await getFluidCacheIndexedDbInstance(this.logger);
189
+ db = await this.openDb();
127
190
 
128
191
  const transaction = db.transaction(FluidDriverObjectStoreName, "readwrite");
129
192
  const index = transaction.store.index("fileId");
@@ -141,7 +204,7 @@ export class FluidCache implements IPersistedCache {
141
204
  error,
142
205
  );
143
206
  } finally {
144
- db?.close();
207
+ this.closeDb(db);
145
208
  }
146
209
  }
147
210
 
@@ -156,7 +219,7 @@ export class FluidCache implements IPersistedCache {
156
219
  type: cacheEntry.type,
157
220
  duration: performance.now() - startTime,
158
221
  dbOpenPerf: cachedItem?.dbOpenPerf,
159
- dbClosePerf: cachedItem?.dbClosePerf,
222
+ dbReuseCount: this.dbReuseCount,
160
223
  pkgVersion,
161
224
  });
162
225
 
@@ -171,12 +234,12 @@ export class FluidCache implements IPersistedCache {
171
234
  const key = getKeyForCacheEntry(cacheEntry);
172
235
 
173
236
  const dbOpenStartTime = performance.now();
174
- db = await getFluidCacheIndexedDbInstance(this.logger);
237
+ db = await this.openDb();
175
238
  const dbOpenPerf = performance.now() - dbOpenStartTime;
176
239
  const value = await db.get(FluidDriverObjectStoreName, key);
177
240
 
178
241
  if (!value) {
179
- db.close();
242
+ this.closeDb(db);
180
243
  return undefined;
181
244
  }
182
245
 
@@ -188,7 +251,7 @@ export class FluidCache implements IPersistedCache {
188
251
  pkgVersion,
189
252
  });
190
253
 
191
- db.close();
254
+ this.closeDb(db);
192
255
  return undefined;
193
256
  }
194
257
 
@@ -196,14 +259,12 @@ export class FluidCache implements IPersistedCache {
196
259
 
197
260
  // If too much time has passed since this cache entry was used, we will also return undefined
198
261
  if (currentTime - value.createdTimeMs > this.maxCacheItemAge) {
199
- db.close();
262
+ this.closeDb(db);
200
263
  return undefined;
201
264
  }
202
265
 
203
- const dbCloseStartTime = performance.now();
204
- db.close();
205
- const dbClosePerf = performance.now() - dbCloseStartTime;
206
- return { ...value, dbOpenPerf, dbClosePerf };
266
+ this.closeDb(db);
267
+ return { ...value, dbOpenPerf };
207
268
  } catch (error: any) {
208
269
  // We can fail to open the db for a variety of reasons,
209
270
  // such as the database version having upgraded underneath us. Return undefined in this case
@@ -211,7 +272,7 @@ export class FluidCache implements IPersistedCache {
211
272
  { eventName: FluidCacheErrorEvent.FluidCacheGetError, pkgVersion },
212
273
  error,
213
274
  );
214
- db?.close();
275
+ this.closeDb(db);
215
276
  return undefined;
216
277
  }
217
278
  }
@@ -219,7 +280,7 @@ export class FluidCache implements IPersistedCache {
219
280
  public async put(entry: ICacheEntry, value: any): Promise<void> {
220
281
  let db: IDBPDatabase<FluidCacheDBSchema> | undefined;
221
282
  try {
222
- db = await getFluidCacheIndexedDbInstance(this.logger);
283
+ db = await this.openDb();
223
284
 
224
285
  const currentTime = new Date().getTime();
225
286
 
@@ -236,8 +297,7 @@ export class FluidCache implements IPersistedCache {
236
297
  },
237
298
  getKeyForCacheEntry(entry),
238
299
  );
239
-
240
- db.close();
300
+ this.closeDb(db);
241
301
  } catch (error: any) {
242
302
  // We can fail to open the db for a variety of reasons,
243
303
  // such as the database version having upgraded underneath us
@@ -246,7 +306,7 @@ export class FluidCache implements IPersistedCache {
246
306
  error,
247
307
  );
248
308
  } finally {
249
- db?.close();
309
+ this.closeDb(db);
250
310
  }
251
311
  }
252
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.3.0";
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;