Documentation
¶
Overview ¶
Package serviceregistry owns the framework-internal service registry used by controllers and modules.
Application code should use github.com/hydroan/gst/service instead. Keeping lookup and registry state in an internal package prevents external projects from mutating framework-owned service mappings.
The package-level API is intentionally small:
- Register registers a service by its type, the registration the public service.Register forwards to.
- RegisterInstance stores a concrete service instance for module/runtime registration.
- Resolve returns the service a controller should execute.
- Init fills loggers for services registered before logger setup.
Index ¶
- func Init() error
- func Key(phase apiaction.Phase, route string) string
- func OverridesHook(svc any, method string) bool
- func Register[S types.Service[M, REQ, RSP], M types.Model, REQ types.Request, ...](phase apiaction.Phase, route string)
- func RegisterInstance[M types.Model, REQ types.Request, RSP types.Response](phase apiaction.Phase, route string, svc types.Service[M, REQ, RSP])
- func Resolve[M types.Model, REQ types.Request, RSP types.Response](key string) types.Service[M, REQ, RSP]
- type Base
- func (Base[M, REQ, RSP]) Create(*types.ServiceContext, REQ) (RSP, error)
- func (Base[M, REQ, RSP]) CreateAfter(*types.ServiceContext, M) error
- func (Base[M, REQ, RSP]) CreateBefore(*types.ServiceContext, M) error
- func (Base[M, REQ, RSP]) CreateMany(*types.ServiceContext, REQ) (RSP, error)
- func (Base[M, REQ, RSP]) CreateManyAfter(*types.ServiceContext, ...M) error
- func (Base[M, REQ, RSP]) CreateManyBefore(*types.ServiceContext, ...M) error
- func (Base[M, REQ, RSP]) Delete(*types.ServiceContext, REQ) (RSP, error)
- func (Base[M, REQ, RSP]) DeleteAfter(*types.ServiceContext, M) error
- func (Base[M, REQ, RSP]) DeleteBefore(*types.ServiceContext, M) error
- func (Base[M, REQ, RSP]) DeleteMany(*types.ServiceContext, REQ) (RSP, error)
- func (Base[M, REQ, RSP]) DeleteManyAfter(*types.ServiceContext, ...M) error
- func (Base[M, REQ, RSP]) DeleteManyBefore(*types.ServiceContext, ...M) error
- func (Base[M, REQ, RSP]) Export(*types.ServiceContext, ...M) ([]byte, error)
- func (Base[M, REQ, RSP]) Filter(_ *types.ServiceContext, m M, opts types.QueryOptions) (M, types.QueryOptions, error)
- func (Base[M, REQ, RSP]) Get(*types.ServiceContext, REQ) (RSP, error)
- func (Base[M, REQ, RSP]) GetAfter(*types.ServiceContext, M) error
- func (Base[M, REQ, RSP]) GetBefore(*types.ServiceContext, M) error
- func (Base[M, REQ, RSP]) Import(*types.ServiceContext, io.Reader) ([]M, error)
- func (Base[M, REQ, RSP]) List(*types.ServiceContext, REQ) (RSP, error)
- func (Base[M, REQ, RSP]) ListAfter(*types.ServiceContext, *[]M) error
- func (Base[M, REQ, RSP]) ListBefore(*types.ServiceContext, *[]M) error
- func (Base[M, REQ, RSP]) Patch(*types.ServiceContext, REQ) (RSP, error)
- func (Base[M, REQ, RSP]) PatchAfter(*types.ServiceContext, M) error
- func (Base[M, REQ, RSP]) PatchBefore(*types.ServiceContext, M) error
- func (Base[M, REQ, RSP]) PatchMany(*types.ServiceContext, REQ) (RSP, error)
- func (Base[M, REQ, RSP]) PatchManyAfter(*types.ServiceContext, ...M) error
- func (Base[M, REQ, RSP]) PatchManyBefore(*types.ServiceContext, ...M) error
- func (Base[M, REQ, RSP]) QueryCursor(ctx *types.ServiceContext) (types.Cursor, error)
- func (Base[M, REQ, RSP]) QueryFilters(ctx *types.ServiceContext) ([]types.Filter, error)
- func (Base[M, REQ, RSP]) QueryModel(ctx *types.ServiceContext) (M, error)
- func (Base[M, REQ, RSP]) QueryOrders(ctx *types.ServiceContext) ([]types.Order, error)
- func (Base[M, REQ, RSP]) QueryPagination(ctx *types.ServiceContext) (page, size int)
- func (Base[M, REQ, RSP]) QueryPresentFields(ctx *types.ServiceContext) map[string]struct{}
- func (Base[M, REQ, RSP]) SSE(*types.ServiceContext) error
- func (Base[M, REQ, RSP]) Update(*types.ServiceContext, REQ) (RSP, error)
- func (Base[M, REQ, RSP]) UpdateAfter(*types.ServiceContext, M) error
- func (Base[M, REQ, RSP]) UpdateBefore(*types.ServiceContext, M) error
- func (Base[M, REQ, RSP]) UpdateMany(*types.ServiceContext, REQ) (RSP, error)
- func (Base[M, REQ, RSP]) UpdateManyAfter(*types.ServiceContext, ...M) error
- func (Base[M, REQ, RSP]) UpdateManyBefore(*types.ServiceContext, ...M) error
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Init ¶
func Init() error
Init injects logger.Service into the services registered before the logger was initialized, once per process; a service registered later gets it as it registers. Bootstrap runs it once the loggers exist.
func Key ¶
Key returns the registry key of the route and phase: the path the route is served at (see apiaction.APIPath) and the phase, so registration and resolution agree however either side spells the route, with or without the prefix or stray whitespace. A blank route, which RegisterInstance refuses, keys no service, not even one registered on the root of the prefix: the key of a handler built without a route resolves none.
The key deliberately carries no type information: Go type aliases collapse distinct request/response declarations into one type, so a type-derived key cannot tell two actions apart. The route is unique per action by HTTP routing rules, which makes route plus phase a collision-free identity.
func OverridesHook ¶
OverridesHook reports whether svc overrides the named hook beyond Base's no-op implementation. Only the methods Base declares are tracked; any other name, and a nil service, report true, so a caller gating work on the answer keeps that work rather than skipping it for a hook the detection does not know.
func Register ¶
func Register[S types.Service[M, REQ, RSP], M types.Model, REQ types.Request, RSP types.Response](phase apiaction.Phase, route string)
Register registers the service type S for the route and phase, the registration service.Register forwards to: S is normally a pointer to a struct type embedding Base, and a fresh pointer instance of it is stored. A pointer to any concrete type that satisfies the constraint implements the service interface, so only an interface type argument fails, with a panic: it names no service to dispatch to, and registering nothing in its place would leave the route on the built-in handling without a word.
func RegisterInstance ¶
func RegisterInstance[M types.Model, REQ types.Request, RSP types.Response](phase apiaction.Phase, route string, svc types.Service[M, REQ, RSP])
RegisterInstance registers a concrete service instance for the route and phase.
The route names the one the HTTP layer registers the matching handler under, spelled either way apiaction.APIPath accepts, because Key derives the registry key from the path the route is served at and the controller handlers resolve services through that key. An empty route panics.
Registering a second service under one route and phase panics: a silent overwrite would dispatch requests to the wrong service, which is exactly the failure mode the route-derived key exists to prevent.
RegisterInstance preserves any preconfigured fields on svc. If svc is a non-pointer value, the registry stores a pointer copy so controller lookups always work with pointer service instances.
func Resolve ¶
func Resolve[M types.Model, REQ types.Request, RSP types.Response](key string) types.Service[M, REQ, RSP]
Resolve returns the registered service for a key produced by Key.
The lookup deliberately stays per request: services registered after route registration remain visible, only the key construction is hoisted to route-registration time. When no service is registered under the key, a no-op Base service is returned so default CRUD hooks can continue without forcing application code to register services.
A service found under the key but failing the type assertion means the route and service registrations disagree about the service types — a wiring bug. It is reported at error level and degrades to the no-op Base instead of panicking mid-request.
Types ¶
type Base ¶
func (Base[M, REQ, RSP]) Create ¶
func (Base[M, REQ, RSP]) Create(*types.ServiceContext, REQ) (RSP, error)
func (Base[M, REQ, RSP]) CreateAfter ¶
func (Base[M, REQ, RSP]) CreateAfter(*types.ServiceContext, M) error
func (Base[M, REQ, RSP]) CreateBefore ¶
func (Base[M, REQ, RSP]) CreateBefore(*types.ServiceContext, M) error
func (Base[M, REQ, RSP]) CreateMany ¶
func (Base[M, REQ, RSP]) CreateMany(*types.ServiceContext, REQ) (RSP, error)
func (Base[M, REQ, RSP]) CreateManyAfter ¶
func (Base[M, REQ, RSP]) CreateManyAfter(*types.ServiceContext, ...M) error
func (Base[M, REQ, RSP]) CreateManyBefore ¶
func (Base[M, REQ, RSP]) CreateManyBefore(*types.ServiceContext, ...M) error
func (Base[M, REQ, RSP]) Delete ¶
func (Base[M, REQ, RSP]) Delete(*types.ServiceContext, REQ) (RSP, error)
func (Base[M, REQ, RSP]) DeleteAfter ¶
func (Base[M, REQ, RSP]) DeleteAfter(*types.ServiceContext, M) error
func (Base[M, REQ, RSP]) DeleteBefore ¶
func (Base[M, REQ, RSP]) DeleteBefore(*types.ServiceContext, M) error
func (Base[M, REQ, RSP]) DeleteMany ¶
func (Base[M, REQ, RSP]) DeleteMany(*types.ServiceContext, REQ) (RSP, error)
func (Base[M, REQ, RSP]) DeleteManyAfter ¶
func (Base[M, REQ, RSP]) DeleteManyAfter(*types.ServiceContext, ...M) error
func (Base[M, REQ, RSP]) DeleteManyBefore ¶
func (Base[M, REQ, RSP]) DeleteManyBefore(*types.ServiceContext, ...M) error
func (Base[M, REQ, RSP]) Export ¶
func (Base[M, REQ, RSP]) Export(*types.ServiceContext, ...M) ([]byte, error)
Export mirrors Import: an empty-bytes default would export an empty file instead of surfacing the missing service.
func (Base[M, REQ, RSP]) Filter ¶
func (Base[M, REQ, RSP]) Filter(_ *types.ServiceContext, m M, opts types.QueryOptions) (M, types.QueryOptions, error)
func (Base[M, REQ, RSP]) Get ¶
func (Base[M, REQ, RSP]) Get(*types.ServiceContext, REQ) (RSP, error)
func (Base[M, REQ, RSP]) GetAfter ¶
func (Base[M, REQ, RSP]) GetAfter(*types.ServiceContext, M) error
func (Base[M, REQ, RSP]) GetBefore ¶
func (Base[M, REQ, RSP]) GetBefore(*types.ServiceContext, M) error
func (Base[M, REQ, RSP]) Import ¶
Import has no built-in parsing behavior: the DSL requires every Import action to declare Service(), so reaching this default means the route was wired without its service implementation. Answering loudly beats an empty-slice default, which would silently import nothing and mask the wiring error.
func (Base[M, REQ, RSP]) List ¶
func (Base[M, REQ, RSP]) List(*types.ServiceContext, REQ) (RSP, error)
func (Base[M, REQ, RSP]) ListAfter ¶
func (Base[M, REQ, RSP]) ListAfter(*types.ServiceContext, *[]M) error
func (Base[M, REQ, RSP]) ListBefore ¶
func (Base[M, REQ, RSP]) ListBefore(*types.ServiceContext, *[]M) error
func (Base[M, REQ, RSP]) Patch ¶
func (Base[M, REQ, RSP]) Patch(*types.ServiceContext, REQ) (RSP, error)
func (Base[M, REQ, RSP]) PatchAfter ¶
func (Base[M, REQ, RSP]) PatchAfter(*types.ServiceContext, M) error
func (Base[M, REQ, RSP]) PatchBefore ¶
func (Base[M, REQ, RSP]) PatchBefore(*types.ServiceContext, M) error
func (Base[M, REQ, RSP]) PatchMany ¶
func (Base[M, REQ, RSP]) PatchMany(*types.ServiceContext, REQ) (RSP, error)
func (Base[M, REQ, RSP]) PatchManyAfter ¶
func (Base[M, REQ, RSP]) PatchManyAfter(*types.ServiceContext, ...M) error
func (Base[M, REQ, RSP]) PatchManyBefore ¶
func (Base[M, REQ, RSP]) PatchManyBefore(*types.ServiceContext, ...M) error
func (Base[M, REQ, RSP]) QueryCursor ¶
QueryCursor returns the cursor position of the request, ready to be passed to Database.WithCursor. Models that did not embed model.Cursor yield a zero cursor, which WithCursor treats as a no-op; a request without _cursor_value yields the feed's first page, ordered but unbounded, and one with _sort_by and no _cursor_value yields a zero cursor, a list the client sorts. An unknown cursor column, or a cursor value the column's Go type cannot represent, is a client error and is reported as such.
func (Base[M, REQ, RSP]) QueryFilters ¶
QueryFilters returns the field-level operator filters ("field[op]=value") of the request, plus the bare framework timestamp keys ("created_at", "updated_at"), which act as exact-match filters. The field must resolve to a filterable column of the model and the operator must be known; anything else is rejected so a mistyped filter can never silently widen the result set. Filters require the model to embed model.Query.
func (Base[M, REQ, RSP]) QueryModel ¶
func (Base[M, REQ, RSP]) QueryModel(ctx *types.ServiceContext) (M, error)
QueryModel returns a new model with its own query fields filled from the request, ready to be passed to Database.WithQuery.
The keys QueryFilters owns never reach the decoder, and neither do framework parameters the model cannot carry, nor, in an export, the export's own _format and _limit; every other key must map to a field of the model, so a mistyped filter name is reported instead of silently widening the result set. The model is returned even when decoding fails, so a caller that tolerates partial input can still use it.
func (Base[M, REQ, RSP]) QueryOrders ¶
QueryOrders returns the ORDER BY terms of the request, ready to be passed to Database.WithOrder. Models embedding model.Query take them from _sort_by; any other model yields no order. An unknown column or direction is a client error and is reported as such.
func (Base[M, REQ, RSP]) QueryPagination ¶
func (Base[M, REQ, RSP]) QueryPagination(ctx *types.ServiceContext) (page, size int)
QueryPagination returns the page and size arguments of the request, ready to be passed to Database.WithPagination. Models embedding model.Pagination take both from the request and models embedding model.Cursor take the size only; any other model keeps the framework's full-table safety limit. The returned page is always at least 1, so a service slicing in-memory pages or computing offsets itself can use it without normalizing again.
func (Base[M, REQ, RSP]) QueryPresentFields ¶
func (Base[M, REQ, RSP]) QueryPresentFields(ctx *types.ServiceContext) map[string]struct{}
QueryPresentFields returns the model filter keys the request provided explicitly, so the database layer keeps their zero values (false, 0) as real conditions instead of dropping them as unset.
func (Base[M, REQ, RSP]) SSE ¶
func (Base[M, REQ, RSP]) SSE(*types.ServiceContext) error
SSE has no default streaming behavior: the DSL requires every SSE action to declare Service(), so reaching this default means the route was wired without its service implementation, which is answered loudly instead of with a silently empty stream.
func (Base[M, REQ, RSP]) Update ¶
func (Base[M, REQ, RSP]) Update(*types.ServiceContext, REQ) (RSP, error)
func (Base[M, REQ, RSP]) UpdateAfter ¶
func (Base[M, REQ, RSP]) UpdateAfter(*types.ServiceContext, M) error
func (Base[M, REQ, RSP]) UpdateBefore ¶
func (Base[M, REQ, RSP]) UpdateBefore(*types.ServiceContext, M) error
func (Base[M, REQ, RSP]) UpdateMany ¶
func (Base[M, REQ, RSP]) UpdateMany(*types.ServiceContext, REQ) (RSP, error)
func (Base[M, REQ, RSP]) UpdateManyAfter ¶
func (Base[M, REQ, RSP]) UpdateManyAfter(*types.ServiceContext, ...M) error
func (Base[M, REQ, RSP]) UpdateManyBefore ¶
func (Base[M, REQ, RSP]) UpdateManyBefore(*types.ServiceContext, ...M) error