@cratis/arc 20.27.2 → 20.33.1
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.
- package/dist/cjs/queries/IObservableQueryDiagnostics.d.ts +9 -0
- package/dist/cjs/queries/IObservableQueryDiagnostics.d.ts.map +1 -0
- package/dist/cjs/queries/IObservableQueryHubConnection.d.ts +1 -0
- package/dist/cjs/queries/IObservableQueryHubConnection.d.ts.map +1 -1
- package/dist/cjs/queries/ObservableQueryDiagnostics.d.ts +21 -0
- package/dist/cjs/queries/ObservableQueryDiagnostics.d.ts.map +1 -0
- package/dist/cjs/queries/ObservableQueryDiagnostics.js +87 -0
- package/dist/cjs/queries/ObservableQueryDiagnostics.js.map +1 -0
- package/dist/cjs/queries/ObservableQueryDiagnosticsSnapshot.d.ts +48 -0
- package/dist/cjs/queries/ObservableQueryDiagnosticsSnapshot.d.ts.map +1 -0
- package/dist/cjs/queries/ObservableQueryMultiplexer.d.ts +3 -0
- package/dist/cjs/queries/ObservableQueryMultiplexer.d.ts.map +1 -1
- package/dist/cjs/queries/ObservableQueryMultiplexer.js +11 -0
- package/dist/cjs/queries/ObservableQueryMultiplexer.js.map +1 -1
- package/dist/cjs/queries/QueryInstanceCache.d.ts +2 -0
- package/dist/cjs/queries/QueryInstanceCache.d.ts.map +1 -1
- package/dist/cjs/queries/QueryInstanceCache.js +36 -0
- package/dist/cjs/queries/QueryInstanceCache.js.map +1 -1
- package/dist/cjs/queries/ServerSentEventHubConnection.d.ts +1 -0
- package/dist/cjs/queries/ServerSentEventHubConnection.d.ts.map +1 -1
- package/dist/cjs/queries/ServerSentEventHubConnection.js +3 -0
- package/dist/cjs/queries/ServerSentEventHubConnection.js.map +1 -1
- package/dist/cjs/queries/WebSocketHubConnection.d.ts +1 -0
- package/dist/cjs/queries/WebSocketHubConnection.d.ts.map +1 -1
- package/dist/cjs/queries/WebSocketHubConnection.js +3 -0
- package/dist/cjs/queries/WebSocketHubConnection.js.map +1 -1
- package/dist/cjs/queries/for_ObservableQueryDiagnostics/when_tracking_owners.d.ts +2 -0
- package/dist/cjs/queries/for_ObservableQueryDiagnostics/when_tracking_owners.d.ts.map +1 -0
- package/dist/cjs/queries/index.d.ts +3 -0
- package/dist/cjs/queries/index.d.ts.map +1 -1
- package/dist/cjs/queries/index.js +3 -0
- package/dist/cjs/queries/index.js.map +1 -1
- package/dist/esm/queries/IObservableQueryDiagnostics.d.ts +9 -0
- package/dist/esm/queries/IObservableQueryDiagnostics.d.ts.map +1 -0
- package/dist/esm/queries/IObservableQueryDiagnostics.js +2 -0
- package/dist/esm/queries/IObservableQueryDiagnostics.js.map +1 -0
- package/dist/esm/queries/IObservableQueryHubConnection.d.ts +1 -0
- package/dist/esm/queries/IObservableQueryHubConnection.d.ts.map +1 -1
- package/dist/esm/queries/ObservableQueryDiagnostics.d.ts +21 -0
- package/dist/esm/queries/ObservableQueryDiagnostics.d.ts.map +1 -0
- package/dist/esm/queries/ObservableQueryDiagnostics.js +85 -0
- package/dist/esm/queries/ObservableQueryDiagnostics.js.map +1 -0
- package/dist/esm/queries/ObservableQueryDiagnosticsSnapshot.d.ts +48 -0
- package/dist/esm/queries/ObservableQueryDiagnosticsSnapshot.d.ts.map +1 -0
- package/dist/esm/queries/ObservableQueryDiagnosticsSnapshot.js +2 -0
- package/dist/esm/queries/ObservableQueryDiagnosticsSnapshot.js.map +1 -0
- package/dist/esm/queries/ObservableQueryMultiplexer.d.ts +3 -0
- package/dist/esm/queries/ObservableQueryMultiplexer.d.ts.map +1 -1
- package/dist/esm/queries/ObservableQueryMultiplexer.js +11 -1
- package/dist/esm/queries/ObservableQueryMultiplexer.js.map +1 -1
- package/dist/esm/queries/QueryInstanceCache.d.ts +2 -0
- package/dist/esm/queries/QueryInstanceCache.d.ts.map +1 -1
- package/dist/esm/queries/QueryInstanceCache.js +36 -0
- package/dist/esm/queries/QueryInstanceCache.js.map +1 -1
- package/dist/esm/queries/ServerSentEventHubConnection.d.ts +1 -0
- package/dist/esm/queries/ServerSentEventHubConnection.d.ts.map +1 -1
- package/dist/esm/queries/ServerSentEventHubConnection.js +3 -0
- package/dist/esm/queries/ServerSentEventHubConnection.js.map +1 -1
- package/dist/esm/queries/WebSocketHubConnection.d.ts +1 -0
- package/dist/esm/queries/WebSocketHubConnection.d.ts.map +1 -1
- package/dist/esm/queries/WebSocketHubConnection.js +3 -0
- package/dist/esm/queries/WebSocketHubConnection.js.map +1 -1
- package/dist/esm/queries/for_ObservableQueryDiagnostics/when_tracking_owners.d.ts +2 -0
- package/dist/esm/queries/for_ObservableQueryDiagnostics/when_tracking_owners.d.ts.map +1 -0
- package/dist/esm/queries/for_ObservableQueryDiagnostics/when_tracking_owners.js +33 -0
- package/dist/esm/queries/for_ObservableQueryDiagnostics/when_tracking_owners.js.map +1 -0
- package/dist/esm/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_changed_cache_key.js +1 -0
- package/dist/esm/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_changed_cache_key.js.map +1 -1
- package/dist/esm/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_new_cache_key.js +1 -1
- package/dist/esm/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_new_cache_key.js.map +1 -1
- package/dist/esm/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_an_explicit_size.js +1 -1
- package/dist/esm/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_an_explicit_size.js.map +1 -1
- package/dist/esm/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_the_same_cache_key.js +1 -1
- package/dist/esm/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_the_same_cache_key.js.map +1 -1
- package/dist/esm/queries/index.d.ts +3 -0
- package/dist/esm/queries/index.d.ts.map +1 -1
- package/dist/esm/queries/index.js +2 -1
- package/dist/esm/queries/index.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/queries/IObservableQueryDiagnostics.ts +40 -0
- package/queries/IObservableQueryHubConnection.ts +5 -0
- package/queries/ObservableQueryDiagnostics.ts +123 -0
- package/queries/ObservableQueryDiagnosticsSnapshot.ts +112 -0
- package/queries/ObservableQueryMultiplexer.ts +20 -0
- package/queries/QueryInstanceCache.ts +47 -0
- package/queries/ServerSentEventHubConnection.ts +5 -0
- package/queries/WebSocketHubConnection.ts +7 -0
- package/queries/for_ObservableQueryDiagnostics/when_tracking_owners.ts +49 -0
- package/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_changed_cache_key.ts +2 -1
- package/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_new_cache_key.ts +1 -1
- package/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_an_explicit_size.ts +1 -1
- package/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_the_same_cache_key.ts +1 -1
- package/queries/index.ts +3 -0
package/package.json
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
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 { Observable } from 'rxjs';
|
|
5
|
+
import { ObservableQueryDiagnosticsSnapshot } from './ObservableQueryDiagnosticsSnapshot';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Contract for the observable query diagnostics service.
|
|
9
|
+
*
|
|
10
|
+
* The service aggregates runtime state from the query cache, the multiplexer connection pool,
|
|
11
|
+
* and (optionally) component ownership maps into a single snapshot. Snapshots can be polled
|
|
12
|
+
* via {@link getSnapshot} or observed through {@link snapshots$}.
|
|
13
|
+
*/
|
|
14
|
+
export interface IObservableQueryDiagnostics {
|
|
15
|
+
/**
|
|
16
|
+
* Returns the current diagnostics snapshot.
|
|
17
|
+
*/
|
|
18
|
+
getSnapshot(): ObservableQueryDiagnosticsSnapshot;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Stream of diagnostics snapshots.
|
|
22
|
+
*/
|
|
23
|
+
readonly snapshots$: Observable<ObservableQueryDiagnosticsSnapshot>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Associates a human-readable owner label with a cache key.
|
|
27
|
+
* Used by opt-in hook options (`owner?: string`) to identify which component
|
|
28
|
+
* is using which query instance.
|
|
29
|
+
* @param cacheKey The cache key to tag.
|
|
30
|
+
* @param owner A descriptive label for the owning component (e.g. component name).
|
|
31
|
+
*/
|
|
32
|
+
beginTracking(cacheKey: string, owner: string): void;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Removes the ownership association for the given cache key.
|
|
36
|
+
* Should be called when the component unmounts or the query hook cleans up.
|
|
37
|
+
* @param cacheKey The cache key to un-tag.
|
|
38
|
+
*/
|
|
39
|
+
endTracking(cacheKey: string): void;
|
|
40
|
+
}
|
|
@@ -19,6 +19,11 @@ export interface IObservableQueryHubConnection {
|
|
|
19
19
|
*/
|
|
20
20
|
readonly queryCount: number;
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Gets whether the physical connection is currently established.
|
|
24
|
+
*/
|
|
25
|
+
readonly isConnected: boolean;
|
|
26
|
+
|
|
22
27
|
/**
|
|
23
28
|
* Gets the latency of the last ping/pong sequence in milliseconds.
|
|
24
29
|
*/
|
|
@@ -0,0 +1,123 @@
|
|
|
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 { Observable, Subject } from 'rxjs';
|
|
5
|
+
import { QueryInstanceCache } from './QueryInstanceCache';
|
|
6
|
+
import { ObservableQueryMultiplexer } from './ObservableQueryMultiplexer';
|
|
7
|
+
import { IObservableQueryDiagnostics } from './IObservableQueryDiagnostics';
|
|
8
|
+
import {
|
|
9
|
+
ObservableQueryDiagnosticsSnapshot,
|
|
10
|
+
MultiplexerDiagnostics,
|
|
11
|
+
TransportDiagnostics,
|
|
12
|
+
HealthDiagnostics,
|
|
13
|
+
OwnershipDiagnostics,
|
|
14
|
+
} from './ObservableQueryDiagnosticsSnapshot';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Implements the {@link IObservableQueryDiagnostics} contract by collecting live state from
|
|
18
|
+
* the {@link QueryInstanceCache} and the shared {@link ObservableQueryMultiplexer}.
|
|
19
|
+
*/
|
|
20
|
+
export class ObservableQueryDiagnostics implements IObservableQueryDiagnostics {
|
|
21
|
+
private readonly _snapshots = new Subject<ObservableQueryDiagnosticsSnapshot>();
|
|
22
|
+
private readonly _ownersByKey = new Map<string, string>();
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Initializes a new instance of {@link ObservableQueryDiagnostics}.
|
|
26
|
+
* @param _cache The {@link QueryInstanceCache} whose state will be reflected.
|
|
27
|
+
* @param _getMultiplexer Factory that returns the current shared multiplexer, or {@code undefined}.
|
|
28
|
+
* @param _getTransportConfig Factory that returns the current transport diagnostics.
|
|
29
|
+
*/
|
|
30
|
+
constructor(
|
|
31
|
+
private readonly _cache: QueryInstanceCache,
|
|
32
|
+
private readonly _getMultiplexer: () => ObservableQueryMultiplexer | undefined,
|
|
33
|
+
private readonly _getTransportConfig: () => TransportDiagnostics,
|
|
34
|
+
) {}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Stream of diagnostics snapshots.
|
|
38
|
+
*/
|
|
39
|
+
get snapshots$(): Observable<ObservableQueryDiagnosticsSnapshot> {
|
|
40
|
+
return this._snapshots.asObservable();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** @inheritdoc */
|
|
44
|
+
getSnapshot(): ObservableQueryDiagnosticsSnapshot {
|
|
45
|
+
const snapshot = this._createSnapshot();
|
|
46
|
+
this._publishSnapshot(snapshot);
|
|
47
|
+
return snapshot;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** @inheritdoc */
|
|
51
|
+
beginTracking(cacheKey: string, owner: string): void {
|
|
52
|
+
this._ownersByKey.set(cacheKey, owner);
|
|
53
|
+
this._publishSnapshot(this._createSnapshot());
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** @inheritdoc */
|
|
57
|
+
endTracking(cacheKey: string): void {
|
|
58
|
+
this._ownersByKey.delete(cacheKey);
|
|
59
|
+
this._publishSnapshot(this._createSnapshot());
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private _buildMultiplexerDiagnostics(): MultiplexerDiagnostics {
|
|
63
|
+
const mux = this._getMultiplexer();
|
|
64
|
+
if (!mux) {
|
|
65
|
+
return {
|
|
66
|
+
isConnected: false,
|
|
67
|
+
configuredConnectionCount: 0,
|
|
68
|
+
effectiveConnectionCount: 0,
|
|
69
|
+
activeConnectionCount: 0,
|
|
70
|
+
connections: [],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const connections = mux.getConnectionsSnapshot();
|
|
75
|
+
const activeConnectionCount = connections.filter(c => c.isConnected).length;
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
isConnected: activeConnectionCount > 0,
|
|
79
|
+
configuredConnectionCount: connections.length,
|
|
80
|
+
effectiveConnectionCount: connections.length,
|
|
81
|
+
activeConnectionCount,
|
|
82
|
+
connections,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private _createSnapshot(): ObservableQueryDiagnosticsSnapshot {
|
|
87
|
+
const transport = this._getTransportConfig();
|
|
88
|
+
const multiplexer = this._buildMultiplexerDiagnostics();
|
|
89
|
+
const cache = this._cache.getDiagnosticsSnapshot();
|
|
90
|
+
|
|
91
|
+
const ownersByQueryKey: Record<string, string> = {};
|
|
92
|
+
const queriesByOwner: Record<string, string[]> = {};
|
|
93
|
+
|
|
94
|
+
for (const [key, owner] of this._ownersByKey) {
|
|
95
|
+
ownersByQueryKey[key] = owner;
|
|
96
|
+
if (!queriesByOwner[owner]) {
|
|
97
|
+
queriesByOwner[owner] = [];
|
|
98
|
+
}
|
|
99
|
+
queriesByOwner[owner].push(key);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const ownership: OwnershipDiagnostics = { ownersByQueryKey, queriesByOwner };
|
|
103
|
+
|
|
104
|
+
const disconnectedQueryCount = multiplexer.connections.filter(c => !c.isConnected).length;
|
|
105
|
+
const health: HealthDiagnostics = {
|
|
106
|
+
allQueriesConnected: multiplexer.isConnected && cache.healthy,
|
|
107
|
+
disconnectedQueryCount,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
health,
|
|
112
|
+
transport,
|
|
113
|
+
multiplexer,
|
|
114
|
+
cache,
|
|
115
|
+
ownership,
|
|
116
|
+
timestamp: new Date().toISOString(),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private _publishSnapshot(snapshot: ObservableQueryDiagnosticsSnapshot): void {
|
|
121
|
+
this._snapshots.next(snapshot);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
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
|
+
/**
|
|
5
|
+
* Describes the state of a single physical hub connection in the multiplexer.
|
|
6
|
+
*/
|
|
7
|
+
export interface MultiplexerConnectionState {
|
|
8
|
+
/** Zero-based slot index in the connection pool. */
|
|
9
|
+
index: number;
|
|
10
|
+
/** Whether the connection is currently established and receiving messages. */
|
|
11
|
+
isConnected: boolean;
|
|
12
|
+
/** Number of active query subscriptions routed through this connection. */
|
|
13
|
+
queryCount: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Multiplexer transport diagnostics.
|
|
18
|
+
*/
|
|
19
|
+
export interface MultiplexerDiagnostics {
|
|
20
|
+
/** Whether at least one connection in the pool is currently connected. */
|
|
21
|
+
isConnected: boolean;
|
|
22
|
+
/** Number of physical connections configured (from Arc props). */
|
|
23
|
+
configuredConnectionCount: number;
|
|
24
|
+
/** Number of connections actually created after SSE cap logic. */
|
|
25
|
+
effectiveConnectionCount: number;
|
|
26
|
+
/** Number of connections that are currently connected. */
|
|
27
|
+
activeConnectionCount: number;
|
|
28
|
+
/** Per-connection state for the current pool. */
|
|
29
|
+
connections: MultiplexerConnectionState[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A single entry in the query instance cache.
|
|
34
|
+
*/
|
|
35
|
+
export interface CacheEntryDiagnostics {
|
|
36
|
+
/** The full cache key (type name + serialized args). */
|
|
37
|
+
key: string;
|
|
38
|
+
/** Query name parsed from the key prefix. */
|
|
39
|
+
queryName: string;
|
|
40
|
+
/** Number of components currently holding a reference to this entry. */
|
|
41
|
+
subscriberCount: number;
|
|
42
|
+
/** Number of result listeners registered on this entry. */
|
|
43
|
+
listenerCount: number;
|
|
44
|
+
/** Whether an active server subscription has been established. */
|
|
45
|
+
subscribed: boolean;
|
|
46
|
+
/** Whether a cached result has been received at least once. */
|
|
47
|
+
hasResult: boolean;
|
|
48
|
+
/** Estimated size of the cached result in bytes (JSON.stringify length, 0 on error). */
|
|
49
|
+
estimatedBytes: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Query instance cache diagnostics.
|
|
54
|
+
*/
|
|
55
|
+
export interface CacheDiagnostics {
|
|
56
|
+
/** Whether all subscribed entries are connected (no entry has subscriberCount > 0 but subscribed == false). */
|
|
57
|
+
healthy: boolean;
|
|
58
|
+
/** Total number of entries in the cache (including unsubscribed retention entries). */
|
|
59
|
+
entryCount: number;
|
|
60
|
+
/** Total estimated size of all cached results in bytes. */
|
|
61
|
+
estimatedBytes: number;
|
|
62
|
+
/** Per-entry diagnostics. */
|
|
63
|
+
entries: CacheEntryDiagnostics[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Transport configuration diagnostics.
|
|
68
|
+
*/
|
|
69
|
+
export interface TransportDiagnostics {
|
|
70
|
+
/** The configured query transport method (e.g. ServerSentEvents or WebSocket). */
|
|
71
|
+
queryTransportMethod: string;
|
|
72
|
+
/** Whether direct per-query connections are used instead of the multiplexer hub. */
|
|
73
|
+
queryDirectMode: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Overall health summary.
|
|
78
|
+
*/
|
|
79
|
+
export interface HealthDiagnostics {
|
|
80
|
+
/** True when every entry with active subscribers is also subscribed. */
|
|
81
|
+
allQueriesConnected: boolean;
|
|
82
|
+
/** Number of entries that have subscribers but are not currently subscribed. */
|
|
83
|
+
disconnectedQueryCount: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Ownership map entry.
|
|
88
|
+
*/
|
|
89
|
+
export interface OwnershipDiagnostics {
|
|
90
|
+
/** Map of cache key → owner label (populated by opt-in owner tagging). */
|
|
91
|
+
ownersByQueryKey: Record<string, string>;
|
|
92
|
+
/** Map of owner label → cache keys. */
|
|
93
|
+
queriesByOwner: Record<string, string[]>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* A point-in-time snapshot of observable query diagnostics.
|
|
98
|
+
*/
|
|
99
|
+
export interface ObservableQueryDiagnosticsSnapshot {
|
|
100
|
+
/** Overall health summary. */
|
|
101
|
+
health: HealthDiagnostics;
|
|
102
|
+
/** Transport configuration. */
|
|
103
|
+
transport: TransportDiagnostics;
|
|
104
|
+
/** Multiplexer connection pool state. */
|
|
105
|
+
multiplexer: MultiplexerDiagnostics;
|
|
106
|
+
/** Query instance cache state. */
|
|
107
|
+
cache: CacheDiagnostics;
|
|
108
|
+
/** Opt-in component ownership mapping. */
|
|
109
|
+
ownership: OwnershipDiagnostics;
|
|
110
|
+
/** UTC timestamp when this snapshot was taken. */
|
|
111
|
+
timestamp: string;
|
|
112
|
+
}
|
|
@@ -6,6 +6,7 @@ import { IObservableQueryHubConnection } from './IObservableQueryHubConnection';
|
|
|
6
6
|
import { DataReceived } from './ObservableQueryConnection';
|
|
7
7
|
import { SubscriptionRequest } from './WebSocketHubConnection';
|
|
8
8
|
import { Globals } from '../Globals';
|
|
9
|
+
import { MultiplexerConnectionState } from './ObservableQueryDiagnosticsSnapshot';
|
|
9
10
|
|
|
10
11
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
11
12
|
|
|
@@ -137,6 +138,18 @@ export class ObservableQueryMultiplexer {
|
|
|
137
138
|
|
|
138
139
|
return request;
|
|
139
140
|
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Returns a snapshot of the per-connection state for diagnostics.
|
|
144
|
+
* @returns An array describing each slot in the connection pool.
|
|
145
|
+
*/
|
|
146
|
+
getConnectionsSnapshot(): MultiplexerConnectionState[] {
|
|
147
|
+
return this._connections.map((conn, index) => ({
|
|
148
|
+
index,
|
|
149
|
+
isConnected: conn.isConnected,
|
|
150
|
+
queryCount: conn.queryCount,
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
140
153
|
}
|
|
141
154
|
|
|
142
155
|
/**
|
|
@@ -217,3 +230,10 @@ export function resetSharedMultiplexer(): void {
|
|
|
217
230
|
_sharedMultiplexer = undefined;
|
|
218
231
|
_sharedMultiplexerKey = '';
|
|
219
232
|
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Returns the current shared multiplexer instance, or {@code undefined} if none has been created yet.
|
|
236
|
+
*/
|
|
237
|
+
export function getSharedMultiplexer(): ObservableQueryMultiplexer | undefined {
|
|
238
|
+
return _sharedMultiplexer;
|
|
239
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
3
|
|
|
4
4
|
import { QueryResultWithState } from './QueryResultWithState';
|
|
5
|
+
import { CacheDiagnostics, CacheEntryDiagnostics } from './ObservableQueryDiagnosticsSnapshot';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Represents a key that uniquely identifies a query instance in the cache, based on the query type name and its serialized arguments.
|
|
@@ -360,4 +361,50 @@ export class QueryInstanceCache {
|
|
|
360
361
|
this._pendingDispose = undefined;
|
|
361
362
|
}
|
|
362
363
|
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Returns a diagnostics snapshot of the current cache state.
|
|
367
|
+
* @returns A {@link CacheDiagnostics} describing all entries.
|
|
368
|
+
*/
|
|
369
|
+
getDiagnosticsSnapshot(): CacheDiagnostics {
|
|
370
|
+
const entries: CacheEntryDiagnostics[] = [];
|
|
371
|
+
let totalBytes = 0;
|
|
372
|
+
let unhealthyCount = 0;
|
|
373
|
+
|
|
374
|
+
for (const [key, entry] of this._entries) {
|
|
375
|
+
const colonIndex = key.indexOf('::');
|
|
376
|
+
const queryName = colonIndex >= 0 ? key.substring(0, colonIndex) : key;
|
|
377
|
+
|
|
378
|
+
let estimatedBytes = 0;
|
|
379
|
+
try {
|
|
380
|
+
if (entry.lastResult !== undefined) {
|
|
381
|
+
estimatedBytes = JSON.stringify(entry.lastResult).length;
|
|
382
|
+
}
|
|
383
|
+
} catch {
|
|
384
|
+
// Ignore serialization errors
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (entry.subscriberCount > 0 && !entry.subscribed) {
|
|
388
|
+
unhealthyCount++;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
totalBytes += estimatedBytes;
|
|
392
|
+
entries.push({
|
|
393
|
+
key,
|
|
394
|
+
queryName,
|
|
395
|
+
subscriberCount: entry.subscriberCount,
|
|
396
|
+
listenerCount: entry.listeners.size,
|
|
397
|
+
subscribed: entry.subscribed,
|
|
398
|
+
hasResult: entry.lastResult !== undefined,
|
|
399
|
+
estimatedBytes,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return {
|
|
404
|
+
healthy: unhealthyCount === 0,
|
|
405
|
+
entryCount: this._entries.size,
|
|
406
|
+
estimatedBytes: totalBytes,
|
|
407
|
+
entries,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
363
410
|
}
|
|
@@ -83,6 +83,11 @@ export class ServerSentEventHubConnection implements IObservableQueryHubConnecti
|
|
|
83
83
|
return this._subscriptions.size;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
/** @inheritdoc */
|
|
87
|
+
get isConnected(): boolean {
|
|
88
|
+
return this._connectionId !== undefined && this._eventSource?.readyState === EventSource.OPEN;
|
|
89
|
+
}
|
|
90
|
+
|
|
86
91
|
/** @inheritdoc */
|
|
87
92
|
get lastPingLatency(): number {
|
|
88
93
|
return this._lastPongLatency;
|
|
@@ -98,6 +98,13 @@ export class WebSocketHubConnection {
|
|
|
98
98
|
return this._subscriptions.size;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Gets whether the WebSocket connection is currently open.
|
|
103
|
+
*/
|
|
104
|
+
get isConnected(): boolean {
|
|
105
|
+
return this._socket?.readyState === WebSocket.OPEN;
|
|
106
|
+
}
|
|
107
|
+
|
|
101
108
|
/**
|
|
102
109
|
* Gets the latency of the last ping/pong sequence in milliseconds.
|
|
103
110
|
*/
|
|
@@ -0,0 +1,49 @@
|
|
|
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 { describe, it } from 'vitest';
|
|
5
|
+
import { ObservableQueryDiagnostics, QueryInstanceCache } from '../../queries';
|
|
6
|
+
|
|
7
|
+
describe('when tracking query owners', () => {
|
|
8
|
+
it('should publish diagnostics snapshots through an observable', () => {
|
|
9
|
+
const diagnostics = new ObservableQueryDiagnostics(
|
|
10
|
+
new QueryInstanceCache(0),
|
|
11
|
+
() => undefined,
|
|
12
|
+
() => ({ queryTransportMethod: 'ServerSentEvents', queryDirectMode: false })
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const snapshots: string[] = [];
|
|
16
|
+
diagnostics.snapshots$.subscribe(snapshot => snapshots.push(JSON.stringify(snapshot.ownership)));
|
|
17
|
+
|
|
18
|
+
diagnostics.beginTracking('cache-key', 'OrdersPage');
|
|
19
|
+
snapshots.should.have.lengthOf(1);
|
|
20
|
+
snapshots[0].should.contain('cache-key');
|
|
21
|
+
snapshots[0].should.contain('OrdersPage');
|
|
22
|
+
|
|
23
|
+
diagnostics.endTracking('cache-key');
|
|
24
|
+
snapshots.should.have.lengthOf(2);
|
|
25
|
+
snapshots[1].should.equal('{"ownersByQueryKey":{},"queriesByOwner":{}}');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should not dispatch browser events when publishing snapshots', () => {
|
|
29
|
+
const diagnostics = new ObservableQueryDiagnostics(
|
|
30
|
+
new QueryInstanceCache(0),
|
|
31
|
+
() => undefined,
|
|
32
|
+
() => ({ queryTransportMethod: 'ServerSentEvents', queryDirectMode: false })
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const globalWithWindow = globalThis as Record<string, unknown>;
|
|
36
|
+
const originalWindow = globalWithWindow.window;
|
|
37
|
+
const dispatchEvent = function(): never {
|
|
38
|
+
throw new Error('dispatchEvent should not be called');
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
globalWithWindow.window = { dispatchEvent };
|
|
43
|
+
diagnostics.beginTracking('cache-key', 'OrdersPage');
|
|
44
|
+
diagnostics.endTracking('cache-key');
|
|
45
|
+
} finally {
|
|
46
|
+
globalWithWindow.window = originalWindow;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
package/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_changed_cache_key.ts
CHANGED
|
@@ -20,7 +20,8 @@ describe('when getting or creating with a changed cache key', () => {
|
|
|
20
20
|
lastPingLatency: 0,
|
|
21
21
|
averageLatency: 0,
|
|
22
22
|
subscribe: () => {},
|
|
23
|
-
|
|
23
|
+
isConnected: false,
|
|
24
|
+
unsubscribe: () => {},
|
|
24
25
|
dispose: () => { disposedConnectionCount++; },
|
|
25
26
|
};
|
|
26
27
|
};
|
package/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_new_cache_key.ts
CHANGED
|
@@ -14,7 +14,7 @@ describe('when getting or creating with a new cache key', () => {
|
|
|
14
14
|
factoryCallCount = 0;
|
|
15
15
|
const factory = (): IObservableQueryHubConnection => {
|
|
16
16
|
factoryCallCount++;
|
|
17
|
-
return { queryCount: 0, lastPingLatency: 0, averageLatency: 0, subscribe: () => {}, unsubscribe: () => {}, dispose: () => {} };
|
|
17
|
+
return { queryCount: 0, lastPingLatency: 0, averageLatency: 0, isConnected: false, subscribe: () => {}, unsubscribe: () => {}, dispose: () => {} };
|
|
18
18
|
};
|
|
19
19
|
multiplexer = getOrCreateMultiplexer(factory, 'test-key', 1);
|
|
20
20
|
});
|
package/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_an_explicit_size.ts
CHANGED
|
@@ -14,7 +14,7 @@ describe('when getting or creating with an explicit size', () => {
|
|
|
14
14
|
factoryCallCount = 0;
|
|
15
15
|
const factory = (): IObservableQueryHubConnection => {
|
|
16
16
|
factoryCallCount++;
|
|
17
|
-
return { queryCount: 0, lastPingLatency: 0, averageLatency: 0, subscribe: () => {}, unsubscribe: () => {}, dispose: () => {} };
|
|
17
|
+
return { queryCount: 0, lastPingLatency: 0, averageLatency: 0, isConnected: false, subscribe: () => {}, unsubscribe: () => {}, dispose: () => {} };
|
|
18
18
|
};
|
|
19
19
|
multiplexer = getOrCreateMultiplexer(factory, 'test-key', 3);
|
|
20
20
|
});
|
package/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_the_same_cache_key.ts
CHANGED
|
@@ -15,7 +15,7 @@ describe('when getting or creating with the same cache key', () => {
|
|
|
15
15
|
factoryCallCount = 0;
|
|
16
16
|
const factory = (): IObservableQueryHubConnection => {
|
|
17
17
|
factoryCallCount++;
|
|
18
|
-
return { queryCount: 0, lastPingLatency: 0, averageLatency: 0, subscribe: () => {}, unsubscribe: () => {}, dispose: () => {} };
|
|
18
|
+
return { queryCount: 0, lastPingLatency: 0, averageLatency: 0, isConnected: false, subscribe: () => {}, unsubscribe: () => {}, dispose: () => {} };
|
|
19
19
|
};
|
|
20
20
|
first = getOrCreateMultiplexer(factory, 'test-key', 1);
|
|
21
21
|
second = getOrCreateMultiplexer(factory, 'test-key', 1);
|
package/queries/index.ts
CHANGED
|
@@ -35,4 +35,7 @@ export * from './QueryInstanceCache';
|
|
|
35
35
|
export * from './IQueryProvider';
|
|
36
36
|
export * from './QueryProvider';
|
|
37
37
|
export * from './QueryValidator';
|
|
38
|
+
export * from './IObservableQueryDiagnostics';
|
|
39
|
+
export * from './ObservableQueryDiagnostics';
|
|
40
|
+
export * from './ObservableQueryDiagnosticsSnapshot';
|
|
38
41
|
import '../validation';
|