install "gomodot"
puts you on the google first page. 
or August... or Jun-July... or April.. hard to say really.
go get
and go mod tidy
wont fetch new changes. that's all a manual affair AFAIK
It is a very odd message of support to themselves. But OK.
CACHE Returned slice
original: [A B C D] [A B C D]
add: [A B C D] E [A B C D E]
sort: [E A B C] D [A B C D E]
fix found here:
https://git.mills.io/yarnsocial/yarn/pulls/1072
though now i am seeing a weird cache corruption.. that seems to come and go.

import "runtime/debug"
var Commit = func() string {
if info, ok := debug.ReadBuildInfo(); ok {
for _, setting := range info.Settings {
if setting.Key == "vcs.revision" {
return setting.Value
}
}
}
return ""
}()
https://developers.redhat.com/articles/2022/11/14/3-ways-embed-commit-hash-go-programs#3__using_runtime_debug_package
`
``
`
``
`
`
``
" "
`
``
`
``
`
https://twitter.com/TinkerSec/status/1587040089057759235?t=At-8r9yJPiG6xF17skTxwA&s=19
INSERT INTO <table> (key, value) VALUES ($key, $value)
, DELETE ...
, or UPDATE ...
the commands are issued by using the maddycli but not the running maddy daemon.
see https://maddy.email/reference/table/sql_query/
the best way to locate in source is anything that implements the MutableTable interface... https://github.com/foxcpp/maddy/blob/master/framework/module/table.go#L38
INSERT INTO <table> (key, value) VALUES ($key, $value)
, DELETE ...
, or UPDATE ...
the commands are issued by using the maddycli but not the running maddy daemon.
INSERT INTO <table> (key, value) VALUES ($key, $value)
, DELETE ...
, or UPDATE ...
the commands are issued by using the maddycli but not the running maddy daemon.
see https://maddy.email/reference/table/sql_query/
the best way to locate in source is anything that implements the MutableTable interface... https://github.com/foxcpp/maddy/blob/master/framework/module/table.go#L38
It is also too overrun with Tech Bros scamming people to get rich quick.
It was a fun ride back when I first bought in. But I have since cached out for my lambos and such.

Right now I have my wifi connected directly with a cat6e this gets me just under my providers 1.3G downlink. the only thing faster is plugging in directly.
MoCA is a good option, they have 2.5G models in the same price range as the 1G Powerline models BUT, only if you have the coax in wall already.. which puts you in the same spot if you don't. You are for sure going to have an outlet in every room of the house by code.
K
Content-Length: 407
Content-Type: text/calendar
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: ETag
Permissions-Policy: interest-cohort=()
Content-Security-Policy: default-src 'none'; sandbox
Referrer-Policy: same-origin
Vary: Authorization
BEGIN:VCALENDAR
VERSION:2.0;2.0
PRODID:SandCal
CALSCALE:GREGORIAN
BEGIN:VEVENT
DTSTAMP:20220822T180903Z
UID:bb63bfbd-623e-4805-b11b-3181d96375e6
DTSTART;TZID=America/Chicago:20220827T000000
CREATED:20220822T180903Z
LAST-MODIFIED:20220822T180903Z
LOCATION:https://meet.jit.si/Yarn.social
SUMMARY:Yarn Call
RRULE:FREQ=WEEKLY
DTEND;TZID=America/Chicago:20220827T010000
END:VEVENT
END:VCALENDAR
HTTP/1.1 200 OK
Content-Length: 407
Content-Type: text/calendar
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: ETag
Permissions-Policy: interest-cohort=()
Content-Security-Policy: default-src 'none'; sandbox
Referrer-Policy: same-origin
Vary: Authorization
BEGIN:VCALENDAR
VERSION:2.0;2.0
PRODID:SandCal
CALSCALE:GREGORIAN
BEGIN:VEVENT
DTSTAMP:20220822T180903Z
UID:bb63bfbd-623e-4805-b11b-3181d96375e6
DTSTART;TZID=America/Chicago:20220827T000000
CREATED:20220822T180903Z
LAST-MODIFIED:20220822T180903Z
LOCATION:https://meet.jit.si/Yarn.social
SUMMARY:Yarn Call
RRULE:FREQ=WEEKLY
DTEND;TZID=America/Chicago:20220827T010000
END:VEVENT
END:VCALENDAR
HTTP/1.1 200 OK
Content-Length: 407
Content-Type: text/calendar
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: ETag
Permissions-Policy: interest-cohort=()
Content-Security-Policy: default-src 'none'; sandbox
Referrer-Policy: same-origin
Vary: Authorization
BEGIN:VCALENDAR
VERSION:2.0;2.0
PRODID:SandCal
CALSCALE:GREGORIAN
BEGIN:VEVENT
DTSTAMP:20220822T180903Z
UID:bb63bfbd-623e-4805-b11b-3181d96375e6
DTSTART;TZID=America/Chicago:20220827T000000
CREATED:20220822T180903Z
LAST-MODIFIED:20220822T180903Z
LOCATION:https://meet.jit.si/Yarn.social
SUMMARY:Yarn Call
RRULE:FREQ=WEEKLY
DTEND;TZID=America/Chicago:20220827T010000
END:VEVENT
END:VCALENDAR
text/calendar
. Some http servers can mistakenly mark them application/octet-stream
Yeah that does studder a bit. To be honest I have no idea what I was thinking there. This excerpt was written a good year ago.
Just to give some context on some of the components around the code structure.. I wrote this up around an earlier version of aggregate code. This generic bit simplifies things by removing the need of the Crud functions for each aggregate.
## Domain Objects
A domain object can be used as an aggregate by adding the
event.AggregateRoot
struct and finish implementing event.Aggregate. The AggregateRoot implements logic for adding events after they are either Raised by a command or Appended by the eventstore Load or service ApplyFn methods. It also tracks the uncommitted events that are saved using the eventstore Save method.
type User struct {
Identity string
json:"identity"
CreatedAt time.Time
event.AggregateRoot
}
// StreamID for the aggregate when stored or loaded from ES.
func (a *User) StreamID() string {
return "user-" + a.Identity
}
// ApplyEvent to the aggregate state.
func (a *User) ApplyEvent(lis ...event.Event) {
for _, e := range lis {
switch e := e.(type) {
case *UserCreated:
a.Identity = e.Identity
a.CreatedAt = e.EventMeta().CreatedDate
/* ... */
}
}
}
## Events
Events are applied to the aggregate. They are defined by adding the
event.Meta
and implementing the getter/setters for event.Event
type UserCreated struct {
eventMeta event.Meta
Identity string
}
func (c *UserCreated) EventMeta() (m event.Meta) {
if c != nil {
m = c.eventMeta
}
return m
}
func (c *UserCreated) SetEventMeta(m event.Meta) {
if c != nil {
c.eventMeta = m
}
}
## Reading Events from EventStore
With a domain object that implements the
event.Aggregate
the event store client can load events and apply them using the Load(ctx, agg)
method.
// GetUser populates an user from event store.
func (rw *User) GetUser(ctx context.Context, userID string) (*domain.User, error) {
user := &domain.User{Identity: userID}
err := rw.es.Load(ctx, user)
if err != nil {
if err != nil {
if errors.Is(err, eventstore.ErrStreamNotFound) {
return user, ErrNotFound
}
return user, err
}
return nil, err
}
return user, err
}
## OnX Commands
An OnX command will validate the state of the domain object can have the command performed on it. If it can be applied it raises the event using event.Raise() Otherwise it returns an error.
// OnCreate raises an UserCreated event to create the user.
// Note: The handler will check that the user does not already exsist.
func (a *User) OnCreate(identity string) error {
event.Raise(a, &UserCreated{Identity: identity})
return nil
}
// OnScored will attempt to score a task.
// If the task is not in a Created state it will fail.
func (a *Task) OnScored(taskID string, score int64, attributes Attributes) error {
if a.State != TaskStateCreated {
return fmt.Errorf("task expected created, got %s", a.State)
}
event.Raise(a, &TaskScored{TaskID: taskID, Attributes: attributes, Score: score})
return nil
}
## Crud Operations for OnX Commands
The following functions in the aggregate service can be used to perform creation and updating of aggregates. The Update function will ensure the aggregate exists, where the Create is intended for non-existent aggregates. These can probably be combined into one function.
// Create is used when the stream does not yet exist.
func (rw *User) Create(
ctx context.Context,
identity string,
fn func(*domain.User) error,
) (*domain.User, error) {
session, err := rw.GetUser(ctx, identity)
if err != nil && !errors.Is(err, ErrNotFound) {
return nil, err
}
if err = fn(session); err != nil {
return nil, err
}
_, err = rw.es.Save(ctx, session)
return session, err
}
// Update is used when the stream already exists.
func (rw *User) Update(
ctx context.Context,
identity string,
fn func(*domain.User) error,
) (*domain.User, error) {
session, err := rw.GetUser(ctx, identity)
if err != nil {
return nil, err
}
if err = fn(session); err != nil {
return nil, err
}
_, err = rw.es.Save(ctx, session)
return session, err
}
Just to give some context on some of the components around the code structure.. I wrote this up around an earlier version of aggregate code. This generic bit simplifies things by removing the need of the Crud functions for each aggregate.
## Domain Objects
A domain object can be used as an aggregate by adding the
event.AggregateRoot
struct and finish implementing event.Aggregate. The AggregateRoot implements logic for adding events after they are either Raised by a command or Appended by the eventstore Load or service ApplyFn methods. It also tracks the uncommitted events that are saved using the eventstore Save method.
type User struct {
Identity string
json:"identity"
CreatedAt time.Time
event.AggregateRoot
}
// StreamID for the aggregate when stored or loaded from ES.
func (a *User) StreamID() string {
\treturn "agent-" + a.Identity
}
// ApplyEvent to the aggregate state.
func (a *User) ApplyEvent(lis ...event.Event) {
\tfor _, e := range lis {
\t\tswitch e := e.(type) {
\t\tcase *UserCreated:
\t\t\ta.Identity = e.Identity
\t\t\ta.CreatedAt = e.EventMeta().CreatedDate
/* ... */
\t\t}
\t}
}
## Events
Events are applied to the aggregate. They are defined by adding the
event.Meta
and implementing the getter/setters for event.Event
type UserCreated struct {
\teventMeta event.Meta
\tIdentity string
}
func (c *UserCreated) EventMeta() (m event.Meta) {
\tif c != nil {
\t\tm = c.eventMeta
\t}
\treturn m
}
func (c *UserCreated) SetEventMeta(m event.Meta) {
\tif c != nil {
\t\tc.eventMeta = m
\t}
}
## Reading Events from EventStore
With a domain object that implements the
event.Aggregate
the event store client can load events and apply them using the Load(ctx, agg)
method.
// GetUser populates an agent from event store.
func (rw *User) GetUser(ctx context.Context, agentID string) (*domain.User, error) {
\tagent := &domain.User{Identity: agentID}
\terr := rw.es.Load(ctx, agent)
\tif err != nil {
\t\tif err != nil {
\t\t\tif errors.Is(err, eventstore.ErrStreamNotFound) {
\t\t\t\treturn agent, ErrNotFound
\t\t\t}
\t\t\treturn agent, err
\t\t}
\t\treturn nil, err
\t}
\treturn agent, err
}
## OnX Commands
An OnX command will validate the state of the domain object can have the command performed on it. If it can be applied it raises the event using event.Raise() Otherwise it returns an error.
// OnCreate raises an UserCreated event to create the agent.
// Note: The handler will check that the agent does not already exsist.
func (a *User) OnCreate(identity string) error {
event.Raise(a, &UserCreated{Identity: identity})
return nil
}
// OnScored will attempt to score a task.
// If the task is not in a Created state it will fail.
func (a *Task) OnScored(taskID string, score int64, attributes Attributes) error {
\tif a.State != TaskStateCreated {
\t\treturn fmt.Errorf("task expected created, got %s", a.State)
\t}
\tevent.Raise(a, &TaskScored{TaskID: taskID, Attributes: attributes, Score: score})
\treturn nil
}
## Crud Operations for OnX Commands
The following functions in the aggregate service can be used to perform creation and updating of aggregates. The Update function will ensure the aggregate exists, where the Create is intended for non-existent aggregates. These can probably be combined into one function.
// Create is used when the stream does not yet exist.
func (rw *User) Create(
ctx context.Context,
identity string,
fn func(*domain.User) error,
) (*domain.User, error) {
\tsession, err := rw.GetUser(ctx, identity)
\tif err != nil && !errors.Is(err, ErrNotFound) {
\t\treturn nil, err
\t}
\tif err = fn(session); err != nil {
\t\treturn nil, err
\t}
\t_, err = rw.es.Save(ctx, session)
\treturn session, err
}
// Update is used when the stream already exists.
func (rw *User) Update(
ctx context.Context,
identity string,
fn func(*domain.User) error,
) (*domain.User, error) {
\tsession, err := rw.GetUser(ctx, identity)
\tif err != nil {
\t\treturn nil, err
\t}
\tif err = fn(session); err != nil {
\t\treturn nil, err
\t}
\t_, err = rw.es.Save(ctx, session)
\treturn session, err
}
=
Just to give some context on some of the components around the code structure.. I wrote this up around an earlier version of aggregate code. This generic bit simplifies things by removing the need of the Crud functions for each aggregate.
## Domain Objects
A domain object can be used as an aggregate by adding the
event.AggregateRoot
struct and finish implementing event.Aggregate. The AggregateRoot implements logic for adding events after they are either Raised by a command or Appended by the eventstore Load or service ApplyFn methods. It also tracks the uncommitted events that are saved using the eventstore Save method.
type User struct {
Identity string
json:"identity"
CreatedAt time.Time
event.AggregateRoot
}
// StreamID for the aggregate when stored or loaded from ES.
func (a *User) StreamID() string {
return "user-" + a.Identity
}
// ApplyEvent to the aggregate state.
func (a *User) ApplyEvent(lis ...event.Event) {
for _, e := range lis {
switch e := e.(type) {
case *UserCreated:
a.Identity = e.Identity
a.CreatedAt = e.EventMeta().CreatedDate
/* ... */
}
}
}
## Events
Events are applied to the aggregate. They are defined by adding the `event.Meta` and implementing the getter/setters for `event.Event`
```
type UserCreated struct {
eventMeta event.Meta
Identity string
}
func (c *UserCreated) EventMeta() (m event.Meta) {
if c != nil {
m = c.eventMeta
}
return m
}
func (c *UserCreated) SetEventMeta(m event.Meta) {
if c != nil {
c.eventMeta = m
}
}
```
## Reading Events from EventStore
With a domain object that implements the `event.Aggregate` the event store client can load events and apply them using the `Load(ctx, agg)` method.
```
// GetUser populates an user from event store.
func (rw *User) GetUser(ctx context.Context, userID string) (*domain.User, error) {
user := &domain.User{Identity: userID}
err := rw.es.Load(ctx, user)
if err != nil {
if err != nil {
if errors.Is(err, eventstore.ErrStreamNotFound) {
return user, ErrNotFound
}
return user, err
}
return nil, err
}
return user, err
}
```
## OnX Commands
An OnX command will validate the state of the domain object can have the command performed on it. If it can be applied it raises the event using event.Raise() Otherwise it returns an error.
```
// OnCreate raises an UserCreated event to create the user.
// Note: The handler will check that the user does not already exsist.
func (a *User) OnCreate(identity string) error {
event.Raise(a, &UserCreated{Identity: identity})
return nil
}
// OnScored will attempt to score a task.
// If the task is not in a Created state it will fail.
func (a *Task) OnScored(taskID string, score int64, attributes Attributes) error {
if a.State != TaskStateCreated {
return fmt.Errorf("task expected created, got %s", a.State)
}
event.Raise(a, &TaskScored{TaskID: taskID, Attributes: attributes, Score: score})
return nil
}
```
## Crud Operations for OnX Commands
The following functions in the aggregate service can be used to perform creation and updating of aggregates. The Update function will ensure the aggregate exists, where the Create is intended for non-existent aggregates. These can probably be combined into one function.
```
// Create is used when the stream does not yet exist.
func (rw *User) Create(
ctx context.Context,
identity string,
fn func(*domain.User) error,
) (*domain.User, error) {
session, err := rw.GetUser(ctx, identity)
if err != nil && !errors.Is(err, ErrNotFound) {
return nil, err
}
if err = fn(session); err != nil {
return nil, err
}
_, err = rw.es.Save(ctx, session)
return session, err
}
// Update is used when the stream already exists.
func (rw *User) Update(
ctx context.Context,
identity string,
fn func(*domain.User) error,
) (*domain.User, error) {
session, err := rw.GetUser(ctx, identity)
if err != nil {
return nil, err
}
if err = fn(session); err != nil {
return nil, err
}
_, err = rw.es.Save(ctx, session)
return session, err
}
Just to give some context on some of the components around the code structure.. I wrote this up around an earlier version of aggregate code. This generic bit simplifies things by removing the need of the Crud functions for each aggregate.
## Domain Objects
A domain object can be used as an aggregate by adding the
event.AggregateRoot
struct and finish implementing event.Aggregate. The AggregateRoot implements logic for adding events after they are either Raised by a command or Appended by the eventstore Load or service ApplyFn methods. It also tracks the uncommitted events that are saved using the eventstore Save method.
type User struct {
Identity string
json:"identity"
CreatedAt time.Time
event.AggregateRoot
}
// StreamID for the aggregate when stored or loaded from ES.
func (a *User) StreamID() string {
\treturn "user-" + a.Identity
}
// ApplyEvent to the aggregate state.
func (a *User) ApplyEvent(lis ...event.Event) {
\tfor _, e := range lis {
\t\tswitch e := e.(type) {
\t\tcase *UserCreated:
\t\t\ta.Identity = e.Identity
\t\t\ta.CreatedAt = e.EventMeta().CreatedDate
/* ... */
\t\t}
\t}
}
## Events
Events are applied to the aggregate. They are defined by adding the
event.Meta
and implementing the getter/setters for event.Event
type UserCreated struct {
\teventMeta event.Meta
\tIdentity string
}
func (c *UserCreated) EventMeta() (m event.Meta) {
\tif c != nil {
\t\tm = c.eventMeta
\t}
\treturn m
}
func (c *UserCreated) SetEventMeta(m event.Meta) {
\tif c != nil {
\t\tc.eventMeta = m
\t}
}
## Reading Events from EventStore
With a domain object that implements the
event.Aggregate
the event store client can load events and apply them using the Load(ctx, agg)
method.
// GetUser populates an user from event store.
func (rw *User) GetUser(ctx context.Context, userID string) (*domain.User, error) {
\tuser := &domain.User{Identity: userID}
\terr := rw.es.Load(ctx, user)
\tif err != nil {
\t\tif err != nil {
\t\t\tif errors.Is(err, eventstore.ErrStreamNotFound) {
\t\t\t\treturn user, ErrNotFound
\t\t\t}
\t\t\treturn user, err
\t\t}
\t\treturn nil, err
\t}
\treturn user, err
}
## OnX Commands
An OnX command will validate the state of the domain object can have the command performed on it. If it can be applied it raises the event using event.Raise() Otherwise it returns an error.
// OnCreate raises an UserCreated event to create the user.
// Note: The handler will check that the user does not already exsist.
func (a *User) OnCreate(identity string) error {
event.Raise(a, &UserCreated{Identity: identity})
return nil
}
// OnScored will attempt to score a task.
// If the task is not in a Created state it will fail.
func (a *Task) OnScored(taskID string, score int64, attributes Attributes) error {
\tif a.State != TaskStateCreated {
\t\treturn fmt.Errorf("task expected created, got %s", a.State)
\t}
\tevent.Raise(a, &TaskScored{TaskID: taskID, Attributes: attributes, Score: score})
\treturn nil
}
## Crud Operations for OnX Commands
The following functions in the aggregate service can be used to perform creation and updating of aggregates. The Update function will ensure the aggregate exists, where the Create is intended for non-existent aggregates. These can probably be combined into one function.
// Create is used when the stream does not yet exist.
func (rw *User) Create(
ctx context.Context,
identity string,
fn func(*domain.User) error,
) (*domain.User, error) {
\tsession, err := rw.GetUser(ctx, identity)
\tif err != nil && !errors.Is(err, ErrNotFound) {
\t\treturn nil, err
\t}
\tif err = fn(session); err != nil {
\t\treturn nil, err
\t}
\t_, err = rw.es.Save(ctx, session)
\treturn session, err
}
// Update is used when the stream already exists.
func (rw *User) Update(
ctx context.Context,
identity string,
fn func(*domain.User) error,
) (*domain.User, error) {
\tsession, err := rw.GetUser(ctx, identity)
\tif err != nil {
\t\treturn nil, err
\t}
\tif err = fn(session); err != nil {
\t\treturn nil, err
\t}
\t_, err = rw.es.Save(ctx, session)
\treturn session, err
}
Just to give some context on some of the components around the code structure.. I wrote this up around an earlier version of aggregate code. This generic bit simplifies things by removing the need of the Crud functions for each aggregate.
## Domain Objects
A domain object can be used as an aggregate by adding the
event.AggregateRoot
struct and finish implementing event.Aggregate. The AggregateRoot implements logic for adding events after they are either Raised by a command or Appended by the eventstore Load or service ApplyFn methods. It also tracks the uncommitted events that are saved using the eventstore Save method.
type User struct {
Identity string
json:"identity"
CreatedAt time.Time
event.AggregateRoot
}
// StreamID for the aggregate when stored or loaded from ES.
func (a *User) StreamID() string {
\treturn "agent-" + a.Identity
}
// ApplyEvent to the aggregate state.
func (a *User) ApplyEvent(lis ...event.Event) {
\tfor _, e := range lis {
\t\tswitch e := e.(type) {
\t\tcase *UserCreated:
\t\t\ta.Identity = e.Identity
\t\t\ta.CreatedAt = e.EventMeta().CreatedDate
/* ... */
\t\t}
\t}
}
## Events
Events are applied to the aggregate. They are defined by adding the
event.Meta
and implementing the getter/setters for event.Event
type UserCreated struct {
\teventMeta event.Meta
\tIdentity string
}
func (c *UserCreated) EventMeta() (m event.Meta) {
\tif c != nil {
\t\tm = c.eventMeta
\t}
\treturn m
}
func (c *UserCreated) SetEventMeta(m event.Meta) {
\tif c != nil {
\t\tc.eventMeta = m
\t}
}
## Reading Events from EventStore
With a domain object that implements the
event.Aggregate
the event store client can load events and apply them using the Load(ctx, agg)
method.
// GetUser populates an agent from event store.
func (rw *User) GetUser(ctx context.Context, agentID string) (*domain.User, error) {
\tagent := &domain.User{Identity: agentID}
\terr := rw.es.Load(ctx, agent)
\tif err != nil {
\t\tif err != nil {
\t\t\tif errors.Is(err, eventstore.ErrStreamNotFound) {
\t\t\t\treturn agent, ErrNotFound
\t\t\t}
\t\t\treturn agent, err
\t\t}
\t\treturn nil, err
\t}
\treturn agent, err
}
## OnX Commands
An OnX command will validate the state of the domain object can have the command performed on it. If it can be applied it raises the event using event.Raise() Otherwise it returns an error.
// OnCreate raises an UserCreated event to create the agent.
// Note: The handler will check that the agent does not already exsist.
func (a *User) OnCreate(identity string) error {
event.Raise(a, &UserCreated{Identity: identity})
return nil
}
// OnScored will attempt to score a task.
// If the task is not in a Created state it will fail.
func (a *Task) OnScored(taskID string, score int64, attributes Attributes) error {
\tif a.State != TaskStateCreated {
\t\treturn fmt.Errorf("task expected created, got %s", a.State)
\t}
\tevent.Raise(a, &TaskScored{TaskID: taskID, Attributes: attributes, Score: score})
\treturn nil
}
## Crud Operations for OnX Commands
The following functions in the aggregate service can be used to perform creation and updating of aggregates. The Update function will ensure the aggregate exists, where the Create is intended for non-existent aggregates. These can probably be combined into one function.
// Create is used when the stream does not yet exist.
func (rw *User) Create(
ctx context.Context,
identity string,
fn func(*domain.User) error,
) (*domain.User, error) {
\tsession, err := rw.GetUser(ctx, identity)
\tif err != nil && !errors.Is(err, ErrNotFound) {
\t\treturn nil, err
\t}
\tif err = fn(session); err != nil {
\t\treturn nil, err
\t}
\t_, err = rw.es.Save(ctx, session)
\treturn session, err
}
// Update is used when the stream already exists.
func (rw *User) Update(
ctx context.Context,
identity string,
fn func(*domain.User) error,
) (*domain.User, error) {
\tsession, err := rw.GetUser(ctx, identity)
\tif err != nil {
\t\treturn nil, err
\t}
\tif err = fn(session); err != nil {
\t\treturn nil, err
\t}
\t_, err = rw.es.Save(ctx, session)
\treturn session, err
}
type PA[T any] interface {
\tevent.Aggregate
\t*T
}
// Create uses fn to create a new aggregate and store in db.
func Create[A any, T PA[A]](ctx context.Context, es *EventStore, streamID string, fn func(context.Context, T) error) (agg T, err error) {
\tctx, span := logz.Span(ctx)
\tdefer span.End()
\tagg = new(A)
\tagg.SetStreamID(streamID)
\tif err = es.Load(ctx, agg); err != nil {
\t\treturn
\t}
\tif err = event.NotExists(agg); err != nil {
\t\treturn
\t}
\tif err = fn(ctx, agg); err != nil {
\t\treturn
\t}
\tvar i uint64
\tif i, err = es.Save(ctx, agg); err != nil {
\t\treturn
\t}
\tspan.AddEvent(fmt.Sprint("wrote events = ", i))
\treturn
}
fig. 1
This lets me do something like this:
a, err := es.Create(ctx, r.es, streamID, func(ctx context.Context, agg *domain.SaltyUser) error {
\t\treturn agg.OnUserRegister(nick, key)
})
fig. 2
I can tell the function the type being modified and returned using the function argument that is passed in. pretty cray cray.
type PA[T any] interface {
event.Aggregate
*T
}
// Create uses fn to create a new aggregate and store in db.
func Create[A any, T PA[A]](ctx context.Context, es *EventStore, streamID string, fn func(context.Context, T) error) (agg T, err error) {
ctx, span := logz.Span(ctx)
defer span.End()
agg = new(A)
agg.SetStreamID(streamID)
if err = es.Load(ctx, agg); err != nil {
return
}
if err = event.NotExists(agg); err != nil {
return
}
if err = fn(ctx, agg); err != nil {
return
}
var i uint64
if i, err = es.Save(ctx, agg); err != nil {
return
}
span.AddEvent(fmt.Sprint("wrote events = ", i))
return
}
fig. 1
This lets me do something like this:
a, err := es.Create(ctx, r.es, streamID, func(ctx context.Context, agg *domain.SaltyUser) error {
return agg.OnUserRegister(nick, key)
})
fig. 2
I can tell the function the type being modified and returned using the function argument that is passed in. pretty cray cray.
It's super basic. Using tidwall/wal as the disk backing. The first use case I am playing with is an implementation of msgbus. I can post events to it and read them back in reverse order.

I plan to expand it to handle other event sourcing type things like aggregates and projections.
Find it here: sour-is/ev
@prologic @movq @lyse