Signal Service
@nx/signalis the platform's single real-time edge: an end-to-end-encrypted WebSocket server (/stream) that other services push to via Redis pub/sub, plus a Kafka-fed activity-notification pipeline that resolves recipients, persistsActivityNotificationrows (in@nx/core), and pushes them to per-recipient rooms. It owns no schema of its own — only seed migrations for its permissions.
1. Quick Reference
| Property | Value |
|---|---|
| Package | @nx/signal |
| Code | SVC-00090-SIGNAL |
| Type | Microservice |
| Runtime | Bun |
| Base Class | VerifierApplication |
| Location | packages/signal |
| Base Path | /v1/api/signal |
| WebSocket Path | /stream (requireEncryption: true) |
| Dev Port | 31090 |
| Container Port | 3000 (external 31090) |
| Snowflake ID | 9 |
| DB Schema | none of its own — reuses @nx/core tables (ActivityNotification) |
| Binding Namespace | @nx/signal |
2. Purpose & Scope
| Included | Excluded |
|---|---|
Encrypted WebSocket server (/stream, ECDH P-256 + AES-256-GCM) | Persisting WebSocket connections (in-memory per instance, lost on restart) |
| Cross-instance / cross-service fan-out via Redis pub/sub | Producing domain events (signal only consumes + pushes) |
| REST client management (list, get, broadcast, room/client send, disconnect) | Authoring notification content for arbitrary event types (only PAYMENT_SUCCESS today) |
| Kafka activity-notification pipeline (resolve → persist → push) | Owning the ActivityNotification schema (centralized in @nx/core) |
| Recipient-scoped notification REST API (list, count, mark-read) | Email / SMS / push-notification channels |
| JWT verification via remote JWKS (Identity) | Issuing tokens (delegated to @nx/identity) |
3. Tech Stack
External:
| Library | Purpose |
|---|---|
@venizia/ignis | IoC container, DI, BaseComponent, WebSocketComponent, JWKSVerifierTokenService |
@venizia/ignis-helpers | ECDH, WebSocketServerHelper, WebSocketEmitter, KafkaConsumerHelper |
@platformatic/kafka | Kafka (de)serializers + SASL types for the notification consumer |
hono | HTTP server framework (via IGNIS) |
@hono/zod-openapi | OpenAPI generator from Zod schemas |
zod | Request/response schema validation |
drizzle-orm / pg | DB access via PostgresCoreDataSource (notification persistence + authz) |
Internal:
| Package | Purpose |
|---|---|
@nx/core | VerifierApplication, createAppConfig, bootstrapApplication, centralized repositories/models (ActivityNotification), KafkaTopics, WebSocketTopics/WebSocketRooms, RedisConnectionFactory, BaseSocketEventService |
4. Project Structure
packages/signal/
├── src/
│ ├── application.ts # VerifierApplication subclass
│ ├── index.ts # bootstrapApplication()
│ ├── migrate.ts # bootstrapMigration()
│ ├── common/ # BindingKeys, RestPaths, WebSocketClientInfoSchema
│ ├── components/
│ │ ├── signal.component.ts # Top-level orchestrator component
│ │ ├── websocket.component.ts # NxWebSocketComponent (ECDH + Redis + handlers)
│ │ └── notification/ # NotificationKafkaComponent + socket-event service
│ ├── controllers/
│ │ ├── websocket-clients/ # WebSocketClientController (+ permissions)
│ │ └── activity-notifications/ # ActivityNotificationController
│ ├── datasources/ # PostgresCoreDataSource binding
│ ├── errors/ # WorkerErrors
│ ├── migrations/processes/ # 2 seed migrations (permissions, role-permissions)
│ ├── models/requests/ # zod query schemas
│ ├── services/ # SignalEventService, ActivityNotification(Worker)Service
│ └── utilities/ # content-decorator (markdown → content/html)
├── package.json
└── tsconfig.json5. Architecture
Detail: see Architecture.
6. Domain Snapshot
Signal owns no tables. It reads/writes the centralized ActivityNotification table from @nx/core.
Full ERD + entity tables: see Domain Model.
7. Surface Summary
REST controllers — full reference rendered live from /v1/api/signal/doc/openapi.json (Scalar viewer at /doc, gateway portal):
| Controller | Base path | Notes |
|---|---|---|
WebSocketClientController | /socket/websocket/clients | status (public) + list / get / broadcast / room-send / client-send / disconnect (auth + WebSocketClient.* permission) |
ActivityNotificationController | /notifications | JWT, recipient-scoped: list / count / mark-read / mark-all-read |
Async surface — full reference in API Events:
| Direction | Channel | Count |
|---|---|---|
| Inbound | Kafka | 1 (SIGNAL_ACTIVITY_NOTIFICATION) |
| Outbound | Kafka | 0 |
| Outbound | WebSocket | 1 topic (observation/signal/notification/created) + ad-hoc broadcast/room/client sends |
8. Components
| Component | File | Purpose |
|---|---|---|
SignalComponent | src/components/signal.component.ts | Orchestrator — registers SignalEventService + ActivityNotificationService, WebSocketClientController + ActivityNotificationController, and the two sub-components below |
NxWebSocketComponent | src/components/websocket.component.ts | Redis, ECDH encryption, /stream options, 6 handler bindings, IGNIS WebSocketComponent, WebSocketEmitter |
NotificationKafkaComponent | src/components/notification/component.ts | Binds notification services, starts the SIGNAL_ACTIVITY_NOTIFICATION consumer (autocommit off, fallbackMode: latest, graceful shutdown) |
9. Services
| Service | File | One-liner |
|---|---|---|
SignalEventService | services/signal-event.service.ts | Façade over WS server/emitter (broadcast, room/client send, introspection, disconnect) |
ActivityNotificationService | services/activity-notification.service.ts | Recipient-scoped read/update over ActivityNotificationRepository (list, count, mark-read, mark-all) |
ActivityNotificationWorkerService | services/activity-notification-worker.service.ts | Kafka handler — resolve recipients, build content, persist rows, push WS |
NotificationSocketEventService | components/notification/socket-event.service.ts | BaseSocketEventService subclass; emits to per-recipient rooms |
10. Repositories
All re-exported from
@nx/core; signal owns no schema. Registered inpreConfigure()(+ rebound byNotificationKafkaComponent).
| Repository | Table | Source | Custom Methods |
|---|---|---|---|
ActivityNotificationRepository | ActivityNotification | @nx/core | — (plain SoftDeletableRepository) |
PolicyDefinitionRepository | PolicyDefinition | @nx/core | findUserIdsInOrganizer, findUserIdsInMerchant (recipient resolution) |
PermissionRepository | Permission | @nx/core | — (seed migration) |
RoleRepository | Role | @nx/core | — (seed migration) |
11. Entry Points
| File | Purpose |
|---|---|
src/index.ts | Service entry → bootstrapApplication() |
src/migrate.ts | Migration entry → bootstrapMigration() (seeds permissions + role-permissions) |
src/application.ts | Application extends VerifierApplication |
12. Configuration
Env vars + seeded data: see Configuration.
13. Operations
Deployment + observability + security + runbook: see Operations.
14. Related Pages
Concepts — why/how:
- Architecture
- Domain Model
- Integration — Redis pub/sub from sister services + identity JWKS
Reference — lookup:
- API Events
- Configuration
- Operations
- REST endpoints — live OpenAPI at
/v1/api/signal/doc/openapi.json
Features — deep dives:
- Encryption — ECDH P-256, HKDF, AES-256-GCM, handshake
- Components & Services — source-level component reference
- REST API — WebSocket client management endpoints
- Client Integration Guide — connect from web / Tauri / cross-service
Decisions: