@cratis/arc 20.27.2 → 20.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +90 -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 +88 -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 +17 -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 +127 -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 +24 -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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ObservableQueryMultiplexer.js","sources":["../../../queries/ObservableQueryMultiplexer.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 { IObservableQueryConnection } from './IObservableQueryConnection';\nimport { IObservableQueryHubConnection } from './IObservableQueryHubConnection';\nimport { DataReceived } from './ObservableQueryConnection';\nimport { SubscriptionRequest } from './WebSocketHubConnection';\nimport { Globals } from '../Globals';\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\n/**\n * The WebSocket demultiplexer route used when connecting through the multiplexed observable query endpoint.\n */\nexport const WS_HUB_ROUTE = '/.cratis/queries/ws';\n\n/**\n * Multiplexes multiple observable query subscriptions across a bounded pool of physical\n * connections (WebSocket or SSE) to the backend demultiplexer.\n *\n * Each pool slot is a single multiplexed connection. When a new subscription is requested,\n * the slot with the fewest active queries is chosen. This balances load across N physical\n * connections while keeping the total connection count bounded.\n */\nexport class ObservableQueryMultiplexer {\n private readonly _connections: IObservableQueryHubConnection[];\n private readonly _size: number;\n\n /**\n * Initializes a new {@link ObservableQueryMultiplexer}.\n * @param {number} size Number of physical connections (pool slots).\n * @param {() => IObservableQueryHubConnection} connectionFactory Factory function to create each connection.\n */\n constructor(size: number, connectionFactory: () => IObservableQueryHubConnection) {\n this._size = Math.max(1, size);\n this._connections = Array.from({ length: this._size }, () => connectionFactory());\n }\n\n /**\n * Gets the pool size.\n */\n get size(): number {\n return this._size;\n }\n\n /**\n * Gets the best available ping latency across all connections.\n */\n get lastPingLatency(): number {\n return this._connections.reduce((min, c) =>\n c.lastPingLatency > 0 && c.lastPingLatency < min ? c.lastPingLatency : min,\n Number.MAX_SAFE_INTEGER\n );\n }\n\n /**\n * Gets the average latency across all connections.\n */\n get averageLatency(): number {\n const active = this._connections.filter(c => c.averageLatency > 0);\n if (active.length === 0) return 0;\n return active.reduce((sum, c) => sum + c.averageLatency, 0) / active.length;\n }\n\n /**\n * Subscribe to a query through the multiplexer. Picks the least-loaded connection\n * and sends a Subscribe message on it.\n * @param {string} queryName Fully qualified backend query name.\n * @param {object} queryArguments Flat query arguments (incl. page, pageSize, sortBy, sortDirection).\n * @param {DataReceived<any>} callback Callback invoked for each result.\n * @returns A cleanup function that unsubscribes from the query.\n */\n subscribe(queryName: string, queryArguments: object | undefined, callback: DataReceived<any>): () => void {\n const request = this.buildSubscriptionRequest(queryName, queryArguments);\n const conn = this.leastLoaded();\n const queryId = this.generateQueryId();\n\n conn.subscribe(queryId, request, callback);\n\n return () => {\n conn.unsubscribe(queryId);\n };\n }\n\n /**\n * Dispose all connections in the multiplexer.\n */\n dispose(): void {\n for (const conn of this._connections) {\n conn.dispose();\n }\n }\n\n private leastLoaded(): IObservableQueryHubConnection {\n return this._connections.reduce((min, c) =>\n c.queryCount < min.queryCount ? c : min, this._connections[0]);\n }\n\n private generateQueryId(): string {\n // Use crypto.randomUUID when available, fall back to timestamp + random\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;\n }\n\n private buildSubscriptionRequest(queryName: string, args?: object): SubscriptionRequest {\n const request: SubscriptionRequest = { queryName };\n\n // Always include the transfer mode so the server knows which emission strategy to use.\n request.transferMode = Globals.observableQueryTransferMode;\n\n if (!args) return request;\n\n const a = args as Record<string, any>;\n const pagingAndSortingKeys = new Set(['page', 'pageSize', 'sortBy', 'sortDirection']);\n\n if (a.page !== undefined && a.page !== null) request.page = Number(a.page);\n if (a.pageSize !== undefined && a.pageSize !== null) request.pageSize = Number(a.pageSize);\n if (a.sortBy !== undefined && a.sortBy !== null) request.sortBy = String(a.sortBy);\n if (a.sortDirection !== undefined && a.sortDirection !== null) request.sortDirection = String(a.sortDirection);\n\n // Everything else goes into arguments as string key-value pairs\n const remaining: Record<string, string | null> = {};\n let hasRemaining = false;\n\n for (const [key, value] of Object.entries(a)) {\n if (pagingAndSortingKeys.has(key)) continue;\n if (value === undefined || value === null) continue;\n remaining[key] = String(value);\n hasRemaining = true;\n }\n\n if (hasRemaining) {\n request.arguments = remaining;\n }\n\n return request;\n }\n}\n\n/**\n * Wraps an {@link ObservableQueryMultiplexer} subscription as an {@link IObservableQueryConnection},\n * allowing the multiplexed transport to plug into the existing query subscription pipeline\n * without changes to callers.\n */\nexport class MultiplexedObservableQueryConnection<TDataType> implements IObservableQueryConnection<TDataType> {\n private _cleanup?: () => void;\n\n /**\n * Initializes a new {@link MultiplexedObservableQueryConnection}.\n * @param {ObservableQueryMultiplexer} multiplexer The shared multiplexer.\n * @param {string} queryName The fully qualified backend query name.\n */\n constructor(\n private readonly _pool: ObservableQueryMultiplexer,\n private readonly _queryName: string,\n ) {\n }\n\n /** @inheritdoc */\n get lastPingLatency(): number {\n return this._pool.lastPingLatency;\n }\n\n /** @inheritdoc */\n get averageLatency(): number {\n return this._pool.averageLatency;\n }\n\n /** @inheritdoc */\n connect(dataReceived: DataReceived<TDataType>, queryArguments?: object): void {\n this._cleanup = this._pool.subscribe(\n this._queryName,\n queryArguments,\n dataReceived as DataReceived<any>,\n );\n }\n\n /** @inheritdoc */\n disconnect(): void {\n this._cleanup?.();\n this._cleanup = undefined;\n }\n}\n\n// ----- Shared pool singleton management -----\n\nlet _sharedMultiplexer: ObservableQueryMultiplexer | undefined;\nlet _sharedMultiplexerKey = '';\n\n/**\n * Returns the shared {@link ObservableQueryMultiplexer}, creating or re-creating it when the\n * configuration (connection count, origin, base path, microservice, transport) changes.\n * @param {() => IObservableQueryHubConnection} connectionFactory Factory to create individual connections.\n * @param {string} cacheKey A string that identifies the current configuration for invalidation.\n * @param {number} size Number of physical connections to create. Defaults to {@link Globals.queryConnectionCount}.\n * @returns The shared multiplexer instance.\n */\nexport function getOrCreateMultiplexer(connectionFactory: () => IObservableQueryHubConnection, cacheKey: string, size: number = Globals.queryConnectionCount): ObservableQueryMultiplexer {\n if (_sharedMultiplexer && _sharedMultiplexerKey === cacheKey) {\n return _sharedMultiplexer;\n }\n\n _sharedMultiplexer?.dispose();\n _sharedMultiplexer = new ObservableQueryMultiplexer(size, connectionFactory);\n _sharedMultiplexerKey = cacheKey;\n return _sharedMultiplexer;\n}\n\n/**\n * Disposes and clears the shared multiplexer singleton.\n * Intended for use in test teardown to prevent state leakage across tests.\n */\nexport function resetSharedMultiplexer(): void {\n _sharedMultiplexer?.dispose();\n _sharedMultiplexer = undefined;\n _sharedMultiplexerKey = '';\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ObservableQueryMultiplexer.js","sources":["../../../queries/ObservableQueryMultiplexer.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 { IObservableQueryConnection } from './IObservableQueryConnection';\nimport { IObservableQueryHubConnection } from './IObservableQueryHubConnection';\nimport { DataReceived } from './ObservableQueryConnection';\nimport { SubscriptionRequest } from './WebSocketHubConnection';\nimport { Globals } from '../Globals';\nimport { MultiplexerConnectionState } from './ObservableQueryDiagnosticsSnapshot';\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\n/**\n * The WebSocket demultiplexer route used when connecting through the multiplexed observable query endpoint.\n */\nexport const WS_HUB_ROUTE = '/.cratis/queries/ws';\n\n/**\n * Multiplexes multiple observable query subscriptions across a bounded pool of physical\n * connections (WebSocket or SSE) to the backend demultiplexer.\n *\n * Each pool slot is a single multiplexed connection. When a new subscription is requested,\n * the slot with the fewest active queries is chosen. This balances load across N physical\n * connections while keeping the total connection count bounded.\n */\nexport class ObservableQueryMultiplexer {\n private readonly _connections: IObservableQueryHubConnection[];\n private readonly _size: number;\n\n /**\n * Initializes a new {@link ObservableQueryMultiplexer}.\n * @param {number} size Number of physical connections (pool slots).\n * @param {() => IObservableQueryHubConnection} connectionFactory Factory function to create each connection.\n */\n constructor(size: number, connectionFactory: () => IObservableQueryHubConnection) {\n this._size = Math.max(1, size);\n this._connections = Array.from({ length: this._size }, () => connectionFactory());\n }\n\n /**\n * Gets the pool size.\n */\n get size(): number {\n return this._size;\n }\n\n /**\n * Gets the best available ping latency across all connections.\n */\n get lastPingLatency(): number {\n return this._connections.reduce((min, c) =>\n c.lastPingLatency > 0 && c.lastPingLatency < min ? c.lastPingLatency : min,\n Number.MAX_SAFE_INTEGER\n );\n }\n\n /**\n * Gets the average latency across all connections.\n */\n get averageLatency(): number {\n const active = this._connections.filter(c => c.averageLatency > 0);\n if (active.length === 0) return 0;\n return active.reduce((sum, c) => sum + c.averageLatency, 0) / active.length;\n }\n\n /**\n * Subscribe to a query through the multiplexer. Picks the least-loaded connection\n * and sends a Subscribe message on it.\n * @param {string} queryName Fully qualified backend query name.\n * @param {object} queryArguments Flat query arguments (incl. page, pageSize, sortBy, sortDirection).\n * @param {DataReceived<any>} callback Callback invoked for each result.\n * @returns A cleanup function that unsubscribes from the query.\n */\n subscribe(queryName: string, queryArguments: object | undefined, callback: DataReceived<any>): () => void {\n const request = this.buildSubscriptionRequest(queryName, queryArguments);\n const conn = this.leastLoaded();\n const queryId = this.generateQueryId();\n\n conn.subscribe(queryId, request, callback);\n\n return () => {\n conn.unsubscribe(queryId);\n };\n }\n\n /**\n * Dispose all connections in the multiplexer.\n */\n dispose(): void {\n for (const conn of this._connections) {\n conn.dispose();\n }\n }\n\n private leastLoaded(): IObservableQueryHubConnection {\n return this._connections.reduce((min, c) =>\n c.queryCount < min.queryCount ? c : min, this._connections[0]);\n }\n\n private generateQueryId(): string {\n // Use crypto.randomUUID when available, fall back to timestamp + random\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;\n }\n\n private buildSubscriptionRequest(queryName: string, args?: object): SubscriptionRequest {\n const request: SubscriptionRequest = { queryName };\n\n // Always include the transfer mode so the server knows which emission strategy to use.\n request.transferMode = Globals.observableQueryTransferMode;\n\n if (!args) return request;\n\n const a = args as Record<string, any>;\n const pagingAndSortingKeys = new Set(['page', 'pageSize', 'sortBy', 'sortDirection']);\n\n if (a.page !== undefined && a.page !== null) request.page = Number(a.page);\n if (a.pageSize !== undefined && a.pageSize !== null) request.pageSize = Number(a.pageSize);\n if (a.sortBy !== undefined && a.sortBy !== null) request.sortBy = String(a.sortBy);\n if (a.sortDirection !== undefined && a.sortDirection !== null) request.sortDirection = String(a.sortDirection);\n\n // Everything else goes into arguments as string key-value pairs\n const remaining: Record<string, string | null> = {};\n let hasRemaining = false;\n\n for (const [key, value] of Object.entries(a)) {\n if (pagingAndSortingKeys.has(key)) continue;\n if (value === undefined || value === null) continue;\n remaining[key] = String(value);\n hasRemaining = true;\n }\n\n if (hasRemaining) {\n request.arguments = remaining;\n }\n\n return request;\n }\n\n /**\n * Returns a snapshot of the per-connection state for diagnostics.\n * @returns An array describing each slot in the connection pool.\n */\n getConnectionsSnapshot(): MultiplexerConnectionState[] {\n return this._connections.map((conn, index) => ({\n index,\n isConnected: conn.isConnected,\n queryCount: conn.queryCount,\n }));\n }\n}\n\n/**\n * Wraps an {@link ObservableQueryMultiplexer} subscription as an {@link IObservableQueryConnection},\n * allowing the multiplexed transport to plug into the existing query subscription pipeline\n * without changes to callers.\n */\nexport class MultiplexedObservableQueryConnection<TDataType> implements IObservableQueryConnection<TDataType> {\n private _cleanup?: () => void;\n\n /**\n * Initializes a new {@link MultiplexedObservableQueryConnection}.\n * @param {ObservableQueryMultiplexer} multiplexer The shared multiplexer.\n * @param {string} queryName The fully qualified backend query name.\n */\n constructor(\n private readonly _pool: ObservableQueryMultiplexer,\n private readonly _queryName: string,\n ) {\n }\n\n /** @inheritdoc */\n get lastPingLatency(): number {\n return this._pool.lastPingLatency;\n }\n\n /** @inheritdoc */\n get averageLatency(): number {\n return this._pool.averageLatency;\n }\n\n /** @inheritdoc */\n connect(dataReceived: DataReceived<TDataType>, queryArguments?: object): void {\n this._cleanup = this._pool.subscribe(\n this._queryName,\n queryArguments,\n dataReceived as DataReceived<any>,\n );\n }\n\n /** @inheritdoc */\n disconnect(): void {\n this._cleanup?.();\n this._cleanup = undefined;\n }\n}\n\n// ----- Shared pool singleton management -----\n\nlet _sharedMultiplexer: ObservableQueryMultiplexer | undefined;\nlet _sharedMultiplexerKey = '';\n\n/**\n * Returns the shared {@link ObservableQueryMultiplexer}, creating or re-creating it when the\n * configuration (connection count, origin, base path, microservice, transport) changes.\n * @param {() => IObservableQueryHubConnection} connectionFactory Factory to create individual connections.\n * @param {string} cacheKey A string that identifies the current configuration for invalidation.\n * @param {number} size Number of physical connections to create. Defaults to {@link Globals.queryConnectionCount}.\n * @returns The shared multiplexer instance.\n */\nexport function getOrCreateMultiplexer(connectionFactory: () => IObservableQueryHubConnection, cacheKey: string, size: number = Globals.queryConnectionCount): ObservableQueryMultiplexer {\n if (_sharedMultiplexer && _sharedMultiplexerKey === cacheKey) {\n return _sharedMultiplexer;\n }\n\n _sharedMultiplexer?.dispose();\n _sharedMultiplexer = new ObservableQueryMultiplexer(size, connectionFactory);\n _sharedMultiplexerKey = cacheKey;\n return _sharedMultiplexer;\n}\n\n/**\n * Disposes and clears the shared multiplexer singleton.\n * Intended for use in test teardown to prevent state leakage across tests.\n */\nexport function resetSharedMultiplexer(): void {\n _sharedMultiplexer?.dispose();\n _sharedMultiplexer = undefined;\n _sharedMultiplexerKey = '';\n}\n\n/**\n * Returns the current shared multiplexer instance, or {@code undefined} if none has been created yet.\n */\nexport function getSharedMultiplexer(): ObservableQueryMultiplexer | undefined {\n return _sharedMultiplexer;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { QueryResultWithState } from './QueryResultWithState';
|
|
2
|
+
import { CacheDiagnostics } from './ObservableQueryDiagnosticsSnapshot';
|
|
2
3
|
export type QueryCacheKey = string;
|
|
3
4
|
export type QueryCacheListener<TDataType> = (result: QueryResultWithState<TDataType>) => void;
|
|
4
5
|
export interface QueryCacheEntry<TDataType> {
|
|
@@ -33,5 +34,6 @@ export declare class QueryInstanceCache {
|
|
|
33
34
|
dispose(): void;
|
|
34
35
|
deferDispose(): void;
|
|
35
36
|
cancelPendingDispose(): void;
|
|
37
|
+
getDiagnosticsSnapshot(): CacheDiagnostics;
|
|
36
38
|
}
|
|
37
39
|
//# sourceMappingURL=QueryInstanceCache.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QueryInstanceCache.d.ts","sourceRoot":"","sources":["../../../queries/QueryInstanceCache.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;
|
|
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;gBAS3B,YAAY,GAAE,MAAe;IAW1D,QAAQ,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,aAAa;IAuB7D,WAAW,CAAC,SAAS,EACjB,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,MAAM,SAAS,GACzB;QAAE,QAAQ,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE;IAyB1C,OAAO,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI;IAmBjC,aAAa,CAAC,SAAS,EAAE,GAAG,EAAE,aAAa,GAAG,oBAAoB,CAAC,SAAS,CAAC,GAAG,SAAS;IAUzF,aAAa,CAAC,SAAS,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,EAAE,oBAAoB,CAAC,SAAS,CAAC,GAAG,IAAI;IA8B3F,WAAW,CAAC,SAAS,EAAE,GAAG,EAAE,aAAa,EAAE,QAAQ,EAAE,kBAAkB,CAAC,SAAS,CAAC,GAAG,IAAI;IAczF,cAAc,CAAC,SAAS,EAAE,GAAG,EAAE,aAAa,EAAE,QAAQ,EAAE,kBAAkB,CAAC,SAAS,CAAC,GAAG,IAAI;IAc5F,WAAW,CAAC,GAAG,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAc3D,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO;IAYzC,OAAO,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI;IA8BjC,GAAG,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO;IAchC,wBAAwB,IAAI,IAAI;IAkBhC,OAAO,IAAI,IAAI;IAwBf,YAAY,IAAI,IAAI;IAiBpB,oBAAoB,IAAI,IAAI;IAW5B,sBAAsB,IAAI,gBAAgB;CAyC7C"}
|
|
@@ -141,6 +141,42 @@ class QueryInstanceCache {
|
|
|
141
141
|
this._pendingDispose = undefined;
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
|
+
getDiagnosticsSnapshot() {
|
|
145
|
+
const entries = [];
|
|
146
|
+
let totalBytes = 0;
|
|
147
|
+
let unhealthyCount = 0;
|
|
148
|
+
for (const [key, entry] of this._entries) {
|
|
149
|
+
const colonIndex = key.indexOf('::');
|
|
150
|
+
const queryName = colonIndex >= 0 ? key.substring(0, colonIndex) : key;
|
|
151
|
+
let estimatedBytes = 0;
|
|
152
|
+
try {
|
|
153
|
+
if (entry.lastResult !== undefined) {
|
|
154
|
+
estimatedBytes = JSON.stringify(entry.lastResult).length;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
}
|
|
159
|
+
if (entry.subscriberCount > 0 && !entry.subscribed) {
|
|
160
|
+
unhealthyCount++;
|
|
161
|
+
}
|
|
162
|
+
totalBytes += estimatedBytes;
|
|
163
|
+
entries.push({
|
|
164
|
+
key,
|
|
165
|
+
queryName,
|
|
166
|
+
subscriberCount: entry.subscriberCount,
|
|
167
|
+
listenerCount: entry.listeners.size,
|
|
168
|
+
subscribed: entry.subscribed,
|
|
169
|
+
hasResult: entry.lastResult !== undefined,
|
|
170
|
+
estimatedBytes,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
healthy: unhealthyCount === 0,
|
|
175
|
+
entryCount: this._entries.size,
|
|
176
|
+
estimatedBytes: totalBytes,
|
|
177
|
+
entries,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
144
180
|
}
|
|
145
181
|
|
|
146
182
|
export { QueryInstanceCache };
|
|
@@ -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';\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"],"names":[],"mappings":"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"}
|
|
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":[],"mappings":"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"}
|
|
@@ -20,6 +20,7 @@ export declare class ServerSentEventHubConnection implements IObservableQueryHub
|
|
|
20
20
|
private readonly _keepAlive;
|
|
21
21
|
constructor(_sseUrl: string, _subscribeUrl: string, _unsubscribeUrl: string, _microservice: string, keepAliveIntervalMs?: number, _connectTimeoutMs?: number, _policy?: IReconnectPolicy);
|
|
22
22
|
get queryCount(): number;
|
|
23
|
+
get isConnected(): boolean;
|
|
23
24
|
get lastPingLatency(): number;
|
|
24
25
|
get averageLatency(): number;
|
|
25
26
|
subscribe(queryId: string, request: SubscriptionRequest, callback: DataReceived<any>): void;
|
|
@@ -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;gBAe/B,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;IAqBtE,IAAI,UAAU,IAAI,MAAM,CAEvB;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;IAe3F,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAclC,OAAO,IAAI,IAAI;IAYf,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;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;gBAe/B,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;IAqBtE,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;IAe3F,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAclC,OAAO,IAAI,IAAI;IAYf,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"}
|
|
@@ -38,6 +38,9 @@ class ServerSentEventHubConnection {
|
|
|
38
38
|
get queryCount() {
|
|
39
39
|
return this._subscriptions.size;
|
|
40
40
|
}
|
|
41
|
+
get isConnected() {
|
|
42
|
+
return this._connectionId !== undefined && this._eventSource?.readyState === EventSource.OPEN;
|
|
43
|
+
}
|
|
41
44
|
get lastPingLatency() {
|
|
42
45
|
return this._lastPongLatency;
|
|
43
46
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ServerSentEventHubConnection.js","sources":["../../../queries/ServerSentEventHubConnection.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 { Globals } from '../Globals';\nimport { IObservableQueryHubConnection } from './IObservableQueryHubConnection';\nimport { DataReceived } from './ObservableQueryConnection';\nimport { HubConnectionKeepAlive } from './HubConnectionKeepAlive';\nimport { IReconnectPolicy } from './IReconnectPolicy';\nimport { ReconnectPolicy } from './ReconnectPolicy';\nimport { QueryResult } from './QueryResult';\nimport { HubMessage, HubMessageType, SubscriptionRequest } from './WebSocketHubConnection';\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\ninterface ActiveSubscription {\n request: SubscriptionRequest;\n callback: DataReceived<any>;\n}\n\n/**\n * A multiplexed SSE hub connection that uses EventSource for server→client streaming\n * and fetch POST requests for client→server subscribe/unsubscribe commands.\n *\n * Protocol:\n * 1. Open EventSource to the SSE hub endpoint.\n * 2. Server sends a {@link HubMessageType.Connected} message with the connection identifier.\n * 3. Client sends POST to subscribe/unsubscribe endpoints using the connection identifier.\n * 4. Server streams {@link HubMessageType.QueryResult} messages tagged with queryId.\n * 5. When EventSource closes, server cleans up all subscriptions for this connection.\n */\nexport class ServerSentEventHubConnection implements IObservableQueryHubConnection {\n private _eventSource?: EventSource;\n private _connectionId?: string;\n private _disconnected = false;\n private _subscriptions: Map<string, ActiveSubscription> = new Map();\n private _pendingSubscriptions: Map<string, ActiveSubscription> = new Map();\n private _lastPongLatency: number = 0;\n private _latencySamples: number[] = [];\n private _connectTimeoutTimer?: ReturnType<typeof setTimeout>;\n private readonly _keepAlive: HubConnectionKeepAlive;\n\n /**\n * Initializes a new instance of {@link ServerSentEventHubConnection}.\n * @param {string} _sseUrl The SSE hub endpoint URL (e.g. `http://localhost:5000/.cratis/queries/sse`).\n * @param {string} _subscribeUrl The subscribe POST endpoint URL.\n * @param {string} _unsubscribeUrl The unsubscribe POST endpoint URL.\n * @param {string} _microservice The microservice name to pass as a query argument.\n * @param {number} keepAliveIntervalMs How long without any server message before the connection\n * is considered stale and a reconnect is forced (default: 30 000 ms).\n * @param {number} connectTimeoutMs How long to wait for the {@link HubMessageType.Connected}\n * message after the HTTP connection opens before giving up and retrying (default: 15 000 ms).\n * @param {IReconnectPolicy} _policy The reconnect policy to use (default: {@link ReconnectPolicy}).\n */\n constructor(\n private readonly _sseUrl: string,\n private readonly _subscribeUrl: string,\n private readonly _unsubscribeUrl: string,\n private readonly _microservice: string,\n keepAliveIntervalMs: number = 30000,\n private readonly _connectTimeoutMs: number = 15000,\n private readonly _policy: IReconnectPolicy = new ReconnectPolicy()\n ) {\n // SSE is server→client only: the client cannot send pings. Instead we watch for\n // inactivity — if the server stops sending messages (including its own keep-alive\n // pings) for the entire idle threshold, the connection is stale and we reconnect.\n //\n // The idle threshold is set to 1.5× the check interval so the server's keep-alive\n // ping (which fires on the same cadence) has time to arrive over the network before\n // the client declares the connection dead. Without this tolerance the client's timer\n // and the server's timer race — the client often fires first and reconnects\n // unnecessarily.\n const idleThresholdMs = Math.round(keepAliveIntervalMs * 1.5);\n this._keepAlive = new HubConnectionKeepAlive(keepAliveIntervalMs, () => {\n if (!this._disconnected && this._subscriptions.size > 0) {\n console.warn(`SSE hub: no messages received for ${idleThresholdMs}ms, reconnecting '${this._sseUrl}'`);\n this.reconnect();\n }\n }, idleThresholdMs);\n }\n\n /** @inheritdoc */\n get queryCount(): number {\n return this._subscriptions.size;\n }\n\n /** @inheritdoc */\n get lastPingLatency(): number {\n return this._lastPongLatency;\n }\n\n /** @inheritdoc */\n get averageLatency(): number {\n if (this._latencySamples.length === 0) return 0;\n return this._latencySamples.reduce((a, b) => a + b, 0) / this._latencySamples.length;\n }\n\n /** @inheritdoc */\n subscribe(queryId: string, request: SubscriptionRequest, callback: DataReceived<any>): void {\n const sub: ActiveSubscription = { request, callback };\n this._subscriptions.set(queryId, sub);\n\n this.ensureConnected();\n\n if (this._connectionId) {\n this.sendSubscribe(queryId, request);\n } else {\n // Not yet connected, queue for when Connected message arrives.\n this._pendingSubscriptions.set(queryId, sub);\n }\n }\n\n /** @inheritdoc */\n unsubscribe(queryId: string): void {\n this._subscriptions.delete(queryId);\n this._pendingSubscriptions.delete(queryId);\n\n if (this._connectionId) {\n this.sendUnsubscribe(queryId);\n }\n\n if (this._subscriptions.size === 0) {\n this.close();\n }\n }\n\n /** @inheritdoc */\n dispose(): void {\n this._disconnected = true;\n this._subscriptions.clear();\n this._pendingSubscriptions.clear();\n this._policy.cancel();\n this._keepAlive.stop();\n this.clearConnectTimeout();\n this._eventSource?.close();\n this._eventSource = undefined;\n this._connectionId = undefined;\n }\n\n private ensureConnected(): void {\n if (this._disconnected) {\n this._disconnected = false;\n }\n\n if (this._eventSource && this._eventSource.readyState !== EventSource.CLOSED) {\n return;\n }\n\n this.openEventSource();\n }\n\n private close(): void {\n this._disconnected = true;\n this._policy.cancel();\n this._keepAlive.stop();\n this.clearConnectTimeout();\n if (this._eventSource) {\n // Detach all handlers BEFORE closing so that the async onerror / onmessage\n // events that fire after close() cannot observe the new _disconnected=false\n // state set by an immediately following ensureConnected() call. Without this,\n // a stale onerror triggers an unintended reconnect with exponential back-off.\n this._eventSource.onopen = null;\n this._eventSource.onmessage = null;\n this._eventSource.onerror = null;\n this._eventSource.close();\n }\n this._eventSource = undefined;\n this._connectionId = undefined;\n }\n\n private openEventSource(): void {\n let url = this._sseUrl;\n if (this._microservice?.length > 0) {\n const param = `${Globals.microserviceWSQueryArgument}=${encodeURIComponent(this._microservice)}`;\n url += (url.includes('?') ? '&' : '?') + param;\n }\n\n this._connectionId = undefined;\n this._eventSource = new EventSource(url);\n\n this._eventSource.onopen = () => {\n if (this._disconnected) return;\n console.log(`SSE hub connection established: '${url}'`);\n this._policy.reset();\n this._keepAlive.start();\n\n // If the server does not send a Connected message within the timeout, the\n // connection is broken. Close and retry via the reconnect policy.\n this.clearConnectTimeout();\n this._connectTimeoutTimer = setTimeout(() => {\n if (!this._disconnected && !this._connectionId) {\n console.warn(`SSE hub: no Connected message within ${this._connectTimeoutMs}ms, retrying '${url}'`);\n this.reconnect();\n }\n }, this._connectTimeoutMs);\n };\n\n this._eventSource.onmessage = (event: MessageEvent) => {\n if (this._disconnected) return;\n this._keepAlive.recordActivity();\n this.handleMessage(event.data as string);\n };\n\n this._eventSource.onerror = () => {\n if (this._disconnected) return;\n console.warn(`SSE hub connection error: '${url}'`);\n this.reconnect();\n };\n }\n\n private reconnect(): void {\n this._keepAlive.stop();\n this.clearConnectTimeout();\n\n // Close the EventSource so the reconnect policy manages the schedule.\n this._eventSource?.close();\n this._eventSource = undefined;\n this._connectionId = undefined;\n\n // Move all active subscriptions to pending so they re-subscribe when\n // the next Connected message arrives after the managed reconnect.\n for (const [queryId, sub] of this._subscriptions) {\n this._pendingSubscriptions.set(queryId, sub);\n }\n\n if (this._subscriptions.size === 0) return;\n\n this._policy.schedule(() => {\n if (!this._disconnected && this._subscriptions.size > 0) {\n this.openEventSource();\n }\n }, this._sseUrl);\n }\n\n private clearConnectTimeout(): void {\n if (this._connectTimeoutTimer !== undefined) {\n clearTimeout(this._connectTimeoutTimer);\n this._connectTimeoutTimer = undefined;\n }\n }\n\n private handleMessage(rawData: string): void {\n try {\n const message = JSON.parse(rawData) as HubMessage;\n\n switch (message.type) {\n case HubMessageType.Connected:\n this.handleConnected(message);\n break;\n case HubMessageType.QueryResult:\n this.handleQueryResult(message);\n break;\n case HubMessageType.Ping:\n // Server-sent keep-alive ping — activity already recorded in onmessage.\n break;\n case HubMessageType.Unauthorized:\n console.warn(`SSE hub: query '${message.queryId}' unauthorized`);\n this.handleUnauthorized(message);\n break;\n case HubMessageType.Error:\n console.error(`SSE hub: query '${message.queryId}' error:`, message.payload);\n break;\n }\n } catch (error) {\n console.error('SSE hub: error parsing message', error);\n }\n }\n\n private handleConnected(message: HubMessage): void {\n this._connectionId = message.payload as string;\n console.log(`SSE hub: connected with id '${this._connectionId}'`);\n\n // Connected message arrived — cancel the connect timeout.\n this.clearConnectTimeout();\n\n // Send all pending subscriptions now that we have a connection ID.\n for (const [queryId, sub] of this._pendingSubscriptions) {\n this.sendSubscribe(queryId, sub.request);\n }\n this._pendingSubscriptions.clear();\n }\n\n private handleQueryResult(message: HubMessage): void {\n if (!message.queryId) return;\n\n const sub = this._subscriptions.get(message.queryId);\n if (!sub) return;\n\n const result = message.payload as QueryResult<any>;\n sub.callback(result);\n }\n\n private handleUnauthorized(message: HubMessage): void {\n if (!message.queryId) return;\n\n const sub = this._subscriptions.get(message.queryId);\n if (!sub) return;\n\n this._subscriptions.delete(message.queryId);\n this._pendingSubscriptions.delete(message.queryId);\n sub.callback(QueryResult.unauthorized());\n }\n\n private sendSubscribe(queryId: string, request: SubscriptionRequest, attempt: number = 0): void {\n if (!this._connectionId || this._disconnected) return;\n\n // Capture the connection ID so retries can detect if a reconnect has already fired\n // for a different reason and made this retry obsolete.\n const connectionId = this._connectionId;\n\n const body = {\n connectionId,\n queryId,\n request,\n };\n\n const customHeaders = Globals.httpHeadersCallback?.() ?? {};\n\n // Maximum number of subscribe retries before falling back to a full SSE reconnect.\n // In a round-robin load-balanced deployment the subscribe POST may land on a different\n // backend instance than the one holding the SSE connection. Retrying gives the load\n // balancer the chance to route a subsequent attempt to the correct instance without\n // tearing down the SSE connection unnecessarily. With N backend instances at most\n // N-1 retries are needed, so 3 retries covers deployments with up to 4 replicas.\n const maxRetries = 3;\n const retryDelayMs = 200;\n\n fetch(this._subscribeUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...customHeaders },\n body: JSON.stringify(body),\n }).then(response => {\n if (!response.ok) {\n if (attempt < maxRetries && this._connectionId === connectionId && !this._disconnected) {\n setTimeout(() => this.sendSubscribe(queryId, request, attempt + 1), retryDelayMs * (attempt + 1));\n } else if (this._connectionId === connectionId && !this._disconnected) {\n console.warn(`SSE hub: subscribe POST for '${queryId}' returned ${response.status} after ${attempt + 1} attempt(s), reconnecting`);\n this.reconnect();\n }\n }\n }).catch(error => {\n if (attempt < maxRetries && this._connectionId === connectionId && !this._disconnected) {\n setTimeout(() => this.sendSubscribe(queryId, request, attempt + 1), retryDelayMs * (attempt + 1));\n } else if (this._connectionId === connectionId && !this._disconnected) {\n console.error(`SSE hub: subscribe POST failed for '${queryId}' after ${attempt + 1} attempt(s), reconnecting`, error);\n this.reconnect();\n }\n });\n }\n\n private sendUnsubscribe(queryId: string): void {\n if (!this._connectionId) return;\n\n const body = {\n connectionId: this._connectionId,\n queryId,\n };\n\n const customHeaders = Globals.httpHeadersCallback?.() ?? {};\n\n fetch(this._unsubscribeUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...customHeaders },\n body: JSON.stringify(body),\n }).catch(error => {\n console.error(`SSE hub: unsubscribe POST failed for '${queryId}'`, error);\n });\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ServerSentEventHubConnection.js","sources":["../../../queries/ServerSentEventHubConnection.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 { Globals } from '../Globals';\nimport { IObservableQueryHubConnection } from './IObservableQueryHubConnection';\nimport { DataReceived } from './ObservableQueryConnection';\nimport { HubConnectionKeepAlive } from './HubConnectionKeepAlive';\nimport { IReconnectPolicy } from './IReconnectPolicy';\nimport { ReconnectPolicy } from './ReconnectPolicy';\nimport { QueryResult } from './QueryResult';\nimport { HubMessage, HubMessageType, SubscriptionRequest } from './WebSocketHubConnection';\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\ninterface ActiveSubscription {\n request: SubscriptionRequest;\n callback: DataReceived<any>;\n}\n\n/**\n * A multiplexed SSE hub connection that uses EventSource for server→client streaming\n * and fetch POST requests for client→server subscribe/unsubscribe commands.\n *\n * Protocol:\n * 1. Open EventSource to the SSE hub endpoint.\n * 2. Server sends a {@link HubMessageType.Connected} message with the connection identifier.\n * 3. Client sends POST to subscribe/unsubscribe endpoints using the connection identifier.\n * 4. Server streams {@link HubMessageType.QueryResult} messages tagged with queryId.\n * 5. When EventSource closes, server cleans up all subscriptions for this connection.\n */\nexport class ServerSentEventHubConnection implements IObservableQueryHubConnection {\n private _eventSource?: EventSource;\n private _connectionId?: string;\n private _disconnected = false;\n private _subscriptions: Map<string, ActiveSubscription> = new Map();\n private _pendingSubscriptions: Map<string, ActiveSubscription> = new Map();\n private _lastPongLatency: number = 0;\n private _latencySamples: number[] = [];\n private _connectTimeoutTimer?: ReturnType<typeof setTimeout>;\n private readonly _keepAlive: HubConnectionKeepAlive;\n\n /**\n * Initializes a new instance of {@link ServerSentEventHubConnection}.\n * @param {string} _sseUrl The SSE hub endpoint URL (e.g. `http://localhost:5000/.cratis/queries/sse`).\n * @param {string} _subscribeUrl The subscribe POST endpoint URL.\n * @param {string} _unsubscribeUrl The unsubscribe POST endpoint URL.\n * @param {string} _microservice The microservice name to pass as a query argument.\n * @param {number} keepAliveIntervalMs How long without any server message before the connection\n * is considered stale and a reconnect is forced (default: 30 000 ms).\n * @param {number} connectTimeoutMs How long to wait for the {@link HubMessageType.Connected}\n * message after the HTTP connection opens before giving up and retrying (default: 15 000 ms).\n * @param {IReconnectPolicy} _policy The reconnect policy to use (default: {@link ReconnectPolicy}).\n */\n constructor(\n private readonly _sseUrl: string,\n private readonly _subscribeUrl: string,\n private readonly _unsubscribeUrl: string,\n private readonly _microservice: string,\n keepAliveIntervalMs: number = 30000,\n private readonly _connectTimeoutMs: number = 15000,\n private readonly _policy: IReconnectPolicy = new ReconnectPolicy()\n ) {\n // SSE is server→client only: the client cannot send pings. Instead we watch for\n // inactivity — if the server stops sending messages (including its own keep-alive\n // pings) for the entire idle threshold, the connection is stale and we reconnect.\n //\n // The idle threshold is set to 1.5× the check interval so the server's keep-alive\n // ping (which fires on the same cadence) has time to arrive over the network before\n // the client declares the connection dead. Without this tolerance the client's timer\n // and the server's timer race — the client often fires first and reconnects\n // unnecessarily.\n const idleThresholdMs = Math.round(keepAliveIntervalMs * 1.5);\n this._keepAlive = new HubConnectionKeepAlive(keepAliveIntervalMs, () => {\n if (!this._disconnected && this._subscriptions.size > 0) {\n console.warn(`SSE hub: no messages received for ${idleThresholdMs}ms, reconnecting '${this._sseUrl}'`);\n this.reconnect();\n }\n }, idleThresholdMs);\n }\n\n /** @inheritdoc */\n get queryCount(): number {\n return this._subscriptions.size;\n }\n\n /** @inheritdoc */\n get isConnected(): boolean {\n return this._connectionId !== undefined && this._eventSource?.readyState === EventSource.OPEN;\n }\n\n /** @inheritdoc */\n get lastPingLatency(): number {\n return this._lastPongLatency;\n }\n\n /** @inheritdoc */\n get averageLatency(): number {\n if (this._latencySamples.length === 0) return 0;\n return this._latencySamples.reduce((a, b) => a + b, 0) / this._latencySamples.length;\n }\n\n /** @inheritdoc */\n subscribe(queryId: string, request: SubscriptionRequest, callback: DataReceived<any>): void {\n const sub: ActiveSubscription = { request, callback };\n this._subscriptions.set(queryId, sub);\n\n this.ensureConnected();\n\n if (this._connectionId) {\n this.sendSubscribe(queryId, request);\n } else {\n // Not yet connected, queue for when Connected message arrives.\n this._pendingSubscriptions.set(queryId, sub);\n }\n }\n\n /** @inheritdoc */\n unsubscribe(queryId: string): void {\n this._subscriptions.delete(queryId);\n this._pendingSubscriptions.delete(queryId);\n\n if (this._connectionId) {\n this.sendUnsubscribe(queryId);\n }\n\n if (this._subscriptions.size === 0) {\n this.close();\n }\n }\n\n /** @inheritdoc */\n dispose(): void {\n this._disconnected = true;\n this._subscriptions.clear();\n this._pendingSubscriptions.clear();\n this._policy.cancel();\n this._keepAlive.stop();\n this.clearConnectTimeout();\n this._eventSource?.close();\n this._eventSource = undefined;\n this._connectionId = undefined;\n }\n\n private ensureConnected(): void {\n if (this._disconnected) {\n this._disconnected = false;\n }\n\n if (this._eventSource && this._eventSource.readyState !== EventSource.CLOSED) {\n return;\n }\n\n this.openEventSource();\n }\n\n private close(): void {\n this._disconnected = true;\n this._policy.cancel();\n this._keepAlive.stop();\n this.clearConnectTimeout();\n if (this._eventSource) {\n // Detach all handlers BEFORE closing so that the async onerror / onmessage\n // events that fire after close() cannot observe the new _disconnected=false\n // state set by an immediately following ensureConnected() call. Without this,\n // a stale onerror triggers an unintended reconnect with exponential back-off.\n this._eventSource.onopen = null;\n this._eventSource.onmessage = null;\n this._eventSource.onerror = null;\n this._eventSource.close();\n }\n this._eventSource = undefined;\n this._connectionId = undefined;\n }\n\n private openEventSource(): void {\n let url = this._sseUrl;\n if (this._microservice?.length > 0) {\n const param = `${Globals.microserviceWSQueryArgument}=${encodeURIComponent(this._microservice)}`;\n url += (url.includes('?') ? '&' : '?') + param;\n }\n\n this._connectionId = undefined;\n this._eventSource = new EventSource(url);\n\n this._eventSource.onopen = () => {\n if (this._disconnected) return;\n console.log(`SSE hub connection established: '${url}'`);\n this._policy.reset();\n this._keepAlive.start();\n\n // If the server does not send a Connected message within the timeout, the\n // connection is broken. Close and retry via the reconnect policy.\n this.clearConnectTimeout();\n this._connectTimeoutTimer = setTimeout(() => {\n if (!this._disconnected && !this._connectionId) {\n console.warn(`SSE hub: no Connected message within ${this._connectTimeoutMs}ms, retrying '${url}'`);\n this.reconnect();\n }\n }, this._connectTimeoutMs);\n };\n\n this._eventSource.onmessage = (event: MessageEvent) => {\n if (this._disconnected) return;\n this._keepAlive.recordActivity();\n this.handleMessage(event.data as string);\n };\n\n this._eventSource.onerror = () => {\n if (this._disconnected) return;\n console.warn(`SSE hub connection error: '${url}'`);\n this.reconnect();\n };\n }\n\n private reconnect(): void {\n this._keepAlive.stop();\n this.clearConnectTimeout();\n\n // Close the EventSource so the reconnect policy manages the schedule.\n this._eventSource?.close();\n this._eventSource = undefined;\n this._connectionId = undefined;\n\n // Move all active subscriptions to pending so they re-subscribe when\n // the next Connected message arrives after the managed reconnect.\n for (const [queryId, sub] of this._subscriptions) {\n this._pendingSubscriptions.set(queryId, sub);\n }\n\n if (this._subscriptions.size === 0) return;\n\n this._policy.schedule(() => {\n if (!this._disconnected && this._subscriptions.size > 0) {\n this.openEventSource();\n }\n }, this._sseUrl);\n }\n\n private clearConnectTimeout(): void {\n if (this._connectTimeoutTimer !== undefined) {\n clearTimeout(this._connectTimeoutTimer);\n this._connectTimeoutTimer = undefined;\n }\n }\n\n private handleMessage(rawData: string): void {\n try {\n const message = JSON.parse(rawData) as HubMessage;\n\n switch (message.type) {\n case HubMessageType.Connected:\n this.handleConnected(message);\n break;\n case HubMessageType.QueryResult:\n this.handleQueryResult(message);\n break;\n case HubMessageType.Ping:\n // Server-sent keep-alive ping — activity already recorded in onmessage.\n break;\n case HubMessageType.Unauthorized:\n console.warn(`SSE hub: query '${message.queryId}' unauthorized`);\n this.handleUnauthorized(message);\n break;\n case HubMessageType.Error:\n console.error(`SSE hub: query '${message.queryId}' error:`, message.payload);\n break;\n }\n } catch (error) {\n console.error('SSE hub: error parsing message', error);\n }\n }\n\n private handleConnected(message: HubMessage): void {\n this._connectionId = message.payload as string;\n console.log(`SSE hub: connected with id '${this._connectionId}'`);\n\n // Connected message arrived — cancel the connect timeout.\n this.clearConnectTimeout();\n\n // Send all pending subscriptions now that we have a connection ID.\n for (const [queryId, sub] of this._pendingSubscriptions) {\n this.sendSubscribe(queryId, sub.request);\n }\n this._pendingSubscriptions.clear();\n }\n\n private handleQueryResult(message: HubMessage): void {\n if (!message.queryId) return;\n\n const sub = this._subscriptions.get(message.queryId);\n if (!sub) return;\n\n const result = message.payload as QueryResult<any>;\n sub.callback(result);\n }\n\n private handleUnauthorized(message: HubMessage): void {\n if (!message.queryId) return;\n\n const sub = this._subscriptions.get(message.queryId);\n if (!sub) return;\n\n this._subscriptions.delete(message.queryId);\n this._pendingSubscriptions.delete(message.queryId);\n sub.callback(QueryResult.unauthorized());\n }\n\n private sendSubscribe(queryId: string, request: SubscriptionRequest, attempt: number = 0): void {\n if (!this._connectionId || this._disconnected) return;\n\n // Capture the connection ID so retries can detect if a reconnect has already fired\n // for a different reason and made this retry obsolete.\n const connectionId = this._connectionId;\n\n const body = {\n connectionId,\n queryId,\n request,\n };\n\n const customHeaders = Globals.httpHeadersCallback?.() ?? {};\n\n // Maximum number of subscribe retries before falling back to a full SSE reconnect.\n // In a round-robin load-balanced deployment the subscribe POST may land on a different\n // backend instance than the one holding the SSE connection. Retrying gives the load\n // balancer the chance to route a subsequent attempt to the correct instance without\n // tearing down the SSE connection unnecessarily. With N backend instances at most\n // N-1 retries are needed, so 3 retries covers deployments with up to 4 replicas.\n const maxRetries = 3;\n const retryDelayMs = 200;\n\n fetch(this._subscribeUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...customHeaders },\n body: JSON.stringify(body),\n }).then(response => {\n if (!response.ok) {\n if (attempt < maxRetries && this._connectionId === connectionId && !this._disconnected) {\n setTimeout(() => this.sendSubscribe(queryId, request, attempt + 1), retryDelayMs * (attempt + 1));\n } else if (this._connectionId === connectionId && !this._disconnected) {\n console.warn(`SSE hub: subscribe POST for '${queryId}' returned ${response.status} after ${attempt + 1} attempt(s), reconnecting`);\n this.reconnect();\n }\n }\n }).catch(error => {\n if (attempt < maxRetries && this._connectionId === connectionId && !this._disconnected) {\n setTimeout(() => this.sendSubscribe(queryId, request, attempt + 1), retryDelayMs * (attempt + 1));\n } else if (this._connectionId === connectionId && !this._disconnected) {\n console.error(`SSE hub: subscribe POST failed for '${queryId}' after ${attempt + 1} attempt(s), reconnecting`, error);\n this.reconnect();\n }\n });\n }\n\n private sendUnsubscribe(queryId: string): void {\n if (!this._connectionId) return;\n\n const body = {\n connectionId: this._connectionId,\n queryId,\n };\n\n const customHeaders = Globals.httpHeadersCallback?.() ?? {};\n\n fetch(this._unsubscribeUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...customHeaders },\n body: JSON.stringify(body),\n }).catch(error => {\n console.error(`SSE hub: unsubscribe POST failed for '${queryId}'`, error);\n });\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -38,6 +38,7 @@ export declare class WebSocketHubConnection {
|
|
|
38
38
|
private _latencySamples;
|
|
39
39
|
constructor(_url: string, _microservice: string, pingIntervalMs?: number, _policy?: IReconnectPolicy);
|
|
40
40
|
get queryCount(): number;
|
|
41
|
+
get isConnected(): boolean;
|
|
41
42
|
get lastPingLatency(): number;
|
|
42
43
|
get averageLatency(): number;
|
|
43
44
|
subscribe(queryId: string, request: SubscriptionRequest, callback: DataReceived<any>): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebSocketHubConnection.d.ts","sourceRoot":"","sources":["../../../queries/WebSocketHubConnection.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAUtD,oBAAY,cAAc;IACtB,SAAS,cAAc;IACvB,WAAW,gBAAgB;IAC3B,WAAW,gBAAgB;IAC3B,YAAY,iBAAiB;IAC7B,KAAK,UAAU;IACf,IAAI,SAAS;IACb,IAAI,SAAS;IACb,SAAS,cAAc;CAC1B;AAKD,MAAM,WAAW,UAAU;IACvB,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAKD,MAAM,WAAW,mBAAmB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAeD,qBAAa,sBAAsB;IAiB3B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAE9B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAnB5B,OAAO,CAAC,OAAO,CAAC,CAAY;IAC5B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;IACpD,OAAO,CAAC,iBAAiB,CAAC,CAAS;IACnC,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,eAAe,CAAgB;gBAUlB,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,MAAM,EACtC,cAAc,GAAE,MAAc,EACb,OAAO,GAAE,gBAAwC;IAatE,IAAI,UAAU,IAAI,MAAM,CAEvB;IAKD,IAAI,eAAe,IAAI,MAAM,CAE5B;IAKD,IAAI,cAAc,IAAI,MAAM,CAG3B;IASD,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,YAAY,CAAC,GAAG,CAAC,GAAG,IAAI;IAc3F,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAgBlC,OAAO,IAAI,IAAI;IASf,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,KAAK;IAmBb,OAAO,CAAC,UAAU;IA0ClB,OAAO,CAAC,oBAAoB;IAM5B,OAAO,CAAC,oBAAoB;IAQ5B,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,aAAa;IA2BrB,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,UAAU;CAWrB"}
|
|
1
|
+
{"version":3,"file":"WebSocketHubConnection.d.ts","sourceRoot":"","sources":["../../../queries/WebSocketHubConnection.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAUtD,oBAAY,cAAc;IACtB,SAAS,cAAc;IACvB,WAAW,gBAAgB;IAC3B,WAAW,gBAAgB;IAC3B,YAAY,iBAAiB;IAC7B,KAAK,UAAU;IACf,IAAI,SAAS;IACb,IAAI,SAAS;IACb,SAAS,cAAc;CAC1B;AAKD,MAAM,WAAW,UAAU;IACvB,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAKD,MAAM,WAAW,mBAAmB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAeD,qBAAa,sBAAsB;IAiB3B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAE9B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAnB5B,OAAO,CAAC,OAAO,CAAC,CAAY;IAC5B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;IACpD,OAAO,CAAC,iBAAiB,CAAC,CAAS;IACnC,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,eAAe,CAAgB;gBAUlB,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,MAAM,EACtC,cAAc,GAAE,MAAc,EACb,OAAO,GAAE,gBAAwC;IAatE,IAAI,UAAU,IAAI,MAAM,CAEvB;IAKD,IAAI,WAAW,IAAI,OAAO,CAEzB;IAKD,IAAI,eAAe,IAAI,MAAM,CAE5B;IAKD,IAAI,cAAc,IAAI,MAAM,CAG3B;IASD,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,YAAY,CAAC,GAAG,CAAC,GAAG,IAAI;IAc3F,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAgBlC,OAAO,IAAI,IAAI;IASf,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,KAAK;IAmBb,OAAO,CAAC,UAAU;IA0ClB,OAAO,CAAC,oBAAoB;IAM5B,OAAO,CAAC,oBAAoB;IAQ5B,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,aAAa;IA2BrB,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,UAAU;CAWrB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebSocketHubConnection.js","sources":["../../../queries/WebSocketHubConnection.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 { Globals } from '../Globals';\nimport { DataReceived } from './ObservableQueryConnection';\nimport { HubConnectionKeepAlive } from './HubConnectionKeepAlive';\nimport { IReconnectPolicy } from './IReconnectPolicy';\nimport { ReconnectPolicy } from './ReconnectPolicy';\nimport { QueryResult } from './QueryResult';\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\n/**\n * Message types matching the backend {@link ObservableQueryHubMessageType} enum.\n * Serialized as strings by the JsonStringEnumConverter on the server.\n */\nexport enum HubMessageType {\n Subscribe = 'Subscribe',\n Unsubscribe = 'Unsubscribe',\n QueryResult = 'QueryResult',\n Unauthorized = 'Unauthorized',\n Error = 'Error',\n Ping = 'Ping',\n Pong = 'Pong',\n Connected = 'Connected',\n}\n\n/**\n * Wire format for messages exchanged over the {@link WebSocketHubConnection}.\n */\nexport interface HubMessage {\n type: HubMessageType;\n queryId?: string;\n payload?: any;\n timestamp?: number;\n}\n\n/**\n * Matches the backend {@link ObservableQuerySubscriptionRequest} record.\n */\nexport interface SubscriptionRequest {\n queryName: string;\n arguments?: Record<string, string | null>;\n page?: number;\n pageSize?: number;\n sortBy?: string;\n sortDirection?: string;\n transferMode?: string;\n}\n\ninterface ActiveSubscription {\n request: SubscriptionRequest;\n callback: DataReceived<any>;\n}\n\n/**\n * Represents a single multiplexed WebSocket connection to the observable query hub\n * at {@code /.cratis/queries/ws}.\n *\n * Multiple query subscriptions are carried over the same physical WebSocket. Each subscription\n * is identified by a client-generated {@code queryId}; the server tags every result message with\n * the same id so responses can be routed to the correct callback.\n */\nexport class WebSocketHubConnection {\n private _socket?: WebSocket;\n private _disconnected = false;\n private _subscriptions: Map<string, ActiveSubscription> = new Map();\n private readonly _keepAlive: HubConnectionKeepAlive;\n private _lastPingSentTime?: number;\n private _lastPongLatency: number = 0;\n private _latencySamples: number[] = [];\n\n /**\n * Initializes a new instance of {@link WebSocketHubConnection}.\n * @param {string} url The WebSocket URL of the hub endpoint (e.g. {@code ws://localhost:5000/.cratis/queries/ws}).\n * @param {string} microservice The microservice name to pass as a query argument.\n * @param {number} pingIntervalMs How often to send keep-alive pings when the connection is idle (default: 10 000 ms).\n * @param {IReconnectPolicy} reconnectPolicy The reconnect policy to use (default: {@link ReconnectPolicy}).\n */\n constructor(\n private readonly _url: string,\n private readonly _microservice: string,\n pingIntervalMs: number = 10000,\n private readonly _policy: IReconnectPolicy = new ReconnectPolicy()\n ) {\n this._keepAlive = new HubConnectionKeepAlive(pingIntervalMs, () => {\n if (this._socket?.readyState === WebSocket.OPEN) {\n this._lastPingSentTime = Date.now();\n this.sendMessage({ type: HubMessageType.Ping, timestamp: this._lastPingSentTime });\n }\n });\n }\n\n /**\n * Gets the number of active query subscriptions on this connection.\n */\n get queryCount(): number {\n return this._subscriptions.size;\n }\n\n /**\n * Gets the latency of the last ping/pong sequence in milliseconds.\n */\n get lastPingLatency(): number {\n return this._lastPongLatency;\n }\n\n /**\n * Gets the rolling average latency in milliseconds.\n */\n get averageLatency(): number {\n if (this._latencySamples.length === 0) return 0;\n return this._latencySamples.reduce((a, b) => a + b, 0) / this._latencySamples.length;\n }\n\n /**\n * Subscribe to a query on this hub connection.\n * If the WebSocket is not yet open, the subscribe message will be sent once the connection is established.\n * @param {string} queryId Client-generated unique identifier for this subscription.\n * @param {SubscriptionRequest} request The subscription request payload.\n * @param {DataReceived<any>} callback Callback invoked whenever the server pushes a result for this query.\n */\n subscribe(queryId: string, request: SubscriptionRequest, callback: DataReceived<any>): void {\n this._subscriptions.set(queryId, { request, callback });\n this.ensureConnected();\n\n if (this._socket?.readyState === WebSocket.OPEN) {\n this.sendSubscribeMessage(queryId, request);\n }\n // If not yet open, sendAllSubscriptions will fire in onopen.\n }\n\n /**\n * Unsubscribe from a query on this hub connection.\n * @param {string} queryId The identifier of the subscription to cancel.\n */\n unsubscribe(queryId: string): void {\n this._subscriptions.delete(queryId);\n\n if (this._socket?.readyState === WebSocket.OPEN) {\n this.sendMessage({ type: HubMessageType.Unsubscribe, queryId });\n }\n\n // If no subscriptions remain, close the connection to free resources.\n if (this._subscriptions.size === 0) {\n this.close();\n }\n }\n\n /**\n * Permanently close this hub connection and clean up all subscriptions.\n */\n dispose(): void {\n this._disconnected = true;\n this._subscriptions.clear();\n this._keepAlive.stop();\n this._policy.cancel();\n this._socket?.close();\n this._socket = undefined;\n }\n\n private ensureConnected(): void {\n if (this._disconnected) {\n // Reset disconnected flag when a new subscription comes in\n this._disconnected = false;\n }\n\n if (this._socket && (this._socket.readyState === WebSocket.OPEN || this._socket.readyState === WebSocket.CONNECTING)) {\n return;\n }\n\n this.openSocket();\n }\n\n private close(): void {\n this._disconnected = true;\n this._keepAlive.stop();\n this._policy.cancel();\n if (this._socket) {\n // Detach all handlers BEFORE closing so that the async onclose event cannot\n // fire after a new subscription has reset _disconnected to false and opened a\n // fresh socket. Without this, the stale onclose triggers an unintended\n // reconnect via the back-off policy, causing a 1-10 second delay before the\n // new page's queries receive their first data.\n this._socket.onopen = null;\n this._socket.onclose = null;\n this._socket.onerror = null;\n this._socket.onmessage = null;\n this._socket.close();\n }\n this._socket = undefined;\n }\n\n private openSocket(): void {\n let url = this._url;\n if (this._microservice?.length > 0) {\n const param = `${Globals.microserviceWSQueryArgument}=${encodeURIComponent(this._microservice)}`;\n url += (url.includes('?') ? '&' : '?') + param;\n }\n\n this._socket = new WebSocket(url);\n\n this._socket.onopen = () => {\n if (this._disconnected) return;\n console.log(`Hub connection established: '${url}'`);\n this._policy.reset();\n this._keepAlive.start();\n this.sendAllSubscriptions();\n };\n\n this._socket.onclose = () => {\n if (this._disconnected) return;\n console.log(`Hub connection closed: '${url}'`);\n this._keepAlive.stop();\n if (this._subscriptions.size === 0) return;\n this._policy.schedule(() => {\n if (!this._disconnected && this._subscriptions.size > 0) {\n this.openSocket();\n }\n }, this._url);\n };\n\n this._socket.onerror = (error) => {\n if (this._disconnected) return;\n console.error(`Hub connection error: '${url}'`, error);\n this._keepAlive.stop();\n // onclose will fire after onerror, triggering reconnect\n };\n\n this._socket.onmessage = (ev) => {\n if (this._disconnected) return;\n this.handleMessage(ev.data as string);\n };\n }\n\n private sendAllSubscriptions(): void {\n for (const [queryId, sub] of this._subscriptions) {\n this.sendSubscribeMessage(queryId, sub.request);\n }\n }\n\n private sendSubscribeMessage(queryId: string, request: SubscriptionRequest): void {\n this.sendMessage({\n type: HubMessageType.Subscribe,\n queryId,\n payload: request,\n });\n }\n\n private sendMessage(message: HubMessage): void {\n if (this._socket?.readyState === WebSocket.OPEN) {\n this._socket.send(JSON.stringify(message));\n }\n }\n\n private handleMessage(rawData: string): void {\n try {\n const message = JSON.parse(rawData) as HubMessage;\n\n // Every received message is activity — skip keep-alive ping if data is flowing.\n this._keepAlive.recordActivity();\n\n switch (message.type) {\n case HubMessageType.QueryResult:\n this.handleQueryResult(message);\n break;\n case HubMessageType.Pong:\n this.handlePong(message);\n break;\n case HubMessageType.Unauthorized:\n console.warn(`Hub: query '${message.queryId}' unauthorized`);\n this.handleUnauthorized(message);\n break;\n case HubMessageType.Error:\n console.error(`Hub: query '${message.queryId}' error:`, message.payload);\n break;\n }\n } catch (error) {\n console.error('Hub: error parsing message', error);\n }\n }\n\n private handleQueryResult(message: HubMessage): void {\n if (!message.queryId) return;\n\n const sub = this._subscriptions.get(message.queryId);\n if (!sub) return;\n\n const result = message.payload as QueryResult<any>;\n sub.callback(result);\n }\n\n private handleUnauthorized(message: HubMessage): void {\n if (!message.queryId) return;\n\n const sub = this._subscriptions.get(message.queryId);\n if (!sub) return;\n\n this._subscriptions.delete(message.queryId);\n sub.callback(QueryResult.unauthorized());\n }\n\n private handlePong(message: HubMessage): void {\n if (message.timestamp && this._lastPingSentTime) {\n const latency = Date.now() - message.timestamp;\n this._lastPongLatency = latency;\n this._latencySamples.push(latency);\n\n if (this._latencySamples.length > 100) {\n this._latencySamples.shift();\n }\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WebSocketHubConnection.js","sources":["../../../queries/WebSocketHubConnection.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 { Globals } from '../Globals';\nimport { DataReceived } from './ObservableQueryConnection';\nimport { HubConnectionKeepAlive } from './HubConnectionKeepAlive';\nimport { IReconnectPolicy } from './IReconnectPolicy';\nimport { ReconnectPolicy } from './ReconnectPolicy';\nimport { QueryResult } from './QueryResult';\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\n/**\n * Message types matching the backend {@link ObservableQueryHubMessageType} enum.\n * Serialized as strings by the JsonStringEnumConverter on the server.\n */\nexport enum HubMessageType {\n Subscribe = 'Subscribe',\n Unsubscribe = 'Unsubscribe',\n QueryResult = 'QueryResult',\n Unauthorized = 'Unauthorized',\n Error = 'Error',\n Ping = 'Ping',\n Pong = 'Pong',\n Connected = 'Connected',\n}\n\n/**\n * Wire format for messages exchanged over the {@link WebSocketHubConnection}.\n */\nexport interface HubMessage {\n type: HubMessageType;\n queryId?: string;\n payload?: any;\n timestamp?: number;\n}\n\n/**\n * Matches the backend {@link ObservableQuerySubscriptionRequest} record.\n */\nexport interface SubscriptionRequest {\n queryName: string;\n arguments?: Record<string, string | null>;\n page?: number;\n pageSize?: number;\n sortBy?: string;\n sortDirection?: string;\n transferMode?: string;\n}\n\ninterface ActiveSubscription {\n request: SubscriptionRequest;\n callback: DataReceived<any>;\n}\n\n/**\n * Represents a single multiplexed WebSocket connection to the observable query hub\n * at {@code /.cratis/queries/ws}.\n *\n * Multiple query subscriptions are carried over the same physical WebSocket. Each subscription\n * is identified by a client-generated {@code queryId}; the server tags every result message with\n * the same id so responses can be routed to the correct callback.\n */\nexport class WebSocketHubConnection {\n private _socket?: WebSocket;\n private _disconnected = false;\n private _subscriptions: Map<string, ActiveSubscription> = new Map();\n private readonly _keepAlive: HubConnectionKeepAlive;\n private _lastPingSentTime?: number;\n private _lastPongLatency: number = 0;\n private _latencySamples: number[] = [];\n\n /**\n * Initializes a new instance of {@link WebSocketHubConnection}.\n * @param {string} url The WebSocket URL of the hub endpoint (e.g. {@code ws://localhost:5000/.cratis/queries/ws}).\n * @param {string} microservice The microservice name to pass as a query argument.\n * @param {number} pingIntervalMs How often to send keep-alive pings when the connection is idle (default: 10 000 ms).\n * @param {IReconnectPolicy} reconnectPolicy The reconnect policy to use (default: {@link ReconnectPolicy}).\n */\n constructor(\n private readonly _url: string,\n private readonly _microservice: string,\n pingIntervalMs: number = 10000,\n private readonly _policy: IReconnectPolicy = new ReconnectPolicy()\n ) {\n this._keepAlive = new HubConnectionKeepAlive(pingIntervalMs, () => {\n if (this._socket?.readyState === WebSocket.OPEN) {\n this._lastPingSentTime = Date.now();\n this.sendMessage({ type: HubMessageType.Ping, timestamp: this._lastPingSentTime });\n }\n });\n }\n\n /**\n * Gets the number of active query subscriptions on this connection.\n */\n get queryCount(): number {\n return this._subscriptions.size;\n }\n\n /**\n * Gets whether the WebSocket connection is currently open.\n */\n get isConnected(): boolean {\n return this._socket?.readyState === WebSocket.OPEN;\n }\n\n /**\n * Gets the latency of the last ping/pong sequence in milliseconds.\n */\n get lastPingLatency(): number {\n return this._lastPongLatency;\n }\n\n /**\n * Gets the rolling average latency in milliseconds.\n */\n get averageLatency(): number {\n if (this._latencySamples.length === 0) return 0;\n return this._latencySamples.reduce((a, b) => a + b, 0) / this._latencySamples.length;\n }\n\n /**\n * Subscribe to a query on this hub connection.\n * If the WebSocket is not yet open, the subscribe message will be sent once the connection is established.\n * @param {string} queryId Client-generated unique identifier for this subscription.\n * @param {SubscriptionRequest} request The subscription request payload.\n * @param {DataReceived<any>} callback Callback invoked whenever the server pushes a result for this query.\n */\n subscribe(queryId: string, request: SubscriptionRequest, callback: DataReceived<any>): void {\n this._subscriptions.set(queryId, { request, callback });\n this.ensureConnected();\n\n if (this._socket?.readyState === WebSocket.OPEN) {\n this.sendSubscribeMessage(queryId, request);\n }\n // If not yet open, sendAllSubscriptions will fire in onopen.\n }\n\n /**\n * Unsubscribe from a query on this hub connection.\n * @param {string} queryId The identifier of the subscription to cancel.\n */\n unsubscribe(queryId: string): void {\n this._subscriptions.delete(queryId);\n\n if (this._socket?.readyState === WebSocket.OPEN) {\n this.sendMessage({ type: HubMessageType.Unsubscribe, queryId });\n }\n\n // If no subscriptions remain, close the connection to free resources.\n if (this._subscriptions.size === 0) {\n this.close();\n }\n }\n\n /**\n * Permanently close this hub connection and clean up all subscriptions.\n */\n dispose(): void {\n this._disconnected = true;\n this._subscriptions.clear();\n this._keepAlive.stop();\n this._policy.cancel();\n this._socket?.close();\n this._socket = undefined;\n }\n\n private ensureConnected(): void {\n if (this._disconnected) {\n // Reset disconnected flag when a new subscription comes in\n this._disconnected = false;\n }\n\n if (this._socket && (this._socket.readyState === WebSocket.OPEN || this._socket.readyState === WebSocket.CONNECTING)) {\n return;\n }\n\n this.openSocket();\n }\n\n private close(): void {\n this._disconnected = true;\n this._keepAlive.stop();\n this._policy.cancel();\n if (this._socket) {\n // Detach all handlers BEFORE closing so that the async onclose event cannot\n // fire after a new subscription has reset _disconnected to false and opened a\n // fresh socket. Without this, the stale onclose triggers an unintended\n // reconnect via the back-off policy, causing a 1-10 second delay before the\n // new page's queries receive their first data.\n this._socket.onopen = null;\n this._socket.onclose = null;\n this._socket.onerror = null;\n this._socket.onmessage = null;\n this._socket.close();\n }\n this._socket = undefined;\n }\n\n private openSocket(): void {\n let url = this._url;\n if (this._microservice?.length > 0) {\n const param = `${Globals.microserviceWSQueryArgument}=${encodeURIComponent(this._microservice)}`;\n url += (url.includes('?') ? '&' : '?') + param;\n }\n\n this._socket = new WebSocket(url);\n\n this._socket.onopen = () => {\n if (this._disconnected) return;\n console.log(`Hub connection established: '${url}'`);\n this._policy.reset();\n this._keepAlive.start();\n this.sendAllSubscriptions();\n };\n\n this._socket.onclose = () => {\n if (this._disconnected) return;\n console.log(`Hub connection closed: '${url}'`);\n this._keepAlive.stop();\n if (this._subscriptions.size === 0) return;\n this._policy.schedule(() => {\n if (!this._disconnected && this._subscriptions.size > 0) {\n this.openSocket();\n }\n }, this._url);\n };\n\n this._socket.onerror = (error) => {\n if (this._disconnected) return;\n console.error(`Hub connection error: '${url}'`, error);\n this._keepAlive.stop();\n // onclose will fire after onerror, triggering reconnect\n };\n\n this._socket.onmessage = (ev) => {\n if (this._disconnected) return;\n this.handleMessage(ev.data as string);\n };\n }\n\n private sendAllSubscriptions(): void {\n for (const [queryId, sub] of this._subscriptions) {\n this.sendSubscribeMessage(queryId, sub.request);\n }\n }\n\n private sendSubscribeMessage(queryId: string, request: SubscriptionRequest): void {\n this.sendMessage({\n type: HubMessageType.Subscribe,\n queryId,\n payload: request,\n });\n }\n\n private sendMessage(message: HubMessage): void {\n if (this._socket?.readyState === WebSocket.OPEN) {\n this._socket.send(JSON.stringify(message));\n }\n }\n\n private handleMessage(rawData: string): void {\n try {\n const message = JSON.parse(rawData) as HubMessage;\n\n // Every received message is activity — skip keep-alive ping if data is flowing.\n this._keepAlive.recordActivity();\n\n switch (message.type) {\n case HubMessageType.QueryResult:\n this.handleQueryResult(message);\n break;\n case HubMessageType.Pong:\n this.handlePong(message);\n break;\n case HubMessageType.Unauthorized:\n console.warn(`Hub: query '${message.queryId}' unauthorized`);\n this.handleUnauthorized(message);\n break;\n case HubMessageType.Error:\n console.error(`Hub: query '${message.queryId}' error:`, message.payload);\n break;\n }\n } catch (error) {\n console.error('Hub: error parsing message', error);\n }\n }\n\n private handleQueryResult(message: HubMessage): void {\n if (!message.queryId) return;\n\n const sub = this._subscriptions.get(message.queryId);\n if (!sub) return;\n\n const result = message.payload as QueryResult<any>;\n sub.callback(result);\n }\n\n private handleUnauthorized(message: HubMessage): void {\n if (!message.queryId) return;\n\n const sub = this._subscriptions.get(message.queryId);\n if (!sub) return;\n\n this._subscriptions.delete(message.queryId);\n sub.callback(QueryResult.unauthorized());\n }\n\n private handlePong(message: HubMessage): void {\n if (message.timestamp && this._lastPingSentTime) {\n const latency = Date.now() - message.timestamp;\n this._lastPongLatency = latency;\n this._latencySamples.push(latency);\n\n if (this._latencySamples.length > 100) {\n this._latencySamples.shift();\n }\n }\n }\n}\n"],"names":[],"mappings":";;;;;IAgBY;AAAZ,CAAA,UAAY,cAAc,EAAA;AACtB,IAAA,cAAA,CAAA,WAAA,CAAA,GAAA,WAAuB;AACvB,IAAA,cAAA,CAAA,aAAA,CAAA,GAAA,aAA2B;AAC3B,IAAA,cAAA,CAAA,aAAA,CAAA,GAAA,aAA2B;AAC3B,IAAA,cAAA,CAAA,cAAA,CAAA,GAAA,cAA6B;AAC7B,IAAA,cAAA,CAAA,OAAA,CAAA,GAAA,OAAe;AACf,IAAA,cAAA,CAAA,MAAA,CAAA,GAAA,MAAa;AACb,IAAA,cAAA,CAAA,MAAA,CAAA,GAAA,MAAa;AACb,IAAA,cAAA,CAAA,WAAA,CAAA,GAAA,WAAuB;AAC3B,CAAC,EATW,cAAc,KAAd,cAAc,GAAA,EAAA,CAAA,CAAA;MA+Cb,sBAAsB,CAAA;AAiBV,IAAA,IAAA;AACA,IAAA,aAAA;AAEA,IAAA,OAAA;AAnBb,IAAA,OAAO;IACP,aAAa,GAAG,KAAK;AACrB,IAAA,cAAc,GAAoC,IAAI,GAAG,EAAE;AAClD,IAAA,UAAU;AACnB,IAAA,iBAAiB;IACjB,gBAAgB,GAAW,CAAC;IAC5B,eAAe,GAAa,EAAE;IAStC,WAAA,CACqB,IAAY,EACZ,aAAqB,EACtC,cAAA,GAAyB,KAAK,EACb,OAAA,GAA4B,IAAI,eAAe,EAAE,EAAA;QAHjD,IAAA,CAAA,IAAI,GAAJ,IAAI;QACJ,IAAA,CAAA,aAAa,GAAb,aAAa;QAEb,IAAA,CAAA,OAAO,GAAP,OAAO;QAExB,IAAI,CAAC,UAAU,GAAG,IAAI,sBAAsB,CAAC,cAAc,EAAE,MAAK;YAC9D,IAAI,IAAI,CAAC,OAAO,EAAE,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE;AAC7C,gBAAA,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,GAAG,EAAE;AACnC,gBAAA,IAAI,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,cAAc,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACtF;AACJ,QAAA,CAAC,CAAC;IACN;AAKA,IAAA,IAAI,UAAU,GAAA;AACV,QAAA,OAAO,IAAI,CAAC,cAAc,CAAC,IAAI;IACnC;AAKA,IAAA,IAAI,WAAW,GAAA;QACX,OAAO,IAAI,CAAC,OAAO,EAAE,UAAU,KAAK,SAAS,CAAC,IAAI;IACtD;AAKA,IAAA,IAAI,eAAe,GAAA;QACf,OAAO,IAAI,CAAC,gBAAgB;IAChC;AAKA,IAAA,IAAI,cAAc,GAAA;AACd,QAAA,IAAI,IAAI,CAAC,eAAe,CAAC,MAAM,KAAK,CAAC;AAAE,YAAA,OAAO,CAAC;QAC/C,OAAO,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM;IACxF;AASA,IAAA,SAAS,CAAC,OAAe,EAAE,OAA4B,EAAE,QAA2B,EAAA;AAChF,QAAA,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;QACvD,IAAI,CAAC,eAAe,EAAE;QAEtB,IAAI,IAAI,CAAC,OAAO,EAAE,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE;AAC7C,YAAA,IAAI,CAAC,oBAAoB,CAAC,OAAO,EAAE,OAAO,CAAC;QAC/C;IAEJ;AAMA,IAAA,WAAW,CAAC,OAAe,EAAA;AACvB,QAAA,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC;QAEnC,IAAI,IAAI,CAAC,OAAO,EAAE,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE;AAC7C,YAAA,IAAI,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,cAAc,CAAC,WAAW,EAAE,OAAO,EAAE,CAAC;QACnE;QAGA,IAAI,IAAI,CAAC,cAAc,CAAC,IAAI,KAAK,CAAC,EAAE;YAChC,IAAI,CAAC,KAAK,EAAE;QAChB;IACJ;IAKA,OAAO,GAAA;AACH,QAAA,IAAI,CAAC,aAAa,GAAG,IAAI;AACzB,QAAA,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE;AAC3B,QAAA,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE;AACtB,QAAA,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE;AACrB,QAAA,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE;AACrB,QAAA,IAAI,CAAC,OAAO,GAAG,SAAS;IAC5B;IAEQ,eAAe,GAAA;AACnB,QAAA,IAAI,IAAI,CAAC,aAAa,EAAE;AAEpB,YAAA,IAAI,CAAC,aAAa,GAAG,KAAK;QAC9B;QAEA,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC,OAAO,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,UAAU,KAAK,SAAS,CAAC,UAAU,CAAC,EAAE;YAClH;QACJ;QAEA,IAAI,CAAC,UAAU,EAAE;IACrB;IAEQ,KAAK,GAAA;AACT,QAAA,IAAI,CAAC,aAAa,GAAG,IAAI;AACzB,QAAA,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE;AACtB,QAAA,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE;AACrB,QAAA,IAAI,IAAI,CAAC,OAAO,EAAE;AAMd,YAAA,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,IAAI;AAC1B,YAAA,IAAI,CAAC,OAAO,CAAC,OAAO,GAAG,IAAI;AAC3B,YAAA,IAAI,CAAC,OAAO,CAAC,OAAO,GAAG,IAAI;AAC3B,YAAA,IAAI,CAAC,OAAO,CAAC,SAAS,GAAG,IAAI;AAC7B,YAAA,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE;QACxB;AACA,QAAA,IAAI,CAAC,OAAO,GAAG,SAAS;IAC5B;IAEQ,UAAU,GAAA;AACd,QAAA,IAAI,GAAG,GAAG,IAAI,CAAC,IAAI;QACnB,IAAI,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,CAAC,EAAE;AAChC,YAAA,MAAM,KAAK,GAAG,CAAA,EAAG,OAAO,CAAC,2BAA2B,CAAA,CAAA,EAAI,kBAAkB,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE;AAChG,YAAA,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,GAAG,IAAI,KAAK;QAClD;QAEA,IAAI,CAAC,OAAO,GAAG,IAAI,SAAS,CAAC,GAAG,CAAC;AAEjC,QAAA,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,MAAK;YACvB,IAAI,IAAI,CAAC,aAAa;gBAAE;AACxB,YAAA,OAAO,CAAC,GAAG,CAAC,gCAAgC,GAAG,CAAA,CAAA,CAAG,CAAC;AACnD,YAAA,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE;AACpB,YAAA,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE;YACvB,IAAI,CAAC,oBAAoB,EAAE;AAC/B,QAAA,CAAC;AAED,QAAA,IAAI,CAAC,OAAO,CAAC,OAAO,GAAG,MAAK;YACxB,IAAI,IAAI,CAAC,aAAa;gBAAE;AACxB,YAAA,OAAO,CAAC,GAAG,CAAC,2BAA2B,GAAG,CAAA,CAAA,CAAG,CAAC;AAC9C,YAAA,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE;AACtB,YAAA,IAAI,IAAI,CAAC,cAAc,CAAC,IAAI,KAAK,CAAC;gBAAE;AACpC,YAAA,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAK;AACvB,gBAAA,IAAI,CAAC,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,cAAc,CAAC,IAAI,GAAG,CAAC,EAAE;oBACrD,IAAI,CAAC,UAAU,EAAE;gBACrB;AACJ,YAAA,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC;AACjB,QAAA,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,OAAO,GAAG,CAAC,KAAK,KAAI;YAC7B,IAAI,IAAI,CAAC,aAAa;gBAAE;YACxB,OAAO,CAAC,KAAK,CAAC,CAAA,uBAAA,EAA0B,GAAG,CAAA,CAAA,CAAG,EAAE,KAAK,CAAC;AACtD,YAAA,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE;AAE1B,QAAA,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,SAAS,GAAG,CAAC,EAAE,KAAI;YAC5B,IAAI,IAAI,CAAC,aAAa;gBAAE;AACxB,YAAA,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC,IAAc,CAAC;AACzC,QAAA,CAAC;IACL;IAEQ,oBAAoB,GAAA;QACxB,KAAK,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,cAAc,EAAE;YAC9C,IAAI,CAAC,oBAAoB,CAAC,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC;QACnD;IACJ;IAEQ,oBAAoB,CAAC,OAAe,EAAE,OAA4B,EAAA;QACtE,IAAI,CAAC,WAAW,CAAC;YACb,IAAI,EAAE,cAAc,CAAC,SAAS;YAC9B,OAAO;AACP,YAAA,OAAO,EAAE,OAAO;AACnB,SAAA,CAAC;IACN;AAEQ,IAAA,WAAW,CAAC,OAAmB,EAAA;QACnC,IAAI,IAAI,CAAC,OAAO,EAAE,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE;AAC7C,YAAA,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAC9C;IACJ;AAEQ,IAAA,aAAa,CAAC,OAAe,EAAA;AACjC,QAAA,IAAI;YACA,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAe;AAGjD,YAAA,IAAI,CAAC,UAAU,CAAC,cAAc,EAAE;AAEhC,YAAA,QAAQ,OAAO,CAAC,IAAI;gBAChB,KAAK,cAAc,CAAC,WAAW;AAC3B,oBAAA,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC;oBAC/B;gBACJ,KAAK,cAAc,CAAC,IAAI;AACpB,oBAAA,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;oBACxB;gBACJ,KAAK,cAAc,CAAC,YAAY;oBAC5B,OAAO,CAAC,IAAI,CAAC,CAAA,YAAA,EAAe,OAAO,CAAC,OAAO,CAAA,cAAA,CAAgB,CAAC;AAC5D,oBAAA,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC;oBAChC;gBACJ,KAAK,cAAc,CAAC,KAAK;AACrB,oBAAA,OAAO,CAAC,KAAK,CAAC,CAAA,YAAA,EAAe,OAAO,CAAC,OAAO,CAAA,QAAA,CAAU,EAAE,OAAO,CAAC,OAAO,CAAC;oBACxE;;QAEZ;QAAE,OAAO,KAAK,EAAE;AACZ,YAAA,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,KAAK,CAAC;QACtD;IACJ;AAEQ,IAAA,iBAAiB,CAAC,OAAmB,EAAA;QACzC,IAAI,CAAC,OAAO,CAAC,OAAO;YAAE;AAEtB,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC;AACpD,QAAA,IAAI,CAAC,GAAG;YAAE;AAEV,QAAA,MAAM,MAAM,GAAG,OAAO,CAAC,OAA2B;AAClD,QAAA,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC;IACxB;AAEQ,IAAA,kBAAkB,CAAC,OAAmB,EAAA;QAC1C,IAAI,CAAC,OAAO,CAAC,OAAO;YAAE;AAEtB,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC;AACpD,QAAA,IAAI,CAAC,GAAG;YAAE;QAEV,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;QAC3C,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,YAAY,EAAE,CAAC;IAC5C;AAEQ,IAAA,UAAU,CAAC,OAAmB,EAAA;QAClC,IAAI,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC,iBAAiB,EAAE;YAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,SAAS;AAC9C,YAAA,IAAI,CAAC,gBAAgB,GAAG,OAAO;AAC/B,YAAA,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC;YAElC,IAAI,IAAI,CAAC,eAAe,CAAC,MAAM,GAAG,GAAG,EAAE;AACnC,gBAAA,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE;YAChC;QACJ;IACJ;AACH;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"when_tracking_owners.d.ts","sourceRoot":"","sources":["../../../../queries/for_ObservableQueryDiagnostics/when_tracking_owners.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, it } from 'vitest';
|
|
2
|
+
import { ObservableQueryDiagnostics, QueryInstanceCache } from '../../queries';
|
|
3
|
+
describe('when tracking query owners', () => {
|
|
4
|
+
it('should publish diagnostics snapshots through an observable', () => {
|
|
5
|
+
const diagnostics = new ObservableQueryDiagnostics(new QueryInstanceCache(0), () => undefined, () => ({ queryTransportMethod: 'ServerSentEvents', queryDirectMode: false }));
|
|
6
|
+
const snapshots = [];
|
|
7
|
+
diagnostics.snapshots$.subscribe(snapshot => snapshots.push(JSON.stringify(snapshot.ownership)));
|
|
8
|
+
diagnostics.beginTracking('cache-key', 'OrdersPage');
|
|
9
|
+
snapshots.should.have.lengthOf(1);
|
|
10
|
+
snapshots[0].should.contain('cache-key');
|
|
11
|
+
snapshots[0].should.contain('OrdersPage');
|
|
12
|
+
diagnostics.endTracking('cache-key');
|
|
13
|
+
snapshots.should.have.lengthOf(2);
|
|
14
|
+
snapshots[1].should.equal('{"ownersByQueryKey":{},"queriesByOwner":{}}');
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
//# sourceMappingURL=when_tracking_owners.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"when_tracking_owners.js","sourceRoot":"","sources":["../../../../queries/for_ObservableQueryDiagnostics/when_tracking_owners.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAE/E,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IACxC,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QAClE,MAAM,WAAW,GAAG,IAAI,0BAA0B,CAC9C,IAAI,kBAAkB,CAAC,CAAC,CAAC,EACzB,GAAG,EAAE,CAAC,SAAS,EACf,GAAG,EAAE,CAAC,CAAC,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,CAC/E,CAAC;QAEF,MAAM,SAAS,GAAa,EAAE,CAAC;QAC/B,WAAW,CAAC,UAAU,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAEjG,WAAW,CAAC,aAAa,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;QACrD,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAClC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QACzC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QAE1C,WAAW,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;QACrC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAClC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,6CAA6C,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"with_a_changed_cache_key.js","sourceRoot":"","sources":["../../../../../queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_changed_cache_key.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAIlG,QAAQ,CAAC,mDAAmD,EAAE,GAAG,EAAE;IAC/D,IAAI,gBAAwB,CAAC;IAC7B,IAAI,uBAA+B,CAAC;IAEpC,UAAU,CAAC,GAAG,EAAE;QACZ,gBAAgB,GAAG,CAAC,CAAC;QACrB,uBAAuB,GAAG,CAAC,CAAC;QAC5B,MAAM,OAAO,GAAG,GAAkC,EAAE;YAChD,gBAAgB,EAAE,CAAC;YACnB,OAAO;gBACH,UAAU,EAAE,CAAC;gBACb,eAAe,EAAE,CAAC;gBAClB,cAAc,EAAE,CAAC;gBACjB,SAAS,EAAE,GAAG,EAAE,GAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"with_a_changed_cache_key.js","sourceRoot":"","sources":["../../../../../queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_changed_cache_key.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAIlG,QAAQ,CAAC,mDAAmD,EAAE,GAAG,EAAE;IAC/D,IAAI,gBAAwB,CAAC;IAC7B,IAAI,uBAA+B,CAAC;IAEpC,UAAU,CAAC,GAAG,EAAE;QACZ,gBAAgB,GAAG,CAAC,CAAC;QACrB,uBAAuB,GAAG,CAAC,CAAC;QAC5B,MAAM,OAAO,GAAG,GAAkC,EAAE;YAChD,gBAAgB,EAAE,CAAC;YACnB,OAAO;gBACH,UAAU,EAAE,CAAC;gBACb,eAAe,EAAE,CAAC;gBAClB,cAAc,EAAE,CAAC;gBACjB,SAAS,EAAE,GAAG,EAAE,GAAE,CAAC;gBACf,WAAW,EAAE,KAAK;gBAClB,WAAW,EAAE,GAAG,EAAE,GAAE,CAAC;gBACzB,OAAO,EAAE,GAAG,EAAE,GAAG,uBAAuB,EAAE,CAAC,CAAC,CAAC;aAChD,CAAC;QACN,CAAC,CAAC;QACF,sBAAsB,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;QAC9C,sBAAsB,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACX,sBAAsB,EAAE,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QAClE,uBAAuB,CAAC,MAAM,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACvD,gBAAgB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
|
|
@@ -6,7 +6,7 @@ describe('when getting or creating with a new cache key', () => {
|
|
|
6
6
|
factoryCallCount = 0;
|
|
7
7
|
const factory = () => {
|
|
8
8
|
factoryCallCount++;
|
|
9
|
-
return { queryCount: 0, lastPingLatency: 0, averageLatency: 0, subscribe: () => { }, unsubscribe: () => { }, dispose: () => { } };
|
|
9
|
+
return { queryCount: 0, lastPingLatency: 0, averageLatency: 0, isConnected: false, subscribe: () => { }, unsubscribe: () => { }, dispose: () => { } };
|
|
10
10
|
};
|
|
11
11
|
multiplexer = getOrCreateMultiplexer(factory, 'test-key', 1);
|
|
12
12
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"with_a_new_cache_key.js","sourceRoot":"","sources":["../../../../../queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_new_cache_key.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAI9H,QAAQ,CAAC,+CAA+C,EAAE,GAAG,EAAE;IAC3D,IAAI,WAAuC,CAAC;IAC5C,IAAI,gBAAwB,CAAC;IAE7B,UAAU,CAAC,GAAG,EAAE;QACZ,gBAAgB,GAAG,CAAC,CAAC;QACrB,MAAM,OAAO,GAAG,GAAkC,EAAE;YAChD,gBAAgB,EAAE,CAAC;YACnB,OAAO,EAAE,UAAU,EAAE,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"with_a_new_cache_key.js","sourceRoot":"","sources":["../../../../../queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_new_cache_key.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAI9H,QAAQ,CAAC,+CAA+C,EAAE,GAAG,EAAE;IAC3D,IAAI,WAAuC,CAAC;IAC5C,IAAI,gBAAwB,CAAC;IAE7B,UAAU,CAAC,GAAG,EAAE;QACZ,gBAAgB,GAAG,CAAC,CAAC;QACrB,MAAM,OAAO,GAAG,GAAkC,EAAE;YAChD,gBAAgB,EAAE,CAAC;YACnB,OAAO,EAAE,UAAU,EAAE,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAC;QACvJ,CAAC,CAAC;QACF,WAAW,GAAG,sBAAsB,CAAC,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACX,sBAAsB,EAAE,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACnC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,0BAA0B,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACrD,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
|
|
@@ -6,7 +6,7 @@ describe('when getting or creating with an explicit size', () => {
|
|
|
6
6
|
factoryCallCount = 0;
|
|
7
7
|
const factory = () => {
|
|
8
8
|
factoryCallCount++;
|
|
9
|
-
return { queryCount: 0, lastPingLatency: 0, averageLatency: 0, subscribe: () => { }, unsubscribe: () => { }, dispose: () => { } };
|
|
9
|
+
return { queryCount: 0, lastPingLatency: 0, averageLatency: 0, isConnected: false, subscribe: () => { }, unsubscribe: () => { }, dispose: () => { } };
|
|
10
10
|
};
|
|
11
11
|
multiplexer = getOrCreateMultiplexer(factory, 'test-key', 3);
|
|
12
12
|
});
|