@fluidframework/driver-web-cache 1.4.0-115997 → 2.0.0-dev-rc.1.0.0.224419

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.
Files changed (88) hide show
  1. package/.eslintrc.js +11 -11
  2. package/CHANGELOG.md +81 -0
  3. package/README.md +49 -18
  4. package/api-extractor-lint.json +4 -0
  5. package/api-extractor.json +2 -2
  6. package/api-report/driver-web-cache.api.md +37 -0
  7. package/dist/{FluidCache.js → FluidCache.cjs} +107 -46
  8. package/dist/FluidCache.cjs.map +1 -0
  9. package/dist/FluidCache.d.ts +18 -2
  10. package/dist/FluidCache.d.ts.map +1 -1
  11. package/dist/{FluidCacheIndexedDb.js → FluidCacheIndexedDb.cjs} +11 -7
  12. package/dist/FluidCacheIndexedDb.cjs.map +1 -0
  13. package/dist/FluidCacheIndexedDb.d.ts +11 -3
  14. package/dist/FluidCacheIndexedDb.d.ts.map +1 -1
  15. package/dist/driver-web-cache-alpha.d.ts +65 -0
  16. package/dist/driver-web-cache-beta.d.ts +19 -0
  17. package/dist/driver-web-cache-public.d.ts +19 -0
  18. package/dist/driver-web-cache-untrimmed.d.ts +65 -0
  19. package/dist/{fluidCacheTelemetry.js → fluidCacheTelemetry.cjs} +1 -1
  20. package/dist/fluidCacheTelemetry.cjs.map +1 -0
  21. package/dist/fluidCacheTelemetry.d.ts.map +1 -1
  22. package/dist/index.cjs +12 -0
  23. package/dist/index.cjs.map +1 -0
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/{packageVersion.js → packageVersion.cjs} +2 -2
  27. package/dist/packageVersion.cjs.map +1 -0
  28. package/dist/packageVersion.d.ts +1 -1
  29. package/dist/packageVersion.d.ts.map +1 -1
  30. package/dist/{scheduleIdleTask.js → scheduleIdleTask.cjs} +7 -7
  31. package/dist/scheduleIdleTask.cjs.map +1 -0
  32. package/dist/tsdoc-metadata.json +11 -0
  33. package/jest.config.js +15 -5
  34. package/lib/{FluidCache.d.ts → FluidCache.d.mts} +18 -2
  35. package/lib/FluidCache.d.mts.map +1 -0
  36. package/lib/{FluidCache.js → FluidCache.mjs} +108 -47
  37. package/lib/FluidCache.mjs.map +1 -0
  38. package/lib/{FluidCacheIndexedDb.d.ts → FluidCacheIndexedDb.d.mts} +11 -3
  39. package/lib/FluidCacheIndexedDb.d.mts.map +1 -0
  40. package/lib/{FluidCacheIndexedDb.js → FluidCacheIndexedDb.mjs} +12 -8
  41. package/lib/FluidCacheIndexedDb.mjs.map +1 -0
  42. package/lib/driver-web-cache-alpha.d.mts +65 -0
  43. package/lib/driver-web-cache-beta.d.mts +19 -0
  44. package/lib/driver-web-cache-public.d.mts +19 -0
  45. package/lib/driver-web-cache-untrimmed.d.mts +65 -0
  46. package/lib/{fluidCacheTelemetry.d.ts.map → fluidCacheTelemetry.d.mts.map} +1 -1
  47. package/lib/{fluidCacheTelemetry.js → fluidCacheTelemetry.mjs} +1 -1
  48. package/lib/fluidCacheTelemetry.mjs.map +1 -0
  49. package/lib/{index.d.ts → index.d.mts} +2 -2
  50. package/lib/index.d.mts.map +1 -0
  51. package/lib/{index.js → index.mjs} +3 -3
  52. package/lib/index.mjs.map +1 -0
  53. package/lib/{packageVersion.d.ts → packageVersion.d.mts} +1 -1
  54. package/lib/{packageVersion.d.ts.map → packageVersion.d.mts.map} +1 -1
  55. package/lib/{packageVersion.js → packageVersion.mjs} +2 -2
  56. package/lib/packageVersion.mjs.map +1 -0
  57. package/lib/{scheduleIdleTask.js → scheduleIdleTask.mjs} +7 -7
  58. package/lib/scheduleIdleTask.mjs.map +1 -0
  59. package/package.json +78 -46
  60. package/prettier.config.cjs +8 -0
  61. package/src/FluidCache.ts +293 -253
  62. package/src/FluidCacheIndexedDb.ts +114 -122
  63. package/src/fluidCacheTelemetry.ts +8 -8
  64. package/src/index.ts +1 -1
  65. package/src/packageVersion.ts +1 -1
  66. package/src/scheduleIdleTask.ts +51 -54
  67. package/tsc-multi.test.json +4 -0
  68. package/tsconfig.json +12 -14
  69. package/dist/FluidCache.js.map +0 -1
  70. package/dist/FluidCacheIndexedDb.js.map +0 -1
  71. package/dist/fluidCacheTelemetry.js.map +0 -1
  72. package/dist/index.js +0 -21
  73. package/dist/index.js.map +0 -1
  74. package/dist/packageVersion.js.map +0 -1
  75. package/dist/scheduleIdleTask.js.map +0 -1
  76. package/lib/FluidCache.d.ts.map +0 -1
  77. package/lib/FluidCache.js.map +0 -1
  78. package/lib/FluidCacheIndexedDb.d.ts.map +0 -1
  79. package/lib/FluidCacheIndexedDb.js.map +0 -1
  80. package/lib/fluidCacheTelemetry.js.map +0 -1
  81. package/lib/index.d.ts.map +0 -1
  82. package/lib/index.js.map +0 -1
  83. package/lib/packageVersion.js.map +0 -1
  84. package/lib/scheduleIdleTask.js.map +0 -1
  85. package/tsconfig.esnext.json +0 -7
  86. /package/lib/{fluidCacheTelemetry.d.ts → fluidCacheTelemetry.d.mts} +0 -0
  87. /package/lib/{scheduleIdleTask.d.ts → scheduleIdleTask.d.mts} +0 -0
  88. /package/lib/{scheduleIdleTask.d.ts.map → scheduleIdleTask.d.mts.map} +0 -0
package/.eslintrc.js CHANGED
@@ -4,15 +4,15 @@
4
4
  */
5
5
 
6
6
  module.exports = {
7
- extends: ["@fluidframework/eslint-config-fluid"],
8
- parserOptions: {
9
- project: ["./tsconfig.json", "./src/test/tsconfig.json"],
10
- },
11
- rules: {
12
- "@typescript-eslint/no-non-null-assertion": "off",
13
- "@typescript-eslint/no-use-before-define": "off",
14
- "@typescript-eslint/strict-boolean-expressions": "off",
15
- "@typescript-eslint/promise-function-async": "off",
16
- "@typescript-eslint/no-misused-promises": "off",
17
- },
7
+ extends: [require.resolve("@fluidframework/eslint-config-fluid/minimal"), "prettier"],
8
+ parserOptions: {
9
+ project: ["./tsconfig.json", "./src/test/tsconfig.json"],
10
+ },
11
+ rules: {
12
+ "@typescript-eslint/no-non-null-assertion": "off",
13
+ "@typescript-eslint/no-use-before-define": "off",
14
+ "@typescript-eslint/strict-boolean-expressions": "off",
15
+ "@typescript-eslint/promise-function-async": "off",
16
+ "@typescript-eslint/no-misused-promises": "off",
17
+ },
18
18
  };
package/CHANGELOG.md ADDED
@@ -0,0 +1,81 @@
1
+ # @fluidframework/driver-web-cache
2
+
3
+ ## 2.0.0-internal.8.0.0
4
+
5
+ Dependency updates only.
6
+
7
+ ## 2.0.0-internal.7.4.0
8
+
9
+ Dependency updates only.
10
+
11
+ ## 2.0.0-internal.7.3.0
12
+
13
+ Dependency updates only.
14
+
15
+ ## 2.0.0-internal.7.2.0
16
+
17
+ Dependency updates only.
18
+
19
+ ## 2.0.0-internal.7.1.0
20
+
21
+ Dependency updates only.
22
+
23
+ ## 2.0.0-internal.7.0.0
24
+
25
+ ### Major Changes
26
+
27
+ - Minimum TypeScript version now 5.1.6 [871b3493dd](https://github.com/microsoft/FluidFramework/commits/871b3493dd0d7ea3a89be64998ceb6cb9021a04e)
28
+
29
+ The minimum supported TypeScript version for Fluid 2.0 clients is now 5.1.6.
30
+
31
+ ## 2.0.0-internal.6.4.0
32
+
33
+ Dependency updates only.
34
+
35
+ ## 2.0.0-internal.6.3.0
36
+
37
+ Dependency updates only.
38
+
39
+ ## 2.0.0-internal.6.2.0
40
+
41
+ Dependency updates only.
42
+
43
+ ## 2.0.0-internal.6.1.0
44
+
45
+ Dependency updates only.
46
+
47
+ ## 2.0.0-internal.6.0.0
48
+
49
+ ### Major Changes
50
+
51
+ - Upgraded typescript transpilation target to ES2020 [8abce8cdb4](https://github.com/microsoft/FluidFramework/commits/8abce8cdb4e2832fb6405fb44e393bef03d5648a)
52
+
53
+ Upgraded typescript transpilation target to ES2020. This is done in order to decrease the bundle sizes of Fluid Framework packages. This has provided size improvements across the board for ex. Loader, Driver, Runtime etc. Reduced bundle sizes helps to load lesser code in apps and hence also helps to improve the perf.If any app wants to target any older versions of browsers with which this target version is not compatible, then they can use packages like babel to transpile to a older target.
54
+
55
+ ## 2.0.0-internal.5.4.0
56
+
57
+ Dependency updates only.
58
+
59
+ ## 2.0.0-internal.5.3.0
60
+
61
+ Dependency updates only.
62
+
63
+ ## 2.0.0-internal.5.2.0
64
+
65
+ Dependency updates only.
66
+
67
+ ## 2.0.0-internal.5.1.0
68
+
69
+ Dependency updates only.
70
+
71
+ ## 2.0.0-internal.5.0.0
72
+
73
+ Dependency updates only.
74
+
75
+ ## 2.0.0-internal.4.4.0
76
+
77
+ Dependency updates only.
78
+
79
+ ## 2.0.0-internal.4.1.0
80
+
81
+ Dependency updates only.
package/README.md CHANGED
@@ -5,28 +5,49 @@ storing of user content on the user's machine in order to provide faster boot ex
5
5
  containers more than once. This implementation has a dependency on indexeddb, so it is intended to only be used in a browser
6
6
  context.
7
7
 
8
+ <!-- AUTO-GENERATED-CONTENT:START (README_DEPENDENCY_GUIDELINES_SECTION:includeHeading=TRUE) -->
9
+
10
+ <!-- prettier-ignore-start -->
11
+ <!-- NOTE: This section is automatically generated using @fluid-tools/markdown-magic. Do not update these generated contents directly. -->
12
+
13
+ ## Using Fluid Framework libraries
14
+
15
+ When taking a dependency on a Fluid Framework library, we recommend using a `^` (caret) version range, such as `^1.3.4`.
16
+ While Fluid Framework libraries may use different ranges with interdependencies between other Fluid Framework libraries,
17
+ library consumers should always prefer `^`.
18
+
19
+ Note that when depending on a library version of the form `2.0.0-internal.x.y.z`, called the Fluid internal version scheme,
20
+ you must use a `>= <` dependency range (such as `>=2.0.0-internal.x.y.z <2.0.0-internal.w.0.0` where `w` is `x+1`).
21
+ Standard `^` and `~` ranges will not work as expected.
22
+ See the [@fluid-tools/version-tools](https://github.com/microsoft/FluidFramework/blob/main/build-tools/packages/version-tools/README.md)
23
+ package for more information including tools to convert between version schemes.
24
+
25
+ <!-- prettier-ignore-end -->
26
+
27
+ <!-- AUTO-GENERATED-CONTENT:END -->
28
+
8
29
  ## Usage
9
30
 
10
31
  ```typescript
11
- import { FluidCache } from '@fluidframework/driver-web-cache';
32
+ import { FluidCache } from "@fluidframework/driver-web-cache";
12
33
 
13
34
  new FluidCache({
14
- partitionKey: userId,
15
- logger,
16
- maxCacheItemAge
17
- })
35
+ partitionKey: userId,
36
+ logger,
37
+ maxCacheItemAge,
38
+ });
18
39
  ```
19
40
 
20
41
  ### Parameters
21
42
 
22
- - `partitionKey` - Used to determine what partition of the cache is being used, and can prevent multiple users on the
23
- same machine from sharing a snapshot cache. If you absolutely know that users will not share the cache,
24
- can also be set to `null`. Currently optional, but is proposed to be required in the next major bump.
25
- The recommendation is to use this key to differentiate users for the cache data.
26
- - `logger` - An optional implementation of the logger contract where diagnostic data can be logged.
27
- - `maxCacheItemAge` - The cache tracks a timestamp with each entry. This flag specifies the maximum age (in milliseconds)
28
- for a cache entry to be used. This flag does not control when cached content is deleted since different scenarios and
29
- applications may have different staleness thresholds for the same data.
43
+ - `partitionKey` - Used to determine what partition of the cache is being used, and can prevent multiple users on the
44
+ same machine from sharing a snapshot cache. If you absolutely know that users will not share the cache,
45
+ can also be set to `null`. Currently optional, but is proposed to be required in the next major bump.
46
+ The recommendation is to use this key to differentiate users for the cache data.
47
+ - `logger` - An optional implementation of the logger contract where diagnostic data can be logged.
48
+ - `maxCacheItemAge` - The cache tracks a timestamp with each entry. This flag specifies the maximum age (in milliseconds)
49
+ for a cache entry to be used. This flag does not control when cached content is deleted since different scenarios and
50
+ applications may have different staleness thresholds for the same data.
30
51
 
31
52
  ## Clearing cache entries
32
53
 
@@ -41,16 +62,26 @@ are on point for ensuring responsible usage of the snapshot caching capability t
41
62
  customer promises, such as clearing out storage when appropriate or disabling snapshot caching under certain circumstances,
42
63
  such as when it is known the user is logged in to a public computer.
43
64
 
44
-
45
65
  ```typescript
46
- import { deleteFluidCacheIndexDbInstance } from '@fluidframework/driver-web-cache';
66
+ import { deleteFluidCacheIndexDbInstance } from "@fluidframework/driver-web-cache";
47
67
 
48
- // We put a catch here because Firefox Incognito will throw an error here. This is why we claim this method is a "best effort", since sometimes the browser won't let us access storage
68
+ // We put a catch here because Firefox Incognito will throw an error here. This is why we claim this method is a "best effort", since sometimes the browser won't let us access storage
49
69
  deleteFluidCacheIndexDbInstance().catch(() => {});
50
70
  ```
51
71
 
72
+ <!-- AUTO-GENERATED-CONTENT:START (README_TRADEMARK_SECTION:includeHeading=TRUE) -->
73
+
74
+ <!-- prettier-ignore-start -->
75
+ <!-- NOTE: This section is automatically generated using @fluid-tools/markdown-magic. Do not update these generated contents directly. -->
76
+
52
77
  ## Trademark
53
78
 
54
- This project may contain Microsoft trademarks or logos for Microsoft projects, products, or services. Use of these trademarks
55
- or logos must follow Microsoft's [Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
79
+ This project may contain Microsoft trademarks or logos for Microsoft projects, products, or services.
80
+
81
+ Use of these trademarks or logos must follow Microsoft's [Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
82
+
56
83
  Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
84
+
85
+ <!-- prettier-ignore-end -->
86
+
87
+ <!-- AUTO-GENERATED-CONTENT:END -->
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
3
+ "extends": "../../../common/build/build-common/api-extractor-lint.json"
4
+ }
@@ -1,4 +1,4 @@
1
1
  {
2
- "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
3
- "extends": "@fluidframework/build-common/api-extractor-common-report.json"
2
+ "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
3
+ "extends": "../../../common/build/build-common/api-extractor-base.json"
4
4
  }
@@ -0,0 +1,37 @@
1
+ ## API Report File for "@fluidframework/driver-web-cache"
2
+
3
+ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
4
+
5
+ ```ts
6
+
7
+ import { DeleteDBCallbacks } from 'idb';
8
+ import { ICacheEntry } from '@fluidframework/odsp-driver-definitions';
9
+ import { IFileEntry } from '@fluidframework/odsp-driver-definitions';
10
+ import { IPersistedCache } from '@fluidframework/odsp-driver-definitions';
11
+ import { ITelemetryBaseLogger } from '@fluidframework/core-interfaces';
12
+
13
+ // @alpha
14
+ export function deleteFluidCacheIndexDbInstance(deleteDBCallbacks?: DeleteDBCallbacks): Promise<void>;
15
+
16
+ // @alpha
17
+ export class FluidCache implements IPersistedCache {
18
+ constructor(config: FluidCacheConfig);
19
+ // (undocumented)
20
+ get(cacheEntry: ICacheEntry): Promise<any>;
21
+ // (undocumented)
22
+ put(entry: ICacheEntry, value: any): Promise<void>;
23
+ // (undocumented)
24
+ removeEntries(file: IFileEntry): Promise<void>;
25
+ }
26
+
27
+ // @alpha (undocumented)
28
+ export interface FluidCacheConfig {
29
+ closeDbAfterMs?: number;
30
+ logger?: ITelemetryBaseLogger;
31
+ maxCacheItemAge: number;
32
+ partitionKey: string | null;
33
+ }
34
+
35
+ // (No @packageDocumentation comment for this package)
36
+
37
+ ```
@@ -5,61 +5,121 @@
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.FluidCache = void 0;
8
+ const core_utils_1 = require("@fluidframework/core-utils");
8
9
  const telemetry_utils_1 = require("@fluidframework/telemetry-utils");
9
- const scheduleIdleTask_1 = require("./scheduleIdleTask");
10
- const FluidCacheIndexedDb_1 = require("./FluidCacheIndexedDb");
10
+ const scheduleIdleTask_1 = require("./scheduleIdleTask.cjs");
11
+ const FluidCacheIndexedDb_1 = require("./FluidCacheIndexedDb.cjs");
12
+ const packageVersion_1 = require("./packageVersion.cjs");
11
13
  /**
12
- * A cache that can be used by the Fluid ODSP driver to cache data for faster performance
14
+ * A cache that can be used by the Fluid ODSP driver to cache data for faster performance.
15
+ * @alpha
13
16
  */
14
17
  class FluidCache {
15
18
  constructor(config) {
16
- this.logger = telemetry_utils_1.ChildLogger.create(config.logger);
19
+ this.closeDbImmediately = true;
20
+ this.dbReuseCount = -1;
21
+ this.logger = (0, telemetry_utils_1.createChildLogger)({ logger: config.logger });
17
22
  this.partitionKey = config.partitionKey;
18
23
  this.maxCacheItemAge = config.maxCacheItemAge;
24
+ this.closeDbAfterMs = config.closeDbAfterMs ?? 0;
25
+ if (this.closeDbAfterMs > 0) {
26
+ this.closeDbImmediately = false;
27
+ }
19
28
  (0, scheduleIdleTask_1.scheduleIdleTask)(async () => {
20
- var _a;
21
29
  // Log how much storage space is currently being used by indexed db.
22
30
  // NOTE: This API is not supported in all browsers and it doesn't let you see the size of a specific DB.
23
31
  // Exception added when eslint rule was added, this should be revisited when modifying this code
24
- if ((_a = navigator.storage) === null || _a === void 0 ? void 0 : _a.estimate) {
32
+ if (navigator.storage?.estimate) {
25
33
  const estimate = await navigator.storage.estimate();
26
34
  // Some browsers have a usageDetails property that will tell you
27
35
  // more detailed information on how the storage is being used
28
36
  let indexedDBSize;
29
37
  if ("usageDetails" in estimate) {
30
- indexedDBSize = estimate
31
- .usageDetails.indexedDB;
38
+ indexedDBSize = estimate.usageDetails
39
+ .indexedDB;
32
40
  }
33
41
  this.logger.sendTelemetryEvent({
34
- eventName: "FluidCacheStorageInfo" /* FluidCacheStorageInfo */,
35
- subCategory: "FluidCache" /* FluidCache */,
42
+ eventName: "FluidCacheStorageInfo" /* FluidCacheGenericEvent.FluidCacheStorageInfo */,
43
+ subCategory: "FluidCache" /* FluidCacheEventSubCategories.FluidCache */,
36
44
  quota: estimate.quota,
37
45
  usage: estimate.usage,
38
46
  indexedDBSize,
47
+ pkgVersion: packageVersion_1.pkgVersion,
39
48
  });
40
49
  }
41
50
  });
42
51
  (0, scheduleIdleTask_1.scheduleIdleTask)(async () => {
52
+ let db;
43
53
  // Delete entries that have not been accessed recently to clean up space
44
54
  try {
45
- const db = await (0, FluidCacheIndexedDb_1.getFluidCacheIndexedDbInstance)(this.logger);
55
+ db = await (0, FluidCacheIndexedDb_1.getFluidCacheIndexedDbInstance)(this.logger);
46
56
  const transaction = db.transaction(FluidCacheIndexedDb_1.FluidDriverObjectStoreName, "readwrite");
47
- const index = transaction.store.index("lastAccessTimeMs");
48
- // Get items that have not been accessed in 4 weeks
49
- const keysToDelete = await index.getAllKeys(IDBKeyRange.upperBound(new Date().getTime() - 4 * 7 * 24 * 60 * 60 * 1000));
57
+ const index = transaction.store.index("createdTimeMs");
58
+ // Get items which were cached before the maxCacheItemAge.
59
+ const keysToDelete = await index.getAllKeys(IDBKeyRange.upperBound(new Date().getTime() - this.maxCacheItemAge));
50
60
  await Promise.all(keysToDelete.map((key) => transaction.store.delete(key)));
51
61
  await transaction.done;
52
62
  }
53
63
  catch (error) {
54
64
  this.logger.sendErrorEvent({
55
- eventName: "FluidCacheDeleteOldEntriesError" /* FluidCacheDeleteOldEntriesError */,
65
+ eventName: "FluidCacheDeleteOldEntriesError" /* FluidCacheErrorEvent.FluidCacheDeleteOldEntriesError */,
66
+ pkgVersion: packageVersion_1.pkgVersion,
56
67
  }, error);
57
68
  }
69
+ finally {
70
+ db?.close();
71
+ }
58
72
  });
59
73
  }
74
+ async openDb() {
75
+ if (this.closeDbImmediately) {
76
+ return (0, FluidCacheIndexedDb_1.getFluidCacheIndexedDbInstance)(this.logger);
77
+ }
78
+ if (this.db === undefined) {
79
+ const dbInstance = await (0, FluidCacheIndexedDb_1.getFluidCacheIndexedDbInstance)(this.logger);
80
+ if (this.db === undefined) {
81
+ // Reset the counter on first open.
82
+ this.dbReuseCount = -1;
83
+ this.db = dbInstance;
84
+ }
85
+ else {
86
+ dbInstance.close();
87
+ this.dbReuseCount += 1;
88
+ return this.db;
89
+ }
90
+ // Need to close the db on version change if opened.
91
+ this.db.onversionchange = (ev) => {
92
+ this.db?.close();
93
+ this.db = undefined;
94
+ clearTimeout(this.dbCloseTimer);
95
+ this.dbCloseTimer = undefined;
96
+ };
97
+ this.db.addEventListener("close", (ev) => {
98
+ clearTimeout(this.dbCloseTimer);
99
+ this.dbCloseTimer = undefined;
100
+ this.db = undefined;
101
+ });
102
+ // Schedule db close after this.closeDbAfterMs.
103
+ (0, core_utils_1.assert)(this.dbCloseTimer === undefined, 0x6c6 /* timer should not be set yet!! */);
104
+ this.dbCloseTimer = setTimeout(() => {
105
+ this.db?.close();
106
+ this.db = undefined;
107
+ this.dbCloseTimer = undefined;
108
+ }, this.closeDbAfterMs);
109
+ }
110
+ (0, core_utils_1.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?.close();
117
+ }
118
+ }
60
119
  async removeEntries(file) {
120
+ let db;
61
121
  try {
62
- const db = await (0, FluidCacheIndexedDb_1.getFluidCacheIndexedDbInstance)(this.logger);
122
+ db = await this.openDb();
63
123
  const transaction = db.transaction(FluidCacheIndexedDb_1.FluidDriverObjectStoreName, "readwrite");
64
124
  const index = transaction.store.index("fileId");
65
125
  const keysToDelete = await index.getAllKeys(file.docId);
@@ -68,9 +128,13 @@ class FluidCache {
68
128
  }
69
129
  catch (error) {
70
130
  this.logger.sendErrorEvent({
71
- eventName: "FluidCacheDeleteOldEntriesError" /* FluidCacheDeleteOldEntriesError */,
131
+ eventName: "FluidCacheDeleteOldEntriesError" /* FluidCacheErrorEvent.FluidCacheDeleteOldEntriesError */,
132
+ pkgVersion: packageVersion_1.pkgVersion,
72
133
  }, error);
73
134
  }
135
+ finally {
136
+ this.closeDb(db);
137
+ }
74
138
  }
75
139
  async get(cacheEntry) {
76
140
  const startTime = performance.now();
@@ -80,63 +144,57 @@ class FluidCache {
80
144
  cacheHit: cachedItem !== undefined,
81
145
  type: cacheEntry.type,
82
146
  duration: performance.now() - startTime,
147
+ dbOpenPerf: cachedItem?.dbOpenPerf,
148
+ dbReuseCount: this.dbReuseCount,
149
+ pkgVersion: packageVersion_1.pkgVersion,
83
150
  });
84
151
  // Value will contain metadata like the expiry time, we just want to return the object we were asked to cache
85
152
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
86
- return cachedItem === null || cachedItem === void 0 ? void 0 : cachedItem.cachedObject;
153
+ return cachedItem?.cachedObject;
87
154
  }
88
155
  async getItemFromCache(cacheEntry) {
156
+ let db;
89
157
  try {
90
158
  const key = (0, FluidCacheIndexedDb_1.getKeyForCacheEntry)(cacheEntry);
91
- const db = await (0, FluidCacheIndexedDb_1.getFluidCacheIndexedDbInstance)(this.logger);
159
+ const dbOpenStartTime = performance.now();
160
+ db = await this.openDb();
161
+ const dbOpenPerf = performance.now() - dbOpenStartTime;
92
162
  const value = await db.get(FluidCacheIndexedDb_1.FluidDriverObjectStoreName, key);
93
163
  if (!value) {
164
+ this.closeDb(db);
94
165
  return undefined;
95
166
  }
96
167
  // If the data does not come from the same partition, don't return it
97
168
  if (value.partitionKey !== this.partitionKey) {
98
169
  this.logger.sendTelemetryEvent({
99
- eventName: "FluidCachePartitionKeyMismatch" /* FluidCachePartitionKeyMismatch */,
100
- subCategory: "FluidCache" /* FluidCache */,
170
+ eventName: "FluidCachePartitionKeyMismatch" /* FluidCacheGenericEvent.FluidCachePartitionKeyMismatch */,
171
+ subCategory: "FluidCache" /* FluidCacheEventSubCategories.FluidCache */,
172
+ pkgVersion: packageVersion_1.pkgVersion,
101
173
  });
174
+ this.closeDb(db);
102
175
  return undefined;
103
176
  }
104
177
  const currentTime = new Date().getTime();
105
178
  // If too much time has passed since this cache entry was used, we will also return undefined
106
179
  if (currentTime - value.createdTimeMs > this.maxCacheItemAge) {
180
+ this.closeDb(db);
107
181
  return undefined;
108
182
  }
109
- const transaction = db.transaction(FluidCacheIndexedDb_1.FluidDriverObjectStoreName, "readwrite");
110
- // We don't want to block the get return of this function on updating the last accessed time
111
- // We catch this promise because there is no user bad if this is rejected.
112
- transaction.store
113
- .get(key)
114
- .then(async (valueToUpdate) => {
115
- // This value in the database could have been updated concurrently by other tabs/iframes
116
- // since we first read it. Only update the last accessed time if the current value in the
117
- // DB was the same one we returned.
118
- if (valueToUpdate !== undefined &&
119
- valueToUpdate.createdTimeMs === value.createdTimeMs &&
120
- (valueToUpdate.lastAccessTimeMs === undefined ||
121
- valueToUpdate.lastAccessTimeMs < currentTime)) {
122
- await transaction.store.put(Object.assign(Object.assign({}, valueToUpdate), { lastAccessTimeMs: currentTime }), key);
123
- }
124
- await transaction.done;
125
- db.close();
126
- })
127
- .catch(() => { });
128
- return value;
183
+ this.closeDb(db);
184
+ return { ...value, dbOpenPerf };
129
185
  }
130
186
  catch (error) {
131
187
  // We can fail to open the db for a variety of reasons,
132
188
  // such as the database version having upgraded underneath us. Return undefined in this case
133
- this.logger.sendErrorEvent({ eventName: "FluidCacheGetError" /* FluidCacheGetError */ }, error);
189
+ this.logger.sendErrorEvent({ eventName: "FluidCacheGetError" /* FluidCacheErrorEvent.FluidCacheGetError */, pkgVersion: packageVersion_1.pkgVersion }, error);
190
+ this.closeDb(db);
134
191
  return undefined;
135
192
  }
136
193
  }
137
194
  async put(entry, value) {
195
+ let db;
138
196
  try {
139
- const db = await (0, FluidCacheIndexedDb_1.getFluidCacheIndexedDbInstance)(this.logger);
197
+ db = await this.openDb();
140
198
  const currentTime = new Date().getTime();
141
199
  await db.put(FluidCacheIndexedDb_1.FluidDriverObjectStoreName, {
142
200
  cachedObject: value,
@@ -147,14 +205,17 @@ class FluidCache {
147
205
  createdTimeMs: currentTime,
148
206
  lastAccessTimeMs: currentTime,
149
207
  }, (0, FluidCacheIndexedDb_1.getKeyForCacheEntry)(entry));
150
- db.close();
208
+ this.closeDb(db);
151
209
  }
152
210
  catch (error) {
153
211
  // We can fail to open the db for a variety of reasons,
154
212
  // such as the database version having upgraded underneath us
155
- this.logger.sendErrorEvent({ eventName: "FluidCachePutError" /* FluidCachePutError */ }, error);
213
+ this.logger.sendErrorEvent({ eventName: "FluidCachePutError" /* FluidCacheErrorEvent.FluidCachePutError */, pkgVersion: packageVersion_1.pkgVersion }, error);
214
+ }
215
+ finally {
216
+ this.closeDb(db);
156
217
  }
157
218
  }
158
219
  }
159
220
  exports.FluidCache = FluidCache;
160
- //# sourceMappingURL=FluidCache.js.map
221
+ //# sourceMappingURL=FluidCache.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FluidCache.cjs","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/core-utils\";\nimport { IPersistedCache, ICacheEntry, IFileEntry } from \"@fluidframework/odsp-driver-definitions\";\nimport { ITelemetryBaseLogger } from \"@fluidframework/core-interfaces\";\nimport { ITelemetryLoggerExt, createChildLogger } 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\n/**\n * @alpha\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 * @alpha\n */\nexport class FluidCache implements IPersistedCache {\n\tprivate readonly logger: ITelemetryLoggerExt;\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 = createChildLogger({ logger: 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"]}
@@ -3,7 +3,10 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
  import { IPersistedCache, ICacheEntry, IFileEntry } from "@fluidframework/odsp-driver-definitions";
6
- import { ITelemetryBaseLogger } from "@fluidframework/common-definitions";
6
+ import { ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
7
+ /**
8
+ * @alpha
9
+ */
7
10
  export interface FluidCacheConfig {
8
11
  /**
9
12
  * A string to specify what partition of the cache you wish to use (e.g. a user id).
@@ -22,15 +25,28 @@ export interface FluidCacheConfig {
22
25
  * If an entry exists in the cache, but is older than this value, the cached value will not be returned.
23
26
  */
24
27
  maxCacheItemAge: number;
28
+ /**
29
+ * Each time db is opened, it will remain open for this much time. To improve perf, if this property is set as
30
+ * any number greater than 0, then db will not be closed immediately after usage. This value is in milliseconds.
31
+ */
32
+ closeDbAfterMs?: number;
25
33
  }
26
34
  /**
27
- * A cache that can be used by the Fluid ODSP driver to cache data for faster performance
35
+ * A cache that can be used by the Fluid ODSP driver to cache data for faster performance.
36
+ * @alpha
28
37
  */
29
38
  export declare class FluidCache implements IPersistedCache {
30
39
  private readonly logger;
31
40
  private readonly partitionKey;
32
41
  private readonly maxCacheItemAge;
42
+ private readonly closeDbImmediately;
43
+ private readonly closeDbAfterMs;
44
+ private db;
45
+ private dbCloseTimer;
46
+ private dbReuseCount;
33
47
  constructor(config: FluidCacheConfig);
48
+ private openDb;
49
+ private closeDb;
34
50
  removeEntries(file: IFileEntry): Promise<void>;
35
51
  get(cacheEntry: ICacheEntry): Promise<any>;
36
52
  private getItemFromCache;
@@ -1 +1 @@
1
- {"version":3,"file":"FluidCache.d.ts","sourceRoot":"","sources":["../src/FluidCache.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACH,eAAe,EACf,WAAW,EACX,UAAU,EACb,MAAM,yCAAyC,CAAC;AACjD,OAAO,EACH,oBAAoB,EAEvB,MAAM,oCAAoC,CAAC;AAoB5C,MAAM,WAAW,gBAAgB;IAC7B;;;;;;OAMG;IAEH,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAE5B;;OAEG;IACH,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAE9B;;;OAGG;IACH,eAAe,EAAE,MAAM,CAAC;CAC3B;AAED;;GAEG;AACH,qBAAa,UAAW,YAAW,eAAe;IAC9C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAE1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAgB;IAE7C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;gBAE7B,MAAM,EAAE,gBAAgB;IAiEvB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IA2B9C,GAAG,CAAC,UAAU,EAAE,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC;YAiBzC,gBAAgB;IAsEjB,GAAG,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;CA8BlE"}
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,EAAE,MAAM,iCAAiC,CAAC;AAsBvE;;GAEG;AACH,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;;;GAGG;AACH,qBAAa,UAAW,YAAW,eAAe;IACjD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAE7C,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"}
@@ -39,8 +39,8 @@ function getFluidCacheIndexedDbInstance(logger) {
39
39
  // Catch any error done when attempting to delete the older version.
40
40
  // If the object does not exist db will throw.
41
41
  // We can now assume that the old version is no longer there regardless.
42
- telemetry_utils_1.ChildLogger.create(logger).sendErrorEvent({
43
- eventName: "FluidCacheDeleteOldDbError" /* FluidCacheDeleteOldDbError */,
42
+ (0, telemetry_utils_1.createChildLogger)({ logger }).sendErrorEvent({
43
+ eventName: "FluidCacheDeleteOldDbError" /* FluidCacheErrorEvent.FluidCacheDeleteOldDbError */,
44
44
  }, error);
45
45
  }
46
46
  const cacheObjectStore = db.createObjectStore(exports.FluidDriverObjectStoreName);
@@ -56,10 +56,14 @@ function getFluidCacheIndexedDbInstance(logger) {
56
56
  });
57
57
  }
58
58
  exports.getFluidCacheIndexedDbInstance = getFluidCacheIndexedDbInstance;
59
- // Deletes the indexed DB instance.
60
- // Warning this can throw an error in Firefox incognito, where accessing storage is prohibited.
61
- function deleteFluidCacheIndexDbInstance() {
62
- return (0, idb_1.deleteDB)(exports.FluidDriverCacheDBName);
59
+ /**
60
+ * Deletes the indexed DB instance.
61
+ *
62
+ * @remarks Warning this can throw an error in Firefox incognito, where accessing storage is prohibited.
63
+ * @alpha
64
+ */
65
+ function deleteFluidCacheIndexDbInstance(deleteDBCallbacks) {
66
+ return (0, idb_1.deleteDB)(exports.FluidDriverCacheDBName, deleteDBCallbacks);
63
67
  }
64
68
  exports.deleteFluidCacheIndexDbInstance = deleteFluidCacheIndexDbInstance;
65
- //# sourceMappingURL=FluidCacheIndexedDb.js.map
69
+ //# sourceMappingURL=FluidCacheIndexedDb.cjs.map