@fluidframework/driver-web-cache 2.0.0-internal.4.2.1 → 2.0.0-internal.4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/dist/FluidCache.d.ts +16 -0
- package/dist/FluidCache.d.ts.map +1 -1
- package/dist/FluidCache.js +80 -17
- package/dist/FluidCache.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/scheduleIdleTask.js +6 -6
- package/dist/scheduleIdleTask.js.map +1 -1
- package/lib/FluidCache.d.ts +16 -0
- package/lib/FluidCache.d.ts.map +1 -1
- package/lib/FluidCache.js +80 -17
- package/lib/FluidCache.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/scheduleIdleTask.js +6 -6
- package/lib/scheduleIdleTask.js.map +1 -1
- package/package.json +4 -3
- package/src/FluidCache.ts +84 -18
- package/src/packageVersion.ts +1 -1
- package/src/scheduleIdleTask.ts +6 -6
package/CHANGELOG.md
CHANGED
package/dist/FluidCache.d.ts
CHANGED
|
@@ -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;
|
package/dist/FluidCache.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FluidCache.d.ts","sourceRoot":"","sources":["../src/FluidCache.ts"],"names":[],"mappings":"
|
|
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/dist/FluidCache.js
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/*!
|
|
3
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
*/
|
|
2
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
7
|
exports.FluidCache = void 0;
|
|
8
|
+
const common_utils_1 = require("@fluidframework/common-utils");
|
|
4
9
|
const telemetry_utils_1 = require("@fluidframework/telemetry-utils");
|
|
5
10
|
const scheduleIdleTask_1 = require("./scheduleIdleTask");
|
|
6
11
|
const FluidCacheIndexedDb_1 = require("./FluidCacheIndexedDb");
|
|
12
|
+
const packageVersion_1 = require("./packageVersion");
|
|
7
13
|
/**
|
|
8
14
|
* A cache that can be used by the Fluid ODSP driver to cache data for faster performance
|
|
9
15
|
*/
|
|
10
16
|
class FluidCache {
|
|
11
17
|
constructor(config) {
|
|
18
|
+
var _a;
|
|
19
|
+
this.closeDbImmediately = true;
|
|
20
|
+
this.dbReuseCount = -1;
|
|
12
21
|
this.logger = telemetry_utils_1.ChildLogger.create(config.logger);
|
|
13
22
|
this.partitionKey = config.partitionKey;
|
|
14
23
|
this.maxCacheItemAge = config.maxCacheItemAge;
|
|
24
|
+
this.closeDbAfterMs = (_a = config.closeDbAfterMs) !== null && _a !== void 0 ? _a : 0;
|
|
25
|
+
if (this.closeDbAfterMs > 0) {
|
|
26
|
+
this.closeDbImmediately = false;
|
|
27
|
+
}
|
|
15
28
|
(0, scheduleIdleTask_1.scheduleIdleTask)(async () => {
|
|
16
29
|
var _a;
|
|
17
30
|
// Log how much storage space is currently being used by indexed db.
|
|
@@ -32,6 +45,7 @@ class FluidCache {
|
|
|
32
45
|
quota: estimate.quota,
|
|
33
46
|
usage: estimate.usage,
|
|
34
47
|
indexedDBSize,
|
|
48
|
+
pkgVersion: packageVersion_1.pkgVersion,
|
|
35
49
|
});
|
|
36
50
|
}
|
|
37
51
|
});
|
|
@@ -50,6 +64,7 @@ class FluidCache {
|
|
|
50
64
|
catch (error) {
|
|
51
65
|
this.logger.sendErrorEvent({
|
|
52
66
|
eventName: "FluidCacheDeleteOldEntriesError" /* FluidCacheDeleteOldEntriesError */,
|
|
67
|
+
pkgVersion: packageVersion_1.pkgVersion,
|
|
53
68
|
}, error);
|
|
54
69
|
}
|
|
55
70
|
finally {
|
|
@@ -57,10 +72,57 @@ class FluidCache {
|
|
|
57
72
|
}
|
|
58
73
|
});
|
|
59
74
|
}
|
|
75
|
+
async openDb() {
|
|
76
|
+
if (this.closeDbImmediately) {
|
|
77
|
+
return (0, FluidCacheIndexedDb_1.getFluidCacheIndexedDbInstance)(this.logger);
|
|
78
|
+
}
|
|
79
|
+
if (this.db === undefined) {
|
|
80
|
+
const dbInstance = await (0, FluidCacheIndexedDb_1.getFluidCacheIndexedDbInstance)(this.logger);
|
|
81
|
+
if (this.db === undefined) {
|
|
82
|
+
// Reset the counter on first open.
|
|
83
|
+
this.dbReuseCount = -1;
|
|
84
|
+
this.db = dbInstance;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
dbInstance.close();
|
|
88
|
+
this.dbReuseCount += 1;
|
|
89
|
+
return this.db;
|
|
90
|
+
}
|
|
91
|
+
// Need to close the db on version change if opened.
|
|
92
|
+
this.db.onversionchange = (ev) => {
|
|
93
|
+
var _a;
|
|
94
|
+
(_a = this.db) === null || _a === void 0 ? void 0 : _a.close();
|
|
95
|
+
this.db = undefined;
|
|
96
|
+
clearTimeout(this.dbCloseTimer);
|
|
97
|
+
this.dbCloseTimer = undefined;
|
|
98
|
+
};
|
|
99
|
+
this.db.addEventListener("close", (ev) => {
|
|
100
|
+
clearTimeout(this.dbCloseTimer);
|
|
101
|
+
this.dbCloseTimer = undefined;
|
|
102
|
+
this.db = undefined;
|
|
103
|
+
});
|
|
104
|
+
// Schedule db close after this.closeDbAfterMs.
|
|
105
|
+
(0, common_utils_1.assert)(this.dbCloseTimer === undefined, 0x6c6 /* timer should not be set yet!! */);
|
|
106
|
+
this.dbCloseTimer = setTimeout(() => {
|
|
107
|
+
var _a;
|
|
108
|
+
(_a = this.db) === null || _a === void 0 ? void 0 : _a.close();
|
|
109
|
+
this.db = undefined;
|
|
110
|
+
this.dbCloseTimer = undefined;
|
|
111
|
+
}, this.closeDbAfterMs);
|
|
112
|
+
}
|
|
113
|
+
(0, common_utils_1.assert)(this.db !== undefined, 0x6c7 /* db should be intialized by now */);
|
|
114
|
+
this.dbReuseCount += 1;
|
|
115
|
+
return this.db;
|
|
116
|
+
}
|
|
117
|
+
closeDb(db) {
|
|
118
|
+
if (this.closeDbImmediately) {
|
|
119
|
+
db === null || db === void 0 ? void 0 : db.close();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
60
122
|
async removeEntries(file) {
|
|
61
123
|
let db;
|
|
62
124
|
try {
|
|
63
|
-
db = await
|
|
125
|
+
db = await this.openDb();
|
|
64
126
|
const transaction = db.transaction(FluidCacheIndexedDb_1.FluidDriverObjectStoreName, "readwrite");
|
|
65
127
|
const index = transaction.store.index("fileId");
|
|
66
128
|
const keysToDelete = await index.getAllKeys(file.docId);
|
|
@@ -70,10 +132,11 @@ class FluidCache {
|
|
|
70
132
|
catch (error) {
|
|
71
133
|
this.logger.sendErrorEvent({
|
|
72
134
|
eventName: "FluidCacheDeleteOldEntriesError" /* FluidCacheDeleteOldEntriesError */,
|
|
135
|
+
pkgVersion: packageVersion_1.pkgVersion,
|
|
73
136
|
}, error);
|
|
74
137
|
}
|
|
75
138
|
finally {
|
|
76
|
-
|
|
139
|
+
this.closeDb(db);
|
|
77
140
|
}
|
|
78
141
|
}
|
|
79
142
|
async get(cacheEntry) {
|
|
@@ -85,7 +148,8 @@ class FluidCache {
|
|
|
85
148
|
type: cacheEntry.type,
|
|
86
149
|
duration: performance.now() - startTime,
|
|
87
150
|
dbOpenPerf: cachedItem === null || cachedItem === void 0 ? void 0 : cachedItem.dbOpenPerf,
|
|
88
|
-
|
|
151
|
+
dbReuseCount: this.dbReuseCount,
|
|
152
|
+
pkgVersion: packageVersion_1.pkgVersion,
|
|
89
153
|
});
|
|
90
154
|
// Value will contain metadata like the expiry time, we just want to return the object we were asked to cache
|
|
91
155
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
@@ -96,11 +160,11 @@ class FluidCache {
|
|
|
96
160
|
try {
|
|
97
161
|
const key = (0, FluidCacheIndexedDb_1.getKeyForCacheEntry)(cacheEntry);
|
|
98
162
|
const dbOpenStartTime = performance.now();
|
|
99
|
-
db = await
|
|
163
|
+
db = await this.openDb();
|
|
100
164
|
const dbOpenPerf = performance.now() - dbOpenStartTime;
|
|
101
165
|
const value = await db.get(FluidCacheIndexedDb_1.FluidDriverObjectStoreName, key);
|
|
102
166
|
if (!value) {
|
|
103
|
-
|
|
167
|
+
this.closeDb(db);
|
|
104
168
|
return undefined;
|
|
105
169
|
}
|
|
106
170
|
// If the data does not come from the same partition, don't return it
|
|
@@ -108,33 +172,32 @@ class FluidCache {
|
|
|
108
172
|
this.logger.sendTelemetryEvent({
|
|
109
173
|
eventName: "FluidCachePartitionKeyMismatch" /* FluidCachePartitionKeyMismatch */,
|
|
110
174
|
subCategory: "FluidCache" /* FluidCache */,
|
|
175
|
+
pkgVersion: packageVersion_1.pkgVersion,
|
|
111
176
|
});
|
|
112
|
-
|
|
177
|
+
this.closeDb(db);
|
|
113
178
|
return undefined;
|
|
114
179
|
}
|
|
115
180
|
const currentTime = new Date().getTime();
|
|
116
181
|
// If too much time has passed since this cache entry was used, we will also return undefined
|
|
117
182
|
if (currentTime - value.createdTimeMs > this.maxCacheItemAge) {
|
|
118
|
-
|
|
183
|
+
this.closeDb(db);
|
|
119
184
|
return undefined;
|
|
120
185
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const dbClosePerf = performance.now() - dbCloseStartTime;
|
|
124
|
-
return Object.assign(Object.assign({}, value), { dbOpenPerf, dbClosePerf });
|
|
186
|
+
this.closeDb(db);
|
|
187
|
+
return Object.assign(Object.assign({}, value), { dbOpenPerf });
|
|
125
188
|
}
|
|
126
189
|
catch (error) {
|
|
127
190
|
// We can fail to open the db for a variety of reasons,
|
|
128
191
|
// such as the database version having upgraded underneath us. Return undefined in this case
|
|
129
|
-
this.logger.sendErrorEvent({ eventName: "FluidCacheGetError" /* FluidCacheGetError
|
|
130
|
-
|
|
192
|
+
this.logger.sendErrorEvent({ eventName: "FluidCacheGetError" /* FluidCacheGetError */, pkgVersion: packageVersion_1.pkgVersion }, error);
|
|
193
|
+
this.closeDb(db);
|
|
131
194
|
return undefined;
|
|
132
195
|
}
|
|
133
196
|
}
|
|
134
197
|
async put(entry, value) {
|
|
135
198
|
let db;
|
|
136
199
|
try {
|
|
137
|
-
db = await
|
|
200
|
+
db = await this.openDb();
|
|
138
201
|
const currentTime = new Date().getTime();
|
|
139
202
|
await db.put(FluidCacheIndexedDb_1.FluidDriverObjectStoreName, {
|
|
140
203
|
cachedObject: value,
|
|
@@ -145,15 +208,15 @@ class FluidCache {
|
|
|
145
208
|
createdTimeMs: currentTime,
|
|
146
209
|
lastAccessTimeMs: currentTime,
|
|
147
210
|
}, (0, FluidCacheIndexedDb_1.getKeyForCacheEntry)(entry));
|
|
148
|
-
|
|
211
|
+
this.closeDb(db);
|
|
149
212
|
}
|
|
150
213
|
catch (error) {
|
|
151
214
|
// We can fail to open the db for a variety of reasons,
|
|
152
215
|
// such as the database version having upgraded underneath us
|
|
153
|
-
this.logger.sendErrorEvent({ eventName: "FluidCachePutError" /* FluidCachePutError
|
|
216
|
+
this.logger.sendErrorEvent({ eventName: "FluidCachePutError" /* FluidCachePutError */, pkgVersion: packageVersion_1.pkgVersion }, error);
|
|
154
217
|
}
|
|
155
218
|
finally {
|
|
156
|
-
|
|
219
|
+
this.closeDb(db);
|
|
157
220
|
}
|
|
158
221
|
}
|
|
159
222
|
}
|
package/dist/FluidCache.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FluidCache.js","sourceRoot":"","sources":["../src/FluidCache.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\nimport { IDBPDatabase } from \"idb\";\nimport { IPersistedCache, ICacheEntry, IFileEntry } from \"@fluidframework/odsp-driver-definitions\";\nimport { ITelemetryBaseLogger, ITelemetryLogger } from \"@fluidframework/common-definitions\";\nimport { ChildLogger } from \"@fluidframework/telemetry-utils\";\nimport { scheduleIdleTask } from \"./scheduleIdleTask\";\nimport {\n\tgetFluidCacheIndexedDbInstance,\n\tFluidCacheDBSchema,\n\tFluidDriverObjectStoreName,\n\tgetKeyForCacheEntry,\n} from \"./FluidCacheIndexedDb\";\nimport {\n\tFluidCacheErrorEvent,\n\tFluidCacheEventSubCategories,\n\tFluidCacheGenericEvent,\n} from \"./fluidCacheTelemetry\";\n\n// Some browsers have a usageDetails property that will tell you more detailed information\n// on how the storage is being used\ninterface StorageQuotaUsageDetails {\n\tindexedDB: number | undefined;\n}\n\nexport interface FluidCacheConfig {\n\t/**\n\t * A string to specify what partition of the cache you wish to use (e.g. a user id).\n\t * Null can be used to explicity indicate no partitioning, and has been chosen\n\t * vs undefined so that it is clear this is an intentional choice by the caller.\n\t * A null value should only be used when the host can ensure that the cache is not able\n\t * to be shared with multiple users.\n\t */\n\t// eslint-disable-next-line @rushstack/no-new-null\n\tpartitionKey: string | null;\n\n\t/**\n\t * A logger that can be used to get insight into cache performance and errors\n\t */\n\tlogger?: ITelemetryBaseLogger;\n\n\t/**\n\t * A value in milliseconds that determines the maximum age of a cache entry to return.\n\t * If an entry exists in the cache, but is older than this value, the cached value will not be returned.\n\t */\n\tmaxCacheItemAge: number;\n}\n\n/**\n * A cache that can be used by the Fluid ODSP driver to cache data for faster performance\n */\nexport class FluidCache implements IPersistedCache {\n\tprivate readonly logger: ITelemetryLogger;\n\n\tprivate readonly partitionKey: string | null;\n\n\tprivate readonly maxCacheItemAge: number;\n\n\tconstructor(config: FluidCacheConfig) {\n\t\tthis.logger = ChildLogger.create(config.logger);\n\t\tthis.partitionKey = config.partitionKey;\n\t\tthis.maxCacheItemAge = config.maxCacheItemAge;\n\n\t\tscheduleIdleTask(async () => {\n\t\t\t// Log how much storage space is currently being used by indexed db.\n\t\t\t// NOTE: This API is not supported in all browsers and it doesn't let you see the size of a specific DB.\n\t\t\t// Exception added when eslint rule was added, this should be revisited when modifying this code\n\t\t\tif (navigator.storage?.estimate) {\n\t\t\t\tconst estimate = await navigator.storage.estimate();\n\n\t\t\t\t// Some browsers have a usageDetails property that will tell you\n\t\t\t\t// more detailed information on how the storage is being used\n\t\t\t\tlet indexedDBSize: number | undefined;\n\t\t\t\tif (\"usageDetails\" in estimate) {\n\t\t\t\t\tindexedDBSize = ((estimate as any).usageDetails as StorageQuotaUsageDetails)\n\t\t\t\t\t\t.indexedDB;\n\t\t\t\t}\n\n\t\t\t\tthis.logger.sendTelemetryEvent({\n\t\t\t\t\teventName: FluidCacheGenericEvent.FluidCacheStorageInfo,\n\t\t\t\t\tsubCategory: FluidCacheEventSubCategories.FluidCache,\n\t\t\t\t\tquota: estimate.quota,\n\t\t\t\t\tusage: estimate.usage,\n\t\t\t\t\tindexedDBSize,\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\tscheduleIdleTask(async () => {\n\t\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\n\t\t\t// Delete entries that have not been accessed recently to clean up space\n\t\t\ttry {\n\t\t\t\tdb = await getFluidCacheIndexedDbInstance(this.logger);\n\n\t\t\t\tconst transaction = db.transaction(FluidDriverObjectStoreName, \"readwrite\");\n\t\t\t\tconst index = transaction.store.index(\"createdTimeMs\");\n\t\t\t\t// Get items which were cached before the maxCacheItemAge.\n\t\t\t\tconst keysToDelete = await index.getAllKeys(\n\t\t\t\t\tIDBKeyRange.upperBound(new Date().getTime() - this.maxCacheItemAge),\n\t\t\t\t);\n\n\t\t\t\tawait Promise.all(keysToDelete.map((key) => transaction.store.delete(key)));\n\t\t\t\tawait transaction.done;\n\t\t\t} catch (error: any) {\n\t\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: FluidCacheErrorEvent.FluidCacheDeleteOldEntriesError,\n\t\t\t\t\t},\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t} finally {\n\t\t\t\tdb?.close();\n\t\t\t}\n\t\t});\n\t}\n\n\tpublic async removeEntries(file: IFileEntry): Promise<void> {\n\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\t\ttry {\n\t\t\tdb = await getFluidCacheIndexedDbInstance(this.logger);\n\n\t\t\tconst transaction = db.transaction(FluidDriverObjectStoreName, \"readwrite\");\n\t\t\tconst index = transaction.store.index(\"fileId\");\n\n\t\t\tconst keysToDelete = await index.getAllKeys(file.docId);\n\n\t\t\tawait Promise.all(keysToDelete.map((key) => transaction.store.delete(key)));\n\t\t\tawait transaction.done;\n\t\t} catch (error: any) {\n\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t{\n\t\t\t\t\teventName: FluidCacheErrorEvent.FluidCacheDeleteOldEntriesError,\n\t\t\t\t},\n\t\t\t\terror,\n\t\t\t);\n\t\t} finally {\n\t\t\tdb?.close();\n\t\t}\n\t}\n\n\tpublic async get(cacheEntry: ICacheEntry): Promise<any> {\n\t\tconst startTime = performance.now();\n\n\t\tconst cachedItem = await this.getItemFromCache(cacheEntry);\n\n\t\tthis.logger.sendPerformanceEvent({\n\t\t\teventName: \"FluidCacheAccess\",\n\t\t\tcacheHit: cachedItem !== undefined,\n\t\t\ttype: cacheEntry.type,\n\t\t\tduration: performance.now() - startTime,\n\t\t\tdbOpenPerf: cachedItem?.dbOpenPerf,\n\t\t\tdbClosePerf: cachedItem?.dbClosePerf,\n\t\t});\n\n\t\t// Value will contain metadata like the expiry time, we just want to return the object we were asked to cache\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-return\n\t\treturn cachedItem?.cachedObject;\n\t}\n\n\tprivate async getItemFromCache(cacheEntry: ICacheEntry) {\n\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\t\ttry {\n\t\t\tconst key = getKeyForCacheEntry(cacheEntry);\n\n\t\t\tconst dbOpenStartTime = performance.now();\n\t\t\tdb = await getFluidCacheIndexedDbInstance(this.logger);\n\t\t\tconst dbOpenPerf = performance.now() - dbOpenStartTime;\n\t\t\tconst value = await db.get(FluidDriverObjectStoreName, key);\n\n\t\t\tif (!value) {\n\t\t\t\tdb.close();\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\t// If the data does not come from the same partition, don't return it\n\t\t\tif (value.partitionKey !== this.partitionKey) {\n\t\t\t\tthis.logger.sendTelemetryEvent({\n\t\t\t\t\teventName: FluidCacheGenericEvent.FluidCachePartitionKeyMismatch,\n\t\t\t\t\tsubCategory: FluidCacheEventSubCategories.FluidCache,\n\t\t\t\t});\n\n\t\t\t\tdb.close();\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\tconst currentTime = new Date().getTime();\n\n\t\t\t// If too much time has passed since this cache entry was used, we will also return undefined\n\t\t\tif (currentTime - value.createdTimeMs > this.maxCacheItemAge) {\n\t\t\t\tdb.close();\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\tconst dbCloseStartTime = performance.now();\n\t\t\tdb.close();\n\t\t\tconst dbClosePerf = performance.now() - dbCloseStartTime;\n\t\t\treturn { ...value, dbOpenPerf, dbClosePerf };\n\t\t} catch (error: any) {\n\t\t\t// We can fail to open the db for a variety of reasons,\n\t\t\t// such as the database version having upgraded underneath us. Return undefined in this case\n\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t{ eventName: FluidCacheErrorEvent.FluidCacheGetError },\n\t\t\t\terror,\n\t\t\t);\n\t\t\tdb?.close();\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tpublic async put(entry: ICacheEntry, value: any): Promise<void> {\n\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\t\ttry {\n\t\t\tdb = await getFluidCacheIndexedDbInstance(this.logger);\n\n\t\t\tconst currentTime = new Date().getTime();\n\n\t\t\tawait db.put(\n\t\t\t\tFluidDriverObjectStoreName,\n\t\t\t\t{\n\t\t\t\t\tcachedObject: value,\n\t\t\t\t\tfileId: entry.file.docId,\n\t\t\t\t\ttype: entry.type,\n\t\t\t\t\tcacheItemId: entry.key,\n\t\t\t\t\tpartitionKey: this.partitionKey,\n\t\t\t\t\tcreatedTimeMs: currentTime,\n\t\t\t\t\tlastAccessTimeMs: currentTime,\n\t\t\t\t},\n\t\t\t\tgetKeyForCacheEntry(entry),\n\t\t\t);\n\n\t\t\tdb.close();\n\t\t} catch (error: any) {\n\t\t\t// We can fail to open the db for a variety of reasons,\n\t\t\t// such as the database version having upgraded underneath us\n\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t{ eventName: FluidCacheErrorEvent.FluidCachePutError },\n\t\t\t\terror,\n\t\t\t);\n\t\t} finally {\n\t\t\tdb?.close();\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"FluidCache.js","sourceRoot":"","sources":["../src/FluidCache.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { IDBPDatabase } from \"idb\";\nimport { assert } from \"@fluidframework/common-utils\";\nimport { IPersistedCache, ICacheEntry, IFileEntry } from \"@fluidframework/odsp-driver-definitions\";\nimport { ITelemetryBaseLogger, ITelemetryLogger } from \"@fluidframework/common-definitions\";\nimport { ChildLogger } from \"@fluidframework/telemetry-utils\";\nimport { scheduleIdleTask } from \"./scheduleIdleTask\";\nimport {\n\tgetFluidCacheIndexedDbInstance,\n\tFluidCacheDBSchema,\n\tFluidDriverObjectStoreName,\n\tgetKeyForCacheEntry,\n} from \"./FluidCacheIndexedDb\";\nimport {\n\tFluidCacheErrorEvent,\n\tFluidCacheEventSubCategories,\n\tFluidCacheGenericEvent,\n} from \"./fluidCacheTelemetry\";\nimport { pkgVersion } from \"./packageVersion\";\n\n// Some browsers have a usageDetails property that will tell you more detailed information\n// on how the storage is being used\ninterface StorageQuotaUsageDetails {\n\tindexedDB: number | undefined;\n}\n\nexport interface FluidCacheConfig {\n\t/**\n\t * A string to specify what partition of the cache you wish to use (e.g. a user id).\n\t * Null can be used to explicity indicate no partitioning, and has been chosen\n\t * vs undefined so that it is clear this is an intentional choice by the caller.\n\t * A null value should only be used when the host can ensure that the cache is not able\n\t * to be shared with multiple users.\n\t */\n\t// eslint-disable-next-line @rushstack/no-new-null\n\tpartitionKey: string | null;\n\n\t/**\n\t * A logger that can be used to get insight into cache performance and errors\n\t */\n\tlogger?: ITelemetryBaseLogger;\n\n\t/**\n\t * A value in milliseconds that determines the maximum age of a cache entry to return.\n\t * If an entry exists in the cache, but is older than this value, the cached value will not be returned.\n\t */\n\tmaxCacheItemAge: number;\n\n\t/**\n\t * Each time db is opened, it will remain open for this much time. To improve perf, if this property is set as\n\t * any number greater than 0, then db will not be closed immediately after usage. This value is in milliseconds.\n\t */\n\tcloseDbAfterMs?: number;\n}\n\n/**\n * A cache that can be used by the Fluid ODSP driver to cache data for faster performance\n */\nexport class FluidCache implements IPersistedCache {\n\tprivate readonly logger: ITelemetryLogger;\n\n\tprivate readonly partitionKey: string | null;\n\n\tprivate readonly maxCacheItemAge: number;\n\tprivate readonly closeDbImmediately: boolean = true;\n\tprivate readonly closeDbAfterMs: number;\n\tprivate db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\tprivate dbCloseTimer: ReturnType<typeof setTimeout> | undefined;\n\tprivate dbReuseCount: number = -1;\n\n\tconstructor(config: FluidCacheConfig) {\n\t\tthis.logger = ChildLogger.create(config.logger);\n\t\tthis.partitionKey = config.partitionKey;\n\t\tthis.maxCacheItemAge = config.maxCacheItemAge;\n\t\tthis.closeDbAfterMs = config.closeDbAfterMs ?? 0;\n\t\tif (this.closeDbAfterMs > 0) {\n\t\t\tthis.closeDbImmediately = false;\n\t\t}\n\n\t\tscheduleIdleTask(async () => {\n\t\t\t// Log how much storage space is currently being used by indexed db.\n\t\t\t// NOTE: This API is not supported in all browsers and it doesn't let you see the size of a specific DB.\n\t\t\t// Exception added when eslint rule was added, this should be revisited when modifying this code\n\t\t\tif (navigator.storage?.estimate) {\n\t\t\t\tconst estimate = await navigator.storage.estimate();\n\n\t\t\t\t// Some browsers have a usageDetails property that will tell you\n\t\t\t\t// more detailed information on how the storage is being used\n\t\t\t\tlet indexedDBSize: number | undefined;\n\t\t\t\tif (\"usageDetails\" in estimate) {\n\t\t\t\t\tindexedDBSize = ((estimate as any).usageDetails as StorageQuotaUsageDetails)\n\t\t\t\t\t\t.indexedDB;\n\t\t\t\t}\n\n\t\t\t\tthis.logger.sendTelemetryEvent({\n\t\t\t\t\teventName: FluidCacheGenericEvent.FluidCacheStorageInfo,\n\t\t\t\t\tsubCategory: FluidCacheEventSubCategories.FluidCache,\n\t\t\t\t\tquota: estimate.quota,\n\t\t\t\t\tusage: estimate.usage,\n\t\t\t\t\tindexedDBSize,\n\t\t\t\t\tpkgVersion,\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\tscheduleIdleTask(async () => {\n\t\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\n\t\t\t// Delete entries that have not been accessed recently to clean up space\n\t\t\ttry {\n\t\t\t\tdb = await getFluidCacheIndexedDbInstance(this.logger);\n\n\t\t\t\tconst transaction = db.transaction(FluidDriverObjectStoreName, \"readwrite\");\n\t\t\t\tconst index = transaction.store.index(\"createdTimeMs\");\n\t\t\t\t// Get items which were cached before the maxCacheItemAge.\n\t\t\t\tconst keysToDelete = await index.getAllKeys(\n\t\t\t\t\tIDBKeyRange.upperBound(new Date().getTime() - this.maxCacheItemAge),\n\t\t\t\t);\n\n\t\t\t\tawait Promise.all(keysToDelete.map((key) => transaction.store.delete(key)));\n\t\t\t\tawait transaction.done;\n\t\t\t} catch (error: any) {\n\t\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: FluidCacheErrorEvent.FluidCacheDeleteOldEntriesError,\n\t\t\t\t\t\tpkgVersion,\n\t\t\t\t\t},\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t} finally {\n\t\t\t\tdb?.close();\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate async openDb() {\n\t\tif (this.closeDbImmediately) {\n\t\t\treturn getFluidCacheIndexedDbInstance(this.logger);\n\t\t}\n\t\tif (this.db === undefined) {\n\t\t\tconst dbInstance = await getFluidCacheIndexedDbInstance(this.logger);\n\t\t\tif (this.db === undefined) {\n\t\t\t\t// Reset the counter on first open.\n\t\t\t\tthis.dbReuseCount = -1;\n\t\t\t\tthis.db = dbInstance;\n\t\t\t} else {\n\t\t\t\tdbInstance.close();\n\t\t\t\tthis.dbReuseCount += 1;\n\t\t\t\treturn this.db;\n\t\t\t}\n\t\t\t// Need to close the db on version change if opened.\n\t\t\tthis.db.onversionchange = (ev) => {\n\t\t\t\tthis.db?.close();\n\t\t\t\tthis.db = undefined;\n\t\t\t\tclearTimeout(this.dbCloseTimer);\n\t\t\t\tthis.dbCloseTimer = undefined;\n\t\t\t};\n\t\t\tthis.db.addEventListener(\"close\", (ev) => {\n\t\t\t\tclearTimeout(this.dbCloseTimer);\n\t\t\t\tthis.dbCloseTimer = undefined;\n\t\t\t\tthis.db = undefined;\n\t\t\t});\n\t\t\t// Schedule db close after this.closeDbAfterMs.\n\t\t\tassert(this.dbCloseTimer === undefined, 0x6c6 /* timer should not be set yet!! */);\n\t\t\tthis.dbCloseTimer = setTimeout(() => {\n\t\t\t\tthis.db?.close();\n\t\t\t\tthis.db = undefined;\n\t\t\t\tthis.dbCloseTimer = undefined;\n\t\t\t}, this.closeDbAfterMs);\n\t\t}\n\t\tassert(this.db !== undefined, 0x6c7 /* db should be intialized by now */);\n\t\tthis.dbReuseCount += 1;\n\t\treturn this.db;\n\t}\n\n\tprivate closeDb(db?: IDBPDatabase<FluidCacheDBSchema>) {\n\t\tif (this.closeDbImmediately) {\n\t\t\tdb?.close();\n\t\t}\n\t}\n\n\tpublic async removeEntries(file: IFileEntry): Promise<void> {\n\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\t\ttry {\n\t\t\tdb = await this.openDb();\n\n\t\t\tconst transaction = db.transaction(FluidDriverObjectStoreName, \"readwrite\");\n\t\t\tconst index = transaction.store.index(\"fileId\");\n\n\t\t\tconst keysToDelete = await index.getAllKeys(file.docId);\n\n\t\t\tawait Promise.all(keysToDelete.map((key) => transaction.store.delete(key)));\n\t\t\tawait transaction.done;\n\t\t} catch (error: any) {\n\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t{\n\t\t\t\t\teventName: FluidCacheErrorEvent.FluidCacheDeleteOldEntriesError,\n\t\t\t\t\tpkgVersion,\n\t\t\t\t},\n\t\t\t\terror,\n\t\t\t);\n\t\t} finally {\n\t\t\tthis.closeDb(db);\n\t\t}\n\t}\n\n\tpublic async get(cacheEntry: ICacheEntry): Promise<any> {\n\t\tconst startTime = performance.now();\n\n\t\tconst cachedItem = await this.getItemFromCache(cacheEntry);\n\n\t\tthis.logger.sendPerformanceEvent({\n\t\t\teventName: \"FluidCacheAccess\",\n\t\t\tcacheHit: cachedItem !== undefined,\n\t\t\ttype: cacheEntry.type,\n\t\t\tduration: performance.now() - startTime,\n\t\t\tdbOpenPerf: cachedItem?.dbOpenPerf,\n\t\t\tdbReuseCount: this.dbReuseCount,\n\t\t\tpkgVersion,\n\t\t});\n\n\t\t// Value will contain metadata like the expiry time, we just want to return the object we were asked to cache\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-return\n\t\treturn cachedItem?.cachedObject;\n\t}\n\n\tprivate async getItemFromCache(cacheEntry: ICacheEntry) {\n\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\t\ttry {\n\t\t\tconst key = getKeyForCacheEntry(cacheEntry);\n\n\t\t\tconst dbOpenStartTime = performance.now();\n\t\t\tdb = await this.openDb();\n\t\t\tconst dbOpenPerf = performance.now() - dbOpenStartTime;\n\t\t\tconst value = await db.get(FluidDriverObjectStoreName, key);\n\n\t\t\tif (!value) {\n\t\t\t\tthis.closeDb(db);\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\t// If the data does not come from the same partition, don't return it\n\t\t\tif (value.partitionKey !== this.partitionKey) {\n\t\t\t\tthis.logger.sendTelemetryEvent({\n\t\t\t\t\teventName: FluidCacheGenericEvent.FluidCachePartitionKeyMismatch,\n\t\t\t\t\tsubCategory: FluidCacheEventSubCategories.FluidCache,\n\t\t\t\t\tpkgVersion,\n\t\t\t\t});\n\n\t\t\t\tthis.closeDb(db);\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\tconst currentTime = new Date().getTime();\n\n\t\t\t// If too much time has passed since this cache entry was used, we will also return undefined\n\t\t\tif (currentTime - value.createdTimeMs > this.maxCacheItemAge) {\n\t\t\t\tthis.closeDb(db);\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\tthis.closeDb(db);\n\t\t\treturn { ...value, dbOpenPerf };\n\t\t} catch (error: any) {\n\t\t\t// We can fail to open the db for a variety of reasons,\n\t\t\t// such as the database version having upgraded underneath us. Return undefined in this case\n\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t{ eventName: FluidCacheErrorEvent.FluidCacheGetError, pkgVersion },\n\t\t\t\terror,\n\t\t\t);\n\t\t\tthis.closeDb(db);\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tpublic async put(entry: ICacheEntry, value: any): Promise<void> {\n\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\t\ttry {\n\t\t\tdb = await this.openDb();\n\n\t\t\tconst currentTime = new Date().getTime();\n\n\t\t\tawait db.put(\n\t\t\t\tFluidDriverObjectStoreName,\n\t\t\t\t{\n\t\t\t\t\tcachedObject: value,\n\t\t\t\t\tfileId: entry.file.docId,\n\t\t\t\t\ttype: entry.type,\n\t\t\t\t\tcacheItemId: entry.key,\n\t\t\t\t\tpartitionKey: this.partitionKey,\n\t\t\t\t\tcreatedTimeMs: currentTime,\n\t\t\t\t\tlastAccessTimeMs: currentTime,\n\t\t\t\t},\n\t\t\t\tgetKeyForCacheEntry(entry),\n\t\t\t);\n\t\t\tthis.closeDb(db);\n\t\t} catch (error: any) {\n\t\t\t// We can fail to open the db for a variety of reasons,\n\t\t\t// such as the database version having upgraded underneath us\n\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t{ eventName: FluidCacheErrorEvent.FluidCachePutError, pkgVersion },\n\t\t\t\terror,\n\t\t\t);\n\t\t} finally {\n\t\t\tthis.closeDb(db);\n\t\t}\n\t}\n}\n"]}
|
package/dist/packageVersion.d.ts
CHANGED
|
@@ -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.
|
|
8
|
+
export declare const pkgVersion = "2.0.0-internal.4.4.0";
|
|
9
9
|
//# sourceMappingURL=packageVersion.d.ts.map
|
package/dist/packageVersion.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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"]}
|
package/dist/scheduleIdleTask.js
CHANGED
|
@@ -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 (
|
|
31
|
-
|
|
30
|
+
if (self.requestIdleCallback) {
|
|
31
|
+
self.requestIdleCallback(idleTaskCallback);
|
|
32
32
|
}
|
|
33
33
|
else {
|
|
34
34
|
const deadline = Date.now() + 50;
|
|
35
|
-
|
|
35
|
+
self.setTimeout(() => idleTaskCallback({
|
|
36
36
|
timeRemaining: () => Math.max(deadline - Date.now(), 0),
|
|
37
37
|
didTimeout: false,
|
|
38
|
-
}),
|
|
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,
|
|
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"]}
|
package/lib/FluidCache.d.ts
CHANGED
|
@@ -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;
|
package/lib/FluidCache.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FluidCache.d.ts","sourceRoot":"","sources":["../src/FluidCache.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"FluidCache.d.ts","sourceRoot":"","sources":["../src/FluidCache.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,yCAAyC,CAAC;AACnG,OAAO,EAAE,oBAAoB,EAAoB,MAAM,oCAAoC,CAAC;AAsB5F,MAAM,WAAW,gBAAgB;IAChC;;;;;;OAMG;IAEH,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAE5B;;OAEG;IACH,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAE9B;;;OAGG;IACH,eAAe,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,qBAAa,UAAW,YAAW,eAAe;IACjD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAE1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAgB;IAE7C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAiB;IACpD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,EAAE,CAA+C;IACzD,OAAO,CAAC,YAAY,CAA4C;IAChE,OAAO,CAAC,YAAY,CAAc;gBAEtB,MAAM,EAAE,gBAAgB;YAiEtB,MAAM;IAwCpB,OAAO,CAAC,OAAO;IAMF,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAyB9C,GAAG,CAAC,UAAU,EAAE,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC;YAoBzC,gBAAgB;IAiDjB,GAAG,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;CAgC/D"}
|
package/lib/FluidCache.js
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
import { assert } from "@fluidframework/common-utils";
|
|
1
6
|
import { ChildLogger } from "@fluidframework/telemetry-utils";
|
|
2
7
|
import { scheduleIdleTask } from "./scheduleIdleTask";
|
|
3
8
|
import { getFluidCacheIndexedDbInstance, FluidDriverObjectStoreName, getKeyForCacheEntry, } from "./FluidCacheIndexedDb";
|
|
9
|
+
import { pkgVersion } from "./packageVersion";
|
|
4
10
|
/**
|
|
5
11
|
* A cache that can be used by the Fluid ODSP driver to cache data for faster performance
|
|
6
12
|
*/
|
|
7
13
|
export class FluidCache {
|
|
8
14
|
constructor(config) {
|
|
15
|
+
var _a;
|
|
16
|
+
this.closeDbImmediately = true;
|
|
17
|
+
this.dbReuseCount = -1;
|
|
9
18
|
this.logger = ChildLogger.create(config.logger);
|
|
10
19
|
this.partitionKey = config.partitionKey;
|
|
11
20
|
this.maxCacheItemAge = config.maxCacheItemAge;
|
|
21
|
+
this.closeDbAfterMs = (_a = config.closeDbAfterMs) !== null && _a !== void 0 ? _a : 0;
|
|
22
|
+
if (this.closeDbAfterMs > 0) {
|
|
23
|
+
this.closeDbImmediately = false;
|
|
24
|
+
}
|
|
12
25
|
scheduleIdleTask(async () => {
|
|
13
26
|
var _a;
|
|
14
27
|
// Log how much storage space is currently being used by indexed db.
|
|
@@ -29,6 +42,7 @@ export class FluidCache {
|
|
|
29
42
|
quota: estimate.quota,
|
|
30
43
|
usage: estimate.usage,
|
|
31
44
|
indexedDBSize,
|
|
45
|
+
pkgVersion,
|
|
32
46
|
});
|
|
33
47
|
}
|
|
34
48
|
});
|
|
@@ -47,6 +61,7 @@ export class FluidCache {
|
|
|
47
61
|
catch (error) {
|
|
48
62
|
this.logger.sendErrorEvent({
|
|
49
63
|
eventName: "FluidCacheDeleteOldEntriesError" /* FluidCacheDeleteOldEntriesError */,
|
|
64
|
+
pkgVersion,
|
|
50
65
|
}, error);
|
|
51
66
|
}
|
|
52
67
|
finally {
|
|
@@ -54,10 +69,57 @@ export class FluidCache {
|
|
|
54
69
|
}
|
|
55
70
|
});
|
|
56
71
|
}
|
|
72
|
+
async openDb() {
|
|
73
|
+
if (this.closeDbImmediately) {
|
|
74
|
+
return getFluidCacheIndexedDbInstance(this.logger);
|
|
75
|
+
}
|
|
76
|
+
if (this.db === undefined) {
|
|
77
|
+
const dbInstance = await getFluidCacheIndexedDbInstance(this.logger);
|
|
78
|
+
if (this.db === undefined) {
|
|
79
|
+
// Reset the counter on first open.
|
|
80
|
+
this.dbReuseCount = -1;
|
|
81
|
+
this.db = dbInstance;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
dbInstance.close();
|
|
85
|
+
this.dbReuseCount += 1;
|
|
86
|
+
return this.db;
|
|
87
|
+
}
|
|
88
|
+
// Need to close the db on version change if opened.
|
|
89
|
+
this.db.onversionchange = (ev) => {
|
|
90
|
+
var _a;
|
|
91
|
+
(_a = this.db) === null || _a === void 0 ? void 0 : _a.close();
|
|
92
|
+
this.db = undefined;
|
|
93
|
+
clearTimeout(this.dbCloseTimer);
|
|
94
|
+
this.dbCloseTimer = undefined;
|
|
95
|
+
};
|
|
96
|
+
this.db.addEventListener("close", (ev) => {
|
|
97
|
+
clearTimeout(this.dbCloseTimer);
|
|
98
|
+
this.dbCloseTimer = undefined;
|
|
99
|
+
this.db = undefined;
|
|
100
|
+
});
|
|
101
|
+
// Schedule db close after this.closeDbAfterMs.
|
|
102
|
+
assert(this.dbCloseTimer === undefined, 0x6c6 /* timer should not be set yet!! */);
|
|
103
|
+
this.dbCloseTimer = setTimeout(() => {
|
|
104
|
+
var _a;
|
|
105
|
+
(_a = this.db) === null || _a === void 0 ? void 0 : _a.close();
|
|
106
|
+
this.db = undefined;
|
|
107
|
+
this.dbCloseTimer = undefined;
|
|
108
|
+
}, this.closeDbAfterMs);
|
|
109
|
+
}
|
|
110
|
+
assert(this.db !== undefined, 0x6c7 /* db should be intialized by now */);
|
|
111
|
+
this.dbReuseCount += 1;
|
|
112
|
+
return this.db;
|
|
113
|
+
}
|
|
114
|
+
closeDb(db) {
|
|
115
|
+
if (this.closeDbImmediately) {
|
|
116
|
+
db === null || db === void 0 ? void 0 : db.close();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
57
119
|
async removeEntries(file) {
|
|
58
120
|
let db;
|
|
59
121
|
try {
|
|
60
|
-
db = await
|
|
122
|
+
db = await this.openDb();
|
|
61
123
|
const transaction = db.transaction(FluidDriverObjectStoreName, "readwrite");
|
|
62
124
|
const index = transaction.store.index("fileId");
|
|
63
125
|
const keysToDelete = await index.getAllKeys(file.docId);
|
|
@@ -67,10 +129,11 @@ export class FluidCache {
|
|
|
67
129
|
catch (error) {
|
|
68
130
|
this.logger.sendErrorEvent({
|
|
69
131
|
eventName: "FluidCacheDeleteOldEntriesError" /* FluidCacheDeleteOldEntriesError */,
|
|
132
|
+
pkgVersion,
|
|
70
133
|
}, error);
|
|
71
134
|
}
|
|
72
135
|
finally {
|
|
73
|
-
|
|
136
|
+
this.closeDb(db);
|
|
74
137
|
}
|
|
75
138
|
}
|
|
76
139
|
async get(cacheEntry) {
|
|
@@ -82,7 +145,8 @@ export class FluidCache {
|
|
|
82
145
|
type: cacheEntry.type,
|
|
83
146
|
duration: performance.now() - startTime,
|
|
84
147
|
dbOpenPerf: cachedItem === null || cachedItem === void 0 ? void 0 : cachedItem.dbOpenPerf,
|
|
85
|
-
|
|
148
|
+
dbReuseCount: this.dbReuseCount,
|
|
149
|
+
pkgVersion,
|
|
86
150
|
});
|
|
87
151
|
// Value will contain metadata like the expiry time, we just want to return the object we were asked to cache
|
|
88
152
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
@@ -93,11 +157,11 @@ export class FluidCache {
|
|
|
93
157
|
try {
|
|
94
158
|
const key = getKeyForCacheEntry(cacheEntry);
|
|
95
159
|
const dbOpenStartTime = performance.now();
|
|
96
|
-
db = await
|
|
160
|
+
db = await this.openDb();
|
|
97
161
|
const dbOpenPerf = performance.now() - dbOpenStartTime;
|
|
98
162
|
const value = await db.get(FluidDriverObjectStoreName, key);
|
|
99
163
|
if (!value) {
|
|
100
|
-
|
|
164
|
+
this.closeDb(db);
|
|
101
165
|
return undefined;
|
|
102
166
|
}
|
|
103
167
|
// If the data does not come from the same partition, don't return it
|
|
@@ -105,33 +169,32 @@ export class FluidCache {
|
|
|
105
169
|
this.logger.sendTelemetryEvent({
|
|
106
170
|
eventName: "FluidCachePartitionKeyMismatch" /* FluidCachePartitionKeyMismatch */,
|
|
107
171
|
subCategory: "FluidCache" /* FluidCache */,
|
|
172
|
+
pkgVersion,
|
|
108
173
|
});
|
|
109
|
-
|
|
174
|
+
this.closeDb(db);
|
|
110
175
|
return undefined;
|
|
111
176
|
}
|
|
112
177
|
const currentTime = new Date().getTime();
|
|
113
178
|
// If too much time has passed since this cache entry was used, we will also return undefined
|
|
114
179
|
if (currentTime - value.createdTimeMs > this.maxCacheItemAge) {
|
|
115
|
-
|
|
180
|
+
this.closeDb(db);
|
|
116
181
|
return undefined;
|
|
117
182
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const dbClosePerf = performance.now() - dbCloseStartTime;
|
|
121
|
-
return Object.assign(Object.assign({}, value), { dbOpenPerf, dbClosePerf });
|
|
183
|
+
this.closeDb(db);
|
|
184
|
+
return Object.assign(Object.assign({}, value), { dbOpenPerf });
|
|
122
185
|
}
|
|
123
186
|
catch (error) {
|
|
124
187
|
// We can fail to open the db for a variety of reasons,
|
|
125
188
|
// such as the database version having upgraded underneath us. Return undefined in this case
|
|
126
|
-
this.logger.sendErrorEvent({ eventName: "FluidCacheGetError" /* FluidCacheGetError
|
|
127
|
-
|
|
189
|
+
this.logger.sendErrorEvent({ eventName: "FluidCacheGetError" /* FluidCacheGetError */, pkgVersion }, error);
|
|
190
|
+
this.closeDb(db);
|
|
128
191
|
return undefined;
|
|
129
192
|
}
|
|
130
193
|
}
|
|
131
194
|
async put(entry, value) {
|
|
132
195
|
let db;
|
|
133
196
|
try {
|
|
134
|
-
db = await
|
|
197
|
+
db = await this.openDb();
|
|
135
198
|
const currentTime = new Date().getTime();
|
|
136
199
|
await db.put(FluidDriverObjectStoreName, {
|
|
137
200
|
cachedObject: value,
|
|
@@ -142,15 +205,15 @@ export class FluidCache {
|
|
|
142
205
|
createdTimeMs: currentTime,
|
|
143
206
|
lastAccessTimeMs: currentTime,
|
|
144
207
|
}, getKeyForCacheEntry(entry));
|
|
145
|
-
|
|
208
|
+
this.closeDb(db);
|
|
146
209
|
}
|
|
147
210
|
catch (error) {
|
|
148
211
|
// We can fail to open the db for a variety of reasons,
|
|
149
212
|
// such as the database version having upgraded underneath us
|
|
150
|
-
this.logger.sendErrorEvent({ eventName: "FluidCachePutError" /* FluidCachePutError
|
|
213
|
+
this.logger.sendErrorEvent({ eventName: "FluidCachePutError" /* FluidCachePutError */, pkgVersion }, error);
|
|
151
214
|
}
|
|
152
215
|
finally {
|
|
153
|
-
|
|
216
|
+
this.closeDb(db);
|
|
154
217
|
}
|
|
155
218
|
}
|
|
156
219
|
}
|
package/lib/FluidCache.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FluidCache.js","sourceRoot":"","sources":["../src/FluidCache.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\nimport { IDBPDatabase } from \"idb\";\nimport { IPersistedCache, ICacheEntry, IFileEntry } from \"@fluidframework/odsp-driver-definitions\";\nimport { ITelemetryBaseLogger, ITelemetryLogger } from \"@fluidframework/common-definitions\";\nimport { ChildLogger } from \"@fluidframework/telemetry-utils\";\nimport { scheduleIdleTask } from \"./scheduleIdleTask\";\nimport {\n\tgetFluidCacheIndexedDbInstance,\n\tFluidCacheDBSchema,\n\tFluidDriverObjectStoreName,\n\tgetKeyForCacheEntry,\n} from \"./FluidCacheIndexedDb\";\nimport {\n\tFluidCacheErrorEvent,\n\tFluidCacheEventSubCategories,\n\tFluidCacheGenericEvent,\n} from \"./fluidCacheTelemetry\";\n\n// Some browsers have a usageDetails property that will tell you more detailed information\n// on how the storage is being used\ninterface StorageQuotaUsageDetails {\n\tindexedDB: number | undefined;\n}\n\nexport interface FluidCacheConfig {\n\t/**\n\t * A string to specify what partition of the cache you wish to use (e.g. a user id).\n\t * Null can be used to explicity indicate no partitioning, and has been chosen\n\t * vs undefined so that it is clear this is an intentional choice by the caller.\n\t * A null value should only be used when the host can ensure that the cache is not able\n\t * to be shared with multiple users.\n\t */\n\t// eslint-disable-next-line @rushstack/no-new-null\n\tpartitionKey: string | null;\n\n\t/**\n\t * A logger that can be used to get insight into cache performance and errors\n\t */\n\tlogger?: ITelemetryBaseLogger;\n\n\t/**\n\t * A value in milliseconds that determines the maximum age of a cache entry to return.\n\t * If an entry exists in the cache, but is older than this value, the cached value will not be returned.\n\t */\n\tmaxCacheItemAge: number;\n}\n\n/**\n * A cache that can be used by the Fluid ODSP driver to cache data for faster performance\n */\nexport class FluidCache implements IPersistedCache {\n\tprivate readonly logger: ITelemetryLogger;\n\n\tprivate readonly partitionKey: string | null;\n\n\tprivate readonly maxCacheItemAge: number;\n\n\tconstructor(config: FluidCacheConfig) {\n\t\tthis.logger = ChildLogger.create(config.logger);\n\t\tthis.partitionKey = config.partitionKey;\n\t\tthis.maxCacheItemAge = config.maxCacheItemAge;\n\n\t\tscheduleIdleTask(async () => {\n\t\t\t// Log how much storage space is currently being used by indexed db.\n\t\t\t// NOTE: This API is not supported in all browsers and it doesn't let you see the size of a specific DB.\n\t\t\t// Exception added when eslint rule was added, this should be revisited when modifying this code\n\t\t\tif (navigator.storage?.estimate) {\n\t\t\t\tconst estimate = await navigator.storage.estimate();\n\n\t\t\t\t// Some browsers have a usageDetails property that will tell you\n\t\t\t\t// more detailed information on how the storage is being used\n\t\t\t\tlet indexedDBSize: number | undefined;\n\t\t\t\tif (\"usageDetails\" in estimate) {\n\t\t\t\t\tindexedDBSize = ((estimate as any).usageDetails as StorageQuotaUsageDetails)\n\t\t\t\t\t\t.indexedDB;\n\t\t\t\t}\n\n\t\t\t\tthis.logger.sendTelemetryEvent({\n\t\t\t\t\teventName: FluidCacheGenericEvent.FluidCacheStorageInfo,\n\t\t\t\t\tsubCategory: FluidCacheEventSubCategories.FluidCache,\n\t\t\t\t\tquota: estimate.quota,\n\t\t\t\t\tusage: estimate.usage,\n\t\t\t\t\tindexedDBSize,\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\tscheduleIdleTask(async () => {\n\t\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\n\t\t\t// Delete entries that have not been accessed recently to clean up space\n\t\t\ttry {\n\t\t\t\tdb = await getFluidCacheIndexedDbInstance(this.logger);\n\n\t\t\t\tconst transaction = db.transaction(FluidDriverObjectStoreName, \"readwrite\");\n\t\t\t\tconst index = transaction.store.index(\"createdTimeMs\");\n\t\t\t\t// Get items which were cached before the maxCacheItemAge.\n\t\t\t\tconst keysToDelete = await index.getAllKeys(\n\t\t\t\t\tIDBKeyRange.upperBound(new Date().getTime() - this.maxCacheItemAge),\n\t\t\t\t);\n\n\t\t\t\tawait Promise.all(keysToDelete.map((key) => transaction.store.delete(key)));\n\t\t\t\tawait transaction.done;\n\t\t\t} catch (error: any) {\n\t\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: FluidCacheErrorEvent.FluidCacheDeleteOldEntriesError,\n\t\t\t\t\t},\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t} finally {\n\t\t\t\tdb?.close();\n\t\t\t}\n\t\t});\n\t}\n\n\tpublic async removeEntries(file: IFileEntry): Promise<void> {\n\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\t\ttry {\n\t\t\tdb = await getFluidCacheIndexedDbInstance(this.logger);\n\n\t\t\tconst transaction = db.transaction(FluidDriverObjectStoreName, \"readwrite\");\n\t\t\tconst index = transaction.store.index(\"fileId\");\n\n\t\t\tconst keysToDelete = await index.getAllKeys(file.docId);\n\n\t\t\tawait Promise.all(keysToDelete.map((key) => transaction.store.delete(key)));\n\t\t\tawait transaction.done;\n\t\t} catch (error: any) {\n\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t{\n\t\t\t\t\teventName: FluidCacheErrorEvent.FluidCacheDeleteOldEntriesError,\n\t\t\t\t},\n\t\t\t\terror,\n\t\t\t);\n\t\t} finally {\n\t\t\tdb?.close();\n\t\t}\n\t}\n\n\tpublic async get(cacheEntry: ICacheEntry): Promise<any> {\n\t\tconst startTime = performance.now();\n\n\t\tconst cachedItem = await this.getItemFromCache(cacheEntry);\n\n\t\tthis.logger.sendPerformanceEvent({\n\t\t\teventName: \"FluidCacheAccess\",\n\t\t\tcacheHit: cachedItem !== undefined,\n\t\t\ttype: cacheEntry.type,\n\t\t\tduration: performance.now() - startTime,\n\t\t\tdbOpenPerf: cachedItem?.dbOpenPerf,\n\t\t\tdbClosePerf: cachedItem?.dbClosePerf,\n\t\t});\n\n\t\t// Value will contain metadata like the expiry time, we just want to return the object we were asked to cache\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-return\n\t\treturn cachedItem?.cachedObject;\n\t}\n\n\tprivate async getItemFromCache(cacheEntry: ICacheEntry) {\n\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\t\ttry {\n\t\t\tconst key = getKeyForCacheEntry(cacheEntry);\n\n\t\t\tconst dbOpenStartTime = performance.now();\n\t\t\tdb = await getFluidCacheIndexedDbInstance(this.logger);\n\t\t\tconst dbOpenPerf = performance.now() - dbOpenStartTime;\n\t\t\tconst value = await db.get(FluidDriverObjectStoreName, key);\n\n\t\t\tif (!value) {\n\t\t\t\tdb.close();\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\t// If the data does not come from the same partition, don't return it\n\t\t\tif (value.partitionKey !== this.partitionKey) {\n\t\t\t\tthis.logger.sendTelemetryEvent({\n\t\t\t\t\teventName: FluidCacheGenericEvent.FluidCachePartitionKeyMismatch,\n\t\t\t\t\tsubCategory: FluidCacheEventSubCategories.FluidCache,\n\t\t\t\t});\n\n\t\t\t\tdb.close();\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\tconst currentTime = new Date().getTime();\n\n\t\t\t// If too much time has passed since this cache entry was used, we will also return undefined\n\t\t\tif (currentTime - value.createdTimeMs > this.maxCacheItemAge) {\n\t\t\t\tdb.close();\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\tconst dbCloseStartTime = performance.now();\n\t\t\tdb.close();\n\t\t\tconst dbClosePerf = performance.now() - dbCloseStartTime;\n\t\t\treturn { ...value, dbOpenPerf, dbClosePerf };\n\t\t} catch (error: any) {\n\t\t\t// We can fail to open the db for a variety of reasons,\n\t\t\t// such as the database version having upgraded underneath us. Return undefined in this case\n\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t{ eventName: FluidCacheErrorEvent.FluidCacheGetError },\n\t\t\t\terror,\n\t\t\t);\n\t\t\tdb?.close();\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tpublic async put(entry: ICacheEntry, value: any): Promise<void> {\n\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\t\ttry {\n\t\t\tdb = await getFluidCacheIndexedDbInstance(this.logger);\n\n\t\t\tconst currentTime = new Date().getTime();\n\n\t\t\tawait db.put(\n\t\t\t\tFluidDriverObjectStoreName,\n\t\t\t\t{\n\t\t\t\t\tcachedObject: value,\n\t\t\t\t\tfileId: entry.file.docId,\n\t\t\t\t\ttype: entry.type,\n\t\t\t\t\tcacheItemId: entry.key,\n\t\t\t\t\tpartitionKey: this.partitionKey,\n\t\t\t\t\tcreatedTimeMs: currentTime,\n\t\t\t\t\tlastAccessTimeMs: currentTime,\n\t\t\t\t},\n\t\t\t\tgetKeyForCacheEntry(entry),\n\t\t\t);\n\n\t\t\tdb.close();\n\t\t} catch (error: any) {\n\t\t\t// We can fail to open the db for a variety of reasons,\n\t\t\t// such as the database version having upgraded underneath us\n\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t{ eventName: FluidCacheErrorEvent.FluidCachePutError },\n\t\t\t\terror,\n\t\t\t);\n\t\t} finally {\n\t\t\tdb?.close();\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"FluidCache.js","sourceRoot":"","sources":["../src/FluidCache.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { IDBPDatabase } from \"idb\";\nimport { assert } from \"@fluidframework/common-utils\";\nimport { IPersistedCache, ICacheEntry, IFileEntry } from \"@fluidframework/odsp-driver-definitions\";\nimport { ITelemetryBaseLogger, ITelemetryLogger } from \"@fluidframework/common-definitions\";\nimport { ChildLogger } from \"@fluidframework/telemetry-utils\";\nimport { scheduleIdleTask } from \"./scheduleIdleTask\";\nimport {\n\tgetFluidCacheIndexedDbInstance,\n\tFluidCacheDBSchema,\n\tFluidDriverObjectStoreName,\n\tgetKeyForCacheEntry,\n} from \"./FluidCacheIndexedDb\";\nimport {\n\tFluidCacheErrorEvent,\n\tFluidCacheEventSubCategories,\n\tFluidCacheGenericEvent,\n} from \"./fluidCacheTelemetry\";\nimport { pkgVersion } from \"./packageVersion\";\n\n// Some browsers have a usageDetails property that will tell you more detailed information\n// on how the storage is being used\ninterface StorageQuotaUsageDetails {\n\tindexedDB: number | undefined;\n}\n\nexport interface FluidCacheConfig {\n\t/**\n\t * A string to specify what partition of the cache you wish to use (e.g. a user id).\n\t * Null can be used to explicity indicate no partitioning, and has been chosen\n\t * vs undefined so that it is clear this is an intentional choice by the caller.\n\t * A null value should only be used when the host can ensure that the cache is not able\n\t * to be shared with multiple users.\n\t */\n\t// eslint-disable-next-line @rushstack/no-new-null\n\tpartitionKey: string | null;\n\n\t/**\n\t * A logger that can be used to get insight into cache performance and errors\n\t */\n\tlogger?: ITelemetryBaseLogger;\n\n\t/**\n\t * A value in milliseconds that determines the maximum age of a cache entry to return.\n\t * If an entry exists in the cache, but is older than this value, the cached value will not be returned.\n\t */\n\tmaxCacheItemAge: number;\n\n\t/**\n\t * Each time db is opened, it will remain open for this much time. To improve perf, if this property is set as\n\t * any number greater than 0, then db will not be closed immediately after usage. This value is in milliseconds.\n\t */\n\tcloseDbAfterMs?: number;\n}\n\n/**\n * A cache that can be used by the Fluid ODSP driver to cache data for faster performance\n */\nexport class FluidCache implements IPersistedCache {\n\tprivate readonly logger: ITelemetryLogger;\n\n\tprivate readonly partitionKey: string | null;\n\n\tprivate readonly maxCacheItemAge: number;\n\tprivate readonly closeDbImmediately: boolean = true;\n\tprivate readonly closeDbAfterMs: number;\n\tprivate db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\tprivate dbCloseTimer: ReturnType<typeof setTimeout> | undefined;\n\tprivate dbReuseCount: number = -1;\n\n\tconstructor(config: FluidCacheConfig) {\n\t\tthis.logger = ChildLogger.create(config.logger);\n\t\tthis.partitionKey = config.partitionKey;\n\t\tthis.maxCacheItemAge = config.maxCacheItemAge;\n\t\tthis.closeDbAfterMs = config.closeDbAfterMs ?? 0;\n\t\tif (this.closeDbAfterMs > 0) {\n\t\t\tthis.closeDbImmediately = false;\n\t\t}\n\n\t\tscheduleIdleTask(async () => {\n\t\t\t// Log how much storage space is currently being used by indexed db.\n\t\t\t// NOTE: This API is not supported in all browsers and it doesn't let you see the size of a specific DB.\n\t\t\t// Exception added when eslint rule was added, this should be revisited when modifying this code\n\t\t\tif (navigator.storage?.estimate) {\n\t\t\t\tconst estimate = await navigator.storage.estimate();\n\n\t\t\t\t// Some browsers have a usageDetails property that will tell you\n\t\t\t\t// more detailed information on how the storage is being used\n\t\t\t\tlet indexedDBSize: number | undefined;\n\t\t\t\tif (\"usageDetails\" in estimate) {\n\t\t\t\t\tindexedDBSize = ((estimate as any).usageDetails as StorageQuotaUsageDetails)\n\t\t\t\t\t\t.indexedDB;\n\t\t\t\t}\n\n\t\t\t\tthis.logger.sendTelemetryEvent({\n\t\t\t\t\teventName: FluidCacheGenericEvent.FluidCacheStorageInfo,\n\t\t\t\t\tsubCategory: FluidCacheEventSubCategories.FluidCache,\n\t\t\t\t\tquota: estimate.quota,\n\t\t\t\t\tusage: estimate.usage,\n\t\t\t\t\tindexedDBSize,\n\t\t\t\t\tpkgVersion,\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\tscheduleIdleTask(async () => {\n\t\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\n\t\t\t// Delete entries that have not been accessed recently to clean up space\n\t\t\ttry {\n\t\t\t\tdb = await getFluidCacheIndexedDbInstance(this.logger);\n\n\t\t\t\tconst transaction = db.transaction(FluidDriverObjectStoreName, \"readwrite\");\n\t\t\t\tconst index = transaction.store.index(\"createdTimeMs\");\n\t\t\t\t// Get items which were cached before the maxCacheItemAge.\n\t\t\t\tconst keysToDelete = await index.getAllKeys(\n\t\t\t\t\tIDBKeyRange.upperBound(new Date().getTime() - this.maxCacheItemAge),\n\t\t\t\t);\n\n\t\t\t\tawait Promise.all(keysToDelete.map((key) => transaction.store.delete(key)));\n\t\t\t\tawait transaction.done;\n\t\t\t} catch (error: any) {\n\t\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t\t{\n\t\t\t\t\t\teventName: FluidCacheErrorEvent.FluidCacheDeleteOldEntriesError,\n\t\t\t\t\t\tpkgVersion,\n\t\t\t\t\t},\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t} finally {\n\t\t\t\tdb?.close();\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate async openDb() {\n\t\tif (this.closeDbImmediately) {\n\t\t\treturn getFluidCacheIndexedDbInstance(this.logger);\n\t\t}\n\t\tif (this.db === undefined) {\n\t\t\tconst dbInstance = await getFluidCacheIndexedDbInstance(this.logger);\n\t\t\tif (this.db === undefined) {\n\t\t\t\t// Reset the counter on first open.\n\t\t\t\tthis.dbReuseCount = -1;\n\t\t\t\tthis.db = dbInstance;\n\t\t\t} else {\n\t\t\t\tdbInstance.close();\n\t\t\t\tthis.dbReuseCount += 1;\n\t\t\t\treturn this.db;\n\t\t\t}\n\t\t\t// Need to close the db on version change if opened.\n\t\t\tthis.db.onversionchange = (ev) => {\n\t\t\t\tthis.db?.close();\n\t\t\t\tthis.db = undefined;\n\t\t\t\tclearTimeout(this.dbCloseTimer);\n\t\t\t\tthis.dbCloseTimer = undefined;\n\t\t\t};\n\t\t\tthis.db.addEventListener(\"close\", (ev) => {\n\t\t\t\tclearTimeout(this.dbCloseTimer);\n\t\t\t\tthis.dbCloseTimer = undefined;\n\t\t\t\tthis.db = undefined;\n\t\t\t});\n\t\t\t// Schedule db close after this.closeDbAfterMs.\n\t\t\tassert(this.dbCloseTimer === undefined, 0x6c6 /* timer should not be set yet!! */);\n\t\t\tthis.dbCloseTimer = setTimeout(() => {\n\t\t\t\tthis.db?.close();\n\t\t\t\tthis.db = undefined;\n\t\t\t\tthis.dbCloseTimer = undefined;\n\t\t\t}, this.closeDbAfterMs);\n\t\t}\n\t\tassert(this.db !== undefined, 0x6c7 /* db should be intialized by now */);\n\t\tthis.dbReuseCount += 1;\n\t\treturn this.db;\n\t}\n\n\tprivate closeDb(db?: IDBPDatabase<FluidCacheDBSchema>) {\n\t\tif (this.closeDbImmediately) {\n\t\t\tdb?.close();\n\t\t}\n\t}\n\n\tpublic async removeEntries(file: IFileEntry): Promise<void> {\n\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\t\ttry {\n\t\t\tdb = await this.openDb();\n\n\t\t\tconst transaction = db.transaction(FluidDriverObjectStoreName, \"readwrite\");\n\t\t\tconst index = transaction.store.index(\"fileId\");\n\n\t\t\tconst keysToDelete = await index.getAllKeys(file.docId);\n\n\t\t\tawait Promise.all(keysToDelete.map((key) => transaction.store.delete(key)));\n\t\t\tawait transaction.done;\n\t\t} catch (error: any) {\n\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t{\n\t\t\t\t\teventName: FluidCacheErrorEvent.FluidCacheDeleteOldEntriesError,\n\t\t\t\t\tpkgVersion,\n\t\t\t\t},\n\t\t\t\terror,\n\t\t\t);\n\t\t} finally {\n\t\t\tthis.closeDb(db);\n\t\t}\n\t}\n\n\tpublic async get(cacheEntry: ICacheEntry): Promise<any> {\n\t\tconst startTime = performance.now();\n\n\t\tconst cachedItem = await this.getItemFromCache(cacheEntry);\n\n\t\tthis.logger.sendPerformanceEvent({\n\t\t\teventName: \"FluidCacheAccess\",\n\t\t\tcacheHit: cachedItem !== undefined,\n\t\t\ttype: cacheEntry.type,\n\t\t\tduration: performance.now() - startTime,\n\t\t\tdbOpenPerf: cachedItem?.dbOpenPerf,\n\t\t\tdbReuseCount: this.dbReuseCount,\n\t\t\tpkgVersion,\n\t\t});\n\n\t\t// Value will contain metadata like the expiry time, we just want to return the object we were asked to cache\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-return\n\t\treturn cachedItem?.cachedObject;\n\t}\n\n\tprivate async getItemFromCache(cacheEntry: ICacheEntry) {\n\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\t\ttry {\n\t\t\tconst key = getKeyForCacheEntry(cacheEntry);\n\n\t\t\tconst dbOpenStartTime = performance.now();\n\t\t\tdb = await this.openDb();\n\t\t\tconst dbOpenPerf = performance.now() - dbOpenStartTime;\n\t\t\tconst value = await db.get(FluidDriverObjectStoreName, key);\n\n\t\t\tif (!value) {\n\t\t\t\tthis.closeDb(db);\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\t// If the data does not come from the same partition, don't return it\n\t\t\tif (value.partitionKey !== this.partitionKey) {\n\t\t\t\tthis.logger.sendTelemetryEvent({\n\t\t\t\t\teventName: FluidCacheGenericEvent.FluidCachePartitionKeyMismatch,\n\t\t\t\t\tsubCategory: FluidCacheEventSubCategories.FluidCache,\n\t\t\t\t\tpkgVersion,\n\t\t\t\t});\n\n\t\t\t\tthis.closeDb(db);\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\tconst currentTime = new Date().getTime();\n\n\t\t\t// If too much time has passed since this cache entry was used, we will also return undefined\n\t\t\tif (currentTime - value.createdTimeMs > this.maxCacheItemAge) {\n\t\t\t\tthis.closeDb(db);\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\tthis.closeDb(db);\n\t\t\treturn { ...value, dbOpenPerf };\n\t\t} catch (error: any) {\n\t\t\t// We can fail to open the db for a variety of reasons,\n\t\t\t// such as the database version having upgraded underneath us. Return undefined in this case\n\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t{ eventName: FluidCacheErrorEvent.FluidCacheGetError, pkgVersion },\n\t\t\t\terror,\n\t\t\t);\n\t\t\tthis.closeDb(db);\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tpublic async put(entry: ICacheEntry, value: any): Promise<void> {\n\t\tlet db: IDBPDatabase<FluidCacheDBSchema> | undefined;\n\t\ttry {\n\t\t\tdb = await this.openDb();\n\n\t\t\tconst currentTime = new Date().getTime();\n\n\t\t\tawait db.put(\n\t\t\t\tFluidDriverObjectStoreName,\n\t\t\t\t{\n\t\t\t\t\tcachedObject: value,\n\t\t\t\t\tfileId: entry.file.docId,\n\t\t\t\t\ttype: entry.type,\n\t\t\t\t\tcacheItemId: entry.key,\n\t\t\t\t\tpartitionKey: this.partitionKey,\n\t\t\t\t\tcreatedTimeMs: currentTime,\n\t\t\t\t\tlastAccessTimeMs: currentTime,\n\t\t\t\t},\n\t\t\t\tgetKeyForCacheEntry(entry),\n\t\t\t);\n\t\t\tthis.closeDb(db);\n\t\t} catch (error: any) {\n\t\t\t// We can fail to open the db for a variety of reasons,\n\t\t\t// such as the database version having upgraded underneath us\n\t\t\tthis.logger.sendErrorEvent(\n\t\t\t\t{ eventName: FluidCacheErrorEvent.FluidCachePutError, pkgVersion },\n\t\t\t\terror,\n\t\t\t);\n\t\t} finally {\n\t\t\tthis.closeDb(db);\n\t\t}\n\t}\n}\n"]}
|
package/lib/packageVersion.d.ts
CHANGED
|
@@ -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.
|
|
8
|
+
export declare const pkgVersion = "2.0.0-internal.4.4.0";
|
|
9
9
|
//# sourceMappingURL=packageVersion.d.ts.map
|
package/lib/packageVersion.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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"]}
|
package/lib/scheduleIdleTask.js
CHANGED
|
@@ -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 (
|
|
27
|
-
|
|
26
|
+
if (self.requestIdleCallback) {
|
|
27
|
+
self.requestIdleCallback(idleTaskCallback);
|
|
28
28
|
}
|
|
29
29
|
else {
|
|
30
30
|
const deadline = Date.now() + 50;
|
|
31
|
-
|
|
31
|
+
self.setTimeout(() => idleTaskCallback({
|
|
32
32
|
timeRemaining: () => Math.max(deadline - Date.now(), 0),
|
|
33
33
|
didTimeout: false,
|
|
34
|
-
}),
|
|
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,
|
|
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
|
+
"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/
|
|
20
|
-
"@fluidframework/
|
|
19
|
+
"@fluidframework/common-utils": "^1.1.1",
|
|
20
|
+
"@fluidframework/odsp-driver-definitions": ">=2.0.0-internal.4.4.0 <2.0.0-internal.4.5.0",
|
|
21
|
+
"@fluidframework/telemetry-utils": ">=2.0.0-internal.4.4.0 <2.0.0-internal.4.5.0",
|
|
21
22
|
"idb": "^6.1.2"
|
|
22
23
|
},
|
|
23
24
|
"devDependencies": {
|
package/src/FluidCache.ts
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
|
+
|
|
5
6
|
import { IDBPDatabase } from "idb";
|
|
7
|
+
import { assert } from "@fluidframework/common-utils";
|
|
6
8
|
import { IPersistedCache, ICacheEntry, IFileEntry } from "@fluidframework/odsp-driver-definitions";
|
|
7
9
|
import { ITelemetryBaseLogger, ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
8
10
|
import { ChildLogger } from "@fluidframework/telemetry-utils";
|
|
@@ -18,6 +20,7 @@ import {
|
|
|
18
20
|
FluidCacheEventSubCategories,
|
|
19
21
|
FluidCacheGenericEvent,
|
|
20
22
|
} from "./fluidCacheTelemetry";
|
|
23
|
+
import { pkgVersion } from "./packageVersion";
|
|
21
24
|
|
|
22
25
|
// Some browsers have a usageDetails property that will tell you more detailed information
|
|
23
26
|
// on how the storage is being used
|
|
@@ -46,6 +49,12 @@ export interface FluidCacheConfig {
|
|
|
46
49
|
* If an entry exists in the cache, but is older than this value, the cached value will not be returned.
|
|
47
50
|
*/
|
|
48
51
|
maxCacheItemAge: number;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Each time db is opened, it will remain open for this much time. To improve perf, if this property is set as
|
|
55
|
+
* any number greater than 0, then db will not be closed immediately after usage. This value is in milliseconds.
|
|
56
|
+
*/
|
|
57
|
+
closeDbAfterMs?: number;
|
|
49
58
|
}
|
|
50
59
|
|
|
51
60
|
/**
|
|
@@ -57,11 +66,20 @@ export class FluidCache implements IPersistedCache {
|
|
|
57
66
|
private readonly partitionKey: string | null;
|
|
58
67
|
|
|
59
68
|
private readonly maxCacheItemAge: number;
|
|
69
|
+
private readonly closeDbImmediately: boolean = true;
|
|
70
|
+
private readonly closeDbAfterMs: number;
|
|
71
|
+
private db: IDBPDatabase<FluidCacheDBSchema> | undefined;
|
|
72
|
+
private dbCloseTimer: ReturnType<typeof setTimeout> | undefined;
|
|
73
|
+
private dbReuseCount: number = -1;
|
|
60
74
|
|
|
61
75
|
constructor(config: FluidCacheConfig) {
|
|
62
76
|
this.logger = ChildLogger.create(config.logger);
|
|
63
77
|
this.partitionKey = config.partitionKey;
|
|
64
78
|
this.maxCacheItemAge = config.maxCacheItemAge;
|
|
79
|
+
this.closeDbAfterMs = config.closeDbAfterMs ?? 0;
|
|
80
|
+
if (this.closeDbAfterMs > 0) {
|
|
81
|
+
this.closeDbImmediately = false;
|
|
82
|
+
}
|
|
65
83
|
|
|
66
84
|
scheduleIdleTask(async () => {
|
|
67
85
|
// Log how much storage space is currently being used by indexed db.
|
|
@@ -84,6 +102,7 @@ export class FluidCache implements IPersistedCache {
|
|
|
84
102
|
quota: estimate.quota,
|
|
85
103
|
usage: estimate.usage,
|
|
86
104
|
indexedDBSize,
|
|
105
|
+
pkgVersion,
|
|
87
106
|
});
|
|
88
107
|
}
|
|
89
108
|
});
|
|
@@ -108,6 +127,7 @@ export class FluidCache implements IPersistedCache {
|
|
|
108
127
|
this.logger.sendErrorEvent(
|
|
109
128
|
{
|
|
110
129
|
eventName: FluidCacheErrorEvent.FluidCacheDeleteOldEntriesError,
|
|
130
|
+
pkgVersion,
|
|
111
131
|
},
|
|
112
132
|
error,
|
|
113
133
|
);
|
|
@@ -117,10 +137,56 @@ export class FluidCache implements IPersistedCache {
|
|
|
117
137
|
});
|
|
118
138
|
}
|
|
119
139
|
|
|
140
|
+
private async openDb() {
|
|
141
|
+
if (this.closeDbImmediately) {
|
|
142
|
+
return getFluidCacheIndexedDbInstance(this.logger);
|
|
143
|
+
}
|
|
144
|
+
if (this.db === undefined) {
|
|
145
|
+
const dbInstance = await getFluidCacheIndexedDbInstance(this.logger);
|
|
146
|
+
if (this.db === undefined) {
|
|
147
|
+
// Reset the counter on first open.
|
|
148
|
+
this.dbReuseCount = -1;
|
|
149
|
+
this.db = dbInstance;
|
|
150
|
+
} else {
|
|
151
|
+
dbInstance.close();
|
|
152
|
+
this.dbReuseCount += 1;
|
|
153
|
+
return this.db;
|
|
154
|
+
}
|
|
155
|
+
// Need to close the db on version change if opened.
|
|
156
|
+
this.db.onversionchange = (ev) => {
|
|
157
|
+
this.db?.close();
|
|
158
|
+
this.db = undefined;
|
|
159
|
+
clearTimeout(this.dbCloseTimer);
|
|
160
|
+
this.dbCloseTimer = undefined;
|
|
161
|
+
};
|
|
162
|
+
this.db.addEventListener("close", (ev) => {
|
|
163
|
+
clearTimeout(this.dbCloseTimer);
|
|
164
|
+
this.dbCloseTimer = undefined;
|
|
165
|
+
this.db = undefined;
|
|
166
|
+
});
|
|
167
|
+
// Schedule db close after this.closeDbAfterMs.
|
|
168
|
+
assert(this.dbCloseTimer === undefined, 0x6c6 /* timer should not be set yet!! */);
|
|
169
|
+
this.dbCloseTimer = setTimeout(() => {
|
|
170
|
+
this.db?.close();
|
|
171
|
+
this.db = undefined;
|
|
172
|
+
this.dbCloseTimer = undefined;
|
|
173
|
+
}, this.closeDbAfterMs);
|
|
174
|
+
}
|
|
175
|
+
assert(this.db !== undefined, 0x6c7 /* db should be intialized by now */);
|
|
176
|
+
this.dbReuseCount += 1;
|
|
177
|
+
return this.db;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private closeDb(db?: IDBPDatabase<FluidCacheDBSchema>) {
|
|
181
|
+
if (this.closeDbImmediately) {
|
|
182
|
+
db?.close();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
120
186
|
public async removeEntries(file: IFileEntry): Promise<void> {
|
|
121
187
|
let db: IDBPDatabase<FluidCacheDBSchema> | undefined;
|
|
122
188
|
try {
|
|
123
|
-
db = await
|
|
189
|
+
db = await this.openDb();
|
|
124
190
|
|
|
125
191
|
const transaction = db.transaction(FluidDriverObjectStoreName, "readwrite");
|
|
126
192
|
const index = transaction.store.index("fileId");
|
|
@@ -133,11 +199,12 @@ export class FluidCache implements IPersistedCache {
|
|
|
133
199
|
this.logger.sendErrorEvent(
|
|
134
200
|
{
|
|
135
201
|
eventName: FluidCacheErrorEvent.FluidCacheDeleteOldEntriesError,
|
|
202
|
+
pkgVersion,
|
|
136
203
|
},
|
|
137
204
|
error,
|
|
138
205
|
);
|
|
139
206
|
} finally {
|
|
140
|
-
db
|
|
207
|
+
this.closeDb(db);
|
|
141
208
|
}
|
|
142
209
|
}
|
|
143
210
|
|
|
@@ -152,7 +219,8 @@ export class FluidCache implements IPersistedCache {
|
|
|
152
219
|
type: cacheEntry.type,
|
|
153
220
|
duration: performance.now() - startTime,
|
|
154
221
|
dbOpenPerf: cachedItem?.dbOpenPerf,
|
|
155
|
-
|
|
222
|
+
dbReuseCount: this.dbReuseCount,
|
|
223
|
+
pkgVersion,
|
|
156
224
|
});
|
|
157
225
|
|
|
158
226
|
// Value will contain metadata like the expiry time, we just want to return the object we were asked to cache
|
|
@@ -166,12 +234,12 @@ export class FluidCache implements IPersistedCache {
|
|
|
166
234
|
const key = getKeyForCacheEntry(cacheEntry);
|
|
167
235
|
|
|
168
236
|
const dbOpenStartTime = performance.now();
|
|
169
|
-
db = await
|
|
237
|
+
db = await this.openDb();
|
|
170
238
|
const dbOpenPerf = performance.now() - dbOpenStartTime;
|
|
171
239
|
const value = await db.get(FluidDriverObjectStoreName, key);
|
|
172
240
|
|
|
173
241
|
if (!value) {
|
|
174
|
-
|
|
242
|
+
this.closeDb(db);
|
|
175
243
|
return undefined;
|
|
176
244
|
}
|
|
177
245
|
|
|
@@ -180,9 +248,10 @@ export class FluidCache implements IPersistedCache {
|
|
|
180
248
|
this.logger.sendTelemetryEvent({
|
|
181
249
|
eventName: FluidCacheGenericEvent.FluidCachePartitionKeyMismatch,
|
|
182
250
|
subCategory: FluidCacheEventSubCategories.FluidCache,
|
|
251
|
+
pkgVersion,
|
|
183
252
|
});
|
|
184
253
|
|
|
185
|
-
|
|
254
|
+
this.closeDb(db);
|
|
186
255
|
return undefined;
|
|
187
256
|
}
|
|
188
257
|
|
|
@@ -190,22 +259,20 @@ export class FluidCache implements IPersistedCache {
|
|
|
190
259
|
|
|
191
260
|
// If too much time has passed since this cache entry was used, we will also return undefined
|
|
192
261
|
if (currentTime - value.createdTimeMs > this.maxCacheItemAge) {
|
|
193
|
-
|
|
262
|
+
this.closeDb(db);
|
|
194
263
|
return undefined;
|
|
195
264
|
}
|
|
196
265
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const dbClosePerf = performance.now() - dbCloseStartTime;
|
|
200
|
-
return { ...value, dbOpenPerf, dbClosePerf };
|
|
266
|
+
this.closeDb(db);
|
|
267
|
+
return { ...value, dbOpenPerf };
|
|
201
268
|
} catch (error: any) {
|
|
202
269
|
// We can fail to open the db for a variety of reasons,
|
|
203
270
|
// such as the database version having upgraded underneath us. Return undefined in this case
|
|
204
271
|
this.logger.sendErrorEvent(
|
|
205
|
-
{ eventName: FluidCacheErrorEvent.FluidCacheGetError },
|
|
272
|
+
{ eventName: FluidCacheErrorEvent.FluidCacheGetError, pkgVersion },
|
|
206
273
|
error,
|
|
207
274
|
);
|
|
208
|
-
db
|
|
275
|
+
this.closeDb(db);
|
|
209
276
|
return undefined;
|
|
210
277
|
}
|
|
211
278
|
}
|
|
@@ -213,7 +280,7 @@ export class FluidCache implements IPersistedCache {
|
|
|
213
280
|
public async put(entry: ICacheEntry, value: any): Promise<void> {
|
|
214
281
|
let db: IDBPDatabase<FluidCacheDBSchema> | undefined;
|
|
215
282
|
try {
|
|
216
|
-
db = await
|
|
283
|
+
db = await this.openDb();
|
|
217
284
|
|
|
218
285
|
const currentTime = new Date().getTime();
|
|
219
286
|
|
|
@@ -230,17 +297,16 @@ export class FluidCache implements IPersistedCache {
|
|
|
230
297
|
},
|
|
231
298
|
getKeyForCacheEntry(entry),
|
|
232
299
|
);
|
|
233
|
-
|
|
234
|
-
db.close();
|
|
300
|
+
this.closeDb(db);
|
|
235
301
|
} catch (error: any) {
|
|
236
302
|
// We can fail to open the db for a variety of reasons,
|
|
237
303
|
// such as the database version having upgraded underneath us
|
|
238
304
|
this.logger.sendErrorEvent(
|
|
239
|
-
{ eventName: FluidCacheErrorEvent.FluidCachePutError },
|
|
305
|
+
{ eventName: FluidCacheErrorEvent.FluidCachePutError, pkgVersion },
|
|
240
306
|
error,
|
|
241
307
|
);
|
|
242
308
|
} finally {
|
|
243
|
-
db
|
|
309
|
+
this.closeDb(db);
|
|
244
310
|
}
|
|
245
311
|
}
|
|
246
312
|
}
|
package/src/packageVersion.ts
CHANGED
package/src/scheduleIdleTask.ts
CHANGED
|
@@ -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 (
|
|
37
|
-
|
|
36
|
+
if (self.requestIdleCallback) {
|
|
37
|
+
self.requestIdleCallback(idleTaskCallback);
|
|
38
38
|
} else {
|
|
39
39
|
const deadline = Date.now() + 50;
|
|
40
|
-
|
|
40
|
+
self.setTimeout(
|
|
41
41
|
() =>
|
|
42
42
|
idleTaskCallback({
|
|
43
43
|
timeRemaining: () => Math.max(deadline - Date.now(), 0),
|
|
44
44
|
didTimeout: false,
|
|
45
45
|
}),
|
|
46
|
-
|
|
46
|
+
timeout,
|
|
47
47
|
);
|
|
48
48
|
}
|
|
49
49
|
idleTaskScheduled = true;
|