@cratis/arc 20.17.1 → 20.17.3

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 (102) hide show
  1. package/Globals.ts +13 -0
  2. package/dist/cjs/Globals.d.ts +1 -0
  3. package/dist/cjs/Globals.d.ts.map +1 -1
  4. package/dist/cjs/Globals.js +1 -0
  5. package/dist/cjs/Globals.js.map +1 -1
  6. package/dist/cjs/queries/QueryInstanceCache.d.ts +2 -1
  7. package/dist/cjs/queries/QueryInstanceCache.d.ts.map +1 -1
  8. package/dist/cjs/queries/QueryInstanceCache.js +10 -2
  9. package/dist/cjs/queries/QueryInstanceCache.js.map +1 -1
  10. package/dist/cjs/queries/ServerSentEventHubConnection.d.ts.map +1 -1
  11. package/dist/cjs/queries/ServerSentEventHubConnection.js +20 -7
  12. package/dist/cjs/queries/ServerSentEventHubConnection.js.map +1 -1
  13. package/dist/cjs/queries/for_QueryInstanceCache/when_releasing/with_retention_timeout/after_timeout_elapses.d.ts +2 -0
  14. package/dist/cjs/queries/for_QueryInstanceCache/when_releasing/with_retention_timeout/after_timeout_elapses.d.ts.map +1 -0
  15. package/dist/cjs/queries/for_QueryInstanceCache/when_releasing/with_retention_timeout/before_timeout_elapses.d.ts +2 -0
  16. package/dist/cjs/queries/for_QueryInstanceCache/when_releasing/with_retention_timeout/before_timeout_elapses.d.ts.map +1 -0
  17. package/dist/cjs/queries/for_QueryInstanceCache/when_setting_last_result/with_changed_data.d.ts +2 -0
  18. package/dist/cjs/queries/for_QueryInstanceCache/when_setting_last_result/with_changed_data.d.ts.map +1 -0
  19. package/dist/cjs/queries/for_QueryInstanceCache/when_setting_last_result/with_identical_data_as_previous_result.d.ts +2 -0
  20. package/dist/cjs/queries/for_QueryInstanceCache/when_setting_last_result/with_identical_data_as_previous_result.d.ts.map +1 -0
  21. package/dist/cjs/queries/for_QueryInstanceCache/when_setting_last_result/without_previous_result.d.ts +2 -0
  22. package/dist/cjs/queries/for_QueryInstanceCache/when_setting_last_result/without_previous_result.d.ts.map +1 -0
  23. package/dist/esm/Globals.d.ts +1 -0
  24. package/dist/esm/Globals.d.ts.map +1 -1
  25. package/dist/esm/Globals.js +1 -0
  26. package/dist/esm/Globals.js.map +1 -1
  27. package/dist/esm/queries/QueryInstanceCache.d.ts +2 -1
  28. package/dist/esm/queries/QueryInstanceCache.d.ts.map +1 -1
  29. package/dist/esm/queries/QueryInstanceCache.js +10 -2
  30. package/dist/esm/queries/QueryInstanceCache.js.map +1 -1
  31. package/dist/esm/queries/ServerSentEventHubConnection.d.ts.map +1 -1
  32. package/dist/esm/queries/ServerSentEventHubConnection.js +20 -7
  33. package/dist/esm/queries/ServerSentEventHubConnection.js.map +1 -1
  34. package/dist/esm/queries/for_QueryInstanceCache/when_acquiring/after_release.js +1 -1
  35. package/dist/esm/queries/for_QueryInstanceCache/when_acquiring/after_release.js.map +1 -1
  36. package/dist/esm/queries/for_QueryInstanceCache/when_deferring_dispose/with_cancellation_before_timeout.js +1 -1
  37. package/dist/esm/queries/for_QueryInstanceCache/when_deferring_dispose/with_cancellation_before_timeout.js.map +1 -1
  38. package/dist/esm/queries/for_QueryInstanceCache/when_deferring_dispose/without_cancellation.js +1 -1
  39. package/dist/esm/queries/for_QueryInstanceCache/when_deferring_dispose/without_cancellation.js.map +1 -1
  40. package/dist/esm/queries/for_QueryInstanceCache/when_disposing/with_pending_deferred_cleanup.js +1 -1
  41. package/dist/esm/queries/for_QueryInstanceCache/when_disposing/with_pending_deferred_cleanup.js.map +1 -1
  42. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber.js +14 -3
  43. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber.js.map +1 -1
  44. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/with_retention_timeout/after_timeout_elapses.d.ts +2 -0
  45. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/with_retention_timeout/after_timeout_elapses.d.ts.map +1 -0
  46. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/with_retention_timeout/after_timeout_elapses.js +21 -0
  47. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/with_retention_timeout/after_timeout_elapses.js.map +1 -0
  48. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/with_retention_timeout/before_timeout_elapses.d.ts +2 -0
  49. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/with_retention_timeout/before_timeout_elapses.d.ts.map +1 -0
  50. package/dist/esm/queries/for_QueryInstanceCache/{when_acquiring/after_release_in_development_mode.js → when_releasing/with_retention_timeout/before_timeout_elapses.js} +6 -7
  51. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/with_retention_timeout/before_timeout_elapses.js.map +1 -0
  52. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/with_changed_data.d.ts +2 -0
  53. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/with_changed_data.d.ts.map +1 -0
  54. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/with_changed_data.js +19 -0
  55. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/with_changed_data.js.map +1 -0
  56. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/with_identical_data_as_previous_result.d.ts +2 -0
  57. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/with_identical_data_as_previous_result.d.ts.map +1 -0
  58. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/with_identical_data_as_previous_result.js +19 -0
  59. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/with_identical_data_as_previous_result.js.map +1 -0
  60. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/without_previous_result.d.ts +2 -0
  61. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/without_previous_result.d.ts.map +1 -0
  62. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/without_previous_result.js +16 -0
  63. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/without_previous_result.js.map +1 -0
  64. package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_pending_deferred_cleanup.js +1 -1
  65. package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_pending_deferred_cleanup.js.map +1 -1
  66. package/dist/esm/queries/for_ServerSentEventHubConnection/when_subscribing/and_subscribe_post_fails.js +31 -6
  67. package/dist/esm/queries/for_ServerSentEventHubConnection/when_subscribing/and_subscribe_post_fails.js.map +1 -1
  68. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  69. package/package.json +1 -1
  70. package/queries/QueryInstanceCache.ts +21 -9
  71. package/queries/ServerSentEventHubConnection.ts +28 -7
  72. package/queries/for_QueryInstanceCache/when_acquiring/after_release.ts +1 -1
  73. package/queries/for_QueryInstanceCache/when_deferring_dispose/with_cancellation_before_timeout.ts +1 -1
  74. package/queries/for_QueryInstanceCache/when_deferring_dispose/without_cancellation.ts +1 -1
  75. package/queries/for_QueryInstanceCache/when_disposing/with_pending_deferred_cleanup.ts +1 -1
  76. package/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber.ts +16 -3
  77. package/queries/for_QueryInstanceCache/when_releasing/with_retention_timeout/after_timeout_elapses.ts +28 -0
  78. package/queries/for_QueryInstanceCache/{when_acquiring/after_release_in_development_mode.ts → when_releasing/with_retention_timeout/before_timeout_elapses.ts} +6 -7
  79. package/queries/for_QueryInstanceCache/when_setting_last_result/with_changed_data.ts +26 -0
  80. package/queries/for_QueryInstanceCache/when_setting_last_result/with_identical_data_as_previous_result.ts +27 -0
  81. package/queries/for_QueryInstanceCache/when_setting_last_result/without_previous_result.ts +22 -0
  82. package/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_pending_deferred_cleanup.ts +1 -1
  83. package/queries/for_ServerSentEventHubConnection/when_subscribing/and_subscribe_post_fails.ts +43 -10
  84. package/dist/cjs/queries/for_QueryInstanceCache/when_acquiring/after_release_in_development_mode.d.ts +0 -2
  85. package/dist/cjs/queries/for_QueryInstanceCache/when_acquiring/after_release_in_development_mode.d.ts.map +0 -1
  86. package/dist/cjs/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_in_development_mode.d.ts +0 -2
  87. package/dist/cjs/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_in_development_mode.d.ts.map +0 -1
  88. package/dist/cjs/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.d.ts +0 -2
  89. package/dist/cjs/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.d.ts.map +0 -1
  90. package/dist/esm/queries/for_QueryInstanceCache/when_acquiring/after_release_in_development_mode.d.ts +0 -2
  91. package/dist/esm/queries/for_QueryInstanceCache/when_acquiring/after_release_in_development_mode.d.ts.map +0 -1
  92. package/dist/esm/queries/for_QueryInstanceCache/when_acquiring/after_release_in_development_mode.js.map +0 -1
  93. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_in_development_mode.d.ts +0 -2
  94. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_in_development_mode.d.ts.map +0 -1
  95. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_in_development_mode.js +0 -28
  96. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_in_development_mode.js.map +0 -1
  97. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.d.ts +0 -2
  98. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.d.ts.map +0 -1
  99. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.js +0 -28
  100. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.js.map +0 -1
  101. package/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_in_development_mode.ts +0 -36
  102. package/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.ts +0 -36
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cratis/arc",
3
- "version": "20.17.1",
3
+ "version": "20.17.3",
4
4
  "description": "",
5
5
  "author": "Cratis",
6
6
  "license": "MIT",
@@ -71,13 +71,12 @@ export class QueryInstanceCache {
71
71
 
72
72
  /**
73
73
  * Initializes a new instance of {@link QueryInstanceCache}.
74
- * @param development Accepted for API compatibility. No longer changes teardown behavior
75
- * teardown is always deferred to handle React StrictMode re-mounts in any environment.
74
+ * @param retentionMs How long in milliseconds to keep a cache entry alive after the last
75
+ * subscriber releases it before evicting the subscription and cached data. A non-zero
76
+ * value lets users navigate away and back without losing cached data. Defaults to
77
+ * 30 000 ms. Pass 0 for immediate eviction.
76
78
  */
77
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
78
- constructor(development: boolean = false) {
79
- // The development parameter is kept for API compatibility only.
80
- // Teardown is always deferred regardless of this flag.
79
+ constructor(private readonly _retentionMs: number = 30_000) {
81
80
  }
82
81
 
83
82
  /**
@@ -170,8 +169,20 @@ export class QueryInstanceCache {
170
169
  const entry = this._entries.get(key);
171
170
 
172
171
  if (entry) {
172
+ const previousResult = entry.lastResult as QueryResultWithState<TDataType> | undefined;
173
173
  entry.lastResult = result as QueryResultWithState<unknown>;
174
174
 
175
+ // Suppress re-renders when the server re-sends identical data after a reconnect.
176
+ // We only compare `data` and `isSuccess` — other fields (e.g. changeSet) are
177
+ // ephemeral and do not affect what the user sees.
178
+ if (
179
+ previousResult !== undefined &&
180
+ previousResult.isSuccess === result.isSuccess &&
181
+ JSON.stringify(previousResult.data) === JSON.stringify(result.data)
182
+ ) {
183
+ return;
184
+ }
185
+
175
186
  for (const listener of entry.listeners) {
176
187
  (listener as QueryCacheListener<TDataType>)(result);
177
188
  }
@@ -245,8 +256,9 @@ export class QueryInstanceCache {
245
256
  entry.subscriberCount--;
246
257
 
247
258
  if (entry.subscriberCount <= 0) {
248
- // Defer both teardown and deletion so React StrictMode re-mounts in any environment
249
- // can cancel by calling acquire() before the timeout fires.
259
+ // Defer both teardown and eviction. React StrictMode re-mounts can cancel by
260
+ // calling acquire() before the timeout fires. A non-zero _retentionMs keeps the
261
+ // entry alive so users navigating back quickly see cached data immediately.
250
262
  entry.pendingCleanup = setTimeout(() => {
251
263
  const current = this._entries.get(key);
252
264
 
@@ -257,7 +269,7 @@ export class QueryInstanceCache {
257
269
  current.pendingCleanup = undefined;
258
270
  this._entries.delete(key);
259
271
  }
260
- }, 0);
272
+ }, this._retentionMs);
261
273
  }
262
274
  }
263
275
  }
@@ -300,29 +300,50 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
300
300
  sub.callback(QueryResult.unauthorized());
301
301
  }
302
302
 
303
- private sendSubscribe(queryId: string, request: SubscriptionRequest): void {
304
- if (!this._connectionId) return;
303
+ private sendSubscribe(queryId: string, request: SubscriptionRequest, attempt: number = 0): void {
304
+ if (!this._connectionId || this._disconnected) return;
305
+
306
+ // Capture the connection ID so retries can detect if a reconnect has already fired
307
+ // for a different reason and made this retry obsolete.
308
+ const connectionId = this._connectionId;
305
309
 
306
310
  const body = {
307
- connectionId: this._connectionId,
311
+ connectionId,
308
312
  queryId,
309
313
  request,
310
314
  };
311
315
 
312
316
  const customHeaders = Globals.httpHeadersCallback?.() ?? {};
313
317
 
318
+ // Maximum number of subscribe retries before falling back to a full SSE reconnect.
319
+ // In a round-robin load-balanced deployment the subscribe POST may land on a different
320
+ // backend instance than the one holding the SSE connection. Retrying gives the load
321
+ // balancer the chance to route a subsequent attempt to the correct instance without
322
+ // tearing down the SSE connection unnecessarily. With N backend instances at most
323
+ // N-1 retries are needed, so 3 retries covers deployments with up to 4 replicas.
324
+ const maxRetries = 3;
325
+ const retryDelayMs = 200;
326
+
314
327
  fetch(this._subscribeUrl, {
315
328
  method: 'POST',
316
329
  headers: { 'Content-Type': 'application/json', ...customHeaders },
317
330
  body: JSON.stringify(body),
318
331
  }).then(response => {
319
332
  if (!response.ok) {
320
- console.warn(`SSE hub: subscribe POST for '${queryId}' returned ${response.status}, reconnecting`);
321
- this.reconnect();
333
+ if (attempt < maxRetries && this._connectionId === connectionId && !this._disconnected) {
334
+ setTimeout(() => this.sendSubscribe(queryId, request, attempt + 1), retryDelayMs * (attempt + 1));
335
+ } else if (this._connectionId === connectionId && !this._disconnected) {
336
+ console.warn(`SSE hub: subscribe POST for '${queryId}' returned ${response.status} after ${attempt + 1} attempt(s), reconnecting`);
337
+ this.reconnect();
338
+ }
322
339
  }
323
340
  }).catch(error => {
324
- console.error(`SSE hub: subscribe POST failed for '${queryId}', reconnecting`, error);
325
- this.reconnect();
341
+ if (attempt < maxRetries && this._connectionId === connectionId && !this._disconnected) {
342
+ setTimeout(() => this.sendSubscribe(queryId, request, attempt + 1), retryDelayMs * (attempt + 1));
343
+ } else if (this._connectionId === connectionId && !this._disconnected) {
344
+ console.error(`SSE hub: subscribe POST failed for '${queryId}' after ${attempt + 1} attempt(s), reconnecting`, error);
345
+ this.reconnect();
346
+ }
326
347
  });
327
348
  }
328
349
 
@@ -10,7 +10,7 @@ describe('when acquiring after release', () => {
10
10
  beforeEach(() => {
11
11
  vi.useFakeTimers();
12
12
  teardownCalled = false;
13
- cache = new QueryInstanceCache();
13
+ cache = new QueryInstanceCache(0);
14
14
  cache.getOrCreate('MyQuery::', () => ({}));
15
15
  cache.acquire('MyQuery::');
16
16
  cache.setTeardown('MyQuery::', () => { teardownCalled = true; });
@@ -10,7 +10,7 @@ describe('when canceling deferred dispose before timeout fires', () => {
10
10
  beforeEach(() => {
11
11
  vi.useFakeTimers();
12
12
  teardownCalled = false;
13
- cache = new QueryInstanceCache(true);
13
+ cache = new QueryInstanceCache(0);
14
14
  cache.getOrCreate('MyQuery::', () => ({}));
15
15
  cache.acquire('MyQuery::');
16
16
  cache.setTeardown('MyQuery::', () => { teardownCalled = true; });
@@ -10,7 +10,7 @@ describe('when deferring dispose without cancellation', () => {
10
10
  beforeEach(() => {
11
11
  vi.useFakeTimers();
12
12
  teardownCalled = false;
13
- cache = new QueryInstanceCache(true);
13
+ cache = new QueryInstanceCache(0);
14
14
  cache.getOrCreate('MyQuery::', () => ({}));
15
15
  cache.acquire('MyQuery::');
16
16
  cache.setTeardown('MyQuery::', () => { teardownCalled = true; });
@@ -10,7 +10,7 @@ describe('when disposing with pending deferred cleanup', () => {
10
10
  beforeEach(() => {
11
11
  vi.useFakeTimers();
12
12
  teardownCalled = 0;
13
- cache = new QueryInstanceCache(true);
13
+ cache = new QueryInstanceCache(0);
14
14
  cache.getOrCreate('MyQuery::', () => ({}));
15
15
  cache.acquire('MyQuery::');
16
16
  cache.setTeardown('MyQuery::', () => { teardownCalled++; });
@@ -5,19 +5,32 @@ import { QueryInstanceCache } from '../../QueryInstanceCache';
5
5
 
6
6
  describe('when releasing the only subscriber', () => {
7
7
  let cache: QueryInstanceCache;
8
+ let teardownCalled: boolean;
8
9
 
9
10
  beforeEach(() => {
10
11
  vi.useFakeTimers();
11
- cache = new QueryInstanceCache();
12
+ teardownCalled = false;
13
+ cache = new QueryInstanceCache(0);
12
14
  cache.getOrCreate('MyQuery::', () => ({}));
13
15
  cache.acquire('MyQuery::');
16
+ cache.setTeardown('MyQuery::', () => { teardownCalled = true; });
14
17
  cache.release('MyQuery::');
15
- vi.advanceTimersByTime(0);
16
18
  });
17
19
 
18
20
  afterEach(() => {
19
21
  vi.useRealTimers();
20
22
  });
21
23
 
22
- it('should evict the entry', () => cache.has('MyQuery::').should.be.false);
24
+ it('should not call teardown synchronously', () => teardownCalled.should.be.false);
25
+ it('should keep the entry before the timer fires', () => cache.has('MyQuery::').should.be.true);
26
+ it('should still report as subscribed before the timer fires', () => cache.isSubscribed('MyQuery::').should.be.true);
27
+
28
+ describe('and the deferred timer fires', () => {
29
+ beforeEach(() => {
30
+ vi.advanceTimersByTime(0);
31
+ });
32
+
33
+ it('should call teardown', () => teardownCalled.should.be.true);
34
+ it('should evict the entry', () => cache.has('MyQuery::').should.be.false);
35
+ });
23
36
  });
@@ -0,0 +1,28 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { QueryInstanceCache } from '../../../QueryInstanceCache';
5
+
6
+ describe('when releasing the only subscriber with a retention timeout, after the timeout elapses', () => {
7
+ let cache: QueryInstanceCache;
8
+ let teardownCalled: boolean;
9
+
10
+ beforeEach(() => {
11
+ vi.useFakeTimers();
12
+ teardownCalled = false;
13
+ cache = new QueryInstanceCache(5000);
14
+ cache.getOrCreate('MyQuery::', () => ({}));
15
+ cache.acquire('MyQuery::');
16
+ cache.setTeardown('MyQuery::', () => { teardownCalled = true; });
17
+ cache.release('MyQuery::');
18
+
19
+ vi.advanceTimersByTime(5000);
20
+ });
21
+
22
+ afterEach(() => {
23
+ vi.useRealTimers();
24
+ });
25
+
26
+ it('should call teardown', () => teardownCalled.should.be.true);
27
+ it('should evict the entry from the cache', () => cache.has('MyQuery::').should.be.false);
28
+ });
@@ -1,24 +1,23 @@
1
1
  // Copyright (c) Cratis. All rights reserved.
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
- import { QueryInstanceCache } from '../../QueryInstanceCache';
4
+ import { QueryInstanceCache } from '../../../QueryInstanceCache';
5
5
 
6
- describe('when acquiring after release in development mode', () => {
6
+ describe('when releasing the only subscriber with a retention timeout, before the timeout elapses', () => {
7
7
  let cache: QueryInstanceCache;
8
8
  let teardownCalled: boolean;
9
9
 
10
10
  beforeEach(() => {
11
11
  vi.useFakeTimers();
12
12
  teardownCalled = false;
13
- cache = new QueryInstanceCache(true);
13
+ cache = new QueryInstanceCache(5000);
14
14
  cache.getOrCreate('MyQuery::', () => ({}));
15
15
  cache.acquire('MyQuery::');
16
16
  cache.setTeardown('MyQuery::', () => { teardownCalled = true; });
17
17
  cache.release('MyQuery::');
18
18
 
19
- // Re-acquire before the deferred timer fires (simulates StrictMode re-mount).
20
- cache.acquire('MyQuery::');
21
- vi.advanceTimersByTime(0);
19
+ // Advance to just before the retention window expires.
20
+ vi.advanceTimersByTime(4999);
22
21
  });
23
22
 
24
23
  afterEach(() => {
@@ -26,6 +25,6 @@ describe('when acquiring after release in development mode', () => {
26
25
  });
27
26
 
28
27
  it('should not call teardown', () => teardownCalled.should.be.false);
29
- it('should keep the entry', () => cache.has('MyQuery::').should.be.true);
28
+ it('should keep the entry in the cache', () => cache.has('MyQuery::').should.be.true);
30
29
  it('should still report as subscribed', () => cache.isSubscribed('MyQuery::').should.be.true);
31
30
  });
@@ -0,0 +1,26 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { QueryInstanceCache } from '../../QueryInstanceCache';
5
+ import { QueryResultWithState } from '../../QueryResultWithState';
6
+
7
+ describe('when setting the last result with changed data', () => {
8
+ let cache: QueryInstanceCache;
9
+ let listenerCallCount: number;
10
+
11
+ beforeEach(() => {
12
+ listenerCallCount = 0;
13
+ cache = new QueryInstanceCache();
14
+ cache.getOrCreate('MyQuery::', () => ({}));
15
+ cache.addListener<string[]>('MyQuery::', () => listenerCallCount++);
16
+
17
+ const first = QueryResultWithState.empty<string[]>(['a', 'b']);
18
+ cache.setLastResult('MyQuery::', first);
19
+ listenerCallCount = 0; // reset after initial push
20
+
21
+ const second = QueryResultWithState.empty<string[]>(['a', 'b', 'c']);
22
+ cache.setLastResult('MyQuery::', second);
23
+ });
24
+
25
+ it('should notify listeners', () => listenerCallCount.should.equal(1));
26
+ });
@@ -0,0 +1,27 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { QueryInstanceCache } from '../../QueryInstanceCache';
5
+ import { QueryResultWithState } from '../../QueryResultWithState';
6
+
7
+ describe('when setting the last result with identical data as the previous result', () => {
8
+ let cache: QueryInstanceCache;
9
+ let listenerCallCount: number;
10
+
11
+ beforeEach(() => {
12
+ listenerCallCount = 0;
13
+ cache = new QueryInstanceCache();
14
+ cache.getOrCreate('MyQuery::', () => ({}));
15
+ cache.addListener<string[]>('MyQuery::', () => listenerCallCount++);
16
+
17
+ const first = QueryResultWithState.empty<string[]>(['a', 'b']);
18
+ cache.setLastResult('MyQuery::', first);
19
+ listenerCallCount = 0; // reset after initial push
20
+
21
+ // Second push with data that serializes identically
22
+ const second = QueryResultWithState.empty<string[]>(['a', 'b']);
23
+ cache.setLastResult('MyQuery::', second);
24
+ });
25
+
26
+ it('should not notify listeners', () => listenerCallCount.should.equal(0));
27
+ });
@@ -0,0 +1,22 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { QueryInstanceCache } from '../../QueryInstanceCache';
5
+ import { QueryResultWithState } from '../../QueryResultWithState';
6
+
7
+ describe('when setting the last result without a previous result', () => {
8
+ let cache: QueryInstanceCache;
9
+ let listenerCallCount: number;
10
+
11
+ beforeEach(() => {
12
+ listenerCallCount = 0;
13
+ cache = new QueryInstanceCache();
14
+ cache.getOrCreate('MyQuery::', () => ({}));
15
+ cache.addListener<string[]>('MyQuery::', () => listenerCallCount++);
16
+
17
+ const result = QueryResultWithState.empty<string[]>(['a', 'b']);
18
+ cache.setLastResult('MyQuery::', result);
19
+ });
20
+
21
+ it('should notify listeners', () => listenerCallCount.should.equal(1));
22
+ });
@@ -10,7 +10,7 @@ describe('when tearing down all subscriptions with pending deferred cleanup', ()
10
10
  beforeEach(() => {
11
11
  vi.useFakeTimers();
12
12
  teardownCalled = false;
13
- cache = new QueryInstanceCache(true);
13
+ cache = new QueryInstanceCache(0);
14
14
  cache.getOrCreate('MyQuery::', () => ({}));
15
15
  cache.acquire('MyQuery::');
16
16
  cache.setTeardown('MyQuery::', () => { teardownCalled = true; });
@@ -6,46 +6,79 @@ import { a_server_sent_event_hub_connection } from '../given/a_server_sent_event
6
6
  import { given } from '../../../given';
7
7
  import { HubMessageType } from '../../WebSocketHubConnection';
8
8
 
9
+ async function exhaustRetries(clock: sinon.SinonFakeTimers): Promise<void> {
10
+ // Three retry delays: 200 ms (attempt 1), 400 ms (attempt 2), 600 ms (attempt 3).
11
+ // Two microtask flushes are needed per tick: the first lets a rejected fetch propagate
12
+ // through the .then() step (which passes it forward), and the second lets .catch() fire
13
+ // and schedule the next setTimeout. For resolved-but-not-ok responses a single flush
14
+ // is enough, but two is harmless.
15
+ for (const delay of [200, 400, 600]) {
16
+ clock.tick(delay);
17
+ await Promise.resolve();
18
+ await Promise.resolve();
19
+ }
20
+ }
21
+
9
22
  describe('when subscribe POST returns a non-OK status', given(a_server_sent_event_hub_connection, context => {
23
+ let clock: sinon.SinonFakeTimers;
24
+
10
25
  beforeEach(async () => {
11
26
  context.setup();
27
+ clock = sinon.useFakeTimers({ toFake: ['setTimeout'] });
12
28
 
13
- // Make the subscribe POST resolve with a 404 (connection not found on server).
14
29
  context.fetchStub.resolves({ ok: false, status: 404 });
15
30
 
16
31
  context.connection.subscribe('q1', { queryName: 'MyQuery' }, sinon.stub());
17
32
  context.simulateOpen();
18
33
  context.simulateMessage({ type: HubMessageType.Connected, payload: 'conn-abc' });
19
34
 
20
- // Allow the fetch promise chain (.then) to execute.
21
- await new Promise(resolve => setTimeout(resolve, 0));
35
+ // Flush the initial fetch promise chain.
36
+ await Promise.resolve();
22
37
  });
23
38
 
24
- afterEach(() => sinon.restore());
39
+ afterEach(() => {
40
+ clock.restore();
41
+ sinon.restore();
42
+ });
43
+
44
+ it('should not immediately reconnect before retries are exhausted', () => {
45
+ (context.policy.schedule as sinon.SinonStub).called.should.be.false;
46
+ });
25
47
 
26
- it('should schedule a reconnect via the policy', () => {
48
+ it('should schedule a reconnect via the policy after exhausting all retries', async () => {
49
+ await exhaustRetries(clock);
27
50
  (context.policy.schedule as sinon.SinonStub).calledOnce.should.be.true;
28
51
  });
29
52
  }));
30
53
 
31
54
  describe('when subscribe POST rejects with a network error', given(a_server_sent_event_hub_connection, context => {
55
+ let clock: sinon.SinonFakeTimers;
56
+
32
57
  beforeEach(async () => {
33
58
  context.setup();
59
+ clock = sinon.useFakeTimers({ toFake: ['setTimeout'] });
34
60
 
35
- // Make the subscribe POST reject (network failure).
36
61
  context.fetchStub.rejects(new Error('Network error'));
37
62
 
38
63
  context.connection.subscribe('q1', { queryName: 'MyQuery' }, sinon.stub());
39
64
  context.simulateOpen();
40
65
  context.simulateMessage({ type: HubMessageType.Connected, payload: 'conn-abc' });
41
66
 
42
- // Allow the fetch promise chain (.catch) to execute.
43
- await new Promise(resolve => setTimeout(resolve, 0));
67
+ // Flush the initial fetch promise chain.
68
+ await Promise.resolve();
69
+ });
70
+
71
+ afterEach(() => {
72
+ clock.restore();
73
+ sinon.restore();
44
74
  });
45
75
 
46
- afterEach(() => sinon.restore());
76
+ it('should not immediately reconnect before retries are exhausted', () => {
77
+ (context.policy.schedule as sinon.SinonStub).called.should.be.false;
78
+ });
47
79
 
48
- it('should schedule a reconnect via the policy', () => {
80
+ it('should schedule a reconnect via the policy after exhausting all retries', async () => {
81
+ await exhaustRetries(clock);
49
82
  (context.policy.schedule as sinon.SinonStub).calledOnce.should.be.true;
50
83
  });
51
84
  }));
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=after_release_in_development_mode.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"after_release_in_development_mode.d.ts","sourceRoot":"","sources":["../../../../../queries/for_QueryInstanceCache/when_acquiring/after_release_in_development_mode.ts"],"names":[],"mappings":""}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=the_only_subscriber_in_development_mode.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"the_only_subscriber_in_development_mode.d.ts","sourceRoot":"","sources":["../../../../../queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_in_development_mode.ts"],"names":[],"mappings":""}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=the_only_subscriber_outside_development_mode.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"the_only_subscriber_outside_development_mode.d.ts","sourceRoot":"","sources":["../../../../../queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.ts"],"names":[],"mappings":""}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=after_release_in_development_mode.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"after_release_in_development_mode.d.ts","sourceRoot":"","sources":["../../../../../queries/for_QueryInstanceCache/when_acquiring/after_release_in_development_mode.ts"],"names":[],"mappings":""}
@@ -1 +0,0 @@
1
- {"version":3,"file":"after_release_in_development_mode.js","sourceRoot":"","sources":["../../../../../queries/for_QueryInstanceCache/when_acquiring/after_release_in_development_mode.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAE9D,QAAQ,CAAC,kDAAkD,EAAE,GAAG,EAAE;IAC9D,IAAI,KAAyB,CAAC;IAC9B,IAAI,cAAuB,CAAC;IAE5B,UAAU,CAAC,GAAG,EAAE;QACZ,EAAE,CAAC,aAAa,EAAE,CAAC;QACnB,cAAc,GAAG,KAAK,CAAC;QACvB,KAAK,GAAG,IAAI,kBAAkB,CAAC,IAAI,CAAC,CAAC;QACrC,KAAK,CAAC,WAAW,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3C,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAC3B,KAAK,CAAC,WAAW,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,cAAc,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACjE,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAG3B,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAC3B,EAAE,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACX,EAAE,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IACrE,EAAE,CAAC,uBAAuB,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;IACzE,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;AAClG,CAAC,CAAC,CAAC"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=the_only_subscriber_in_development_mode.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"the_only_subscriber_in_development_mode.d.ts","sourceRoot":"","sources":["../../../../../queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_in_development_mode.ts"],"names":[],"mappings":""}
@@ -1,28 +0,0 @@
1
- import { QueryInstanceCache } from '../../QueryInstanceCache';
2
- describe('when releasing the only subscriber in development mode', () => {
3
- let cache;
4
- let teardownCalled;
5
- beforeEach(() => {
6
- vi.useFakeTimers();
7
- teardownCalled = false;
8
- cache = new QueryInstanceCache(true);
9
- cache.getOrCreate('MyQuery::', () => ({}));
10
- cache.acquire('MyQuery::');
11
- cache.setTeardown('MyQuery::', () => { teardownCalled = true; });
12
- cache.release('MyQuery::');
13
- });
14
- afterEach(() => {
15
- vi.useRealTimers();
16
- });
17
- it('should not call teardown synchronously', () => teardownCalled.should.be.false);
18
- it('should keep the entry before the timer fires', () => cache.has('MyQuery::').should.be.true);
19
- it('should still report as subscribed before the timer fires', () => cache.isSubscribed('MyQuery::').should.be.true);
20
- describe('and the deferred timer fires', () => {
21
- beforeEach(() => {
22
- vi.advanceTimersByTime(0);
23
- });
24
- it('should call teardown', () => teardownCalled.should.be.true);
25
- it('should evict the entry', () => cache.has('MyQuery::').should.be.false);
26
- });
27
- });
28
- //# sourceMappingURL=the_only_subscriber_in_development_mode.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"the_only_subscriber_in_development_mode.js","sourceRoot":"","sources":["../../../../../queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_in_development_mode.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAE9D,QAAQ,CAAC,wDAAwD,EAAE,GAAG,EAAE;IACpE,IAAI,KAAyB,CAAC;IAC9B,IAAI,cAAuB,CAAC;IAE5B,UAAU,CAAC,GAAG,EAAE;QACZ,EAAE,CAAC,aAAa,EAAE,CAAC;QACnB,cAAc,GAAG,KAAK,CAAC;QACvB,KAAK,GAAG,IAAI,kBAAkB,CAAC,IAAI,CAAC,CAAC;QACrC,KAAK,CAAC,WAAW,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3C,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAC3B,KAAK,CAAC,WAAW,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,cAAc,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACjE,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACX,EAAE,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IACnF,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;IAChG,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;IAErH,QAAQ,CAAC,8BAA8B,EAAE,GAAG,EAAE;QAC1C,UAAU,CAAC,GAAG,EAAE;YACZ,EAAE,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;QAChE,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=the_only_subscriber_outside_development_mode.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"the_only_subscriber_outside_development_mode.d.ts","sourceRoot":"","sources":["../../../../../queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.ts"],"names":[],"mappings":""}
@@ -1,28 +0,0 @@
1
- import { QueryInstanceCache } from '../../QueryInstanceCache';
2
- describe('when releasing the only subscriber outside development mode', () => {
3
- let cache;
4
- let teardownCalled;
5
- beforeEach(() => {
6
- vi.useFakeTimers();
7
- teardownCalled = false;
8
- cache = new QueryInstanceCache(false);
9
- cache.getOrCreate('MyQuery::', () => ({}));
10
- cache.acquire('MyQuery::');
11
- cache.setTeardown('MyQuery::', () => { teardownCalled = true; });
12
- cache.release('MyQuery::');
13
- });
14
- afterEach(() => {
15
- vi.useRealTimers();
16
- });
17
- it('should not call teardown synchronously', () => teardownCalled.should.be.false);
18
- it('should keep the entry before the timer fires', () => cache.has('MyQuery::').should.be.true);
19
- it('should still report as subscribed before the timer fires', () => cache.isSubscribed('MyQuery::').should.be.true);
20
- describe('and the deferred timer fires', () => {
21
- beforeEach(() => {
22
- vi.advanceTimersByTime(0);
23
- });
24
- it('should call teardown', () => teardownCalled.should.be.true);
25
- it('should evict the entry', () => cache.has('MyQuery::').should.be.false);
26
- });
27
- });
28
- //# sourceMappingURL=the_only_subscriber_outside_development_mode.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"the_only_subscriber_outside_development_mode.js","sourceRoot":"","sources":["../../../../../queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAE9D,QAAQ,CAAC,6DAA6D,EAAE,GAAG,EAAE;IACzE,IAAI,KAAyB,CAAC;IAC9B,IAAI,cAAuB,CAAC;IAE5B,UAAU,CAAC,GAAG,EAAE;QACZ,EAAE,CAAC,aAAa,EAAE,CAAC;QACnB,cAAc,GAAG,KAAK,CAAC;QACvB,KAAK,GAAG,IAAI,kBAAkB,CAAC,KAAK,CAAC,CAAC;QACtC,KAAK,CAAC,WAAW,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3C,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAC3B,KAAK,CAAC,WAAW,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,cAAc,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACjE,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACX,EAAE,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IACnF,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;IAChG,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;IAErH,QAAQ,CAAC,8BAA8B,EAAE,GAAG,EAAE;QAC1C,UAAU,CAAC,GAAG,EAAE;YACZ,EAAE,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;QAChE,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
@@ -1,36 +0,0 @@
1
- // Copyright (c) Cratis. All rights reserved.
2
- // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
-
4
- import { QueryInstanceCache } from '../../QueryInstanceCache';
5
-
6
- describe('when releasing the only subscriber in development mode', () => {
7
- let cache: QueryInstanceCache;
8
- let teardownCalled: boolean;
9
-
10
- beforeEach(() => {
11
- vi.useFakeTimers();
12
- teardownCalled = false;
13
- cache = new QueryInstanceCache(true);
14
- cache.getOrCreate('MyQuery::', () => ({}));
15
- cache.acquire('MyQuery::');
16
- cache.setTeardown('MyQuery::', () => { teardownCalled = true; });
17
- cache.release('MyQuery::');
18
- });
19
-
20
- afterEach(() => {
21
- vi.useRealTimers();
22
- });
23
-
24
- it('should not call teardown synchronously', () => teardownCalled.should.be.false);
25
- it('should keep the entry before the timer fires', () => cache.has('MyQuery::').should.be.true);
26
- it('should still report as subscribed before the timer fires', () => cache.isSubscribed('MyQuery::').should.be.true);
27
-
28
- describe('and the deferred timer fires', () => {
29
- beforeEach(() => {
30
- vi.advanceTimersByTime(0);
31
- });
32
-
33
- it('should call teardown', () => teardownCalled.should.be.true);
34
- it('should evict the entry', () => cache.has('MyQuery::').should.be.false);
35
- });
36
- });