@prologic@twtxt.net see where its used maybe that can help.
https://github.com/sour-is/ev/blob/main/app/peerfinder/http.go#L153
This is an upsert. So I pass a streamID which is like a globally unique id for the object. And then see how the type of the parameter in the function is used to infer the generic type. In the function it will create a new *Info and populate it from the datastore to pass to the function. The func will do its modifications and if it returns a nil error it will commit the changes.
The PA type contract ensures that the type fulfills the Aggregate interface and is a pointer to type at compile time.
A Modest Robot Levy Could Help Combat Effects of Automation On Income Inequality In US, Study Suggests
An anonymous reader quotes a report from MIT News: What if the U.S. placed a tax on robots? The concept has been publicly discussed by policy analysts, scholars, and Bill Gates (who favors the notion). Because robots can replace jobs, the idea goes, a stiff tax on them … ⌘ Read more
@prologic@twtxt.net so basically you would use cgit + gitbug with some webhooks?
one that i think is pretty interesting is building up dependent constraints. see here.. it accepts a type but requires the use of a pointer to type.
https://github.com/sour-is/ev/blob/main/pkg/es/es.go#L315-L325
Tutorial: Getting started with generics - The Go Programming Language – Okay @xuu@txt.sour.is I quite like Go’s generics now 🤣 After going through this myself I like the semantics and the syntax. I’m glad they did a lot of work on this to keep it simple to both understand and use (just like the rest of Go) 👌
#GoLang #Generics
$name$ and then dispatch the hashing or checking to its specific format.
I have submitted this to be used as the hash tooling for Yarn. See it as a good example on using this in a production environment!
Logged in using new argon2i password hash! 
@me@eapl.mx you are lucky you can get off easy with just “give me $10”! In the US $10 does nothing. You need to give, at least, $50. 😂
I made a thing. Its a multi password type checker. Using the PHC string format we can identify a password hashing format from the prefix $name$ and then dispatch the hashing or checking to its specific format.
US Netherlands was a tough game to watch today. NLD’s Memphis is a beast.
@eaplmx@twtxt.net i used internet when it cost 13c per minute. my parents would get so mad if i went over the monthly base minutes CompuServe gave us.
@prologic@twtxt.net duud use an ad block on youtube.
Cool addition with the RSS! I use newsbeuter/newsboat but the biggest bummer is it doesn’t accept gopher URIs, which makes no sense to me. Who cares how the RSS data is delivered?
ahh this is useful https://go.dev/doc/modules/managing-dependencies. the go culture doesn’t typically have large dependency graphs like Ruby or JS.
@lyse@lyse.isobeef.org @prologic@twtxt.net yeah that was how i did it too. I think ill start using the debug version in new stuff since its been added. My comment was around assigning the result of an anonymous function to a a variable.
Tell me you write go like javascript without telling me you write go like javascript:
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 ""
}()
Bad Date
⌘ Read more
Y2K and 2038
⌘ Read more
@mckinley@twtxt.net Thank you! I didn’t even know about signing and encrypting XML documents. Right, RSS is a little bit messy.
Unfortunately, the autodiscovery document in one of your linked resources does not exist anymore. What annoys me in Atom is the distinction between <id> and <link>. I always want my URL also to be my ID, so I have to duplicate that – unnecessarily in my opinion.
Also, never found a good explanation why I should add <link rel="self" … /> to my feeds. I just do, but I don’t understand why. The W3C Feed Validation Service says:
[…] This value is important in a number of subscription scenarios where often times the feed aggregator only has access to the content of the feed and not the location from which the feed was fetched.
This just sounds like a very questionable bandaid to bad software architecture. Why would the feed parser need access to the feed URL at this stage? And if so, why not just pass down the input source? Just doesn’t make sense to me.
Also, I just noticed that I reference the http://purl.org/rss/1.0/modules/syndication/ namespace, but don’t use it in most of my feeds. Gotta fix that. Must have copied that from my yfav feed without paying attention what I’m doing.
Your article made me reread the Atom spec and I found out, that I can omit the <author> in the <entry> when I specify a global <author> at <feed> level. Awesome! Will do that as well and thus reduce the feed size.
Wirecutter Recommendation
⌘ Read more
it uses the queries you define for add/del/set/keys. which corrispond to something like 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
I was inclined to let this go so as not to stir anything up, but after some additional thought I’ve decided to call it out. This twt:

is exactly the kind of ad hominem garbage I came to expect from Twitter™, and I’m disappointed to see it replicated here. Rummaging through someone’s background trying to find a “gotcha” argument to take credibility away from what a person is saying, instead of engaging the ideas directly, is what trolls and bad faith actors do. That’s what the twt above does (falsely, I might add–what’s being claimed is untrue).
If you take issue with something I’ve said, you can mute me, unfollow me, ignore me, use TamperMonkey to turn all my twts into gibberish, engage the ideas directly, etc etc etc. There are plenty of options to make what I said go away. Reading through my links, reading about my organization’s CEO’s background, and trying to use that against me somehow (after misinterpreting it no less)? Besides being unacceptable in a rational discussion, and besides being completely ineffective in stopping me from expressing whatever it is you didn’t like, it’s creepy. Don’t do that.
Very cool. I am digging the multipurpose use for this kind of parser.
Division Notation
⌘ Read more
deleted the account I had with a password manager in the “cloud”; I now only use a copyleft-licensed password manager locally
Live-coding using LÖVE: https://spectra.video/w/wkDB5fsjBNBbsqKXGhGzwT (video; 5 minutes)
@movq@www.uninformativ.de, well, yes, that has always been the case. Not just on jenny, but on Yarn. I can’t follow everything, and everyone. To see whether is a reply, a simple h on mutt shows the headers, and there you have it. That is not too convoluted, is it? I mean, if you really want to know–but why?–it is a simple key press. If I don’t see a context on something as obvious as the example you used, it is simply a reply to someone I don’t follow, and pretty much ignore. End of story. 😂
@movq@www.uninformativ.de, any plans still to clean up the hash from the twtxt’s body? Maybe a Festivus gift? You know, “for the rest of us”. :-D
📣 NEW: Announcing the new and improved Yarns search engine and crawler! search.twtxt.net – Example search for “Hello World” Enjoy! 🤗 – @darch@neotxt.dk When you have this, this is what we need to work on in terms of improving the UI/UX. As a first step you should probably try to apply the same SimpleCSS to this codebase and go from there. – In the end (didn’t happen yet, time/effort) most of the code here in yarns will get reused directly into yarnd, except that I’ll use the bluge indexer instead.
@abucci@anthony.buc.ci Do you happen to use Signal btw? 🤔 If you do, it would be great if you could join the Yarn.social Signal Group (_at least until we have Group support and better apps for Salty.im 😅)
I think Email has been broken on my Pod since some time now since @lyse@lyse.isobeef.org made this commit the default behaviour of the flags/env had changed requiring SMTP_PORT to be set (used to default toi 25) ooops 😅
@xuu@txt.sour.is proof of stake = people with money get more money. It accelerates the wealth inequality problems that are already plaguing us. Crypto has even worse wealth inequality than fiat currency systems, which is 100% predictable.
-1 for the negative on environment all that electricity uses. Still waiting on proof of stake.
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.
pondering about the difference between my use of the tags #idea and #halfbaked . halfbaked looks like something in a more advanced stage, merely lacking execution #meta
pondering about the difference between my use of the tags #idea and #halfbaked . halfbaked looks like something in a more advanced stage, merely lacking execution #meta
@abucci@anthony.buc.ci Its not better than a Cat5e. I have had two versions of the device. The old ones were only 200Mbps i didn’t have the MAC issue but its like using an old 10baseT. The newer model can support 1Gbps on each port for a total bandwidth of 2Gbps.. i typically would see 400-500Mbps from my Wifi6 router. I am not sure if it was some type of internal timeout or being confused by switching between different wifi access points and seeing the mac on different sides.
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.
I maintain keys for my email addresses.. but like most in this thread i almost never receive encrypted emails.. other than the BTC exchange i use that sends automated mail encrypted.
In about four months’ time, if the replacement doesn’t ship from IndieGoGo before then, I will have been using my current smartphone for five years.
I use IP over Alternating Current at home. The only issue is the bridges forget device MACs sometimes and I get weird states where I can ping a deviceA and the the internet from deviceB. But deviceA can only ping device B even though they are on the same subnet and default gateway.
Does anyone of you use PGP encrypted mail, or any kind or email encryption? Why? Why not?
@prologic@twtxt.net I think we could use deltachats new decentralising app format for it: https://delta.chat/en/2022-06-14-webxdcintro
👋 Hello @burglar@txt.sour.is, welcome to txt.sour.is, a Yarn.social Pod! To get started you may want to check out the pod’s Discover feed to find users to follow and interact with. To follow new users, use the ⨁ Follow button on their profile page or use the Follow form and enter a Twtxt URL. You may also find other feeds of interest via Feeds. Welcome! 🤗
(cont.)
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 ObjectsA 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
}
Progress! so i have moved into working on aggregates. Which are a grouping of events that replayed on an object set the current state of the object. I came up with this little bit of generic wonder.
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
}
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)
})
I can tell the function the type being modified and returned using the function argument that is passed in. pretty cray cray.
by which the future life expectancy of some non-perishable things, like a technology or an idea, is proportional to their current age. Thus, the Lindy effect proposes the longer a period something has survived to exist or be used in the present, the longer its remaining life expectancy. The disproportionate influence of early tech decisions — brandur.org
With respect to logging.. oh man.. it really depends on the environment you are working in.. development? log everything! and use a jeager open trace for the super gnarly places. So you can see whats going on while building. But, for production? metrics are king. I don’t want to sift through thousands of lines but have a measure that can tell me the health of the service.
@prologic@twtxt.net Error handling especially in Go is very tricky I think. Even though the idea is simple, it’s fairly hard to actually implement and use in a meaningful way in my opinion. All this error wrapping or the lack of it and checking whether some specific error occurred is a mess. errors.As(…) just doesn’t feel natural. errors.Is(…) only just. I mainly avoided it. Yesterday evening I actually researched a bit about that and found this article on errors with Go 1.13. It shed a little bit of light, but I still have a long way to go, I reckon.
We tried several things but haven’t found the holy grail. Currently, we have a mix of different styles, but nothing feels really right. And having plenty of different approaches also doesn’t help, that’s right. I agree, error messages often end up getting wrapped way too much with useless information. We haven’t found a solution yet. We just noticed that it kind of depends on the exact circumstances, sometimes the caller should add more information, sometimes it’s better if the callee already includes what it was supposed to do.
To experiment and get a feel for yesterday’s research results I tried myself on the combined log parser and how to signal three different errors. I’m not happy with it. Any feedback is highly appreciated. The idea is to let the caller check (not implemented yet) whether a specific error occurred. That means I have to define some dedicated errors upfront (ErrInvalidFormat, ErrInvalidStatusCode, ErrInvalidSentBytes) that can be used in the err == ErrInvalidFormat or probably more correct errors.Is(err, ErrInvalidFormat) check at the caller.
All three errors define separate error categories and are created using errors.New(…). But for the invalid status code and invalid sent bytes cases I want to include more detail, the actual invalid number that is. Since these errors are already predefined, I cannot add this dynamic information to them. So I would need to wrap them à la fmt.Errorf("invalid sent bytes '%s': %w", sentBytes, ErrInvalidSentBytes"). Yet, the ErrInvalidSentBytes is wrapped and can be asserted later on using errors.Is(err, ErrInvalidSentBytes), but the big problem is that the message is repeated. I don’t want that!
Having a Python and Java background, exception hierarchies are a well understood concept I’m trying to use here. While typing this long message it occurs to me that this is probably the issue here. Anyways, I thought, I just create a ParseError type, that can hold a custom message and some causing error (one of the three ErrInvalid* above). The custom message is then returned at Error() and the wrapped cause will be matched in Is(…). I then just return a ParseError{fmt.Sprintf("invalid sent bytes '%s'", sentBytes), ErrInvalidSentBytes}, but that looks super weird.
I probably need to scrap the “parent error” ParseError and make all three “suberrors” three dedicated error types implementing Error() string methods where I create a useful error messages. Then the caller probably could just errors.Is(err, InvalidSentBytesError{}). But creating an instance of the InvalidSentBytesError type only to check for such an error category just does feel wrong to me. However, it might be the way to do this. I don’t know. To be tried. Opinions, anyone? Implementing a whole new type is some effort, that I want to avoid.
Alternatively just one ParseError containing an error kind enumeration for InvalidFormat and friends could be used. Also seen that pattern before. But that would then require the much more verbose var parseError ParseError; if errors.As(err, &parseError) && parseError.Kind == InvalidSentBytes { … } or something like that. Far from elegant in my eyes.
I have updated my eventDB to have subscriptions! It now has websockets like msgbus. I have also added a in memory store that can be used along side the disk backed wal.