Releases: floatdrop/di
Release list
v0.16.2
A faster warm path and four lifecycle fixes. go doc -all against 0.16.1 is
unchanged in di, dihttp and dislog apart from the package doc's own
text; an upgrade cannot break a caller.
Changed
- A warm resolution, of a service that is already built, no longer takes a
mutex in the scope that owns it, and observers are called without one.
EveryGetof an application singleton used to serialise on the
application scope, so it got slower per call as cores were added: 46 ns on
one core and 418 ns on eight, on an M3 Max. It is now 44 ns and 55 ns, and
a whole request scope at eight cores went from 2.3 µs to 1.4 µs.
No API or behaviour changes; the newbenchmarks/parallel_test.gorecords
the shapes. - The cycle check a resolution makes before waiting on another goroutine's
build no longer scans every waiting resolution in the container. Many
resolutions arriving at one slow constructor made it quadratic under the
container's one lock: 8192 of them took over 300 ms to settle and now take
about 20 ms on an M3 Max (BenchmarkDI_Herd_8192in
benchmarks/parallel_test.go).
Fixed
- A
Wrapin a child scope no longer keeps the parent's registration from
being overridden after the child has stopped. The parent'sOverride()was
rejected as "wrapped at" a site in a scope that no longer existed. The same
held after the child overrode its own wrapper. A wrapper in a live scope
still guards what it wraps, including when a sibling scope that wrapped the
same registration has stopped. - A service that was built and waiting for its start step when a drain swept
past it now gets itsOnDrainonce it starts. The sweep decided it owed
nothing, since it had not started, and made that final, so aStopissued
whileStartwas still starting earlier services released it withOnStop
and noOnDrain. The drain phase also no longer misses a service built
between its last sweep and the scope being marked stopped: the phase ends
only when nothing in the scope's subtree has been built or started since
that sweep began. So aStopgoes round again while anything below
it is still being built or started, and under work that never quiets it
returns when its context expires.
v0.16.1
One fix. go doc -all against 0.16.0 is unchanged in di, dihttp and
dislog; an upgrade cannot break a caller.
Fixed
- A worker registered with
Gothat panics is now a worker that failed: the
panic becomes its error,Shutdownreceives it,Runreturns it,OnStop
runs and the stop event carries it, as for a panicking hook. It was the one
user function not called through the recovering wrapper, so its panic took
the process down with no release and no event.
v0.16.0
Binding.Worker is now Binding.Go, after errgroup.Group.Go and
sync.WaitGroup.Go, whose contract it has always had: run a function in a
goroutine the group tracks, cancel it when the group winds down, wait for it,
and let its error take the group down. go doc -all against 0.15.1 removes
Binding.Worker and adds Binding.Go with the same signature. An upgrade
breaks a caller that registers a worker: rename the call from Worker to
Go, and nothing else changes.
Changed
Binding.Workeris renamedBinding.Go, with no alias left behind. The
word "worker" still names what the method registers, in the docs and in the
one errorStopreports about it, which now reads "worker did not return"
rather than "Worker hook did not return".
v0.15.1
A code-organisation release: the library is six files rather than one, and
the one observability gap the reorganisation turned up is closed. go doc -all against 0.15.0 changes doc comments only and no signature; dihttp and
dislog are untouched. An upgrade cannot break a caller.
Fixed
- A panicking
OnStarthook reports itsEventStart, with the panic as
Err, the way a panickingOnDrainorOnStophook already reported
theirs. Observers saw no start step at all for such a service; the failure
still reachedStartandResolveas before.
v0.15.0
An application can say what it is doing. go doc -all against 0.14.0 adds one
field to Event, leaves every signature alone and leaves dihttp untouched;
dislog is a new package beside it. An upgrade cannot break a caller unless
it built an Event with an unkeyed composite literal.
Added
dislog, a new package that logs a scope's lifecycle events through
log/slog:app.Observe(dislog.New(logger))gives one line per constructor
and per hook, so an application says what it is doing as it builds, starts,
drains and stops. The event's kind is the message; the service, its scope,
the module it came from and the duration are attributes. A step that failed
logs atslog.LevelErrorwith the error and the registration site, a step
that succeeded atslog.LevelInfoor whereverdislog.Levelputs it, and
dislog.Site()logs the site every time. A service is named the way it is
written in Go, with the import path lifted out into apkgattribute. The
package imports nothing beyondlog/sloganddi, so the library stays
dependency-free and any handler will do;examples/uses
charmbracelet/log.Event.Package, the import path of the typeEvent.Servicenames, so an
observer can shorten a service name or group by its package without parsing
one. It walks through pointers, since a pointer type is unnamed and carries
no path of its own, and is empty for a key whose type is unnamed -- a
[]byte, amap[string]int-- because reflect already writes those with a
short package name. An upgrade can break a caller only if it built an
Eventwith an unkeyed composite literal, whichgo vet's composites check
reports.
v0.14.0
A module dependency report, derived from what registrations already carry.
go doc -all against 0.13.1 adds Scope.Modules and changes no signature;
an upgrade cannot break a caller.
Added
Scope.Modules()renders the modules registered into a scope and its
ancestors: what each provides, what it needs and which module serves it,
what it wraps, and which of its constructors are closures whose needs are
known only when they run. A need only a resolving scope can provide is
reported as owed, asValidatereports it, and a module's dependency on its
own services is left out. It is derived from what registrations already
carry, the module label and the parametersWiredeclares, so there is
nothing to declare twice. Keys are named by their package rather than their
import path, to read beside the module labels.
v0.13.1
Three defects reported in #35, each with a reproduction, each real. go doc -all against 0.13.0 is unchanged in di and dihttp; an upgrade cannot
break a caller, and every fix turns a panic, a false rejection or a lost
error into the behaviour the documentation already promised.
Fixed
WireandWrapaccepted a result merely assignable to the key, such as a
chan intfor a<-chan intkey or a[]bytefor a named slice type, and
then stored it as the constructor's own type, soGetpanicked asserting
it. The value is stored as the key's type now (#35).Validatereported a cycle where a valid graph visited oneScoped
binding in two scopes. A node on its path is now a binding in a holder, as
it is on a resolution path at run time, so the two instances are told
apart (#35).- A
Workerthat returned its own failure joined with the cancellation, as
errors.Join(ctx.Err(), err), had the failure dropped with the
cancellation, andStopreturned nil. Only an error that says nothing
beyond the cancellation is dropped now (#35).
v0.13.0
One route, one line. go doc -all against 0.12.0 leaves di untouched and
adds dihttp.Handle; nothing changes shape or behaviour, and an upgrade
cannot break a caller.
Added
dihttp.Handle(method)makes anhttp.Handlerthat resolves a handler
type from the request's scope and calls one of its methods, named by a
method expression:mux.Handle("GET /users/{id}", dihttp.Handle((*Users).Show)).
One type per resource with a method per route replaces a type, a
registration and a closure per route. The type isScopedwhen it needs
the request and a plain singleton when it does not;Handlefollows
either. Nothing existing changes; an upgrade cannot break a caller.
v0.12.0
The request-scope middleware becomes a dependency a server's constructor can
take, so an application needs no Provide closure to serve HTTP. go doc -all against 0.11.0 leaves di untouched and changes dihttp: Middleware
is now the type, NewMiddleware makes one, Module registers one. The rename
is the breaking change, and the reason for the minor bump.
Changed
dihttp.Middlewareis now the type,func(http.Handler) http.Handler, so
a server's constructor can take one as a parameter; the function that
makes one isdihttp.NewMiddleware. A calldihttp.Middleware(app)no
longer compiles: rename it, or take the middleware as a dependency.
Added
dihttp.Moduleregisters aMiddlewareover the scope it is applied to.
With it the guide's server is a wired constructor with its dependencies
declared, and the guide application uses noProvideclosure at all.
v0.11.0
Validate no longer needs an adapter to check request scopes: the caller
says what such a scope will hold. go doc -all against 0.10.0 adds Stub
and Provided, gives Scope.Validate a variadic parameter, which every
existing call satisfies, and removes dihttp.Validate, the one breaking
change.
Added
di.Provided[T]()makes aStub, a key the scope resolving aScoped
binding will provide.s.Validate(stubs...)then makes the check that scope
would make: what the stubs cover is satisfied, and what neither the scope
nor the stubs provide is an error rather thanOwed. Stubs apply to the
Scopedpath only; a singleton that would build aScopedservice in its
own scope still fails there, whatever a request scope holds.
Removed
dihttp.Validate, which opened a throwaway request scope to do what
app.Validate(di.Provided[*http.Request]())now does from the application
scope, without an HTTP-shaped helper in the way. Replace the call with that
expression and.Err().