@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.
Files changed (185) hide show
  1. package/dist/cjs/identity/IdentityProvider.d.ts +2 -1
  2. package/dist/cjs/identity/IdentityProvider.d.ts.map +1 -1
  3. package/dist/cjs/identity/IdentityProvider.js +16 -2
  4. package/dist/cjs/identity/IdentityProvider.js.map +1 -1
  5. package/dist/cjs/identity/for_IdentityProvider/when_refreshing/with_unauthorized_response.d.ts +2 -0
  6. package/dist/cjs/identity/for_IdentityProvider/when_refreshing/with_unauthorized_response.d.ts.map +1 -0
  7. package/dist/cjs/queries/HubConnectionKeepAlive.d.ts +2 -1
  8. package/dist/cjs/queries/HubConnectionKeepAlive.d.ts.map +1 -1
  9. package/dist/cjs/queries/HubConnectionKeepAlive.js +4 -2
  10. package/dist/cjs/queries/HubConnectionKeepAlive.js.map +1 -1
  11. package/dist/cjs/queries/QueryInstanceCache.d.ts +8 -0
  12. package/dist/cjs/queries/QueryInstanceCache.d.ts.map +1 -1
  13. package/dist/cjs/queries/QueryInstanceCache.js +70 -9
  14. package/dist/cjs/queries/QueryInstanceCache.js.map +1 -1
  15. package/dist/cjs/queries/QueryResult.d.ts +1 -0
  16. package/dist/cjs/queries/QueryResult.d.ts.map +1 -1
  17. package/dist/cjs/queries/QueryResult.js +18 -0
  18. package/dist/cjs/queries/QueryResult.js.map +1 -1
  19. package/dist/cjs/queries/ReconnectPolicy.d.ts.map +1 -1
  20. package/dist/cjs/queries/ReconnectPolicy.js +1 -0
  21. package/dist/cjs/queries/ReconnectPolicy.js.map +1 -1
  22. package/dist/cjs/queries/ServerSentEventHubConnection.d.ts +1 -0
  23. package/dist/cjs/queries/ServerSentEventHubConnection.d.ts.map +1 -1
  24. package/dist/cjs/queries/ServerSentEventHubConnection.js +22 -3
  25. package/dist/cjs/queries/ServerSentEventHubConnection.js.map +1 -1
  26. package/dist/cjs/queries/WebSocketHubConnection.d.ts +1 -0
  27. package/dist/cjs/queries/WebSocketHubConnection.d.ts.map +1 -1
  28. package/dist/cjs/queries/WebSocketHubConnection.js +11 -0
  29. package/dist/cjs/queries/WebSocketHubConnection.js.map +1 -1
  30. package/dist/cjs/queries/for_HubConnectionKeepAlive/when_started_with_custom_idle_threshold/respects_threshold.d.ts +2 -0
  31. package/dist/cjs/queries/for_HubConnectionKeepAlive/when_started_with_custom_idle_threshold/respects_threshold.d.ts.map +1 -0
  32. package/dist/cjs/queries/for_QueryInstanceCache/when_acquiring/after_release_in_development_mode.d.ts +2 -0
  33. package/dist/cjs/queries/for_QueryInstanceCache/when_acquiring/after_release_in_development_mode.d.ts.map +1 -0
  34. package/dist/cjs/queries/for_QueryInstanceCache/when_deferring_dispose/with_cancellation_before_timeout.d.ts +2 -0
  35. package/dist/cjs/queries/for_QueryInstanceCache/when_deferring_dispose/with_cancellation_before_timeout.d.ts.map +1 -0
  36. package/dist/cjs/queries/for_QueryInstanceCache/when_deferring_dispose/without_cancellation.d.ts +2 -0
  37. package/dist/cjs/queries/for_QueryInstanceCache/when_deferring_dispose/without_cancellation.d.ts.map +1 -0
  38. package/dist/cjs/queries/for_QueryInstanceCache/when_disposing/an_empty_cache.d.ts +2 -0
  39. package/dist/cjs/queries/for_QueryInstanceCache/when_disposing/an_empty_cache.d.ts.map +1 -0
  40. package/dist/cjs/queries/for_QueryInstanceCache/when_disposing/with_active_subscriptions.d.ts +2 -0
  41. package/dist/cjs/queries/for_QueryInstanceCache/when_disposing/with_active_subscriptions.d.ts.map +1 -0
  42. package/dist/cjs/queries/for_QueryInstanceCache/when_disposing/with_pending_deferred_cleanup.d.ts +2 -0
  43. package/dist/cjs/queries/for_QueryInstanceCache/when_disposing/with_pending_deferred_cleanup.d.ts.map +1 -0
  44. package/dist/cjs/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_in_development_mode.d.ts +2 -0
  45. package/dist/cjs/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_in_development_mode.d.ts.map +1 -0
  46. package/dist/cjs/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.d.ts +2 -0
  47. package/dist/cjs/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.d.ts.map +1 -0
  48. package/dist/cjs/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_active_subscriptions.d.ts +2 -0
  49. package/dist/cjs/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_active_subscriptions.d.ts.map +1 -0
  50. package/dist/cjs/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_no_subscriptions.d.ts +2 -0
  51. package/dist/cjs/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_no_subscriptions.d.ts.map +1 -0
  52. package/dist/cjs/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_pending_deferred_cleanup.d.ts +2 -0
  53. package/dist/cjs/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_pending_deferred_cleanup.d.ts.map +1 -0
  54. package/dist/cjs/queries/for_ReconnectPolicy/when_scheduling/and_rescheduled_before_first_fires/cancels_previous_timer.d.ts +2 -0
  55. package/dist/cjs/queries/for_ReconnectPolicy/when_scheduling/and_rescheduled_before_first_fires/cancels_previous_timer.d.ts.map +1 -0
  56. package/dist/cjs/queries/for_ServerSentEventHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.d.ts +2 -0
  57. package/dist/cjs/queries/for_ServerSentEventHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.d.ts.map +1 -0
  58. package/dist/cjs/queries/for_ServerSentEventHubConnection/when_subscribing/and_subscribe_post_fails.d.ts +2 -0
  59. package/dist/cjs/queries/for_ServerSentEventHubConnection/when_subscribing/and_subscribe_post_fails.d.ts.map +1 -0
  60. package/dist/cjs/queries/for_WebSocketHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.d.ts +2 -0
  61. package/dist/cjs/queries/for_WebSocketHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.d.ts.map +1 -0
  62. package/dist/esm/identity/IdentityProvider.d.ts +2 -1
  63. package/dist/esm/identity/IdentityProvider.d.ts.map +1 -1
  64. package/dist/esm/identity/IdentityProvider.js +16 -2
  65. package/dist/esm/identity/IdentityProvider.js.map +1 -1
  66. package/dist/esm/identity/for_IdentityProvider/when_refreshing/with_unauthorized_response.d.ts +2 -0
  67. package/dist/esm/identity/for_IdentityProvider/when_refreshing/with_unauthorized_response.d.ts.map +1 -0
  68. package/dist/esm/identity/for_IdentityProvider/when_refreshing/with_unauthorized_response.js +19 -0
  69. package/dist/esm/identity/for_IdentityProvider/when_refreshing/with_unauthorized_response.js.map +1 -0
  70. package/dist/esm/queries/HubConnectionKeepAlive.d.ts +2 -1
  71. package/dist/esm/queries/HubConnectionKeepAlive.d.ts.map +1 -1
  72. package/dist/esm/queries/HubConnectionKeepAlive.js +4 -2
  73. package/dist/esm/queries/HubConnectionKeepAlive.js.map +1 -1
  74. package/dist/esm/queries/QueryInstanceCache.d.ts +8 -0
  75. package/dist/esm/queries/QueryInstanceCache.d.ts.map +1 -1
  76. package/dist/esm/queries/QueryInstanceCache.js +70 -9
  77. package/dist/esm/queries/QueryInstanceCache.js.map +1 -1
  78. package/dist/esm/queries/QueryResult.d.ts +1 -0
  79. package/dist/esm/queries/QueryResult.d.ts.map +1 -1
  80. package/dist/esm/queries/QueryResult.js +18 -0
  81. package/dist/esm/queries/QueryResult.js.map +1 -1
  82. package/dist/esm/queries/ReconnectPolicy.d.ts.map +1 -1
  83. package/dist/esm/queries/ReconnectPolicy.js +1 -0
  84. package/dist/esm/queries/ReconnectPolicy.js.map +1 -1
  85. package/dist/esm/queries/ServerSentEventHubConnection.d.ts +1 -0
  86. package/dist/esm/queries/ServerSentEventHubConnection.d.ts.map +1 -1
  87. package/dist/esm/queries/ServerSentEventHubConnection.js +22 -3
  88. package/dist/esm/queries/ServerSentEventHubConnection.js.map +1 -1
  89. package/dist/esm/queries/WebSocketHubConnection.d.ts +1 -0
  90. package/dist/esm/queries/WebSocketHubConnection.d.ts.map +1 -1
  91. package/dist/esm/queries/WebSocketHubConnection.js +11 -0
  92. package/dist/esm/queries/WebSocketHubConnection.js.map +1 -1
  93. package/dist/esm/queries/for_HubConnectionKeepAlive/when_started_with_custom_idle_threshold/respects_threshold.d.ts +2 -0
  94. package/dist/esm/queries/for_HubConnectionKeepAlive/when_started_with_custom_idle_threshold/respects_threshold.d.ts.map +1 -0
  95. package/dist/esm/queries/for_HubConnectionKeepAlive/when_started_with_custom_idle_threshold/respects_threshold.js +33 -0
  96. package/dist/esm/queries/for_HubConnectionKeepAlive/when_started_with_custom_idle_threshold/respects_threshold.js.map +1 -0
  97. package/dist/esm/queries/for_QueryInstanceCache/when_acquiring/after_release_in_development_mode.d.ts +2 -0
  98. package/dist/esm/queries/for_QueryInstanceCache/when_acquiring/after_release_in_development_mode.d.ts.map +1 -0
  99. package/dist/esm/queries/for_QueryInstanceCache/when_acquiring/after_release_in_development_mode.js +23 -0
  100. package/dist/esm/queries/for_QueryInstanceCache/when_acquiring/after_release_in_development_mode.js.map +1 -0
  101. package/dist/esm/queries/for_QueryInstanceCache/when_deferring_dispose/with_cancellation_before_timeout.d.ts +2 -0
  102. package/dist/esm/queries/for_QueryInstanceCache/when_deferring_dispose/with_cancellation_before_timeout.d.ts.map +1 -0
  103. package/dist/esm/queries/for_QueryInstanceCache/when_deferring_dispose/with_cancellation_before_timeout.js +23 -0
  104. package/dist/esm/queries/for_QueryInstanceCache/when_deferring_dispose/with_cancellation_before_timeout.js.map +1 -0
  105. package/dist/esm/queries/for_QueryInstanceCache/when_deferring_dispose/without_cancellation.d.ts +2 -0
  106. package/dist/esm/queries/for_QueryInstanceCache/when_deferring_dispose/without_cancellation.d.ts.map +1 -0
  107. package/dist/esm/queries/for_QueryInstanceCache/when_deferring_dispose/without_cancellation.js +21 -0
  108. package/dist/esm/queries/for_QueryInstanceCache/when_deferring_dispose/without_cancellation.js.map +1 -0
  109. package/dist/esm/queries/for_QueryInstanceCache/when_disposing/an_empty_cache.d.ts +2 -0
  110. package/dist/esm/queries/for_QueryInstanceCache/when_disposing/an_empty_cache.d.ts.map +1 -0
  111. package/dist/esm/queries/for_QueryInstanceCache/when_disposing/an_empty_cache.js +17 -0
  112. package/dist/esm/queries/for_QueryInstanceCache/when_disposing/an_empty_cache.js.map +1 -0
  113. package/dist/esm/queries/for_QueryInstanceCache/when_disposing/with_active_subscriptions.d.ts +2 -0
  114. package/dist/esm/queries/for_QueryInstanceCache/when_disposing/with_active_subscriptions.d.ts.map +1 -0
  115. package/dist/esm/queries/for_QueryInstanceCache/when_disposing/with_active_subscriptions.js +23 -0
  116. package/dist/esm/queries/for_QueryInstanceCache/when_disposing/with_active_subscriptions.js.map +1 -0
  117. package/dist/esm/queries/for_QueryInstanceCache/when_disposing/with_pending_deferred_cleanup.d.ts +2 -0
  118. package/dist/esm/queries/for_QueryInstanceCache/when_disposing/with_pending_deferred_cleanup.d.ts.map +1 -0
  119. package/dist/esm/queries/for_QueryInstanceCache/when_disposing/with_pending_deferred_cleanup.js +22 -0
  120. package/dist/esm/queries/for_QueryInstanceCache/when_disposing/with_pending_deferred_cleanup.js.map +1 -0
  121. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_in_development_mode.d.ts +2 -0
  122. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_in_development_mode.d.ts.map +1 -0
  123. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_in_development_mode.js +28 -0
  124. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_in_development_mode.js.map +1 -0
  125. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.d.ts +2 -0
  126. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.d.ts.map +1 -0
  127. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.js +25 -0
  128. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.js.map +1 -0
  129. package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_active_subscriptions.d.ts +2 -0
  130. package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_active_subscriptions.d.ts.map +1 -0
  131. package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_active_subscriptions.js +25 -0
  132. package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_active_subscriptions.js.map +1 -0
  133. package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_no_subscriptions.d.ts +2 -0
  134. package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_no_subscriptions.d.ts.map +1 -0
  135. package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_no_subscriptions.js +12 -0
  136. package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_no_subscriptions.js.map +1 -0
  137. package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_pending_deferred_cleanup.d.ts +2 -0
  138. package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_pending_deferred_cleanup.d.ts.map +1 -0
  139. package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_pending_deferred_cleanup.js +23 -0
  140. package/dist/esm/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_pending_deferred_cleanup.js.map +1 -0
  141. package/dist/esm/queries/for_ReconnectPolicy/when_scheduling/and_rescheduled_before_first_fires/cancels_previous_timer.d.ts +2 -0
  142. package/dist/esm/queries/for_ReconnectPolicy/when_scheduling/and_rescheduled_before_first_fires/cancels_previous_timer.d.ts.map +1 -0
  143. package/dist/esm/queries/for_ReconnectPolicy/when_scheduling/and_rescheduled_before_first_fires/cancels_previous_timer.js +24 -0
  144. package/dist/esm/queries/for_ReconnectPolicy/when_scheduling/and_rescheduled_before_first_fires/cancels_previous_timer.js.map +1 -0
  145. package/dist/esm/queries/for_ServerSentEventHubConnection/when_keep_alive_is_idle/triggers_reconnect.js +1 -1
  146. package/dist/esm/queries/for_ServerSentEventHubConnection/when_keep_alive_is_idle/triggers_reconnect.js.map +1 -1
  147. package/dist/esm/queries/for_ServerSentEventHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.d.ts +2 -0
  148. package/dist/esm/queries/for_ServerSentEventHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.d.ts.map +1 -0
  149. package/dist/esm/queries/for_ServerSentEventHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.js +35 -0
  150. package/dist/esm/queries/for_ServerSentEventHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.js.map +1 -0
  151. package/dist/esm/queries/for_ServerSentEventHubConnection/when_subscribing/and_subscribe_post_fails.d.ts +2 -0
  152. package/dist/esm/queries/for_ServerSentEventHubConnection/when_subscribing/and_subscribe_post_fails.d.ts.map +1 -0
  153. package/dist/esm/queries/for_ServerSentEventHubConnection/when_subscribing/and_subscribe_post_fails.js +33 -0
  154. package/dist/esm/queries/for_ServerSentEventHubConnection/when_subscribing/and_subscribe_post_fails.js.map +1 -0
  155. package/dist/esm/queries/for_WebSocketHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.d.ts +2 -0
  156. package/dist/esm/queries/for_WebSocketHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.d.ts.map +1 -0
  157. package/dist/esm/queries/for_WebSocketHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.js +33 -0
  158. package/dist/esm/queries/for_WebSocketHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.js.map +1 -0
  159. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  160. package/identity/IdentityProvider.ts +23 -2
  161. package/identity/for_IdentityProvider/when_refreshing/with_unauthorized_response.ts +26 -0
  162. package/package.json +1 -1
  163. package/queries/HubConnectionKeepAlive.ts +20 -6
  164. package/queries/QueryInstanceCache.ts +133 -12
  165. package/queries/QueryResult.ts +19 -0
  166. package/queries/ReconnectPolicy.ts +4 -0
  167. package/queries/ServerSentEventHubConnection.ts +29 -4
  168. package/queries/WebSocketHubConnection.ts +11 -0
  169. package/queries/for_HubConnectionKeepAlive/when_started_with_custom_idle_threshold/respects_threshold.ts +46 -0
  170. package/queries/for_QueryInstanceCache/when_acquiring/after_release_in_development_mode.ts +31 -0
  171. package/queries/for_QueryInstanceCache/when_deferring_dispose/with_cancellation_before_timeout.ts +31 -0
  172. package/queries/for_QueryInstanceCache/when_deferring_dispose/without_cancellation.ts +28 -0
  173. package/queries/for_QueryInstanceCache/when_disposing/an_empty_cache.ts +21 -0
  174. package/queries/for_QueryInstanceCache/when_disposing/with_active_subscriptions.ts +30 -0
  175. package/queries/for_QueryInstanceCache/when_disposing/with_pending_deferred_cleanup.ts +31 -0
  176. package/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_in_development_mode.ts +36 -0
  177. package/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.ts +33 -0
  178. package/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_active_subscriptions.ts +32 -0
  179. package/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_no_subscriptions.ts +18 -0
  180. package/queries/for_QueryInstanceCache/when_tearing_down_all_subscriptions/with_pending_deferred_cleanup.ts +33 -0
  181. package/queries/for_ReconnectPolicy/when_scheduling/and_rescheduled_before_first_fires/cancels_previous_timer.ts +36 -0
  182. package/queries/for_ServerSentEventHubConnection/when_keep_alive_is_idle/triggers_reconnect.ts +5 -2
  183. package/queries/for_ServerSentEventHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.ts +51 -0
  184. package/queries/for_ServerSentEventHubConnection/when_subscribing/and_subscribe_post_fails.ts +51 -0
  185. 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.clearCookie();
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
- private static clearCookie() {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cratis/arc",
3
- "version": "20.1.3",
3
+ "version": "20.1.5",
4
4
  "description": "",
5
5
  "author": "Cratis",
6
6
  "license": "MIT",
@@ -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 tick the
9
- * provided {@link onIdle} callback is invoked — the caller decides whether to send a ping,
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 in the last {@link intervalMs} milliseconds.
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._intervalMs) {
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
- entry.subscribed = false;
220
- entry.teardown?.();
221
- entry.teardown = undefined;
222
-
223
- // Defer deletion so React Strict Mode re-mounts can re-acquire the entry.
224
- setTimeout(() => {
225
- const current = this._entries.get(key);
226
-
227
- if (current && current.subscriberCount <= 0) {
228
- this._entries.delete(key);
229
- }
230
- }, 0);
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
  }
@@ -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 interval, the connection is stale and we reconnect.
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 ${keepAliveIntervalMs}ms, reconnecting '${this._sseUrl}'`);
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
+ });
@@ -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
+ });