@dbx-tools/appkit 0.9.117 → 0.9.119

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/package.json CHANGED
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "devDependencies": {
22
22
  "@databricks/appkit": "0.81.0",
23
- "@dbx-tools/projen": "^0.9.117",
23
+ "@dbx-tools/projen": "^0.9.119",
24
24
  "@types/node": "^24.6.0",
25
25
  "typescript": "^5.9.3",
26
26
  "vitest": "3.2.4"
@@ -30,13 +30,13 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@databricks/sdk-experimental": "^0.17.0",
33
- "@dbx-tools/auth": "^0.9.117",
34
- "@dbx-tools/core": "^0.9.117",
35
- "@dbx-tools/lakebase": "^0.9.117",
36
- "@dbx-tools/postgres": "^0.9.117",
37
- "@dbx-tools/shared-core": "^0.9.117",
38
- "@dbx-tools/shared-fs": "^0.9.117",
39
- "lru-cache": "11.5.3",
33
+ "@dbx-tools/auth": "^0.9.119",
34
+ "@dbx-tools/cache": "^0.9.119",
35
+ "@dbx-tools/core": "^0.9.119",
36
+ "@dbx-tools/lakebase": "^0.9.119",
37
+ "@dbx-tools/postgres": "^0.9.119",
38
+ "@dbx-tools/shared-core": "^0.9.119",
39
+ "@dbx-tools/shared-fs": "^0.9.119",
40
40
  "zod": "4.3.6"
41
41
  },
42
42
  "main": "./lib/index.js",
@@ -62,6 +62,10 @@
62
62
  "types": "./lib/src/bundle.d.ts",
63
63
  "default": "./lib/src/bundle.js"
64
64
  },
65
+ "./cache-storage": {
66
+ "types": "./lib/src/cache-storage.d.ts",
67
+ "default": "./lib/src/cache-storage.js"
68
+ },
65
69
  "./child-process": {
66
70
  "types": "./lib/src/child-process.d.ts",
67
71
  "default": "./lib/src/child-process.js"
@@ -125,7 +129,7 @@
125
129
  "./package.json": "./package.json"
126
130
  }
127
131
  },
128
- "version": "0.9.117",
132
+ "version": "0.9.119",
129
133
  "types": "./lib/index.d.ts",
130
134
  "dbxToolsConfig": {
131
135
  "tags": [
@@ -150,6 +154,10 @@
150
154
  "types": "./lib/src/bundle.d.ts",
151
155
  "default": "./lib/src/bundle.js"
152
156
  },
157
+ "./cache-storage": {
158
+ "types": "./lib/src/cache-storage.d.ts",
159
+ "default": "./lib/src/cache-storage.js"
160
+ },
153
161
  "./child-process": {
154
162
  "types": "./lib/src/child-process.d.ts",
155
163
  "default": "./lib/src/child-process.js"
package/src/appkit.ts CHANGED
@@ -39,8 +39,8 @@ import { profile as authProfile } from "@dbx-tools/auth";
39
39
  import { asyncUtils, environmentUtils, log } from "@dbx-tools/shared-core";
40
40
 
41
41
  import { resolveAutoConfigurePolicy } from "./_auto-configure.ts";
42
- import { createSoftPersistentStorage } from "./_cache-storage.ts";
43
42
  import { loadBrandContext } from "./brand-context.ts";
43
+ import { createAppKitCacheStorage } from "./cache-storage.ts";
44
44
  import {
45
45
  createInterceptorContext,
46
46
  type Interceptor,
@@ -244,20 +244,12 @@ function applyDefaultProfile(): string | undefined {
244
244
  return resolved.name;
245
245
  }
246
246
 
247
- /**
248
- * Whether `createApp` should inject soft-fail Lakebase cache storage.
249
- *
250
- * Skips when the caller disabled the cache, already supplied a storage
251
- * backend (including in-memory), or Lakebase is not in play.
252
- */
253
- function shouldInjectSoftPersistentCache(
254
- config: CreateAppConfig | undefined,
255
- lakebase: LakebaseConnection | undefined,
256
- ): boolean {
247
+ /** Whether `createApp` should inject the default memory-first cache storage. */
248
+ function shouldInjectCacheStorage(config: CreateAppConfig | undefined): boolean {
257
249
  const cache = config?.cache;
258
250
  if (cache?.enabled === false) return false;
259
251
  if (cache?.storage) return false;
260
- return Boolean(lakebase) || usesPlugin(config, LAKEBASE_PLUGIN);
252
+ return true;
261
253
  }
262
254
 
263
255
  function redactLakebaseConnection(resolved: LakebaseConnection): Record<string, unknown> {
@@ -328,15 +320,16 @@ export async function createApp<T extends AppKitPlugins>(
328
320
  delete appConfig.autoConfigure;
329
321
  delete appConfig.interceptor;
330
322
 
331
- if (shouldInjectSoftPersistentCache(config, lakebase)) {
332
- logger.debug("createApp: inject soft persistent cache storage");
333
- const storage = await createSoftPersistentStorage(config?.cache);
334
- if (storage) {
335
- appConfig.cache = { ...config?.cache, storage };
336
- logger.debug("createApp: soft persistent cache storage ready");
337
- } else {
338
- logger.debug("createApp: soft persistent cache storage unavailable");
339
- }
323
+ if (shouldInjectCacheStorage(config)) {
324
+ logger.debug("createApp: inject memory-first cache storage");
325
+ const storage = await createAppKitCacheStorage({
326
+ cache: config?.cache,
327
+ preferLakebase: Boolean(lakebase) || usesPlugin(config, LAKEBASE_PLUGIN),
328
+ });
329
+ appConfig.cache = { ...config?.cache, storage };
330
+ logger.debug("createApp: memory-first cache storage ready", {
331
+ persistent: storage.isPersistent(),
332
+ });
340
333
  }
341
334
 
342
335
  if (interceptors.length === 0) {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Soft-fail Lakebase cache storage for AppKit's persistent cache.
2
+ * Memory-first AppKit cache storage with soft-fail Lakebase persistence.
3
3
  *
4
4
  * AppKit's `PersistentStorage.initialize()` runs DDL migrations and throws on
5
5
  * any step failure. With `cache.strictPersistence: true` that throw makes
@@ -8,18 +8,25 @@
8
8
  * usable table keeps serving.
9
9
  *
10
10
  * `PersistentStorage` is not on AppKit's public export map. The deep load is
11
- * lazy and best-effort: if the installed AppKit layout changes, this returns
12
- * `undefined` and `createApp` leaves AppKit on its default cache path.
11
+ * lazy and best-effort: if the installed AppKit layout changes, Lakebase is
12
+ * skipped and the adapter keeps its project-scoped disk persistence.
13
13
  *
14
14
  * @module
15
15
  */
16
16
 
17
+ import { EventEmitter } from "node:events";
17
18
  import { createRequire } from "node:module";
18
19
  import { dirname, join } from "node:path";
19
20
  import { createLakebasePool, getWorkspaceClient, type CacheConfig } from "@databricks/appkit";
21
+ import {
22
+ closeCache,
23
+ createCache,
24
+ flushCache,
25
+ projectCache,
26
+ type CacheOptions,
27
+ } from "@dbx-tools/cache/cache";
20
28
  import { postgresConnectionOptions } from "@dbx-tools/postgres";
21
29
  import { errorUtils, hash, log } from "@dbx-tools/shared-core";
22
- import { LRUCache } from "lru-cache";
23
30
  import { handleOwnershipMigrationError } from "./migration.ts";
24
31
 
25
32
  const logger = log.logger("cache-storage");
@@ -32,9 +39,17 @@ interface CacheEntry<T = unknown> {
32
39
  value: T;
33
40
  }
34
41
 
35
- /** Bounds for the process-local cache layered over persistent AppKit storage. */
36
- export interface L1CacheStorageOptions {
37
- maxBytes?: number;
42
+ /** Options for AppKit's memory-first cache storage adapter. */
43
+ export interface AppKitCacheStorageOptions {
44
+ /** Native AppKit cache configuration. */
45
+ cache?: CacheConfig;
46
+ /** Whether to attempt AppKit's Lakebase PersistentStorage. Defaults to true. */
47
+ preferLakebase?: boolean;
48
+ /** Portable disk scope used when Lakebase is unavailable. */
49
+ diskScope?: string;
50
+ /** Write-behind coalescing delay in milliseconds. */
51
+ writeDelayMs?: number;
52
+ /** Maximum entries retained in the process-local memory primary. */
38
53
  maxEntries?: number;
39
54
  }
40
55
 
@@ -54,95 +69,126 @@ type PersistentStorageConstructor = new (
54
69
 
55
70
  let persistentStorageCtor: PersistentStorageConstructor | undefined | null = null;
56
71
 
57
- /** Process-local LRU that avoids a persistent cache query on every warm hit. */
58
- export class L1CacheStorage implements CacheStorage {
59
- private readonly entries: LRUCache<string, CacheEntry>;
72
+ class AppKitStorageLevel extends EventEmitter {
73
+ readonly opts = {};
74
+
75
+ constructor(private readonly storage: CacheStorage) {
76
+ super();
77
+ }
78
+
79
+ async get<T>(key: string): Promise<T | undefined> {
80
+ const entry = await this.storage.get<T>(key);
81
+ if (!entry) return undefined;
82
+ if (entry.expiry <= Date.now()) {
83
+ await this.storage.delete(key);
84
+ return undefined;
85
+ }
86
+ return entry.value;
87
+ }
88
+
89
+ async set(key: string, value: unknown, ttl?: number): Promise<void> {
90
+ await this.storage.set(key, {
91
+ value,
92
+ expiry: Date.now() + (ttl ?? 60_000),
93
+ });
94
+ }
95
+
96
+ async delete(key: string): Promise<boolean> {
97
+ await this.storage.delete(key);
98
+ return true;
99
+ }
100
+
101
+ clear(): Promise<void> {
102
+ return this.storage.clear();
103
+ }
104
+
105
+ has(key: string): Promise<boolean> {
106
+ return this.storage.has(key);
107
+ }
108
+
109
+ disconnect(): Promise<void> {
110
+ return this.storage.close();
111
+ }
112
+ }
113
+
114
+ /** Memory-first AppKit CacheStorage backed by @dbx-tools/cache. */
115
+ export class AppKitCacheStorage implements CacheStorage {
116
+ private readonly cache: ReturnType<typeof createCache>;
117
+ private readonly keys = new Set<string>();
118
+ private closed = false;
60
119
 
61
120
  constructor(
62
- private readonly storage: CacheStorage,
63
- options: L1CacheStorageOptions = {},
121
+ cache: ReturnType<typeof createCache>,
122
+ private readonly persistent: CacheStorage | undefined,
64
123
  ) {
65
- this.entries = new LRUCache({
66
- max: positiveBound(options.maxEntries, 1_000, "maxEntries"),
67
- maxSize: positiveBound(options.maxBytes, 64 * 1024 * 1024, "maxBytes"),
68
- sizeCalculation: (entry, key) => retainedSize(key, entry),
69
- });
124
+ this.cache = cache;
70
125
  }
71
126
 
72
127
  async get<T>(key: string): Promise<CacheEntry<T> | null> {
73
- const local = this.entries.get(key);
74
- if (local) return local as CacheEntry<T>;
75
- const persisted = await this.storage.get<T>(key);
76
- if (persisted && persisted.expiry > Date.now()) this.retain(key, persisted);
77
- return persisted;
128
+ const entry = await this.cache.get<CacheEntry<T>>(key);
129
+ if (!entry) return null;
130
+ this.keys.add(key);
131
+ return entry;
78
132
  }
79
133
 
80
134
  async set<T>(key: string, entry: CacheEntry<T>): Promise<void> {
81
- await this.storage.set(key, entry);
82
- this.retain(key, entry);
135
+ const ttl = entry.expiry - Date.now();
136
+ if (ttl <= 0) {
137
+ await this.delete(key);
138
+ return;
139
+ }
140
+ await this.cache.set(key, entry, { ttl });
141
+ this.keys.add(key);
83
142
  }
84
143
 
85
144
  async delete(key: string): Promise<void> {
86
- await this.storage.delete(key);
87
- this.entries.delete(key);
145
+ await this.cache.delete(key);
146
+ this.keys.delete(key);
88
147
  }
89
148
 
90
149
  async clear(): Promise<void> {
91
- await this.storage.clear();
92
- this.entries.clear();
150
+ await this.cache.clear();
151
+ this.keys.clear();
93
152
  }
94
153
 
95
154
  async has(key: string): Promise<boolean> {
96
- if (this.entries.has(key)) return true;
97
- return this.storage.has(key);
155
+ return this.cache.has(key);
98
156
  }
99
157
 
100
- size(): Promise<number> {
101
- return this.storage.size();
158
+ async size(): Promise<number> {
159
+ return this.persistent ? this.persistent.size() : this.keys.size;
102
160
  }
103
161
 
104
162
  isPersistent(): boolean {
105
- return this.storage.isPersistent();
163
+ return true;
106
164
  }
107
165
 
108
- healthCheck(): Promise<boolean> {
109
- return this.storage.healthCheck();
166
+ async healthCheck(): Promise<boolean> {
167
+ return this.persistent ? this.persistent.healthCheck() : true;
168
+ }
169
+
170
+ /** Drain tracked write-behind persistence without closing the storage. */
171
+ flush(): Promise<void> {
172
+ return flushCache(this.cache);
110
173
  }
111
174
 
112
175
  async close(): Promise<void> {
113
- this.entries.clear();
114
- await this.storage.close();
176
+ if (this.closed) return;
177
+ await closeCache(this.cache);
178
+ this.closed = true;
179
+ this.keys.clear();
115
180
  }
116
181
 
117
182
  /** Forward AppKit's runtime-only persistent cleanup hook when available. */
118
183
  async cleanupExpired(): Promise<number> {
119
184
  const cleanup = (
120
- this.storage as CacheStorage & {
121
- cleanupExpired?: () => Promise<number>;
122
- }
123
- ).cleanupExpired;
124
- return cleanup ? cleanup.call(this.storage) : 0;
125
- }
126
-
127
- private retain<T>(key: string, entry: CacheEntry<T>): void {
128
- const ttl = entry.expiry - Date.now();
129
- if (ttl > 0) this.entries.set(key, entry, { ttl });
130
- }
131
- }
132
-
133
- function positiveBound(value: number | undefined, fallback: number, name: string): number {
134
- const resolved = value ?? fallback;
135
- if (!Number.isFinite(resolved) || resolved <= 0) {
136
- throw new RangeError(`${name} must be a finite positive number`);
137
- }
138
- return Math.floor(resolved);
139
- }
140
-
141
- function retainedSize(key: string, entry: CacheEntry): number {
142
- try {
143
- return Buffer.byteLength(key) + Buffer.byteLength(JSON.stringify(entry));
144
- } catch {
145
- return Number.MAX_SAFE_INTEGER;
185
+ this.persistent as
186
+ | (CacheStorage & {
187
+ cleanupExpired?: () => Promise<number>;
188
+ })
189
+ | undefined
190
+ )?.cleanupExpired;
191
+ return cleanup && this.persistent ? cleanup.call(this.persistent) : 0;
146
192
  }
147
193
  }
148
194
 
@@ -257,9 +303,9 @@ export async function probeStorage(storage: PersistentStorageBase): Promise<void
257
303
  /**
258
304
  * Build a soft-fail Lakebase cache storage when a pool can be created and
259
305
  * AppKit's PersistentStorage can be loaded. Returns `undefined` otherwise so
260
- * AppKit can fall through to its normal cache path.
306
+ * the caller can use portable disk persistence.
261
307
  */
262
- export async function createSoftPersistentStorage(
308
+ async function createPersistentStorage(
263
309
  cache: CacheConfig | undefined,
264
310
  ): Promise<CacheStorage | undefined> {
265
311
  const PersistentStorage = loadPersistentStorage();
@@ -281,10 +327,7 @@ export async function createSoftPersistentStorage(
281
327
  }
282
328
  await storage.initialize();
283
329
  await probeStorage(storage);
284
- return new L1CacheStorage(storage, {
285
- maxBytes: cache?.maxBytes,
286
- maxEntries: cache?.maxSize,
287
- });
330
+ return storage;
288
331
  } catch (err) {
289
332
  logger.debug("soft persistent cache unavailable", {
290
333
  error: errorUtils.errorMessage(err),
@@ -295,3 +338,27 @@ export async function createSoftPersistentStorage(
295
338
  return undefined;
296
339
  }
297
340
  }
341
+
342
+ /**
343
+ * Create AppKit CacheStorage with bounded memory first and tracked persistence.
344
+ * Lakebase remains AppKit's persistent owner when available; otherwise the
345
+ * shared project-scoped portable disk level is used.
346
+ */
347
+ export async function createAppKitCacheStorage(
348
+ options: AppKitCacheStorageOptions = {},
349
+ ): Promise<AppKitCacheStorage> {
350
+ const nativeCache = options.cache;
351
+ const persistent =
352
+ options.preferLakebase === false ? undefined : await createPersistentStorage(nativeCache);
353
+ const common: CacheOptions = {
354
+ maxEntries: options.maxEntries ?? nativeCache?.maxSize,
355
+ writeDelayMs: options.writeDelayMs,
356
+ };
357
+ const cache = persistent
358
+ ? createCache({
359
+ ...common,
360
+ levels: [{ name: "appkit-persistent", store: new AppKitStorageLevel(persistent) }],
361
+ })
362
+ : projectCache(options.diskScope ?? "appkit", common);
363
+ return new AppKitCacheStorage(cache, persistent);
364
+ }
@@ -1,28 +1,23 @@
1
1
  /**
2
- * AppKit-global filesystem cache with user-isolated process-local LRU values.
2
+ * AppKit-global filesystem cache with user-isolated process-local values.
3
3
  *
4
4
  * @module
5
5
  */
6
6
 
7
7
  import { Plugin, toPlugin, type BasePluginConfig, type PluginManifest } from "@databricks/appkit";
8
+ import { cacheKeys, closeCache, createCache, getOrSet } from "@dbx-tools/cache/cache";
8
9
  import { errorUtils, log, object } from "@dbx-tools/shared-core";
9
10
  import { posixPath, type CacheValue, type FileSystemCache } from "@dbx-tools/shared-fs";
10
- import { LRUCache } from "lru-cache";
11
11
 
12
12
  const DEFAULT_TTL_MS = 60_000;
13
13
  const DEFAULT_INVALIDATION_BATCH_MS = 25;
14
14
  const DEFAULT_MAX_ENTRIES = 1_000;
15
- const DEFAULT_MAX_BYTES = 64 * 1024 * 1024;
16
15
  const DEFAULT_MAX_SCOPES = 256;
17
16
  const logger = log.logger("files-cache");
18
17
 
19
- interface RetainedEntry {
20
- value: CacheValue;
21
- }
22
-
23
18
  interface ScopeState {
24
19
  cache: FileSystemCache;
25
- entries: LRUCache<string, RetainedEntry>;
20
+ entries: ReturnType<typeof createCache>;
26
21
  group: GroupState;
27
22
  scope: ResolvedFilesCacheScope;
28
23
  sources: Map<string, Promise<object>>;
@@ -36,7 +31,6 @@ interface GroupState {
36
31
 
37
32
  interface ResolvedFilesCacheConfig {
38
33
  invalidationBatchMs: number;
39
- maxBytes: number;
40
34
  maxEntries: number;
41
35
  maxScopes: number;
42
36
  ttlMs: number;
@@ -57,8 +51,6 @@ export interface FilesCachePluginConfig extends BasePluginConfig {
57
51
  invalidationBatchMs?: number;
58
52
  /** Maximum entries retained in each user L1. Defaults to 1,000. */
59
53
  maxEntries?: number;
60
- /** Maximum retained bytes in each user L1. Defaults to 64 MiB. */
61
- maxBytes?: number;
62
54
  /** Maximum active user scopes retained by the process. Defaults to 256. */
63
55
  maxScopes?: number;
64
56
  }
@@ -101,7 +93,7 @@ export interface FilesCacheExports {
101
93
  export class FilesCacheManager {
102
94
  private readonly config: ResolvedFilesCacheConfig;
103
95
  private readonly groups = new Map<string, GroupState>();
104
- private readonly scopes: LRUCache<string, ScopeState>;
96
+ private readonly scopes: ReturnType<typeof createCache>;
105
97
 
106
98
  constructor(config: FilesCachePluginConfig = {}) {
107
99
  this.config = {
@@ -112,15 +104,9 @@ export class FilesCacheManager {
112
104
  "invalidationBatchMs",
113
105
  ),
114
106
  maxEntries: positiveInteger(config.maxEntries, DEFAULT_MAX_ENTRIES, "maxEntries"),
115
- maxBytes: positiveInteger(config.maxBytes, DEFAULT_MAX_BYTES, "maxBytes"),
116
107
  maxScopes: positiveInteger(config.maxScopes, DEFAULT_MAX_SCOPES, "maxScopes"),
117
108
  };
118
- this.scopes = new LRUCache({
119
- max: this.config.maxScopes,
120
- dispose: (state) => {
121
- state.group.scopes.delete(state);
122
- },
123
- });
109
+ this.scopes = createCache({ maxEntries: this.config.maxScopes, useClone: false });
124
110
  }
125
111
 
126
112
  /** Return one user-isolated cache within a host/workspace invalidation group. */
@@ -158,12 +144,23 @@ export class FilesCacheManager {
158
144
  userKey: scope.userKey,
159
145
  workspaceId: scope.workspaceId,
160
146
  });
161
- let state = this.scopes.get(scopeKey);
147
+ let state = await this.scopes.get<ScopeState>(scopeKey);
162
148
  if (!state) {
149
+ if ([...cacheKeys(this.scopes)].length >= this.config.maxScopes) {
150
+ const oldestKey = cacheKeys(this.scopes).next().value;
151
+ if (oldestKey) {
152
+ const oldest = await this.scopes.get<ScopeState>(oldestKey);
153
+ if (oldest) {
154
+ oldest.group.scopes.delete(oldest);
155
+ await closeCache(oldest.entries);
156
+ }
157
+ await this.scopes.delete(oldestKey);
158
+ }
159
+ }
163
160
  const group = this.group(scope);
164
161
  state = this.createScope(scope, group);
165
162
  group.scopes.add(state);
166
- this.scopes.set(scopeKey, state);
163
+ await this.scopes.set(scopeKey, state);
167
164
  }
168
165
  return state;
169
166
  }
@@ -180,7 +177,11 @@ export class FilesCacheManager {
180
177
  group.timer = undefined;
181
178
  }
182
179
  await this.flush();
183
- this.scopes.clear();
180
+ for (const key of cacheKeys(this.scopes)) {
181
+ const state = await this.scopes.get<ScopeState>(key);
182
+ if (state) await closeCache(state.entries);
183
+ }
184
+ await closeCache(this.scopes);
184
185
  this.groups.clear();
185
186
  }
186
187
 
@@ -203,11 +204,7 @@ export class FilesCacheManager {
203
204
  const state = {} as ScopeState;
204
205
  state.scope = scope;
205
206
  state.group = group;
206
- state.entries = new LRUCache({
207
- max: this.config.maxEntries,
208
- maxSize: this.config.maxBytes,
209
- sizeCalculation: (entry, cacheKey) => retainedSize(cacheKey, entry),
210
- });
207
+ state.entries = createCache({ maxEntries: this.config.maxEntries, useClone: false });
211
208
  state.sources = new Map();
212
209
  state.cache = {
213
210
  read: <T extends CacheValue>(cacheKey: string, load: () => T | Promise<T>) =>
@@ -224,16 +221,21 @@ export class FilesCacheManager {
224
221
  load: () => T | Promise<T>,
225
222
  ): Promise<T> {
226
223
  if (!state.group.pending.has(key)) {
227
- const retained = state.entries.get(key);
228
- if (retained) return retained.value as T;
224
+ const retained = await state.entries.get<T>(key);
225
+ if (retained !== undefined) return retained;
229
226
  }
230
- const value = await load();
231
- state.entries.set(key, { value }, { ttl: state.scope.ttlMs });
232
- return value;
227
+ if (state.group.pending.has(key)) {
228
+ const value = await load();
229
+ await state.entries.set(key, value, { ttl: state.scope.ttlMs });
230
+ return value;
231
+ }
232
+ return (await getOrSet(state.entries, key, () => Promise.resolve(load()), {
233
+ ttl: state.scope.ttlMs,
234
+ })) as T;
233
235
  }
234
236
 
235
237
  private *keys(state: ScopeState): Iterable<string> {
236
- for (const key of state.entries.keys()) {
238
+ for (const key of cacheKeys(state.entries)) {
237
239
  if (!state.group.pending.has(key)) yield key;
238
240
  }
239
241
  }
@@ -259,12 +261,12 @@ export class FilesCacheManager {
259
261
  const keys = [...group.pending];
260
262
  group.pending.clear();
261
263
  for (const state of group.scopes) {
262
- for (const key of keys) state.entries.delete(key);
264
+ await Promise.all(keys.map((key) => state.entries.delete(key)));
263
265
  }
264
266
  }
265
267
  }
266
268
 
267
- /** AppKit plugin that owns process-local filesystem LRU caches. */
269
+ /** AppKit plugin that owns process-local filesystem caches. */
268
270
  export class FilesCachePlugin extends Plugin<FilesCachePluginConfig> {
269
271
  static manifest = {
270
272
  name: "files-cache",
@@ -324,11 +326,3 @@ function positiveDuration(value: number | undefined, fallback: number, name: str
324
326
  function positiveInteger(value: number | undefined, fallback: number, name: string): number {
325
327
  return Math.floor(positiveDuration(value, fallback, name));
326
328
  }
327
-
328
- function retainedSize(key: string, entry: RetainedEntry): number {
329
- try {
330
- return Buffer.byteLength(key) + Buffer.byteLength(JSON.stringify(entry.value));
331
- } catch {
332
- return Number.MAX_SAFE_INTEGER;
333
- }
334
- }