feat(11-05): add the beachcomber search sync package and its Typesense engine
- Searchable, Engine, Gate and an init-time engine registry with the null engine - GORM callbacks installed through lagoon.OnDatabase register an after-commit sync that reloads the row and upserts or deletes its document - Gates run before any request: engine configured, database published, app Gate - hand-rolled net/http Typesense engine following the Scout wire contract - module README and root modules row
This commit is contained in:
77
modules/beachcomber/engines.go
Normal file
77
modules/beachcomber/engines.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package beachcomber
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"git.golem15.com/golem15/summercms/modules/backpack"
|
||||
)
|
||||
|
||||
// NullEngine is the name of the built-in engine that indexes nothing.
|
||||
const NullEngine = "null"
|
||||
|
||||
// EngineFactory builds an engine for an app.
|
||||
type EngineFactory func(app *backpack.App) (Engine, error)
|
||||
|
||||
// engineTable is the init-time engine registry, like database/sql's: it is
|
||||
// written only from package init functions and read when a Service is built.
|
||||
var engineTable = struct {
|
||||
mu sync.RWMutex
|
||||
factories map[string]EngineFactory
|
||||
}{factories: map[string]EngineFactory{}}
|
||||
|
||||
// RegisterEngine makes an engine available under name. Call it from the
|
||||
// engine package's init function. A duplicate name, an empty name or a nil
|
||||
// factory panics.
|
||||
func RegisterEngine(name string, f EngineFactory) {
|
||||
if name == "" {
|
||||
panic("beachcomber: RegisterEngine with an empty name")
|
||||
}
|
||||
if f == nil {
|
||||
panic("beachcomber: RegisterEngine " + name + " with a nil factory")
|
||||
}
|
||||
engineTable.mu.Lock()
|
||||
defer engineTable.mu.Unlock()
|
||||
if _, dup := engineTable.factories[name]; dup {
|
||||
panic("beachcomber: RegisterEngine called twice for engine " + name)
|
||||
}
|
||||
engineTable.factories[name] = f
|
||||
}
|
||||
|
||||
func engineFactory(name string) (EngineFactory, bool) {
|
||||
engineTable.mu.RLock()
|
||||
defer engineTable.mu.RUnlock()
|
||||
f, ok := engineTable.factories[name]
|
||||
return f, ok
|
||||
}
|
||||
|
||||
func engineNames() []string {
|
||||
engineTable.mu.RLock()
|
||||
defer engineTable.mu.RUnlock()
|
||||
names := make([]string, 0, len(engineTable.factories))
|
||||
for n := range engineTable.factories {
|
||||
names = append(names, n)
|
||||
}
|
||||
sort.Strings(names)
|
||||
return names
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterEngine(NullEngine, func(*backpack.App) (Engine, error) { return nullEngine{}, nil })
|
||||
}
|
||||
|
||||
// nullEngine is never configured, so sync is skipped before it is called.
|
||||
// Every method is a no-op; SearchIDs returns an empty list.
|
||||
type nullEngine struct{}
|
||||
|
||||
func (nullEngine) Name() string { return NullEngine }
|
||||
func (nullEngine) Configured() bool { return false }
|
||||
func (nullEngine) Upsert(context.Context, string, map[string]any, []map[string]any) error {
|
||||
return nil
|
||||
}
|
||||
func (nullEngine) Delete(context.Context, string, []string) error { return nil }
|
||||
func (nullEngine) Flush(context.Context, string) error { return nil }
|
||||
func (nullEngine) SearchIDs(context.Context, string, Query) ([]string, error) {
|
||||
return []string{}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user