alt/services/api/internal/socket/handlers.go
toki 57000c8cb6 feat(operator-runtime-refactor): complete subtasks 02+01 and 03, refactor client bootstrap and API socket layer
- Archive completed subtask documents (02+01_api_lifecycle_capabilities, 03_client_bootstrap)
- Update roadmap and PHASE.md for operator-surface progress
- Refactor apps/client/lib/src/app/bootstrap.dart for client initialization
- Update API socket layer: backtest, handlers, market, server, workerclient
- Add bootstrap_test.dart for client app
2026-05-31 09:43:42 +09:00

87 lines
3.2 KiB
Go

package socket
import (
protoSocket "git.toki-labs.com/toki/proto-socket/go"
altv1 "git.toki-labs.com/toki/alt/packages/contracts/gen/go/alt/v1"
"git.toki-labs.com/toki/alt/services/api/internal/workerclient"
)
// sessionHandler is one request-response unit attached to a freshly connected
// client communicator. Splitting registration into independent units keeps the
// api hub control-plane surface thin: later command/query handlers attach
// through the same registry instead of growing a single registration function.
type sessionHandler struct {
// requestType is the ALT protobuf request type name the handler answers.
// It lets the registry detect duplicate or missing handler coverage without
// a live socket connection.
requestType string
// register attaches the handler onto the client communicator.
register func(*protoSocket.WsClient)
}
// sessionHandlers returns the ordered set of handlers registered on every API
// client session. Hello is registered through this registry so its behaviour
// stays identical while gaining the shared registration path.
func sessionHandlers(worker workerclient.WorkerClient) []sessionHandler {
handlers := []sessionHandler{
helloHandler(worker),
}
handlers = append(handlers, marketHandlers(worker)...)
handlers = append(handlers, backtestHandlers(worker)...)
return handlers
}
// registerSessionHandlers wires every session handler onto a newly connected
// client. It is the OnClientConnected entrypoint for the API socket server.
func registerSessionHandlers(worker workerclient.WorkerClient) func(*protoSocket.WsClient) {
return func(client *protoSocket.WsClient) {
registerHandlers(client, sessionHandlers(worker))
}
}
// registerHandlers attaches the given handlers onto a client. nil registrars
// are skipped so a malformed registry entry cannot panic the connection setup
// path.
func registerHandlers(client *protoSocket.WsClient, handlers []sessionHandler) {
for _, handler := range handlers {
if handler.register == nil {
continue
}
handler.register(client)
}
}
func helloHandler(worker workerclient.WorkerClient) sessionHandler {
return sessionHandler{
requestType: protoSocket.TypeNameOf(&altv1.HelloRequest{}),
register: func(client *protoSocket.WsClient) {
protoSocket.AddRequestListenerTyped[*altv1.HelloRequest, *altv1.HelloResponse](&client.Communicator, func(req *altv1.HelloRequest) (*altv1.HelloResponse, error) {
return handleHello(worker, req)
})
},
}
}
func handleHello(worker workerclient.WorkerClient, req *altv1.HelloRequest) (*altv1.HelloResponse, error) {
protocolVersion := req.GetAltProtocolVersion()
if protocolVersion == "" {
protocolVersion = defaultAltProtocolVersion
}
return &altv1.HelloResponse{
ServerName: serverName,
ServerVersion: serverVersion,
AltProtocolVersion: protocolVersion,
Capabilities: capabilitiesForSession(worker),
}, nil
}
func capabilitiesForSession(worker workerclient.WorkerClient) []string {
caps := []string{"hello", "request-response", "market-read", "backtest-read", "backtest-start", "worker-execution"}
if worker != nil && worker.IsConnected() {
caps = append(caps, "worker-available")
} else {
caps = append(caps, "worker-unavailable")
}
return caps
}