Always ensure this document remains up-to-date with progress
- Extract the reusable RxJS-driven web server stack from
packages/examples/serverintopackages/rxpress. - Ship a TypeScript-friendly package that external apps can consume via
npm i rxpress. - Leave
packages/examples/serveras a thin example/host that depends on the published library.
packages/rxpress/libalready mirrors some server services but is incomplete (mixed.tssources, no build pipeline, no exported entry point).packages/examples/serverowns the production-ready implementations (EventService,Logger,ConfigService, RPC route/event definitions, CRON wiring).- Workspace tooling uses Nx + npm workspaces; there is no dedicated publish flow for
rxpress.
- Audit server features: catalogue route/event/cron helpers, metrics, logging, KV store expectations, and config contracts still living only in
packages/examples/server. - Align TypeScript configs: create
packages/rxpress/tsconfig.json(emit todist/, ES2022 target, declaration output) and add build/test scripts. - Drop ad-hoc TS in
lib/: relocate sources tosrc/to match TS build output, keeping.d.tsgeneration in mind.
-
packages/examples/server/src/main.tsorchestrates express server setup, dynamic loading of routes/events, and usesConfigService.__rootDirplusglobto discover handlers. - Logging relies on
Loggerwrapping EventService (app::logtopic) with env-driven levels; no equivalent concrete logger bundled inrxpress. -
KVServiceprovides optional file persistence and seeds keys relative toConfigService.__rootDir;rxpressexposes onlyKVBaseinterface without storage implementation. - Events (
src/events/*.app-log.js) demonstrate log sinks and expectlogger/triggercontext; cron wiring currently exists only inpackages/rxpress(library) and is unused by server example. - Metrics, process handlers, and OTEL setup live in
rxpress/lib/services/metrics.service.tsbut servermain.tsstill configures emits/topics manually; need unified bootstrap. - Type declarations in
packages/examples/server/src/types/rpc.tsdepend on concreteLoggerandKVService; library variant already abstracts these via interfaces.
- Port services/types: move
ConfigService,EventService, route orchestrator, metrics, cron, and typing utilities from server intorxpress/src, merging with any partial implementations already there. - Design pluggable infrastructure: define minimal logger/KV/process handler interfaces so consumers can attach adapters (pino/console/morgan, Redis/in-memory, etc.); keep
rxpressagnostic to concrete implementations and avoid bundling defaults. - Rebuild orchestrator API: expose
init/start/stop/loadentry points insrc/index.ts, ensure dynamic loader paths work outside the monorepo (avoidConfigService.__rootDirassumptions).
- Switched
rxpressbuild to NodeNext ESM output with explicit.jsextensions and runtime guards. - Declared package dependencies (express, glob, cron, RxJS, OTEL) under
packages/rxpress/package.jsonto satisfy TypeScript and publish requirements. - Added ts-node based smoke test and
tsconfigtuned forsrc/sources;npm run build --workspace rxpressnow succeeds. - Logger and KV adapters stay consumer-supplied; rxpress exposes interfaces plus new config hooks for rootDir/envFiles.
- Wire build tooling: add
npm run buildto compile todist, updatepackage.json(main,exports,types, clean description, semver). Includefiles: ["dist"]and remove TypeScript sources from publish payload. - Add tests/examples: port existing smoke tests, add integration tests that spin up express app via library; document fixtures under
__tests__. - Update docs: author README usage guide and migration notes; ensure AGENTS.md references new workflow if needed.
- Refactor
packages/examples/server: replace local service imports withrxpressexports; keep only project-specific routes/events/config. - Provide wrapper bootstrap: update server startup scripts to call
rxpress.init()/start()instead of bespoke logic; confirm environment loading still works via new API.
-
Cleaned
package.jsonmetadata, set version 0.1.0, and limited publish artifacts todist+ README. -
Added README quick start documenting adapter expectations.
-
Documented helper adapters under
src/helpersand wired README examples to real implementations. -
Added ts-node driven integration test with stub adapters; skips gracefully when sandbox blocks listening sockets.
-
Added CHANGELOG and publishing checklist to document release flow.
-
Added cron integration smoke test covering scheduler + event pipeline.
-
Automated semantic-release workflow with changelog, npm publish, and git tagging.
-
README helper example covered by automated integration test.
-
Observability dashboard plots “Total rxpress requests” via Prometheus (other panels pending).
- Add automated test that compiles the README helper example to guard against regressions.
- Document how to run the
npm run releaseworkflow (semantic-release) in CONTRIBUTING/README.
- Retire duplicated config service: removed local ConfigService after exposing root resolution via library helpers.
- Server example now calls
rxpress.init/start, registers routes/events through the library, and relies on shared logger/KV adapters. - Local ConfigService removed; server uses shared helper configuration.
- End-to-end validation: run
npm run build --workspace rxpress,npm packto inspect tarball, and test consuming it from a sample app (npm init -y && npm install ../rxpress-*.tgz). - Lint & format automation: wired ESLint + Prettier into CI workflow and added Husky/lint-staged pre-commit checks.
- Version & release: establish release checklist, bump
version, add CHANGELOG entry, and preparenpm publishworkflow. - Post-publish integration: update server workspace to depend on published semver (instead of relative path) and verify
npm install rxpressworks in a clean environment. - Observability stack: added Docker Compose (OTel Collector + Grafana) and default server telemetry configuration. Sample Grafana dashboard auto-provisioned via docker-compose.
- Design bridge service: create
GrpcBridgeServiceunderpackages/rxpress/src/services/grpc.service.tsthat loadshandler_bridge.proto, hostsInvoker+ControlPlane, and exposesinit,invokeRoute, andinvokeEventAPIs. Move the proto into the library (e.g.,packages/rxpress/src/grpc/proto/handler_bridge.proto) and ensure it ships in the bundle. - Extend configuration types: update
RxpressConfig,RPCConfig, andEventConfigto support{ kind: 'grpc'; handlerName: string; target?: string; timeoutMs?: number; metadata?: Record<string,string>; }alongside existing local handlers. Providegrpcroot config (proto path, default target, optional local handler directories) inpackages/rxpress/src/types/index.ts. - Integrate with routes/events: modify
RouteServiceandEventServiceso entries markedkind: 'grpc'forward requests viaGrpcBridgeService, carry run IDs/span context inInvokeRequest.meta, and translateInvokeResponsepayloads/errors back into current HTTP/event semantics. - Context bridging: implement ControlPlane handling that maps remote
log,emit, andkvoperations onto Rxpress’ logger,EventService.emit, and KV adapters (including run-scoped KV keys). Add cleanup to release run scopes when the stream closes. - Local handler bootstrap: add optional
grpc.localHandlersconfig that loads TypeScript handlers (mirroringgrpc_example/orchestrator/handlers) so existing projects can adopt gRPC without remote processes. Ensure future remote handlers can reuse the same proto without code changes. - Testing matrix: create integration tests in
packages/rxpress/__tests__/covering (a) HTTP route invoking a gRPC handler, (b) event subscription invoking a gRPC handler, and (c) run-scope propagation across the boundary. Tests should assertlog,emit, andkvround-tripping via ControlPlane. - Documentation updates: document gRPC usage in
packages/rxpress/docs/(newgrpc.md, references in routing/events guides, README quick links) including polyglot handler guidance, configuration examples, and local vs remote handler deployment notes. - Future remote support notes: documented the remaining gRPC roadmap and stood up first-class health checks plus file-based discovery refresh. Follow-on items now focus on:
- Extending discovery beyond static files (e.g., DNS/service registry adapters, dynamic scale-out).
- Streaming RPC support (allow long-lived bidi streams for real-time workflows).
- Operational tooling (metrics on bridge throughput/errors, admin endpoints to list active handler connections).
- Implement service discovery/health checks so the bridge can target multiple remote handler hosts.
- Add mTLS to secure traffic between the Node.js orchestrator and remote language runtimes.
- Extend your handlers with streaming RPCs when you need long-lived bidirectional workflows.
- Keep logger/KV integration fully adapter-based; document required interface signatures so teams can slot in console, pino, Redis, memory, or other tooling without coupling.
- Ensure dynamic
globloaders resolve correctly once compiled todist/(might require switching toimport.meta.urlrelative paths). - Decide how much telemetry/metrics functionality is core vs optional to keep install size reasonable.
- Document Node.js version requirement (Node 20+) and any peer dependencies (e.g.,
express,rxjs). - Plan for backwards compatibility if external consumers expect existing server behavior; provide migration notes or wrappers.
Cluster Rollout Plan
- Config & Initialization
services always see { enabled: boolean; workers: number; hashSelectors: [...] }.
- Update packages/rxpress/src/types to expose the new config, and add validation so values ≤0 fall back to CPU count.
- ClusterService (Primary Role)
- Create ClusterService.start(config) invoked from rxpress.createServer when worker count > 1.
- Primary responsibilities:
- Fork N workers, track PIDs, restart on exit if restartOnExit !== false.
- Stand up a lightweight TCP server (sticky server) bound to the configured WS port. On connection, inspect headers for x-forwarded-for first, else use socket.remoteAddress, hash to worker index, and hand off the socket via worker.send({ type: 'sticky-connection' }, socket).
- Forward OS signals (SIGINT/SIGTERM) to workers, await “shutdown-complete” acknowledgements, then close dispatcher and exit.
- Emit topology/trace events (SYS::CLUSTER::*) for observability.
- Worker Bootstrap Path
- Workers run existing Express HTTP server as-is; when notified (process.on('message')) of a sticky upgrade, recreate the upgrade by calling a new WSSService.attachSocket({ request, socket, head }).
- Ensure workers register WSSService.configureClusterBridge({ workerId, sendToPrimary }) so broadcasts can loop through the primary.
- Broadcast Fan-out & Trace Preservation
- Keep SYS::WSS::BROADCAST event handler per worker; modifies WSSService.publish to:
- Dispatch locally.
- Send serialized envelopes to primary when origin is worker.
- Primary listens on WSSService.onPublish (registered within ClusterService) to rebroadcast envelopes to all other workers, preserving traceContext and skipping the origin worker.
- Keep SYS::WSS::BROADCAST event handler per worker; modifies WSSService.publish to:
- Lifecycle & Health
- Workers send READY/SHUTDOWN messages; primary maintains a map to support graceful stop and restarts.
- Incorporate restart throttling (e.g., max restarts in 1 min) to avoid thrash.
- Stateless HTTP Handling
- HTTP requests continue to use Node’s server.listen per worker (SO_REUSEPORT) since stateless behavior is acceptable; sticky dispatcher only handles WS upgrades to maintain session affinity.
- Documentation & Tests
- Update PLAN.md Phase 6 checklist as milestones complete.
- Add docs: new cluster.md in docs/, README section, and example server configuration snippet.
- Write integration test (skippable if clustering unsupported) that starts 2-worker cluster, broadcasts from worker A, asserts worker B receives via bridge.
- Adjust existing tests to account for new config defaults.
- Add cluster configuration block (worker count, dispatcher strategy, graceful shutdown).
- Introduce startCluster/stopCluster orchestration within
rxpress.createServer/rxpress.stop(primary vs worker roles). - Implement primary net dispatcher for sticky sockets (or SO_REUSEPORT fallback).
- Replace direct WebSocket writes with RxJS broadcast subject.
- Route broadcasts through EventService.emit to retain span/run context.
- Fan-out payloads from primary to workers and handle worker-side delivery.
- Ensure graceful shutdown (signal forwarding, acknowledgements).
- Document clustering usage and update example server to showcase it.