chore: move apps to go.sour.is/tools
This commit is contained in:
153
driver/projecter/projecter.go
Normal file
153
driver/projecter/projecter.go
Normal file
@@ -0,0 +1,153 @@
|
||||
// package projecter provides a driver middleware to derive new events from other events.
|
||||
package projecter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"go.sour.is/pkg/lg"
|
||||
|
||||
"go.sour.is/ev"
|
||||
"go.sour.is/ev/driver"
|
||||
"go.sour.is/ev/event"
|
||||
)
|
||||
|
||||
type projector struct {
|
||||
up driver.Driver
|
||||
fns []func(event.Event) []event.Event
|
||||
}
|
||||
|
||||
func New(_ context.Context, fns ...func(event.Event) []event.Event) *projector {
|
||||
return &projector{fns: fns}
|
||||
}
|
||||
func (p *projector) Apply(e *ev.EventStore) {
|
||||
up := e.Driver
|
||||
for up != nil {
|
||||
if op, ok := up.(*projector); ok {
|
||||
op.AddProjections(p.fns...)
|
||||
p.up = op.up
|
||||
return
|
||||
}
|
||||
|
||||
up = ev.Unwrap(up)
|
||||
}
|
||||
|
||||
p.up = e.Driver
|
||||
e.Driver = p
|
||||
}
|
||||
func (s *projector) Unwrap() driver.Driver {
|
||||
return s.up
|
||||
}
|
||||
func (s *projector) Open(ctx context.Context, dsn string) (driver.Driver, error) {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
return s.up.Open(ctx, dsn)
|
||||
}
|
||||
func (s *projector) EventLog(ctx context.Context, streamID string) (driver.EventLog, error) {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
l, err := s.up.EventLog(ctx, streamID)
|
||||
return &wrapper{l, s}, err
|
||||
}
|
||||
func (s *projector) AddProjections(fns ...func(event.Event) []event.Event) {
|
||||
s.fns = append(s.fns, fns...)
|
||||
}
|
||||
|
||||
type wrapper struct {
|
||||
up driver.EventLog
|
||||
projector *projector
|
||||
}
|
||||
|
||||
var _ driver.EventLog = (*wrapper)(nil)
|
||||
|
||||
func (r *wrapper) Unwrap() driver.EventLog {
|
||||
return r.up
|
||||
}
|
||||
func (w *wrapper) Read(ctx context.Context, after int64, count int64) (event.Events, error) {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
return w.up.Read(ctx, after, count)
|
||||
}
|
||||
func (w *wrapper) ReadN(ctx context.Context, index ...uint64) (event.Events, error) {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
return w.up.ReadN(ctx, index...)
|
||||
}
|
||||
func (w *wrapper) Append(ctx context.Context, events event.Events, version uint64) (uint64, error) {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
i, err := w.up.Append(ctx, events, version)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
|
||||
{
|
||||
ctx, span := lg.Fork(ctx)
|
||||
|
||||
go func() {
|
||||
defer span.End()
|
||||
|
||||
var pevents []event.Event
|
||||
|
||||
for i := range events {
|
||||
e := events[i]
|
||||
|
||||
for _, fn := range w.projector.fns {
|
||||
pevents = append(
|
||||
pevents,
|
||||
fn(e)...,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
for i := range pevents {
|
||||
e := pevents[i]
|
||||
l, err := w.projector.up.EventLog(ctx, event.StreamID(e).StreamID())
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
continue
|
||||
}
|
||||
_, err = l.Append(ctx, event.NewEvents(e), ev.AppendOnly)
|
||||
span.RecordError(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return i, err
|
||||
}
|
||||
func (w *wrapper) FirstIndex(ctx context.Context) (uint64, error) {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
return w.up.FirstIndex(ctx)
|
||||
}
|
||||
func (w *wrapper) LastIndex(ctx context.Context) (uint64, error) {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
return w.up.LastIndex(ctx)
|
||||
}
|
||||
|
||||
func DefaultProjection(e event.Event) []event.Event {
|
||||
m := e.EventMeta()
|
||||
streamID := m.StreamID
|
||||
streamPos := m.Position
|
||||
eventType := event.TypeOf(e)
|
||||
pkg, _, _ := strings.Cut(eventType, ".")
|
||||
|
||||
e1 := event.NewPtr(streamID, streamPos)
|
||||
event.SetStreamID("$all", e1)
|
||||
|
||||
e2 := event.NewPtr(streamID, streamPos)
|
||||
event.SetStreamID("$type-"+eventType, e2)
|
||||
|
||||
e3 := event.NewPtr(streamID, streamPos)
|
||||
event.SetStreamID("$pkg-"+pkg, e3)
|
||||
|
||||
return []event.Event{e1, e2, e3}
|
||||
}
|
||||
137
driver/projecter/projector_test.go
Normal file
137
driver/projecter/projector_test.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package projecter_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/matryer/is"
|
||||
|
||||
"go.sour.is/ev"
|
||||
"go.sour.is/ev/driver"
|
||||
"go.sour.is/ev/driver/projecter"
|
||||
"go.sour.is/ev/event"
|
||||
)
|
||||
|
||||
type mockDriver struct {
|
||||
onOpen func(context.Context, string) (driver.Driver, error)
|
||||
onEventLog func(context.Context, string) (driver.EventLog, error)
|
||||
}
|
||||
|
||||
// Open implements driver.Driver
|
||||
func (m *mockDriver) Open(ctx context.Context, dsn string) (driver.Driver, error) {
|
||||
if m.onOpen != nil {
|
||||
return m.onOpen(ctx, dsn)
|
||||
}
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
// EventLog implements driver.Driver
|
||||
func (m *mockDriver) EventLog(ctx context.Context, streamID string) (driver.EventLog, error) {
|
||||
if m.onEventLog != nil {
|
||||
return m.onEventLog(ctx, streamID)
|
||||
}
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
var _ driver.Driver = (*mockDriver)(nil)
|
||||
|
||||
type mockEventLog struct {
|
||||
onAppend func(context.Context, event.Events, uint64) (uint64, error)
|
||||
onFirstIndex func(context.Context) (uint64, error)
|
||||
onLastIndex func(context.Context) (uint64, error)
|
||||
onRead func(context.Context, int64, int64) (event.Events, error)
|
||||
onReadN func(context.Context, ...uint64) (event.Events, error)
|
||||
}
|
||||
|
||||
// Append implements driver.EventLog
|
||||
func (m *mockEventLog) Append(ctx context.Context, events event.Events, version uint64) (uint64, error) {
|
||||
if m.onAppend != nil {
|
||||
return m.onAppend(ctx, events, version)
|
||||
}
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
// FirstIndex implements driver.EventLog
|
||||
func (m *mockEventLog) FirstIndex(ctx context.Context) (uint64, error) {
|
||||
if m.onFirstIndex != nil {
|
||||
return m.onFirstIndex(ctx)
|
||||
}
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
// LastIndex implements driver.EventLog
|
||||
func (m *mockEventLog) LastIndex(ctx context.Context) (uint64, error) {
|
||||
if m.onLastIndex != nil {
|
||||
return m.onLastIndex(ctx)
|
||||
}
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
// Read implements driver.EventLog
|
||||
func (m *mockEventLog) Read(ctx context.Context, pos int64, count int64) (event.Events, error) {
|
||||
if m.onRead != nil {
|
||||
return m.onRead(ctx, pos, count)
|
||||
}
|
||||
|
||||
panic("unimplemented")
|
||||
}
|
||||
func (m *mockEventLog) ReadN(ctx context.Context, index ...uint64) (event.Events, error) {
|
||||
if m.onReadN != nil {
|
||||
return m.onReadN(ctx, index...)
|
||||
}
|
||||
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
var _ driver.EventLog = (*mockEventLog)(nil)
|
||||
|
||||
func TestProjecter(t *testing.T) {
|
||||
is := is.New(t)
|
||||
ctx := context.Background()
|
||||
|
||||
var events []event.Event
|
||||
|
||||
wait := make(chan struct{})
|
||||
|
||||
mockEL := &mockEventLog{}
|
||||
mockEL.onRead = func(ctx context.Context, i1, i2 int64) (event.Events, error) {
|
||||
return event.NewEvents(), nil
|
||||
}
|
||||
mockEL.onAppend = func(ctx context.Context, e event.Events, u uint64) (uint64, error) {
|
||||
events = append(events, e...)
|
||||
if wait != nil && len(events) > 3 {
|
||||
close(wait)
|
||||
}
|
||||
return uint64(len(e)), nil
|
||||
}
|
||||
|
||||
mock := &mockDriver{}
|
||||
mock.onOpen = func(ctx context.Context, s string) (driver.Driver, error) {
|
||||
return mock, nil
|
||||
}
|
||||
mock.onEventLog = func(ctx context.Context, s string) (driver.EventLog, error) {
|
||||
return mockEL, nil
|
||||
}
|
||||
|
||||
ev.Init(ctx)
|
||||
ev.Register(ctx, "mock", mock)
|
||||
|
||||
es, err := ev.Open(
|
||||
ctx,
|
||||
"mock:",
|
||||
projecter.New(ctx, projecter.DefaultProjection),
|
||||
)
|
||||
|
||||
is.NoErr(err)
|
||||
|
||||
_, err = es.Read(ctx, "test", 0, 1)
|
||||
|
||||
is.NoErr(err)
|
||||
|
||||
_, err = es.Append(ctx, "test", event.NewEvents(event.NilEvent))
|
||||
is.NoErr(err)
|
||||
|
||||
<-wait
|
||||
|
||||
is.Equal(len(events), 4)
|
||||
}
|
||||
Reference in New Issue
Block a user