feat(11-03): broadcast model writes through River jobs enqueued in the write transaction
- Broadcastable contract and Bind[T] bindings; event {action}.{alias},
default {model, actor, timestamp, ttl} payload, delete snapshot taken
before the row goes
- GORM callbacks installed via lagoon.OnDatabase enqueue a summer.broadcast
job on the write's *sql.Tx inside a savepoint; failures are logged and
never abort the write; zero-key batch writes are skipped
- WithoutBroadcasting[T] (ctx-scoped, per type) and Service.Emit for one
summary event; the one-attempt job namespaces channels and publishes or
broadcasts; the payload travels as a JSON string so JSONB keeps its order
- no jobs for the null driver or Centrifugo without an API key
This commit is contained in:
@@ -8,14 +8,18 @@
|
||||
package lighthouse
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.golem15.com/golem15/summercms/modules/backpack"
|
||||
"git.golem15.com/golem15/summercms/modules/compass"
|
||||
"git.golem15.com/golem15/summercms/modules/lagoon"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -39,8 +43,9 @@ type Service struct {
|
||||
queue string
|
||||
timeout time.Duration
|
||||
|
||||
mu sync.RWMutex
|
||||
lookup UserLookup
|
||||
mu sync.RWMutex
|
||||
lookup UserLookup
|
||||
bindings map[reflect.Type]*handler
|
||||
}
|
||||
|
||||
// From returns the app's Service, building and publishing it on first use.
|
||||
@@ -48,7 +53,10 @@ type Service struct {
|
||||
// realtime.broadcast_namespace (default ""), realtime.broadcast_queue
|
||||
// (default "broadcasts") and realtime.broadcast_timeout (default 5s; an
|
||||
// integer number of seconds or a duration string) and builds the driver
|
||||
// through its registered factory. An unknown driver name is an error.
|
||||
// through its registered factory. An unknown driver name is an error. It
|
||||
// also registers the broadcast job with the app's conga manager (so call it
|
||||
// from Boot, before a worker starts) and installs the broadcast GORM
|
||||
// callbacks through lagoon.OnDatabase.
|
||||
func From(app *backpack.App) (*Service, error) {
|
||||
if app == nil {
|
||||
return nil, fmt.Errorf("lighthouse: app is nil")
|
||||
@@ -75,6 +83,16 @@ func From(app *backpack.App) (*Service, error) {
|
||||
return nil, fmt.Errorf("lighthouse: driver %s returned nil", name)
|
||||
}
|
||||
svc.driver = d
|
||||
if err := svc.registerJob(); err != nil {
|
||||
return nil, fmt.Errorf("lighthouse: register broadcast job: %w", err)
|
||||
}
|
||||
// The broadcast callbacks need the GORM handle, which serve publishes
|
||||
// after plugins boot.
|
||||
if err := lagoon.OnDatabase(app, func(_ *sql.DB, gdb *gorm.DB) error {
|
||||
return svc.installCallbacks(gdb)
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("lighthouse: install broadcast callbacks: %w", err)
|
||||
}
|
||||
if err := app.Publish(svc); err != nil {
|
||||
if existing, ok := app.Lookup[*Service](); ok && existing != nil {
|
||||
return existing, nil
|
||||
|
||||
Reference in New Issue
Block a user