threadforge 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -20
- package/bin/forge.js +2 -1058
- package/bin/host-commands.d.ts +2 -0
- package/bin/host-commands.d.ts.map +1 -0
- package/bin/host-commands.js +7 -8
- package/bin/platform-commands.d.ts +2 -0
- package/bin/platform-commands.d.ts.map +1 -0
- package/bin/platform-commands.js +118 -36
- package/dist/cli/base-command.d.ts +12 -0
- package/dist/cli/base-command.d.ts.map +1 -0
- package/dist/cli/base-command.js +25 -0
- package/dist/cli/base-command.js.map +1 -0
- package/dist/cli/commands/build.d.ts +10 -0
- package/dist/cli/commands/build.d.ts.map +1 -0
- package/dist/cli/commands/build.js +110 -0
- package/dist/cli/commands/build.js.map +1 -0
- package/dist/cli/commands/deploy.d.ts +12 -0
- package/dist/cli/commands/deploy.d.ts.map +1 -0
- package/dist/cli/commands/deploy.js +143 -0
- package/dist/cli/commands/deploy.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +10 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/dev.js +138 -0
- package/dist/cli/commands/dev.js.map +1 -0
- package/dist/cli/commands/generate.d.ts +10 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/generate.js +76 -0
- package/dist/cli/commands/generate.js.map +1 -0
- package/dist/cli/commands/host.d.ts +8 -0
- package/dist/cli/commands/host.d.ts.map +1 -0
- package/dist/cli/commands/host.js +20 -0
- package/dist/cli/commands/host.js.map +1 -0
- package/dist/cli/commands/init.d.ts +16 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +246 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/platform.d.ts +8 -0
- package/dist/cli/commands/platform.d.ts.map +1 -0
- package/dist/cli/commands/platform.js +20 -0
- package/dist/cli/commands/platform.js.map +1 -0
- package/dist/cli/commands/restart.d.ts +8 -0
- package/dist/cli/commands/restart.d.ts.map +1 -0
- package/dist/cli/commands/restart.js +13 -0
- package/dist/cli/commands/restart.js.map +1 -0
- package/dist/cli/commands/scaffold/frontend.d.ts +10 -0
- package/dist/cli/commands/scaffold/frontend.d.ts.map +1 -0
- package/dist/cli/commands/scaffold/frontend.js +130 -0
- package/dist/cli/commands/scaffold/frontend.js.map +1 -0
- package/dist/cli/commands/scaffold/react.d.ts +7 -0
- package/dist/cli/commands/scaffold/react.d.ts.map +1 -0
- package/dist/cli/commands/scaffold/react.js +12 -0
- package/dist/cli/commands/scaffold/react.js.map +1 -0
- package/dist/cli/commands/scale.d.ts +8 -0
- package/dist/cli/commands/scale.d.ts.map +1 -0
- package/dist/cli/commands/scale.js +13 -0
- package/dist/cli/commands/scale.js.map +1 -0
- package/dist/cli/commands/start.d.ts +10 -0
- package/dist/cli/commands/start.d.ts.map +1 -0
- package/dist/cli/commands/start.js +71 -0
- package/dist/cli/commands/start.js.map +1 -0
- package/dist/cli/commands/status.d.ts +11 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +60 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/stop.d.ts +10 -0
- package/dist/cli/commands/stop.d.ts.map +1 -0
- package/dist/cli/commands/stop.js +89 -0
- package/dist/cli/commands/stop.js.map +1 -0
- package/dist/cli/util/config-discovery.d.ts +8 -0
- package/dist/cli/util/config-discovery.d.ts.map +1 -0
- package/dist/cli/util/config-discovery.js +70 -0
- package/dist/cli/util/config-discovery.js.map +1 -0
- package/dist/cli/util/config-patcher.d.ts +17 -0
- package/dist/cli/util/config-patcher.d.ts.map +1 -0
- package/dist/cli/util/config-patcher.js +439 -0
- package/dist/cli/util/config-patcher.js.map +1 -0
- package/dist/cli/util/frontend-dev.d.ts +8 -0
- package/dist/cli/util/frontend-dev.d.ts.map +1 -0
- package/dist/cli/util/frontend-dev.js +117 -0
- package/dist/cli/util/frontend-dev.js.map +1 -0
- package/dist/cli/util/process.d.ts +5 -0
- package/dist/cli/util/process.d.ts.map +1 -0
- package/dist/cli/util/process.js +17 -0
- package/dist/cli/util/process.js.map +1 -0
- package/dist/cli/util/templates.d.ts +10 -0
- package/dist/cli/util/templates.d.ts.map +1 -0
- package/dist/cli/util/templates.js +157 -0
- package/dist/cli/util/templates.js.map +1 -0
- package/dist/core/AlertSink.d.ts +83 -0
- package/dist/core/AlertSink.d.ts.map +1 -0
- package/dist/core/AlertSink.js +126 -0
- package/dist/core/AlertSink.js.map +1 -0
- package/dist/core/DirectMessageBus.d.ts +88 -0
- package/dist/core/DirectMessageBus.d.ts.map +1 -0
- package/dist/core/DirectMessageBus.js +352 -0
- package/dist/core/DirectMessageBus.js.map +1 -0
- package/dist/core/EndpointResolver.d.ts +111 -0
- package/dist/core/EndpointResolver.d.ts.map +1 -0
- package/dist/core/EndpointResolver.js +336 -0
- package/dist/core/EndpointResolver.js.map +1 -0
- package/dist/core/ForgeContext.d.ts +221 -0
- package/dist/core/ForgeContext.d.ts.map +1 -0
- package/dist/core/ForgeContext.js +1169 -0
- package/dist/core/ForgeContext.js.map +1 -0
- package/dist/core/ForgeEndpoints.d.ts +71 -0
- package/dist/core/ForgeEndpoints.d.ts.map +1 -0
- package/dist/core/ForgeEndpoints.js +442 -0
- package/dist/core/ForgeEndpoints.js.map +1 -0
- package/dist/core/ForgeHost.d.ts +82 -0
- package/dist/core/ForgeHost.d.ts.map +1 -0
- package/dist/core/ForgeHost.js +107 -0
- package/dist/core/ForgeHost.js.map +1 -0
- package/dist/core/ForgePlatform.d.ts +96 -0
- package/dist/core/ForgePlatform.d.ts.map +1 -0
- package/dist/core/ForgePlatform.js +136 -0
- package/dist/core/ForgePlatform.js.map +1 -0
- package/dist/core/ForgeWebSocket.d.ts +56 -0
- package/dist/core/ForgeWebSocket.d.ts.map +1 -0
- package/dist/core/ForgeWebSocket.js +415 -0
- package/dist/core/ForgeWebSocket.js.map +1 -0
- package/dist/core/Ingress.d.ts +329 -0
- package/dist/core/Ingress.d.ts.map +1 -0
- package/dist/core/Ingress.js +694 -0
- package/dist/core/Ingress.js.map +1 -0
- package/dist/core/Interceptors.d.ts +134 -0
- package/dist/core/Interceptors.d.ts.map +1 -0
- package/dist/core/Interceptors.js +416 -0
- package/dist/core/Interceptors.js.map +1 -0
- package/dist/core/Logger.d.ts +20 -0
- package/dist/core/Logger.d.ts.map +1 -0
- package/dist/core/Logger.js +77 -0
- package/dist/core/Logger.js.map +1 -0
- package/dist/core/MessageBus.d.ts +15 -0
- package/dist/core/MessageBus.d.ts.map +1 -0
- package/dist/core/MessageBus.js +18 -0
- package/dist/core/MessageBus.js.map +1 -0
- package/dist/core/Prometheus.d.ts +80 -0
- package/dist/core/Prometheus.d.ts.map +1 -0
- package/dist/core/Prometheus.js +332 -0
- package/dist/core/Prometheus.js.map +1 -0
- package/dist/core/RequestContext.d.ts +214 -0
- package/dist/core/RequestContext.d.ts.map +1 -0
- package/dist/core/RequestContext.js +556 -0
- package/dist/core/RequestContext.js.map +1 -0
- package/dist/core/Router.d.ts +45 -0
- package/dist/core/Router.d.ts.map +1 -0
- package/dist/core/Router.js +285 -0
- package/dist/core/Router.js.map +1 -0
- package/dist/core/RoutingStrategy.d.ts +116 -0
- package/dist/core/RoutingStrategy.d.ts.map +1 -0
- package/dist/core/RoutingStrategy.js +306 -0
- package/dist/core/RoutingStrategy.js.map +1 -0
- package/dist/core/RpcConfig.d.ts +72 -0
- package/dist/core/RpcConfig.d.ts.map +1 -0
- package/dist/core/RpcConfig.js +127 -0
- package/dist/core/RpcConfig.js.map +1 -0
- package/dist/core/SignatureCache.d.ts +81 -0
- package/dist/core/SignatureCache.d.ts.map +1 -0
- package/dist/core/SignatureCache.js +172 -0
- package/dist/core/SignatureCache.js.map +1 -0
- package/dist/core/StaticFileServer.d.ts +34 -0
- package/dist/core/StaticFileServer.d.ts.map +1 -0
- package/dist/core/StaticFileServer.js +497 -0
- package/dist/core/StaticFileServer.js.map +1 -0
- package/dist/core/Supervisor.d.ts +198 -0
- package/dist/core/Supervisor.d.ts.map +1 -0
- package/dist/core/Supervisor.js +1418 -0
- package/dist/core/Supervisor.js.map +1 -0
- package/dist/core/ThreadAllocator.d.ts +52 -0
- package/dist/core/ThreadAllocator.d.ts.map +1 -0
- package/dist/core/ThreadAllocator.js +174 -0
- package/dist/core/ThreadAllocator.js.map +1 -0
- package/dist/core/WorkerChannelManager.d.ts +130 -0
- package/dist/core/WorkerChannelManager.d.ts.map +1 -0
- package/dist/core/WorkerChannelManager.js +956 -0
- package/dist/core/WorkerChannelManager.js.map +1 -0
- package/dist/core/config-enums.d.ts +41 -0
- package/dist/core/config-enums.d.ts.map +1 -0
- package/dist/core/config-enums.js +59 -0
- package/dist/core/config-enums.js.map +1 -0
- package/dist/core/config.d.ts +159 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +694 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/host-config.d.ts +146 -0
- package/dist/core/host-config.d.ts.map +1 -0
- package/dist/core/host-config.js +312 -0
- package/dist/core/host-config.js.map +1 -0
- package/dist/core/ipc-errors.d.ts +27 -0
- package/dist/core/ipc-errors.d.ts.map +1 -0
- package/dist/core/ipc-errors.js +36 -0
- package/dist/core/ipc-errors.js.map +1 -0
- package/dist/core/network-utils.d.ts +35 -0
- package/dist/core/network-utils.d.ts.map +1 -0
- package/dist/core/network-utils.js +145 -0
- package/dist/core/network-utils.js.map +1 -0
- package/dist/core/platform-config.d.ts +142 -0
- package/dist/core/platform-config.d.ts.map +1 -0
- package/dist/core/platform-config.js +299 -0
- package/dist/core/platform-config.js.map +1 -0
- package/dist/decorators/ServiceProxy.d.ts +175 -0
- package/dist/decorators/ServiceProxy.d.ts.map +1 -0
- package/dist/decorators/ServiceProxy.js +969 -0
- package/dist/decorators/ServiceProxy.js.map +1 -0
- package/dist/decorators/index.d.ts +146 -0
- package/dist/decorators/index.d.ts.map +1 -0
- package/dist/decorators/index.js +545 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/deploy/NginxGenerator.d.ts +165 -0
- package/dist/deploy/NginxGenerator.d.ts.map +1 -0
- package/dist/deploy/NginxGenerator.js +781 -0
- package/dist/deploy/NginxGenerator.js.map +1 -0
- package/dist/deploy/PlatformManifestGenerator.d.ts +43 -0
- package/dist/deploy/PlatformManifestGenerator.d.ts.map +1 -0
- package/dist/deploy/PlatformManifestGenerator.js +80 -0
- package/dist/deploy/PlatformManifestGenerator.js.map +1 -0
- package/dist/deploy/RouteManifestGenerator.d.ts +42 -0
- package/dist/deploy/RouteManifestGenerator.d.ts.map +1 -0
- package/dist/deploy/RouteManifestGenerator.js +105 -0
- package/dist/deploy/RouteManifestGenerator.js.map +1 -0
- package/dist/deploy/index.d.ts +210 -0
- package/dist/deploy/index.d.ts.map +1 -0
- package/dist/deploy/index.js +918 -0
- package/dist/deploy/index.js.map +1 -0
- package/dist/frontend/FrontendDevLifecycle.d.ts +26 -0
- package/dist/frontend/FrontendDevLifecycle.d.ts.map +1 -0
- package/dist/frontend/FrontendDevLifecycle.js +60 -0
- package/dist/frontend/FrontendDevLifecycle.js.map +1 -0
- package/dist/frontend/FrontendPluginOrchestrator.d.ts +64 -0
- package/dist/frontend/FrontendPluginOrchestrator.d.ts.map +1 -0
- package/dist/frontend/FrontendPluginOrchestrator.js +167 -0
- package/dist/frontend/FrontendPluginOrchestrator.js.map +1 -0
- package/dist/frontend/SiteResolver.d.ts +33 -0
- package/dist/frontend/SiteResolver.d.ts.map +1 -0
- package/dist/frontend/SiteResolver.js +53 -0
- package/dist/frontend/SiteResolver.js.map +1 -0
- package/dist/frontend/StaticMountRegistry.d.ts +36 -0
- package/dist/frontend/StaticMountRegistry.d.ts.map +1 -0
- package/dist/frontend/StaticMountRegistry.js +94 -0
- package/dist/frontend/StaticMountRegistry.js.map +1 -0
- package/dist/frontend/index.d.ts +7 -0
- package/dist/frontend/index.d.ts.map +1 -0
- package/{src → dist}/frontend/index.js +4 -2
- package/dist/frontend/index.js.map +1 -0
- package/dist/frontend/pathUtils.d.ts +8 -0
- package/dist/frontend/pathUtils.d.ts.map +1 -0
- package/dist/frontend/pathUtils.js +17 -0
- package/dist/frontend/pathUtils.js.map +1 -0
- package/dist/frontend/plugins/index.d.ts +2 -0
- package/dist/frontend/plugins/index.d.ts.map +1 -0
- package/{src → dist}/frontend/plugins/index.js +1 -1
- package/dist/frontend/plugins/index.js.map +1 -0
- package/dist/frontend/plugins/viteFrontend.d.ts +51 -0
- package/dist/frontend/plugins/viteFrontend.d.ts.map +1 -0
- package/dist/frontend/plugins/viteFrontend.js +134 -0
- package/dist/frontend/plugins/viteFrontend.js.map +1 -0
- package/dist/frontend/types.d.ts +25 -0
- package/dist/frontend/types.d.ts.map +1 -0
- package/dist/frontend/types.js +2 -0
- package/dist/frontend/types.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/internals.d.ts +21 -0
- package/dist/internals.d.ts.map +1 -0
- package/{src → dist}/internals.js +12 -14
- package/dist/internals.js.map +1 -0
- package/dist/plugins/PluginManager.d.ts +209 -0
- package/dist/plugins/PluginManager.d.ts.map +1 -0
- package/dist/plugins/PluginManager.js +365 -0
- package/dist/plugins/PluginManager.js.map +1 -0
- package/dist/plugins/ScopedPostgres.d.ts +78 -0
- package/dist/plugins/ScopedPostgres.d.ts.map +1 -0
- package/dist/plugins/ScopedPostgres.js +190 -0
- package/dist/plugins/ScopedPostgres.js.map +1 -0
- package/dist/plugins/ScopedRedis.d.ts +88 -0
- package/dist/plugins/ScopedRedis.d.ts.map +1 -0
- package/dist/plugins/ScopedRedis.js +169 -0
- package/dist/plugins/ScopedRedis.js.map +1 -0
- package/dist/plugins/index.d.ts +289 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +1942 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/types.d.ts +59 -0
- package/dist/plugins/types.d.ts.map +1 -0
- package/dist/plugins/types.js +2 -0
- package/dist/plugins/types.js.map +1 -0
- package/dist/registry/ServiceRegistry.d.ts +305 -0
- package/dist/registry/ServiceRegistry.d.ts.map +1 -0
- package/dist/registry/ServiceRegistry.js +735 -0
- package/dist/registry/ServiceRegistry.js.map +1 -0
- package/dist/scaling/ScaleAdvisor.d.ts +214 -0
- package/dist/scaling/ScaleAdvisor.d.ts.map +1 -0
- package/dist/scaling/ScaleAdvisor.js +526 -0
- package/dist/scaling/ScaleAdvisor.js.map +1 -0
- package/dist/services/Service.d.ts +164 -0
- package/dist/services/Service.d.ts.map +1 -0
- package/dist/services/Service.js +106 -0
- package/dist/services/Service.js.map +1 -0
- package/dist/services/worker-bootstrap.d.ts +15 -0
- package/dist/services/worker-bootstrap.d.ts.map +1 -0
- package/dist/services/worker-bootstrap.js +744 -0
- package/dist/services/worker-bootstrap.js.map +1 -0
- package/dist/templates/auth-service.d.ts +42 -0
- package/dist/templates/auth-service.d.ts.map +1 -0
- package/dist/templates/auth-service.js +54 -0
- package/dist/templates/auth-service.js.map +1 -0
- package/dist/templates/identity-service.d.ts +50 -0
- package/dist/templates/identity-service.d.ts.map +1 -0
- package/dist/templates/identity-service.js +62 -0
- package/dist/templates/identity-service.js.map +1 -0
- package/dist/types/contract.d.ts +120 -0
- package/dist/types/contract.d.ts.map +1 -0
- package/dist/types/contract.js +69 -0
- package/dist/types/contract.js.map +1 -0
- package/package.json +78 -20
- package/src/core/DirectMessageBus.js +0 -364
- package/src/core/EndpointResolver.js +0 -259
- package/src/core/ForgeContext.js +0 -2236
- package/src/core/ForgeHost.js +0 -122
- package/src/core/ForgePlatform.js +0 -145
- package/src/core/Ingress.js +0 -768
- package/src/core/Interceptors.js +0 -420
- package/src/core/MessageBus.js +0 -321
- package/src/core/Prometheus.js +0 -305
- package/src/core/RequestContext.js +0 -413
- package/src/core/RoutingStrategy.js +0 -330
- package/src/core/Supervisor.js +0 -1349
- package/src/core/ThreadAllocator.js +0 -196
- package/src/core/WorkerChannelManager.js +0 -879
- package/src/core/config.js +0 -637
- package/src/core/host-config.js +0 -311
- package/src/core/network-utils.js +0 -166
- package/src/core/platform-config.js +0 -308
- package/src/decorators/ServiceProxy.js +0 -904
- package/src/decorators/index.js +0 -571
- package/src/deploy/NginxGenerator.js +0 -865
- package/src/deploy/PlatformManifestGenerator.js +0 -96
- package/src/deploy/RouteManifestGenerator.js +0 -112
- package/src/deploy/index.js +0 -984
- package/src/frontend/FrontendDevLifecycle.js +0 -65
- package/src/frontend/FrontendPluginOrchestrator.js +0 -187
- package/src/frontend/SiteResolver.js +0 -63
- package/src/frontend/StaticMountRegistry.js +0 -90
- package/src/frontend/plugins/viteFrontend.js +0 -79
- package/src/frontend/types.js +0 -35
- package/src/index.js +0 -58
- package/src/plugins/PluginManager.js +0 -537
- package/src/plugins/ScopedPostgres.js +0 -192
- package/src/plugins/ScopedRedis.js +0 -142
- package/src/plugins/index.js +0 -1756
- package/src/registry/ServiceRegistry.js +0 -797
- package/src/scaling/ScaleAdvisor.js +0 -442
- package/src/services/Service.js +0 -195
- package/src/services/worker-bootstrap.js +0 -679
- package/src/templates/auth-service.js +0 -65
- package/src/templates/identity-service.js +0 -75
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routing Strategies
|
|
3
|
+
*
|
|
4
|
+
* When a service has multiple workers, the proxy needs to decide
|
|
5
|
+
* which worker handles each request. Different strategies suit
|
|
6
|
+
* different service patterns.
|
|
7
|
+
*
|
|
8
|
+
* Strategies:
|
|
9
|
+
*
|
|
10
|
+
* round-robin Default. Cycles through workers sequentially.
|
|
11
|
+
* Best for: stateless services, uniform workloads.
|
|
12
|
+
*
|
|
13
|
+
* hash Routes by a key (e.g., userId). Same key always
|
|
14
|
+
* goes to the same worker. Uses consistent hashing
|
|
15
|
+
* so adding/removing workers only remaps ~1/N keys.
|
|
16
|
+
* Best for: stateful services, in-memory caches,
|
|
17
|
+
* services that benefit from data locality.
|
|
18
|
+
*
|
|
19
|
+
* least-pending Routes to the worker with fewest in-flight requests.
|
|
20
|
+
* Best for: variable-latency services (DB queries,
|
|
21
|
+
* external API calls).
|
|
22
|
+
*
|
|
23
|
+
* broadcast Sends to ALL workers. Returns the first response.
|
|
24
|
+
* Best for: search/aggregation across sharded data.
|
|
25
|
+
*
|
|
26
|
+
* primary Always routes to worker 0. Others are standbys.
|
|
27
|
+
* Best for: singleton services (cron scheduler,
|
|
28
|
+
* leader election).
|
|
29
|
+
*/
|
|
30
|
+
import { createHmac, randomBytes } from "node:crypto";
|
|
31
|
+
import { ROUTING_STRATEGY_NAMES, } from "./config-enums.js";
|
|
32
|
+
// RT-L1: Allow configurable salt via FORGE_HASH_SALT for cross-restart consistency
|
|
33
|
+
const HASH_SALT = process.env.FORGE_HASH_SALT || randomBytes(16).toString("hex");
|
|
34
|
+
// P-1: Fast non-cryptographic FNV-1a hash for runtime routing.
|
|
35
|
+
// HMAC-SHA256 is ~100x slower and unnecessary for load distribution.
|
|
36
|
+
function fnv1a(str) {
|
|
37
|
+
let hash = 0x811c9dc5 | 0; // FNV offset basis
|
|
38
|
+
for (let i = 0; i < str.length; i++) {
|
|
39
|
+
hash ^= str.charCodeAt(i);
|
|
40
|
+
hash = (hash * 0x01000193) | 0; // FNV prime
|
|
41
|
+
}
|
|
42
|
+
return hash >>> 0; // Convert to unsigned 32-bit
|
|
43
|
+
}
|
|
44
|
+
// ─── Round-Robin ────────────────────────────────────────────
|
|
45
|
+
export class RoundRobinStrategy {
|
|
46
|
+
index;
|
|
47
|
+
constructor() {
|
|
48
|
+
this.index = 0;
|
|
49
|
+
}
|
|
50
|
+
pick(workers, _callContext) {
|
|
51
|
+
if (workers.length === 0)
|
|
52
|
+
return null;
|
|
53
|
+
const worker = workers[this.index % workers.length];
|
|
54
|
+
this.index = (this.index + 1) % 1_000_000_000;
|
|
55
|
+
return worker;
|
|
56
|
+
}
|
|
57
|
+
get name() {
|
|
58
|
+
return "round-robin";
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// ─── Hash-Based (Consistent Hashing) ────────────────────────
|
|
62
|
+
export class HashStrategy {
|
|
63
|
+
keyField;
|
|
64
|
+
vnodes;
|
|
65
|
+
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: used as ring-built sentinel at runtime
|
|
66
|
+
_ring;
|
|
67
|
+
_workerCacheKey;
|
|
68
|
+
_ringHashes;
|
|
69
|
+
_ringKeys;
|
|
70
|
+
_fallbackCounter;
|
|
71
|
+
_salt;
|
|
72
|
+
constructor(options = {}) {
|
|
73
|
+
this.keyField = options.key ?? null;
|
|
74
|
+
this.vnodes = options.vnodes ?? 150;
|
|
75
|
+
this._ring = null;
|
|
76
|
+
this._workerCacheKey = "";
|
|
77
|
+
this._fallbackCounter = 0;
|
|
78
|
+
// RT-L1: Allow per-instance salt override for cross-restart consistency
|
|
79
|
+
this._salt = options.salt ?? HASH_SALT;
|
|
80
|
+
}
|
|
81
|
+
pick(workers, callContext) {
|
|
82
|
+
if (workers.length === 0)
|
|
83
|
+
return null;
|
|
84
|
+
if (workers.length === 1)
|
|
85
|
+
return workers[0];
|
|
86
|
+
// Extract the hash key from the call context
|
|
87
|
+
const hashKey = this._extractKey(callContext ?? null);
|
|
88
|
+
if (!hashKey) {
|
|
89
|
+
// No key available — fall back to counter-based round-robin
|
|
90
|
+
const idx = this._fallbackCounter % workers.length;
|
|
91
|
+
this._fallbackCounter = (this._fallbackCounter + 1) % 1_000_000_000;
|
|
92
|
+
return workers[idx];
|
|
93
|
+
}
|
|
94
|
+
// Rebuild ring when worker identities change (not just count).
|
|
95
|
+
// This ensures the ring is correct when workers restart with new keys.
|
|
96
|
+
const currentCacheKey = workers
|
|
97
|
+
.map((w) => w.key)
|
|
98
|
+
.sort()
|
|
99
|
+
.join(",");
|
|
100
|
+
if (this._workerCacheKey !== currentCacheKey) {
|
|
101
|
+
this._buildRing(workers);
|
|
102
|
+
}
|
|
103
|
+
// Find the worker on the ring
|
|
104
|
+
const hash = this._simpleHash(hashKey);
|
|
105
|
+
return this._findOnRing(hash, workers);
|
|
106
|
+
}
|
|
107
|
+
_extractKey(callContext) {
|
|
108
|
+
if (!callContext)
|
|
109
|
+
return null;
|
|
110
|
+
// If a key field is specified, look for it in the call args
|
|
111
|
+
if (this.keyField) {
|
|
112
|
+
const args = callContext.__forge_args ?? [];
|
|
113
|
+
// Check first arg (usually the main parameter)
|
|
114
|
+
if (args.length > 0) {
|
|
115
|
+
if (typeof args[0] === "string" || typeof args[0] === "number") {
|
|
116
|
+
return String(args[0]);
|
|
117
|
+
}
|
|
118
|
+
if (typeof args[0] === "object" && args[0] !== null && args[0][this.keyField]) {
|
|
119
|
+
return String(args[0][this.keyField]);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Try to use __forge_hash if explicitly set by the caller
|
|
124
|
+
if (callContext.__forge_hash) {
|
|
125
|
+
return String(callContext.__forge_hash);
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
_buildRing(workers) {
|
|
130
|
+
// P5: Pre-compute all virtual node hashes and store sorted for binary search
|
|
131
|
+
const totalNodes = workers.length * this.vnodes;
|
|
132
|
+
const hashes = new Uint32Array(totalNodes);
|
|
133
|
+
// RT-H3: Store worker keys instead of positional indices to avoid
|
|
134
|
+
// incorrect mapping when the workers array arrives in a different order.
|
|
135
|
+
const keys = new Array(totalNodes);
|
|
136
|
+
let pos = 0;
|
|
137
|
+
for (let i = 0; i < workers.length; i++) {
|
|
138
|
+
for (let v = 0; v < this.vnodes; v++) {
|
|
139
|
+
hashes[pos] = this._computeHash(`${workers[i].key}:${v}`);
|
|
140
|
+
keys[pos] = workers[i].key;
|
|
141
|
+
pos++;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Sort by hash value, keeping keys aligned
|
|
145
|
+
const sortOrder = Array.from({ length: totalNodes }, (_, i) => i);
|
|
146
|
+
sortOrder.sort((a, b) => hashes[a] - hashes[b]);
|
|
147
|
+
this._ringHashes = new Uint32Array(totalNodes);
|
|
148
|
+
this._ringKeys = new Array(totalNodes);
|
|
149
|
+
for (let i = 0; i < totalNodes; i++) {
|
|
150
|
+
this._ringHashes[i] = hashes[sortOrder[i]];
|
|
151
|
+
this._ringKeys[i] = keys[sortOrder[i]];
|
|
152
|
+
}
|
|
153
|
+
this._ring = true; // sentinel
|
|
154
|
+
this._workerCacheKey = workers
|
|
155
|
+
.map((w) => w.key)
|
|
156
|
+
.sort()
|
|
157
|
+
.join(",");
|
|
158
|
+
}
|
|
159
|
+
_findOnRing(hash, workers) {
|
|
160
|
+
const hashes = this._ringHashes;
|
|
161
|
+
// Binary search for the first ring entry >= hash
|
|
162
|
+
let lo = 0, hi = hashes.length;
|
|
163
|
+
while (lo < hi) {
|
|
164
|
+
const mid = (lo + hi) >>> 1;
|
|
165
|
+
if (hashes[mid] < hash)
|
|
166
|
+
lo = mid + 1;
|
|
167
|
+
else
|
|
168
|
+
hi = mid;
|
|
169
|
+
}
|
|
170
|
+
// Wrap around
|
|
171
|
+
const idx = lo < hashes.length ? lo : 0;
|
|
172
|
+
// RT-H3: Look up worker by key instead of positional index
|
|
173
|
+
const targetKey = this._ringKeys[idx];
|
|
174
|
+
return workers.find((w) => w.key === targetKey) ?? workers[0];
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Pre-compute a keyed SHA-256 hash for ring building (called at build time).
|
|
178
|
+
*/
|
|
179
|
+
_computeHash(str) {
|
|
180
|
+
return createHmac("sha256", this._salt).update(str).digest().readUInt32BE(0);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* P-1: Fast FNV-1a hash for runtime routing decisions.
|
|
184
|
+
* Salted to prevent predictable distribution from external input.
|
|
185
|
+
*/
|
|
186
|
+
_simpleHash(str) {
|
|
187
|
+
return fnv1a(this._salt + str);
|
|
188
|
+
}
|
|
189
|
+
get name() {
|
|
190
|
+
return `hash(${this.keyField ?? "auto"})`;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// ─── Least-Pending ──────────────────────────────────────────
|
|
194
|
+
export class LeastPendingStrategy {
|
|
195
|
+
_pending;
|
|
196
|
+
// RT-M1: Track worker identity set (not just count) for accurate pruning
|
|
197
|
+
_lastWorkerKeys;
|
|
198
|
+
constructor() {
|
|
199
|
+
this._pending = new Map();
|
|
200
|
+
this._lastWorkerKeys = "";
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Pick the worker with the fewest in-flight requests.
|
|
204
|
+
*/
|
|
205
|
+
pick(workers, _callContext) {
|
|
206
|
+
if (workers.length === 0)
|
|
207
|
+
return null;
|
|
208
|
+
if (workers.length === 1)
|
|
209
|
+
return workers[0];
|
|
210
|
+
// RT-M1: Prune when worker identity set changes (handles replacements at same count)
|
|
211
|
+
const currentKeys = workers.map((w) => w.key).sort().join(",");
|
|
212
|
+
if (this._pending.size > 0 && currentKeys !== this._lastWorkerKeys) {
|
|
213
|
+
const activeKeys = new Set(workers.map((w) => w.key));
|
|
214
|
+
for (const key of this._pending.keys()) {
|
|
215
|
+
if (!activeKeys.has(key)) {
|
|
216
|
+
this._pending.delete(key);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
this._lastWorkerKeys = currentKeys;
|
|
221
|
+
let best = workers[0];
|
|
222
|
+
let bestPending = this._pending.get(best.key) ?? 0;
|
|
223
|
+
for (let i = 1; i < workers.length; i++) {
|
|
224
|
+
const p = this._pending.get(workers[i].key) ?? 0;
|
|
225
|
+
if (p < bestPending) {
|
|
226
|
+
best = workers[i];
|
|
227
|
+
bestPending = p;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return best;
|
|
231
|
+
}
|
|
232
|
+
/** Call when dispatching a request to a worker */
|
|
233
|
+
acquire(workerKey) {
|
|
234
|
+
this._pending.set(workerKey, (this._pending.get(workerKey) ?? 0) + 1);
|
|
235
|
+
}
|
|
236
|
+
/** Call when a request to a worker completes */
|
|
237
|
+
release(workerKey) {
|
|
238
|
+
const count = this._pending.get(workerKey) ?? 0;
|
|
239
|
+
if (count <= 0) {
|
|
240
|
+
if (process.env.NODE_ENV !== "production") {
|
|
241
|
+
console.warn("[LeastPendingStrategy] Spurious release()");
|
|
242
|
+
}
|
|
243
|
+
this._pending.delete(workerKey);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (count <= 1) {
|
|
247
|
+
this._pending.delete(workerKey);
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
this._pending.set(workerKey, count - 1);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
get name() {
|
|
254
|
+
return "least-pending";
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// ─── Broadcast ──────────────────────────────────────────────
|
|
258
|
+
export class BroadcastStrategy {
|
|
259
|
+
/**
|
|
260
|
+
* Returns ALL workers. The caller is responsible for sending
|
|
261
|
+
* to all of them and handling multiple responses.
|
|
262
|
+
*/
|
|
263
|
+
pick(workers, _callContext) {
|
|
264
|
+
return workers; // Return the full array — caller detects this
|
|
265
|
+
}
|
|
266
|
+
get name() {
|
|
267
|
+
return "broadcast";
|
|
268
|
+
}
|
|
269
|
+
get isBroadcast() {
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// ─── Primary ────────────────────────────────────────────────
|
|
274
|
+
export class PrimaryStrategy {
|
|
275
|
+
/**
|
|
276
|
+
* Always routes to worker index 0. If worker 0 is down,
|
|
277
|
+
* routes to the next available.
|
|
278
|
+
*/
|
|
279
|
+
pick(workers, _callContext) {
|
|
280
|
+
if (workers.length === 0)
|
|
281
|
+
return null;
|
|
282
|
+
return workers[0]; // Primary is always first
|
|
283
|
+
}
|
|
284
|
+
get name() {
|
|
285
|
+
return "primary";
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// ─── Strategy Factory ───────────────────────────────────────
|
|
289
|
+
const STRATEGIES = {
|
|
290
|
+
"round-robin": (_opts) => new RoundRobinStrategy(),
|
|
291
|
+
hash: (opts) => new HashStrategy(opts),
|
|
292
|
+
"least-pending": (_opts) => new LeastPendingStrategy(),
|
|
293
|
+
broadcast: (_opts) => new BroadcastStrategy(),
|
|
294
|
+
primary: (_opts) => new PrimaryStrategy(),
|
|
295
|
+
};
|
|
296
|
+
/**
|
|
297
|
+
* Create a routing strategy by name.
|
|
298
|
+
*/
|
|
299
|
+
export function createStrategy(name, options = {}) {
|
|
300
|
+
const factory = STRATEGIES[name];
|
|
301
|
+
if (!factory) {
|
|
302
|
+
throw new Error(`Unknown routing strategy: "${name}". Available: ${ROUTING_STRATEGY_NAMES.join(", ")}`);
|
|
303
|
+
}
|
|
304
|
+
return factory(options);
|
|
305
|
+
}
|
|
306
|
+
//# sourceMappingURL=RoutingStrategy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RoutingStrategy.js","sourceRoot":"","sources":["../../src/core/RoutingStrategy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RPC Configuration Utilities
|
|
3
|
+
*
|
|
4
|
+
* Centralizes validation, merge logic, and resolution for per-service
|
|
5
|
+
* and per-target RPC options (circuit breaker, retry, timeout, bulkhead,
|
|
6
|
+
* concurrency).
|
|
7
|
+
*
|
|
8
|
+
* Merge precedence (lowest → highest):
|
|
9
|
+
* 1. Framework defaults (in Interceptors.ts constructors)
|
|
10
|
+
* 2. Service-level `rpc` field
|
|
11
|
+
* 3. Target-level `rpcTargets[targetName]`
|
|
12
|
+
* 4. Contract-level options (static contract = { circuitBreaker: ... })
|
|
13
|
+
* 5. Per-method decorator options (@Expose({ retry: false }))
|
|
14
|
+
*
|
|
15
|
+
* Steps 1-3 are resolved here. Steps 4-5 are resolved in ServiceProxy.
|
|
16
|
+
*/
|
|
17
|
+
export interface CircuitBreakerOptions {
|
|
18
|
+
failureThreshold?: number;
|
|
19
|
+
/** Percentage-based failure rate threshold (0-100). When set, the breaker
|
|
20
|
+
* opens if the failure rate within the sliding window exceeds this value
|
|
21
|
+
* AND the minimum sample count is met. Takes precedence over failureThreshold. */
|
|
22
|
+
failureRateThreshold?: number;
|
|
23
|
+
/** Minimum number of calls in the window before failure rate is evaluated. */
|
|
24
|
+
minimumCallCount?: number;
|
|
25
|
+
resetTimeoutMs?: number;
|
|
26
|
+
successThreshold?: number;
|
|
27
|
+
windowMs?: number;
|
|
28
|
+
/** Number of concurrent probe requests allowed in half-open state. */
|
|
29
|
+
halfOpenMaxProbes?: number;
|
|
30
|
+
}
|
|
31
|
+
export interface RetryOptions {
|
|
32
|
+
maxAttempts?: number;
|
|
33
|
+
baseDelayMs?: number;
|
|
34
|
+
maxDelayMs?: number;
|
|
35
|
+
}
|
|
36
|
+
export interface BulkheadOptions {
|
|
37
|
+
maxConcurrent?: number;
|
|
38
|
+
}
|
|
39
|
+
export interface ConcurrencyOptions {
|
|
40
|
+
initialLimit?: number;
|
|
41
|
+
minLimit?: number;
|
|
42
|
+
maxLimit?: number;
|
|
43
|
+
smoothing?: number;
|
|
44
|
+
tolerance?: number;
|
|
45
|
+
}
|
|
46
|
+
export interface RpcOptions {
|
|
47
|
+
circuitBreaker?: CircuitBreakerOptions;
|
|
48
|
+
retry?: RetryOptions | false;
|
|
49
|
+
defaultTimeout?: number;
|
|
50
|
+
bulkhead?: BulkheadOptions;
|
|
51
|
+
concurrency?: ConcurrencyOptions;
|
|
52
|
+
}
|
|
53
|
+
export interface RpcConfig {
|
|
54
|
+
rpc?: RpcOptions;
|
|
55
|
+
rpcTargets?: Record<string, RpcOptions>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Validate an RPC options object shape.
|
|
59
|
+
*/
|
|
60
|
+
export declare function validateRpcOptions(serviceName: string, label: string, options: unknown): void;
|
|
61
|
+
/**
|
|
62
|
+
* Deep-merge two RPC options objects. Override values take precedence.
|
|
63
|
+
* For nested objects (circuitBreaker, retry, bulkhead, concurrency),
|
|
64
|
+
* individual fields merge so you only need to specify what changes.
|
|
65
|
+
*/
|
|
66
|
+
export declare function mergeRpcOptions(base?: RpcOptions, override?: RpcOptions): RpcOptions;
|
|
67
|
+
/**
|
|
68
|
+
* Resolve the final RPC options for a specific target service.
|
|
69
|
+
* Merges service-level `rpc` defaults with target-specific `rpcTargets` overrides.
|
|
70
|
+
*/
|
|
71
|
+
export declare function resolveRpcOptionsForTarget(config: RpcConfig | undefined, targetName: string): RpcOptions;
|
|
72
|
+
//# sourceMappingURL=RpcConfig.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RpcConfig.d.ts","sourceRoot":"","sources":["../../src/core/RpcConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,WAAW,qBAAqB;IACpC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;uFAEmF;IACnF,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,cAAc,CAAC,EAAE,qBAAqB,CAAC;IACvC,KAAK,CAAC,EAAE,YAAY,GAAG,KAAK,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,WAAW,CAAC,EAAE,kBAAkB,CAAC;CAClC;AAED,MAAM,WAAW,SAAS;IACxB,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACzC;AAqBD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAuC7F;AA0BD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,QAAQ,CAAC,EAAE,UAAU,GAAG,UAAU,CAmCpF;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,EAAE,UAAU,EAAE,MAAM,GAAG,UAAU,CAIxG"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RPC Configuration Utilities
|
|
3
|
+
*
|
|
4
|
+
* Centralizes validation, merge logic, and resolution for per-service
|
|
5
|
+
* and per-target RPC options (circuit breaker, retry, timeout, bulkhead,
|
|
6
|
+
* concurrency).
|
|
7
|
+
*
|
|
8
|
+
* Merge precedence (lowest → highest):
|
|
9
|
+
* 1. Framework defaults (in Interceptors.ts constructors)
|
|
10
|
+
* 2. Service-level `rpc` field
|
|
11
|
+
* 3. Target-level `rpcTargets[targetName]`
|
|
12
|
+
* 4. Contract-level options (static contract = { circuitBreaker: ... })
|
|
13
|
+
* 5. Per-method decorator options (@Expose({ retry: false }))
|
|
14
|
+
*
|
|
15
|
+
* Steps 1-3 are resolved here. Steps 4-5 are resolved in ServiceProxy.
|
|
16
|
+
*/
|
|
17
|
+
const VALID_RPC_KEYS = new Set([
|
|
18
|
+
"circuitBreaker", "retry", "defaultTimeout", "bulkhead", "concurrency",
|
|
19
|
+
]);
|
|
20
|
+
const VALID_CB_KEYS = new Set([
|
|
21
|
+
"failureThreshold", "failureRateThreshold", "minimumCallCount",
|
|
22
|
+
"resetTimeoutMs", "successThreshold", "windowMs", "halfOpenMaxProbes",
|
|
23
|
+
]);
|
|
24
|
+
const VALID_RETRY_KEYS = new Set([
|
|
25
|
+
"maxAttempts", "baseDelayMs", "maxDelayMs",
|
|
26
|
+
]);
|
|
27
|
+
const VALID_BULKHEAD_KEYS = new Set(["maxConcurrent"]);
|
|
28
|
+
const VALID_CONCURRENCY_KEYS = new Set([
|
|
29
|
+
"initialLimit", "minLimit", "maxLimit", "smoothing", "tolerance",
|
|
30
|
+
]);
|
|
31
|
+
/**
|
|
32
|
+
* Validate an RPC options object shape.
|
|
33
|
+
*/
|
|
34
|
+
export function validateRpcOptions(serviceName, label, options) {
|
|
35
|
+
if (options === undefined || options === null)
|
|
36
|
+
return;
|
|
37
|
+
if (typeof options !== "object" || Array.isArray(options)) {
|
|
38
|
+
throw new Error(`Service "${serviceName}": ${label} must be an object`);
|
|
39
|
+
}
|
|
40
|
+
const opts = options;
|
|
41
|
+
for (const key of Object.keys(opts)) {
|
|
42
|
+
if (!VALID_RPC_KEYS.has(key)) {
|
|
43
|
+
throw new Error(`Service "${serviceName}": unknown ${label} key "${key}". ` +
|
|
44
|
+
`Valid keys: ${[...VALID_RPC_KEYS].join(", ")}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (opts.circuitBreaker !== undefined) {
|
|
48
|
+
_validateSubObject(serviceName, `${label}.circuitBreaker`, opts.circuitBreaker, VALID_CB_KEYS, "number");
|
|
49
|
+
}
|
|
50
|
+
if (opts.retry !== undefined && opts.retry !== false) {
|
|
51
|
+
_validateSubObject(serviceName, `${label}.retry`, opts.retry, VALID_RETRY_KEYS, "number");
|
|
52
|
+
}
|
|
53
|
+
if (opts.defaultTimeout !== undefined) {
|
|
54
|
+
if (typeof opts.defaultTimeout !== "number" || opts.defaultTimeout <= 0) {
|
|
55
|
+
throw new Error(`Service "${serviceName}": ${label}.defaultTimeout must be a positive number`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (opts.bulkhead !== undefined) {
|
|
59
|
+
_validateSubObject(serviceName, `${label}.bulkhead`, opts.bulkhead, VALID_BULKHEAD_KEYS, "number");
|
|
60
|
+
}
|
|
61
|
+
if (opts.concurrency !== undefined) {
|
|
62
|
+
_validateSubObject(serviceName, `${label}.concurrency`, opts.concurrency, VALID_CONCURRENCY_KEYS, "number");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function _validateSubObject(serviceName, path, obj, validKeys, valueType) {
|
|
66
|
+
if (typeof obj !== "object" || Array.isArray(obj) || obj === null) {
|
|
67
|
+
throw new Error(`Service "${serviceName}": ${path} must be an object`);
|
|
68
|
+
}
|
|
69
|
+
const record = obj;
|
|
70
|
+
for (const key of Object.keys(record)) {
|
|
71
|
+
if (!validKeys.has(key)) {
|
|
72
|
+
throw new Error(`Service "${serviceName}": unknown ${path} key "${key}". ` +
|
|
73
|
+
`Valid keys: ${[...validKeys].join(", ")}`);
|
|
74
|
+
}
|
|
75
|
+
if (typeof record[key] !== valueType) {
|
|
76
|
+
throw new Error(`Service "${serviceName}": ${path}.${key} must be a ${valueType}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Deep-merge two RPC options objects. Override values take precedence.
|
|
82
|
+
* For nested objects (circuitBreaker, retry, bulkhead, concurrency),
|
|
83
|
+
* individual fields merge so you only need to specify what changes.
|
|
84
|
+
*/
|
|
85
|
+
export function mergeRpcOptions(base, override) {
|
|
86
|
+
if (!override)
|
|
87
|
+
return base ?? {};
|
|
88
|
+
if (!base)
|
|
89
|
+
return override;
|
|
90
|
+
const merged = {};
|
|
91
|
+
// circuitBreaker: deep merge
|
|
92
|
+
if (base.circuitBreaker || override.circuitBreaker) {
|
|
93
|
+
merged.circuitBreaker = { ...base.circuitBreaker, ...override.circuitBreaker };
|
|
94
|
+
}
|
|
95
|
+
// retry: false disables retries; object values deep-merge; undefined inherits from base
|
|
96
|
+
if (override.retry === false) {
|
|
97
|
+
merged.retry = false;
|
|
98
|
+
}
|
|
99
|
+
else if (override.retry !== undefined) {
|
|
100
|
+
const baseRetry = base.retry === false ? {} : (base.retry ?? {});
|
|
101
|
+
merged.retry = { ...baseRetry, ...override.retry };
|
|
102
|
+
}
|
|
103
|
+
else if (base.retry !== undefined) {
|
|
104
|
+
merged.retry = base.retry;
|
|
105
|
+
}
|
|
106
|
+
// defaultTimeout: scalar override
|
|
107
|
+
merged.defaultTimeout = override.defaultTimeout ?? base.defaultTimeout;
|
|
108
|
+
// bulkhead: deep merge
|
|
109
|
+
if (base.bulkhead || override.bulkhead) {
|
|
110
|
+
merged.bulkhead = { ...base.bulkhead, ...override.bulkhead };
|
|
111
|
+
}
|
|
112
|
+
// concurrency: deep merge
|
|
113
|
+
if (base.concurrency || override.concurrency) {
|
|
114
|
+
merged.concurrency = { ...base.concurrency, ...override.concurrency };
|
|
115
|
+
}
|
|
116
|
+
return merged;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Resolve the final RPC options for a specific target service.
|
|
120
|
+
* Merges service-level `rpc` defaults with target-specific `rpcTargets` overrides.
|
|
121
|
+
*/
|
|
122
|
+
export function resolveRpcOptionsForTarget(config, targetName) {
|
|
123
|
+
const base = config?.rpc ?? {};
|
|
124
|
+
const targetOverride = config?.rpcTargets?.[targetName];
|
|
125
|
+
return mergeRpcOptions(base, targetOverride);
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=RpcConfig.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RpcConfig.js","sourceRoot":"","sources":["../../src/core/RpcConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAgDH,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC;IAC7B,gBAAgB,EAAE,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,aAAa;CACvE,CAAC,CAAC;AAEH,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC;IAC5B,kBAAkB,EAAE,sBAAsB,EAAE,kBAAkB;IAC9D,gBAAgB,EAAE,kBAAkB,EAAE,UAAU,EAAE,mBAAmB;CACtE,CAAC,CAAC;AAEH,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC;IAC/B,aAAa,EAAE,aAAa,EAAE,YAAY;CAC3C,CAAC,CAAC;AAEH,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC;AAEvD,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC;IACrC,cAAc,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW;CACjE,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,WAAmB,EAAE,KAAa,EAAE,OAAgB;IACrF,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO;IAEtD,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,KAAK,CAAC,YAAY,WAAW,MAAM,KAAK,oBAAoB,CAAC,CAAC;IAC1E,CAAC;IAED,MAAM,IAAI,GAAG,OAAkC,CAAC;IAEhD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CACb,YAAY,WAAW,cAAc,KAAK,SAAS,GAAG,KAAK;gBAC3D,eAAe,CAAC,GAAG,cAAc,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAChD,CAAC;QACJ,CAAC;IACH,CAAC;IAED,IAAI,IAAI,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;QACtC,kBAAkB,CAAC,WAAW,EAAE,GAAG,KAAK,iBAAiB,EAAE,IAAI,CAAC,cAAc,EAAE,aAAa,EAAE,QAAQ,CAAC,CAAC;IAC3G,CAAC;IAED,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;QACrD,kBAAkB,CAAC,WAAW,EAAE,GAAG,KAAK,QAAQ,EAAE,IAAI,CAAC,KAAK,EAAE,gBAAgB,EAAE,QAAQ,CAAC,CAAC;IAC5F,CAAC;IAED,IAAI,IAAI,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;QACtC,IAAI,OAAO,IAAI,CAAC,cAAc,KAAK,QAAQ,IAAI,IAAI,CAAC,cAAc,IAAI,CAAC,EAAE,CAAC;YACxE,MAAM,IAAI,KAAK,CAAC,YAAY,WAAW,MAAM,KAAK,2CAA2C,CAAC,CAAC;QACjG,CAAC;IACH,CAAC;IAED,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;QAChC,kBAAkB,CAAC,WAAW,EAAE,GAAG,KAAK,WAAW,EAAE,IAAI,CAAC,QAAQ,EAAE,mBAAmB,EAAE,QAAQ,CAAC,CAAC;IACrG,CAAC;IAED,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACnC,kBAAkB,CAAC,WAAW,EAAE,GAAG,KAAK,cAAc,EAAE,IAAI,CAAC,WAAW,EAAE,sBAAsB,EAAE,QAAQ,CAAC,CAAC;IAC9G,CAAC;AACH,CAAC;AAED,SAAS,kBAAkB,CACzB,WAAmB,EACnB,IAAY,EACZ,GAAY,EACZ,SAAsB,EACtB,SAAiB;IAEjB,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QAClE,MAAM,IAAI,KAAK,CAAC,YAAY,WAAW,MAAM,IAAI,oBAAoB,CAAC,CAAC;IACzE,CAAC;IACD,MAAM,MAAM,GAAG,GAA8B,CAAC;IAC9C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACtC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CACb,YAAY,WAAW,cAAc,IAAI,SAAS,GAAG,KAAK;gBAC1D,eAAe,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC3C,CAAC;QACJ,CAAC;QACD,IAAI,OAAO,MAAM,CAAC,GAAG,CAAC,KAAK,SAAS,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CAAC,YAAY,WAAW,MAAM,IAAI,IAAI,GAAG,cAAc,SAAS,EAAE,CAAC,CAAC;QACrF,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,IAAiB,EAAE,QAAqB;IACtE,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,IAAI,EAAE,CAAC;IACjC,IAAI,CAAC,IAAI;QAAE,OAAO,QAAQ,CAAC;IAE3B,MAAM,MAAM,GAAe,EAAE,CAAC;IAE9B,6BAA6B;IAC7B,IAAI,IAAI,CAAC,cAAc,IAAI,QAAQ,CAAC,cAAc,EAAE,CAAC;QACnD,MAAM,CAAC,cAAc,GAAG,EAAE,GAAG,IAAI,CAAC,cAAc,EAAE,GAAG,QAAQ,CAAC,cAAc,EAAE,CAAC;IACjF,CAAC;IAED,wFAAwF;IACxF,IAAI,QAAQ,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;QAC7B,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;IACvB,CAAC;SAAM,IAAI,QAAQ,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QACxC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;QACjE,MAAM,CAAC,KAAK,GAAG,EAAE,GAAG,SAAS,EAAE,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC;IACrD,CAAC;SAAM,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QACpC,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IAC5B,CAAC;IAED,kCAAkC;IAClC,MAAM,CAAC,cAAc,GAAG,QAAQ,CAAC,cAAc,IAAI,IAAI,CAAC,cAAc,CAAC;IAEvE,uBAAuB;IACvB,IAAI,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACvC,MAAM,CAAC,QAAQ,GAAG,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,QAAQ,CAAC,QAAQ,EAAE,CAAC;IAC/D,CAAC;IAED,0BAA0B;IAC1B,IAAI,IAAI,CAAC,WAAW,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC;QAC7C,MAAM,CAAC,WAAW,GAAG,EAAE,GAAG,IAAI,CAAC,WAAW,EAAE,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;IACxE,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CAAC,MAA6B,EAAE,UAAkB;IAC1F,MAAM,IAAI,GAAG,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC;IAC/B,MAAM,cAAc,GAAG,MAAM,EAAE,UAAU,EAAE,CAAC,UAAU,CAAC,CAAC;IACxD,OAAO,eAAe,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;AAC/C,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SignatureCache - Rotating HMAC signature cache for internal RPC optimization
|
|
3
|
+
*
|
|
4
|
+
* P-PERF-29: Reduce per-request HMAC computation cost by caching signatures with
|
|
5
|
+
* periodic rotation. Maintains replay protection guarantees while reducing CPU overhead.
|
|
6
|
+
*
|
|
7
|
+
* Security Model:
|
|
8
|
+
* - Signatures rotate every 5 seconds (configurable)
|
|
9
|
+
* - Multiple signatures active during overlap window for clock drift tolerance
|
|
10
|
+
* - Replay protection window: 30 seconds (INTERNAL_SIG_MAX_AGE_MS)
|
|
11
|
+
* - Rotation interval (5s) << replay window (30s) = safe
|
|
12
|
+
*
|
|
13
|
+
* Performance:
|
|
14
|
+
* - Reduces HMAC computations by ~5s/rotation_interval ratio
|
|
15
|
+
* - At 1000 RPS: 5000 HMAC ops/rotation vs 1 per rotation = ~5000x reduction
|
|
16
|
+
* - Memory overhead: ~200 bytes per cached signature entry
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Configuration for signature cache behavior
|
|
20
|
+
*/
|
|
21
|
+
export interface SignatureCacheConfig {
|
|
22
|
+
/** How often to rotate signatures (ms). Default: 5000 (5s) */
|
|
23
|
+
rotationIntervalMs?: number;
|
|
24
|
+
/** Whether caching is enabled. Default: false (opt-in for safety) */
|
|
25
|
+
enabled?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* SignatureCache maintains a rotating set of precomputed HMAC signatures
|
|
29
|
+
* for internal RPC authentication. Reduces per-request CPU cost while
|
|
30
|
+
* preserving replay protection guarantees.
|
|
31
|
+
*/
|
|
32
|
+
export declare class SignatureCache {
|
|
33
|
+
private readonly secret;
|
|
34
|
+
private readonly rotationIntervalMs;
|
|
35
|
+
private readonly enabled;
|
|
36
|
+
private currentSignatures;
|
|
37
|
+
private previousSignatures;
|
|
38
|
+
private currentTimestamp;
|
|
39
|
+
private previousTimestamp;
|
|
40
|
+
private rotationTimer;
|
|
41
|
+
constructor(secret: string, config?: SignatureCacheConfig);
|
|
42
|
+
/**
|
|
43
|
+
* Get a signature for the given method and path.
|
|
44
|
+
* If caching is disabled, computes a fresh signature each time.
|
|
45
|
+
* If caching is enabled, returns the current cached signature.
|
|
46
|
+
*/
|
|
47
|
+
getSignature(method: string, path: string): {
|
|
48
|
+
signature: string;
|
|
49
|
+
timestamp: string;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Check if caching is enabled
|
|
53
|
+
*/
|
|
54
|
+
isEnabled(): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Get cache statistics for monitoring
|
|
57
|
+
*/
|
|
58
|
+
getStats(): {
|
|
59
|
+
enabled: boolean;
|
|
60
|
+
rotationIntervalMs: number;
|
|
61
|
+
currentTimestamp: number | null;
|
|
62
|
+
previousTimestamp: number | null;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Stop the rotation timer (for cleanup)
|
|
66
|
+
*/
|
|
67
|
+
stop(): void;
|
|
68
|
+
/**
|
|
69
|
+
* Force an immediate rotation (useful for testing)
|
|
70
|
+
*/
|
|
71
|
+
rotate(): void;
|
|
72
|
+
private _initializeCache;
|
|
73
|
+
private _startRotation;
|
|
74
|
+
private _rotateSignature;
|
|
75
|
+
private _computeFreshSignature;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Create a signature cache instance from environment configuration
|
|
79
|
+
*/
|
|
80
|
+
export declare function createSignatureCacheFromEnv(): SignatureCache | null;
|
|
81
|
+
//# sourceMappingURL=SignatureCache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SignatureCache.d.ts","sourceRoot":"","sources":["../../src/core/SignatureCache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAYH;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,8DAA8D;IAC9D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,qEAAqE;IACrE,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;GAIG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAGlC,OAAO,CAAC,iBAAiB,CAA0C;IACnE,OAAO,CAAC,kBAAkB,CAA0C;IAGpE,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,iBAAiB,CAAuB;IAGhD,OAAO,CAAC,aAAa,CAA+B;gBAExC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,oBAAyB;IAmC7D;;;;OAIG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE;IA8BpF;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,QAAQ,IAAI;QACV,OAAO,EAAE,OAAO,CAAC;QACjB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;QAChC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;KAClC;IASD;;OAEG;IACH,IAAI,IAAI,IAAI;IAOZ;;OAEG;IACH,MAAM,IAAI,IAAI;IAMd,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,gBAAgB;IAYxB,OAAO,CAAC,sBAAsB;CAK/B;AAED;;GAEG;AACH,wBAAgB,2BAA2B,IAAI,cAAc,GAAG,IAAI,CAenE"}
|