mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2025-12-14 01:20:36 +00:00
332 lines
8.5 KiB
Go
332 lines
8.5 KiB
Go
// Package websocketspec provides a WebSocket-based API specification for real-time
|
|
// CRUD operations with bidirectional communication and subscription support.
|
|
//
|
|
// # Key Features
|
|
//
|
|
// - Real-time bidirectional communication over WebSocket
|
|
// - CRUD operations (Create, Read, Update, Delete)
|
|
// - Real-time subscriptions with filtering
|
|
// - Lifecycle hooks for all operations
|
|
// - Database-agnostic: Works with GORM and Bun ORM through adapters
|
|
// - Automatic change notifications to subscribers
|
|
// - Connection and subscription management
|
|
//
|
|
// # Message Protocol
|
|
//
|
|
// WebSocketSpec uses JSON messages for communication:
|
|
//
|
|
// {
|
|
// "id": "unique-message-id",
|
|
// "type": "request|response|notification|subscription",
|
|
// "operation": "read|create|update|delete|subscribe|unsubscribe",
|
|
// "schema": "public",
|
|
// "entity": "users",
|
|
// "data": {...},
|
|
// "options": {
|
|
// "filters": [...],
|
|
// "columns": [...],
|
|
// "preload": [...],
|
|
// "sort": [...],
|
|
// "limit": 10
|
|
// }
|
|
// }
|
|
//
|
|
// # Usage Example
|
|
//
|
|
// // Create handler with GORM
|
|
// handler := websocketspec.NewHandlerWithGORM(db)
|
|
//
|
|
// // Register models
|
|
// handler.Registry.RegisterModel("public.users", &User{})
|
|
//
|
|
// // Setup WebSocket endpoint
|
|
// http.HandleFunc("/ws", handler.HandleWebSocket)
|
|
//
|
|
// // Start server
|
|
// http.ListenAndServe(":8080", nil)
|
|
//
|
|
// # Client Example
|
|
//
|
|
// // Connect to WebSocket
|
|
// ws := new WebSocket("ws://localhost:8080/ws")
|
|
//
|
|
// // Send read request
|
|
// ws.send(JSON.stringify({
|
|
// id: "msg-1",
|
|
// type: "request",
|
|
// operation: "read",
|
|
// entity: "users",
|
|
// options: {
|
|
// filters: [{column: "status", operator: "eq", value: "active"}],
|
|
// limit: 10
|
|
// }
|
|
// }))
|
|
//
|
|
// // Subscribe to changes
|
|
// ws.send(JSON.stringify({
|
|
// id: "msg-2",
|
|
// type: "subscription",
|
|
// operation: "subscribe",
|
|
// entity: "users",
|
|
// options: {
|
|
// filters: [{column: "status", operator: "eq", value: "active"}]
|
|
// }
|
|
// }))
|
|
package websocketspec
|
|
|
|
import (
|
|
"github.com/bitechdev/ResolveSpec/pkg/common"
|
|
"github.com/bitechdev/ResolveSpec/pkg/common/adapters/database"
|
|
"github.com/bitechdev/ResolveSpec/pkg/modelregistry"
|
|
"github.com/uptrace/bun"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// NewHandlerWithGORM creates a new Handler with GORM adapter
|
|
func NewHandlerWithGORM(db *gorm.DB) *Handler {
|
|
gormAdapter := database.NewGormAdapter(db)
|
|
registry := modelregistry.NewModelRegistry()
|
|
return NewHandler(gormAdapter, registry)
|
|
}
|
|
|
|
// NewHandlerWithBun creates a new Handler with Bun adapter
|
|
func NewHandlerWithBun(db *bun.DB) *Handler {
|
|
bunAdapter := database.NewBunAdapter(db)
|
|
registry := modelregistry.NewModelRegistry()
|
|
return NewHandler(bunAdapter, registry)
|
|
}
|
|
|
|
// NewHandlerWithDatabase creates a new Handler with a custom database adapter
|
|
func NewHandlerWithDatabase(db common.Database, registry common.ModelRegistry) *Handler {
|
|
return NewHandler(db, registry)
|
|
}
|
|
|
|
// Example usage functions for documentation:
|
|
|
|
// ExampleWithGORM shows how to use WebSocketSpec with GORM
|
|
func ExampleWithGORM(db *gorm.DB) {
|
|
// Create handler using GORM
|
|
handler := NewHandlerWithGORM(db)
|
|
|
|
// Register models
|
|
handler.Registry().RegisterModel("public.users", &struct{}{})
|
|
|
|
// Register hooks (optional)
|
|
handler.Hooks().RegisterBefore(OperationRead, func(ctx *HookContext) error {
|
|
// Add custom logic before read operations
|
|
return nil
|
|
})
|
|
|
|
// Setup WebSocket endpoint
|
|
// http.HandleFunc("/ws", handler.HandleWebSocket)
|
|
|
|
// Start server
|
|
// http.ListenAndServe(":8080", nil)
|
|
}
|
|
|
|
// ExampleWithBun shows how to use WebSocketSpec with Bun ORM
|
|
func ExampleWithBun(bunDB *bun.DB) {
|
|
// Create handler using Bun
|
|
handler := NewHandlerWithBun(bunDB)
|
|
|
|
// Register models
|
|
handler.Registry().RegisterModel("public.users", &struct{}{})
|
|
|
|
// Setup WebSocket endpoint
|
|
// http.HandleFunc("/ws", handler.HandleWebSocket)
|
|
}
|
|
|
|
// ExampleWithHooks shows how to use lifecycle hooks
|
|
func ExampleWithHooks(db *gorm.DB) {
|
|
handler := NewHandlerWithGORM(db)
|
|
|
|
// Register a before-read hook for authorization
|
|
handler.Hooks().RegisterBefore(OperationRead, func(ctx *HookContext) error {
|
|
// Check if user has permission to read this entity
|
|
// return fmt.Errorf("unauthorized") if not allowed
|
|
return nil
|
|
})
|
|
|
|
// Register an after-create hook for logging
|
|
handler.Hooks().RegisterAfter(OperationCreate, func(ctx *HookContext) error {
|
|
// Log the created record
|
|
// logger.Info("Created record: %v", ctx.Result)
|
|
return nil
|
|
})
|
|
|
|
// Register a before-subscribe hook to limit subscriptions
|
|
handler.Hooks().Register(BeforeSubscribe, func(ctx *HookContext) error {
|
|
// Limit number of subscriptions per connection
|
|
// if len(ctx.Connection.subscriptions) >= 10 {
|
|
// return fmt.Errorf("maximum subscriptions reached")
|
|
// }
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// ExampleWithSubscriptions shows subscription usage
|
|
func ExampleWithSubscriptions() {
|
|
// Client-side JavaScript example:
|
|
/*
|
|
const ws = new WebSocket("ws://localhost:8080/ws");
|
|
|
|
// Subscribe to user changes
|
|
ws.send(JSON.stringify({
|
|
id: "sub-1",
|
|
type: "subscription",
|
|
operation: "subscribe",
|
|
schema: "public",
|
|
entity: "users",
|
|
options: {
|
|
filters: [
|
|
{column: "status", operator: "eq", value: "active"}
|
|
]
|
|
}
|
|
}));
|
|
|
|
// Handle notifications
|
|
ws.onmessage = (event) => {
|
|
const msg = JSON.parse(event.data);
|
|
if (msg.type === "notification") {
|
|
console.log("User changed:", msg.data);
|
|
console.log("Operation:", msg.operation); // create, update, or delete
|
|
}
|
|
};
|
|
|
|
// Unsubscribe
|
|
ws.send(JSON.stringify({
|
|
id: "unsub-1",
|
|
type: "subscription",
|
|
operation: "unsubscribe",
|
|
subscription_id: "sub-abc123"
|
|
}));
|
|
*/
|
|
}
|
|
|
|
// ExampleCRUDOperations shows basic CRUD operations
|
|
func ExampleCRUDOperations() {
|
|
// Client-side JavaScript example:
|
|
/*
|
|
const ws = new WebSocket("ws://localhost:8080/ws");
|
|
|
|
// CREATE - Create a new user
|
|
ws.send(JSON.stringify({
|
|
id: "create-1",
|
|
type: "request",
|
|
operation: "create",
|
|
schema: "public",
|
|
entity: "users",
|
|
data: {
|
|
name: "John Doe",
|
|
email: "john@example.com",
|
|
status: "active"
|
|
}
|
|
}));
|
|
|
|
// READ - Get all active users
|
|
ws.send(JSON.stringify({
|
|
id: "read-1",
|
|
type: "request",
|
|
operation: "read",
|
|
schema: "public",
|
|
entity: "users",
|
|
options: {
|
|
filters: [{column: "status", operator: "eq", value: "active"}],
|
|
columns: ["id", "name", "email"],
|
|
sort: [{column: "name", direction: "asc"}],
|
|
limit: 10
|
|
}
|
|
}));
|
|
|
|
// READ BY ID - Get a specific user
|
|
ws.send(JSON.stringify({
|
|
id: "read-2",
|
|
type: "request",
|
|
operation: "read",
|
|
schema: "public",
|
|
entity: "users",
|
|
record_id: "123"
|
|
}));
|
|
|
|
// UPDATE - Update a user
|
|
ws.send(JSON.stringify({
|
|
id: "update-1",
|
|
type: "request",
|
|
operation: "update",
|
|
schema: "public",
|
|
entity: "users",
|
|
record_id: "123",
|
|
data: {
|
|
name: "John Updated",
|
|
email: "john.updated@example.com"
|
|
}
|
|
}));
|
|
|
|
// DELETE - Delete a user
|
|
ws.send(JSON.stringify({
|
|
id: "delete-1",
|
|
type: "request",
|
|
operation: "delete",
|
|
schema: "public",
|
|
entity: "users",
|
|
record_id: "123"
|
|
}));
|
|
|
|
// Handle responses
|
|
ws.onmessage = (event) => {
|
|
const response = JSON.parse(event.data);
|
|
if (response.type === "response") {
|
|
if (response.success) {
|
|
console.log("Operation successful:", response.data);
|
|
} else {
|
|
console.error("Operation failed:", response.error);
|
|
}
|
|
}
|
|
};
|
|
*/
|
|
}
|
|
|
|
// ExampleAuthentication shows how to implement authentication
|
|
func ExampleAuthentication() {
|
|
// Server-side example with authentication hook:
|
|
/*
|
|
handler := NewHandlerWithGORM(db)
|
|
|
|
// Register before-connect hook for authentication
|
|
handler.Hooks().Register(BeforeConnect, func(ctx *HookContext) error {
|
|
// Extract token from query params or headers
|
|
r := ctx.Connection.ws.UnderlyingConn().RemoteAddr()
|
|
|
|
// Validate token
|
|
// token := extractToken(r)
|
|
// user, err := validateToken(token)
|
|
// if err != nil {
|
|
// return fmt.Errorf("authentication failed: %w", err)
|
|
// }
|
|
|
|
// Store user info in connection metadata
|
|
// ctx.Connection.SetMetadata("user", user)
|
|
// ctx.Connection.SetMetadata("user_id", user.ID)
|
|
|
|
return nil
|
|
})
|
|
|
|
// Use connection metadata in other hooks
|
|
handler.Hooks().RegisterBefore(OperationRead, func(ctx *HookContext) error {
|
|
// Get user from connection metadata
|
|
// userID, _ := ctx.Connection.GetMetadata("user_id")
|
|
|
|
// Add filter to only show user's own records
|
|
// if ctx.Entity == "orders" {
|
|
// ctx.Options.Filters = append(ctx.Options.Filters, common.FilterOption{
|
|
// Column: "user_id",
|
|
// Operator: "eq",
|
|
// Value: userID,
|
|
// })
|
|
// }
|
|
|
|
return nil
|
|
})
|
|
*/
|
|
}
|