@cratis/arc 22.0.1 → 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 (205) hide show
  1. package/dist/cjs/queries/ObservableQueryFor.js +9 -4
  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 +6 -6
  16. package/dist/esm/queries/ObservableQueryFor.d.ts.map +1 -1
  17. package/dist/esm/queries/ObservableQueryFor.js +9 -4
  18. package/dist/esm/queries/ObservableQueryFor.js.map +1 -1
  19. package/dist/esm/queries/QueryInstanceCache.d.ts +1 -1
  20. package/dist/esm/queries/QueryInstanceCache.d.ts.map +1 -1
  21. package/dist/esm/queries/QueryInstanceCache.js +6 -6
  22. package/dist/esm/queries/QueryInstanceCache.js.map +1 -1
  23. package/dist/esm/queries/QueryResult.d.ts +2 -0
  24. package/dist/esm/queries/QueryResult.d.ts.map +1 -1
  25. package/dist/esm/queries/QueryResult.js +23 -4
  26. package/dist/esm/queries/QueryResult.js.map +1 -1
  27. package/dist/esm/queries/QueryResultWithState.d.ts +3 -3
  28. package/dist/esm/queries/QueryResultWithState.d.ts.map +1 -1
  29. package/dist/esm/queries/QueryResultWithState.js +20 -7
  30. package/dist/esm/queries/QueryResultWithState.js.map +1 -1
  31. package/dist/esm/queries/ServerSentEventHubConnection.d.ts +11 -4
  32. package/dist/esm/queries/ServerSentEventHubConnection.d.ts.map +1 -1
  33. package/dist/esm/queries/ServerSentEventHubConnection.js +120 -58
  34. package/dist/esm/queries/ServerSentEventHubConnection.js.map +1 -1
  35. package/dist/esm/queries/WebSocketHubConnection.d.ts +13 -2
  36. package/dist/esm/queries/WebSocketHubConnection.d.ts.map +1 -1
  37. package/dist/esm/queries/WebSocketHubConnection.js +121 -51
  38. package/dist/esm/queries/WebSocketHubConnection.js.map +1 -1
  39. package/dist/esm/queries/for_ObservableQueryFor/given/TestQueries.d.ts +17 -0
  40. package/dist/esm/queries/for_ObservableQueryFor/given/TestQueries.d.ts.map +1 -1
  41. package/dist/esm/queries/for_ObservableQueryFor/given/TestQueries.js +34 -0
  42. package/dist/esm/queries/for_ObservableQueryFor/given/TestQueries.js.map +1 -1
  43. package/dist/esm/queries/for_ObservableQueryFor/given/an_observable_query_for.d.ts +2 -1
  44. package/dist/esm/queries/for_ObservableQueryFor/given/an_observable_query_for.d.ts.map +1 -1
  45. package/dist/esm/queries/for_ObservableQueryFor/given/an_observable_query_for.js +3 -1
  46. package/dist/esm/queries/for_ObservableQueryFor/given/an_observable_query_for.js.map +1 -1
  47. package/dist/esm/queries/for_ObservableQueryFor/when_performing/with_a_not_ready_result.d.ts +2 -0
  48. package/dist/esm/queries/for_ObservableQueryFor/when_performing/with_a_not_ready_result.d.ts.map +1 -0
  49. package/dist/esm/queries/for_ObservableQueryFor/when_performing/with_a_not_ready_result.js +34 -0
  50. package/dist/esm/queries/for_ObservableQueryFor/when_performing/with_a_not_ready_result.js.map +1 -0
  51. package/dist/esm/queries/for_ObservableQueryFor/when_receiving_a_result/and_the_model_has_a_calendar_collection.d.ts +2 -0
  52. package/dist/esm/queries/for_ObservableQueryFor/when_receiving_a_result/and_the_model_has_a_calendar_collection.d.ts.map +1 -0
  53. package/dist/esm/queries/for_ObservableQueryFor/when_receiving_a_result/and_the_model_has_a_calendar_collection.js +52 -0
  54. package/dist/esm/queries/for_ObservableQueryFor/when_receiving_a_result/and_the_model_has_a_calendar_collection.js.map +1 -0
  55. package/dist/esm/queries/for_ObservableQueryFor/when_subscribing/with_direct_mode_and_sse_transport.js +26 -2
  56. package/dist/esm/queries/for_ObservableQueryFor/when_subscribing/with_direct_mode_and_sse_transport.js.map +1 -1
  57. package/dist/esm/queries/for_ObservableQueryFor/when_subscribing/with_direct_mode_disabled_and_sse_transport.js +60 -3
  58. package/dist/esm/queries/for_ObservableQueryFor/when_subscribing/with_direct_mode_disabled_and_sse_transport.js.map +1 -1
  59. package/dist/esm/queries/for_ObservableQueryFor/when_subscribing/with_web_socket_hub_payload_without_readiness.d.ts +2 -0
  60. package/dist/esm/queries/for_ObservableQueryFor/when_subscribing/with_web_socket_hub_payload_without_readiness.d.ts.map +1 -0
  61. package/dist/esm/queries/for_ObservableQueryFor/when_subscribing/with_web_socket_hub_payload_without_readiness.js +83 -0
  62. package/dist/esm/queries/for_ObservableQueryFor/when_subscribing/with_web_socket_hub_payload_without_readiness.js.map +1 -0
  63. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/with_only_readiness_changed.d.ts +2 -0
  64. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/with_only_readiness_changed.d.ts.map +1 -0
  65. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/with_only_readiness_changed.js +22 -0
  66. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/with_only_readiness_changed.js.map +1 -0
  67. package/dist/esm/queries/for_QueryResult/when_constructing_from_a_server_result/with_readiness_false.d.ts +2 -0
  68. package/dist/esm/queries/for_QueryResult/when_constructing_from_a_server_result/with_readiness_false.d.ts.map +1 -0
  69. package/dist/esm/queries/for_QueryResult/when_constructing_from_a_server_result/with_readiness_false.js +17 -0
  70. package/dist/esm/queries/for_QueryResult/when_constructing_from_a_server_result/with_readiness_false.js.map +1 -0
  71. package/dist/esm/queries/for_QueryResult/when_constructing_from_a_server_result/without_readiness.d.ts +2 -0
  72. package/dist/esm/queries/for_QueryResult/when_constructing_from_a_server_result/without_readiness.d.ts.map +1 -0
  73. package/dist/esm/queries/for_QueryResult/when_constructing_from_a_server_result/without_readiness.js +16 -0
  74. package/dist/esm/queries/for_QueryResult/when_constructing_from_a_server_result/without_readiness.js.map +1 -0
  75. package/dist/esm/queries/for_QueryResultWithState/when_creating_from_query_result/with_readiness_false.d.ts +2 -0
  76. package/dist/esm/queries/for_QueryResultWithState/when_creating_from_query_result/with_readiness_false.d.ts.map +1 -0
  77. package/dist/esm/queries/for_QueryResultWithState/when_creating_from_query_result/with_readiness_false.js +19 -0
  78. package/dist/esm/queries/for_QueryResultWithState/when_creating_from_query_result/with_readiness_false.js.map +1 -0
  79. package/dist/esm/queries/for_QueryResultWithState/when_creating_from_query_result/without_readiness.d.ts +2 -0
  80. package/dist/esm/queries/for_QueryResultWithState/when_creating_from_query_result/without_readiness.d.ts.map +1 -0
  81. package/dist/esm/queries/for_QueryResultWithState/when_creating_from_query_result/without_readiness.js +19 -0
  82. package/dist/esm/queries/for_QueryResultWithState/when_creating_from_query_result/without_readiness.js.map +1 -0
  83. package/dist/esm/queries/for_ServerSentEventHubConnection/given/a_deferred_promise.d.ts +7 -0
  84. package/dist/esm/queries/for_ServerSentEventHubConnection/given/a_deferred_promise.d.ts.map +1 -0
  85. package/dist/esm/queries/for_ServerSentEventHubConnection/given/a_deferred_promise.js +12 -0
  86. package/dist/esm/queries/for_ServerSentEventHubConnection/given/a_deferred_promise.js.map +1 -0
  87. package/dist/esm/queries/for_ServerSentEventHubConnection/given/a_server_sent_event_hub_connection.d.ts +2 -1
  88. package/dist/esm/queries/for_ServerSentEventHubConnection/given/a_server_sent_event_hub_connection.d.ts.map +1 -1
  89. package/dist/esm/queries/for_ServerSentEventHubConnection/given/a_server_sent_event_hub_connection.js +15 -4
  90. package/dist/esm/queries/for_ServerSentEventHubConnection/given/a_server_sent_event_hub_connection.js.map +1 -1
  91. package/dist/esm/queries/for_ServerSentEventHubConnection/when_a_retired_event_source_fires/ignores_connected_and_error_callbacks.d.ts +2 -0
  92. package/dist/esm/queries/for_ServerSentEventHubConnection/when_a_retired_event_source_fires/ignores_connected_and_error_callbacks.d.ts.map +1 -0
  93. package/dist/esm/queries/for_ServerSentEventHubConnection/when_a_retired_event_source_fires/ignores_connected_and_error_callbacks.js +44 -0
  94. package/dist/esm/queries/for_ServerSentEventHubConnection/when_a_retired_event_source_fires/ignores_connected_and_error_callbacks.js.map +1 -0
  95. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_query_is_replaced.d.ts +2 -0
  96. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_query_is_replaced.d.ts.map +1 -0
  97. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_query_is_replaced.js +74 -0
  98. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_query_is_replaced.js.map +1 -0
  99. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_query_is_unsubscribed.d.ts +2 -0
  100. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_query_is_unsubscribed.d.ts.map +1 -0
  101. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_query_is_unsubscribed.js +74 -0
  102. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_query_is_unsubscribed.js.map +1 -0
  103. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_stale_request_is_rejected.d.ts +2 -0
  104. 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
  105. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_stale_request_is_rejected.js +59 -0
  106. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_stale_request_is_rejected.js.map +1 -0
  107. 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
  108. 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
  109. package/dist/esm/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_stale_request_returns_non_ok.js +58 -0
  110. 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
  111. package/dist/esm/queries/for_ServerSentEventHubConnection/when_disposing/cleans_up_resources.js +6 -3
  112. package/dist/esm/queries/for_ServerSentEventHubConnection/when_disposing/cleans_up_resources.js.map +1 -1
  113. package/dist/esm/queries/for_ServerSentEventHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.js +23 -6
  114. package/dist/esm/queries/for_ServerSentEventHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.js.map +1 -1
  115. package/dist/esm/queries/for_ServerSentEventHubConnection/when_reconnecting/after_error.js +20 -5
  116. package/dist/esm/queries/for_ServerSentEventHubConnection/when_reconnecting/after_error.js.map +1 -1
  117. package/dist/esm/queries/for_ServerSentEventHubConnection/when_reconnecting/and_an_immediate_open_wins_the_backoff_race.d.ts +2 -0
  118. package/dist/esm/queries/for_ServerSentEventHubConnection/when_reconnecting/and_an_immediate_open_wins_the_backoff_race.d.ts.map +1 -0
  119. package/dist/esm/queries/for_ServerSentEventHubConnection/when_reconnecting/and_an_immediate_open_wins_the_backoff_race.js +20 -0
  120. package/dist/esm/queries/for_ServerSentEventHubConnection/when_reconnecting/and_an_immediate_open_wins_the_backoff_race.js.map +1 -0
  121. package/dist/esm/queries/for_ServerSentEventHubConnection/when_replacing_a_subscription/ignores_messages_from_the_retired_generation.d.ts +2 -0
  122. package/dist/esm/queries/for_ServerSentEventHubConnection/when_replacing_a_subscription/ignores_messages_from_the_retired_generation.d.ts.map +1 -0
  123. package/dist/esm/queries/for_ServerSentEventHubConnection/when_replacing_a_subscription/ignores_messages_from_the_retired_generation.js +68 -0
  124. package/dist/esm/queries/for_ServerSentEventHubConnection/when_replacing_a_subscription/ignores_messages_from_the_retired_generation.js.map +1 -0
  125. package/dist/esm/queries/for_ServerSentEventHubConnection/when_subscribing/sends_subscribe_post.js +38 -13
  126. package/dist/esm/queries/for_ServerSentEventHubConnection/when_subscribing/sends_subscribe_post.js.map +1 -1
  127. package/dist/esm/queries/for_ServerSentEventHubConnection/when_subscription_revisions_are_not_advertised/uses_legacy_protocol.d.ts +2 -0
  128. package/dist/esm/queries/for_ServerSentEventHubConnection/when_subscription_revisions_are_not_advertised/uses_legacy_protocol.d.ts.map +1 -0
  129. package/dist/esm/queries/for_ServerSentEventHubConnection/when_subscription_revisions_are_not_advertised/uses_legacy_protocol.js +54 -0
  130. package/dist/esm/queries/for_ServerSentEventHubConnection/when_subscription_revisions_are_not_advertised/uses_legacy_protocol.js.map +1 -0
  131. package/dist/esm/queries/for_ServerSentEventHubConnection/when_unsubscribing/the_only_query.js +18 -4
  132. package/dist/esm/queries/for_ServerSentEventHubConnection/when_unsubscribing/the_only_query.js.map +1 -1
  133. package/dist/esm/queries/for_WebSocketHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.js +22 -5
  134. package/dist/esm/queries/for_WebSocketHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.js.map +1 -1
  135. package/dist/esm/queries/for_WebSocketHubConnection/when_reconnecting/ignores_a_pending_reconnect_and_stale_socket_callbacks.d.ts +2 -0
  136. package/dist/esm/queries/for_WebSocketHubConnection/when_reconnecting/ignores_a_pending_reconnect_and_stale_socket_callbacks.d.ts.map +1 -0
  137. package/dist/esm/queries/for_WebSocketHubConnection/when_reconnecting/ignores_a_pending_reconnect_and_stale_socket_callbacks.js +108 -0
  138. package/dist/esm/queries/for_WebSocketHubConnection/when_reconnecting/ignores_a_pending_reconnect_and_stale_socket_callbacks.js.map +1 -0
  139. package/dist/esm/queries/for_WebSocketHubConnection/when_reconnecting/replaces_a_closing_socket_safely.d.ts +2 -0
  140. package/dist/esm/queries/for_WebSocketHubConnection/when_reconnecting/replaces_a_closing_socket_safely.d.ts.map +1 -0
  141. package/dist/esm/queries/for_WebSocketHubConnection/when_reconnecting/replaces_a_closing_socket_safely.js +33 -0
  142. package/dist/esm/queries/for_WebSocketHubConnection/when_reconnecting/replaces_a_closing_socket_safely.js.map +1 -0
  143. package/dist/esm/queries/for_WebSocketHubConnection/when_reconnecting/resubscribes_all_queries.js +26 -4
  144. package/dist/esm/queries/for_WebSocketHubConnection/when_reconnecting/resubscribes_all_queries.js.map +1 -1
  145. package/dist/esm/queries/for_WebSocketHubConnection/when_replacing_a_subscription/ignores_stale_result_unauthorized_and_error.d.ts +2 -0
  146. package/dist/esm/queries/for_WebSocketHubConnection/when_replacing_a_subscription/ignores_stale_result_unauthorized_and_error.d.ts.map +1 -0
  147. package/dist/esm/queries/for_WebSocketHubConnection/when_replacing_a_subscription/ignores_stale_result_unauthorized_and_error.js +62 -0
  148. package/dist/esm/queries/for_WebSocketHubConnection/when_replacing_a_subscription/ignores_stale_result_unauthorized_and_error.js.map +1 -0
  149. package/dist/esm/queries/for_WebSocketHubConnection/when_subscribing/to_a_query.js +18 -5
  150. package/dist/esm/queries/for_WebSocketHubConnection/when_subscribing/to_a_query.js.map +1 -1
  151. package/dist/esm/queries/for_WebSocketHubConnection/when_subscription_revisions_are_supported/upgrades_active_subscriptions_to_strict_mode.d.ts +2 -0
  152. package/dist/esm/queries/for_WebSocketHubConnection/when_subscription_revisions_are_supported/upgrades_active_subscriptions_to_strict_mode.d.ts.map +1 -0
  153. package/dist/esm/queries/for_WebSocketHubConnection/when_subscription_revisions_are_supported/upgrades_active_subscriptions_to_strict_mode.js +56 -0
  154. package/dist/esm/queries/for_WebSocketHubConnection/when_subscription_revisions_are_supported/upgrades_active_subscriptions_to_strict_mode.js.map +1 -0
  155. package/dist/esm/queries/for_WebSocketHubConnection/when_unsubscribing/the_only_query.js +16 -3
  156. package/dist/esm/queries/for_WebSocketHubConnection/when_unsubscribing/the_only_query.js.map +1 -1
  157. package/dist/esm/queries/for_WebSocketHubConnection/when_unsubscribing/with_revision_support_not_advertised.d.ts +2 -0
  158. package/dist/esm/queries/for_WebSocketHubConnection/when_unsubscribing/with_revision_support_not_advertised.d.ts.map +1 -0
  159. package/dist/esm/queries/for_WebSocketHubConnection/when_unsubscribing/with_revision_support_not_advertised.js +33 -0
  160. package/dist/esm/queries/for_WebSocketHubConnection/when_unsubscribing/with_revision_support_not_advertised.js.map +1 -0
  161. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  162. package/package.json +1 -1
  163. package/queries/IQueryResult.ts +10 -0
  164. package/queries/ObservableQueryFor.ts +54 -26
  165. package/queries/QueryInstanceCache.ts +42 -19
  166. package/queries/QueryResult.ts +145 -79
  167. package/queries/QueryResultWithState.ts +32 -9
  168. package/queries/ServerSentEventHubConnection.ts +270 -83
  169. package/queries/WebSocketHubConnection.ts +188 -56
  170. package/queries/for_ObservableQueryFor/given/TestQueries.ts +30 -1
  171. package/queries/for_ObservableQueryFor/given/an_observable_query_for.ts +3 -0
  172. package/queries/for_ObservableQueryFor/when_performing/with_a_not_ready_result.ts +46 -0
  173. package/queries/for_ObservableQueryFor/when_receiving_a_result/and_the_model_has_a_calendar_collection.ts +76 -0
  174. package/queries/for_ObservableQueryFor/when_subscribing/with_direct_mode_and_sse_transport.ts +91 -57
  175. package/queries/for_ObservableQueryFor/when_subscribing/with_direct_mode_disabled_and_sse_transport.ts +125 -53
  176. package/queries/for_ObservableQueryFor/when_subscribing/with_web_socket_hub_payload_without_readiness.ts +107 -0
  177. package/queries/for_QueryInstanceCache/when_setting_last_result/with_only_readiness_changed.ts +63 -0
  178. package/queries/for_QueryResult/when_constructing_from_a_server_result/with_readiness_false.ts +25 -0
  179. package/queries/for_QueryResult/when_constructing_from_a_server_result/without_readiness.ts +24 -0
  180. package/queries/for_QueryResultWithState/when_creating_from_query_result/with_readiness_false.ts +28 -0
  181. package/queries/for_QueryResultWithState/when_creating_from_query_result/without_readiness.ts +25 -0
  182. package/queries/for_ServerSentEventHubConnection/given/a_deferred_promise.ts +15 -0
  183. package/queries/for_ServerSentEventHubConnection/given/a_server_sent_event_hub_connection.ts +18 -7
  184. package/queries/for_ServerSentEventHubConnection/when_a_retired_event_source_fires/ignores_connected_and_error_callbacks.ts +59 -0
  185. package/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_query_is_replaced.ts +113 -0
  186. package/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_query_is_unsubscribed.ts +109 -0
  187. package/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_stale_request_is_rejected.ts +81 -0
  188. package/queries/for_ServerSentEventHubConnection/when_connection_changes_while_subscribe_is_in_flight/and_the_stale_request_returns_non_ok.ts +83 -0
  189. package/queries/for_ServerSentEventHubConnection/when_disposing/cleans_up_resources.ts +30 -21
  190. package/queries/for_ServerSentEventHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.ts +56 -32
  191. package/queries/for_ServerSentEventHubConnection/when_reconnecting/after_error.ts +23 -6
  192. package/queries/for_ServerSentEventHubConnection/when_reconnecting/and_an_immediate_open_wins_the_backoff_race.ts +41 -0
  193. package/queries/for_ServerSentEventHubConnection/when_replacing_a_subscription/ignores_messages_from_the_retired_generation.ts +92 -0
  194. package/queries/for_ServerSentEventHubConnection/when_subscribing/sends_subscribe_post.ts +69 -30
  195. package/queries/for_ServerSentEventHubConnection/when_subscription_revisions_are_not_advertised/uses_legacy_protocol.ts +69 -0
  196. package/queries/for_ServerSentEventHubConnection/when_unsubscribing/the_only_query.ts +43 -24
  197. package/queries/for_WebSocketHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.ts +53 -29
  198. package/queries/for_WebSocketHubConnection/when_reconnecting/ignores_a_pending_reconnect_and_stale_socket_callbacks.ts +131 -0
  199. package/queries/for_WebSocketHubConnection/when_reconnecting/replaces_a_closing_socket_safely.ts +39 -0
  200. package/queries/for_WebSocketHubConnection/when_reconnecting/resubscribes_all_queries.ts +72 -36
  201. package/queries/for_WebSocketHubConnection/when_replacing_a_subscription/ignores_stale_result_unauthorized_and_error.ts +88 -0
  202. package/queries/for_WebSocketHubConnection/when_subscribing/to_a_query.ts +54 -27
  203. package/queries/for_WebSocketHubConnection/when_subscription_revisions_are_supported/upgrades_active_subscriptions_to_strict_mode.ts +72 -0
  204. package/queries/for_WebSocketHubConnection/when_unsubscribing/the_only_query.ts +43 -18
  205. package/queries/for_WebSocketHubConnection/when_unsubscribing/with_revision_support_not_advertised.ts +44 -0
@@ -2,9 +2,9 @@
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 { DataReceived } from './ObservableQueryConnection';
5
+ import type { DataReceived } from './ObservableQueryConnection';
6
6
  import { HubConnectionKeepAlive } from './HubConnectionKeepAlive';
7
- import { IReconnectPolicy } from './IReconnectPolicy';
7
+ import type { IReconnectPolicy } from './IReconnectPolicy';
8
8
  import { ReconnectPolicy } from './ReconnectPolicy';
9
9
  import { QueryResult } from './QueryResult';
10
10
 
@@ -26,11 +26,20 @@ export enum HubMessageType {
26
26
  }
27
27
 
28
28
  /**
29
- * Wire format for messages exchanged over the {@link WebSocketHubConnection}.
29
+ * Wire format for messages exchanged over the observable query WebSocket and SSE hub transports.
30
30
  */
31
31
  export interface HubMessage {
32
32
  type: HubMessageType;
33
33
  queryId?: string;
34
+
35
+ /** Optional positive safe-integer revision used to reject stale replacement messages. */
36
+ revision?: number;
37
+
38
+ /**
39
+ * Whether the server supports revision-aware subscription operations and result frames.
40
+ * Only present on {@link HubMessageType.Connected} messages from capable servers.
41
+ */
42
+ supportsSubscriptionRevisions?: boolean;
34
43
  payload?: any;
35
44
  timestamp?: number;
36
45
 
@@ -57,6 +66,7 @@ export interface SubscriptionRequest {
57
66
  interface ActiveSubscription {
58
67
  request: SubscriptionRequest;
59
68
  callback: DataReceived<any>;
69
+ revision: number;
60
70
  }
61
71
 
62
72
  /**
@@ -65,7 +75,9 @@ interface ActiveSubscription {
65
75
  *
66
76
  * Multiple query subscriptions are carried over the same physical WebSocket. Each subscription
67
77
  * is identified by a client-generated {@code queryId}; the server tags every result message with
68
- * the same id so responses can be routed to the correct callback.
78
+ * the same id so responses can be routed to the correct callback. The client initially subscribes
79
+ * without revisions for compatibility with older servers, then replaces active subscriptions with
80
+ * revision-aware operations when the server advertises support in a Connected message.
69
81
  */
70
82
  export class WebSocketHubConnection {
71
83
  private _socket?: WebSocket;
@@ -75,24 +87,30 @@ export class WebSocketHubConnection {
75
87
  private _lastPingSentTime?: number;
76
88
  private _lastPongLatency: number = 0;
77
89
  private _latencySamples: number[] = [];
90
+ private _nextRevision = 0;
91
+ private _supportsSubscriptionRevisions = false;
92
+ private _reconnectPending = false;
78
93
 
79
94
  /**
80
95
  * Initializes a new instance of {@link WebSocketHubConnection}.
81
- * @param {string} url The WebSocket URL of the hub endpoint (e.g. {@code ws://localhost:5000/.cratis/queries/ws}).
82
- * @param {string} microservice The microservice name to pass as a query argument.
96
+ * @param {string} _url The WebSocket URL of the hub endpoint (e.g. {@code ws://localhost:5000/.cratis/queries/ws}).
97
+ * @param {string} _microservice The microservice name to pass as a query argument.
83
98
  * @param {number} pingIntervalMs How often to send keep-alive pings when the connection is idle (default: 10 000 ms).
84
- * @param {IReconnectPolicy} reconnectPolicy The reconnect policy to use (default: {@link ReconnectPolicy}).
99
+ * @param {IReconnectPolicy} _policy The reconnect policy to use (default: {@link ReconnectPolicy}).
85
100
  */
86
101
  constructor(
87
102
  private readonly _url: string,
88
103
  private readonly _microservice: string,
89
104
  pingIntervalMs: number = 10000,
90
- private readonly _policy: IReconnectPolicy = new ReconnectPolicy()
105
+ private readonly _policy: IReconnectPolicy = new ReconnectPolicy(),
91
106
  ) {
92
107
  this._keepAlive = new HubConnectionKeepAlive(pingIntervalMs, () => {
93
108
  if (this._socket?.readyState === WebSocket.OPEN) {
94
109
  this._lastPingSentTime = Date.now();
95
- this.sendMessage({ type: HubMessageType.Ping, timestamp: this._lastPingSentTime });
110
+ this.sendMessage({
111
+ type: HubMessageType.Ping,
112
+ timestamp: this._lastPingSentTime,
113
+ });
96
114
  }
97
115
  });
98
116
  }
@@ -123,7 +141,9 @@ export class WebSocketHubConnection {
123
141
  */
124
142
  get averageLatency(): number {
125
143
  if (this._latencySamples.length === 0) return 0;
126
- return this._latencySamples.reduce((a, b) => a + b, 0) / this._latencySamples.length;
144
+ return (
145
+ this._latencySamples.reduce((a, b) => a + b, 0) / this._latencySamples.length
146
+ );
127
147
  }
128
148
 
129
149
  /**
@@ -133,12 +153,17 @@ export class WebSocketHubConnection {
133
153
  * @param {SubscriptionRequest} request The subscription request payload.
134
154
  * @param {DataReceived<any>} callback Callback invoked whenever the server pushes a result for this query.
135
155
  */
136
- subscribe(queryId: string, request: SubscriptionRequest, callback: DataReceived<any>): void {
137
- this._subscriptions.set(queryId, { request, callback });
156
+ subscribe(
157
+ queryId: string,
158
+ request: SubscriptionRequest,
159
+ callback: DataReceived<any>,
160
+ ): void {
161
+ const subscription = { request, callback, revision: this.getNextRevision() };
162
+ this._subscriptions.set(queryId, subscription);
138
163
  this.ensureConnected();
139
164
 
140
165
  if (this._socket?.readyState === WebSocket.OPEN) {
141
- this.sendSubscribeMessage(queryId, request);
166
+ this.sendSubscribeMessage(queryId, subscription);
142
167
  }
143
168
  // If not yet open, sendAllSubscriptions will fire in onopen.
144
169
  }
@@ -148,10 +173,18 @@ export class WebSocketHubConnection {
148
173
  * @param {string} queryId The identifier of the subscription to cancel.
149
174
  */
150
175
  unsubscribe(queryId: string): void {
176
+ const subscription = this._subscriptions.get(queryId);
151
177
  this._subscriptions.delete(queryId);
152
178
 
153
- if (this._socket?.readyState === WebSocket.OPEN) {
154
- this.sendMessage({ type: HubMessageType.Unsubscribe, queryId });
179
+ if (this._socket?.readyState === WebSocket.OPEN && subscription) {
180
+ const message: HubMessage = {
181
+ type: HubMessageType.Unsubscribe,
182
+ queryId,
183
+ };
184
+ if (this._supportsSubscriptionRevisions) {
185
+ message.revision = subscription.revision;
186
+ }
187
+ this.sendMessage(message);
155
188
  }
156
189
 
157
190
  // If no subscriptions remain, close the connection to free resources.
@@ -164,12 +197,8 @@ export class WebSocketHubConnection {
164
197
  * Permanently close this hub connection and clean up all subscriptions.
165
198
  */
166
199
  dispose(): void {
167
- this._disconnected = true;
168
200
  this._subscriptions.clear();
169
- this._keepAlive.stop();
170
- this._policy.cancel();
171
- this._socket?.close();
172
- this._socket = undefined;
201
+ this.close();
173
202
  }
174
203
 
175
204
  private ensureConnected(): void {
@@ -178,10 +207,18 @@ export class WebSocketHubConnection {
178
207
  this._disconnected = false;
179
208
  }
180
209
 
181
- if (this._socket && (this._socket.readyState === WebSocket.OPEN || this._socket.readyState === WebSocket.CONNECTING)) {
210
+ if (
211
+ this._socket &&
212
+ (this._socket.readyState === WebSocket.OPEN ||
213
+ this._socket.readyState === WebSocket.CONNECTING)
214
+ ) {
215
+ this.cancelPendingReconnect();
182
216
  return;
183
217
  }
184
218
 
219
+ // A subscription can arrive while an earlier close is waiting in reconnect backoff. The immediate open owns
220
+ // the connection now, so the delayed callback must not be allowed to create a competing socket later.
221
+ this.cancelPendingReconnect();
185
222
  this.openSocket();
186
223
  }
187
224
 
@@ -189,75 +226,116 @@ export class WebSocketHubConnection {
189
226
  this._disconnected = true;
190
227
  this._keepAlive.stop();
191
228
  this._policy.cancel();
192
- if (this._socket) {
193
- // Detach all handlers BEFORE closing so that the async onclose event cannot
194
- // fire after a new subscription has reset _disconnected to false and opened a
195
- // fresh socket. Without this, the stale onclose triggers an unintended
196
- // reconnect via the back-off policy, causing a 1-10 second delay before the
197
- // new page's queries receive their first data.
198
- this._socket.onopen = null;
199
- this._socket.onclose = null;
200
- this._socket.onerror = null;
201
- this._socket.onmessage = null;
202
- this._socket.close();
203
- }
229
+ this._reconnectPending = false;
230
+ const socket = this._socket;
204
231
  this._socket = undefined;
232
+ this._supportsSubscriptionRevisions = false;
233
+ if (socket) {
234
+ this.detachAndCloseSocket(socket);
235
+ }
205
236
  }
206
237
 
207
238
  private openSocket(): void {
239
+ const currentSocket = this._socket;
240
+ if (
241
+ currentSocket &&
242
+ (currentSocket.readyState === WebSocket.OPEN ||
243
+ currentSocket.readyState === WebSocket.CONNECTING)
244
+ ) {
245
+ return;
246
+ }
247
+
248
+ if (currentSocket) {
249
+ this._socket = undefined;
250
+ this.detachAndCloseSocket(currentSocket);
251
+ }
252
+
253
+ this._supportsSubscriptionRevisions = false;
254
+
208
255
  let url = this._url;
209
256
  if (this._microservice?.length > 0) {
210
257
  const param = `${Globals.microserviceWSQueryArgument}=${encodeURIComponent(this._microservice)}`;
211
258
  url += (url.includes('?') ? '&' : '?') + param;
212
259
  }
213
260
 
214
- this._socket = new WebSocket(url);
261
+ const socket = new WebSocket(url);
262
+ this._socket = socket;
215
263
 
216
- this._socket.onopen = () => {
217
- if (this._disconnected) return;
218
- console.log(`Hub connection established: '${url}'`);
264
+ socket.onopen = () => {
265
+ if (this._socket !== socket || this._disconnected) return;
266
+ console.log('Hub connection established', url);
267
+ this.cancelPendingReconnect();
219
268
  this._policy.reset();
220
269
  this._keepAlive.start();
221
270
  this.sendAllSubscriptions();
222
271
  };
223
272
 
224
- this._socket.onclose = () => {
225
- if (this._disconnected) return;
226
- console.log(`Hub connection closed: '${url}'`);
273
+ socket.onclose = () => {
274
+ if (this._socket !== socket || this._disconnected) return;
275
+ console.log('Hub connection closed', url);
276
+ this._socket = undefined;
277
+ this._supportsSubscriptionRevisions = false;
227
278
  this._keepAlive.stop();
228
279
  if (this._subscriptions.size === 0) return;
229
- this._policy.schedule(() => {
280
+ this._reconnectPending = true;
281
+ const scheduled = this._policy.schedule(() => {
282
+ this._reconnectPending = false;
230
283
  if (!this._disconnected && this._subscriptions.size > 0) {
231
- this.openSocket();
284
+ this.ensureConnected();
232
285
  }
233
286
  }, this._url);
287
+ if (!scheduled) {
288
+ this._reconnectPending = false;
289
+ }
234
290
  };
235
291
 
236
- this._socket.onerror = (error) => {
237
- if (this._disconnected) return;
238
- console.error(`Hub connection error: '${url}'`, error);
292
+ socket.onerror = (error) => {
293
+ if (this._socket !== socket || this._disconnected) return;
294
+ console.error('Hub connection error', url, error);
239
295
  this._keepAlive.stop();
240
296
  // onclose will fire after onerror, triggering reconnect
241
297
  };
242
298
 
243
- this._socket.onmessage = (ev) => {
244
- if (this._disconnected) return;
299
+ socket.onmessage = (ev) => {
300
+ if (this._socket !== socket || this._disconnected) return;
245
301
  this.handleMessage(ev.data as string);
246
302
  };
247
303
  }
248
304
 
305
+ private cancelPendingReconnect(): void {
306
+ if (!this._reconnectPending) return;
307
+
308
+ this._policy.cancel();
309
+ this._reconnectPending = false;
310
+ }
311
+
312
+ private detachAndCloseSocket(socket: WebSocket): void {
313
+ socket.onopen = null;
314
+ socket.onclose = null;
315
+ socket.onerror = null;
316
+ socket.onmessage = null;
317
+ socket.close();
318
+ }
319
+
249
320
  private sendAllSubscriptions(): void {
250
321
  for (const [queryId, sub] of this._subscriptions) {
251
- this.sendSubscribeMessage(queryId, sub.request);
322
+ this.sendSubscribeMessage(queryId, sub);
252
323
  }
253
324
  }
254
325
 
255
- private sendSubscribeMessage(queryId: string, request: SubscriptionRequest): void {
256
- this.sendMessage({
326
+ private sendSubscribeMessage(
327
+ queryId: string,
328
+ subscription: ActiveSubscription,
329
+ ): void {
330
+ const message: HubMessage = {
257
331
  type: HubMessageType.Subscribe,
258
332
  queryId,
259
- payload: request,
260
- });
333
+ payload: subscription.request,
334
+ };
335
+ if (this._supportsSubscriptionRevisions) {
336
+ message.revision = subscription.revision;
337
+ }
338
+ this.sendMessage(message);
261
339
  }
262
340
 
263
341
  private sendMessage(message: HubMessage): void {
@@ -274,6 +352,9 @@ export class WebSocketHubConnection {
274
352
  this._keepAlive.recordActivity();
275
353
 
276
354
  switch (message.type) {
355
+ case HubMessageType.Connected:
356
+ this.handleConnected(message);
357
+ break;
277
358
  case HubMessageType.QueryResult:
278
359
  this.handleQueryResult(message);
279
360
  break;
@@ -281,11 +362,17 @@ export class WebSocketHubConnection {
281
362
  this.handlePong(message);
282
363
  break;
283
364
  case HubMessageType.Unauthorized:
284
- console.warn(`Hub: query '${message.queryId}' unauthorized`);
365
+ console.warn('Hub: query unauthorized', message.queryId);
285
366
  this.handleUnauthorized(message);
286
367
  break;
287
368
  case HubMessageType.Error:
288
- console.error(`Hub: query '${message.queryId}' error:`, message.payload);
369
+ if (this.isMessageForCurrentSubscription(message)) {
370
+ console.error(
371
+ 'Hub: query error',
372
+ message.queryId,
373
+ message.payload,
374
+ );
375
+ }
289
376
  break;
290
377
  }
291
378
  } catch (error) {
@@ -293,11 +380,26 @@ export class WebSocketHubConnection {
293
380
  }
294
381
  }
295
382
 
383
+ private handleConnected(message: HubMessage): void {
384
+ if (
385
+ message.supportsSubscriptionRevisions !== true ||
386
+ this._supportsSubscriptionRevisions
387
+ ) {
388
+ return;
389
+ }
390
+
391
+ this._supportsSubscriptionRevisions = true;
392
+
393
+ // The legacy subscriptions sent on socket open keep this client compatible with older servers.
394
+ // Ordered WebSocket delivery means these revision-aware replacements are processed afterwards.
395
+ this.sendAllSubscriptions();
396
+ }
397
+
296
398
  private handleQueryResult(message: HubMessage): void {
297
399
  if (!message.queryId) return;
298
400
 
299
401
  const sub = this._subscriptions.get(message.queryId);
300
- if (!sub) return;
402
+ if (!sub || !this.isMessageForCurrentSubscription(message)) return;
301
403
 
302
404
  const result = message.payload as QueryResult<any>;
303
405
  sub.callback(result);
@@ -307,12 +409,42 @@ export class WebSocketHubConnection {
307
409
  if (!message.queryId) return;
308
410
 
309
411
  const sub = this._subscriptions.get(message.queryId);
310
- if (!sub) return;
412
+ if (!sub || !this.isMessageForCurrentSubscription(message)) return;
311
413
 
312
414
  this._subscriptions.delete(message.queryId);
313
415
  sub.callback(QueryResult.unauthorized());
314
416
  }
315
417
 
418
+ private isMessageForCurrentSubscription(message: HubMessage): boolean {
419
+ if (!message.queryId) return false;
420
+
421
+ if (!this._supportsSubscriptionRevisions) {
422
+ return (
423
+ message.revision === undefined &&
424
+ this._subscriptions.has(message.queryId)
425
+ );
426
+ }
427
+
428
+ return (
429
+ this.isValidRevision(message.revision) &&
430
+ this._subscriptions.get(message.queryId)?.revision === message.revision
431
+ );
432
+ }
433
+
434
+ private isValidRevision(revision: number | undefined): revision is number {
435
+ return revision !== undefined && Number.isSafeInteger(revision) && revision > 0;
436
+ }
437
+
438
+ private getNextRevision(): number {
439
+ if (this._nextRevision >= Number.MAX_SAFE_INTEGER) {
440
+ throw new RangeError(
441
+ 'WebSocket hub subscription revision exhausted the safe integer range',
442
+ );
443
+ }
444
+
445
+ return ++this._nextRevision;
446
+ }
447
+
316
448
  private handlePong(message: HubMessage): void {
317
449
  if (message.timestamp && this._lastPingSentTime) {
318
450
  const latency = Date.now() - message.timestamp;
@@ -2,7 +2,7 @@
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
4
  import { ObservableQueryFor } from '../../ObservableQueryFor';
5
- import { Constructor } from '@cratis/fundamentals';
5
+ import { Constructor, DateOnly, field, TimeOnly } from '@cratis/fundamentals';
6
6
  import { ParameterDescriptor } from '../../../reflection/ParameterDescriptor';
7
7
 
8
8
  export class TestObservableQuery extends ObservableQueryFor<string, { id: string }> {
@@ -37,6 +37,35 @@ export class TestEnumerableQuery extends ObservableQueryFor<string[], { category
37
37
  }
38
38
  }
39
39
 
40
+ export class TestCalendarChild {
41
+ @field(DateOnly)
42
+ date!: DateOnly;
43
+
44
+ @field(TimeOnly)
45
+ time!: TimeOnly;
46
+ }
47
+
48
+ export class TestCalendarItem {
49
+ @field(TestCalendarChild, true)
50
+ children!: TestCalendarChild[];
51
+ }
52
+
53
+ export class TestCalendarEnumerableQuery extends ObservableQueryFor<TestCalendarItem[], { category: string }> {
54
+ readonly route = '/api/calendar-items/{category}';
55
+ readonly defaultValue: TestCalendarItem[] = [];
56
+ readonly parameterDescriptors: ParameterDescriptor[] = [
57
+ new ParameterDescriptor('category', String as Constructor)
58
+ ];
59
+
60
+ get requiredRequestParameters(): string[] {
61
+ return ['category'];
62
+ }
63
+
64
+ constructor() {
65
+ super(TestCalendarItem, true);
66
+ }
67
+ }
68
+
40
69
  export class TestObservableQueryWithParameterDescriptorValues extends ObservableQueryFor<string, object> {
41
70
  readonly route = '/api/search';
42
71
  readonly defaultValue = '';
@@ -4,6 +4,7 @@
4
4
  import {
5
5
  TestObservableQuery,
6
6
  TestEnumerableQuery,
7
+ TestCalendarEnumerableQuery,
7
8
  TestObservableQueryWithParameterDescriptorValues,
8
9
  TestObservableQueryWithRouteAndQueryArgs,
9
10
  TestObservableQueryWithMultipleRequiredParameters
@@ -12,6 +13,7 @@ import {
12
13
  export class an_observable_query_for {
13
14
  query: TestObservableQuery;
14
15
  enumerableQuery: TestEnumerableQuery;
16
+ calendarEnumerableQuery: TestCalendarEnumerableQuery;
15
17
  queryWithParameterDescriptorValues: TestObservableQueryWithParameterDescriptorValues;
16
18
  queryWithRouteAndQueryArgs: TestObservableQueryWithRouteAndQueryArgs;
17
19
  queryWithMultipleRequiredParameters: TestObservableQueryWithMultipleRequiredParameters;
@@ -19,6 +21,7 @@ export class an_observable_query_for {
19
21
  constructor() {
20
22
  this.query = new TestObservableQuery();
21
23
  this.enumerableQuery = new TestEnumerableQuery();
24
+ this.calendarEnumerableQuery = new TestCalendarEnumerableQuery();
22
25
  this.queryWithParameterDescriptorValues = new TestObservableQueryWithParameterDescriptorValues();
23
26
  this.queryWithRouteAndQueryArgs = new TestObservableQueryWithRouteAndQueryArgs();
24
27
  this.queryWithMultipleRequiredParameters = new TestObservableQueryWithMultipleRequiredParameters();
@@ -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 * as sinon from 'sinon';
5
+ import { given } from '../../../given';
6
+ import { createFetchHelper } from '../../../helpers/fetchHelper';
7
+ import type { QueryResult } from '../../QueryResult';
8
+ import { an_observable_query_for } from '../given/an_observable_query_for';
9
+
10
+ describe(
11
+ 'when performing with a not ready result',
12
+ given(an_observable_query_for, (context) => {
13
+ let result: QueryResult<string>;
14
+ let fetchHelper: { stubFetch: () => sinon.SinonStub; restore: () => void };
15
+
16
+ beforeEach(async () => {
17
+ fetchHelper = createFetchHelper();
18
+ const fetchStub = fetchHelper.stubFetch();
19
+ // SAFETY: The query only consumes this response's JSON payload, status, and success flag.
20
+ fetchStub.resolves({
21
+ json: sinon.stub().resolves({
22
+ data: null,
23
+ isSuccess: false,
24
+ isReady: false,
25
+ isAuthorized: true,
26
+ isValid: true,
27
+ hasExceptions: false,
28
+ validationResults: [],
29
+ exceptionMessages: [],
30
+ exceptionStackTrace: '',
31
+ paging: { page: 0, size: 0, totalItems: 0, totalPages: 0 },
32
+ }),
33
+ ok: true,
34
+ status: 202,
35
+ } as unknown as Response);
36
+
37
+ context.query.setOrigin('https://api.example.com');
38
+ result = await context.query.perform({ id: 'test-id' });
39
+ });
40
+
41
+ afterEach(() => fetchHelper.restore());
42
+
43
+ it('should return the not ready result', () => result.isReady.should.be.false);
44
+ it('should not report an exception', () => result.hasExceptions.should.be.false);
45
+ }),
46
+ );
@@ -0,0 +1,76 @@
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 * as sinon from 'sinon';
5
+ import { DateOnly, TimeOnly } from '@cratis/fundamentals';
6
+ import { an_observable_query_for } from '../given/an_observable_query_for';
7
+ import type { TestCalendarItem } from '../given/TestQueries';
8
+ import { given } from '../../../given';
9
+ import { Globals } from '../../../Globals';
10
+ import type { EventSourceFactory } from '../../../EventSourceFactory';
11
+ import type { QueryResult } from '../../QueryResult';
12
+ import { QueryTransportMethod } from '../../QueryTransportMethod';
13
+ import type { ObservableQuerySubscription } from '../../ObservableQuerySubscription';
14
+
15
+ interface FakeEventSource {
16
+ onmessage: ((event: MessageEvent) => void) | null;
17
+ onerror: (() => void) | null;
18
+ close: sinon.SinonStub;
19
+ }
20
+
21
+ describe('and the model has a calendar collection', given(an_observable_query_for, context => {
22
+ let subscription: ObservableQuerySubscription<TestCalendarItem[]>;
23
+ let fakeEventSource: FakeEventSource;
24
+ let received: QueryResult<TestCalendarItem[]>[];
25
+ let originalQueryDirectMode: boolean;
26
+ let originalTransportMethod: QueryTransportMethod;
27
+ let originalFactory: EventSourceFactory | undefined;
28
+
29
+ beforeEach(() => {
30
+ originalQueryDirectMode = Globals.queryDirectMode;
31
+ originalTransportMethod = Globals.queryTransportMethod;
32
+ originalFactory = Globals.eventSourceFactory;
33
+
34
+ Globals.queryDirectMode = true;
35
+ Globals.queryTransportMethod = QueryTransportMethod.ServerSentEvents;
36
+
37
+ fakeEventSource = { onmessage: null, onerror: null, close: sinon.stub() };
38
+ // SAFETY: The test double implements the EventSource members exercised by the query connection.
39
+ Globals.eventSourceFactory = (() => fakeEventSource) as unknown as EventSourceFactory;
40
+
41
+ context.calendarEnumerableQuery.setOrigin('https://example.com');
42
+
43
+ received = [];
44
+ subscription = context.calendarEnumerableQuery.subscribe(
45
+ result => received.push(result),
46
+ { category: 'test-category' });
47
+
48
+ fakeEventSource.onmessage!({
49
+ data: JSON.stringify({
50
+ data: [{ children: [{ date: '2026-02-03', time: '14:15:16' }] }],
51
+ isSuccess: true,
52
+ isAuthorized: true,
53
+ isValid: true,
54
+ hasExceptions: false,
55
+ validationResults: [],
56
+ exceptionMessages: [],
57
+ exceptionStackTrace: '',
58
+ paging: { page: 0, size: 0, totalItems: 1, totalPages: 1 }
59
+ })
60
+ } as MessageEvent);
61
+ });
62
+
63
+ afterEach(() => {
64
+ subscription?.unsubscribe();
65
+ Globals.queryDirectMode = originalQueryDirectMode;
66
+ Globals.queryTransportMethod = originalTransportMethod;
67
+ Globals.eventSourceFactory = originalFactory;
68
+ sinon.restore();
69
+ });
70
+
71
+ it('should deliver the result to the subscriber', () => received.length.should.equal(1));
72
+ it('should deserialize the child date', () => received[0].data[0].children[0].date.should.be.instanceOf(DateOnly));
73
+ it('should preserve the child date', () => received[0].data[0].children[0].date.equals(DateOnly.from(2026, 2, 3)).should.be.true);
74
+ it('should deserialize the child time', () => received[0].data[0].children[0].time.should.be.instanceOf(TimeOnly));
75
+ it('should preserve the child time', () => received[0].data[0].children[0].time.equals(TimeOnly.from(14, 15, 16)).should.be.true);
76
+ }));