@cratis/arc 20.1.1 → 20.1.4
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/Globals.ts +7 -0
- package/dist/cjs/Globals.d.ts +2 -0
- package/dist/cjs/Globals.d.ts.map +1 -1
- package/dist/cjs/Globals.js +1 -0
- package/dist/cjs/Globals.js.map +1 -1
- package/dist/cjs/identity/IdentityProvider.d.ts +2 -1
- package/dist/cjs/identity/IdentityProvider.d.ts.map +1 -1
- package/dist/cjs/identity/IdentityProvider.js +16 -2
- package/dist/cjs/identity/IdentityProvider.js.map +1 -1
- package/dist/cjs/identity/for_IdentityProvider/when_refreshing/with_unauthorized_response.d.ts +2 -0
- package/dist/cjs/identity/for_IdentityProvider/when_refreshing/with_unauthorized_response.d.ts.map +1 -0
- package/dist/cjs/queries/QueryInstanceCache.d.ts +8 -0
- package/dist/cjs/queries/QueryInstanceCache.d.ts.map +1 -1
- package/dist/cjs/queries/QueryInstanceCache.js +70 -9
- package/dist/cjs/queries/QueryInstanceCache.js.map +1 -1
- package/dist/cjs/queries/QueryResult.d.ts +1 -0
- package/dist/cjs/queries/QueryResult.d.ts.map +1 -1
- package/dist/cjs/queries/QueryResult.js +18 -0
- package/dist/cjs/queries/QueryResult.js.map +1 -1
- package/dist/cjs/queries/ServerSentEventHubConnection.d.ts +1 -0
- package/dist/cjs/queries/ServerSentEventHubConnection.d.ts.map +1 -1
- package/dist/cjs/queries/ServerSentEventHubConnection.js +16 -2
- package/dist/cjs/queries/ServerSentEventHubConnection.js.map +1 -1
- package/dist/cjs/queries/WebSocketHubConnection.d.ts +1 -0
- package/dist/cjs/queries/WebSocketHubConnection.d.ts.map +1 -1
- package/dist/cjs/queries/WebSocketHubConnection.js +11 -0
- package/dist/cjs/queries/WebSocketHubConnection.js.map +1 -1
- package/dist/cjs/queries/for_QueryInstanceCache/when_acquiring/after_release_in_development_mode.d.ts +2 -0
- package/dist/cjs/queries/for_QueryInstanceCache/when_acquiring/after_release_in_development_mode.d.ts.map +1 -0
- package/dist/cjs/queries/for_QueryInstanceCache/when_deferring_dispose/with_cancellation_before_timeout.d.ts +2 -0
- package/dist/cjs/queries/for_QueryInstanceCache/when_deferring_dispose/with_cancellation_before_timeout.d.ts.map +1 -0
- package/dist/cjs/queries/for_QueryInstanceCache/when_deferring_dispose/without_cancellation.d.ts +2 -0
- package/dist/cjs/queries/for_QueryInstanceCache/when_deferring_dispose/without_cancellation.d.ts.map +1 -0
- package/dist/cjs/queries/for_QueryInstanceCache/when_disposing/an_empty_cache.d.ts +2 -0
- package/dist/cjs/queries/for_QueryInstanceCache/when_disposing/an_empty_cache.d.ts.map +1 -0
- package/dist/cjs/queries/for_QueryInstanceCache/when_disposing/with_active_subscriptions.d.ts +2 -0
- package/dist/cjs/queries/for_QueryInstanceCache/when_disposing/with_active_subscriptions.d.ts.map +1 -0
- package/dist/cjs/queries/for_QueryInstanceCache/when_disposing/with_pending_deferred_cleanup.d.ts +2 -0
- package/dist/cjs/queries/for_QueryInstanceCache/when_disposing/with_pending_deferred_cleanup.d.ts.map +1 -0
- package/dist/cjs/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_in_development_mode.d.ts +2 -0
- package/dist/cjs/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_in_development_mode.d.ts.map +1 -0
- package/dist/cjs/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.d.ts +2 -0
- package/dist/cjs/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.d.ts.map +1 -0
- package/dist/cjs/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_active_subscriptions.d.ts +2 -0
- package/dist/cjs/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_active_subscriptions.d.ts.map +1 -0
- package/dist/cjs/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_no_subscriptions.d.ts +2 -0
- package/dist/cjs/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_no_subscriptions.d.ts.map +1 -0
- package/dist/cjs/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_pending_deferred_cleanup.d.ts +2 -0
- package/dist/cjs/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_pending_deferred_cleanup.d.ts.map +1 -0
- package/dist/cjs/queries/for_ServerSentEventHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.d.ts +2 -0
- package/dist/cjs/queries/for_ServerSentEventHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.d.ts.map +1 -0
- package/dist/cjs/queries/for_WebSocketHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.d.ts +2 -0
- package/dist/cjs/queries/for_WebSocketHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.d.ts.map +1 -0
- package/dist/esm/Globals.d.ts +2 -0
- package/dist/esm/Globals.d.ts.map +1 -1
- package/dist/esm/Globals.js +1 -0
- package/dist/esm/Globals.js.map +1 -1
- package/dist/esm/identity/IdentityProvider.d.ts +2 -1
- package/dist/esm/identity/IdentityProvider.d.ts.map +1 -1
- package/dist/esm/identity/IdentityProvider.js +16 -2
- package/dist/esm/identity/IdentityProvider.js.map +1 -1
- package/dist/esm/identity/for_IdentityProvider/when_refreshing/with_unauthorized_response.d.ts +2 -0
- package/dist/esm/identity/for_IdentityProvider/when_refreshing/with_unauthorized_response.d.ts.map +1 -0
- package/dist/esm/identity/for_IdentityProvider/when_refreshing/with_unauthorized_response.js +19 -0
- package/dist/esm/identity/for_IdentityProvider/when_refreshing/with_unauthorized_response.js.map +1 -0
- package/dist/esm/queries/QueryInstanceCache.d.ts +8 -0
- package/dist/esm/queries/QueryInstanceCache.d.ts.map +1 -1
- package/dist/esm/queries/QueryInstanceCache.js +70 -9
- package/dist/esm/queries/QueryInstanceCache.js.map +1 -1
- package/dist/esm/queries/QueryResult.d.ts +1 -0
- package/dist/esm/queries/QueryResult.d.ts.map +1 -1
- package/dist/esm/queries/QueryResult.js +18 -0
- package/dist/esm/queries/QueryResult.js.map +1 -1
- package/dist/esm/queries/ServerSentEventHubConnection.d.ts +1 -0
- package/dist/esm/queries/ServerSentEventHubConnection.d.ts.map +1 -1
- package/dist/esm/queries/ServerSentEventHubConnection.js +16 -2
- package/dist/esm/queries/ServerSentEventHubConnection.js.map +1 -1
- package/dist/esm/queries/WebSocketHubConnection.d.ts +1 -0
- package/dist/esm/queries/WebSocketHubConnection.d.ts.map +1 -1
- package/dist/esm/queries/WebSocketHubConnection.js +11 -0
- package/dist/esm/queries/WebSocketHubConnection.js.map +1 -1
- package/dist/esm/queries/for_QueryInstanceCache/when_acquiring/after_release_in_development_mode.d.ts +2 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_acquiring/after_release_in_development_mode.d.ts.map +1 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_acquiring/after_release_in_development_mode.js +23 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_acquiring/after_release_in_development_mode.js.map +1 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_deferring_dispose/with_cancellation_before_timeout.d.ts +2 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_deferring_dispose/with_cancellation_before_timeout.d.ts.map +1 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_deferring_dispose/with_cancellation_before_timeout.js +23 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_deferring_dispose/with_cancellation_before_timeout.js.map +1 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_deferring_dispose/without_cancellation.d.ts +2 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_deferring_dispose/without_cancellation.d.ts.map +1 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_deferring_dispose/without_cancellation.js +21 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_deferring_dispose/without_cancellation.js.map +1 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_disposing/an_empty_cache.d.ts +2 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_disposing/an_empty_cache.d.ts.map +1 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_disposing/an_empty_cache.js +17 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_disposing/an_empty_cache.js.map +1 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_disposing/with_active_subscriptions.d.ts +2 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_disposing/with_active_subscriptions.d.ts.map +1 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_disposing/with_active_subscriptions.js +23 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_disposing/with_active_subscriptions.js.map +1 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_disposing/with_pending_deferred_cleanup.d.ts +2 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_disposing/with_pending_deferred_cleanup.d.ts.map +1 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_disposing/with_pending_deferred_cleanup.js +22 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_disposing/with_pending_deferred_cleanup.js.map +1 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_in_development_mode.d.ts +2 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_in_development_mode.d.ts.map +1 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_in_development_mode.js +28 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_in_development_mode.js.map +1 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.d.ts +2 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.d.ts.map +1 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.js +25 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.js.map +1 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_active_subscriptions.d.ts +2 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_active_subscriptions.d.ts.map +1 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_active_subscriptions.js +25 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_active_subscriptions.js.map +1 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_no_subscriptions.d.ts +2 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_no_subscriptions.d.ts.map +1 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_no_subscriptions.js +12 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_no_subscriptions.js.map +1 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_pending_deferred_cleanup.d.ts +2 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_pending_deferred_cleanup.d.ts.map +1 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_pending_deferred_cleanup.js +23 -0
- package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_pending_deferred_cleanup.js.map +1 -0
- package/dist/esm/queries/for_ServerSentEventHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.d.ts +2 -0
- package/dist/esm/queries/for_ServerSentEventHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.d.ts.map +1 -0
- package/dist/esm/queries/for_ServerSentEventHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.js +35 -0
- package/dist/esm/queries/for_ServerSentEventHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.js.map +1 -0
- package/dist/esm/queries/for_WebSocketHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.d.ts +2 -0
- package/dist/esm/queries/for_WebSocketHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.d.ts.map +1 -0
- package/dist/esm/queries/for_WebSocketHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.js +33 -0
- package/dist/esm/queries/for_WebSocketHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.js.map +1 -0
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/identity/IdentityProvider.ts +23 -2
- package/identity/for_IdentityProvider/when_refreshing/with_unauthorized_response.ts +26 -0
- package/package.json +1 -1
- package/queries/QueryInstanceCache.ts +133 -12
- package/queries/QueryResult.ts +19 -0
- package/queries/ServerSentEventHubConnection.ts +18 -2
- package/queries/WebSocketHubConnection.ts +11 -0
- package/queries/for_QueryInstanceCache/when_acquiring/after_release_in_development_mode.ts +31 -0
- package/queries/for_QueryInstanceCache/when_deferring_dispose/with_cancellation_before_timeout.ts +31 -0
- package/queries/for_QueryInstanceCache/when_deferring_dispose/without_cancellation.ts +28 -0
- package/queries/for_QueryInstanceCache/when_disposing/an_empty_cache.ts +21 -0
- package/queries/for_QueryInstanceCache/when_disposing/with_active_subscriptions.ts +30 -0
- package/queries/for_QueryInstanceCache/when_disposing/with_pending_deferred_cleanup.ts +31 -0
- package/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_in_development_mode.ts +36 -0
- package/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.ts +33 -0
- package/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_active_subscriptions.ts +32 -0
- package/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_no_subscriptions.ts +18 -0
- package/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_pending_deferred_cleanup.ts +33 -0
- package/queries/for_ServerSentEventHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.ts +51 -0
- package/queries/for_WebSocketHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.ts +47 -0
|
@@ -77,7 +77,7 @@ export class IdentityProvider extends IIdentityProvider {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
static async refresh<TDetails extends object = object>(type?: Constructor<TDetails>): Promise<IIdentity<TDetails>> {
|
|
80
|
-
IdentityProvider.
|
|
80
|
+
IdentityProvider.clearIdentityCookie();
|
|
81
81
|
const origin = IdentityProvider.origin || Globals.origin || '';
|
|
82
82
|
const apiBasePath = IdentityProvider.apiBasePath || Globals.apiBasePath || '';
|
|
83
83
|
const route = joinPaths(apiBasePath, '/.cratis/me');
|
|
@@ -88,6 +88,10 @@ export class IdentityProvider extends IIdentityProvider {
|
|
|
88
88
|
headers: IdentityProvider.httpHeadersCallback?.() ?? {}
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
return IdentityProvider.notSet(type);
|
|
93
|
+
}
|
|
94
|
+
|
|
91
95
|
const result = await response.json() as IdentityProviderResult;
|
|
92
96
|
const details = type ? JsonSerializer.deserializeFromInstance(type, result.details) : result.details;
|
|
93
97
|
|
|
@@ -102,6 +106,18 @@ export class IdentityProvider extends IIdentityProvider {
|
|
|
102
106
|
};
|
|
103
107
|
}
|
|
104
108
|
|
|
109
|
+
private static notSet<TDetails extends object = object>(type?: Constructor<TDetails>): IIdentity<TDetails> {
|
|
110
|
+
return {
|
|
111
|
+
id: '',
|
|
112
|
+
name: '',
|
|
113
|
+
roles: [],
|
|
114
|
+
details: {} as TDetails,
|
|
115
|
+
isSet: false,
|
|
116
|
+
isInRole: () => false,
|
|
117
|
+
refresh: () => IdentityProvider.refresh(type)
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
105
121
|
private static getCookie() {
|
|
106
122
|
if (typeof document === 'undefined') return [];
|
|
107
123
|
const decoded = decodeURIComponent(document.cookie);
|
|
@@ -114,7 +130,12 @@ export class IdentityProvider extends IIdentityProvider {
|
|
|
114
130
|
return [];
|
|
115
131
|
}
|
|
116
132
|
|
|
117
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Clears the identity cookie used by Arc to cache the current identity.
|
|
135
|
+
* Call this when the user logs out to ensure subsequent requests and WebSocket
|
|
136
|
+
* connections do not carry stale credentials.
|
|
137
|
+
*/
|
|
138
|
+
static clearIdentityCookie(): void {
|
|
118
139
|
if (typeof document === 'undefined') return;
|
|
119
140
|
document.cookie = `${IdentityProvider.CookieName}=;expires=Thu, 01 Jan 1970 00:00:00 GMT`;
|
|
120
141
|
}
|
|
@@ -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 { IdentityProvider } from '../../IdentityProvider';
|
|
5
|
+
import { IIdentity } from '../../IIdentity';
|
|
6
|
+
import { an_identity_provider } from '../given/an_identity_provider';
|
|
7
|
+
import { given } from '../../../given';
|
|
8
|
+
|
|
9
|
+
describe('when refreshing with unauthorized response', given(an_identity_provider, context => {
|
|
10
|
+
let result: IIdentity;
|
|
11
|
+
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
context.fetchStub.resolves({
|
|
14
|
+
ok: false,
|
|
15
|
+
status: 401,
|
|
16
|
+
} as Response);
|
|
17
|
+
|
|
18
|
+
result = await IdentityProvider.refresh();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should return identity with isSet false', () => result.isSet.should.be.false);
|
|
22
|
+
it('should return empty identity id', () => result.id.should.equal(''));
|
|
23
|
+
it('should return empty identity name', () => result.name.should.equal(''));
|
|
24
|
+
it('should return empty roles', () => result.roles.should.be.empty);
|
|
25
|
+
it('should provide a refresh method', () => result.refresh.should.be.a('function'));
|
|
26
|
+
}));
|
package/package.json
CHANGED
|
@@ -48,6 +48,13 @@ export interface QueryCacheEntry<TDataType> {
|
|
|
48
48
|
* Whether an active subscription has been established for this entry.
|
|
49
49
|
*/
|
|
50
50
|
subscribed: boolean;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Timer handle for deferred cleanup when running in development mode.
|
|
54
|
+
* Allows React StrictMode re-mounts to cancel the pending teardown
|
|
55
|
+
* so the connection is reused instead of torn down and recreated.
|
|
56
|
+
*/
|
|
57
|
+
pendingCleanup?: ReturnType<typeof setTimeout>;
|
|
51
58
|
}
|
|
52
59
|
|
|
53
60
|
/**
|
|
@@ -60,6 +67,17 @@ export interface QueryCacheEntry<TDataType> {
|
|
|
60
67
|
*/
|
|
61
68
|
export class QueryInstanceCache {
|
|
62
69
|
private readonly _entries = new Map<QueryCacheKey, QueryCacheEntry<unknown>>();
|
|
70
|
+
private readonly _development: boolean;
|
|
71
|
+
private _pendingDispose?: ReturnType<typeof setTimeout>;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Initializes a new instance of {@link QueryInstanceCache}.
|
|
75
|
+
* @param development When true, teardown is deferred on release so React StrictMode
|
|
76
|
+
* re-mounts can re-acquire the entry without an unnecessary disconnect/reconnect cycle.
|
|
77
|
+
*/
|
|
78
|
+
constructor(development: boolean = false) {
|
|
79
|
+
this._development = development;
|
|
80
|
+
}
|
|
63
81
|
|
|
64
82
|
/**
|
|
65
83
|
* Builds the cache key for a query.
|
|
@@ -113,6 +131,8 @@ export class QueryInstanceCache {
|
|
|
113
131
|
|
|
114
132
|
/**
|
|
115
133
|
* Increments the active subscriber count for the given key.
|
|
134
|
+
* If a deferred cleanup was pending (from a recent {@link release} in development mode),
|
|
135
|
+
* it is cancelled so the existing subscription is reused.
|
|
116
136
|
* Call from `useEffect` setup to pair with {@link release} in the cleanup.
|
|
117
137
|
* @param key The cache key produced by {@link buildKey}.
|
|
118
138
|
*/
|
|
@@ -120,6 +140,11 @@ export class QueryInstanceCache {
|
|
|
120
140
|
const entry = this._entries.get(key);
|
|
121
141
|
|
|
122
142
|
if (entry) {
|
|
143
|
+
if (entry.pendingCleanup !== undefined) {
|
|
144
|
+
clearTimeout(entry.pendingCleanup);
|
|
145
|
+
entry.pendingCleanup = undefined;
|
|
146
|
+
}
|
|
147
|
+
|
|
123
148
|
entry.subscriberCount++;
|
|
124
149
|
}
|
|
125
150
|
}
|
|
@@ -207,6 +232,11 @@ export class QueryInstanceCache {
|
|
|
207
232
|
/**
|
|
208
233
|
* Decrements the subscriber count for the given key. When the count reaches zero the teardown
|
|
209
234
|
* function is called (if set) and the entry is evicted.
|
|
235
|
+
*
|
|
236
|
+
* In development mode, both teardown and eviction are deferred by one microtask so that
|
|
237
|
+
* React StrictMode re-mounts can re-acquire the entry and cancel the cleanup. This prevents
|
|
238
|
+
* an unnecessary disconnect/reconnect cycle during the synthetic unmount/remount that
|
|
239
|
+
* StrictMode performs in development builds.
|
|
210
240
|
* @param key The cache key produced by {@link buildKey}.
|
|
211
241
|
*/
|
|
212
242
|
release(key: QueryCacheKey): void {
|
|
@@ -216,18 +246,33 @@ export class QueryInstanceCache {
|
|
|
216
246
|
entry.subscriberCount--;
|
|
217
247
|
|
|
218
248
|
if (entry.subscriberCount <= 0) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
249
|
+
if (this._development) {
|
|
250
|
+
// Defer both teardown and deletion so StrictMode re-mounts can cancel.
|
|
251
|
+
entry.pendingCleanup = setTimeout(() => {
|
|
252
|
+
const current = this._entries.get(key);
|
|
253
|
+
|
|
254
|
+
if (current && current.subscriberCount <= 0) {
|
|
255
|
+
current.subscribed = false;
|
|
256
|
+
current.teardown?.();
|
|
257
|
+
current.teardown = undefined;
|
|
258
|
+
current.pendingCleanup = undefined;
|
|
259
|
+
this._entries.delete(key);
|
|
260
|
+
}
|
|
261
|
+
}, 0);
|
|
262
|
+
} else {
|
|
263
|
+
entry.subscribed = false;
|
|
264
|
+
entry.teardown?.();
|
|
265
|
+
entry.teardown = undefined;
|
|
266
|
+
|
|
267
|
+
// Defer deletion so React Strict Mode re-mounts can re-acquire the entry.
|
|
268
|
+
setTimeout(() => {
|
|
269
|
+
const current = this._entries.get(key);
|
|
270
|
+
|
|
271
|
+
if (current && current.subscriberCount <= 0) {
|
|
272
|
+
this._entries.delete(key);
|
|
273
|
+
}
|
|
274
|
+
}, 0);
|
|
275
|
+
}
|
|
231
276
|
}
|
|
232
277
|
}
|
|
233
278
|
}
|
|
@@ -240,4 +285,80 @@ export class QueryInstanceCache {
|
|
|
240
285
|
has(key: QueryCacheKey): boolean {
|
|
241
286
|
return this._entries.has(key);
|
|
242
287
|
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Tears down all active subscriptions and marks every entry as not subscribed,
|
|
291
|
+
* but preserves entries, subscriber counts, and listeners. This allows
|
|
292
|
+
* hooks whose effects re-run afterward to detect that the entry is no longer
|
|
293
|
+
* subscribed and re-establish a fresh connection.
|
|
294
|
+
*
|
|
295
|
+
* Use this when the underlying transport must be replaced (e.g. after an
|
|
296
|
+
* authentication change that requires new WebSocket connections with updated
|
|
297
|
+
* credentials).
|
|
298
|
+
*/
|
|
299
|
+
teardownAllSubscriptions(): void {
|
|
300
|
+
for (const [, entry] of this._entries) {
|
|
301
|
+
if (entry.pendingCleanup !== undefined) {
|
|
302
|
+
clearTimeout(entry.pendingCleanup);
|
|
303
|
+
entry.pendingCleanup = undefined;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
entry.subscribed = false;
|
|
307
|
+
entry.teardown?.();
|
|
308
|
+
entry.teardown = undefined;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Immediately tears down all subscriptions, cancels any pending deferred cleanups,
|
|
314
|
+
* and evicts all entries. Call when the owning component (e.g. the {@link Arc} provider)
|
|
315
|
+
* unmounts permanently so that all query connections are closed synchronously.
|
|
316
|
+
*/
|
|
317
|
+
dispose(): void {
|
|
318
|
+
for (const [, entry] of this._entries) {
|
|
319
|
+
if (entry.pendingCleanup !== undefined) {
|
|
320
|
+
clearTimeout(entry.pendingCleanup);
|
|
321
|
+
entry.pendingCleanup = undefined;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
entry.subscribed = false;
|
|
325
|
+
entry.teardown?.();
|
|
326
|
+
entry.teardown = undefined;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
this._entries.clear();
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Schedules a deferred {@link dispose} using {@code setTimeout(0)}.
|
|
334
|
+
*
|
|
335
|
+
* This allows React StrictMode re-mounts to call {@link cancelPendingDispose}
|
|
336
|
+
* before the dispose fires, avoiding the destruction of cache entries that child
|
|
337
|
+
* effects are about to re-acquire.
|
|
338
|
+
*
|
|
339
|
+
* If a deferred dispose is already pending, it is replaced.
|
|
340
|
+
*/
|
|
341
|
+
deferDispose(): void {
|
|
342
|
+
if (this._pendingDispose !== undefined) {
|
|
343
|
+
clearTimeout(this._pendingDispose);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
this._pendingDispose = setTimeout(() => {
|
|
347
|
+
this._pendingDispose = undefined;
|
|
348
|
+
this.dispose();
|
|
349
|
+
}, 0);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Cancels a pending deferred dispose scheduled by {@link deferDispose}.
|
|
354
|
+
*
|
|
355
|
+
* Call from the {@code useEffect} setup phase so that a StrictMode re-mount
|
|
356
|
+
* prevents the synthetic unmount's deferred dispose from firing.
|
|
357
|
+
*/
|
|
358
|
+
cancelPendingDispose(): void {
|
|
359
|
+
if (this._pendingDispose !== undefined) {
|
|
360
|
+
clearTimeout(this._pendingDispose);
|
|
361
|
+
this._pendingDispose = undefined;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
243
364
|
}
|
package/queries/QueryResult.ts
CHANGED
|
@@ -60,6 +60,25 @@ export class QueryResult<TDataType = object> implements IQueryResult<TDataType>
|
|
|
60
60
|
}, Object, false);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
static unauthorized<TDataType>(): QueryResult<TDataType> {
|
|
64
|
+
return new QueryResult({
|
|
65
|
+
data: null as unknown as object,
|
|
66
|
+
isSuccess: false,
|
|
67
|
+
isAuthorized: false,
|
|
68
|
+
isValid: true,
|
|
69
|
+
hasExceptions: false,
|
|
70
|
+
validationResults: [],
|
|
71
|
+
exceptionMessages: [],
|
|
72
|
+
exceptionStackTrace: '',
|
|
73
|
+
paging: {
|
|
74
|
+
totalItems: 0,
|
|
75
|
+
totalPages: 0,
|
|
76
|
+
page: 0,
|
|
77
|
+
size: 0
|
|
78
|
+
}
|
|
79
|
+
}, Object, false);
|
|
80
|
+
}
|
|
81
|
+
|
|
63
82
|
static noSuccess: QueryResult = new QueryResult({
|
|
64
83
|
data: {},
|
|
65
84
|
isSuccess: false,
|
|
@@ -238,6 +238,7 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
|
|
|
238
238
|
break;
|
|
239
239
|
case HubMessageType.Unauthorized:
|
|
240
240
|
console.warn(`SSE hub: query '${message.queryId}' unauthorized`);
|
|
241
|
+
this.handleUnauthorized(message);
|
|
241
242
|
break;
|
|
242
243
|
case HubMessageType.Error:
|
|
243
244
|
console.error(`SSE hub: query '${message.queryId}' error:`, message.payload);
|
|
@@ -272,6 +273,17 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
|
|
|
272
273
|
sub.callback(result);
|
|
273
274
|
}
|
|
274
275
|
|
|
276
|
+
private handleUnauthorized(message: HubMessage): void {
|
|
277
|
+
if (!message.queryId) return;
|
|
278
|
+
|
|
279
|
+
const sub = this._subscriptions.get(message.queryId);
|
|
280
|
+
if (!sub) return;
|
|
281
|
+
|
|
282
|
+
this._subscriptions.delete(message.queryId);
|
|
283
|
+
this._pendingSubscriptions.delete(message.queryId);
|
|
284
|
+
sub.callback(QueryResult.unauthorized());
|
|
285
|
+
}
|
|
286
|
+
|
|
275
287
|
private sendSubscribe(queryId: string, request: SubscriptionRequest): void {
|
|
276
288
|
if (!this._connectionId) return;
|
|
277
289
|
|
|
@@ -281,9 +293,11 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
|
|
|
281
293
|
request,
|
|
282
294
|
};
|
|
283
295
|
|
|
296
|
+
const customHeaders = Globals.httpHeadersCallback?.() ?? {};
|
|
297
|
+
|
|
284
298
|
fetch(this._subscribeUrl, {
|
|
285
299
|
method: 'POST',
|
|
286
|
-
headers: { 'Content-Type': 'application/json' },
|
|
300
|
+
headers: { 'Content-Type': 'application/json', ...customHeaders },
|
|
287
301
|
body: JSON.stringify(body),
|
|
288
302
|
}).catch(error => {
|
|
289
303
|
console.error(`SSE hub: subscribe POST failed for '${queryId}'`, error);
|
|
@@ -298,9 +312,11 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
|
|
|
298
312
|
queryId,
|
|
299
313
|
};
|
|
300
314
|
|
|
315
|
+
const customHeaders = Globals.httpHeadersCallback?.() ?? {};
|
|
316
|
+
|
|
301
317
|
fetch(this._unsubscribeUrl, {
|
|
302
318
|
method: 'POST',
|
|
303
|
-
headers: { 'Content-Type': 'application/json' },
|
|
319
|
+
headers: { 'Content-Type': 'application/json', ...customHeaders },
|
|
304
320
|
body: JSON.stringify(body),
|
|
305
321
|
}).catch(error => {
|
|
306
322
|
console.error(`SSE hub: unsubscribe POST failed for '${queryId}'`, error);
|
|
@@ -257,6 +257,7 @@ export class WebSocketHubConnection {
|
|
|
257
257
|
break;
|
|
258
258
|
case HubMessageType.Unauthorized:
|
|
259
259
|
console.warn(`Hub: query '${message.queryId}' unauthorized`);
|
|
260
|
+
this.handleUnauthorized(message);
|
|
260
261
|
break;
|
|
261
262
|
case HubMessageType.Error:
|
|
262
263
|
console.error(`Hub: query '${message.queryId}' error:`, message.payload);
|
|
@@ -277,6 +278,16 @@ export class WebSocketHubConnection {
|
|
|
277
278
|
sub.callback(result);
|
|
278
279
|
}
|
|
279
280
|
|
|
281
|
+
private handleUnauthorized(message: HubMessage): void {
|
|
282
|
+
if (!message.queryId) return;
|
|
283
|
+
|
|
284
|
+
const sub = this._subscriptions.get(message.queryId);
|
|
285
|
+
if (!sub) return;
|
|
286
|
+
|
|
287
|
+
this._subscriptions.delete(message.queryId);
|
|
288
|
+
sub.callback(QueryResult.unauthorized());
|
|
289
|
+
}
|
|
290
|
+
|
|
280
291
|
private handlePong(message: HubMessage): void {
|
|
281
292
|
if (message.timestamp && this._lastPingSentTime) {
|
|
282
293
|
const latency = Date.now() - message.timestamp;
|
|
@@ -0,0 +1,31 @@
|
|
|
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 acquiring after release 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
|
+
// Re-acquire before the deferred timer fires (simulates StrictMode re-mount).
|
|
20
|
+
cache.acquire('MyQuery::');
|
|
21
|
+
vi.advanceTimersByTime(0);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
vi.useRealTimers();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should not call teardown', () => teardownCalled.should.be.false);
|
|
29
|
+
it('should keep the entry', () => cache.has('MyQuery::').should.be.true);
|
|
30
|
+
it('should still report as subscribed', () => cache.isSubscribed('MyQuery::').should.be.true);
|
|
31
|
+
});
|
package/queries/for_QueryInstanceCache/when_deferring_dispose/with_cancellation_before_timeout.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
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 canceling deferred dispose before timeout fires', () => {
|
|
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
|
+
|
|
18
|
+
// Simulates StrictMode: unmount defers dispose, remount cancels it.
|
|
19
|
+
cache.deferDispose();
|
|
20
|
+
cache.cancelPendingDispose();
|
|
21
|
+
vi.advanceTimersByTime(0);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
vi.useRealTimers();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should not call teardown', () => teardownCalled.should.be.false);
|
|
29
|
+
it('should keep the entry', () => cache.has('MyQuery::').should.be.true);
|
|
30
|
+
it('should still report as subscribed', () => cache.isSubscribed('MyQuery::').should.be.true);
|
|
31
|
+
});
|
|
@@ -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 deferring dispose without cancellation', () => {
|
|
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
|
+
|
|
18
|
+
cache.deferDispose();
|
|
19
|
+
vi.advanceTimersByTime(0);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
vi.useRealTimers();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should call teardown', () => teardownCalled.should.be.true);
|
|
27
|
+
it('should evict the entry', () => cache.has('MyQuery::').should.be.false);
|
|
28
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
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 disposing an empty cache', () => {
|
|
7
|
+
let cache: QueryInstanceCache;
|
|
8
|
+
let threwError: boolean;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
threwError = false;
|
|
12
|
+
cache = new QueryInstanceCache();
|
|
13
|
+
try {
|
|
14
|
+
cache.dispose();
|
|
15
|
+
} catch {
|
|
16
|
+
threwError = true;
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should not throw', () => threwError.should.be.false);
|
|
21
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
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 disposing with active subscriptions', () => {
|
|
7
|
+
let cache: QueryInstanceCache;
|
|
8
|
+
let firstTeardownCalled: boolean;
|
|
9
|
+
let secondTeardownCalled: boolean;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
firstTeardownCalled = false;
|
|
13
|
+
secondTeardownCalled = false;
|
|
14
|
+
cache = new QueryInstanceCache();
|
|
15
|
+
cache.getOrCreate('QueryA::', () => ({}));
|
|
16
|
+
cache.acquire('QueryA::');
|
|
17
|
+
cache.setTeardown('QueryA::', () => { firstTeardownCalled = true; });
|
|
18
|
+
|
|
19
|
+
cache.getOrCreate('QueryB::', () => ({}));
|
|
20
|
+
cache.acquire('QueryB::');
|
|
21
|
+
cache.setTeardown('QueryB::', () => { secondTeardownCalled = true; });
|
|
22
|
+
|
|
23
|
+
cache.dispose();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should call teardown for the first entry', () => firstTeardownCalled.should.be.true);
|
|
27
|
+
it('should call teardown for the second entry', () => secondTeardownCalled.should.be.true);
|
|
28
|
+
it('should evict the first entry', () => cache.has('QueryA::').should.be.false);
|
|
29
|
+
it('should evict the second entry', () => cache.has('QueryB::').should.be.false);
|
|
30
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
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 disposing with pending deferred cleanup', () => {
|
|
7
|
+
let cache: QueryInstanceCache;
|
|
8
|
+
let teardownCalled: number;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
vi.useFakeTimers();
|
|
12
|
+
teardownCalled = 0;
|
|
13
|
+
cache = new QueryInstanceCache(true);
|
|
14
|
+
cache.getOrCreate('MyQuery::', () => ({}));
|
|
15
|
+
cache.acquire('MyQuery::');
|
|
16
|
+
cache.setTeardown('MyQuery::', () => { teardownCalled++; });
|
|
17
|
+
cache.release('MyQuery::');
|
|
18
|
+
|
|
19
|
+
// At this point a deferred cleanup is pending. Dispose should
|
|
20
|
+
// tear down immediately and cancel the deferred cleanup.
|
|
21
|
+
cache.dispose();
|
|
22
|
+
vi.advanceTimersByTime(0);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
vi.useRealTimers();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should call teardown exactly once', () => teardownCalled.should.equal(1));
|
|
30
|
+
it('should evict the entry', () => cache.has('MyQuery::').should.be.false);
|
|
31
|
+
});
|
package/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_in_development_mode.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
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 outside development mode', () => {
|
|
7
|
+
let cache: QueryInstanceCache;
|
|
8
|
+
let teardownCalled: boolean;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
vi.useFakeTimers();
|
|
12
|
+
teardownCalled = false;
|
|
13
|
+
cache = new QueryInstanceCache(false);
|
|
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 call teardown synchronously', () => teardownCalled.should.be.true);
|
|
25
|
+
|
|
26
|
+
describe('and the deferred timer fires', () => {
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
vi.advanceTimersByTime(0);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should evict the entry', () => cache.has('MyQuery::').should.be.false);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
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 tearing down all subscriptions with active subscriptions', () => {
|
|
7
|
+
let cache: QueryInstanceCache;
|
|
8
|
+
let firstTeardownCalled: boolean;
|
|
9
|
+
let secondTeardownCalled: boolean;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
firstTeardownCalled = false;
|
|
13
|
+
secondTeardownCalled = false;
|
|
14
|
+
cache = new QueryInstanceCache();
|
|
15
|
+
cache.getOrCreate('QueryA::', () => ({}));
|
|
16
|
+
cache.acquire('QueryA::');
|
|
17
|
+
cache.setTeardown('QueryA::', () => { firstTeardownCalled = true; });
|
|
18
|
+
|
|
19
|
+
cache.getOrCreate('QueryB::', () => ({}));
|
|
20
|
+
cache.acquire('QueryB::');
|
|
21
|
+
cache.setTeardown('QueryB::', () => { secondTeardownCalled = true; });
|
|
22
|
+
|
|
23
|
+
cache.teardownAllSubscriptions();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should call teardown for the first entry', () => firstTeardownCalled.should.be.true);
|
|
27
|
+
it('should call teardown for the second entry', () => secondTeardownCalled.should.be.true);
|
|
28
|
+
it('should keep the first entry', () => cache.has('QueryA::').should.be.true);
|
|
29
|
+
it('should keep the second entry', () => cache.has('QueryB::').should.be.true);
|
|
30
|
+
it('should mark the first entry as not subscribed', () => cache.isSubscribed('QueryA::').should.be.false);
|
|
31
|
+
it('should mark the second entry as not subscribed', () => cache.isSubscribed('QueryB::').should.be.false);
|
|
32
|
+
});
|