@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
@@ -0,0 +1,31 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { reconcileQueryData } from '../../reconcileQueryData';
5
+
6
+ describe('when reconciling a single object that did not change', () => {
7
+ const previous = { id: 'a', name: 'First' };
8
+ const next = { id: 'a', name: 'First' };
9
+
10
+ const result = reconcileQueryData(previous, next);
11
+
12
+ it('should return the previous object', () => result.should.equal(previous));
13
+ });
14
+
15
+ describe('when reconciling a single object that changed', () => {
16
+ const previous = { id: 'a', name: 'First' };
17
+ const next = { id: 'a', name: 'Changed' };
18
+
19
+ const result = reconcileQueryData(previous, next);
20
+
21
+ it('should return the new object', () => result.should.equal(next));
22
+ });
23
+
24
+ describe('when reconciling items that carry no identity', () => {
25
+ const previous = [{ name: 'First' }, { name: 'Second' }];
26
+ const next = [{ name: 'First' }, { name: 'Second' }];
27
+
28
+ const result = reconcileQueryData(previous, next);
29
+
30
+ it('should fall back to positional matching and preserve the collection', () => result.should.equal(previous));
31
+ });
package/queries/index.ts CHANGED
@@ -35,6 +35,7 @@ export * from './ObservableQuerySubscription';
35
35
  export * from './WebSocketMessage';
36
36
  export * from './QueryTransportMethod';
37
37
  export * from './QueryInstanceCache';
38
+ export * from './reconcileQueryData';
38
39
  export * from './IQueryProvider';
39
40
  export * from './QueryProvider';
40
41
  export * from './QueryValidator';
@@ -0,0 +1,98 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { deepEqual } from '../deepEqual';
5
+
6
+ /**
7
+ * Resolves the conventional identity of an item so it can be matched against its previous version.
8
+ *
9
+ * Uses the `id` property, the same strategy the server-side change-set computation applies, and
10
+ * reduces concept-style identities (objects with a meaningful `toString()`) to a comparable value.
11
+ * @param {unknown} item The item to resolve identity for.
12
+ * @returns {unknown} The identity value, or `undefined` when the item carries no usable identity.
13
+ */
14
+ function getIdentity(item: unknown): unknown {
15
+ const id = (item as Record<string, unknown>)?.id;
16
+
17
+ if (id === null || id === undefined) {
18
+ return undefined;
19
+ }
20
+
21
+ if (typeof id === 'object') {
22
+ const stringValue = id.toString();
23
+ return stringValue !== '[object Object]' ? stringValue : JSON.stringify(id);
24
+ }
25
+
26
+ return id;
27
+ }
28
+
29
+ /**
30
+ * Reconciles a full payload against the previous one, carrying over the previous object references
31
+ * for every item that did not actually change.
32
+ *
33
+ * Observable queries re-deliver a complete snapshot whenever a subscription is (re-)established.
34
+ * Deserializing that snapshot produces entirely new object references, so consumers that compare by
35
+ * reference — memoized components, effect dependencies — treat every item as changed even when the
36
+ * payload is identical to what they already display. Reconciling restores referential stability:
37
+ * unchanged items keep their previous reference, and when nothing changed at all the previous
38
+ * payload itself is returned so callers can detect "no change" with a single `===`.
39
+ *
40
+ * Items are matched by their `id` when present, falling back to position for identity-less items.
41
+ * @template T The payload type.
42
+ * @param {T} previous The payload currently held.
43
+ * @param {T} next The freshly received payload.
44
+ * @returns {T} `previous` when nothing changed; otherwise the new payload with unchanged items carried over by reference.
45
+ */
46
+ export function reconcileQueryData<T>(previous: T, next: T): T {
47
+ if (previous === next) {
48
+ return previous;
49
+ }
50
+
51
+ if (Array.isArray(previous) && Array.isArray(next)) {
52
+ return reconcileArray(previous, next) as T;
53
+ }
54
+
55
+ return deepEqual(previous, next) ? previous : next;
56
+ }
57
+
58
+ /**
59
+ * Reconciles two arrays element by element, preserving previous references for unchanged items.
60
+ * @param {unknown[]} previous The array currently held.
61
+ * @param {unknown[]} next The freshly received array.
62
+ * @returns {unknown[]} `previous` when the arrays are equivalent; otherwise a new array reusing unchanged items.
63
+ */
64
+ function reconcileArray(previous: unknown[], next: unknown[]): unknown[] {
65
+ const previousByIdentity = new Map<unknown, unknown>();
66
+
67
+ for (const item of previous) {
68
+ const identity = getIdentity(item);
69
+
70
+ // Keep the first occurrence — duplicate identities are not a shape we can reconcile
71
+ // meaningfully, and preferring the first keeps the result deterministic.
72
+ if (identity !== undefined && !previousByIdentity.has(identity)) {
73
+ previousByIdentity.set(identity, item);
74
+ }
75
+ }
76
+
77
+ let changed = previous.length !== next.length;
78
+
79
+ const reconciled = next.map((item, index) => {
80
+ const identity = getIdentity(item);
81
+ const candidate = identity !== undefined ? previousByIdentity.get(identity) : previous[index];
82
+
83
+ if (candidate !== undefined && deepEqual(candidate, item)) {
84
+ // An unchanged item that moved still leaves the collection changed as a whole, but the
85
+ // item itself keeps its reference so only the ordering re-renders, not its content.
86
+ if (candidate !== previous[index]) {
87
+ changed = true;
88
+ }
89
+
90
+ return candidate;
91
+ }
92
+
93
+ changed = true;
94
+ return item;
95
+ });
96
+
97
+ return changed ? reconciled : previous;
98
+ }