Install
openclaw skills install @psyb0t/gen-asyncapiGenerates Go or TypeScript code from an AsyncAPI 3 spec, providing typed models, strict decoding, validation, message dispatch, and a WebSocket client.
openclaw skills install @psyb0t/gen-asyncapiGenerates Go or TypeScript from an AsyncAPI 3 document: types, strict decoding with validation, message dispatch and a WebSocket client. The generated code is regular source that gets committed next to the spec and regenerated when the spec changes.
Install paths, every config key and the generated API are in references/setup.md.
*.gen.go or *.gen.ts file. Change the spec or the config and regenerate; the next run overwrites manual edits.gorillaws.WithBearerToken or the transport you build, never in the spec or config.gen.go with go tool gen-asyncapi and the spec or config changed.go get -tool github.com/psyb0t/gen-asyncapi/cmd/gen-asyncapi@latest
# config.yaml, next to gen.go
package: chat
output: chat.gen.go
// gen.go
package chat
//go:generate go tool gen-asyncapi --config=config.yaml ../../api/asyncapi.yaml
Run go generate ./.... Paths in the config and on the command line are relative to the directory holding gen.go.
conn, err := gorillaws.Dial(ctx, chat.ServerProductionURL, gorillaws.WithBearerToken(token))
if err != nil {
return err
}
client := chat.NewClient(conn)
defer client.Close()
if err := client.JoinRoom(ctx, chat.JoinRequest{ID: id, Room: "lobby"}); err != nil {
return err // the payload failed Validate, or the write failed
}
type handler struct {
chat.UnimplementedHandler
}
func (handler) HandleAck(ctx context.Context, ack chat.Ack) error {
return nil
}
err = client.Run(ctx, handler{}) // reads until ctx ends or the connection drops
Send method names come from the operation names in the spec, handler methods are Handle<Message>, and const properties fill themselves in on marshal. Decoding errors wrap ctxerrors/commerr sentinels such as commerr.ErrValidationFailed and commerr.ErrRequiredFieldNotSet.
go run github.com/psyb0t/gen-asyncapi/cmd/gen-asyncapi@latest -language=typescript -o=src/chat.gen.ts asyncapi.yaml
const socket = new WebSocket(ServerProductionURL);
const client = new Client(socket);
await client.joinRoom({ type: "join", id: crypto.randomUUID(), room: "lobby" });
socket.addEventListener("message", (message) => {
client
.receive(message.data, {
handleAck: (ack) => render(ack.ok),
})
.catch(report);
});
In TypeScript, const properties are literal types the caller writes out. Validation failures throw ValidationError with a path; unmatched frames go to handleUnknownMessage or reject with UnknownMessageError.
When the client cannot tell incoming messages apart by const properties or strict decoding, pin each one with a JSON pointer in the config:
output-options:
message-matchers:
Notice:
/messages/0/type: notice
Alert:
/messages/0/type: alert
A list value such as [notice, info] matches any of its entries. An empty list fails generation.