@cratis/arc 22.0.2 → 22.1.0

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 (189) hide show
  1. package/dist/cjs/queries/ObservableQueryFor.js +6 -1
  2. package/dist/cjs/queries/ObservableQueryFor.js.map +1 -1
  3. package/dist/cjs/queries/QueryInstanceCache.js +6 -6
  4. package/dist/cjs/queries/QueryInstanceCache.js.map +1 -1
  5. package/dist/cjs/queries/QueryResult.js +23 -4
  6. package/dist/cjs/queries/QueryResult.js.map +1 -1
  7. package/dist/cjs/queries/QueryResultWithState.js +20 -7
  8. package/dist/cjs/queries/QueryResultWithState.js.map +1 -1
  9. package/dist/cjs/queries/ServerSentEventHubConnection.js +120 -58
  10. package/dist/cjs/queries/ServerSentEventHubConnection.js.map +1 -1
  11. package/dist/cjs/queries/WebSocketHubConnection.js +121 -51
  12. package/dist/cjs/queries/WebSocketHubConnection.js.map +1 -1
  13. package/dist/esm/queries/IQueryResult.d.ts +1 -0
  14. package/dist/esm/queries/IQueryResult.d.ts.map +1 -1
  15. package/dist/esm/queries/ObservableQueryFor.d.ts.map +1 -1
  16. package/dist/esm/queries/ObservableQueryFor.js +6 -1
  17. package/dist/esm/queries/ObservableQueryFor.js.map +1 -1
  18. package/dist/esm/queries/QueryInstanceCache.d.ts +1 -1
  19. package/dist/esm/queries/QueryInstanceCache.d.ts.map +1 -1
  20. package/dist/esm/queries/QueryInstanceCache.js +6 -6
  21. package/dist/esm/queries/QueryInstanceCache.js.map +1 -1
  22. package/dist/esm/queries/QueryResult.d.ts +2 -0
  23. package/dist/esm/queries/QueryResult.d.ts.map +1 -1
  24. package/dist/esm/queries/QueryResult.js +23 -4
  25. package/dist/esm/queries/QueryResult.js.map +1 -1
  26. package/dist/esm/queries/QueryResultWithState.d.ts +3 -3
  27. package/dist/esm/queries/QueryResultWithState.d.ts.map +1 -1
  28. package/dist/esm/queries/QueryResultWithState.js +20 -7
  29. package/dist/esm/queries/QueryResultWithState.js.map +1 -1
  30. package/dist/esm/queries/ServerSentEventHubConnection.d.ts +11 -4
  31. package/dist/esm/queries/ServerSentEventHubConnection.d.ts.map +1 -1
  32. package/dist/esm/queries/ServerSentEventHubConnection.js +120 -58
  33. package/dist/esm/queries/ServerSentEventHubConnection.js.map +1 -1
  34. package/dist/esm/queries/WebSocketHubConnection.d.ts +13 -2
  35. package/dist/esm/queries/WebSocketHubConnection.d.ts.map +1 -1
  36. package/dist/esm/queries/WebSocketHubConnection.js +121 -51
  37. package/dist/esm/queries/WebSocketHubConnection.js.map +1 -1
  38. package/dist/esm/queries/for_ObservableQueryFor/when_performing/with_a_not_ready_result.d.ts +2 -0
  39. package/dist/esm/queries/for_ObservableQueryFor/when_performing/with_a_not_ready_result.d.ts.map +1 -0
  40. package/dist/esm/queries/for_ObservableQueryFor/when_performing/with_a_not_ready_result.js +34 -0
  41. package/dist/esm/queries/for_ObservableQueryFor/when_performing/with_a_not_ready_result.js.map +1 -0
  42. package/dist/esm/queries/for_ObservableQueryFor/when_subscribing/with_direct_mode_and_sse_transport.js +26 -2
  43. package/dist/esm/queries/for_ObservableQueryFor/when_subscribing/with_direct_mode_and_sse_transport.js.map +1 -1
  44. package/dist/esm/queries/for_ObservableQueryFor/when_subscribing/with_direct_mode_disabled_and_sse_transport.js +60 -3
  45. package/dist/esm/queries/for_ObservableQueryFor/when_subscribing/with_direct_mode_disabled_and_sse_transport.js.map +1 -1
  46. package/dist/esm/queries/for_ObservableQueryFor/when_subscribing/with_web_socket_hub_payload_without_readiness.d.ts +2 -0
  47. package/dist/esm/queries/for_ObservableQueryFor/when_subscribing/with_web_socket_hub_payload_without_readiness.d.ts.map +1 -0
  48. package/dist/esm/queries/for_ObservableQueryFor/when_subscribing/with_web_socket_hub_payload_without_readiness.js +83 -0
  49. package/dist/esm/queries/for_ObservableQueryFor/when_subscribing/with_web_socket_hub_payload_without_readiness.js.map +1 -0
  50. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/with_only_readiness_changed.d.ts +2 -0
  51. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/with_only_readiness_changed.d.ts.map +1 -0
  52. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/with_only_readiness_changed.js +22 -0
  53. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/with_only_readiness_changed.js.map +1 -0
  54. package/dist/esm/queries/for_QueryResult/when_constructing_from_a_server_result/with_readiness_false.d.ts +2 -0
  55. package/dist/esm/queries/for_QueryResult/when_constructing_from_a_server_result/with_readiness_false.d.ts.map +1 -0
  56. package/dist/esm/queries/for_QueryResult/when_constructing_from_a_server_result/with_readiness_false.js +17 -0
  57. package/dist/esm/queries/for_QueryResult/when_constructing_from_a_server_result/with_readiness_false.js.map +1 -0
  58. package/dist/esm/queries/for_QueryResult/when_constructing_from_a_server_result/without_readiness.d.ts +2 -0
  59. package/dist/esm/queries/for_QueryResult/when_constructing_from_a_server_result/without_readiness.d.ts.map +1 -0
  60. package/dist/esm/queries/for_QueryResult/when_constructing_from_a_server_result/without_readiness.js +16 -0
  61. package/dist/esm/queries/for_QueryResult/when_constructing_from_a_server_result/without_readiness.js.map +1 -0
  62. package/dist/esm/queries/for_QueryResultWithState/when_creating_from_query_result/with_readiness_false.d.ts +2 -0
  63. package/dist/esm/queries/for_QueryResultWithState/when_creating_from_query_result/with_readiness_false.d.ts.map +1 -0
  64. package/dist/esm/queries/for_QueryResultWithState/when_creating_from_query_result/with_readiness_false.js +19 -0
  65. package/dist/esm/queries/for_QueryResultWithState/when_creating_from_query_result/with_readiness_false.js.map +1 -0
  66. package/dist/esm/queries/for_QueryResultWithState/when_creating_from_query_result/without_readiness.d.ts +2 -0
  67. package/dist/esm/queries/for_QueryResultWithState/when_creating_from_query_result/without_readiness.d.ts.map +1 -0
  68. package/dist/esm/queries/for_QueryResultWithState/when_creating_from_query_result/without_readiness.js +19 -0
  69. package/dist/esm/queries/for_QueryResultWithState/when_creating_from_query_result/without_readiness.js.map +1 -0
  70. package/dist/esm/queries/for_ServerSentEventHubConnection/given/a_deferred_promise.d.ts +7 -0
  71. package/dist/esm/queries/for_ServerSentEventHubConnection/given/a_deferred_promise.d.ts.map +1 -0
  72. package/dist/esm/queries/for_ServerSentEventHubConnection/given/a_deferred_promise.js +12 -0
  73. package/dist/esm/queries/for_ServerSentEventHubConnection/given/a_deferred_promise.js.map +1 -0
  74. package/dist/esm/queries/for_ServerSentEventHubConnection/given/a_server_sent_event_hub_connection.d.ts +2 -1
  75. package/dist/esm/queries/for_ServerSentEventHubConnection/given/a_server_sent_event_hub_connection.d.ts.map +1 -1
  76. package/dist/esm/queries/for_ServerSentEventHubConnection/given/a_server_sent_event_hub_connection.js +15 -4
  77. package/dist/esm/queries/for_ServerSentEventHubConnection/given/a_server_sent_event_hub_connection.js.map +1 -1
  78. package/dist/esm/queries/for_ServerSentEventHubConnection/when_a_retired_event_source_fires/ignores_connected_and_error_callbacks.d.ts +2 -0
  79. package/dist/esm/queries/for_ServerSentEventHubConnection/when_a_retired_event_source_fires/ignores_connected_and_error_callbacks.d.ts.map +1 -0
  80. package/dist/esm/queries/for_ServerSentEventHubConnection/when_a_retired_event_source_fires/ignores_connected_and_error_callbacks.js +44 -0
  81. package/dist/esm/queries/for_ServerSentEventHubConnection/when_a_retired_event_source_fires/ignores_connected_and_error_callbacks.js.map +1 -0
  82. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_query_is_replaced.d.ts +2 -0
  83. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_query_is_replaced.d.ts.map +1 -0
  84. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_query_is_replaced.js +74 -0
  85. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_query_is_replaced.js.map +1 -0
  86. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_query_is_unsubscribed.d.ts +2 -0
  87. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_query_is_unsubscribed.d.ts.map +1 -0
  88. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_query_is_unsubscribed.js +74 -0
  89. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_query_is_unsubscribed.js.map +1 -0
  90. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_stale_request_is_rejected.d.ts +2 -0
  91. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_stale_request_is_rejected.d.ts.map +1 -0
  92. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_stale_request_is_rejected.js +59 -0
  93. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_stale_request_is_rejected.js.map +1 -0
  94. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_stale_request_returns_non_ok.d.ts +2 -0
  95. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_stale_request_returns_non_ok.d.ts.map +1 -0
  96. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_stale_request_returns_non_ok.js +58 -0
  97. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_stale_request_returns_non_ok.js.map +1 -0
  98. package/dist/esm/queries/for_ServerSentEventHubConnection/when_disposing/cleans_up_resources.js +6 -3
  99. package/dist/esm/queries/for_ServerSentEventHubConnection/when_disposing/cleans_up_resources.js.map +1 -1
  100. package/dist/esm/queries/for_ServerSentEventHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.js +23 -6
  101. package/dist/esm/queries/for_ServerSentEventHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.js.map +1 -1
  102. package/dist/esm/queries/for_ServerSentEventHubConnection/when_reconnecting/after_error.js +20 -5
  103. package/dist/esm/queries/for_ServerSentEventHubConnection/when_reconnecting/after_error.js.map +1 -1
  104. package/dist/esm/queries/for_ServerSentEventHubConnection/when_reconnecting/and_an_immediate_open_wins_the_backoff_race.d.ts +2 -0
  105. package/dist/esm/queries/for_ServerSentEventHubConnection/when_reconnecting/and_an_immediate_open_wins_the_backoff_race.d.ts.map +1 -0
  106. package/dist/esm/queries/for_ServerSentEventHubConnection/when_reconnecting/and_an_immediate_open_wins_the_backoff_race.js +20 -0
  107. package/dist/esm/queries/for_ServerSentEventHubConnection/when_reconnecting/and_an_immediate_open_wins_the_backoff_race.js.map +1 -0
  108. package/dist/esm/queries/for_ServerSentEventHubConnection/when_replacing_a_subscription/ignores_messages_from_the_retired_generation.d.ts +2 -0
  109. package/dist/esm/queries/for_ServerSentEventHubConnection/when_replacing_a_subscription/ignores_messages_from_the_retired_generation.d.ts.map +1 -0
  110. package/dist/esm/queries/for_ServerSentEventHubConnection/when_replacing_a_subscription/ignores_messages_from_the_retired_generation.js +68 -0
  111. package/dist/esm/queries/for_ServerSentEventHubConnection/when_replacing_a_subscription/ignores_messages_from_the_retired_generation.js.map +1 -0
  112. package/dist/esm/queries/for_ServerSentEventHubConnection/when_subscribing/sends_subscribe_post.js +38 -13
  113. package/dist/esm/queries/for_ServerSentEventHubConnection/when_subscribing/sends_subscribe_post.js.map +1 -1
  114. package/dist/esm/queries/for_ServerSentEventHubConnection/when_subscription_revisions_are_not_advertised/uses_legacy_protocol.d.ts +2 -0
  115. package/dist/esm/queries/for_ServerSentEventHubConnection/when_subscription_revisions_are_not_advertised/uses_legacy_protocol.d.ts.map +1 -0
  116. package/dist/esm/queries/for_ServerSentEventHubConnection/when_subscription_revisions_are_not_advertised/uses_legacy_protocol.js +54 -0
  117. package/dist/esm/queries/for_ServerSentEventHubConnection/when_subscription_revisions_are_not_advertised/uses_legacy_protocol.js.map +1 -0
  118. package/dist/esm/queries/for_ServerSentEventHubConnection/when_unsubscribing/the_only_query.js +18 -4
  119. package/dist/esm/queries/for_ServerSentEventHubConnection/when_unsubscribing/the_only_query.js.map +1 -1
  120. package/dist/esm/queries/for_WebSocketHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.js +22 -5
  121. package/dist/esm/queries/for_WebSocketHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.js.map +1 -1
  122. package/dist/esm/queries/for_WebSocketHubConnection/when_reconnecting/ignores_a_pending_reconnect_and_stale_socket_callbacks.d.ts +2 -0
  123. package/dist/esm/queries/for_WebSocketHubConnection/when_reconnecting/ignores_a_pending_reconnect_and_stale_socket_callbacks.d.ts.map +1 -0
  124. package/dist/esm/queries/for_WebSocketHubConnection/when_reconnecting/ignores_a_pending_reconnect_and_stale_socket_callbacks.js +108 -0
  125. package/dist/esm/queries/for_WebSocketHubConnection/when_reconnecting/ignores_a_pending_reconnect_and_stale_socket_callbacks.js.map +1 -0
  126. package/dist/esm/queries/for_WebSocketHubConnection/when_reconnecting/replaces_a_closing_socket_safely.d.ts +2 -0
  127. package/dist/esm/queries/for_WebSocketHubConnection/when_reconnecting/replaces_a_closing_socket_safely.d.ts.map +1 -0
  128. package/dist/esm/queries/for_WebSocketHubConnection/when_reconnecting/replaces_a_closing_socket_safely.js +33 -0
  129. package/dist/esm/queries/for_WebSocketHubConnection/when_reconnecting/replaces_a_closing_socket_safely.js.map +1 -0
  130. package/dist/esm/queries/for_WebSocketHubConnection/when_reconnecting/resubscribes_all_queries.js +26 -4
  131. package/dist/esm/queries/for_WebSocketHubConnection/when_reconnecting/resubscribes_all_queries.js.map +1 -1
  132. package/dist/esm/queries/for_WebSocketHubConnection/when_replacing_a_subscription/ignores_stale_result_unauthorized_and_error.d.ts +2 -0
  133. package/dist/esm/queries/for_WebSocketHubConnection/when_replacing_a_subscription/ignores_stale_result_unauthorized_and_error.d.ts.map +1 -0
  134. package/dist/esm/queries/for_WebSocketHubConnection/when_replacing_a_subscription/ignores_stale_result_unauthorized_and_error.js +62 -0
  135. package/dist/esm/queries/for_WebSocketHubConnection/when_replacing_a_subscription/ignores_stale_result_unauthorized_and_error.js.map +1 -0
  136. package/dist/esm/queries/for_WebSocketHubConnection/when_subscribing/to_a_query.js +18 -5
  137. package/dist/esm/queries/for_WebSocketHubConnection/when_subscribing/to_a_query.js.map +1 -1
  138. package/dist/esm/queries/for_WebSocketHubConnection/when_subscription_revisions_are_supported/upgrades_active_subscriptions_to_strict_mode.d.ts +2 -0
  139. package/dist/esm/queries/for_WebSocketHubConnection/when_subscription_revisions_are_supported/upgrades_active_subscriptions_to_strict_mode.d.ts.map +1 -0
  140. package/dist/esm/queries/for_WebSocketHubConnection/when_subscription_revisions_are_supported/upgrades_active_subscriptions_to_strict_mode.js +56 -0
  141. package/dist/esm/queries/for_WebSocketHubConnection/when_subscription_revisions_are_supported/upgrades_active_subscriptions_to_strict_mode.js.map +1 -0
  142. package/dist/esm/queries/for_WebSocketHubConnection/when_unsubscribing/the_only_query.js +16 -3
  143. package/dist/esm/queries/for_WebSocketHubConnection/when_unsubscribing/the_only_query.js.map +1 -1
  144. package/dist/esm/queries/for_WebSocketHubConnection/when_unsubscribing/with_revision_support_not_advertised.d.ts +2 -0
  145. package/dist/esm/queries/for_WebSocketHubConnection/when_unsubscribing/with_revision_support_not_advertised.d.ts.map +1 -0
  146. package/dist/esm/queries/for_WebSocketHubConnection/when_unsubscribing/with_revision_support_not_advertised.js +33 -0
  147. package/dist/esm/queries/for_WebSocketHubConnection/when_unsubscribing/with_revision_support_not_advertised.js.map +1 -0
  148. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  149. package/package.json +1 -1
  150. package/queries/IQueryResult.ts +10 -0
  151. package/queries/ObservableQueryFor.ts +44 -16
  152. package/queries/QueryInstanceCache.ts +42 -19
  153. package/queries/QueryResult.ts +145 -79
  154. package/queries/QueryResultWithState.ts +32 -9
  155. package/queries/ServerSentEventHubConnection.ts +270 -83
  156. package/queries/WebSocketHubConnection.ts +188 -56
  157. package/queries/for_ObservableQueryFor/when_performing/with_a_not_ready_result.ts +46 -0
  158. package/queries/for_ObservableQueryFor/when_subscribing/with_direct_mode_and_sse_transport.ts +91 -57
  159. package/queries/for_ObservableQueryFor/when_subscribing/with_direct_mode_disabled_and_sse_transport.ts +125 -53
  160. package/queries/for_ObservableQueryFor/when_subscribing/with_web_socket_hub_payload_without_readiness.ts +107 -0
  161. package/queries/for_QueryInstanceCache/when_setting_last_result/with_only_readiness_changed.ts +63 -0
  162. package/queries/for_QueryResult/when_constructing_from_a_server_result/with_readiness_false.ts +25 -0
  163. package/queries/for_QueryResult/when_constructing_from_a_server_result/without_readiness.ts +24 -0
  164. package/queries/for_QueryResultWithState/when_creating_from_query_result/with_readiness_false.ts +28 -0
  165. package/queries/for_QueryResultWithState/when_creating_from_query_result/without_readiness.ts +25 -0
  166. package/queries/for_ServerSentEventHubConnection/given/a_deferred_promise.ts +15 -0
  167. package/queries/for_ServerSentEventHubConnection/given/a_server_sent_event_hub_connection.ts +18 -7
  168. package/queries/for_ServerSentEventHubConnection/when_a_retired_event_source_fires/ignores_connected_and_error_callbacks.ts +59 -0
  169. package/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_query_is_replaced.ts +113 -0
  170. package/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_query_is_unsubscribed.ts +109 -0
  171. package/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_stale_request_is_rejected.ts +81 -0
  172. package/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_stale_request_returns_non_ok.ts +83 -0
  173. package/queries/for_ServerSentEventHubConnection/when_disposing/cleans_up_resources.ts +30 -21
  174. package/queries/for_ServerSentEventHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.ts +56 -32
  175. package/queries/for_ServerSentEventHubConnection/when_reconnecting/after_error.ts +23 -6
  176. package/queries/for_ServerSentEventHubConnection/when_reconnecting/and_an_immediate_open_wins_the_backoff_race.ts +41 -0
  177. package/queries/for_ServerSentEventHubConnection/when_replacing_a_subscription/ignores_messages_from_the_retired_generation.ts +92 -0
  178. package/queries/for_ServerSentEventHubConnection/when_subscribing/sends_subscribe_post.ts +69 -30
  179. package/queries/for_ServerSentEventHubConnection/when_subscription_revisions_are_not_advertised/uses_legacy_protocol.ts +69 -0
  180. package/queries/for_ServerSentEventHubConnection/when_unsubscribing/the_only_query.ts +43 -24
  181. package/queries/for_WebSocketHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.ts +53 -29
  182. package/queries/for_WebSocketHubConnection/when_reconnecting/ignores_a_pending_reconnect_and_stale_socket_callbacks.ts +131 -0
  183. package/queries/for_WebSocketHubConnection/when_reconnecting/replaces_a_closing_socket_safely.ts +39 -0
  184. package/queries/for_WebSocketHubConnection/when_reconnecting/resubscribes_all_queries.ts +72 -36
  185. package/queries/for_WebSocketHubConnection/when_replacing_a_subscription/ignores_stale_result_unauthorized_and_error.ts +88 -0
  186. package/queries/for_WebSocketHubConnection/when_subscribing/to_a_query.ts +54 -27
  187. package/queries/for_WebSocketHubConnection/when_subscription_revisions_are_supported/upgrades_active_subscriptions_to_strict_mode.ts +72 -0
  188. package/queries/for_WebSocketHubConnection/when_unsubscribing/the_only_query.ts +43 -18
  189. package/queries/for_WebSocketHubConnection/when_unsubscribing/with_revision_support_not_advertised.ts +44 -0
@@ -4,14 +4,17 @@
4
4
  import { ValidationResult } from '../validation/ValidationResult';
5
5
  import { IQueryResult } from './IQueryResult';
6
6
  import { PagingInfo } from './PagingInfo';
7
- import { QueryResult } from './QueryResult';
8
7
  import { ChangeSet } from './ChangeSet';
9
8
 
10
9
  /**
11
10
  * Represents a specialized {@link QueryResult<TDataType>} that holds state for its execution
12
11
  */
13
12
  export class QueryResultWithState<TDataType> implements IQueryResult<TDataType> {
14
-
13
+ /**
14
+ * Creates a settled empty state, such as the state returned by a disabled query.
15
+ * @param defaultValue The query's empty data value.
16
+ * @returns A ready state that is not performing.
17
+ */
15
18
  static empty<TDataType>(defaultValue: TDataType): QueryResultWithState<TDataType> {
16
19
  return new QueryResultWithState(
17
20
  defaultValue,
@@ -23,9 +26,18 @@ export class QueryResultWithState<TDataType> implements IQueryResult<TDataType>
23
26
  false,
24
27
  [],
25
28
  '',
26
- false);
29
+ false,
30
+ undefined,
31
+ true,
32
+ );
27
33
  }
28
34
 
35
+ /**
36
+ * Creates the local initial state before any server result has been received. This state is not ready because
37
+ * no result exists yet; its performing state independently describes the active client request.
38
+ * @param defaultValue The query's initial data value.
39
+ * @returns A not-ready state that is performing.
40
+ */
29
41
  static initial<TDataType>(defaultValue: TDataType): QueryResultWithState<TDataType> {
30
42
  return new QueryResultWithState(
31
43
  defaultValue,
@@ -37,7 +49,10 @@ export class QueryResultWithState<TDataType> implements IQueryResult<TDataType>
37
49
  false,
38
50
  [],
39
51
  '',
40
- true);
52
+ true,
53
+ undefined,
54
+ false,
55
+ );
41
56
  }
42
57
 
43
58
  /**
@@ -53,6 +68,8 @@ export class QueryResultWithState<TDataType> implements IQueryResult<TDataType>
53
68
  * @param {string} exceptionStackTrace Exception stack trace, if any.
54
69
  * @param {boolean} isPerforming Whether or not the query is being performed. True if its performing, false if it is done.
55
70
  * @param {ChangeSet<unknown> | undefined} changeSet Optional change set from the server describing the delta since the last update.
71
+ * @param {boolean} isReady Whether the query has produced a result. Defaults to true so existing constructor calls
72
+ * represent settled synthetic states; {@link initial} explicitly uses false because no server result exists yet.
56
73
  */
57
74
  constructor(
58
75
  readonly data: TDataType,
@@ -65,8 +82,9 @@ export class QueryResultWithState<TDataType> implements IQueryResult<TDataType>
65
82
  readonly exceptionMessages: string[],
66
83
  readonly exceptionStackTrace: string,
67
84
  readonly isPerforming: boolean,
68
- readonly changeSet?: ChangeSet<unknown>) {
69
- }
85
+ readonly changeSet?: ChangeSet<unknown>,
86
+ readonly isReady: boolean = true,
87
+ ) {}
70
88
 
71
89
  /** @inheritdoc */
72
90
  get hasData(): boolean {
@@ -77,12 +95,15 @@ export class QueryResultWithState<TDataType> implements IQueryResult<TDataType>
77
95
  }
78
96
 
79
97
  /**
80
- * Create a new {@link QueryResultWithState<TDataType>} from {@link QueryResult<TDataType>}.
98
+ * Create a new {@link QueryResultWithState<TDataType>} from an {@link IQueryResult<TDataType>}.
81
99
  * @param queryResult The original query result.
82
100
  * @param isPerforming Whether or not the query is performing.
83
101
  * @returns A new {@link QueryResultWithState<TDataType>}
84
102
  */
85
- static fromQueryResult<TDataType>(queryResult: QueryResult<TDataType>, isPerforming: boolean) {
103
+ static fromQueryResult<TDataType>(
104
+ queryResult: IQueryResult<TDataType>,
105
+ isPerforming: boolean,
106
+ ) {
86
107
  return new QueryResultWithState<TDataType>(
87
108
  queryResult.data,
88
109
  queryResult.paging,
@@ -94,6 +115,8 @@ export class QueryResultWithState<TDataType> implements IQueryResult<TDataType>
94
115
  queryResult.exceptionMessages,
95
116
  queryResult.exceptionStackTrace,
96
117
  isPerforming,
97
- queryResult.changeSet);
118
+ queryResult.changeSet,
119
+ queryResult.isReady ?? true,
120
+ );
98
121
  }
99
122
  }
@@ -2,19 +2,24 @@
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
4
  import { Globals } from '../Globals';
5
- import { IObservableQueryHubConnection } from './IObservableQueryHubConnection';
6
- import { DataReceived } from './ObservableQueryConnection';
5
+ import type { IObservableQueryHubConnection } from './IObservableQueryHubConnection';
6
+ import type { DataReceived } from './ObservableQueryConnection';
7
7
  import { HubConnectionKeepAlive } from './HubConnectionKeepAlive';
8
- import { IReconnectPolicy } from './IReconnectPolicy';
8
+ import type { IReconnectPolicy } from './IReconnectPolicy';
9
9
  import { ReconnectPolicy } from './ReconnectPolicy';
10
10
  import { QueryResult } from './QueryResult';
11
- import { HubMessage, HubMessageType, SubscriptionRequest } from './WebSocketHubConnection';
11
+ import {
12
+ type HubMessage,
13
+ HubMessageType,
14
+ type SubscriptionRequest,
15
+ } from './WebSocketHubConnection';
12
16
 
13
17
  /* eslint-disable @typescript-eslint/no-explicit-any */
14
18
 
15
19
  interface ActiveSubscription {
16
20
  request: SubscriptionRequest;
17
21
  callback: DataReceived<any>;
22
+ revision: number;
18
23
  }
19
24
 
20
25
  /**
@@ -46,6 +51,8 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
46
51
  private _lastPongLatency: number = 0;
47
52
  private _latencySamples: number[] = [];
48
53
  private _connectTimeoutTimer?: ReturnType<typeof setTimeout>;
54
+ private _nextRevision = 0;
55
+ private _supportsSubscriptionRevisions = false;
49
56
  private readonly _keepAlive: HubConnectionKeepAlive;
50
57
 
51
58
  /**
@@ -57,7 +64,7 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
57
64
  * @param {number} keepAliveIntervalMs The keep-alive cadence to assume until the server advertises
58
65
  * its own on the {@link HubMessageType.Connected} message (default: 30 000 ms). The connection is
59
66
  * considered stale after {@link IDLE_THRESHOLD_FACTOR} times this without any server message.
60
- * @param {number} connectTimeoutMs How long to wait for the {@link HubMessageType.Connected}
67
+ * @param {number} _connectTimeoutMs How long to wait for the {@link HubMessageType.Connected}
61
68
  * message after the HTTP connection opens before giving up and retrying (default: 15 000 ms).
62
69
  * @param {IReconnectPolicy} _policy The reconnect policy to use (default: {@link ReconnectPolicy}).
63
70
  */
@@ -68,7 +75,7 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
68
75
  private readonly _microservice: string,
69
76
  keepAliveIntervalMs: number = 30000,
70
77
  private readonly _connectTimeoutMs: number = 15000,
71
- private readonly _policy: IReconnectPolicy = new ReconnectPolicy()
78
+ private readonly _policy: IReconnectPolicy = new ReconnectPolicy(),
72
79
  ) {
73
80
  // SSE is server→client only: the client cannot send pings. Instead we watch for
74
81
  // inactivity — if the server stops sending messages (including its own keep-alive
@@ -82,12 +89,20 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
82
89
  //
83
90
  // The interval below is only the starting assumption; the server advertises its real
84
91
  // cadence on the Connected message and {@link handleConnected} reconfigures from it.
85
- this._keepAlive = new HubConnectionKeepAlive(keepAliveIntervalMs, () => {
86
- if (!this._disconnected && this._subscriptions.size > 0) {
87
- console.warn(`SSE hub: no messages received for ${this._keepAlive.idleThresholdMs}ms, reconnecting '${this._sseUrl}'`);
88
- this.reconnect();
89
- }
90
- }, keepAliveIntervalMs * IDLE_THRESHOLD_FACTOR);
92
+ this._keepAlive = new HubConnectionKeepAlive(
93
+ keepAliveIntervalMs,
94
+ () => {
95
+ if (!this._disconnected && this._subscriptions.size > 0) {
96
+ console.warn(
97
+ 'SSE hub: no messages received; reconnecting',
98
+ this._keepAlive.idleThresholdMs,
99
+ this._sseUrl,
100
+ );
101
+ this.reconnect();
102
+ }
103
+ },
104
+ keepAliveIntervalMs * IDLE_THRESHOLD_FACTOR,
105
+ );
91
106
  }
92
107
 
93
108
  /** @inheritdoc */
@@ -97,7 +112,10 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
97
112
 
98
113
  /** @inheritdoc */
99
114
  get isConnected(): boolean {
100
- return this._connectionId !== undefined && this._eventSource?.readyState === EventSource.OPEN;
115
+ return (
116
+ this._connectionId !== undefined &&
117
+ this._eventSource?.readyState === EventSource.OPEN
118
+ );
101
119
  }
102
120
 
103
121
  /** @inheritdoc */
@@ -108,18 +126,28 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
108
126
  /** @inheritdoc */
109
127
  get averageLatency(): number {
110
128
  if (this._latencySamples.length === 0) return 0;
111
- return this._latencySamples.reduce((a, b) => a + b, 0) / this._latencySamples.length;
129
+ return (
130
+ this._latencySamples.reduce((a, b) => a + b, 0) / this._latencySamples.length
131
+ );
112
132
  }
113
133
 
114
134
  /** @inheritdoc */
115
- subscribe(queryId: string, request: SubscriptionRequest, callback: DataReceived<any>): void {
116
- const sub: ActiveSubscription = { request, callback };
135
+ subscribe(
136
+ queryId: string,
137
+ request: SubscriptionRequest,
138
+ callback: DataReceived<any>,
139
+ ): void {
140
+ const sub: ActiveSubscription = {
141
+ request,
142
+ callback,
143
+ revision: this.getNextRevision(),
144
+ };
117
145
  this._subscriptions.set(queryId, sub);
118
146
 
119
147
  this.ensureConnected();
120
148
 
121
149
  if (this._connectionId) {
122
- this.sendSubscribe(queryId, request);
150
+ this.sendSubscribe(queryId, sub);
123
151
  } else {
124
152
  // Not yet connected, queue for when Connected message arrives.
125
153
  this._pendingSubscriptions.set(queryId, sub);
@@ -128,11 +156,12 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
128
156
 
129
157
  /** @inheritdoc */
130
158
  unsubscribe(queryId: string): void {
159
+ const subscription = this._subscriptions.get(queryId);
131
160
  this._subscriptions.delete(queryId);
132
161
  this._pendingSubscriptions.delete(queryId);
133
162
 
134
- if (this._connectionId) {
135
- this.sendUnsubscribe(queryId);
163
+ if (this._connectionId && subscription) {
164
+ this.sendUnsubscribe(queryId, subscription.revision);
136
165
  }
137
166
 
138
167
  if (this._subscriptions.size === 0) {
@@ -148,11 +177,24 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
148
177
  this._policy.cancel();
149
178
  this._keepAlive.stop();
150
179
  this.clearConnectTimeout();
151
- this._eventSource?.close();
152
- this._eventSource = undefined;
180
+ this.detachAndCloseEventSource();
153
181
  this._connectionId = undefined;
154
182
  }
155
183
 
184
+ private detachAndCloseEventSource(): void {
185
+ const eventSource = this._eventSource;
186
+ if (!eventSource) return;
187
+
188
+ eventSource.onopen = null;
189
+ eventSource.onmessage = null;
190
+ eventSource.onerror = null;
191
+ eventSource.close();
192
+
193
+ if (this._eventSource === eventSource) {
194
+ this._eventSource = undefined;
195
+ }
196
+ }
197
+
156
198
  private ensureConnected(): void {
157
199
  if (this._disconnected) {
158
200
  this._disconnected = false;
@@ -162,6 +204,9 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
162
204
  return;
163
205
  }
164
206
 
207
+ // An immediate subscription can win while a back-off callback is pending. Cancel that callback before
208
+ // opening, and let openEventSource perform the same live-source guard for already-queued callbacks.
209
+ this._policy.cancel();
165
210
  this.openEventSource();
166
211
  }
167
212
 
@@ -170,21 +215,16 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
170
215
  this._policy.cancel();
171
216
  this._keepAlive.stop();
172
217
  this.clearConnectTimeout();
173
- if (this._eventSource) {
174
- // Detach all handlers BEFORE closing so that the async onerror / onmessage
175
- // events that fire after close() cannot observe the new _disconnected=false
176
- // state set by an immediately following ensureConnected() call. Without this,
177
- // a stale onerror triggers an unintended reconnect with exponential back-off.
178
- this._eventSource.onopen = null;
179
- this._eventSource.onmessage = null;
180
- this._eventSource.onerror = null;
181
- this._eventSource.close();
182
- }
183
- this._eventSource = undefined;
218
+ this.detachAndCloseEventSource();
184
219
  this._connectionId = undefined;
185
220
  }
186
221
 
187
222
  private openEventSource(): void {
223
+ if (this._eventSource && this._eventSource.readyState !== EventSource.CLOSED) {
224
+ return;
225
+ }
226
+
227
+ this._policy.cancel();
188
228
  let url = this._sseUrl;
189
229
  if (this._microservice?.length > 0) {
190
230
  const param = `${Globals.microserviceWSQueryArgument}=${encodeURIComponent(this._microservice)}`;
@@ -192,11 +232,15 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
192
232
  }
193
233
 
194
234
  this._connectionId = undefined;
195
- this._eventSource = Globals.eventSourceFactory ? Globals.eventSourceFactory(url) : new EventSource(url);
196
-
197
- this._eventSource.onopen = () => {
198
- if (this._disconnected) return;
199
- console.log(`SSE hub connection established: '${url}'`);
235
+ this._supportsSubscriptionRevisions = false;
236
+ const eventSource = Globals.eventSourceFactory
237
+ ? Globals.eventSourceFactory(url)
238
+ : new EventSource(url);
239
+ this._eventSource = eventSource;
240
+
241
+ eventSource.onopen = () => {
242
+ if (this._disconnected || this._eventSource !== eventSource) return;
243
+ console.log('SSE hub connection established', url);
200
244
  this._policy.reset();
201
245
  this._keepAlive.start();
202
246
 
@@ -204,22 +248,30 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
204
248
  // connection is broken. Close and retry via the reconnect policy.
205
249
  this.clearConnectTimeout();
206
250
  this._connectTimeoutTimer = setTimeout(() => {
207
- if (!this._disconnected && !this._connectionId) {
208
- console.warn(`SSE hub: no Connected message within ${this._connectTimeoutMs}ms, retrying '${url}'`);
251
+ if (
252
+ !this._disconnected &&
253
+ this._eventSource === eventSource &&
254
+ !this._connectionId
255
+ ) {
256
+ console.warn(
257
+ 'SSE hub: no Connected message within timeout; retrying',
258
+ this._connectTimeoutMs,
259
+ url,
260
+ );
209
261
  this.reconnect();
210
262
  }
211
263
  }, this._connectTimeoutMs);
212
264
  };
213
265
 
214
- this._eventSource.onmessage = (event: MessageEvent) => {
215
- if (this._disconnected) return;
266
+ eventSource.onmessage = (event: MessageEvent) => {
267
+ if (this._disconnected || this._eventSource !== eventSource) return;
216
268
  this._keepAlive.recordActivity();
217
269
  this.handleMessage(event.data as string);
218
270
  };
219
271
 
220
- this._eventSource.onerror = () => {
221
- if (this._disconnected) return;
222
- console.warn(`SSE hub connection error: '${url}'`);
272
+ eventSource.onerror = () => {
273
+ if (this._disconnected || this._eventSource !== eventSource) return;
274
+ console.warn('SSE hub connection error', url);
223
275
  this.reconnect();
224
276
  };
225
277
  }
@@ -228,9 +280,8 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
228
280
  this._keepAlive.stop();
229
281
  this.clearConnectTimeout();
230
282
 
231
- // Close the EventSource so the reconnect policy manages the schedule.
232
- this._eventSource?.close();
233
- this._eventSource = undefined;
283
+ // Detach before closing so callbacks already queued by the retired source are inert.
284
+ this.detachAndCloseEventSource();
234
285
  this._connectionId = undefined;
235
286
 
236
287
  // Move all active subscriptions to pending so they re-subscribe when
@@ -243,7 +294,7 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
243
294
 
244
295
  this._policy.schedule(() => {
245
296
  if (!this._disconnected && this._subscriptions.size > 0) {
246
- this.openEventSource();
297
+ this.ensureConnected();
247
298
  }
248
299
  }, this._sseUrl);
249
300
  }
@@ -270,11 +321,19 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
270
321
  // Server-sent keep-alive ping — activity already recorded in onmessage.
271
322
  break;
272
323
  case HubMessageType.Unauthorized:
273
- console.warn(`SSE hub: query '${message.queryId}' unauthorized`);
274
- this.handleUnauthorized(message);
324
+ if (this.isMessageForCurrentSubscription(message)) {
325
+ console.warn('SSE hub: query unauthorized', message.queryId);
326
+ this.handleUnauthorized(message);
327
+ }
275
328
  break;
276
329
  case HubMessageType.Error:
277
- console.error(`SSE hub: query '${message.queryId}' error:`, message.payload);
330
+ if (this.isMessageForCurrentSubscription(message)) {
331
+ console.error(
332
+ 'SSE hub: query error',
333
+ message.queryId,
334
+ message.payload,
335
+ );
336
+ }
278
337
  break;
279
338
  }
280
339
  } catch (error) {
@@ -283,17 +342,25 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
283
342
  }
284
343
 
285
344
  private handleConnected(message: HubMessage): void {
286
- this._connectionId = message.payload as string;
287
- console.log(`SSE hub: connected with id '${this._connectionId}'`);
345
+ const connectionId = message.payload as string;
346
+ const connectionIdChanged = this._connectionId !== connectionId;
347
+ this._connectionId = connectionId;
348
+ this._supportsSubscriptionRevisions =
349
+ message.supportsSubscriptionRevisions === true;
350
+ console.log('SSE hub: connected', this._connectionId);
288
351
 
289
352
  // Connected message arrived — cancel the connect timeout.
290
353
  this.clearConnectTimeout();
291
354
 
292
355
  this.applyServerKeepAliveInterval(message.keepAliveIntervalMs);
293
356
 
294
- // Send all pending subscriptions now that we have a connection ID.
295
- for (const [queryId, sub] of this._pendingSubscriptions) {
296
- this.sendSubscribe(queryId, sub.request);
357
+ // The active collection is authoritative. A previous connection's subscribe POST may
358
+ // still be in flight, so relying only on pending subscriptions can orphan a query when
359
+ // the server assigns a new connection ID before that POST completes.
360
+ if (connectionIdChanged) {
361
+ for (const [queryId, subscription] of this._subscriptions) {
362
+ this.sendSubscribe(queryId, subscription);
363
+ }
297
364
  }
298
365
  this._pendingSubscriptions.clear();
299
366
  }
@@ -316,14 +383,17 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
316
383
  return;
317
384
  }
318
385
 
319
- this._keepAlive.reconfigure(serverIntervalMs, serverIntervalMs * IDLE_THRESHOLD_FACTOR);
386
+ this._keepAlive.reconfigure(
387
+ serverIntervalMs,
388
+ serverIntervalMs * IDLE_THRESHOLD_FACTOR,
389
+ );
320
390
  }
321
391
 
322
392
  private handleQueryResult(message: HubMessage): void {
323
393
  if (!message.queryId) return;
324
394
 
325
395
  const sub = this._subscriptions.get(message.queryId);
326
- if (!sub) return;
396
+ if (!sub || !this.isMessageForCurrentSubscription(message)) return;
327
397
 
328
398
  const result = message.payload as QueryResult<any>;
329
399
  sub.callback(result);
@@ -333,25 +403,37 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
333
403
  if (!message.queryId) return;
334
404
 
335
405
  const sub = this._subscriptions.get(message.queryId);
336
- if (!sub) return;
406
+ if (!sub || !this.isMessageForCurrentSubscription(message)) return;
337
407
 
338
408
  this._subscriptions.delete(message.queryId);
339
409
  this._pendingSubscriptions.delete(message.queryId);
340
410
  sub.callback(QueryResult.unauthorized());
341
411
  }
342
412
 
343
- private sendSubscribe(queryId: string, request: SubscriptionRequest, attempt: number = 0): void {
413
+ private sendSubscribe(
414
+ queryId: string,
415
+ subscription: ActiveSubscription,
416
+ attempt: number = 0,
417
+ ): void {
344
418
  if (!this._connectionId || this._disconnected) return;
345
419
 
346
- // Capture the connection ID so retries can detect if a reconnect has already fired
347
- // for a different reason and made this retry obsolete.
420
+ // Capture the connection ID and subscription instance so retries can detect whether
421
+ // either has been replaced while this request is in flight.
348
422
  const connectionId = this._connectionId;
349
423
 
350
- const body = {
424
+ const body: {
425
+ connectionId: string;
426
+ queryId: string;
427
+ request: SubscriptionRequest;
428
+ revision?: number;
429
+ } = {
351
430
  connectionId,
352
431
  queryId,
353
- request,
432
+ request: subscription.request,
354
433
  };
434
+ if (this._supportsSubscriptionRevisions) {
435
+ body.revision = subscription.revision;
436
+ }
355
437
 
356
438
  const customHeaders = Globals.httpHeadersCallback?.() ?? {};
357
439
 
@@ -368,32 +450,137 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
368
450
  method: 'POST',
369
451
  headers: { 'Content-Type': 'application/json', ...customHeaders },
370
452
  body: JSON.stringify(body),
371
- }).then(response => {
372
- if (!response.ok) {
373
- if (attempt < maxRetries && this._connectionId === connectionId && !this._disconnected) {
374
- setTimeout(() => this.sendSubscribe(queryId, request, attempt + 1), retryDelayMs * (attempt + 1));
375
- } else if (this._connectionId === connectionId && !this._disconnected) {
376
- console.warn(`SSE hub: subscribe POST for '${queryId}' returned ${response.status} after ${attempt + 1} attempt(s), reconnecting`);
453
+ })
454
+ .then((response) => {
455
+ if (!response.ok) {
456
+ if (
457
+ !this.isCurrentSubscription(queryId, subscription, connectionId)
458
+ ) {
459
+ console.warn(
460
+ 'SSE hub: subscribe POST returned a failure for an obsolete connection or subscription; ignoring',
461
+ queryId,
462
+ response.status,
463
+ );
464
+ } else if (attempt < maxRetries) {
465
+ setTimeout(
466
+ () => {
467
+ if (
468
+ this.isCurrentSubscription(
469
+ queryId,
470
+ subscription,
471
+ connectionId,
472
+ )
473
+ ) {
474
+ this.sendSubscribe(
475
+ queryId,
476
+ subscription,
477
+ attempt + 1,
478
+ );
479
+ }
480
+ },
481
+ retryDelayMs * (attempt + 1),
482
+ );
483
+ } else {
484
+ console.warn(
485
+ 'SSE hub: subscribe POST failed after retries; reconnecting',
486
+ queryId,
487
+ response.status,
488
+ attempt + 1,
489
+ );
490
+ this.reconnect();
491
+ }
492
+ }
493
+ })
494
+ .catch((error) => {
495
+ if (!this.isCurrentSubscription(queryId, subscription, connectionId)) {
496
+ console.warn(
497
+ 'SSE hub: subscribe POST failed for an obsolete connection or subscription; ignoring',
498
+ queryId,
499
+ error,
500
+ );
501
+ } else if (attempt < maxRetries) {
502
+ setTimeout(
503
+ () => {
504
+ if (
505
+ this.isCurrentSubscription(
506
+ queryId,
507
+ subscription,
508
+ connectionId,
509
+ )
510
+ ) {
511
+ this.sendSubscribe(queryId, subscription, attempt + 1);
512
+ }
513
+ },
514
+ retryDelayMs * (attempt + 1),
515
+ );
516
+ } else {
517
+ console.error(
518
+ 'SSE hub: subscribe POST failed after retries; reconnecting',
519
+ queryId,
520
+ attempt + 1,
521
+ error,
522
+ );
377
523
  this.reconnect();
378
524
  }
379
- }
380
- }).catch(error => {
381
- if (attempt < maxRetries && this._connectionId === connectionId && !this._disconnected) {
382
- setTimeout(() => this.sendSubscribe(queryId, request, attempt + 1), retryDelayMs * (attempt + 1));
383
- } else if (this._connectionId === connectionId && !this._disconnected) {
384
- console.error(`SSE hub: subscribe POST failed for '${queryId}' after ${attempt + 1} attempt(s), reconnecting`, error);
385
- this.reconnect();
386
- }
387
- });
525
+ });
388
526
  }
389
527
 
390
- private sendUnsubscribe(queryId: string): void {
528
+ private isMessageForCurrentSubscription(message: HubMessage): boolean {
529
+ if (!message.queryId) return false;
530
+
531
+ const subscription = this._subscriptions.get(message.queryId);
532
+ if (!subscription) return false;
533
+
534
+ if (!this._supportsSubscriptionRevisions) {
535
+ return message.revision === undefined;
536
+ }
537
+
538
+ return (
539
+ this.isValidRevision(message.revision) &&
540
+ message.revision === subscription.revision
541
+ );
542
+ }
543
+
544
+ private isCurrentSubscription(
545
+ queryId: string,
546
+ subscription: ActiveSubscription,
547
+ connectionId: string,
548
+ ): boolean {
549
+ return (
550
+ !this._disconnected &&
551
+ this._connectionId === connectionId &&
552
+ this._subscriptions.get(queryId) === subscription
553
+ );
554
+ }
555
+
556
+ private getNextRevision(): number {
557
+ if (this._nextRevision >= Number.MAX_SAFE_INTEGER) {
558
+ throw new RangeError(
559
+ 'SSE hub subscription revision exhausted the safe integer range',
560
+ );
561
+ }
562
+
563
+ return ++this._nextRevision;
564
+ }
565
+
566
+ private isValidRevision(revision: number | undefined): revision is number {
567
+ return revision !== undefined && Number.isSafeInteger(revision) && revision > 0;
568
+ }
569
+
570
+ private sendUnsubscribe(queryId: string, revision: number): void {
391
571
  if (!this._connectionId) return;
392
572
 
393
- const body = {
573
+ const body: {
574
+ connectionId: string;
575
+ queryId: string;
576
+ revision?: number;
577
+ } = {
394
578
  connectionId: this._connectionId,
395
579
  queryId,
396
580
  };
581
+ if (this._supportsSubscriptionRevisions) {
582
+ body.revision = revision;
583
+ }
397
584
 
398
585
  const customHeaders = Globals.httpHeadersCallback?.() ?? {};
399
586
 
@@ -401,8 +588,8 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
401
588
  method: 'POST',
402
589
  headers: { 'Content-Type': 'application/json', ...customHeaders },
403
590
  body: JSON.stringify(body),
404
- }).catch(error => {
405
- console.error(`SSE hub: unsubscribe POST failed for '${queryId}'`, error);
591
+ }).catch((error) => {
592
+ console.error('SSE hub: unsubscribe POST failed', queryId, error);
406
593
  });
407
594
  }
408
595
  }