mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-01-10 05:04:25 +00:00
Prototype for websockspec
This commit is contained in:
331
pkg/websocketspec/websocketspec.go
Normal file
331
pkg/websocketspec/websocketspec.go
Normal file
@@ -0,0 +1,331 @@
|
||||
// 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
|
||||
})
|
||||
*/
|
||||
}
|
||||
Reference in New Issue
Block a user