@stackra/decorators
v2.0.0
Published
Centralised decorator library for the Stackra framework — generic factories for class-level, method-level, and property-level metadata decorators, plus domain wrappers (@OnEvent, @Cacheable, @Route, @Widget, ...). Feature packages import from here to stay
Maintainers
Readme
@stackra/decorators
Centralised decorator library for the Stackra framework.
Purpose
Feature packages that want to CONTRIBUTE a discoverable class or method (an
analytics provider, an event listener, a cache store, ...) should NOT need to
depend on the runtime package that OWNS the loader for that concern.
@stackra/decorators breaks the coupling.
- Each runtime package (e.g.
@stackra/cache,@stackra/events) owns the runtime that reads stamped metadata viadiscovery.getProvidersByMetadata(...). @stackra/decoratorsowns the stamping side — the class / method / property decorators that write the metadata.@stackra/contractsowns the metadata keys + options interfaces.
Result: a package like @stackra/analytics can ship a decorated provider by
importing from @stackra/decorators/<domain> without ever loading the loader
runtime that consumes it.
API — factories
Import from @stackra/decorators/core:
createDiscoverableClassDecorator(metadataKey, overrides?)— class-level, auto-applies@Injectable(), stampsoptionsundermetadataKey. Optional secondarydiscoveryKeyfor a boolean marker.createMetadataClassDecorator(metadataKey, overrides?)— same as above but does NOT apply@Injectable(). Used for DTOs and policies (@Setting,@CspPolicy,@Route).createDiscoverableMethodDecorator(methodMetadataKey, toOptions, overrides?)— method-level. Stamps per-method options on the prototype. OptionalclassDiscoveryKeymarker on the constructor so class-level discovery can find candidate classes.createMapAccumulatorPropertyDecorator(metadataKey, overrides?)— property-level. Maintains aMap<propertyKey, Entry>on the constructor with optionaltoEntrytransform andmergestrategy for repeated applications.createMetadataReader(metadataKey)— companion reader helper returning{ get, has, hasOwn }. Inheritance-aware.
Domain wrappers
Each domain barrel exports the specific decorators for that concern:
@stackra/decorators/cache—Cacheable,CacheEvict, ...@stackra/decorators/dashboard—Widget.@stackra/decorators/events—OnEvent.@stackra/decorators/http— HTTP-layer decorators.@stackra/decorators/logger—Reporter.@stackra/decorators/queue— queue processor decorators.@stackra/decorators/routing—Route,AsController.@stackra/decorators/sdui— SDUI component + layout stamps.
More domain wrappers land as their runtime packages get promoted.
Inheritance semantics
Metadata is stored via @vivtel/metadata on top of reflect-metadata. Standard
Reflect.getMetadata semantics apply:
- Class-level decorators: subclass inherits parent's stamp through the prototype chain. Re-decorating the subclass writes to the subclass's own metadata slot — child wins for that class, parent's stamp remains untouched.
- Method-level decorators: metadata is stamped on the
prototype[method]pair. If the subclass inherits the method (does NOT override), lookup walks the chain and finds the parent's stamp. If the subclass overrides + re-decorates, its own stamp wins for that method. - Property-level (Map-accumulator) decorators: the Map lives on the
constructor. Subclasses that re-decorate get their OWN Map; the parent's Map
is unaffected. Domain readers can merge across the chain if needed via
hasOwnMetadata+ prototype walk.
Why this package exists
Before: every feature package that wanted to contribute a discoverable class would import from the OWNING runtime package purely for the decorator, adding that runtime as a peer dep even when the feature package didn't need any of the runtime's services.
After: feature packages import from @stackra/decorators/<domain>. Their
dependency chain is contracts + container + decorators. The runtime
packages that OWN the loader — the ones that actually surface the discovery
- registry semantics — stay as peers of the CONSUMING app, not of every individual feature package.
