@cratis/arc 20.1.3 → 20.1.5
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/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/HubConnectionKeepAlive.d.ts +2 -1
- package/dist/cjs/queries/HubConnectionKeepAlive.d.ts.map +1 -1
- package/dist/cjs/queries/HubConnectionKeepAlive.js +4 -2
- package/dist/cjs/queries/HubConnectionKeepAlive.js.map +1 -1
- 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/ReconnectPolicy.d.ts.map +1 -1
- package/dist/cjs/queries/ReconnectPolicy.js +1 -0
- package/dist/cjs/queries/ReconnectPolicy.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 +22 -3
- 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_HubConnectionKeepAlive/when_started_with_custom_idle_threshold/respects_threshold.d.ts +2 -0
- package/dist/cjs/queries/for_HubConnectionKeepAlive/when_started_with_custom_idle_threshold/respects_threshold.d.ts.map +1 -0
- 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_ReconnectPolicy/when_scheduling/and_rescheduled_before_first_fires/cancels_previous_timer.d.ts +2 -0
- package/dist/cjs/queries/for_ReconnectPolicy/when_scheduling/and_rescheduled_before_first_fires/cancels_previous_timer.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_ServerSentEventHubConnection/when_subscribing/and_subscribe_post_fails.d.ts +2 -0
- package/dist/cjs/queries/for_ServerSentEventHubConnection/when_subscribing/and_subscribe_post_fails.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/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/HubConnectionKeepAlive.d.ts +2 -1
- package/dist/esm/queries/HubConnectionKeepAlive.d.ts.map +1 -1
- package/dist/esm/queries/HubConnectionKeepAlive.js +4 -2
- package/dist/esm/queries/HubConnectionKeepAlive.js.map +1 -1
- 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/ReconnectPolicy.d.ts.map +1 -1
- package/dist/esm/queries/ReconnectPolicy.js +1 -0
- package/dist/esm/queries/ReconnectPolicy.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 +22 -3
- 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_HubConnectionKeepAlive/when_started_with_custom_idle_threshold/respects_threshold.d.ts +2 -0
- package/dist/esm/queries/for_HubConnectionKeepAlive/when_started_with_custom_idle_threshold/respects_threshold.d.ts.map +1 -0
- package/dist/esm/queries/for_HubConnectionKeepAlive/when_started_with_custom_idle_threshold/respects_threshold.js +33 -0
- package/dist/esm/queries/for_HubConnectionKeepAlive/when_started_with_custom_idle_threshold/respects_threshold.js.map +1 -0
- 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_ReconnectPolicy/when_scheduling/and_rescheduled_before_first_fires/cancels_previous_timer.d.ts +2 -0
- package/dist/esm/queries/for_ReconnectPolicy/when_scheduling/and_rescheduled_before_first_fires/cancels_previous_timer.d.ts.map +1 -0
- package/dist/esm/queries/for_ReconnectPolicy/when_scheduling/and_rescheduled_before_first_fires/cancels_previous_timer.js +24 -0
- package/dist/esm/queries/for_ReconnectPolicy/when_scheduling/and_rescheduled_before_first_fires/cancels_previous_timer.js.map +1 -0
- package/dist/esm/queries/for_ServerSentEventHubConnection/when_keep_alive_is_idle/triggers_reconnect.js +1 -1
- package/dist/esm/queries/for_ServerSentEventHubConnection/when_keep_alive_is_idle/triggers_reconnect.js.map +1 -1
- 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_ServerSentEventHubConnection/when_subscribing/and_subscribe_post_fails.d.ts +2 -0
- package/dist/esm/queries/for_ServerSentEventHubConnection/when_subscribing/and_subscribe_post_fails.d.ts.map +1 -0
- package/dist/esm/queries/for_ServerSentEventHubConnection/when_subscribing/and_subscribe_post_fails.js +33 -0
- package/dist/esm/queries/for_ServerSentEventHubConnection/when_subscribing/and_subscribe_post_fails.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/HubConnectionKeepAlive.ts +20 -6
- package/queries/QueryInstanceCache.ts +133 -12
- package/queries/QueryResult.ts +19 -0
- package/queries/ReconnectPolicy.ts +4 -0
- package/queries/ServerSentEventHubConnection.ts +29 -4
- package/queries/WebSocketHubConnection.ts +11 -0
- package/queries/for_HubConnectionKeepAlive/when_started_with_custom_idle_threshold/respects_threshold.ts +46 -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_ReconnectPolicy/when_scheduling/and_rescheduled_before_first_fires/cancels_previous_timer.ts +36 -0
- package/queries/for_ServerSentEventHubConnection/when_keep_alive_is_idle/triggers_reconnect.ts +5 -2
- package/queries/for_ServerSentEventHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.ts +51 -0
- package/queries/for_ServerSentEventHubConnection/when_subscribing/and_subscribe_post_fails.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
|
@@ -5,9 +5,15 @@
|
|
|
5
5
|
* Manages keep-alive behavior for hub connections (both WebSocket and Server-Sent Events).
|
|
6
6
|
*
|
|
7
7
|
* Records connection activity (any message received or sent). An interval fires every
|
|
8
|
-
* {@link intervalMs} milliseconds; if no activity has been recorded since the last
|
|
9
|
-
* provided {@link onIdle} callback is invoked —
|
|
10
|
-
* trigger a reconnect, or take some other action.
|
|
8
|
+
* {@link intervalMs} milliseconds; if no activity has been recorded since the last
|
|
9
|
+
* {@link idleThresholdMs} milliseconds the provided {@link onIdle} callback is invoked —
|
|
10
|
+
* the caller decides whether to send a ping, trigger a reconnect, or take some other action.
|
|
11
|
+
*
|
|
12
|
+
* The idle threshold defaults to the check interval but can be set higher to tolerate
|
|
13
|
+
* network latency between the server's keep-alive ping and the client's idle check.
|
|
14
|
+
* For SSE connections, where the idle callback triggers a reconnect, a tolerance of
|
|
15
|
+
* 1.5× the server's keep-alive interval prevents spurious reconnects caused by the
|
|
16
|
+
* client's timer firing just before the server's ping arrives.
|
|
11
17
|
*
|
|
12
18
|
* Both {@link WebSocketHubConnection} and {@link ServerSentEventHubConnection} own one instance
|
|
13
19
|
* of this class so the keep-alive logic is written once and behaves identically for both
|
|
@@ -16,17 +22,25 @@
|
|
|
16
22
|
export class HubConnectionKeepAlive {
|
|
17
23
|
private _lastActivityTime = Date.now();
|
|
18
24
|
private _timer?: ReturnType<typeof setInterval>;
|
|
25
|
+
private readonly _idleThresholdMs: number;
|
|
19
26
|
|
|
20
27
|
/**
|
|
21
28
|
* Initializes a new instance of {@link HubConnectionKeepAlive}.
|
|
22
29
|
* @param {number} intervalMs How often (in milliseconds) to check for idle connections.
|
|
23
30
|
* @param {() => void} onIdle Callback invoked when the interval fires and no activity has
|
|
24
|
-
* been recorded
|
|
31
|
+
* been recorded within the idle threshold.
|
|
32
|
+
* @param {number} idleThresholdMs Optional. How long (in milliseconds) without activity
|
|
33
|
+
* before the connection is considered idle. Defaults to {@link intervalMs}. Set this
|
|
34
|
+
* higher than {@link intervalMs} when the peer sends keep-alive messages on a similar
|
|
35
|
+
* cadence to account for network latency and timer jitter.
|
|
25
36
|
*/
|
|
26
37
|
constructor(
|
|
27
38
|
private readonly _intervalMs: number,
|
|
28
39
|
private readonly _onIdle: () => void,
|
|
29
|
-
|
|
40
|
+
idleThresholdMs?: number,
|
|
41
|
+
) {
|
|
42
|
+
this._idleThresholdMs = idleThresholdMs ?? _intervalMs;
|
|
43
|
+
}
|
|
30
44
|
|
|
31
45
|
/**
|
|
32
46
|
* Start the keep-alive timer. Safe to call multiple times — a running timer is stopped first.
|
|
@@ -35,7 +49,7 @@ export class HubConnectionKeepAlive {
|
|
|
35
49
|
this.stop();
|
|
36
50
|
this._lastActivityTime = Date.now();
|
|
37
51
|
this._timer = setInterval(() => {
|
|
38
|
-
if (Date.now() - this._lastActivityTime >= this.
|
|
52
|
+
if (Date.now() - this._lastActivityTime >= this._idleThresholdMs) {
|
|
39
53
|
this._onIdle();
|
|
40
54
|
}
|
|
41
55
|
}, this._intervalMs);
|
|
@@ -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,
|
|
@@ -45,6 +45,10 @@ export class ReconnectPolicy implements IReconnectPolicy {
|
|
|
45
45
|
return false;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
// Cancel any pending reconnect timer so we don't fire multiple
|
|
49
|
+
// concurrent reconnect attempts when schedule() is called rapidly.
|
|
50
|
+
this.cancel();
|
|
51
|
+
|
|
48
52
|
this._attempt++;
|
|
49
53
|
const delay = Math.min(this._initialDelayMs + this._delayStepMs * this._attempt, this._maxDelayMs);
|
|
50
54
|
console.log(`Reconnect: attempt ${this._attempt} in ${delay}ms for '${label}'`);
|
|
@@ -62,13 +62,20 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
|
|
|
62
62
|
) {
|
|
63
63
|
// SSE is server→client only: the client cannot send pings. Instead we watch for
|
|
64
64
|
// inactivity — if the server stops sending messages (including its own keep-alive
|
|
65
|
-
// pings) for the entire
|
|
65
|
+
// pings) for the entire idle threshold, the connection is stale and we reconnect.
|
|
66
|
+
//
|
|
67
|
+
// The idle threshold is set to 1.5× the check interval so the server's keep-alive
|
|
68
|
+
// ping (which fires on the same cadence) has time to arrive over the network before
|
|
69
|
+
// the client declares the connection dead. Without this tolerance the client's timer
|
|
70
|
+
// and the server's timer race — the client often fires first and reconnects
|
|
71
|
+
// unnecessarily.
|
|
72
|
+
const idleThresholdMs = Math.round(keepAliveIntervalMs * 1.5);
|
|
66
73
|
this._keepAlive = new HubConnectionKeepAlive(keepAliveIntervalMs, () => {
|
|
67
74
|
if (!this._disconnected && this._subscriptions.size > 0) {
|
|
68
|
-
console.warn(`SSE hub: no messages received for ${
|
|
75
|
+
console.warn(`SSE hub: no messages received for ${idleThresholdMs}ms, reconnecting '${this._sseUrl}'`);
|
|
69
76
|
this.reconnect();
|
|
70
77
|
}
|
|
71
|
-
});
|
|
78
|
+
}, idleThresholdMs);
|
|
72
79
|
}
|
|
73
80
|
|
|
74
81
|
/** @inheritdoc */
|
|
@@ -238,6 +245,7 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
|
|
|
238
245
|
break;
|
|
239
246
|
case HubMessageType.Unauthorized:
|
|
240
247
|
console.warn(`SSE hub: query '${message.queryId}' unauthorized`);
|
|
248
|
+
this.handleUnauthorized(message);
|
|
241
249
|
break;
|
|
242
250
|
case HubMessageType.Error:
|
|
243
251
|
console.error(`SSE hub: query '${message.queryId}' error:`, message.payload);
|
|
@@ -272,6 +280,17 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
|
|
|
272
280
|
sub.callback(result);
|
|
273
281
|
}
|
|
274
282
|
|
|
283
|
+
private handleUnauthorized(message: HubMessage): void {
|
|
284
|
+
if (!message.queryId) return;
|
|
285
|
+
|
|
286
|
+
const sub = this._subscriptions.get(message.queryId);
|
|
287
|
+
if (!sub) return;
|
|
288
|
+
|
|
289
|
+
this._subscriptions.delete(message.queryId);
|
|
290
|
+
this._pendingSubscriptions.delete(message.queryId);
|
|
291
|
+
sub.callback(QueryResult.unauthorized());
|
|
292
|
+
}
|
|
293
|
+
|
|
275
294
|
private sendSubscribe(queryId: string, request: SubscriptionRequest): void {
|
|
276
295
|
if (!this._connectionId) return;
|
|
277
296
|
|
|
@@ -287,8 +306,14 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
|
|
|
287
306
|
method: 'POST',
|
|
288
307
|
headers: { 'Content-Type': 'application/json', ...customHeaders },
|
|
289
308
|
body: JSON.stringify(body),
|
|
309
|
+
}).then(response => {
|
|
310
|
+
if (!response.ok) {
|
|
311
|
+
console.warn(`SSE hub: subscribe POST for '${queryId}' returned ${response.status}, reconnecting`);
|
|
312
|
+
this.reconnect();
|
|
313
|
+
}
|
|
290
314
|
}).catch(error => {
|
|
291
|
-
console.error(`SSE hub: subscribe POST failed for '${queryId}'`, error);
|
|
315
|
+
console.error(`SSE hub: subscribe POST failed for '${queryId}', reconnecting`, error);
|
|
316
|
+
this.reconnect();
|
|
292
317
|
});
|
|
293
318
|
}
|
|
294
319
|
|
|
@@ -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,46 @@
|
|
|
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 sinon from 'sinon';
|
|
5
|
+
import { HubConnectionKeepAlive } from '../../HubConnectionKeepAlive';
|
|
6
|
+
|
|
7
|
+
describe('when started with a custom idle threshold larger than the check interval', () => {
|
|
8
|
+
let clock: sinon.SinonFakeTimers;
|
|
9
|
+
let onIdle: sinon.SinonStub;
|
|
10
|
+
let keepAlive: HubConnectionKeepAlive;
|
|
11
|
+
|
|
12
|
+
const checkIntervalMs = 500;
|
|
13
|
+
const idleThresholdMs = 750;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
clock = sinon.useFakeTimers();
|
|
17
|
+
onIdle = sinon.stub();
|
|
18
|
+
keepAlive = new HubConnectionKeepAlive(checkIntervalMs, onIdle, idleThresholdMs);
|
|
19
|
+
keepAlive.start();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
keepAlive.stop();
|
|
24
|
+
clock.restore();
|
|
25
|
+
sinon.restore();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('and the check interval elapses but idle threshold has not', () => {
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
// Advance past the check interval (500ms) but not the idle threshold (750ms).
|
|
31
|
+
clock.tick(checkIntervalMs + 1);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should not invoke the onIdle callback', () => onIdle.called.should.be.false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('and the idle threshold elapses', () => {
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
// Advance past the idle threshold. The second interval tick at 1000ms
|
|
40
|
+
// sees 1000ms of inactivity which exceeds the 750ms threshold.
|
|
41
|
+
clock.tick(checkIntervalMs * 2 + 1);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should invoke the onIdle callback', () => onIdle.calledOnce.should.be.true);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -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
|
+
});
|