@cratis/arc 20.3.1 → 20.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/dist/cjs/queries/ObservableQueryConnectionFactory.js +11 -2
  2. package/dist/cjs/queries/ObservableQueryConnectionFactory.js.map +1 -1
  3. package/dist/cjs/queries/ObservableQueryMultiplexer.d.ts +1 -1
  4. package/dist/cjs/queries/ObservableQueryMultiplexer.d.ts.map +1 -1
  5. package/dist/cjs/queries/ObservableQueryMultiplexer.js +2 -2
  6. package/dist/cjs/queries/ObservableQueryMultiplexer.js.map +1 -1
  7. package/dist/cjs/queries/QueryInstanceCache.d.ts +0 -1
  8. package/dist/cjs/queries/QueryInstanceCache.d.ts.map +1 -1
  9. package/dist/cjs/queries/QueryInstanceCache.js +10 -25
  10. package/dist/cjs/queries/QueryInstanceCache.js.map +1 -1
  11. package/dist/cjs/queries/for_ObservableQueryConnectionFactory/when_creating/with_hub_mode_and_sse_transport_and_connection_count_exceeding_safe_limit.d.ts +2 -0
  12. package/dist/cjs/queries/for_ObservableQueryConnectionFactory/when_creating/with_hub_mode_and_sse_transport_and_connection_count_exceeding_safe_limit.d.ts.map +1 -0
  13. package/dist/cjs/queries/for_ObservableQueryConnectionFactory/when_creating/with_hub_mode_and_sse_transport_and_connection_count_within_safe_limit.d.ts +2 -0
  14. package/dist/cjs/queries/for_ObservableQueryConnectionFactory/when_creating/with_hub_mode_and_sse_transport_and_connection_count_within_safe_limit.d.ts.map +1 -0
  15. package/dist/cjs/queries/for_ObservableQueryConnectionFactory/when_creating/with_hub_mode_and_websocket_transport_and_high_connection_count.d.ts +2 -0
  16. package/dist/cjs/queries/for_ObservableQueryConnectionFactory/when_creating/with_hub_mode_and_websocket_transport_and_high_connection_count.d.ts.map +1 -0
  17. package/dist/cjs/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_changed_cache_key.d.ts +2 -0
  18. package/dist/cjs/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_changed_cache_key.d.ts.map +1 -0
  19. package/dist/cjs/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_new_cache_key.d.ts +2 -0
  20. package/dist/cjs/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_new_cache_key.d.ts.map +1 -0
  21. package/dist/cjs/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_an_explicit_size.d.ts +2 -0
  22. package/dist/cjs/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_an_explicit_size.d.ts.map +1 -0
  23. package/dist/cjs/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_the_same_cache_key.d.ts +2 -0
  24. package/dist/cjs/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_the_same_cache_key.d.ts.map +1 -0
  25. package/dist/cjs/queries/for_QueryInstanceCache/when_acquiring/after_release.d.ts +2 -0
  26. package/dist/cjs/queries/for_QueryInstanceCache/when_acquiring/after_release.d.ts.map +1 -0
  27. package/dist/esm/queries/ObservableQueryConnectionFactory.js +11 -2
  28. package/dist/esm/queries/ObservableQueryConnectionFactory.js.map +1 -1
  29. package/dist/esm/queries/ObservableQueryMultiplexer.d.ts +1 -1
  30. package/dist/esm/queries/ObservableQueryMultiplexer.d.ts.map +1 -1
  31. package/dist/esm/queries/ObservableQueryMultiplexer.js +2 -2
  32. package/dist/esm/queries/ObservableQueryMultiplexer.js.map +1 -1
  33. package/dist/esm/queries/QueryInstanceCache.d.ts +0 -1
  34. package/dist/esm/queries/QueryInstanceCache.d.ts.map +1 -1
  35. package/dist/esm/queries/QueryInstanceCache.js +10 -25
  36. package/dist/esm/queries/QueryInstanceCache.js.map +1 -1
  37. package/dist/esm/queries/for_ObservableQueryConnectionFactory/when_creating/with_hub_mode_and_sse_transport_and_connection_count_exceeding_safe_limit.d.ts +2 -0
  38. package/dist/esm/queries/for_ObservableQueryConnectionFactory/when_creating/with_hub_mode_and_sse_transport_and_connection_count_exceeding_safe_limit.d.ts.map +1 -0
  39. package/dist/esm/queries/for_ObservableQueryConnectionFactory/when_creating/with_hub_mode_and_sse_transport_and_connection_count_exceeding_safe_limit.js +55 -0
  40. package/dist/esm/queries/for_ObservableQueryConnectionFactory/when_creating/with_hub_mode_and_sse_transport_and_connection_count_exceeding_safe_limit.js.map +1 -0
  41. package/dist/esm/queries/for_ObservableQueryConnectionFactory/when_creating/with_hub_mode_and_sse_transport_and_connection_count_within_safe_limit.d.ts +2 -0
  42. package/dist/esm/queries/for_ObservableQueryConnectionFactory/when_creating/with_hub_mode_and_sse_transport_and_connection_count_within_safe_limit.d.ts.map +1 -0
  43. package/dist/esm/queries/for_ObservableQueryConnectionFactory/when_creating/with_hub_mode_and_sse_transport_and_connection_count_within_safe_limit.js +49 -0
  44. package/dist/esm/queries/for_ObservableQueryConnectionFactory/when_creating/with_hub_mode_and_sse_transport_and_connection_count_within_safe_limit.js.map +1 -0
  45. package/dist/esm/queries/for_ObservableQueryConnectionFactory/when_creating/with_hub_mode_and_websocket_transport_and_high_connection_count.d.ts +2 -0
  46. package/dist/esm/queries/for_ObservableQueryConnectionFactory/when_creating/with_hub_mode_and_websocket_transport_and_high_connection_count.d.ts.map +1 -0
  47. package/dist/esm/queries/for_ObservableQueryConnectionFactory/when_creating/with_hub_mode_and_websocket_transport_and_high_connection_count.js +48 -0
  48. package/dist/esm/queries/for_ObservableQueryConnectionFactory/when_creating/with_hub_mode_and_websocket_transport_and_high_connection_count.js.map +1 -0
  49. package/dist/esm/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_changed_cache_key.d.ts +2 -0
  50. package/dist/esm/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_changed_cache_key.d.ts.map +1 -0
  51. package/dist/esm/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_changed_cache_key.js +32 -0
  52. package/dist/esm/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_changed_cache_key.js.map +1 -0
  53. package/dist/esm/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_new_cache_key.d.ts +2 -0
  54. package/dist/esm/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_new_cache_key.d.ts.map +1 -0
  55. package/dist/esm/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_new_cache_key.js +23 -0
  56. package/dist/esm/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_new_cache_key.js.map +1 -0
  57. package/dist/esm/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_an_explicit_size.d.ts +2 -0
  58. package/dist/esm/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_an_explicit_size.d.ts.map +1 -0
  59. package/dist/esm/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_an_explicit_size.js +23 -0
  60. package/dist/esm/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_an_explicit_size.js.map +1 -0
  61. package/dist/esm/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_the_same_cache_key.d.ts +2 -0
  62. package/dist/esm/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_the_same_cache_key.d.ts.map +1 -0
  63. package/dist/esm/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_the_same_cache_key.js +25 -0
  64. package/dist/esm/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_the_same_cache_key.js.map +1 -0
  65. package/dist/esm/queries/for_QueryInstanceCache/when_acquiring/after_release.d.ts +2 -0
  66. package/dist/esm/queries/for_QueryInstanceCache/when_acquiring/after_release.d.ts.map +1 -0
  67. package/dist/esm/queries/for_QueryInstanceCache/when_acquiring/after_release.js +23 -0
  68. package/dist/esm/queries/for_QueryInstanceCache/when_acquiring/after_release.js.map +1 -0
  69. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.js +4 -1
  70. package/dist/esm/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.js.map +1 -1
  71. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  72. package/package.json +1 -1
  73. package/queries/ObservableQueryConnectionFactory.ts +23 -2
  74. package/queries/ObservableQueryMultiplexer.ts +3 -2
  75. package/queries/QueryInstanceCache.ts +27 -42
  76. package/queries/for_ObservableQueryConnectionFactory/when_creating/with_hub_mode_and_sse_transport_and_connection_count_exceeding_safe_limit.ts +69 -0
  77. package/queries/for_ObservableQueryConnectionFactory/when_creating/with_hub_mode_and_sse_transport_and_connection_count_within_safe_limit.ts +61 -0
  78. package/queries/for_ObservableQueryConnectionFactory/when_creating/with_hub_mode_and_websocket_transport_and_high_connection_count.ts +60 -0
  79. package/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_changed_cache_key.ts +42 -0
  80. package/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_new_cache_key.ts +34 -0
  81. package/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_an_explicit_size.ts +33 -0
  82. package/queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_the_same_cache_key.ts +35 -0
  83. package/queries/for_QueryInstanceCache/when_acquiring/after_release.ts +31 -0
  84. package/queries/for_QueryInstanceCache/when_releasing/the_only_subscriber_outside_development_mode.ts +4 -1
@@ -32,9 +32,18 @@ function createDirectWebSocketConnection(descriptor) {
32
32
  function createDirectSSEConnection(descriptor) {
33
33
  return new ServerSentEventQueryConnection.ServerSentEventQueryConnection(buildDirectUrl(descriptor));
34
34
  }
35
+ const MAX_SAFE_SSE_CONNECTIONS = 4;
35
36
  function createMultiplexedConnection(descriptor, isSSE) {
36
37
  const transport = isSSE ? 'sse' : 'ws';
37
- const cacheKey = `${Globals.Globals.queryConnectionCount}|${transport}|${descriptor.origin}|${descriptor.apiBasePath}|${descriptor.microservice}`;
38
+ const requestedCount = Globals.Globals.queryConnectionCount;
39
+ const effectiveCount = isSSE ? Math.min(requestedCount, MAX_SAFE_SSE_CONNECTIONS) : requestedCount;
40
+ if (isSSE && requestedCount > MAX_SAFE_SSE_CONNECTIONS) {
41
+ console.warn(`[Arc] queryConnectionCount (${requestedCount}) exceeds the safe limit for SSE transport (${MAX_SAFE_SSE_CONNECTIONS}). ` +
42
+ `HTTP/1.1 browsers allow at most 6 concurrent connections per origin; ` +
43
+ `using more SSE connections blocks subscribe/unsubscribe requests, causing queries to hang. ` +
44
+ `Capping at ${MAX_SAFE_SSE_CONNECTIONS}. Enable HTTP/2 on your server to use a higher connection count.`);
45
+ }
46
+ const cacheKey = `${requestedCount}|${transport}|${descriptor.origin}|${descriptor.apiBasePath}|${descriptor.microservice}`;
38
47
  const multiplexer = ObservableQueryMultiplexer.getOrCreateMultiplexer(() => {
39
48
  if (isSSE) {
40
49
  const sseRoute = joinPaths.joinPaths(descriptor.apiBasePath, ServerSentEventQueryConnection.SSE_HUB_ROUTE);
@@ -52,7 +61,7 @@ function createMultiplexedConnection(descriptor, isSSE) {
52
61
  const wsUrl = `${secure ? 'wss' : 'ws'}://${hubUrl.host}${hubUrl.pathname}${hubUrl.search}`;
53
62
  return new WebSocketHubConnection.WebSocketHubConnection(wsUrl, descriptor.microservice);
54
63
  }
55
- }, cacheKey);
64
+ }, cacheKey, effectiveCount);
56
65
  return new ObservableQueryMultiplexer.MultiplexedObservableQueryConnection(multiplexer, descriptor.queryName);
57
66
  }
58
67
 
@@ -1 +1 @@
1
- {"version":3,"file":"ObservableQueryConnectionFactory.js","sources":["../../../queries/ObservableQueryConnectionFactory.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 { joinPaths } from '../joinPaths';\nimport { UrlHelpers } from '../UrlHelpers';\nimport { IObservableQueryConnection } from './IObservableQueryConnection';\nimport { ObservableQueryConnection } from './ObservableQueryConnection';\nimport { QueryTransportMethod } from './QueryTransportMethod';\nimport { ServerSentEventQueryConnection, SSE_HUB_ROUTE } from './ServerSentEventQueryConnection';\nimport { ServerSentEventHubConnection } from './ServerSentEventHubConnection';\nimport { WebSocketHubConnection } from './WebSocketHubConnection';\nimport { MultiplexedObservableQueryConnection, WS_HUB_ROUTE, getOrCreateMultiplexer } from './ObservableQueryMultiplexer';\n\n/**\n * The SSE subscribe POST endpoint route.\n */\nexport const SSE_SUBSCRIBE_ROUTE = '/.cratis/queries/sse/subscribe';\n\n/**\n * The SSE unsubscribe POST endpoint route.\n */\nexport const SSE_UNSUBSCRIBE_ROUTE = '/.cratis/queries/sse/unsubscribe';\n\n/**\n * Describes what the factory needs to know about a query in order to create\n * the right connection. Transport details are intentionally absent — the\n * factory reads those from {@link Globals}.\n */\nexport interface QueryConnectionDescriptor {\n /** The route template (e.g. `/api/accounts/debit/all-accounts`). */\n route: string;\n /** Fully-qualified backend query name used by hub transports. */\n queryName: string;\n /** Origin base URL (e.g. `http://localhost:5000`). */\n origin: string;\n /** API base path prefix. */\n apiBasePath: string;\n /** Microservice identifier. */\n microservice: string;\n /** Route/query arguments for the current subscription. */\n args?: object;\n}\n\n/**\n * Single entry-point for creating observable query connections.\n *\n * The transport is chosen as a 2x2 matrix of (direct | hub) x (WebSocket | SSE):\n *\n * | | Direct (per-query URL) | Hub (centralized endpoint) |\n * |----------------|-------------------------------|-------------------------------|\n * | **WebSocket** | ObservableQueryConnection | MultiplexedObservableQueryConnection |\n * | **SSE** | ServerSentEventQueryConnection | MultiplexedObservableQueryConnection |\n *\n * In multiplexed mode, both transports share the same multiplexer — it creates either\n * WebSocket or SSE connections depending on the transport setting.\n *\n * {@link ObservableQueryFor} never needs to know which transport is in use.\n */\nexport function createObservableQueryConnection<TDataType>(descriptor: QueryConnectionDescriptor): IObservableQueryConnection<TDataType> {\n const isSSE = Globals.queryTransportMethod === QueryTransportMethod.ServerSentEvents;\n\n if (Globals.queryDirectMode) {\n return isSSE\n ? createDirectSSEConnection<TDataType>(descriptor)\n : createDirectWebSocketConnection<TDataType>(descriptor);\n }\n\n return createMultiplexedConnection<TDataType>(descriptor, isSSE);\n}\n\n// ---- Direct mode: one connection per query, hitting the query's own URL ----\n\nfunction buildDirectUrl(descriptor: QueryConnectionDescriptor): URL {\n const { route } = UrlHelpers.replaceRouteParameters(descriptor.route, descriptor.args as object);\n const actualRoute = joinPaths(descriptor.apiBasePath, route);\n return UrlHelpers.createUrlFrom(descriptor.origin, descriptor.apiBasePath, actualRoute);\n}\n\nfunction createDirectWebSocketConnection<TDataType>(descriptor: QueryConnectionDescriptor): IObservableQueryConnection<TDataType> {\n return new ObservableQueryConnection<TDataType>(buildDirectUrl(descriptor), descriptor.microservice);\n}\n\nfunction createDirectSSEConnection<TDataType>(descriptor: QueryConnectionDescriptor): IObservableQueryConnection<TDataType> {\n return new ServerSentEventQueryConnection<TDataType>(buildDirectUrl(descriptor));\n}\n\n// ---- Multiplexed mode: centralized endpoint, multiple queries share connections ----\n\nfunction createMultiplexedConnection<TDataType>(descriptor: QueryConnectionDescriptor, isSSE: boolean): IObservableQueryConnection<TDataType> {\n const transport = isSSE ? 'sse' : 'ws';\n const cacheKey = `${Globals.queryConnectionCount}|${transport}|${descriptor.origin}|${descriptor.apiBasePath}|${descriptor.microservice}`;\n\n const multiplexer = getOrCreateMultiplexer(() => {\n if (isSSE) {\n const sseRoute = joinPaths(descriptor.apiBasePath, SSE_HUB_ROUTE);\n const sseUrl = UrlHelpers.createUrlFrom(descriptor.origin, descriptor.apiBasePath, sseRoute);\n const subscribeRoute = joinPaths(descriptor.apiBasePath, SSE_SUBSCRIBE_ROUTE);\n const subscribeUrl = UrlHelpers.createUrlFrom(descriptor.origin, descriptor.apiBasePath, subscribeRoute);\n const unsubscribeRoute = joinPaths(descriptor.apiBasePath, SSE_UNSUBSCRIBE_ROUTE);\n const unsubscribeUrl = UrlHelpers.createUrlFrom(descriptor.origin, descriptor.apiBasePath, unsubscribeRoute);\n return new ServerSentEventHubConnection(\n sseUrl.toString(),\n subscribeUrl.toString(),\n unsubscribeUrl.toString(),\n descriptor.microservice,\n );\n } else {\n const hubRoute = joinPaths(descriptor.apiBasePath, WS_HUB_ROUTE);\n const hubUrl = UrlHelpers.createUrlFrom(descriptor.origin, descriptor.apiBasePath, hubRoute);\n const secure = hubUrl.protocol?.indexOf('https') === 0;\n const wsUrl = `${secure ? 'wss' : 'ws'}://${hubUrl.host}${hubUrl.pathname}${hubUrl.search}`;\n return new WebSocketHubConnection(wsUrl, descriptor.microservice);\n }\n }, cacheKey);\n\n return new MultiplexedObservableQueryConnection<TDataType>(multiplexer, descriptor.queryName);\n}\n"],"names":["Globals","QueryTransportMethod","UrlHelpers","joinPaths","ObservableQueryConnection","ServerSentEventQueryConnection","getOrCreateMultiplexer","SSE_HUB_ROUTE","ServerSentEventHubConnection","WS_HUB_ROUTE","WebSocketHubConnection","MultiplexedObservableQueryConnection"],"mappings":"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"}
1
+ {"version":3,"file":"ObservableQueryConnectionFactory.js","sources":["../../../queries/ObservableQueryConnectionFactory.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 { joinPaths } from '../joinPaths';\nimport { UrlHelpers } from '../UrlHelpers';\nimport { IObservableQueryConnection } from './IObservableQueryConnection';\nimport { ObservableQueryConnection } from './ObservableQueryConnection';\nimport { QueryTransportMethod } from './QueryTransportMethod';\nimport { ServerSentEventQueryConnection, SSE_HUB_ROUTE } from './ServerSentEventQueryConnection';\nimport { ServerSentEventHubConnection } from './ServerSentEventHubConnection';\nimport { WebSocketHubConnection } from './WebSocketHubConnection';\nimport { MultiplexedObservableQueryConnection, WS_HUB_ROUTE, getOrCreateMultiplexer } from './ObservableQueryMultiplexer';\n\n/**\n * The SSE subscribe POST endpoint route.\n */\nexport const SSE_SUBSCRIBE_ROUTE = '/.cratis/queries/sse/subscribe';\n\n/**\n * The SSE unsubscribe POST endpoint route.\n */\nexport const SSE_UNSUBSCRIBE_ROUTE = '/.cratis/queries/sse/unsubscribe';\n\n/**\n * Describes what the factory needs to know about a query in order to create\n * the right connection. Transport details are intentionally absent — the\n * factory reads those from {@link Globals}.\n */\nexport interface QueryConnectionDescriptor {\n /** The route template (e.g. `/api/accounts/debit/all-accounts`). */\n route: string;\n /** Fully-qualified backend query name used by hub transports. */\n queryName: string;\n /** Origin base URL (e.g. `http://localhost:5000`). */\n origin: string;\n /** API base path prefix. */\n apiBasePath: string;\n /** Microservice identifier. */\n microservice: string;\n /** Route/query arguments for the current subscription. */\n args?: object;\n}\n\n/**\n * Single entry-point for creating observable query connections.\n *\n * The transport is chosen as a 2x2 matrix of (direct | hub) x (WebSocket | SSE):\n *\n * | | Direct (per-query URL) | Hub (centralized endpoint) |\n * |----------------|-------------------------------|-------------------------------|\n * | **WebSocket** | ObservableQueryConnection | MultiplexedObservableQueryConnection |\n * | **SSE** | ServerSentEventQueryConnection | MultiplexedObservableQueryConnection |\n *\n * In multiplexed mode, both transports share the same multiplexer — it creates either\n * WebSocket or SSE connections depending on the transport setting.\n *\n * {@link ObservableQueryFor} never needs to know which transport is in use.\n */\nexport function createObservableQueryConnection<TDataType>(descriptor: QueryConnectionDescriptor): IObservableQueryConnection<TDataType> {\n const isSSE = Globals.queryTransportMethod === QueryTransportMethod.ServerSentEvents;\n\n if (Globals.queryDirectMode) {\n return isSSE\n ? createDirectSSEConnection<TDataType>(descriptor)\n : createDirectWebSocketConnection<TDataType>(descriptor);\n }\n\n return createMultiplexedConnection<TDataType>(descriptor, isSSE);\n}\n\n// ---- Direct mode: one connection per query, hitting the query's own URL ----\n\nfunction buildDirectUrl(descriptor: QueryConnectionDescriptor): URL {\n const { route } = UrlHelpers.replaceRouteParameters(descriptor.route, descriptor.args as object);\n const actualRoute = joinPaths(descriptor.apiBasePath, route);\n return UrlHelpers.createUrlFrom(descriptor.origin, descriptor.apiBasePath, actualRoute);\n}\n\nfunction createDirectWebSocketConnection<TDataType>(descriptor: QueryConnectionDescriptor): IObservableQueryConnection<TDataType> {\n return new ObservableQueryConnection<TDataType>(buildDirectUrl(descriptor), descriptor.microservice);\n}\n\nfunction createDirectSSEConnection<TDataType>(descriptor: QueryConnectionDescriptor): IObservableQueryConnection<TDataType> {\n return new ServerSentEventQueryConnection<TDataType>(buildDirectUrl(descriptor));\n}\n\n// ---- Multiplexed mode: centralized endpoint, multiple queries share connections ----\n\n/**\n * Maximum number of SSE hub connections that fits safely within the HTTP/1.1 browser\n * per-origin connection limit (typically 6 for Chrome/Firefox). Each SSE EventSource\n * occupies one persistent connection slot indefinitely. Exceeding this cap blocks the\n * subscribe/unsubscribe POST requests that share the same pool, causing queries to hang.\n * Servers configured for HTTP/2 do not have this restriction.\n */\nconst MAX_SAFE_SSE_CONNECTIONS = 4;\n\nfunction createMultiplexedConnection<TDataType>(descriptor: QueryConnectionDescriptor, isSSE: boolean): IObservableQueryConnection<TDataType> {\n const transport = isSSE ? 'sse' : 'ws';\n const requestedCount = Globals.queryConnectionCount;\n const effectiveCount = isSSE ? Math.min(requestedCount, MAX_SAFE_SSE_CONNECTIONS) : requestedCount;\n\n if (isSSE && requestedCount > MAX_SAFE_SSE_CONNECTIONS) {\n console.warn(\n `[Arc] queryConnectionCount (${requestedCount}) exceeds the safe limit for SSE transport (${MAX_SAFE_SSE_CONNECTIONS}). ` +\n `HTTP/1.1 browsers allow at most 6 concurrent connections per origin; ` +\n `using more SSE connections blocks subscribe/unsubscribe requests, causing queries to hang. ` +\n `Capping at ${MAX_SAFE_SSE_CONNECTIONS}. Enable HTTP/2 on your server to use a higher connection count.`\n );\n }\n\n const cacheKey = `${requestedCount}|${transport}|${descriptor.origin}|${descriptor.apiBasePath}|${descriptor.microservice}`;\n\n const multiplexer = getOrCreateMultiplexer(() => {\n if (isSSE) {\n const sseRoute = joinPaths(descriptor.apiBasePath, SSE_HUB_ROUTE);\n const sseUrl = UrlHelpers.createUrlFrom(descriptor.origin, descriptor.apiBasePath, sseRoute);\n const subscribeRoute = joinPaths(descriptor.apiBasePath, SSE_SUBSCRIBE_ROUTE);\n const subscribeUrl = UrlHelpers.createUrlFrom(descriptor.origin, descriptor.apiBasePath, subscribeRoute);\n const unsubscribeRoute = joinPaths(descriptor.apiBasePath, SSE_UNSUBSCRIBE_ROUTE);\n const unsubscribeUrl = UrlHelpers.createUrlFrom(descriptor.origin, descriptor.apiBasePath, unsubscribeRoute);\n return new ServerSentEventHubConnection(\n sseUrl.toString(),\n subscribeUrl.toString(),\n unsubscribeUrl.toString(),\n descriptor.microservice,\n );\n } else {\n const hubRoute = joinPaths(descriptor.apiBasePath, WS_HUB_ROUTE);\n const hubUrl = UrlHelpers.createUrlFrom(descriptor.origin, descriptor.apiBasePath, hubRoute);\n const secure = hubUrl.protocol?.indexOf('https') === 0;\n const wsUrl = `${secure ? 'wss' : 'ws'}://${hubUrl.host}${hubUrl.pathname}${hubUrl.search}`;\n return new WebSocketHubConnection(wsUrl, descriptor.microservice);\n }\n }, cacheKey, effectiveCount);\n\n return new MultiplexedObservableQueryConnection<TDataType>(multiplexer, descriptor.queryName);\n}\n"],"names":["Globals","QueryTransportMethod","UrlHelpers","joinPaths","ObservableQueryConnection","ServerSentEventQueryConnection","getOrCreateMultiplexer","SSE_HUB_ROUTE","ServerSentEventHubConnection","WS_HUB_ROUTE","WebSocketHubConnection","MultiplexedObservableQueryConnection"],"mappings":"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"}
@@ -25,6 +25,6 @@ export declare class MultiplexedObservableQueryConnection<TDataType> implements
25
25
  connect(dataReceived: DataReceived<TDataType>, queryArguments?: object): void;
26
26
  disconnect(): void;
27
27
  }
28
- export declare function getOrCreateMultiplexer(connectionFactory: () => IObservableQueryHubConnection, cacheKey: string): ObservableQueryMultiplexer;
28
+ export declare function getOrCreateMultiplexer(connectionFactory: () => IObservableQueryHubConnection, cacheKey: string, size?: number): ObservableQueryMultiplexer;
29
29
  export declare function resetSharedMultiplexer(): void;
30
30
  //# sourceMappingURL=ObservableQueryMultiplexer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ObservableQueryMultiplexer.d.ts","sourceRoot":"","sources":["../../../queries/ObservableQueryMultiplexer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAS3D,eAAO,MAAM,YAAY,wBAAwB,CAAC;AAUlD,qBAAa,0BAA0B;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkC;IAC/D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAOnB,IAAI,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,6BAA6B;IAQhF,IAAI,IAAI,IAAI,MAAM,CAEjB;IAKD,IAAI,eAAe,IAAI,MAAM,CAK5B;IAKD,IAAI,cAAc,IAAI,MAAM,CAI3B;IAUD,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,YAAY,CAAC,GAAG,CAAC,GAAG,MAAM,IAAI;IAezG,OAAO,IAAI,IAAI;IAMf,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,wBAAwB;CA6BnC;AAOD,qBAAa,oCAAoC,CAAC,SAAS,CAAE,YAAW,0BAA0B,CAAC,SAAS,CAAC;IASrG,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAT/B,OAAO,CAAC,QAAQ,CAAC,CAAa;gBAQT,KAAK,EAAE,0BAA0B,EACjC,UAAU,EAAE,MAAM;IAKvC,IAAI,eAAe,IAAI,MAAM,CAE5B;IAGD,IAAI,cAAc,IAAI,MAAM,CAE3B;IAGD,OAAO,CAAC,YAAY,EAAE,YAAY,CAAC,SAAS,CAAC,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI;IAS7E,UAAU,IAAI,IAAI;CAIrB;AAcD,wBAAgB,sBAAsB,CAAC,iBAAiB,EAAE,MAAM,6BAA6B,EAAE,QAAQ,EAAE,MAAM,GAAG,0BAA0B,CAS3I;AAMD,wBAAgB,sBAAsB,IAAI,IAAI,CAI7C"}
1
+ {"version":3,"file":"ObservableQueryMultiplexer.d.ts","sourceRoot":"","sources":["../../../queries/ObservableQueryMultiplexer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAS3D,eAAO,MAAM,YAAY,wBAAwB,CAAC;AAUlD,qBAAa,0BAA0B;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkC;IAC/D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAOnB,IAAI,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,6BAA6B;IAQhF,IAAI,IAAI,IAAI,MAAM,CAEjB;IAKD,IAAI,eAAe,IAAI,MAAM,CAK5B;IAKD,IAAI,cAAc,IAAI,MAAM,CAI3B;IAUD,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,YAAY,CAAC,GAAG,CAAC,GAAG,MAAM,IAAI;IAezG,OAAO,IAAI,IAAI;IAMf,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,wBAAwB;CA6BnC;AAOD,qBAAa,oCAAoC,CAAC,SAAS,CAAE,YAAW,0BAA0B,CAAC,SAAS,CAAC;IASrG,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAT/B,OAAO,CAAC,QAAQ,CAAC,CAAa;gBAQT,KAAK,EAAE,0BAA0B,EACjC,UAAU,EAAE,MAAM;IAKvC,IAAI,eAAe,IAAI,MAAM,CAE5B;IAGD,IAAI,cAAc,IAAI,MAAM,CAE3B;IAGD,OAAO,CAAC,YAAY,EAAE,YAAY,CAAC,SAAS,CAAC,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI;IAS7E,UAAU,IAAI,IAAI;CAIrB;AAeD,wBAAgB,sBAAsB,CAAC,iBAAiB,EAAE,MAAM,6BAA6B,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,GAAE,MAAqC,GAAG,0BAA0B,CASxL;AAMD,wBAAgB,sBAAsB,IAAI,IAAI,CAI7C"}
@@ -97,12 +97,12 @@ class MultiplexedObservableQueryConnection {
97
97
  }
98
98
  let _sharedMultiplexer;
99
99
  let _sharedMultiplexerKey = '';
100
- function getOrCreateMultiplexer(connectionFactory, cacheKey) {
100
+ function getOrCreateMultiplexer(connectionFactory, cacheKey, size = Globals.Globals.queryConnectionCount) {
101
101
  if (_sharedMultiplexer && _sharedMultiplexerKey === cacheKey) {
102
102
  return _sharedMultiplexer;
103
103
  }
104
104
  _sharedMultiplexer?.dispose();
105
- _sharedMultiplexer = new ObservableQueryMultiplexer(Globals.Globals.queryConnectionCount, connectionFactory);
105
+ _sharedMultiplexer = new ObservableQueryMultiplexer(size, connectionFactory);
106
106
  _sharedMultiplexerKey = cacheKey;
107
107
  return _sharedMultiplexer;
108
108
  }
@@ -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 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 remaining[key] = value !== undefined && value !== null ? String(value) : null;\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 * @returns The shared multiplexer instance.\n */\nexport function getOrCreateMultiplexer(connectionFactory: () => IObservableQueryHubConnection, cacheKey: string): ObservableQueryMultiplexer {\n if (_sharedMultiplexer && _sharedMultiplexerKey === cacheKey) {\n return _sharedMultiplexer;\n }\n\n _sharedMultiplexer?.dispose();\n _sharedMultiplexer = new ObservableQueryMultiplexer(Globals.queryConnectionCount, 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":["Globals"],"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';\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 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 remaining[key] = value !== undefined && value !== null ? String(value) : null;\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":["Globals"],"mappings":"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"}
@@ -12,7 +12,6 @@ export interface QueryCacheEntry<TDataType> {
12
12
  }
13
13
  export declare class QueryInstanceCache {
14
14
  private readonly _entries;
15
- private readonly _development;
16
15
  private _pendingDispose?;
17
16
  constructor(development?: boolean);
18
17
  buildKey(queryTypeName: string, args?: object): QueryCacheKey;
@@ -1 +1 @@
1
- {"version":3,"file":"QueryInstanceCache.d.ts","sourceRoot":"","sources":["../../../queries/QueryInstanceCache.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAK9D,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;IAC3B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsD;IAC/E,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAU;IACvC,OAAO,CAAC,eAAe,CAAC,CAAgC;gBAO5C,WAAW,GAAE,OAAe;IAUxC,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;IAkB3F,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;IAczC,OAAO,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI;IA2CjC,GAAG,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO;IAchC,wBAAwB,IAAI,IAAI;IAkBhC,OAAO,IAAI,IAAI;IAwBf,YAAY,IAAI,IAAI;IAiBpB,oBAAoB,IAAI,IAAI;CAM/B"}
1
+ {"version":3,"file":"QueryInstanceCache.d.ts","sourceRoot":"","sources":["../../../queries/QueryInstanceCache.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAK9D,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;IAC3B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsD;IAC/E,OAAO,CAAC,eAAe,CAAC,CAAgC;gBAQ5C,WAAW,GAAE,OAAe;IAWxC,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;IAkB3F,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;IA6BjC,GAAG,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO;IAchC,wBAAwB,IAAI,IAAI;IAkBhC,OAAO,IAAI,IAAI;IAwBf,YAAY,IAAI,IAAI;IAiBpB,oBAAoB,IAAI,IAAI;CAM/B"}
@@ -2,10 +2,8 @@
2
2
 
3
3
  class QueryInstanceCache {
4
4
  _entries = new Map();
5
- _development;
6
5
  _pendingDispose;
7
6
  constructor(development = false) {
8
- this._development = development;
9
7
  }
10
8
  buildKey(queryTypeName, args) {
11
9
  if (!args || Object.keys(args).length === 0) {
@@ -83,29 +81,16 @@ class QueryInstanceCache {
83
81
  if (entry) {
84
82
  entry.subscriberCount--;
85
83
  if (entry.subscriberCount <= 0) {
86
- if (this._development) {
87
- entry.pendingCleanup = setTimeout(() => {
88
- const current = this._entries.get(key);
89
- if (current && current.subscriberCount <= 0) {
90
- current.subscribed = false;
91
- current.teardown?.();
92
- current.teardown = undefined;
93
- current.pendingCleanup = undefined;
94
- this._entries.delete(key);
95
- }
96
- }, 0);
97
- }
98
- else {
99
- entry.subscribed = false;
100
- entry.teardown?.();
101
- entry.teardown = undefined;
102
- setTimeout(() => {
103
- const current = this._entries.get(key);
104
- if (current && current.subscriberCount <= 0) {
105
- this._entries.delete(key);
106
- }
107
- }, 0);
108
- }
84
+ entry.pendingCleanup = setTimeout(() => {
85
+ const current = this._entries.get(key);
86
+ if (current && current.subscriberCount <= 0) {
87
+ current.subscribed = false;
88
+ current.teardown?.();
89
+ current.teardown = undefined;
90
+ current.pendingCleanup = undefined;
91
+ this._entries.delete(key);
92
+ }
93
+ }, 0);
109
94
  }
110
95
  }
111
96
  }
@@ -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 when running in development mode.\n * Allows React StrictMode re-mounts to cancel the pending teardown\n * so the connection is reused instead of 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 readonly _development: boolean;\n private _pendingDispose?: ReturnType<typeof setTimeout>;\n\n /**\n * Initializes a new instance of {@link QueryInstanceCache}.\n * @param development When true, teardown is deferred on release so React StrictMode\n * re-mounts can re-acquire the entry without an unnecessary disconnect/reconnect cycle.\n */\n constructor(development: boolean = false) {\n this._development = development;\n }\n\n /**\n * Builds the cache key for a query.\n * @param queryTypeName The name of the query constructor (i.e. `constructor.name`).\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} in development mode),\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 entry.lastResult = result as QueryResultWithState<unknown>;\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 the teardown\n * function is called (if set) and the entry is evicted.\n *\n * In development mode, both teardown and eviction are deferred by one microtask so that\n * React StrictMode re-mounts can re-acquire the entry and cancel the cleanup. This prevents\n * an unnecessary disconnect/reconnect cycle during the synthetic unmount/remount that\n * StrictMode performs in development builds.\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 if (this._development) {\n // Defer both teardown and deletion so StrictMode re-mounts can cancel.\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 }, 0);\n } else {\n entry.subscribed = false;\n entry.teardown?.();\n entry.teardown = undefined;\n\n // Defer deletion so React Strict Mode re-mounts can re-acquire the entry.\n setTimeout(() => {\n const current = this._entries.get(key);\n\n if (current && current.subscriberCount <= 0) {\n this._entries.delete(key);\n }\n }, 0);\n }\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":";;MAmEa,kBAAkB,CAAA;AACV,IAAA,QAAQ,GAAG,IAAI,GAAG,EAA2C;AAC7D,IAAA,YAAY;AACrB,IAAA,eAAe;AAOvB,IAAA,WAAA,CAAY,cAAuB,KAAK,EAAA;AACpC,QAAA,IAAI,CAAC,YAAY,GAAG,WAAW;IACnC;IAQA,QAAQ,CAAC,aAAqB,EAAE,IAAa,EAAA;AACzC,QAAA,IAAI,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE;YACzC,OAAO,CAAA,EAAG,aAAa,CAAA,EAAA,CAAI;QAC/B;AAEA,QAAA,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI;AAC1B,aAAA,IAAI;AACJ,aAAA,MAAM,CAA0B,CAAC,WAAW,EAAE,GAAG,KAAI;YAClD,WAAW,CAAC,GAAG,CAAC,GAAI,IAAgC,CAAC,GAAG,CAAC;AACzD,YAAA,OAAO,WAAW;QACtB,CAAC,EAAE,EAAE,CAAC;QAEV,OAAO,CAAA,EAAG,aAAa,CAAA,EAAA,EAAK,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA,CAAE;IACxD;IAUA,WAAW,CACP,GAAkB,EAClB,OAAwB,EAAA;QAExB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;AACzB,YAAA,MAAM,KAAK,GAA6B;gBACpC,QAAQ,EAAE,OAAO,EAAE;AACnB,gBAAA,UAAU,EAAE,SAAS;AACrB,gBAAA,eAAe,EAAE,CAAC;gBAClB,SAAS,EAAE,IAAI,GAAG,EAAE;AACpB,gBAAA,UAAU,EAAE,KAAK;aACpB;YAED,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC;YAC7B,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAqB,EAAE,KAAK,EAAE,IAAI,EAAE;QACjE;QAEA,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAE;QACrC,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAqB,EAAE,KAAK,EAAE,KAAK,EAAE;IAClE;AASA,IAAA,OAAO,CAAC,GAAkB,EAAA;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;QAEpC,IAAI,KAAK,EAAE;AACP,YAAA,IAAI,KAAK,CAAC,cAAc,KAAK,SAAS,EAAE;AACpC,gBAAA,YAAY,CAAC,KAAK,CAAC,cAAc,CAAC;AAClC,gBAAA,KAAK,CAAC,cAAc,GAAG,SAAS;YACpC;YAEA,KAAK,CAAC,eAAe,EAAE;QAC3B;IACJ;AAQA,IAAA,aAAa,CAAY,GAAkB,EAAA;QACvC,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,UAAyD;IAC5F;IAQA,aAAa,CAAY,GAAkB,EAAE,MAAuC,EAAA;QAChF,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;QAEpC,IAAI,KAAK,EAAE;AACP,YAAA,KAAK,CAAC,UAAU,GAAG,MAAuC;AAE1D,YAAA,KAAK,MAAM,QAAQ,IAAI,KAAK,CAAC,SAAS,EAAE;gBACnC,QAA0C,CAAC,MAAM,CAAC;YACvD;QACJ;IACJ;IAQA,WAAW,CAAY,GAAkB,EAAE,QAAuC,EAAA;QAC9E,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;QAEpC,IAAI,KAAK,EAAE;AACP,YAAA,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,QAAuC,CAAC;QAChE;IACJ;IAQA,cAAc,CAAY,GAAkB,EAAE,QAAuC,EAAA;QACjF,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;QAEpC,IAAI,KAAK,EAAE;AACP,YAAA,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,QAAuC,CAAC;QACnE;IACJ;IAQA,WAAW,CAAC,GAAkB,EAAE,QAAoB,EAAA;QAChD,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;QAEpC,IAAI,KAAK,EAAE;AACP,YAAA,KAAK,CAAC,QAAQ,GAAG,QAAQ;AACzB,YAAA,KAAK,CAAC,UAAU,GAAG,IAAI;QAC3B;IACJ;AAOA,IAAA,YAAY,CAAC,GAAkB,EAAA;AAC3B,QAAA,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,UAAU,IAAI,KAAK;IACtD;AAYA,IAAA,OAAO,CAAC,GAAkB,EAAA;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;QAEpC,IAAI,KAAK,EAAE;YACP,KAAK,CAAC,eAAe,EAAE;AAEvB,YAAA,IAAI,KAAK,CAAC,eAAe,IAAI,CAAC,EAAE;AAC5B,gBAAA,IAAI,IAAI,CAAC,YAAY,EAAE;AAEnB,oBAAA,KAAK,CAAC,cAAc,GAAG,UAAU,CAAC,MAAK;wBACnC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;wBAEtC,IAAI,OAAO,IAAI,OAAO,CAAC,eAAe,IAAI,CAAC,EAAE;AACzC,4BAAA,OAAO,CAAC,UAAU,GAAG,KAAK;AAC1B,4BAAA,OAAO,CAAC,QAAQ,IAAI;AACpB,4BAAA,OAAO,CAAC,QAAQ,GAAG,SAAS;AAC5B,4BAAA,OAAO,CAAC,cAAc,GAAG,SAAS;AAClC,4BAAA,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC;wBAC7B;oBACJ,CAAC,EAAE,CAAC,CAAC;gBACT;qBAAO;AACH,oBAAA,KAAK,CAAC,UAAU,GAAG,KAAK;AACxB,oBAAA,KAAK,CAAC,QAAQ,IAAI;AAClB,oBAAA,KAAK,CAAC,QAAQ,GAAG,SAAS;oBAG1B,UAAU,CAAC,MAAK;wBACZ,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;wBAEtC,IAAI,OAAO,IAAI,OAAO,CAAC,eAAe,IAAI,CAAC,EAAE;AACzC,4BAAA,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC;wBAC7B;oBACJ,CAAC,EAAE,CAAC,CAAC;gBACT;YACJ;QACJ;IACJ;AAOA,IAAA,GAAG,CAAC,GAAkB,EAAA;QAClB,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;IACjC;IAYA,wBAAwB,GAAA;QACpB,KAAK,MAAM,GAAG,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE;AACnC,YAAA,IAAI,KAAK,CAAC,cAAc,KAAK,SAAS,EAAE;AACpC,gBAAA,YAAY,CAAC,KAAK,CAAC,cAAc,CAAC;AAClC,gBAAA,KAAK,CAAC,cAAc,GAAG,SAAS;YACpC;AAEA,YAAA,KAAK,CAAC,UAAU,GAAG,KAAK;AACxB,YAAA,KAAK,CAAC,QAAQ,IAAI;AAClB,YAAA,KAAK,CAAC,QAAQ,GAAG,SAAS;QAC9B;IACJ;IAOA,OAAO,GAAA;QACH,KAAK,MAAM,GAAG,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE;AACnC,YAAA,IAAI,KAAK,CAAC,cAAc,KAAK,SAAS,EAAE;AACpC,gBAAA,YAAY,CAAC,KAAK,CAAC,cAAc,CAAC;AAClC,gBAAA,KAAK,CAAC,cAAc,GAAG,SAAS;YACpC;AAEA,YAAA,KAAK,CAAC,UAAU,GAAG,KAAK;AACxB,YAAA,KAAK,CAAC,QAAQ,IAAI;AAClB,YAAA,KAAK,CAAC,QAAQ,GAAG,SAAS;QAC9B;AAEA,QAAA,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE;IACzB;IAWA,YAAY,GAAA;AACR,QAAA,IAAI,IAAI,CAAC,eAAe,KAAK,SAAS,EAAE;AACpC,YAAA,YAAY,CAAC,IAAI,CAAC,eAAe,CAAC;QACtC;AAEA,QAAA,IAAI,CAAC,eAAe,GAAG,UAAU,CAAC,MAAK;AACnC,YAAA,IAAI,CAAC,eAAe,GAAG,SAAS;YAChC,IAAI,CAAC,OAAO,EAAE;QAClB,CAAC,EAAE,CAAC,CAAC;IACT;IAQA,oBAAoB,GAAA;AAChB,QAAA,IAAI,IAAI,CAAC,eAAe,KAAK,SAAS,EAAE;AACpC,YAAA,YAAY,CAAC,IAAI,CAAC,eAAe,CAAC;AAClC,YAAA,IAAI,CAAC,eAAe,GAAG,SAAS;QACpC;IACJ;AACH;;;;"}
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 development Accepted for API compatibility. No longer changes teardown behavior —\n * teardown is always deferred to handle React StrictMode re-mounts in any environment.\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n constructor(development: boolean = false) {\n // The development parameter is kept for API compatibility only.\n // Teardown is always deferred regardless of this flag.\n }\n\n /**\n * Builds the cache key for a query.\n * @param queryTypeName The name of the query constructor (i.e. `constructor.name`).\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 entry.lastResult = result as QueryResultWithState<unknown>;\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 deletion so React StrictMode re-mounts in any environment\n // can cancel by calling acquire() before the timeout fires.\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 }, 0);\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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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=with_hub_mode_and_sse_transport_and_connection_count_exceeding_safe_limit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"with_hub_mode_and_sse_transport_and_connection_count_exceeding_safe_limit.d.ts","sourceRoot":"","sources":["../../../../../queries/for_ObservableQueryConnectionFactory/when_creating/with_hub_mode_and_sse_transport_and_connection_count_exceeding_safe_limit.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=with_hub_mode_and_sse_transport_and_connection_count_within_safe_limit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"with_hub_mode_and_sse_transport_and_connection_count_within_safe_limit.d.ts","sourceRoot":"","sources":["../../../../../queries/for_ObservableQueryConnectionFactory/when_creating/with_hub_mode_and_sse_transport_and_connection_count_within_safe_limit.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=with_hub_mode_and_websocket_transport_and_high_connection_count.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"with_hub_mode_and_websocket_transport_and_high_connection_count.d.ts","sourceRoot":"","sources":["../../../../../queries/for_ObservableQueryConnectionFactory/when_creating/with_hub_mode_and_websocket_transport_and_high_connection_count.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=with_a_changed_cache_key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"with_a_changed_cache_key.d.ts","sourceRoot":"","sources":["../../../../../queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_changed_cache_key.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=with_a_new_cache_key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"with_a_new_cache_key.d.ts","sourceRoot":"","sources":["../../../../../queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_a_new_cache_key.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=with_an_explicit_size.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"with_an_explicit_size.d.ts","sourceRoot":"","sources":["../../../../../queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_an_explicit_size.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=with_the_same_cache_key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"with_the_same_cache_key.d.ts","sourceRoot":"","sources":["../../../../../queries/for_ObservableQueryMultiplexer/when_getting_or_creating/with_the_same_cache_key.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=after_release.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"after_release.d.ts","sourceRoot":"","sources":["../../../../../queries/for_QueryInstanceCache/when_acquiring/after_release.ts"],"names":[],"mappings":""}
@@ -30,9 +30,18 @@ function createDirectWebSocketConnection(descriptor) {
30
30
  function createDirectSSEConnection(descriptor) {
31
31
  return new ServerSentEventQueryConnection(buildDirectUrl(descriptor));
32
32
  }
33
+ const MAX_SAFE_SSE_CONNECTIONS = 4;
33
34
  function createMultiplexedConnection(descriptor, isSSE) {
34
35
  const transport = isSSE ? 'sse' : 'ws';
35
- const cacheKey = `${Globals.queryConnectionCount}|${transport}|${descriptor.origin}|${descriptor.apiBasePath}|${descriptor.microservice}`;
36
+ const requestedCount = Globals.queryConnectionCount;
37
+ const effectiveCount = isSSE ? Math.min(requestedCount, MAX_SAFE_SSE_CONNECTIONS) : requestedCount;
38
+ if (isSSE && requestedCount > MAX_SAFE_SSE_CONNECTIONS) {
39
+ console.warn(`[Arc] queryConnectionCount (${requestedCount}) exceeds the safe limit for SSE transport (${MAX_SAFE_SSE_CONNECTIONS}). ` +
40
+ `HTTP/1.1 browsers allow at most 6 concurrent connections per origin; ` +
41
+ `using more SSE connections blocks subscribe/unsubscribe requests, causing queries to hang. ` +
42
+ `Capping at ${MAX_SAFE_SSE_CONNECTIONS}. Enable HTTP/2 on your server to use a higher connection count.`);
43
+ }
44
+ const cacheKey = `${requestedCount}|${transport}|${descriptor.origin}|${descriptor.apiBasePath}|${descriptor.microservice}`;
36
45
  const multiplexer = getOrCreateMultiplexer(() => {
37
46
  if (isSSE) {
38
47
  const sseRoute = joinPaths(descriptor.apiBasePath, SSE_HUB_ROUTE);
@@ -50,7 +59,7 @@ function createMultiplexedConnection(descriptor, isSSE) {
50
59
  const wsUrl = `${secure ? 'wss' : 'ws'}://${hubUrl.host}${hubUrl.pathname}${hubUrl.search}`;
51
60
  return new WebSocketHubConnection(wsUrl, descriptor.microservice);
52
61
  }
53
- }, cacheKey);
62
+ }, cacheKey, effectiveCount);
54
63
  return new MultiplexedObservableQueryConnection(multiplexer, descriptor.queryName);
55
64
  }
56
65
 
@@ -1 +1 @@
1
- {"version":3,"file":"ObservableQueryConnectionFactory.js","sources":["../../../queries/ObservableQueryConnectionFactory.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 { joinPaths } from '../joinPaths';\nimport { UrlHelpers } from '../UrlHelpers';\nimport { IObservableQueryConnection } from './IObservableQueryConnection';\nimport { ObservableQueryConnection } from './ObservableQueryConnection';\nimport { QueryTransportMethod } from './QueryTransportMethod';\nimport { ServerSentEventQueryConnection, SSE_HUB_ROUTE } from './ServerSentEventQueryConnection';\nimport { ServerSentEventHubConnection } from './ServerSentEventHubConnection';\nimport { WebSocketHubConnection } from './WebSocketHubConnection';\nimport { MultiplexedObservableQueryConnection, WS_HUB_ROUTE, getOrCreateMultiplexer } from './ObservableQueryMultiplexer';\n\n/**\n * The SSE subscribe POST endpoint route.\n */\nexport const SSE_SUBSCRIBE_ROUTE = '/.cratis/queries/sse/subscribe';\n\n/**\n * The SSE unsubscribe POST endpoint route.\n */\nexport const SSE_UNSUBSCRIBE_ROUTE = '/.cratis/queries/sse/unsubscribe';\n\n/**\n * Describes what the factory needs to know about a query in order to create\n * the right connection. Transport details are intentionally absent — the\n * factory reads those from {@link Globals}.\n */\nexport interface QueryConnectionDescriptor {\n /** The route template (e.g. `/api/accounts/debit/all-accounts`). */\n route: string;\n /** Fully-qualified backend query name used by hub transports. */\n queryName: string;\n /** Origin base URL (e.g. `http://localhost:5000`). */\n origin: string;\n /** API base path prefix. */\n apiBasePath: string;\n /** Microservice identifier. */\n microservice: string;\n /** Route/query arguments for the current subscription. */\n args?: object;\n}\n\n/**\n * Single entry-point for creating observable query connections.\n *\n * The transport is chosen as a 2x2 matrix of (direct | hub) x (WebSocket | SSE):\n *\n * | | Direct (per-query URL) | Hub (centralized endpoint) |\n * |----------------|-------------------------------|-------------------------------|\n * | **WebSocket** | ObservableQueryConnection | MultiplexedObservableQueryConnection |\n * | **SSE** | ServerSentEventQueryConnection | MultiplexedObservableQueryConnection |\n *\n * In multiplexed mode, both transports share the same multiplexer — it creates either\n * WebSocket or SSE connections depending on the transport setting.\n *\n * {@link ObservableQueryFor} never needs to know which transport is in use.\n */\nexport function createObservableQueryConnection<TDataType>(descriptor: QueryConnectionDescriptor): IObservableQueryConnection<TDataType> {\n const isSSE = Globals.queryTransportMethod === QueryTransportMethod.ServerSentEvents;\n\n if (Globals.queryDirectMode) {\n return isSSE\n ? createDirectSSEConnection<TDataType>(descriptor)\n : createDirectWebSocketConnection<TDataType>(descriptor);\n }\n\n return createMultiplexedConnection<TDataType>(descriptor, isSSE);\n}\n\n// ---- Direct mode: one connection per query, hitting the query's own URL ----\n\nfunction buildDirectUrl(descriptor: QueryConnectionDescriptor): URL {\n const { route } = UrlHelpers.replaceRouteParameters(descriptor.route, descriptor.args as object);\n const actualRoute = joinPaths(descriptor.apiBasePath, route);\n return UrlHelpers.createUrlFrom(descriptor.origin, descriptor.apiBasePath, actualRoute);\n}\n\nfunction createDirectWebSocketConnection<TDataType>(descriptor: QueryConnectionDescriptor): IObservableQueryConnection<TDataType> {\n return new ObservableQueryConnection<TDataType>(buildDirectUrl(descriptor), descriptor.microservice);\n}\n\nfunction createDirectSSEConnection<TDataType>(descriptor: QueryConnectionDescriptor): IObservableQueryConnection<TDataType> {\n return new ServerSentEventQueryConnection<TDataType>(buildDirectUrl(descriptor));\n}\n\n// ---- Multiplexed mode: centralized endpoint, multiple queries share connections ----\n\nfunction createMultiplexedConnection<TDataType>(descriptor: QueryConnectionDescriptor, isSSE: boolean): IObservableQueryConnection<TDataType> {\n const transport = isSSE ? 'sse' : 'ws';\n const cacheKey = `${Globals.queryConnectionCount}|${transport}|${descriptor.origin}|${descriptor.apiBasePath}|${descriptor.microservice}`;\n\n const multiplexer = getOrCreateMultiplexer(() => {\n if (isSSE) {\n const sseRoute = joinPaths(descriptor.apiBasePath, SSE_HUB_ROUTE);\n const sseUrl = UrlHelpers.createUrlFrom(descriptor.origin, descriptor.apiBasePath, sseRoute);\n const subscribeRoute = joinPaths(descriptor.apiBasePath, SSE_SUBSCRIBE_ROUTE);\n const subscribeUrl = UrlHelpers.createUrlFrom(descriptor.origin, descriptor.apiBasePath, subscribeRoute);\n const unsubscribeRoute = joinPaths(descriptor.apiBasePath, SSE_UNSUBSCRIBE_ROUTE);\n const unsubscribeUrl = UrlHelpers.createUrlFrom(descriptor.origin, descriptor.apiBasePath, unsubscribeRoute);\n return new ServerSentEventHubConnection(\n sseUrl.toString(),\n subscribeUrl.toString(),\n unsubscribeUrl.toString(),\n descriptor.microservice,\n );\n } else {\n const hubRoute = joinPaths(descriptor.apiBasePath, WS_HUB_ROUTE);\n const hubUrl = UrlHelpers.createUrlFrom(descriptor.origin, descriptor.apiBasePath, hubRoute);\n const secure = hubUrl.protocol?.indexOf('https') === 0;\n const wsUrl = `${secure ? 'wss' : 'ws'}://${hubUrl.host}${hubUrl.pathname}${hubUrl.search}`;\n return new WebSocketHubConnection(wsUrl, descriptor.microservice);\n }\n }, cacheKey);\n\n return new MultiplexedObservableQueryConnection<TDataType>(multiplexer, descriptor.queryName);\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ObservableQueryConnectionFactory.js","sources":["../../../queries/ObservableQueryConnectionFactory.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 { joinPaths } from '../joinPaths';\nimport { UrlHelpers } from '../UrlHelpers';\nimport { IObservableQueryConnection } from './IObservableQueryConnection';\nimport { ObservableQueryConnection } from './ObservableQueryConnection';\nimport { QueryTransportMethod } from './QueryTransportMethod';\nimport { ServerSentEventQueryConnection, SSE_HUB_ROUTE } from './ServerSentEventQueryConnection';\nimport { ServerSentEventHubConnection } from './ServerSentEventHubConnection';\nimport { WebSocketHubConnection } from './WebSocketHubConnection';\nimport { MultiplexedObservableQueryConnection, WS_HUB_ROUTE, getOrCreateMultiplexer } from './ObservableQueryMultiplexer';\n\n/**\n * The SSE subscribe POST endpoint route.\n */\nexport const SSE_SUBSCRIBE_ROUTE = '/.cratis/queries/sse/subscribe';\n\n/**\n * The SSE unsubscribe POST endpoint route.\n */\nexport const SSE_UNSUBSCRIBE_ROUTE = '/.cratis/queries/sse/unsubscribe';\n\n/**\n * Describes what the factory needs to know about a query in order to create\n * the right connection. Transport details are intentionally absent — the\n * factory reads those from {@link Globals}.\n */\nexport interface QueryConnectionDescriptor {\n /** The route template (e.g. `/api/accounts/debit/all-accounts`). */\n route: string;\n /** Fully-qualified backend query name used by hub transports. */\n queryName: string;\n /** Origin base URL (e.g. `http://localhost:5000`). */\n origin: string;\n /** API base path prefix. */\n apiBasePath: string;\n /** Microservice identifier. */\n microservice: string;\n /** Route/query arguments for the current subscription. */\n args?: object;\n}\n\n/**\n * Single entry-point for creating observable query connections.\n *\n * The transport is chosen as a 2x2 matrix of (direct | hub) x (WebSocket | SSE):\n *\n * | | Direct (per-query URL) | Hub (centralized endpoint) |\n * |----------------|-------------------------------|-------------------------------|\n * | **WebSocket** | ObservableQueryConnection | MultiplexedObservableQueryConnection |\n * | **SSE** | ServerSentEventQueryConnection | MultiplexedObservableQueryConnection |\n *\n * In multiplexed mode, both transports share the same multiplexer — it creates either\n * WebSocket or SSE connections depending on the transport setting.\n *\n * {@link ObservableQueryFor} never needs to know which transport is in use.\n */\nexport function createObservableQueryConnection<TDataType>(descriptor: QueryConnectionDescriptor): IObservableQueryConnection<TDataType> {\n const isSSE = Globals.queryTransportMethod === QueryTransportMethod.ServerSentEvents;\n\n if (Globals.queryDirectMode) {\n return isSSE\n ? createDirectSSEConnection<TDataType>(descriptor)\n : createDirectWebSocketConnection<TDataType>(descriptor);\n }\n\n return createMultiplexedConnection<TDataType>(descriptor, isSSE);\n}\n\n// ---- Direct mode: one connection per query, hitting the query's own URL ----\n\nfunction buildDirectUrl(descriptor: QueryConnectionDescriptor): URL {\n const { route } = UrlHelpers.replaceRouteParameters(descriptor.route, descriptor.args as object);\n const actualRoute = joinPaths(descriptor.apiBasePath, route);\n return UrlHelpers.createUrlFrom(descriptor.origin, descriptor.apiBasePath, actualRoute);\n}\n\nfunction createDirectWebSocketConnection<TDataType>(descriptor: QueryConnectionDescriptor): IObservableQueryConnection<TDataType> {\n return new ObservableQueryConnection<TDataType>(buildDirectUrl(descriptor), descriptor.microservice);\n}\n\nfunction createDirectSSEConnection<TDataType>(descriptor: QueryConnectionDescriptor): IObservableQueryConnection<TDataType> {\n return new ServerSentEventQueryConnection<TDataType>(buildDirectUrl(descriptor));\n}\n\n// ---- Multiplexed mode: centralized endpoint, multiple queries share connections ----\n\n/**\n * Maximum number of SSE hub connections that fits safely within the HTTP/1.1 browser\n * per-origin connection limit (typically 6 for Chrome/Firefox). Each SSE EventSource\n * occupies one persistent connection slot indefinitely. Exceeding this cap blocks the\n * subscribe/unsubscribe POST requests that share the same pool, causing queries to hang.\n * Servers configured for HTTP/2 do not have this restriction.\n */\nconst MAX_SAFE_SSE_CONNECTIONS = 4;\n\nfunction createMultiplexedConnection<TDataType>(descriptor: QueryConnectionDescriptor, isSSE: boolean): IObservableQueryConnection<TDataType> {\n const transport = isSSE ? 'sse' : 'ws';\n const requestedCount = Globals.queryConnectionCount;\n const effectiveCount = isSSE ? Math.min(requestedCount, MAX_SAFE_SSE_CONNECTIONS) : requestedCount;\n\n if (isSSE && requestedCount > MAX_SAFE_SSE_CONNECTIONS) {\n console.warn(\n `[Arc] queryConnectionCount (${requestedCount}) exceeds the safe limit for SSE transport (${MAX_SAFE_SSE_CONNECTIONS}). ` +\n `HTTP/1.1 browsers allow at most 6 concurrent connections per origin; ` +\n `using more SSE connections blocks subscribe/unsubscribe requests, causing queries to hang. ` +\n `Capping at ${MAX_SAFE_SSE_CONNECTIONS}. Enable HTTP/2 on your server to use a higher connection count.`\n );\n }\n\n const cacheKey = `${requestedCount}|${transport}|${descriptor.origin}|${descriptor.apiBasePath}|${descriptor.microservice}`;\n\n const multiplexer = getOrCreateMultiplexer(() => {\n if (isSSE) {\n const sseRoute = joinPaths(descriptor.apiBasePath, SSE_HUB_ROUTE);\n const sseUrl = UrlHelpers.createUrlFrom(descriptor.origin, descriptor.apiBasePath, sseRoute);\n const subscribeRoute = joinPaths(descriptor.apiBasePath, SSE_SUBSCRIBE_ROUTE);\n const subscribeUrl = UrlHelpers.createUrlFrom(descriptor.origin, descriptor.apiBasePath, subscribeRoute);\n const unsubscribeRoute = joinPaths(descriptor.apiBasePath, SSE_UNSUBSCRIBE_ROUTE);\n const unsubscribeUrl = UrlHelpers.createUrlFrom(descriptor.origin, descriptor.apiBasePath, unsubscribeRoute);\n return new ServerSentEventHubConnection(\n sseUrl.toString(),\n subscribeUrl.toString(),\n unsubscribeUrl.toString(),\n descriptor.microservice,\n );\n } else {\n const hubRoute = joinPaths(descriptor.apiBasePath, WS_HUB_ROUTE);\n const hubUrl = UrlHelpers.createUrlFrom(descriptor.origin, descriptor.apiBasePath, hubRoute);\n const secure = hubUrl.protocol?.indexOf('https') === 0;\n const wsUrl = `${secure ? 'wss' : 'ws'}://${hubUrl.host}${hubUrl.pathname}${hubUrl.search}`;\n return new WebSocketHubConnection(wsUrl, descriptor.microservice);\n }\n }, cacheKey, effectiveCount);\n\n return new MultiplexedObservableQueryConnection<TDataType>(multiplexer, descriptor.queryName);\n}\n"],"names":[],"mappings":"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"}
@@ -25,6 +25,6 @@ export declare class MultiplexedObservableQueryConnection<TDataType> implements
25
25
  connect(dataReceived: DataReceived<TDataType>, queryArguments?: object): void;
26
26
  disconnect(): void;
27
27
  }
28
- export declare function getOrCreateMultiplexer(connectionFactory: () => IObservableQueryHubConnection, cacheKey: string): ObservableQueryMultiplexer;
28
+ export declare function getOrCreateMultiplexer(connectionFactory: () => IObservableQueryHubConnection, cacheKey: string, size?: number): ObservableQueryMultiplexer;
29
29
  export declare function resetSharedMultiplexer(): void;
30
30
  //# sourceMappingURL=ObservableQueryMultiplexer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ObservableQueryMultiplexer.d.ts","sourceRoot":"","sources":["../../../queries/ObservableQueryMultiplexer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAS3D,eAAO,MAAM,YAAY,wBAAwB,CAAC;AAUlD,qBAAa,0BAA0B;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkC;IAC/D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAOnB,IAAI,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,6BAA6B;IAQhF,IAAI,IAAI,IAAI,MAAM,CAEjB;IAKD,IAAI,eAAe,IAAI,MAAM,CAK5B;IAKD,IAAI,cAAc,IAAI,MAAM,CAI3B;IAUD,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,YAAY,CAAC,GAAG,CAAC,GAAG,MAAM,IAAI;IAezG,OAAO,IAAI,IAAI;IAMf,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,wBAAwB;CA6BnC;AAOD,qBAAa,oCAAoC,CAAC,SAAS,CAAE,YAAW,0BAA0B,CAAC,SAAS,CAAC;IASrG,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAT/B,OAAO,CAAC,QAAQ,CAAC,CAAa;gBAQT,KAAK,EAAE,0BAA0B,EACjC,UAAU,EAAE,MAAM;IAKvC,IAAI,eAAe,IAAI,MAAM,CAE5B;IAGD,IAAI,cAAc,IAAI,MAAM,CAE3B;IAGD,OAAO,CAAC,YAAY,EAAE,YAAY,CAAC,SAAS,CAAC,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI;IAS7E,UAAU,IAAI,IAAI;CAIrB;AAcD,wBAAgB,sBAAsB,CAAC,iBAAiB,EAAE,MAAM,6BAA6B,EAAE,QAAQ,EAAE,MAAM,GAAG,0BAA0B,CAS3I;AAMD,wBAAgB,sBAAsB,IAAI,IAAI,CAI7C"}
1
+ {"version":3,"file":"ObservableQueryMultiplexer.d.ts","sourceRoot":"","sources":["../../../queries/ObservableQueryMultiplexer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAS3D,eAAO,MAAM,YAAY,wBAAwB,CAAC;AAUlD,qBAAa,0BAA0B;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkC;IAC/D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAOnB,IAAI,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,6BAA6B;IAQhF,IAAI,IAAI,IAAI,MAAM,CAEjB;IAKD,IAAI,eAAe,IAAI,MAAM,CAK5B;IAKD,IAAI,cAAc,IAAI,MAAM,CAI3B;IAUD,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,YAAY,CAAC,GAAG,CAAC,GAAG,MAAM,IAAI;IAezG,OAAO,IAAI,IAAI;IAMf,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,wBAAwB;CA6BnC;AAOD,qBAAa,oCAAoC,CAAC,SAAS,CAAE,YAAW,0BAA0B,CAAC,SAAS,CAAC;IASrG,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAT/B,OAAO,CAAC,QAAQ,CAAC,CAAa;gBAQT,KAAK,EAAE,0BAA0B,EACjC,UAAU,EAAE,MAAM;IAKvC,IAAI,eAAe,IAAI,MAAM,CAE5B;IAGD,IAAI,cAAc,IAAI,MAAM,CAE3B;IAGD,OAAO,CAAC,YAAY,EAAE,YAAY,CAAC,SAAS,CAAC,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI;IAS7E,UAAU,IAAI,IAAI;CAIrB;AAeD,wBAAgB,sBAAsB,CAAC,iBAAiB,EAAE,MAAM,6BAA6B,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,GAAE,MAAqC,GAAG,0BAA0B,CASxL;AAMD,wBAAgB,sBAAsB,IAAI,IAAI,CAI7C"}
@@ -95,12 +95,12 @@ class MultiplexedObservableQueryConnection {
95
95
  }
96
96
  let _sharedMultiplexer;
97
97
  let _sharedMultiplexerKey = '';
98
- function getOrCreateMultiplexer(connectionFactory, cacheKey) {
98
+ function getOrCreateMultiplexer(connectionFactory, cacheKey, size = Globals.queryConnectionCount) {
99
99
  if (_sharedMultiplexer && _sharedMultiplexerKey === cacheKey) {
100
100
  return _sharedMultiplexer;
101
101
  }
102
102
  _sharedMultiplexer?.dispose();
103
- _sharedMultiplexer = new ObservableQueryMultiplexer(Globals.queryConnectionCount, connectionFactory);
103
+ _sharedMultiplexer = new ObservableQueryMultiplexer(size, connectionFactory);
104
104
  _sharedMultiplexerKey = cacheKey;
105
105
  return _sharedMultiplexer;
106
106
  }
@@ -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 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 remaining[key] = value !== undefined && value !== null ? String(value) : null;\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 * @returns The shared multiplexer instance.\n */\nexport function getOrCreateMultiplexer(connectionFactory: () => IObservableQueryHubConnection, cacheKey: string): ObservableQueryMultiplexer {\n if (_sharedMultiplexer && _sharedMultiplexerKey === cacheKey) {\n return _sharedMultiplexer;\n }\n\n _sharedMultiplexer?.dispose();\n _sharedMultiplexer = new ObservableQueryMultiplexer(Globals.queryConnectionCount, 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';\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 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 remaining[key] = value !== undefined && value !== null ? String(value) : null;\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":";;AAcO,MAAM,YAAY,GAAG;MAUf,0BAA0B,CAAA;AAClB,IAAA,YAAY;AACZ,IAAA,KAAK;IAOtB,WAAA,CAAY,IAAY,EAAE,iBAAsD,EAAA;QAC5E,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC;QAC9B,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,EAAE,MAAM,iBAAiB,EAAE,CAAC;IACrF;AAKA,IAAA,IAAI,IAAI,GAAA;QACJ,OAAO,IAAI,CAAC,KAAK;IACrB;AAKA,IAAA,IAAI,eAAe,GAAA;AACf,QAAA,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,KACnC,CAAC,CAAC,eAAe,GAAG,CAAC,IAAI,CAAC,CAAC,eAAe,GAAG,GAAG,GAAG,CAAC,CAAC,eAAe,GAAG,GAAG,EAC1E,MAAM,CAAC,gBAAgB,CAC1B;IACL;AAKA,IAAA,IAAI,cAAc,GAAA;AACd,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,cAAc,GAAG,CAAC,CAAC;AAClE,QAAA,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;AAAE,YAAA,OAAO,CAAC;QACjC,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM;IAC/E;AAUA,IAAA,SAAS,CAAC,SAAiB,EAAE,cAAkC,EAAE,QAA2B,EAAA;QACxF,MAAM,OAAO,GAAG,IAAI,CAAC,wBAAwB,CAAC,SAAS,EAAE,cAAc,CAAC;AACxE,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE;AAC/B,QAAA,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,EAAE;QAEtC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC;AAE1C,QAAA,OAAO,MAAK;AACR,YAAA,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC;AAC7B,QAAA,CAAC;IACL;IAKA,OAAO,GAAA;AACH,QAAA,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,YAAY,EAAE;YAClC,IAAI,CAAC,OAAO,EAAE;QAClB;IACJ;IAEQ,WAAW,GAAA;AACf,QAAA,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,KACnC,CAAC,CAAC,UAAU,GAAG,GAAG,CAAC,UAAU,GAAG,CAAC,GAAG,GAAG,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACtE;IAEQ,eAAe,GAAA;AAEnB,QAAA,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,UAAU,EAAE;AAC1E,YAAA,OAAO,MAAM,CAAC,UAAU,EAAE;QAC9B;QACA,OAAO,CAAA,EAAG,IAAI,CAAC,GAAG,EAAE,CAAA,CAAA,EAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA,CAAE;IACzE;IAEQ,wBAAwB,CAAC,SAAiB,EAAE,IAAa,EAAA;AAC7D,QAAA,MAAM,OAAO,GAAwB,EAAE,SAAS,EAAE;AAElD,QAAA,IAAI,CAAC,IAAI;AAAE,YAAA,OAAO,OAAO;QAEzB,MAAM,CAAC,GAAG,IAA2B;AACrC,QAAA,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC;QAErF,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI;YAAE,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;QAC1E,IAAI,CAAC,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,CAAC,QAAQ,KAAK,IAAI;YAAE,OAAO,CAAC,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;QAC1F,IAAI,CAAC,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI;YAAE,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;QAClF,IAAI,CAAC,CAAC,aAAa,KAAK,SAAS,IAAI,CAAC,CAAC,aAAa,KAAK,IAAI;YAAE,OAAO,CAAC,aAAa,GAAG,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC;QAG9G,MAAM,SAAS,GAAkC,EAAE;QACnD,IAAI,YAAY,GAAG,KAAK;AAExB,QAAA,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;AAC1C,YAAA,IAAI,oBAAoB,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE;YACnC,SAAS,CAAC,GAAG,CAAC,GAAG,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI;YAC7E,YAAY,GAAG,IAAI;QACvB;QAEA,IAAI,YAAY,EAAE;AACd,YAAA,OAAO,CAAC,SAAS,GAAG,SAAS;QACjC;AAEA,QAAA,OAAO,OAAO;IAClB;AACH;MAOY,oCAAoC,CAAA;AASxB,IAAA,KAAA;AACA,IAAA,UAAA;AATb,IAAA,QAAQ;IAOhB,WAAA,CACqB,KAAiC,EACjC,UAAkB,EAAA;QADlB,IAAA,CAAA,KAAK,GAAL,KAAK;QACL,IAAA,CAAA,UAAU,GAAV,UAAU;IAE/B;AAGA,IAAA,IAAI,eAAe,GAAA;AACf,QAAA,OAAO,IAAI,CAAC,KAAK,CAAC,eAAe;IACrC;AAGA,IAAA,IAAI,cAAc,GAAA;AACd,QAAA,OAAO,IAAI,CAAC,KAAK,CAAC,cAAc;IACpC;IAGA,OAAO,CAAC,YAAqC,EAAE,cAAuB,EAAA;AAClE,QAAA,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAChC,IAAI,CAAC,UAAU,EACf,cAAc,EACd,YAAiC,CACpC;IACL;IAGA,UAAU,GAAA;AACN,QAAA,IAAI,CAAC,QAAQ,IAAI;AACjB,QAAA,IAAI,CAAC,QAAQ,GAAG,SAAS;IAC7B;AACH;AAID,IAAI,kBAA0D;AAC9D,IAAI,qBAAqB,GAAG,EAAE;AAUxB,SAAU,sBAAsB,CAAC,iBAAsD,EAAE,QAAgB,EAAE,IAAA,GAAe,OAAO,CAAC,oBAAoB,EAAA;AACxJ,IAAA,IAAI,kBAAkB,IAAI,qBAAqB,KAAK,QAAQ,EAAE;AAC1D,QAAA,OAAO,kBAAkB;IAC7B;IAEA,kBAAkB,EAAE,OAAO,EAAE;IAC7B,kBAAkB,GAAG,IAAI,0BAA0B,CAAC,IAAI,EAAE,iBAAiB,CAAC;IAC5E,qBAAqB,GAAG,QAAQ;AAChC,IAAA,OAAO,kBAAkB;AAC7B;SAMgB,sBAAsB,GAAA;IAClC,kBAAkB,EAAE,OAAO,EAAE;IAC7B,kBAAkB,GAAG,SAAS;IAC9B,qBAAqB,GAAG,EAAE;AAC9B;;;;"}
@@ -12,7 +12,6 @@ export interface QueryCacheEntry<TDataType> {
12
12
  }
13
13
  export declare class QueryInstanceCache {
14
14
  private readonly _entries;
15
- private readonly _development;
16
15
  private _pendingDispose?;
17
16
  constructor(development?: boolean);
18
17
  buildKey(queryTypeName: string, args?: object): QueryCacheKey;