@cratis/arc 20.60.1 → 20.60.3

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 (93) hide show
  1. package/dist/cjs/queries/HubConnectionKeepAlive.js +24 -4
  2. package/dist/cjs/queries/HubConnectionKeepAlive.js.map +1 -1
  3. package/dist/cjs/queries/QueryInstanceCache.js +35 -12
  4. package/dist/cjs/queries/QueryInstanceCache.js.map +1 -1
  5. package/dist/cjs/queries/ServerSentEventHubConnection.js +38 -10
  6. package/dist/cjs/queries/ServerSentEventHubConnection.js.map +1 -1
  7. package/dist/cjs/queries/WebSocketHubConnection.js.map +1 -1
  8. package/dist/cjs/queries/index.js +2 -0
  9. package/dist/cjs/queries/index.js.map +1 -1
  10. package/dist/cjs/queries/reconcileQueryData.js +84 -0
  11. package/dist/cjs/queries/reconcileQueryData.js.map +1 -0
  12. package/dist/esm/queries/HubConnectionKeepAlive.d.ts +5 -3
  13. package/dist/esm/queries/HubConnectionKeepAlive.d.ts.map +1 -1
  14. package/dist/esm/queries/HubConnectionKeepAlive.js +24 -4
  15. package/dist/esm/queries/HubConnectionKeepAlive.js.map +1 -1
  16. package/dist/esm/queries/QueryInstanceCache.d.ts +1 -0
  17. package/dist/esm/queries/QueryInstanceCache.d.ts.map +1 -1
  18. package/dist/esm/queries/QueryInstanceCache.js +35 -12
  19. package/dist/esm/queries/QueryInstanceCache.js.map +1 -1
  20. package/dist/esm/queries/ServerSentEventHubConnection.d.ts +1 -0
  21. package/dist/esm/queries/ServerSentEventHubConnection.d.ts.map +1 -1
  22. package/dist/esm/queries/ServerSentEventHubConnection.js +38 -10
  23. package/dist/esm/queries/ServerSentEventHubConnection.js.map +1 -1
  24. package/dist/esm/queries/WebSocketHubConnection.d.ts +1 -0
  25. package/dist/esm/queries/WebSocketHubConnection.d.ts.map +1 -1
  26. package/dist/esm/queries/WebSocketHubConnection.js.map +1 -1
  27. package/dist/esm/queries/for_HubConnectionKeepAlive/when_reconfiguring/while_running.d.ts +2 -0
  28. package/dist/esm/queries/for_HubConnectionKeepAlive/when_reconfiguring/while_running.d.ts.map +1 -0
  29. package/dist/esm/queries/for_HubConnectionKeepAlive/when_reconfiguring/while_running.js +37 -0
  30. package/dist/esm/queries/for_HubConnectionKeepAlive/when_reconfiguring/while_running.js.map +1 -0
  31. package/dist/esm/queries/for_HubConnectionKeepAlive/when_reconfiguring/while_stopped.d.ts +2 -0
  32. package/dist/esm/queries/for_HubConnectionKeepAlive/when_reconfiguring/while_stopped.d.ts.map +1 -0
  33. package/dist/esm/queries/for_HubConnectionKeepAlive/when_reconfiguring/while_stopped.js +22 -0
  34. package/dist/esm/queries/for_HubConnectionKeepAlive/when_reconfiguring/while_stopped.js.map +1 -0
  35. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/with_a_partially_changed_result.d.ts +2 -0
  36. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/with_a_partially_changed_result.d.ts.map +1 -0
  37. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/with_a_partially_changed_result.js +32 -0
  38. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/with_a_partially_changed_result.js.map +1 -0
  39. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/with_a_re_delivered_identical_result.d.ts +2 -0
  40. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/with_a_re_delivered_identical_result.d.ts.map +1 -0
  41. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/with_a_re_delivered_identical_result.js +22 -0
  42. package/dist/esm/queries/for_QueryInstanceCache/when_setting_last_result/with_a_re_delivered_identical_result.js.map +1 -0
  43. package/dist/esm/queries/for_ServerSentEventHubConnection/when_server_advertises_a_longer_keep_alive_interval/does_not_reconnect_at_the_assumed_threshold.d.ts +2 -0
  44. package/dist/esm/queries/for_ServerSentEventHubConnection/when_server_advertises_a_longer_keep_alive_interval/does_not_reconnect_at_the_assumed_threshold.d.ts.map +1 -0
  45. package/dist/esm/queries/for_ServerSentEventHubConnection/when_server_advertises_a_longer_keep_alive_interval/does_not_reconnect_at_the_assumed_threshold.js +39 -0
  46. package/dist/esm/queries/for_ServerSentEventHubConnection/when_server_advertises_a_longer_keep_alive_interval/does_not_reconnect_at_the_assumed_threshold.js.map +1 -0
  47. package/dist/esm/queries/for_ServerSentEventHubConnection/when_server_reports_keep_alive_disabled/never_reconnects_from_idle.d.ts +2 -0
  48. package/dist/esm/queries/for_ServerSentEventHubConnection/when_server_reports_keep_alive_disabled/never_reconnects_from_idle.d.ts.map +1 -0
  49. package/dist/esm/queries/for_ServerSentEventHubConnection/when_server_reports_keep_alive_disabled/never_reconnects_from_idle.js +28 -0
  50. package/dist/esm/queries/for_ServerSentEventHubConnection/when_server_reports_keep_alive_disabled/never_reconnects_from_idle.js.map +1 -0
  51. package/dist/esm/queries/for_reconcileQueryData/when_items_are_added_or_removed/reuses_the_surviving_items.d.ts +2 -0
  52. package/dist/esm/queries/for_reconcileQueryData/when_items_are_added_or_removed/reuses_the_surviving_items.d.ts.map +1 -0
  53. package/dist/esm/queries/for_reconcileQueryData/when_items_are_added_or_removed/reuses_the_surviving_items.js +36 -0
  54. package/dist/esm/queries/for_reconcileQueryData/when_items_are_added_or_removed/reuses_the_surviving_items.js.map +1 -0
  55. package/dist/esm/queries/for_reconcileQueryData/when_one_item_changed/only_the_changed_item_gets_a_new_reference.d.ts +2 -0
  56. package/dist/esm/queries/for_reconcileQueryData/when_one_item_changed/only_the_changed_item_gets_a_new_reference.d.ts.map +1 -0
  57. package/dist/esm/queries/for_reconcileQueryData/when_one_item_changed/only_the_changed_item_gets_a_new_reference.js +20 -0
  58. package/dist/esm/queries/for_reconcileQueryData/when_one_item_changed/only_the_changed_item_gets_a_new_reference.js.map +1 -0
  59. package/dist/esm/queries/for_reconcileQueryData/when_reconciling_an_unchanged_collection/preserves_all_references.d.ts +2 -0
  60. package/dist/esm/queries/for_reconcileQueryData/when_reconciling_an_unchanged_collection/preserves_all_references.d.ts.map +1 -0
  61. package/dist/esm/queries/for_reconcileQueryData/when_reconciling_an_unchanged_collection/preserves_all_references.js +15 -0
  62. package/dist/esm/queries/for_reconcileQueryData/when_reconciling_an_unchanged_collection/preserves_all_references.js.map +1 -0
  63. package/dist/esm/queries/for_reconcileQueryData/when_reconciling_non_collection_payloads/compares_by_value.d.ts +2 -0
  64. package/dist/esm/queries/for_reconcileQueryData/when_reconciling_non_collection_payloads/compares_by_value.d.ts.map +1 -0
  65. package/dist/esm/queries/for_reconcileQueryData/when_reconciling_non_collection_payloads/compares_by_value.js +20 -0
  66. package/dist/esm/queries/for_reconcileQueryData/when_reconciling_non_collection_payloads/compares_by_value.js.map +1 -0
  67. package/dist/esm/queries/index.d.ts +1 -0
  68. package/dist/esm/queries/index.d.ts.map +1 -1
  69. package/dist/esm/queries/index.js +1 -0
  70. package/dist/esm/queries/index.js.map +1 -1
  71. package/dist/esm/queries/reconcileQueryData.d.ts +2 -0
  72. package/dist/esm/queries/reconcileQueryData.d.ts.map +1 -0
  73. package/dist/esm/queries/reconcileQueryData.js +82 -0
  74. package/dist/esm/queries/reconcileQueryData.js.map +1 -0
  75. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  76. package/package.json +1 -1
  77. package/queries/HubConnectionKeepAlive.ts +30 -3
  78. package/queries/QueryInstanceCache.ts +61 -17
  79. package/queries/ServerSentEventHubConnection.ts +45 -10
  80. package/queries/WebSocketHubConnection.ts +6 -0
  81. package/queries/for_HubConnectionKeepAlive/when_reconfiguring/while_running.ts +50 -0
  82. package/queries/for_HubConnectionKeepAlive/when_reconfiguring/while_stopped.ts +28 -0
  83. package/queries/for_QueryInstanceCache/when_setting_last_result/with_a_partially_changed_result.ts +43 -0
  84. package/queries/for_QueryInstanceCache/when_setting_last_result/with_a_re_delivered_identical_result.ts +34 -0
  85. package/queries/for_ServerSentEventHubConnection/when_keep_alive_is_idle/triggers_reconnect.ts +4 -4
  86. package/queries/for_ServerSentEventHubConnection/when_server_advertises_a_longer_keep_alive_interval/does_not_reconnect_at_the_assumed_threshold.ts +64 -0
  87. package/queries/for_ServerSentEventHubConnection/when_server_reports_keep_alive_disabled/never_reconnects_from_idle.ts +50 -0
  88. package/queries/for_reconcileQueryData/when_items_are_added_or_removed/reuses_the_surviving_items.ts +46 -0
  89. package/queries/for_reconcileQueryData/when_one_item_changed/only_the_changed_item_gets_a_new_reference.ts +26 -0
  90. package/queries/for_reconcileQueryData/when_reconciling_an_unchanged_collection/preserves_all_references.ts +22 -0
  91. package/queries/for_reconcileQueryData/when_reconciling_non_collection_payloads/compares_by_value.ts +31 -0
  92. package/queries/index.ts +1 -0
  93. package/queries/reconcileQueryData.ts +98 -0
@@ -26,6 +26,7 @@ var ObservableQuerySubscription = require('./ObservableQuerySubscription.js');
26
26
  var WebSocketMessage = require('./WebSocketMessage.js');
27
27
  var QueryTransportMethod = require('./QueryTransportMethod.js');
28
28
  var QueryInstanceCache = require('./QueryInstanceCache.js');
29
+ var reconcileQueryData = require('./reconcileQueryData.js');
29
30
  var IQueryProvider = require('./IQueryProvider.js');
30
31
  var QueryProvider = require('./QueryProvider.js');
31
32
  var QueryValidator = require('./QueryValidator.js');
@@ -73,6 +74,7 @@ exports.ObservableQuerySubscription = ObservableQuerySubscription.ObservableQuer
73
74
  exports.WebSocketMessageType = WebSocketMessage.WebSocketMessageType;
74
75
  exports.QueryTransportMethod = QueryTransportMethod.QueryTransportMethod;
75
76
  exports.QueryInstanceCache = QueryInstanceCache.QueryInstanceCache;
77
+ exports.reconcileQueryData = reconcileQueryData.reconcileQueryData;
76
78
  exports.IQueryProvider = IQueryProvider.IQueryProvider;
77
79
  exports.QueryProvider = QueryProvider.QueryProvider;
78
80
  exports.QueryValidator = QueryValidator.QueryValidator;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../queries/index.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nexport * from './ChangeSet';\nexport * from './IChangeStreamFor';\nexport * from './IQuery';\nexport * from './IQueryFor';\nexport * from './Paging';\nexport * from './Sorting';\nexport * from './SortDirection';\nexport * from './SortingActions';\nexport * from './SortingActionsForQuery';\nexport * from './SortingActionsForObservableQuery';\nexport * from './QueryFor';\nexport * from './QueryHttpMethod';\nexport * from './QueryHttpMethodResolver';\nexport * from './QueryHttpRequest';\nexport * from './QueryResult';\nexport * from './QueryResultWithState';\nexport * from './IObservableQueryFor';\nexport * from './ObservableQueryFor';\nexport * from './IObservableQueryConnection';\nexport * from './IObservableQueryHubConnection';\nexport * from './IReconnectPolicy';\nexport * from './ReconnectPolicy';\nexport * from './HubConnectionKeepAlive';\nexport * from './ObservableQueryConnection';\nexport * from './ObservableQueryConnectionFactory';\nexport * from './ObservableQueryConnectionPool';\nexport * from './ServerSentEventQueryConnection';\nexport * from './ServerSentEventHubConnection';\nexport * from './WebSocketHubConnection';\nexport * from './ObservableQueryMultiplexer';\nexport * from './ObservableQuerySubscription';\nexport * from './WebSocketMessage';\nexport * from './QueryTransportMethod';\nexport * from './QueryInstanceCache';\nexport * from './IQueryProvider';\nexport * from './QueryProvider';\nexport * from './QueryValidator';\nexport * from './IObservableQueryDiagnostics';\nexport * from './ObservableQueryDiagnostics';\nexport * from './ObservableQueryDiagnosticsSnapshot';\nimport '../validation';"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":["../../../queries/index.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nexport * from './ChangeSet';\nexport * from './IChangeStreamFor';\nexport * from './IQuery';\nexport * from './IQueryFor';\nexport * from './Paging';\nexport * from './Sorting';\nexport * from './SortDirection';\nexport * from './SortingActions';\nexport * from './SortingActionsForQuery';\nexport * from './SortingActionsForObservableQuery';\nexport * from './QueryFor';\nexport * from './QueryHttpMethod';\nexport * from './QueryHttpMethodResolver';\nexport * from './QueryHttpRequest';\nexport * from './QueryResult';\nexport * from './QueryResultWithState';\nexport * from './IObservableQueryFor';\nexport * from './ObservableQueryFor';\nexport * from './IObservableQueryConnection';\nexport * from './IObservableQueryHubConnection';\nexport * from './IReconnectPolicy';\nexport * from './ReconnectPolicy';\nexport * from './HubConnectionKeepAlive';\nexport * from './ObservableQueryConnection';\nexport * from './ObservableQueryConnectionFactory';\nexport * from './ObservableQueryConnectionPool';\nexport * from './ServerSentEventQueryConnection';\nexport * from './ServerSentEventHubConnection';\nexport * from './WebSocketHubConnection';\nexport * from './ObservableQueryMultiplexer';\nexport * from './ObservableQuerySubscription';\nexport * from './WebSocketMessage';\nexport * from './QueryTransportMethod';\nexport * from './QueryInstanceCache';\nexport * from './reconcileQueryData';\nexport * from './IQueryProvider';\nexport * from './QueryProvider';\nexport * from './QueryValidator';\nexport * from './IObservableQueryDiagnostics';\nexport * from './ObservableQueryDiagnostics';\nexport * from './ObservableQueryDiagnosticsSnapshot';\nimport '../validation';"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,84 @@
1
+ 'use strict';
2
+
3
+ var deepEqual = require('../deepEqual.js');
4
+
5
+ // Copyright (c) Cratis. All rights reserved.
6
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
7
+ /**
8
+ * Resolves the conventional identity of an item so it can be matched against its previous version.
9
+ *
10
+ * Uses the `id` property, the same strategy the server-side change-set computation applies, and
11
+ * reduces concept-style identities (objects with a meaningful `toString()`) to a comparable value.
12
+ * @param {unknown} item The item to resolve identity for.
13
+ * @returns {unknown} The identity value, or `undefined` when the item carries no usable identity.
14
+ */ function getIdentity(item) {
15
+ const id = item?.id;
16
+ if (id === null || id === undefined) {
17
+ return undefined;
18
+ }
19
+ if (typeof id === 'object') {
20
+ const stringValue = id.toString();
21
+ return stringValue !== '[object Object]' ? stringValue : JSON.stringify(id);
22
+ }
23
+ return id;
24
+ }
25
+ /**
26
+ * Reconciles a full payload against the previous one, carrying over the previous object references
27
+ * for every item that did not actually change.
28
+ *
29
+ * Observable queries re-deliver a complete snapshot whenever a subscription is (re-)established.
30
+ * Deserializing that snapshot produces entirely new object references, so consumers that compare by
31
+ * reference — memoized components, effect dependencies — treat every item as changed even when the
32
+ * payload is identical to what they already display. Reconciling restores referential stability:
33
+ * unchanged items keep their previous reference, and when nothing changed at all the previous
34
+ * payload itself is returned so callers can detect "no change" with a single `===`.
35
+ *
36
+ * Items are matched by their `id` when present, falling back to position for identity-less items.
37
+ * @template T The payload type.
38
+ * @param {T} previous The payload currently held.
39
+ * @param {T} next The freshly received payload.
40
+ * @returns {T} `previous` when nothing changed; otherwise the new payload with unchanged items carried over by reference.
41
+ */ function reconcileQueryData(previous, next) {
42
+ if (previous === next) {
43
+ return previous;
44
+ }
45
+ if (Array.isArray(previous) && Array.isArray(next)) {
46
+ return reconcileArray(previous, next);
47
+ }
48
+ return deepEqual.deepEqual(previous, next) ? previous : next;
49
+ }
50
+ /**
51
+ * Reconciles two arrays element by element, preserving previous references for unchanged items.
52
+ * @param {unknown[]} previous The array currently held.
53
+ * @param {unknown[]} next The freshly received array.
54
+ * @returns {unknown[]} `previous` when the arrays are equivalent; otherwise a new array reusing unchanged items.
55
+ */ function reconcileArray(previous, next) {
56
+ const previousByIdentity = new Map();
57
+ for (const item of previous){
58
+ const identity = getIdentity(item);
59
+ // Keep the first occurrence — duplicate identities are not a shape we can reconcile
60
+ // meaningfully, and preferring the first keeps the result deterministic.
61
+ if (identity !== undefined && !previousByIdentity.has(identity)) {
62
+ previousByIdentity.set(identity, item);
63
+ }
64
+ }
65
+ let changed = previous.length !== next.length;
66
+ const reconciled = next.map((item, index)=>{
67
+ const identity = getIdentity(item);
68
+ const candidate = identity !== undefined ? previousByIdentity.get(identity) : previous[index];
69
+ if (candidate !== undefined && deepEqual.deepEqual(candidate, item)) {
70
+ // An unchanged item that moved still leaves the collection changed as a whole, but the
71
+ // item itself keeps its reference so only the ordering re-renders, not its content.
72
+ if (candidate !== previous[index]) {
73
+ changed = true;
74
+ }
75
+ return candidate;
76
+ }
77
+ changed = true;
78
+ return item;
79
+ });
80
+ return changed ? reconciled : previous;
81
+ }
82
+
83
+ exports.reconcileQueryData = reconcileQueryData;
84
+ //# sourceMappingURL=reconcileQueryData.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcileQueryData.js","sources":["../../../queries/reconcileQueryData.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { deepEqual } from '../deepEqual';\n\n/**\n * Resolves the conventional identity of an item so it can be matched against its previous version.\n *\n * Uses the `id` property, the same strategy the server-side change-set computation applies, and\n * reduces concept-style identities (objects with a meaningful `toString()`) to a comparable value.\n * @param {unknown} item The item to resolve identity for.\n * @returns {unknown} The identity value, or `undefined` when the item carries no usable identity.\n */\nfunction getIdentity(item: unknown): unknown {\n const id = (item as Record<string, unknown>)?.id;\n\n if (id === null || id === undefined) {\n return undefined;\n }\n\n if (typeof id === 'object') {\n const stringValue = id.toString();\n return stringValue !== '[object Object]' ? stringValue : JSON.stringify(id);\n }\n\n return id;\n}\n\n/**\n * Reconciles a full payload against the previous one, carrying over the previous object references\n * for every item that did not actually change.\n *\n * Observable queries re-deliver a complete snapshot whenever a subscription is (re-)established.\n * Deserializing that snapshot produces entirely new object references, so consumers that compare by\n * reference — memoized components, effect dependencies — treat every item as changed even when the\n * payload is identical to what they already display. Reconciling restores referential stability:\n * unchanged items keep their previous reference, and when nothing changed at all the previous\n * payload itself is returned so callers can detect \"no change\" with a single `===`.\n *\n * Items are matched by their `id` when present, falling back to position for identity-less items.\n * @template T The payload type.\n * @param {T} previous The payload currently held.\n * @param {T} next The freshly received payload.\n * @returns {T} `previous` when nothing changed; otherwise the new payload with unchanged items carried over by reference.\n */\nexport function reconcileQueryData<T>(previous: T, next: T): T {\n if (previous === next) {\n return previous;\n }\n\n if (Array.isArray(previous) && Array.isArray(next)) {\n return reconcileArray(previous, next) as T;\n }\n\n return deepEqual(previous, next) ? previous : next;\n}\n\n/**\n * Reconciles two arrays element by element, preserving previous references for unchanged items.\n * @param {unknown[]} previous The array currently held.\n * @param {unknown[]} next The freshly received array.\n * @returns {unknown[]} `previous` when the arrays are equivalent; otherwise a new array reusing unchanged items.\n */\nfunction reconcileArray(previous: unknown[], next: unknown[]): unknown[] {\n const previousByIdentity = new Map<unknown, unknown>();\n\n for (const item of previous) {\n const identity = getIdentity(item);\n\n // Keep the first occurrence — duplicate identities are not a shape we can reconcile\n // meaningfully, and preferring the first keeps the result deterministic.\n if (identity !== undefined && !previousByIdentity.has(identity)) {\n previousByIdentity.set(identity, item);\n }\n }\n\n let changed = previous.length !== next.length;\n\n const reconciled = next.map((item, index) => {\n const identity = getIdentity(item);\n const candidate = identity !== undefined ? previousByIdentity.get(identity) : previous[index];\n\n if (candidate !== undefined && deepEqual(candidate, item)) {\n // An unchanged item that moved still leaves the collection changed as a whole, but the\n // item itself keeps its reference so only the ordering re-renders, not its content.\n if (candidate !== previous[index]) {\n changed = true;\n }\n\n return candidate;\n }\n\n changed = true;\n return item;\n });\n\n return changed ? reconciled : previous;\n}\n"],"names":["getIdentity","item","id","undefined","stringValue","toString","JSON","stringify","reconcileQueryData","previous","next","Array","isArray","reconcileArray","deepEqual","previousByIdentity","Map","identity","has","set","changed","length","reconciled","map","index","candidate","get"],"mappings":";;;;AAAA;AACA;AAIA;;;;;;;IAQA,SAASA,YAAYC,IAAa,EAAA;AAC9B,IAAA,MAAMC,KAAMD,IAAAA,EAAkCC,EAAAA;IAE9C,IAAIA,EAAAA,KAAO,IAAA,IAAQA,EAAAA,KAAOC,SAAAA,EAAW;QACjC,OAAOA,SAAAA;AACX,IAAA;IAEA,IAAI,OAAOD,OAAO,QAAA,EAAU;QACxB,MAAME,WAAAA,GAAcF,GAAGG,QAAQ,EAAA;AAC/B,QAAA,OAAOD,WAAAA,KAAgB,iBAAA,GAAoBA,WAAAA,GAAcE,IAAAA,CAAKC,SAAS,CAACL,EAAAA,CAAAA;AAC5E,IAAA;IAEA,OAAOA,EAAAA;AACX;AAEA;;;;;;;;;;;;;;;;AAgBC,IACM,SAASM,kBAAAA,CAAsBC,QAAW,EAAEC,IAAO,EAAA;AACtD,IAAA,IAAID,aAAaC,IAAAA,EAAM;QACnB,OAAOD,QAAAA;AACX,IAAA;AAEA,IAAA,IAAIE,MAAMC,OAAO,CAACH,aAAaE,KAAAA,CAAMC,OAAO,CAACF,IAAAA,CAAAA,EAAO;AAChD,QAAA,OAAOG,eAAeJ,QAAAA,EAAUC,IAAAA,CAAAA;AACpC,IAAA;IAEA,OAAOI,mBAAAA,CAAUL,QAAAA,EAAUC,IAAAA,CAAAA,GAAQD,QAAAA,GAAWC,IAAAA;AAClD;AAEA;;;;;AAKC,IACD,SAASG,cAAAA,CAAeJ,QAAmB,EAAEC,IAAe,EAAA;AACxD,IAAA,MAAMK,qBAAqB,IAAIC,GAAAA,EAAAA;IAE/B,KAAK,MAAMf,QAAQQ,QAAAA,CAAU;AACzB,QAAA,MAAMQ,WAAWjB,WAAAA,CAAYC,IAAAA,CAAAA;;;AAI7B,QAAA,IAAIgB,aAAad,SAAAA,IAAa,CAACY,kBAAAA,CAAmBG,GAAG,CAACD,QAAAA,CAAAA,EAAW;YAC7DF,kBAAAA,CAAmBI,GAAG,CAACF,QAAAA,EAAUhB,IAAAA,CAAAA;AACrC,QAAA;AACJ,IAAA;AAEA,IAAA,IAAImB,OAAAA,GAAUX,QAAAA,CAASY,MAAM,KAAKX,KAAKW,MAAM;AAE7C,IAAA,MAAMC,UAAAA,GAAaZ,IAAAA,CAAKa,GAAG,CAAC,CAACtB,IAAAA,EAAMuB,KAAAA,GAAAA;AAC/B,QAAA,MAAMP,WAAWjB,WAAAA,CAAYC,IAAAA,CAAAA;QAC7B,MAAMwB,SAAAA,GAAYR,aAAad,SAAAA,GAAYY,kBAAAA,CAAmBW,GAAG,CAACT,QAAAA,CAAAA,GAAYR,QAAQ,CAACe,KAAAA,CAAM;AAE7F,QAAA,IAAIC,SAAAA,KAActB,SAAAA,IAAaW,mBAAAA,CAAUW,SAAAA,EAAWxB,IAAAA,CAAAA,EAAO;;;AAGvD,YAAA,IAAIwB,SAAAA,KAAchB,QAAQ,CAACe,KAAAA,CAAM,EAAE;gBAC/BJ,OAAAA,GAAU,IAAA;AACd,YAAA;YAEA,OAAOK,SAAAA;AACX,QAAA;QAEAL,OAAAA,GAAU,IAAA;QACV,OAAOnB,IAAAA;AACX,IAAA,CAAA,CAAA;AAEA,IAAA,OAAOmB,UAAUE,UAAAA,GAAab,QAAAA;AAClC;;;;"}
@@ -1,10 +1,12 @@
1
1
  export declare class HubConnectionKeepAlive {
2
- private readonly _intervalMs;
3
2
  private readonly _onIdle;
4
3
  private _lastActivityTime;
5
4
  private _timer?;
6
- private readonly _idleThresholdMs;
7
- constructor(_intervalMs: number, _onIdle: () => void, idleThresholdMs?: number);
5
+ private _intervalMs;
6
+ private _idleThresholdMs;
7
+ constructor(intervalMs: number, _onIdle: () => void, idleThresholdMs?: number);
8
+ get idleThresholdMs(): number;
9
+ reconfigure(intervalMs: number, idleThresholdMs: number): void;
8
10
  start(): void;
9
11
  stop(): void;
10
12
  recordActivity(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"HubConnectionKeepAlive.d.ts","sourceRoot":"","sources":["../../../queries/HubConnectionKeepAlive.ts"],"names":[],"mappings":"AAqBA,qBAAa,sBAAsB;IAgB3B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAhB5B,OAAO,CAAC,iBAAiB,CAAc;IACvC,OAAO,CAAC,MAAM,CAAC,CAAiC;IAChD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAY1C,YACqB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,IAAI,EACpC,eAAe,CAAC,EAAE,MAAM,EAG3B;IAKD,KAAK,IAAI,IAAI,CAQZ;IAKD,IAAI,IAAI,IAAI,CAKX;IAMD,cAAc,IAAI,IAAI,CAErB;CACJ"}
1
+ {"version":3,"file":"HubConnectionKeepAlive.d.ts","sourceRoot":"","sources":["../../../queries/HubConnectionKeepAlive.ts"],"names":[],"mappings":"AAqBA,qBAAa,sBAAsB;IAkB3B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAjB5B,OAAO,CAAC,iBAAiB,CAAc;IACvC,OAAO,CAAC,MAAM,CAAC,CAAiC;IAChD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,gBAAgB,CAAS;IAYjC,YACI,UAAU,EAAE,MAAM,EACD,OAAO,EAAE,MAAM,IAAI,EACpC,eAAe,CAAC,EAAE,MAAM,EAI3B;IAKD,IAAI,eAAe,IAAI,MAAM,CAE5B;IAUD,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,IAAI,CAQ7D;IAKD,KAAK,IAAI,IAAI,CAQZ;IAKD,IAAI,IAAI,IAAI,CAKX;IAMD,cAAc,IAAI,IAAI,CAErB;CACJ"}
@@ -18,10 +18,10 @@
18
18
  * of this class so the keep-alive logic is written once and behaves identically for both
19
19
  * transports.
20
20
  */ class HubConnectionKeepAlive {
21
- _intervalMs;
22
21
  _onIdle;
23
22
  _lastActivityTime = Date.now();
24
23
  _timer;
24
+ _intervalMs;
25
25
  _idleThresholdMs;
26
26
  /**
27
27
  * Initializes a new instance of {@link HubConnectionKeepAlive}.
@@ -32,10 +32,30 @@
32
32
  * before the connection is considered idle. Defaults to {@link intervalMs}. Set this
33
33
  * higher than {@link intervalMs} when the peer sends keep-alive messages on a similar
34
34
  * cadence to account for network latency and timer jitter.
35
- */ constructor(_intervalMs, _onIdle, idleThresholdMs){
36
- this._intervalMs = _intervalMs;
35
+ */ constructor(intervalMs, _onIdle, idleThresholdMs){
37
36
  this._onIdle = _onIdle;
38
- this._idleThresholdMs = idleThresholdMs ?? _intervalMs;
37
+ this._intervalMs = intervalMs;
38
+ this._idleThresholdMs = idleThresholdMs ?? intervalMs;
39
+ }
40
+ /**
41
+ * Gets how long (in milliseconds) without activity before the connection is considered idle.
42
+ */ get idleThresholdMs() {
43
+ return this._idleThresholdMs;
44
+ }
45
+ /**
46
+ * Change the check interval and idle threshold, restarting the timer when it is already running.
47
+ *
48
+ * Used when the peer advertises its actual keep-alive cadence after connecting, so the client
49
+ * stops relying on an assumed default that may not match the server's configuration.
50
+ * @param {number} intervalMs How often (in milliseconds) to check for idle connections.
51
+ * @param {number} idleThresholdMs How long (in milliseconds) without activity before the connection is considered idle.
52
+ */ reconfigure(intervalMs, idleThresholdMs) {
53
+ const wasRunning = this._timer !== undefined;
54
+ this._intervalMs = intervalMs;
55
+ this._idleThresholdMs = idleThresholdMs;
56
+ if (wasRunning) {
57
+ this.start();
58
+ }
39
59
  }
40
60
  /**
41
61
  * Start the keep-alive timer. Safe to call multiple times — a running timer is stopped first.
@@ -1 +1 @@
1
- {"version":3,"file":"HubConnectionKeepAlive.js","sources":["../../../queries/HubConnectionKeepAlive.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\n/**\n * Manages keep-alive behavior for hub connections (both WebSocket and Server-Sent Events).\n *\n * Records connection activity (any message received or sent). An interval fires every\n * {@link intervalMs} milliseconds; if no activity has been recorded since the last\n * {@link idleThresholdMs} milliseconds the provided {@link onIdle} callback is invoked —\n * the caller decides whether to send a ping, trigger a reconnect, or take some other action.\n *\n * The idle threshold defaults to the check interval but can be set higher to tolerate\n * network latency between the server's keep-alive ping and the client's idle check.\n * For SSE connections, where the idle callback triggers a reconnect, a tolerance of\n * 1.5× the server's keep-alive interval prevents spurious reconnects caused by the\n * client's timer firing just before the server's ping arrives.\n *\n * Both {@link WebSocketHubConnection} and {@link ServerSentEventHubConnection} own one instance\n * of this class so the keep-alive logic is written once and behaves identically for both\n * transports.\n */\nexport class HubConnectionKeepAlive {\n private _lastActivityTime = Date.now();\n private _timer?: ReturnType<typeof setInterval>;\n private readonly _idleThresholdMs: number;\n\n /**\n * Initializes a new instance of {@link HubConnectionKeepAlive}.\n * @param {number} intervalMs How often (in milliseconds) to check for idle connections.\n * @param {() => void} onIdle Callback invoked when the interval fires and no activity has\n * been recorded within the idle threshold.\n * @param {number} idleThresholdMs Optional. How long (in milliseconds) without activity\n * before the connection is considered idle. Defaults to {@link intervalMs}. Set this\n * higher than {@link intervalMs} when the peer sends keep-alive messages on a similar\n * cadence to account for network latency and timer jitter.\n */\n constructor(\n private readonly _intervalMs: number,\n private readonly _onIdle: () => void,\n idleThresholdMs?: number,\n ) {\n this._idleThresholdMs = idleThresholdMs ?? _intervalMs;\n }\n\n /**\n * Start the keep-alive timer. Safe to call multiple times — a running timer is stopped first.\n */\n start(): void {\n this.stop();\n this._lastActivityTime = Date.now();\n this._timer = setInterval(() => {\n if (Date.now() - this._lastActivityTime >= this._idleThresholdMs) {\n this._onIdle();\n }\n }, this._intervalMs);\n }\n\n /**\n * Stop the keep-alive timer.\n */\n stop(): void {\n if (this._timer !== undefined) {\n clearInterval(this._timer);\n this._timer = undefined;\n }\n }\n\n /**\n * Record that the connection has been active (message sent or received).\n * Resets the idle timer so that a keep-alive is not sent while data is already flowing.\n */\n recordActivity(): void {\n this._lastActivityTime = Date.now();\n }\n}\n"],"names":["HubConnectionKeepAlive","_lastActivityTime","Date","now","_timer","_idleThresholdMs","idleThresholdMs","_intervalMs","_onIdle","start","stop","setInterval","undefined","clearInterval","recordActivity"],"mappings":"AAAA;AACA;AAEA;;;;;;;;;;;;;;;;;AAiBC,IACM,MAAMA,sBAAAA,CAAAA;;;IACDC,iBAAAA,GAAoBC,IAAAA,CAAKC,GAAG,EAAA;IAC5BC,MAAAA;IACSC,gBAAAA;AAEjB;;;;;;;;;QAUA,WAAA,CACI,WAAoC,EACpC,OAAoC,EACpCC,eAAwB,CAC1B;aAHmBC,WAAAA,GAAAA,WAAAA;aACAC,OAAAA,GAAAA,OAAAA;QAGjB,IAAI,CAACH,gBAAgB,GAAGC,eAAAA,IAAmBC,WAAAA;AAC/C,IAAA;AAEA;;AAEC,QACDE,KAAAA,GAAc;AACV,QAAA,IAAI,CAACC,IAAI,EAAA;AACT,QAAA,IAAI,CAACT,iBAAiB,GAAGC,IAAAA,CAAKC,GAAG,EAAA;QACjC,IAAI,CAACC,MAAM,GAAGO,WAAAA,CAAY,IAAA;YACtB,IAAIT,IAAAA,CAAKC,GAAG,EAAA,GAAK,IAAI,CAACF,iBAAiB,IAAI,IAAI,CAACI,gBAAgB,EAAE;AAC9D,gBAAA,IAAI,CAACG,OAAO,EAAA;AAChB,YAAA;QACJ,CAAA,EAAG,IAAI,CAACD,WAAW,CAAA;AACvB,IAAA;AAEA;;AAEC,QACDG,IAAAA,GAAa;AACT,QAAA,IAAI,IAAI,CAACN,MAAM,KAAKQ,SAAAA,EAAW;YAC3BC,aAAAA,CAAc,IAAI,CAACT,MAAM,CAAA;YACzB,IAAI,CAACA,MAAM,GAAGQ,SAAAA;AAClB,QAAA;AACJ,IAAA;AAEA;;;AAGC,QACDE,cAAAA,GAAuB;AACnB,QAAA,IAAI,CAACb,iBAAiB,GAAGC,IAAAA,CAAKC,GAAG,EAAA;AACrC,IAAA;AACJ;;;;"}
1
+ {"version":3,"file":"HubConnectionKeepAlive.js","sources":["../../../queries/HubConnectionKeepAlive.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\n/**\n * Manages keep-alive behavior for hub connections (both WebSocket and Server-Sent Events).\n *\n * Records connection activity (any message received or sent). An interval fires every\n * {@link intervalMs} milliseconds; if no activity has been recorded since the last\n * {@link idleThresholdMs} milliseconds the provided {@link onIdle} callback is invoked —\n * the caller decides whether to send a ping, trigger a reconnect, or take some other action.\n *\n * The idle threshold defaults to the check interval but can be set higher to tolerate\n * network latency between the server's keep-alive ping and the client's idle check.\n * For SSE connections, where the idle callback triggers a reconnect, a tolerance of\n * 1.5× the server's keep-alive interval prevents spurious reconnects caused by the\n * client's timer firing just before the server's ping arrives.\n *\n * Both {@link WebSocketHubConnection} and {@link ServerSentEventHubConnection} own one instance\n * of this class so the keep-alive logic is written once and behaves identically for both\n * transports.\n */\nexport class HubConnectionKeepAlive {\n private _lastActivityTime = Date.now();\n private _timer?: ReturnType<typeof setInterval>;\n private _intervalMs: number;\n private _idleThresholdMs: number;\n\n /**\n * Initializes a new instance of {@link HubConnectionKeepAlive}.\n * @param {number} intervalMs How often (in milliseconds) to check for idle connections.\n * @param {() => void} onIdle Callback invoked when the interval fires and no activity has\n * been recorded within the idle threshold.\n * @param {number} idleThresholdMs Optional. How long (in milliseconds) without activity\n * before the connection is considered idle. Defaults to {@link intervalMs}. Set this\n * higher than {@link intervalMs} when the peer sends keep-alive messages on a similar\n * cadence to account for network latency and timer jitter.\n */\n constructor(\n intervalMs: number,\n private readonly _onIdle: () => void,\n idleThresholdMs?: number,\n ) {\n this._intervalMs = intervalMs;\n this._idleThresholdMs = idleThresholdMs ?? intervalMs;\n }\n\n /**\n * Gets how long (in milliseconds) without activity before the connection is considered idle.\n */\n get idleThresholdMs(): number {\n return this._idleThresholdMs;\n }\n\n /**\n * Change the check interval and idle threshold, restarting the timer when it is already running.\n *\n * Used when the peer advertises its actual keep-alive cadence after connecting, so the client\n * stops relying on an assumed default that may not match the server's configuration.\n * @param {number} intervalMs How often (in milliseconds) to check for idle connections.\n * @param {number} idleThresholdMs How long (in milliseconds) without activity before the connection is considered idle.\n */\n reconfigure(intervalMs: number, idleThresholdMs: number): void {\n const wasRunning = this._timer !== undefined;\n this._intervalMs = intervalMs;\n this._idleThresholdMs = idleThresholdMs;\n\n if (wasRunning) {\n this.start();\n }\n }\n\n /**\n * Start the keep-alive timer. Safe to call multiple times — a running timer is stopped first.\n */\n start(): void {\n this.stop();\n this._lastActivityTime = Date.now();\n this._timer = setInterval(() => {\n if (Date.now() - this._lastActivityTime >= this._idleThresholdMs) {\n this._onIdle();\n }\n }, this._intervalMs);\n }\n\n /**\n * Stop the keep-alive timer.\n */\n stop(): void {\n if (this._timer !== undefined) {\n clearInterval(this._timer);\n this._timer = undefined;\n }\n }\n\n /**\n * Record that the connection has been active (message sent or received).\n * Resets the idle timer so that a keep-alive is not sent while data is already flowing.\n */\n recordActivity(): void {\n this._lastActivityTime = Date.now();\n }\n}\n"],"names":["HubConnectionKeepAlive","_lastActivityTime","Date","now","_timer","_intervalMs","_idleThresholdMs","intervalMs","idleThresholdMs","_onIdle","reconfigure","wasRunning","undefined","start","stop","setInterval","clearInterval","recordActivity"],"mappings":"AAAA;AACA;AAEA;;;;;;;;;;;;;;;;;AAiBC,IACM,MAAMA,sBAAAA,CAAAA;;IACDC,iBAAAA,GAAoBC,IAAAA,CAAKC,GAAG,EAAA;IAC5BC,MAAAA;IACAC,WAAAA;IACAC,gBAAAA;AAER;;;;;;;;;AASC,QACD,YACIC,UAAkB,EAClB,OAAoC,EACpCC,eAAwB,CAC1B;aAFmBC,OAAAA,GAAAA,OAAAA;QAGjB,IAAI,CAACJ,WAAW,GAAGE,UAAAA;QACnB,IAAI,CAACD,gBAAgB,GAAGE,eAAAA,IAAmBD,UAAAA;AAC/C,IAAA;AAEA;;AAEC,QACD,IAAIC,eAAAA,GAA0B;QAC1B,OAAO,IAAI,CAACF,gBAAgB;AAChC,IAAA;AAEA;;;;;;;AAOC,QACDI,WAAAA,CAAYH,UAAkB,EAAEC,eAAuB,EAAQ;AAC3D,QAAA,MAAMG,UAAAA,GAAa,IAAI,CAACP,MAAM,KAAKQ,SAAAA;QACnC,IAAI,CAACP,WAAW,GAAGE,UAAAA;QACnB,IAAI,CAACD,gBAAgB,GAAGE,eAAAA;AAExB,QAAA,IAAIG,UAAAA,EAAY;AACZ,YAAA,IAAI,CAACE,KAAK,EAAA;AACd,QAAA;AACJ,IAAA;AAEA;;AAEC,QACDA,KAAAA,GAAc;AACV,QAAA,IAAI,CAACC,IAAI,EAAA;AACT,QAAA,IAAI,CAACb,iBAAiB,GAAGC,IAAAA,CAAKC,GAAG,EAAA;QACjC,IAAI,CAACC,MAAM,GAAGW,WAAAA,CAAY,IAAA;YACtB,IAAIb,IAAAA,CAAKC,GAAG,EAAA,GAAK,IAAI,CAACF,iBAAiB,IAAI,IAAI,CAACK,gBAAgB,EAAE;AAC9D,gBAAA,IAAI,CAACG,OAAO,EAAA;AAChB,YAAA;QACJ,CAAA,EAAG,IAAI,CAACJ,WAAW,CAAA;AACvB,IAAA;AAEA;;AAEC,QACDS,IAAAA,GAAa;AACT,QAAA,IAAI,IAAI,CAACV,MAAM,KAAKQ,SAAAA,EAAW;YAC3BI,aAAAA,CAAc,IAAI,CAACZ,MAAM,CAAA;YACzB,IAAI,CAACA,MAAM,GAAGQ,SAAAA;AAClB,QAAA;AACJ,IAAA;AAEA;;;AAGC,QACDK,cAAAA,GAAuB;AACnB,QAAA,IAAI,CAAChB,iBAAiB,GAAGC,IAAAA,CAAKC,GAAG,EAAA;AACrC,IAAA;AACJ;;;;"}
@@ -24,6 +24,7 @@ export declare class QueryInstanceCache {
24
24
  acquire(key: QueryCacheKey): void;
25
25
  getLastResult<TDataType>(key: QueryCacheKey): QueryResultWithState<TDataType> | undefined;
26
26
  setLastResult<TDataType>(key: QueryCacheKey, result: QueryResultWithState<TDataType>): void;
27
+ private withReconciledData;
27
28
  addListener<TDataType>(key: QueryCacheKey, listener: QueryCacheListener<TDataType>): void;
28
29
  removeListener<TDataType>(key: QueryCacheKey, listener: QueryCacheListener<TDataType>): void;
29
30
  setTeardown(key: QueryCacheKey, teardown: () => void): void;
@@ -1 +1 @@
1
- {"version":3,"file":"QueryInstanceCache.d.ts","sourceRoot":"","sources":["../../../queries/QueryInstanceCache.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAyB,MAAM,sCAAsC,CAAC;AAK/F,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC;AAKnC,MAAM,MAAM,kBAAkB,CAAC,SAAS,IAAI,CAAC,MAAM,EAAE,oBAAoB,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC;AAM9F,MAAM,WAAW,eAAe,CAAC,SAAS;IAItC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAK3B,UAAU,CAAC,EAAE,oBAAoB,CAAC,SAAS,CAAC,CAAC;IAK7C,eAAe,EAAE,MAAM,CAAC;IAKxB,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC,CAAC;IAMvD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IAKtB,UAAU,EAAE,OAAO,CAAC;IAOpB,cAAc,CAAC,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;CAClD;AAUD,qBAAa,kBAAkB;IAWf,OAAO,CAAC,QAAQ,CAAC,YAAY;IAVzC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsD;IAC/E,OAAO,CAAC,eAAe,CAAC,CAAgC;IASxD,YAA6B,YAAY,GAAE,MAAe,EACzD;IAUD,QAAQ,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,aAAa,CAa5D;IAUD,WAAW,CAAC,SAAS,EACjB,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,MAAM,SAAS,GACzB;QAAE,QAAQ,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAgBzC;IASD,OAAO,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAWhC;IAQD,aAAa,CAAC,SAAS,EAAE,GAAG,EAAE,aAAa,GAAG,oBAAoB,CAAC,SAAS,CAAC,GAAG,SAAS,CAExF;IAQD,aAAa,CAAC,SAAS,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,EAAE,oBAAoB,CAAC,SAAS,CAAC,GAAG,IAAI,CAsB1F;IAQD,WAAW,CAAC,SAAS,EAAE,GAAG,EAAE,aAAa,EAAE,QAAQ,EAAE,kBAAkB,CAAC,SAAS,CAAC,GAAG,IAAI,CAMxF;IAQD,cAAc,CAAC,SAAS,EAAE,GAAG,EAAE,aAAa,EAAE,QAAQ,EAAE,kBAAkB,CAAC,SAAS,CAAC,GAAG,IAAI,CAM3F;IAQD,WAAW,CAAC,GAAG,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAO1D;IAOD,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAExC;IAUD,OAAO,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAuBhC;IAOD,GAAG,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAE/B;IAYD,wBAAwB,IAAI,IAAI,CAW/B;IAOD,OAAO,IAAI,IAAI,CAad;IAWD,YAAY,IAAI,IAAI,CASnB;IAQD,oBAAoB,IAAI,IAAI,CAK3B;IAMD,sBAAsB,IAAI,gBAAgB,CAwCzC;CACJ"}
1
+ {"version":3,"file":"QueryInstanceCache.d.ts","sourceRoot":"","sources":["../../../queries/QueryInstanceCache.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAyB,MAAM,sCAAsC,CAAC;AAM/F,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC;AAKnC,MAAM,MAAM,kBAAkB,CAAC,SAAS,IAAI,CAAC,MAAM,EAAE,oBAAoB,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC;AAM9F,MAAM,WAAW,eAAe,CAAC,SAAS;IAItC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAK3B,UAAU,CAAC,EAAE,oBAAoB,CAAC,SAAS,CAAC,CAAC;IAK7C,eAAe,EAAE,MAAM,CAAC;IAKxB,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC,CAAC;IAMvD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IAKtB,UAAU,EAAE,OAAO,CAAC;IAOpB,cAAc,CAAC,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;CAClD;AAUD,qBAAa,kBAAkB;IAWf,OAAO,CAAC,QAAQ,CAAC,YAAY;IAVzC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsD;IAC/E,OAAO,CAAC,eAAe,CAAC,CAAgC;IASxD,YAA6B,YAAY,GAAE,MAAe,EACzD;IAUD,QAAQ,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,aAAa,CAa5D;IAUD,WAAW,CAAC,SAAS,EACjB,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,MAAM,SAAS,GACzB;QAAE,QAAQ,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAgBzC;IASD,OAAO,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAWhC;IAQD,aAAa,CAAC,SAAS,EAAE,GAAG,EAAE,aAAa,GAAG,oBAAoB,CAAC,SAAS,CAAC,GAAG,SAAS,CAExF;IAQD,aAAa,CAAC,SAAS,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,EAAE,oBAAoB,CAAC,SAAS,CAAC,GAAG,IAAI,CAiC1F;IAUD,OAAO,CAAC,kBAAkB;IA8B1B,WAAW,CAAC,SAAS,EAAE,GAAG,EAAE,aAAa,EAAE,QAAQ,EAAE,kBAAkB,CAAC,SAAS,CAAC,GAAG,IAAI,CAMxF;IAQD,cAAc,CAAC,SAAS,EAAE,GAAG,EAAE,aAAa,EAAE,QAAQ,EAAE,kBAAkB,CAAC,SAAS,CAAC,GAAG,IAAI,CAM3F;IAQD,WAAW,CAAC,GAAG,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAO1D;IAOD,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAExC;IAUD,OAAO,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAuBhC;IAOD,GAAG,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAE/B;IAYD,wBAAwB,IAAI,IAAI,CAW/B;IAOD,OAAO,IAAI,IAAI,CAad;IAWD,YAAY,IAAI,IAAI,CASnB;IAQD,oBAAoB,IAAI,IAAI,CAK3B;IAMD,sBAAsB,IAAI,gBAAgB,CAwCzC;CACJ"}
@@ -1,3 +1,6 @@
1
+ import { QueryResultWithState } from './QueryResultWithState.js';
2
+ import { reconcileQueryData } from './reconcileQueryData.js';
3
+
1
4
  // Copyright (c) Cratis. All rights reserved.
2
5
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
6
  /**
@@ -96,19 +99,39 @@
96
99
  * @param result The result to store.
97
100
  */ setLastResult(key, result) {
98
101
  const entry = this._entries.get(key);
99
- if (entry) {
100
- const previousResult = entry.lastResult;
101
- entry.lastResult = result;
102
- // Suppress re-renders when the server re-sends identical data after a reconnect.
103
- // We only compare `data` and `isSuccess` other fields (e.g. changeSet) are
104
- // ephemeral and do not affect what the user sees.
105
- if (previousResult !== undefined && previousResult.isSuccess === result.isSuccess && JSON.stringify(previousResult.data) === JSON.stringify(result.data)) {
106
- return;
107
- }
108
- for (const listener of entry.listeners){
109
- listener(result);
110
- }
102
+ if (!entry) {
103
+ return;
104
+ }
105
+ const previousResult = entry.lastResult;
106
+ // Reconcile the incoming payload against the one already held so items that did not actually
107
+ // change keep their previous references. A full snapshot — which is what the server re-sends
108
+ // whenever a subscription is re-established otherwise arrives as all-new references and
109
+ // makes every consumer treat every item as changed.
110
+ const reconciled = previousResult !== undefined ? this.withReconciledData(previousResult, result) : result;
111
+ entry.lastResult = reconciled;
112
+ // Reconciliation returns the previous data reference when nothing changed, so identity is
113
+ // enough to detect it. Only `data` and `isSuccess` matter here — the other fields (e.g.
114
+ // changeSet) are ephemeral and do not affect what the user sees.
115
+ if (previousResult !== undefined && previousResult.isSuccess === reconciled.isSuccess && previousResult.data === reconciled.data) {
116
+ return;
117
+ }
118
+ for (const listener of entry.listeners){
119
+ listener(reconciled);
120
+ }
121
+ }
122
+ /**
123
+ * Returns the incoming result with its data reconciled against the previous result, or the
124
+ * incoming result unchanged when reconciliation found nothing to carry over.
125
+ * @template TDataType The type of data returned by the query.
126
+ * @param previous The previously held result.
127
+ * @param next The freshly received result.
128
+ * @returns A result whose data preserves references for everything that did not change.
129
+ */ withReconciledData(previous, next) {
130
+ const reconciledData = reconcileQueryData(previous.data, next.data);
131
+ if (reconciledData === next.data) {
132
+ return next;
111
133
  }
134
+ return new QueryResultWithState(reconciledData, next.paging, next.isSuccess, next.isAuthorized, next.isValid, next.validationResults, next.hasExceptions, next.exceptionMessages, next.exceptionStackTrace, next.isPerforming, next.changeSet);
112
135
  }
113
136
  /**
114
137
  * Registers a listener that is invoked whenever the cached result for the given key changes.
@@ -1 +1 @@
1
- {"version":3,"file":"QueryInstanceCache.js","sources":["../../../queries/QueryInstanceCache.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { QueryResultWithState } from './QueryResultWithState';\nimport { CacheDiagnostics, CacheEntryDiagnostics } from './ObservableQueryDiagnosticsSnapshot';\n\n/**\n * Represents a key that uniquely identifies a query instance in the cache, based on the query type name and its serialized arguments.\n */\nexport type QueryCacheKey = string;\n\n/**\n * Callback invoked when the cached result for an entry changes.\n */\nexport type QueryCacheListener<TDataType> = (result: QueryResultWithState<TDataType>) => void;\n\n/**\n * Represents a single entry in the {@link QueryInstanceCache}.\n * @template TDataType The type of data returned by the query.\n */\nexport interface QueryCacheEntry<TDataType> {\n /**\n * The cached query instance.\n */\n readonly instance: unknown;\n\n /**\n * The last result received from the query, if any.\n */\n lastResult?: QueryResultWithState<TDataType>;\n\n /**\n * The number of active subscribers holding a reference to this entry.\n */\n subscriberCount: number;\n\n /**\n * Set of listener callbacks that are notified when {@link lastResult} changes.\n */\n readonly listeners: Set<QueryCacheListener<TDataType>>;\n\n /**\n * Cleanup function returned by the first subscriber that starts the query connection.\n * Called when the last subscriber releases the entry.\n */\n teardown?: () => void;\n\n /**\n * Whether an active subscription has been established for this entry.\n */\n subscribed: boolean;\n\n /**\n * Timer handle for deferred cleanup. Allows React StrictMode re-mounts (in any build\n * environment) to cancel the pending teardown so the connection is reused instead of\n * torn down and recreated.\n */\n pendingCleanup?: ReturnType<typeof setTimeout>;\n}\n\n/**\n * Provides a cache for query instances, keyed by query type and serialized arguments.\n *\n * Two callers requesting the same query type with identical arguments receive the same\n * cached instance and immediately see the last known result — without an additional\n * round trip to the server. When the last subscriber releases its reference the entry\n * is evicted.\n */\nexport class QueryInstanceCache {\n private readonly _entries = new Map<QueryCacheKey, QueryCacheEntry<unknown>>();\n private _pendingDispose?: ReturnType<typeof setTimeout>;\n\n /**\n * Initializes a new instance of {@link QueryInstanceCache}.\n * @param retentionMs How long in milliseconds to keep a cache entry alive after the last\n * subscriber releases it before evicting the subscription and cached data. A non-zero\n * value lets users navigate away and back without losing cached data. Defaults to\n * 30 000 ms. Pass 0 for immediate eviction.\n */\n constructor(private readonly _retentionMs: number = 30_000) {\n }\n\n /**\n * Builds the cache key for a query.\n * @param queryTypeName The stable type name for the query. Use the instance's {@link queryName}\n * (a hardcoded fully-qualified string in generated proxies) rather than {@link Function.name},\n * which is unstable under minification.\n * @param args Optional arguments supplied to the query.\n * @returns A stable string key.\n */\n buildKey(queryTypeName: string, args?: object): QueryCacheKey {\n if (!args || Object.keys(args).length === 0) {\n return `${queryTypeName}::`;\n }\n\n const sorted = Object.keys(args)\n .sort()\n .reduce<Record<string, unknown>>((accumulator, key) => {\n accumulator[key] = (args as Record<string, unknown>)[key];\n return accumulator;\n }, {});\n\n return `${queryTypeName}::${JSON.stringify(sorted)}`;\n }\n\n /**\n * Returns a cached instance for the given key, or creates a new one using the provided factory.\n * The subscriber count of the entry is incremented.\n * @template TInstance The type of the query instance.\n * @param key The cache key produced by {@link buildKey}.\n * @param factory A zero-argument factory that creates a fresh query instance when one is not yet cached.\n * @returns The cached (or newly created) instance and whether it was newly created.\n */\n getOrCreate<TInstance>(\n key: QueryCacheKey,\n factory: () => TInstance\n ): { instance: TInstance; isNew: boolean } {\n if (!this._entries.has(key)) {\n const entry: QueryCacheEntry<unknown> = {\n instance: factory(),\n lastResult: undefined,\n subscriberCount: 0,\n listeners: new Set(),\n subscribed: false,\n };\n\n this._entries.set(key, entry);\n return { instance: entry.instance as TInstance, isNew: true };\n }\n\n const entry = this._entries.get(key)!;\n return { instance: entry.instance as TInstance, isNew: false };\n }\n\n /**\n * Increments the active subscriber count for the given key.\n * If a deferred cleanup was pending (from a recent {@link release}),\n * it is cancelled so the existing subscription is reused.\n * Call from `useEffect` setup to pair with {@link release} in the cleanup.\n * @param key The cache key produced by {@link buildKey}.\n */\n acquire(key: QueryCacheKey): void {\n const entry = this._entries.get(key);\n\n if (entry) {\n if (entry.pendingCleanup !== undefined) {\n clearTimeout(entry.pendingCleanup);\n entry.pendingCleanup = undefined;\n }\n\n entry.subscriberCount++;\n }\n }\n\n /**\n * Returns the last cached result for the given key, or `undefined` if no result has been stored yet.\n * @template TDataType The type of data returned by the query.\n * @param key The cache key produced by {@link buildKey}.\n * @returns The last {@link QueryResultWithState}, or `undefined`.\n */\n getLastResult<TDataType>(key: QueryCacheKey): QueryResultWithState<TDataType> | undefined {\n return this._entries.get(key)?.lastResult as QueryResultWithState<TDataType> | undefined;\n }\n\n /**\n * Stores the most recent result for the given key and notifies all registered listeners.\n * @template TDataType The type of data returned by the query.\n * @param key The cache key produced by {@link buildKey}.\n * @param result The result to store.\n */\n setLastResult<TDataType>(key: QueryCacheKey, result: QueryResultWithState<TDataType>): void {\n const entry = this._entries.get(key);\n\n if (entry) {\n const previousResult = entry.lastResult as QueryResultWithState<TDataType> | undefined;\n entry.lastResult = result as QueryResultWithState<unknown>;\n\n // Suppress re-renders when the server re-sends identical data after a reconnect.\n // We only compare `data` and `isSuccess` — other fields (e.g. changeSet) are\n // ephemeral and do not affect what the user sees.\n if (\n previousResult !== undefined &&\n previousResult.isSuccess === result.isSuccess &&\n JSON.stringify(previousResult.data) === JSON.stringify(result.data)\n ) {\n return;\n }\n\n for (const listener of entry.listeners) {\n (listener as QueryCacheListener<TDataType>)(result);\n }\n }\n }\n\n /**\n * Registers a listener that is invoked whenever the cached result for the given key changes.\n * @template TDataType The type of data returned by the query.\n * @param key The cache key produced by {@link buildKey}.\n * @param listener The callback to register.\n */\n addListener<TDataType>(key: QueryCacheKey, listener: QueryCacheListener<TDataType>): void {\n const entry = this._entries.get(key);\n\n if (entry) {\n entry.listeners.add(listener as QueryCacheListener<unknown>);\n }\n }\n\n /**\n * Removes a previously registered listener.\n * @template TDataType The type of data returned by the query.\n * @param key The cache key produced by {@link buildKey}.\n * @param listener The callback to remove.\n */\n removeListener<TDataType>(key: QueryCacheKey, listener: QueryCacheListener<TDataType>): void {\n const entry = this._entries.get(key);\n\n if (entry) {\n entry.listeners.delete(listener as QueryCacheListener<unknown>);\n }\n }\n\n /**\n * Stores a teardown function for the given key and marks the entry as subscribed.\n * Called automatically when the last subscriber releases the entry.\n * @param key The cache key produced by {@link buildKey}.\n * @param teardown Cleanup function that disconnects the underlying query subscription.\n */\n setTeardown(key: QueryCacheKey, teardown: () => void): void {\n const entry = this._entries.get(key);\n\n if (entry) {\n entry.teardown = teardown;\n entry.subscribed = true;\n }\n }\n\n /**\n * Returns whether an active subscription exists for the given key.\n * @param key The cache key to check.\n * @returns `true` if a subscription has been established; `false` otherwise.\n */\n isSubscribed(key: QueryCacheKey): boolean {\n return this._entries.get(key)?.subscribed ?? false;\n }\n\n /**\n * Decrements the subscriber count for the given key. When the count reaches zero, both teardown\n * and eviction are deferred by one microtask so that React StrictMode re-mounts — in any build\n * environment — can re-acquire the entry and cancel the cleanup before the timeout fires. This\n * prevents an unnecessary disconnect/reconnect cycle during the synthetic unmount/remount that\n * StrictMode performs.\n * @param key The cache key produced by {@link buildKey}.\n */\n release(key: QueryCacheKey): void {\n const entry = this._entries.get(key);\n\n if (entry) {\n entry.subscriberCount--;\n\n if (entry.subscriberCount <= 0) {\n // Defer both teardown and eviction. React StrictMode re-mounts can cancel by\n // calling acquire() before the timeout fires. A non-zero _retentionMs keeps the\n // entry alive so users navigating back quickly see cached data immediately.\n entry.pendingCleanup = setTimeout(() => {\n const current = this._entries.get(key);\n\n if (current && current.subscriberCount <= 0) {\n current.subscribed = false;\n current.teardown?.();\n current.teardown = undefined;\n current.pendingCleanup = undefined;\n this._entries.delete(key);\n }\n }, this._retentionMs);\n }\n }\n }\n\n /**\n * Returns whether an entry exists for the given key.\n * @param key The cache key to check.\n * @returns `true` if an entry exists; `false` otherwise.\n */\n has(key: QueryCacheKey): boolean {\n return this._entries.has(key);\n }\n\n /**\n * Tears down all active subscriptions and marks every entry as not subscribed,\n * but preserves entries, subscriber counts, and listeners. This allows\n * hooks whose effects re-run afterward to detect that the entry is no longer\n * subscribed and re-establish a fresh connection.\n *\n * Use this when the underlying transport must be replaced (e.g. after an\n * authentication change that requires new WebSocket connections with updated\n * credentials).\n */\n teardownAllSubscriptions(): void {\n for (const [, entry] of this._entries) {\n if (entry.pendingCleanup !== undefined) {\n clearTimeout(entry.pendingCleanup);\n entry.pendingCleanup = undefined;\n }\n\n entry.subscribed = false;\n entry.teardown?.();\n entry.teardown = undefined;\n }\n }\n\n /**\n * Immediately tears down all subscriptions, cancels any pending deferred cleanups,\n * and evicts all entries. Call when the owning component (e.g. the {@link Arc} provider)\n * unmounts permanently so that all query connections are closed synchronously.\n */\n dispose(): void {\n for (const [, entry] of this._entries) {\n if (entry.pendingCleanup !== undefined) {\n clearTimeout(entry.pendingCleanup);\n entry.pendingCleanup = undefined;\n }\n\n entry.subscribed = false;\n entry.teardown?.();\n entry.teardown = undefined;\n }\n\n this._entries.clear();\n }\n\n /**\n * Schedules a deferred {@link dispose} using {@code setTimeout(0)}.\n *\n * This allows React StrictMode re-mounts to call {@link cancelPendingDispose}\n * before the dispose fires, avoiding the destruction of cache entries that child\n * effects are about to re-acquire.\n *\n * If a deferred dispose is already pending, it is replaced.\n */\n deferDispose(): void {\n if (this._pendingDispose !== undefined) {\n clearTimeout(this._pendingDispose);\n }\n\n this._pendingDispose = setTimeout(() => {\n this._pendingDispose = undefined;\n this.dispose();\n }, 0);\n }\n\n /**\n * Cancels a pending deferred dispose scheduled by {@link deferDispose}.\n *\n * Call from the {@code useEffect} setup phase so that a StrictMode re-mount\n * prevents the synthetic unmount's deferred dispose from firing.\n */\n cancelPendingDispose(): void {\n if (this._pendingDispose !== undefined) {\n clearTimeout(this._pendingDispose);\n this._pendingDispose = undefined;\n }\n }\n\n /**\n * Returns a diagnostics snapshot of the current cache state.\n * @returns A {@link CacheDiagnostics} describing all entries.\n */\n getDiagnosticsSnapshot(): CacheDiagnostics {\n const entries: CacheEntryDiagnostics[] = [];\n let totalBytes = 0;\n let unhealthyCount = 0;\n\n for (const [key, entry] of this._entries) {\n const colonIndex = key.indexOf('::');\n const queryName = colonIndex >= 0 ? key.substring(0, colonIndex) : key;\n\n let estimatedBytes = 0;\n try {\n if (entry.lastResult !== undefined) {\n estimatedBytes = JSON.stringify(entry.lastResult).length;\n }\n } catch {\n // Ignore serialization errors\n }\n\n if (entry.subscriberCount > 0 && !entry.subscribed) {\n unhealthyCount++;\n }\n\n totalBytes += estimatedBytes;\n entries.push({\n key,\n queryName,\n subscriberCount: entry.subscriberCount,\n listenerCount: entry.listeners.size,\n subscribed: entry.subscribed,\n hasResult: entry.lastResult !== undefined,\n estimatedBytes,\n });\n }\n\n return {\n healthy: unhealthyCount === 0,\n entryCount: this._entries.size,\n estimatedBytes: totalBytes,\n entries,\n };\n }\n}\n"],"names":["QueryInstanceCache","_entries","Map","_pendingDispose","_retentionMs","buildKey","queryTypeName","args","Object","keys","length","sorted","sort","reduce","accumulator","key","JSON","stringify","getOrCreate","factory","has","entry","instance","lastResult","undefined","subscriberCount","listeners","Set","subscribed","set","isNew","get","acquire","pendingCleanup","clearTimeout","getLastResult","setLastResult","result","previousResult","isSuccess","data","listener","addListener","add","removeListener","delete","setTeardown","teardown","isSubscribed","release","setTimeout","current","teardownAllSubscriptions","dispose","clear","deferDispose","cancelPendingDispose","getDiagnosticsSnapshot","entries","totalBytes","unhealthyCount","colonIndex","indexOf","queryName","substring","estimatedBytes","push","listenerCount","size","hasResult","healthy","entryCount"],"mappings":"AAAA;AACA;AA2DA;;;;;;;AAOC,IACM,MAAMA,kBAAAA,CAAAA;;AACQC,IAAAA,QAAAA,GAAW,IAAIC,GAAAA,EAAAA;IACxBC,eAAAA;AAER;;;;;;AAMC,QACD,WAAA,CAA6BC,YAAAA,GAAuB,MAAM,CAAE;aAA/BA,YAAAA,GAAAA,YAAAA;AAC7B,IAAA;AAEA;;;;;;;AAOC,QACDC,QAAAA,CAASC,aAAqB,EAAEC,IAAa,EAAiB;QAC1D,IAAI,CAACA,QAAQC,MAAAA,CAAOC,IAAI,CAACF,IAAAA,CAAAA,CAAMG,MAAM,KAAK,CAAA,EAAG;YACzC,OAAO,CAAA,EAAGJ,aAAAA,CAAc,EAAE,CAAC;AAC/B,QAAA;QAEA,MAAMK,MAAAA,GAASH,MAAAA,CAAOC,IAAI,CAACF,IAAAA,CAAAA,CACtBK,IAAI,EAAA,CACJC,MAAM,CAA0B,CAACC,WAAAA,EAAaC,GAAAA,GAAAA;AAC3CD,YAAAA,WAAW,CAACC,GAAAA,CAAI,GAAG,IAAiC,CAACA,GAAAA,CAAI;YACzD,OAAOD,WAAAA;AACX,QAAA,CAAA,EAAG,EAAC,CAAA;AAER,QAAA,OAAO,GAAGR,aAAAA,CAAc,EAAE,EAAEU,IAAAA,CAAKC,SAAS,CAACN,MAAAA,CAAAA,CAAAA,CAAS;AACxD,IAAA;AAEA;;;;;;;AAOC,QACDO,WAAAA,CACIH,GAAkB,EAClBI,OAAwB,EACe;AACvC,QAAA,IAAI,CAAC,IAAI,CAAClB,QAAQ,CAACmB,GAAG,CAACL,GAAAA,CAAAA,EAAM;AACzB,YAAA,MAAMM,KAAAA,GAAkC;gBACpCC,QAAAA,EAAUH,OAAAA,EAAAA;gBACVI,UAAAA,EAAYC,SAAAA;gBACZC,eAAAA,EAAiB,CAAA;AACjBC,gBAAAA,SAAAA,EAAW,IAAIC,GAAAA,EAAAA;gBACfC,UAAAA,EAAY;AAChB,aAAA;AAEA,YAAA,IAAI,CAAC3B,QAAQ,CAAC4B,GAAG,CAACd,GAAAA,EAAKM,KAAAA,CAAAA;YACvB,OAAO;AAAEC,gBAAAA,QAAAA,EAAUD,MAAMC,QAAQ;gBAAeQ,KAAAA,EAAO;AAAK,aAAA;AAChE,QAAA;AAEA,QAAA,MAAMT,QAAQ,IAAI,CAACpB,QAAQ,CAAC8B,GAAG,CAAChB,GAAAA,CAAAA;QAChC,OAAO;AAAEO,YAAAA,QAAAA,EAAUD,MAAMC,QAAQ;YAAeQ,KAAAA,EAAO;AAAM,SAAA;AACjE,IAAA;AAEA;;;;;;QAOAE,OAAAA,CAAQjB,GAAkB,EAAQ;AAC9B,QAAA,MAAMM,QAAQ,IAAI,CAACpB,QAAQ,CAAC8B,GAAG,CAAChB,GAAAA,CAAAA;AAEhC,QAAA,IAAIM,KAAAA,EAAO;YACP,IAAIA,KAAAA,CAAMY,cAAc,KAAKT,SAAAA,EAAW;AACpCU,gBAAAA,YAAAA,CAAab,MAAMY,cAAc,CAAA;AACjCZ,gBAAAA,KAAAA,CAAMY,cAAc,GAAGT,SAAAA;AAC3B,YAAA;AAEAH,YAAAA,KAAAA,CAAMI,eAAe,EAAA;AACzB,QAAA;AACJ,IAAA;AAEA;;;;;QAMAU,aAAAA,CAAyBpB,GAAkB,EAA+C;AACtF,QAAA,OAAO,IAAI,CAACd,QAAQ,CAAC8B,GAAG,CAAChB,GAAAA,CAAAA,EAAMQ,UAAAA;AACnC,IAAA;AAEA;;;;;AAKC,QACDa,aAAAA,CAAyBrB,GAAkB,EAAEsB,MAAuC,EAAQ;AACxF,QAAA,MAAMhB,QAAQ,IAAI,CAACpB,QAAQ,CAAC8B,GAAG,CAAChB,GAAAA,CAAAA;AAEhC,QAAA,IAAIM,KAAAA,EAAO;YACP,MAAMiB,cAAAA,GAAiBjB,MAAME,UAAU;AACvCF,YAAAA,KAAAA,CAAME,UAAU,GAAGc,MAAAA;;;;AAKnB,YAAA,IACIC,mBAAmBd,SAAAA,IACnBc,cAAAA,CAAeC,SAAS,KAAKF,MAAAA,CAAOE,SAAS,IAC7CvB,IAAAA,CAAKC,SAAS,CAACqB,cAAAA,CAAeE,IAAI,CAAA,KAAMxB,IAAAA,CAAKC,SAAS,CAACoB,MAAAA,CAAOG,IAAI,CAAA,EACpE;AACE,gBAAA;AACJ,YAAA;AAEA,YAAA,KAAK,MAAMC,QAAAA,IAAYpB,KAAAA,CAAMK,SAAS,CAAE;gBACnCe,QAAAA,CAA2CJ,MAAAA,CAAAA;AAChD,YAAA;AACJ,QAAA;AACJ,IAAA;AAEA;;;;;AAKC,QACDK,WAAAA,CAAuB3B,GAAkB,EAAE0B,QAAuC,EAAQ;AACtF,QAAA,MAAMpB,QAAQ,IAAI,CAACpB,QAAQ,CAAC8B,GAAG,CAAChB,GAAAA,CAAAA;AAEhC,QAAA,IAAIM,KAAAA,EAAO;YACPA,KAAAA,CAAMK,SAAS,CAACiB,GAAG,CAACF,QAAAA,CAAAA;AACxB,QAAA;AACJ,IAAA;AAEA;;;;;AAKC,QACDG,cAAAA,CAA0B7B,GAAkB,EAAE0B,QAAuC,EAAQ;AACzF,QAAA,MAAMpB,QAAQ,IAAI,CAACpB,QAAQ,CAAC8B,GAAG,CAAChB,GAAAA,CAAAA;AAEhC,QAAA,IAAIM,KAAAA,EAAO;YACPA,KAAAA,CAAMK,SAAS,CAACmB,MAAM,CAACJ,QAAAA,CAAAA;AAC3B,QAAA;AACJ,IAAA;AAEA;;;;;AAKC,QACDK,WAAAA,CAAY/B,GAAkB,EAAEgC,QAAoB,EAAQ;AACxD,QAAA,MAAM1B,QAAQ,IAAI,CAACpB,QAAQ,CAAC8B,GAAG,CAAChB,GAAAA,CAAAA;AAEhC,QAAA,IAAIM,KAAAA,EAAO;AACPA,YAAAA,KAAAA,CAAM0B,QAAQ,GAAGA,QAAAA;AACjB1B,YAAAA,KAAAA,CAAMO,UAAU,GAAG,IAAA;AACvB,QAAA;AACJ,IAAA;AAEA;;;;QAKAoB,YAAAA,CAAajC,GAAkB,EAAW;AACtC,QAAA,OAAO,IAAI,CAACd,QAAQ,CAAC8B,GAAG,CAAChB,MAAMa,UAAAA,IAAc,KAAA;AACjD,IAAA;AAEA;;;;;;;QAQAqB,OAAAA,CAAQlC,GAAkB,EAAQ;AAC9B,QAAA,MAAMM,QAAQ,IAAI,CAACpB,QAAQ,CAAC8B,GAAG,CAAChB,GAAAA,CAAAA;AAEhC,QAAA,IAAIM,KAAAA,EAAO;AACPA,YAAAA,KAAAA,CAAMI,eAAe,EAAA;YAErB,IAAIJ,KAAAA,CAAMI,eAAe,IAAI,CAAA,EAAG;;;;gBAI5BJ,KAAAA,CAAMY,cAAc,GAAGiB,UAAAA,CAAW,IAAA;AAC9B,oBAAA,MAAMC,UAAU,IAAI,CAAClD,QAAQ,CAAC8B,GAAG,CAAChB,GAAAA,CAAAA;AAElC,oBAAA,IAAIoC,OAAAA,IAAWA,OAAAA,CAAQ1B,eAAe,IAAI,CAAA,EAAG;AACzC0B,wBAAAA,OAAAA,CAAQvB,UAAU,GAAG,KAAA;AACrBuB,wBAAAA,OAAAA,CAAQJ,QAAQ,IAAA;AAChBI,wBAAAA,OAAAA,CAAQJ,QAAQ,GAAGvB,SAAAA;AACnB2B,wBAAAA,OAAAA,CAAQlB,cAAc,GAAGT,SAAAA;AACzB,wBAAA,IAAI,CAACvB,QAAQ,CAAC4C,MAAM,CAAC9B,GAAAA,CAAAA;AACzB,oBAAA;gBACJ,CAAA,EAAG,IAAI,CAACX,YAAY,CAAA;AACxB,YAAA;AACJ,QAAA;AACJ,IAAA;AAEA;;;;QAKAgB,GAAAA,CAAIL,GAAkB,EAAW;AAC7B,QAAA,OAAO,IAAI,CAACd,QAAQ,CAACmB,GAAG,CAACL,GAAAA,CAAAA;AAC7B,IAAA;AAEA;;;;;;;;;AASC,QACDqC,wBAAAA,GAAiC;AAC7B,QAAA,KAAK,MAAM,GAAG/B,KAAAA,CAAM,IAAI,IAAI,CAACpB,QAAQ,CAAE;YACnC,IAAIoB,KAAAA,CAAMY,cAAc,KAAKT,SAAAA,EAAW;AACpCU,gBAAAA,YAAAA,CAAab,MAAMY,cAAc,CAAA;AACjCZ,gBAAAA,KAAAA,CAAMY,cAAc,GAAGT,SAAAA;AAC3B,YAAA;AAEAH,YAAAA,KAAAA,CAAMO,UAAU,GAAG,KAAA;AACnBP,YAAAA,KAAAA,CAAM0B,QAAQ,IAAA;AACd1B,YAAAA,KAAAA,CAAM0B,QAAQ,GAAGvB,SAAAA;AACrB,QAAA;AACJ,IAAA;AAEA;;;;AAIC,QACD6B,OAAAA,GAAgB;AACZ,QAAA,KAAK,MAAM,GAAGhC,KAAAA,CAAM,IAAI,IAAI,CAACpB,QAAQ,CAAE;YACnC,IAAIoB,KAAAA,CAAMY,cAAc,KAAKT,SAAAA,EAAW;AACpCU,gBAAAA,YAAAA,CAAab,MAAMY,cAAc,CAAA;AACjCZ,gBAAAA,KAAAA,CAAMY,cAAc,GAAGT,SAAAA;AAC3B,YAAA;AAEAH,YAAAA,KAAAA,CAAMO,UAAU,GAAG,KAAA;AACnBP,YAAAA,KAAAA,CAAM0B,QAAQ,IAAA;AACd1B,YAAAA,KAAAA,CAAM0B,QAAQ,GAAGvB,SAAAA;AACrB,QAAA;QAEA,IAAI,CAACvB,QAAQ,CAACqD,KAAK,EAAA;AACvB,IAAA;AAEA;;;;;;;;AAQC,QACDC,YAAAA,GAAqB;AACjB,QAAA,IAAI,IAAI,CAACpD,eAAe,KAAKqB,SAAAA,EAAW;YACpCU,YAAAA,CAAa,IAAI,CAAC/B,eAAe,CAAA;AACrC,QAAA;QAEA,IAAI,CAACA,eAAe,GAAG+C,UAAAA,CAAW,IAAA;YAC9B,IAAI,CAAC/C,eAAe,GAAGqB,SAAAA;AACvB,YAAA,IAAI,CAAC6B,OAAO,EAAA;QAChB,CAAA,EAAG,CAAA,CAAA;AACP,IAAA;AAEA;;;;;AAKC,QACDG,oBAAAA,GAA6B;AACzB,QAAA,IAAI,IAAI,CAACrD,eAAe,KAAKqB,SAAAA,EAAW;YACpCU,YAAAA,CAAa,IAAI,CAAC/B,eAAe,CAAA;YACjC,IAAI,CAACA,eAAe,GAAGqB,SAAAA;AAC3B,QAAA;AACJ,IAAA;AAEA;;;AAGC,QACDiC,sBAAAA,GAA2C;AACvC,QAAA,MAAMC,UAAmC,EAAE;AAC3C,QAAA,IAAIC,UAAAA,GAAa,CAAA;AACjB,QAAA,IAAIC,cAAAA,GAAiB,CAAA;QAErB,KAAK,MAAM,CAAC7C,GAAAA,EAAKM,KAAAA,CAAM,IAAI,IAAI,CAACpB,QAAQ,CAAE;YACtC,MAAM4D,UAAAA,GAAa9C,GAAAA,CAAI+C,OAAO,CAAC,IAAA,CAAA;AAC/B,YAAA,MAAMC,YAAYF,UAAAA,IAAc,CAAA,GAAI9C,IAAIiD,SAAS,CAAC,GAAGH,UAAAA,CAAAA,GAAc9C,GAAAA;AAEnE,YAAA,IAAIkD,cAAAA,GAAiB,CAAA;YACrB,IAAI;gBACA,IAAI5C,KAAAA,CAAME,UAAU,KAAKC,SAAAA,EAAW;AAChCyC,oBAAAA,cAAAA,GAAiBjD,KAAKC,SAAS,CAACI,KAAAA,CAAME,UAAU,EAAEb,MAAM;AAC5D,gBAAA;AACJ,YAAA,CAAA,CAAE,OAAM;;AAER,YAAA;AAEA,YAAA,IAAIW,MAAMI,eAAe,GAAG,KAAK,CAACJ,KAAAA,CAAMO,UAAU,EAAE;AAChDgC,gBAAAA,cAAAA,EAAAA;AACJ,YAAA;YAEAD,UAAAA,IAAcM,cAAAA;AACdP,YAAAA,OAAAA,CAAQQ,IAAI,CAAC;AACTnD,gBAAAA,GAAAA;AACAgD,gBAAAA,SAAAA;AACAtC,gBAAAA,eAAAA,EAAiBJ,MAAMI,eAAe;gBACtC0C,aAAAA,EAAe9C,KAAAA,CAAMK,SAAS,CAAC0C,IAAI;AACnCxC,gBAAAA,UAAAA,EAAYP,MAAMO,UAAU;gBAC5ByC,SAAAA,EAAWhD,KAAAA,CAAME,UAAU,KAAKC,SAAAA;AAChCyC,gBAAAA;AACJ,aAAA,CAAA;AACJ,QAAA;QAEA,OAAO;AACHK,YAAAA,OAAAA,EAASV,cAAAA,KAAmB,CAAA;AAC5BW,YAAAA,UAAAA,EAAY,IAAI,CAACtE,QAAQ,CAACmE,IAAI;YAC9BH,cAAAA,EAAgBN,UAAAA;AAChBD,YAAAA;AACJ,SAAA;AACJ,IAAA;AACJ;;;;"}
1
+ {"version":3,"file":"QueryInstanceCache.js","sources":["../../../queries/QueryInstanceCache.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { QueryResultWithState } from './QueryResultWithState';\nimport { CacheDiagnostics, CacheEntryDiagnostics } from './ObservableQueryDiagnosticsSnapshot';\nimport { reconcileQueryData } from './reconcileQueryData';\n\n/**\n * Represents a key that uniquely identifies a query instance in the cache, based on the query type name and its serialized arguments.\n */\nexport type QueryCacheKey = string;\n\n/**\n * Callback invoked when the cached result for an entry changes.\n */\nexport type QueryCacheListener<TDataType> = (result: QueryResultWithState<TDataType>) => void;\n\n/**\n * Represents a single entry in the {@link QueryInstanceCache}.\n * @template TDataType The type of data returned by the query.\n */\nexport interface QueryCacheEntry<TDataType> {\n /**\n * The cached query instance.\n */\n readonly instance: unknown;\n\n /**\n * The last result received from the query, if any.\n */\n lastResult?: QueryResultWithState<TDataType>;\n\n /**\n * The number of active subscribers holding a reference to this entry.\n */\n subscriberCount: number;\n\n /**\n * Set of listener callbacks that are notified when {@link lastResult} changes.\n */\n readonly listeners: Set<QueryCacheListener<TDataType>>;\n\n /**\n * Cleanup function returned by the first subscriber that starts the query connection.\n * Called when the last subscriber releases the entry.\n */\n teardown?: () => void;\n\n /**\n * Whether an active subscription has been established for this entry.\n */\n subscribed: boolean;\n\n /**\n * Timer handle for deferred cleanup. Allows React StrictMode re-mounts (in any build\n * environment) to cancel the pending teardown so the connection is reused instead of\n * torn down and recreated.\n */\n pendingCleanup?: ReturnType<typeof setTimeout>;\n}\n\n/**\n * Provides a cache for query instances, keyed by query type and serialized arguments.\n *\n * Two callers requesting the same query type with identical arguments receive the same\n * cached instance and immediately see the last known result — without an additional\n * round trip to the server. When the last subscriber releases its reference the entry\n * is evicted.\n */\nexport class QueryInstanceCache {\n private readonly _entries = new Map<QueryCacheKey, QueryCacheEntry<unknown>>();\n private _pendingDispose?: ReturnType<typeof setTimeout>;\n\n /**\n * Initializes a new instance of {@link QueryInstanceCache}.\n * @param retentionMs How long in milliseconds to keep a cache entry alive after the last\n * subscriber releases it before evicting the subscription and cached data. A non-zero\n * value lets users navigate away and back without losing cached data. Defaults to\n * 30 000 ms. Pass 0 for immediate eviction.\n */\n constructor(private readonly _retentionMs: number = 30_000) {\n }\n\n /**\n * Builds the cache key for a query.\n * @param queryTypeName The stable type name for the query. Use the instance's {@link queryName}\n * (a hardcoded fully-qualified string in generated proxies) rather than {@link Function.name},\n * which is unstable under minification.\n * @param args Optional arguments supplied to the query.\n * @returns A stable string key.\n */\n buildKey(queryTypeName: string, args?: object): QueryCacheKey {\n if (!args || Object.keys(args).length === 0) {\n return `${queryTypeName}::`;\n }\n\n const sorted = Object.keys(args)\n .sort()\n .reduce<Record<string, unknown>>((accumulator, key) => {\n accumulator[key] = (args as Record<string, unknown>)[key];\n return accumulator;\n }, {});\n\n return `${queryTypeName}::${JSON.stringify(sorted)}`;\n }\n\n /**\n * Returns a cached instance for the given key, or creates a new one using the provided factory.\n * The subscriber count of the entry is incremented.\n * @template TInstance The type of the query instance.\n * @param key The cache key produced by {@link buildKey}.\n * @param factory A zero-argument factory that creates a fresh query instance when one is not yet cached.\n * @returns The cached (or newly created) instance and whether it was newly created.\n */\n getOrCreate<TInstance>(\n key: QueryCacheKey,\n factory: () => TInstance\n ): { instance: TInstance; isNew: boolean } {\n if (!this._entries.has(key)) {\n const entry: QueryCacheEntry<unknown> = {\n instance: factory(),\n lastResult: undefined,\n subscriberCount: 0,\n listeners: new Set(),\n subscribed: false,\n };\n\n this._entries.set(key, entry);\n return { instance: entry.instance as TInstance, isNew: true };\n }\n\n const entry = this._entries.get(key)!;\n return { instance: entry.instance as TInstance, isNew: false };\n }\n\n /**\n * Increments the active subscriber count for the given key.\n * If a deferred cleanup was pending (from a recent {@link release}),\n * it is cancelled so the existing subscription is reused.\n * Call from `useEffect` setup to pair with {@link release} in the cleanup.\n * @param key The cache key produced by {@link buildKey}.\n */\n acquire(key: QueryCacheKey): void {\n const entry = this._entries.get(key);\n\n if (entry) {\n if (entry.pendingCleanup !== undefined) {\n clearTimeout(entry.pendingCleanup);\n entry.pendingCleanup = undefined;\n }\n\n entry.subscriberCount++;\n }\n }\n\n /**\n * Returns the last cached result for the given key, or `undefined` if no result has been stored yet.\n * @template TDataType The type of data returned by the query.\n * @param key The cache key produced by {@link buildKey}.\n * @returns The last {@link QueryResultWithState}, or `undefined`.\n */\n getLastResult<TDataType>(key: QueryCacheKey): QueryResultWithState<TDataType> | undefined {\n return this._entries.get(key)?.lastResult as QueryResultWithState<TDataType> | undefined;\n }\n\n /**\n * Stores the most recent result for the given key and notifies all registered listeners.\n * @template TDataType The type of data returned by the query.\n * @param key The cache key produced by {@link buildKey}.\n * @param result The result to store.\n */\n setLastResult<TDataType>(key: QueryCacheKey, result: QueryResultWithState<TDataType>): void {\n const entry = this._entries.get(key);\n\n if (!entry) {\n return;\n }\n\n const previousResult = entry.lastResult as QueryResultWithState<TDataType> | undefined;\n\n // Reconcile the incoming payload against the one already held so items that did not actually\n // change keep their previous references. A full snapshot — which is what the server re-sends\n // whenever a subscription is re-established — otherwise arrives as all-new references and\n // makes every consumer treat every item as changed.\n const reconciled = previousResult !== undefined\n ? this.withReconciledData(previousResult, result)\n : result;\n\n entry.lastResult = reconciled as QueryResultWithState<unknown>;\n\n // Reconciliation returns the previous data reference when nothing changed, so identity is\n // enough to detect it. Only `data` and `isSuccess` matter here — the other fields (e.g.\n // changeSet) are ephemeral and do not affect what the user sees.\n if (\n previousResult !== undefined &&\n previousResult.isSuccess === reconciled.isSuccess &&\n previousResult.data === reconciled.data\n ) {\n return;\n }\n\n for (const listener of entry.listeners) {\n (listener as QueryCacheListener<TDataType>)(reconciled);\n }\n }\n\n /**\n * Returns the incoming result with its data reconciled against the previous result, or the\n * incoming result unchanged when reconciliation found nothing to carry over.\n * @template TDataType The type of data returned by the query.\n * @param previous The previously held result.\n * @param next The freshly received result.\n * @returns A result whose data preserves references for everything that did not change.\n */\n private withReconciledData<TDataType>(\n previous: QueryResultWithState<TDataType>,\n next: QueryResultWithState<TDataType>\n ): QueryResultWithState<TDataType> {\n const reconciledData = reconcileQueryData(previous.data, next.data);\n\n if (reconciledData === next.data) {\n return next;\n }\n\n return new QueryResultWithState<TDataType>(\n reconciledData,\n next.paging,\n next.isSuccess,\n next.isAuthorized,\n next.isValid,\n next.validationResults,\n next.hasExceptions,\n next.exceptionMessages,\n next.exceptionStackTrace,\n next.isPerforming,\n next.changeSet);\n }\n\n /**\n * Registers a listener that is invoked whenever the cached result for the given key changes.\n * @template TDataType The type of data returned by the query.\n * @param key The cache key produced by {@link buildKey}.\n * @param listener The callback to register.\n */\n addListener<TDataType>(key: QueryCacheKey, listener: QueryCacheListener<TDataType>): void {\n const entry = this._entries.get(key);\n\n if (entry) {\n entry.listeners.add(listener as QueryCacheListener<unknown>);\n }\n }\n\n /**\n * Removes a previously registered listener.\n * @template TDataType The type of data returned by the query.\n * @param key The cache key produced by {@link buildKey}.\n * @param listener The callback to remove.\n */\n removeListener<TDataType>(key: QueryCacheKey, listener: QueryCacheListener<TDataType>): void {\n const entry = this._entries.get(key);\n\n if (entry) {\n entry.listeners.delete(listener as QueryCacheListener<unknown>);\n }\n }\n\n /**\n * Stores a teardown function for the given key and marks the entry as subscribed.\n * Called automatically when the last subscriber releases the entry.\n * @param key The cache key produced by {@link buildKey}.\n * @param teardown Cleanup function that disconnects the underlying query subscription.\n */\n setTeardown(key: QueryCacheKey, teardown: () => void): void {\n const entry = this._entries.get(key);\n\n if (entry) {\n entry.teardown = teardown;\n entry.subscribed = true;\n }\n }\n\n /**\n * Returns whether an active subscription exists for the given key.\n * @param key The cache key to check.\n * @returns `true` if a subscription has been established; `false` otherwise.\n */\n isSubscribed(key: QueryCacheKey): boolean {\n return this._entries.get(key)?.subscribed ?? false;\n }\n\n /**\n * Decrements the subscriber count for the given key. When the count reaches zero, both teardown\n * and eviction are deferred by one microtask so that React StrictMode re-mounts — in any build\n * environment — can re-acquire the entry and cancel the cleanup before the timeout fires. This\n * prevents an unnecessary disconnect/reconnect cycle during the synthetic unmount/remount that\n * StrictMode performs.\n * @param key The cache key produced by {@link buildKey}.\n */\n release(key: QueryCacheKey): void {\n const entry = this._entries.get(key);\n\n if (entry) {\n entry.subscriberCount--;\n\n if (entry.subscriberCount <= 0) {\n // Defer both teardown and eviction. React StrictMode re-mounts can cancel by\n // calling acquire() before the timeout fires. A non-zero _retentionMs keeps the\n // entry alive so users navigating back quickly see cached data immediately.\n entry.pendingCleanup = setTimeout(() => {\n const current = this._entries.get(key);\n\n if (current && current.subscriberCount <= 0) {\n current.subscribed = false;\n current.teardown?.();\n current.teardown = undefined;\n current.pendingCleanup = undefined;\n this._entries.delete(key);\n }\n }, this._retentionMs);\n }\n }\n }\n\n /**\n * Returns whether an entry exists for the given key.\n * @param key The cache key to check.\n * @returns `true` if an entry exists; `false` otherwise.\n */\n has(key: QueryCacheKey): boolean {\n return this._entries.has(key);\n }\n\n /**\n * Tears down all active subscriptions and marks every entry as not subscribed,\n * but preserves entries, subscriber counts, and listeners. This allows\n * hooks whose effects re-run afterward to detect that the entry is no longer\n * subscribed and re-establish a fresh connection.\n *\n * Use this when the underlying transport must be replaced (e.g. after an\n * authentication change that requires new WebSocket connections with updated\n * credentials).\n */\n teardownAllSubscriptions(): void {\n for (const [, entry] of this._entries) {\n if (entry.pendingCleanup !== undefined) {\n clearTimeout(entry.pendingCleanup);\n entry.pendingCleanup = undefined;\n }\n\n entry.subscribed = false;\n entry.teardown?.();\n entry.teardown = undefined;\n }\n }\n\n /**\n * Immediately tears down all subscriptions, cancels any pending deferred cleanups,\n * and evicts all entries. Call when the owning component (e.g. the {@link Arc} provider)\n * unmounts permanently so that all query connections are closed synchronously.\n */\n dispose(): void {\n for (const [, entry] of this._entries) {\n if (entry.pendingCleanup !== undefined) {\n clearTimeout(entry.pendingCleanup);\n entry.pendingCleanup = undefined;\n }\n\n entry.subscribed = false;\n entry.teardown?.();\n entry.teardown = undefined;\n }\n\n this._entries.clear();\n }\n\n /**\n * Schedules a deferred {@link dispose} using {@code setTimeout(0)}.\n *\n * This allows React StrictMode re-mounts to call {@link cancelPendingDispose}\n * before the dispose fires, avoiding the destruction of cache entries that child\n * effects are about to re-acquire.\n *\n * If a deferred dispose is already pending, it is replaced.\n */\n deferDispose(): void {\n if (this._pendingDispose !== undefined) {\n clearTimeout(this._pendingDispose);\n }\n\n this._pendingDispose = setTimeout(() => {\n this._pendingDispose = undefined;\n this.dispose();\n }, 0);\n }\n\n /**\n * Cancels a pending deferred dispose scheduled by {@link deferDispose}.\n *\n * Call from the {@code useEffect} setup phase so that a StrictMode re-mount\n * prevents the synthetic unmount's deferred dispose from firing.\n */\n cancelPendingDispose(): void {\n if (this._pendingDispose !== undefined) {\n clearTimeout(this._pendingDispose);\n this._pendingDispose = undefined;\n }\n }\n\n /**\n * Returns a diagnostics snapshot of the current cache state.\n * @returns A {@link CacheDiagnostics} describing all entries.\n */\n getDiagnosticsSnapshot(): CacheDiagnostics {\n const entries: CacheEntryDiagnostics[] = [];\n let totalBytes = 0;\n let unhealthyCount = 0;\n\n for (const [key, entry] of this._entries) {\n const colonIndex = key.indexOf('::');\n const queryName = colonIndex >= 0 ? key.substring(0, colonIndex) : key;\n\n let estimatedBytes = 0;\n try {\n if (entry.lastResult !== undefined) {\n estimatedBytes = JSON.stringify(entry.lastResult).length;\n }\n } catch {\n // Ignore serialization errors\n }\n\n if (entry.subscriberCount > 0 && !entry.subscribed) {\n unhealthyCount++;\n }\n\n totalBytes += estimatedBytes;\n entries.push({\n key,\n queryName,\n subscriberCount: entry.subscriberCount,\n listenerCount: entry.listeners.size,\n subscribed: entry.subscribed,\n hasResult: entry.lastResult !== undefined,\n estimatedBytes,\n });\n }\n\n return {\n healthy: unhealthyCount === 0,\n entryCount: this._entries.size,\n estimatedBytes: totalBytes,\n entries,\n };\n }\n}\n"],"names":["QueryInstanceCache","_entries","Map","_pendingDispose","_retentionMs","buildKey","queryTypeName","args","Object","keys","length","sorted","sort","reduce","accumulator","key","JSON","stringify","getOrCreate","factory","has","entry","instance","lastResult","undefined","subscriberCount","listeners","Set","subscribed","set","isNew","get","acquire","pendingCleanup","clearTimeout","getLastResult","setLastResult","result","previousResult","reconciled","withReconciledData","isSuccess","data","listener","previous","next","reconciledData","reconcileQueryData","QueryResultWithState","paging","isAuthorized","isValid","validationResults","hasExceptions","exceptionMessages","exceptionStackTrace","isPerforming","changeSet","addListener","add","removeListener","delete","setTeardown","teardown","isSubscribed","release","setTimeout","current","teardownAllSubscriptions","dispose","clear","deferDispose","cancelPendingDispose","getDiagnosticsSnapshot","entries","totalBytes","unhealthyCount","colonIndex","indexOf","queryName","substring","estimatedBytes","push","listenerCount","size","hasResult","healthy","entryCount"],"mappings":"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"}
@@ -33,6 +33,7 @@ export declare class ServerSentEventHubConnection implements IObservableQueryHub
33
33
  private clearConnectTimeout;
34
34
  private handleMessage;
35
35
  private handleConnected;
36
+ private applyServerKeepAliveInterval;
36
37
  private handleQueryResult;
37
38
  private handleUnauthorized;
38
39
  private sendSubscribe;
@@ -1 +1 @@
1
- {"version":3,"file":"ServerSentEventHubConnection.d.ts","sourceRoot":"","sources":["../../../queries/ServerSentEventHubConnection.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtD,OAAO,EAA8B,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAoB3F,qBAAa,4BAA6B,YAAW,6BAA6B;IAwB1E,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAE9B,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO;IA7B5B,OAAO,CAAC,YAAY,CAAC,CAAc;IACnC,OAAO,CAAC,aAAa,CAAC,CAAS;IAC/B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,qBAAqB,CAA8C;IAC3E,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,eAAe,CAAgB;IACvC,OAAO,CAAC,oBAAoB,CAAC,CAAgC;IAC7D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;IAcpD,YACqB,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,MAAM,EACvB,aAAa,EAAE,MAAM,EACtC,mBAAmB,GAAE,MAAc,EAClB,iBAAiB,GAAE,MAAc,EACjC,OAAO,GAAE,gBAAwC,EAkBrE;IAGD,IAAI,UAAU,IAAI,MAAM,CAEvB;IAGD,IAAI,WAAW,IAAI,OAAO,CAEzB;IAGD,IAAI,eAAe,IAAI,MAAM,CAE5B;IAGD,IAAI,cAAc,IAAI,MAAM,CAG3B;IAGD,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,YAAY,CAAC,GAAG,CAAC,GAAG,IAAI,CAY1F;IAGD,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAWjC;IAGD,OAAO,IAAI,IAAI,CAUd;IAED,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,KAAK;IAmBb,OAAO,CAAC,eAAe;IAwCvB,OAAO,CAAC,SAAS;IAwBjB,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,aAAa;IA2BrB,OAAO,CAAC,eAAe;IAcvB,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,aAAa;IA+CrB,OAAO,CAAC,eAAe;CAkB1B"}
1
+ {"version":3,"file":"ServerSentEventHubConnection.d.ts","sourceRoot":"","sources":["../../../queries/ServerSentEventHubConnection.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtD,OAAO,EAA8B,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AA6B3F,qBAAa,4BAA6B,YAAW,6BAA6B;IAyB1E,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAE9B,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO;IA9B5B,OAAO,CAAC,YAAY,CAAC,CAAc;IACnC,OAAO,CAAC,aAAa,CAAC,CAAS;IAC/B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,qBAAqB,CAA8C;IAC3E,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,eAAe,CAAgB;IACvC,OAAO,CAAC,oBAAoB,CAAC,CAAgC;IAC7D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;IAepD,YACqB,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,MAAM,EACvB,aAAa,EAAE,MAAM,EACtC,mBAAmB,GAAE,MAAc,EAClB,iBAAiB,GAAE,MAAc,EACjC,OAAO,GAAE,gBAAwC,EAoBrE;IAGD,IAAI,UAAU,IAAI,MAAM,CAEvB;IAGD,IAAI,WAAW,IAAI,OAAO,CAEzB;IAGD,IAAI,eAAe,IAAI,MAAM,CAE5B;IAGD,IAAI,cAAc,IAAI,MAAM,CAG3B;IAGD,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,YAAY,CAAC,GAAG,CAAC,GAAG,IAAI,CAY1F;IAGD,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAWjC;IAGD,OAAO,IAAI,IAAI,CAUd;IAED,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,KAAK;IAmBb,OAAO,CAAC,eAAe;IAwCvB,OAAO,CAAC,SAAS;IAwBjB,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,aAAa;IA2BrB,OAAO,CAAC,eAAe;IAwBvB,OAAO,CAAC,4BAA4B;IAapC,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,aAAa;IA+CrB,OAAO,CAAC,eAAe;CAkB1B"}
@@ -6,6 +6,13 @@ import { HubMessageType } from './WebSocketHubConnection.js';
6
6
 
7
7
  // Copyright (c) Cratis. All rights reserved.
8
8
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
9
+ /**
10
+ * How many keep-alive intervals of silence are tolerated before the connection is considered dead.
11
+ *
12
+ * The server guarantees a message every interval, so this is pure slack for latency and jitter.
13
+ * It must stay above 1 — a threshold at or below the server's own cadence makes the client and
14
+ * server timers race, and the client tears down connections the server considers healthy.
15
+ */ const IDLE_THRESHOLD_FACTOR = 2;
9
16
  /**
10
17
  * A multiplexed SSE hub connection that uses EventSource for server→client streaming
11
18
  * and fetch POST requests for client→server subscribe/unsubscribe commands.
@@ -38,8 +45,9 @@ import { HubMessageType } from './WebSocketHubConnection.js';
38
45
  * @param {string} _subscribeUrl The subscribe POST endpoint URL.
39
46
  * @param {string} _unsubscribeUrl The unsubscribe POST endpoint URL.
40
47
  * @param {string} _microservice The microservice name to pass as a query argument.
41
- * @param {number} keepAliveIntervalMs How long without any server message before the connection
42
- * is considered stale and a reconnect is forced (default: 30 000 ms).
48
+ * @param {number} keepAliveIntervalMs The keep-alive cadence to assume until the server advertises
49
+ * its own on the {@link HubMessageType.Connected} message (default: 30 000 ms). The connection is
50
+ * considered stale after {@link IDLE_THRESHOLD_FACTOR} times this without any server message.
43
51
  * @param {number} connectTimeoutMs How long to wait for the {@link HubMessageType.Connected}
44
52
  * message after the HTTP connection opens before giving up and retrying (default: 15 000 ms).
45
53
  * @param {IReconnectPolicy} _policy The reconnect policy to use (default: {@link ReconnectPolicy}).
@@ -54,18 +62,20 @@ import { HubMessageType } from './WebSocketHubConnection.js';
54
62
  // inactivity — if the server stops sending messages (including its own keep-alive
55
63
  // pings) for the entire idle threshold, the connection is stale and we reconnect.
56
64
  //
57
- // The idle threshold is set to 1.5× the check interval so the server's keep-alive
58
- // ping (which fires on the same cadence) has time to arrive over the network before
59
- // the client declares the connection dead. Without this tolerance the client's timer
60
- // and the server's timer race the client often fires first and reconnects
61
- // unnecessarily.
62
- const idleThresholdMs = Math.round(keepAliveIntervalMs * 1.5);
65
+ // The server guarantees a message at least every keep-alive interval, so the threshold
66
+ // only has to absorb network latency, timer jitter and server-side scheduling hiccups.
67
+ // A hard TCP drop surfaces immediately through `onerror`, so this watchdog only needs to
68
+ // catch silent black-holes that makes a generous tolerance strictly better than a tight
69
+ // one, which would tear down healthy connections.
70
+ //
71
+ // The interval below is only the starting assumption; the server advertises its real
72
+ // cadence on the Connected message and {@link handleConnected} reconfigures from it.
63
73
  this._keepAlive = new HubConnectionKeepAlive(keepAliveIntervalMs, ()=>{
64
74
  if (!this._disconnected && this._subscriptions.size > 0) {
65
- console.warn(`SSE hub: no messages received for ${idleThresholdMs}ms, reconnecting '${this._sseUrl}'`);
75
+ console.warn(`SSE hub: no messages received for ${this._keepAlive.idleThresholdMs}ms, reconnecting '${this._sseUrl}'`);
66
76
  this.reconnect();
67
77
  }
68
- }, idleThresholdMs);
78
+ }, keepAliveIntervalMs * IDLE_THRESHOLD_FACTOR);
69
79
  }
70
80
  /** @inheritdoc */ get queryCount() {
71
81
  return this._subscriptions.size;
@@ -230,12 +240,30 @@ import { HubMessageType } from './WebSocketHubConnection.js';
230
240
  console.log(`SSE hub: connected with id '${this._connectionId}'`);
231
241
  // Connected message arrived — cancel the connect timeout.
232
242
  this.clearConnectTimeout();
243
+ this.applyServerKeepAliveInterval(message.keepAliveIntervalMs);
233
244
  // Send all pending subscriptions now that we have a connection ID.
234
245
  for (const [queryId, sub] of this._pendingSubscriptions){
235
246
  this.sendSubscribe(queryId, sub.request);
236
247
  }
237
248
  this._pendingSubscriptions.clear();
238
249
  }
250
+ /**
251
+ * Align the idle watchdog with the keep-alive cadence the server actually runs on.
252
+ *
253
+ * Without this the client assumes the default interval, so a server configured with a longer
254
+ * interval — or with keep-alive switched off entirely — would look silent and be reconnected
255
+ * on a loop even though it is perfectly healthy.
256
+ * @param {number | undefined} serverIntervalMs The interval advertised by the server, if any.
257
+ */ applyServerKeepAliveInterval(serverIntervalMs) {
258
+ if (serverIntervalMs === undefined) return;
259
+ // Keep-alive disabled server-side: silence is expected, so watching for it would guarantee
260
+ // an endless reconnect loop. Hard drops still surface through `onerror`.
261
+ if (serverIntervalMs <= 0) {
262
+ this._keepAlive.stop();
263
+ return;
264
+ }
265
+ this._keepAlive.reconfigure(serverIntervalMs, serverIntervalMs * IDLE_THRESHOLD_FACTOR);
266
+ }
239
267
  handleQueryResult(message) {
240
268
  if (!message.queryId) return;
241
269
  const sub = this._subscriptions.get(message.queryId);