Decorators catalog
Titan and its ecosystem ship a lot of decorators. This page is the single-source-of-truth lookup: what each decorator does, which package to import it from, and which decorators share a name across packages (so you don't accidentally import the wrong one).
Every entry below is checked against the packages' real exports by a script, not by hand — a hand check is true on the day it is made and decays silently afterwards:
node internal/website/scripts/check-titan-inventories.mjs .
It exits non-zero if this page names a decorator no package exports, or if the set the root re-exports stops being exactly the nine listed above.
Quick lookup by purpose
| You want to… | Reach for |
|---|---|
| Mark a class as DI-injectable | @Injectable() (core) |
| Expose a class as RPC service | @Service('name@version') (core) |
| Expose a method on the wire | @Public() (core) |
| Validate a method's args / return | @Validate(schema) (core) |
| Require authenticated caller | @RequireAuth() (titan-auth) |
| Skip auth for one method | @Public() (titan-auth — opts out of auth) |
| Memoise an idempotent method | @Cacheable() (titan-cache) |
| Throttle calls per key | @RateLimit({ key, limit }) (titan-ratelimit) |
| Run at a cron schedule | @Cron('0 * * * *') (titan-scheduler) |
| Run on an interval | @Interval(60_000) (titan-scheduler) |
| Subscribe to a typed event | @OnEvent('user.created') (titan-events) |
| Auto-emit an event from a method | @EmitEvent({ event: 'user.created' }) (titan-events) |
| Wrap a method in a distributed lock | @WithDistributedLock(key, ttl) (titan-lock) |
| Instrument a method with RPC counters + latency | @Metrics(metricName?) (titan-metrics) |
| Define a worker process class | @Process() (titan-pm) |
| Define a supervised hierarchy | @Supervisor() + @Child() (titan-pm) |
| Bind a class to a config subtree | @Configuration('cache') (config built-in) |
| Read one config value | @Config('cache.ttlMs', 60_000) (config built-in) |
| Apply RLS to a repository | @Policy(...) + @Allow/@Deny/@Filter (titan-database) |
| Soft-delete a repository's rows | @SoftDelete() (titan-database) |
Auto-add created_at/updated_at | @Timestamps() (titan-database) |
| Audit row changes | @Audit() (titan-database) |
Core decorators
@omnitron-dev/titanShips inside @omnitron-dev/titan. No additional install required.
Imported from @omnitron-dev/titan/decorators, which exports all of
them. The package root re-exports exactly nine — Service,
Injectable, Inject, Optional, Singleton, Transient,
Module, PostConstruct, PreDestroy — and nothing else, so
@Public, @Validate, @Timeout, @RateLimit, @Cache and the
rest below are not importable from @omnitron-dev/titan. Real
code follows this: every one of the 52 @Public imports in the DAOS
backends reads from '@omnitron-dev/titan/decorators'.
DI
| Decorator | Targets | Effect |
|---|---|---|
@Injectable(opts?) | class | Marks a class as a DI provider |
@Service(name) | class | Marks a class as a Netron RPC service |
@Inject(token) | parameter / property | Resolves a token from the container |
@Optional() | parameter / property | Allow undefined if the token isn't registered |
@Global() | class (on Module) | Makes the module's exports visible everywhere |
RPC surface
| Decorator | Targets | Effect |
|---|---|---|
@Public(opts?) | method / property | Exposes the method on the wire |
@Contract(class) | class | Binds a Zod-based input/output contract |
@Validate({input, output}) | method | Runs Zod schemas around the call |
@NoValidation() | method | Opts out of class-level validation |
@WithValidationOptions(opts) | class | Sets default validation behaviour for the class |
@ValidateInput(schema) | method | Input-only validation |
@ValidateOutput(schema) | method | Output-only validation |
The core
@Public()exposes; the titan-auth@Public()further marks the same method as auth-bypass. They compose:@Public() @RequireAuth()for an exposed-but-protected method is the common pattern.
Core also ships three lightweight composable annotations meant
to sit alongside @Public() — they write metadata that transports /
modules read, rather than implementing the behaviour themselves:
| Decorator | Targets | Effect |
|---|---|---|
@Auth(opts?) | method | Marks the method as requiring authentication |
@RateLimit(opts?) | method | Attaches rate-limit metadata to the method |
@Cache(opts?) | method | Attaches cache metadata to the method |
These share names with the satellite-package decorators (
titan-ratelimit.@RateLimit,titan-pm.@Cache, etc.). The core ones are bare metadata annotations; see the collision table below.
Lifecycle
| Decorator | Targets | Effect |
|---|---|---|
@PostConstruct() | method | Run after construction, before onInit |
@PreDestroy() | method | Run during onDestroy |
For full hooks (onInit, onStart, onStop, onDestroy)
implement the interfaces instead — they're more discoverable than
decorators. See Lifecycle reference.
Method utilities
Most utility decorators are method interceptors that compose freely with other decorators.
| Decorator | Effect |
|---|---|
@Memoize | Cache the result of a function in-memory by arg fingerprint |
@Retry({ attempts, delay }) | Retry on throw with linear delay |
@Retryable({...}) | More configurable retry (backoff curve, predicate) |
@Timeout({ ms }) | Reject if the method doesn't resolve within ms |
@Log({...}) | Log entry / exit / error |
@Monitor({...}) | Emit metric samples around the call |
@Deprecated({ message, version }) | Warn callers; metadata for tooling |
The core
@Timeoutclashes withtitan-scheduler.@Timeout— distinguish by import path (@omnitron-dev/titan/decoratorsvs@omnitron-dev/titan-scheduler).
Built-in module decorators
config
@omnitron-dev/titan/module/configShips inside @omnitron-dev/titan. No additional install required.
| Decorator | Targets | Effect |
|---|---|---|
@Config(path, default?) | property | Inject one config value at a dotted path |
@InjectConfig() | property | Inject the whole ConfigService |
@Configuration(prefix?) | class | Bind the class as a typed view of a config subtree |
@ConfigSchema(schema) | class | Class-level Zod schema for the bound subtree |
@ConfigWatch(path) | method | Method runs when the watched path changes |
@ConfigDefaults({...}) | class | Provide class-level defaults |
@ConfigProvider(name) | class | Custom config provider class |
@Configuration('cache')
class CacheConfig {
@Config('ttlMs', 60_000) ttlMs!: number;
@Config('maxItems', 1024) maxItems!: number;
}
logger
@omnitron-dev/titan/module/loggerShips inside @omnitron-dev/titan. No additional install required.
The logger module exports no decorators — instances are injected
via token / interface (LOGGER_SERVICE_TOKEN, LOGGER_TOKEN).
See logger.
Official module decorators
titan-auth
@omnitron-dev/titan-authMaintained by the Omnitron team. Independent npm package.
| Decorator | Targets | Effect |
|---|---|---|
@RequireAuth(opts?) | method | Caller must present a valid JWT |
@RequireServiceAuth() | method | Caller must hold the service role |
@RequireAdminAuth() | method | Caller must hold the admin role |
@RequireRole(roles[], message?) | method | Caller must hold at least one of the listed roles |
@Public() | class / method | Auth bypass — explicitly mark as anonymous-allowed |
Two
@Public()decorators exist. The core one exposes the method on Netron. The titan-auth one opts it out of authentication. Import path determines which:@omnitron-dev/titan/decorators(expose) vs@omnitron-dev/titan-auth(auth bypass).
titan-cache
@omnitron-dev/titan-cacheMaintained by the Omnitron team. Independent npm package.
| Decorator | Targets | Effect |
|---|---|---|
@Cacheable(opts?) | method | Memoise return value; key derived from args |
@CachePut(opts?) | method | Always run the method, but update cache with result |
@CacheInvalidate(opts?) | method | Remove cache entry / key pattern after method runs |
@CacheKey() | parameter | Mark one parameter as the cache key seed |
@InjectCacheService() | property | Inject the ICacheService directly |
@InjectCacheLogger() | property | Inject the cache-scoped logger |
titan-database
@omnitron-dev/titan-databaseMaintained by the Omnitron team. Independent npm package.
| Decorator | Targets | Effect |
|---|---|---|
@InjectConnection(name?) | parameter | Inject a named database connection |
@InjectDatabaseManager() | parameter | Inject the global DatabaseManager |
@SoftDelete(config?) | class | Apply Kysera soft-delete plugin |
@Timestamps(config?) | class | Apply Kysera timestamps plugin |
@Audit(config?) | class | Apply Kysera audit plugin |
@Policy(config?) | class | Declare RLS policy for the repository |
@Allow(rule) | method | RLS rule that ALLOWS access |
@Deny(rule) | method | RLS rule that DENIES access |
@Filter(filter?) | method | RLS row-level filter |
@BypassRLS() | method | Skip RLS for an admin-only operation |
@AutoTransactional(opts?) | method | Run the method in a transaction; commit on success |
titan-discovery
@omnitron-dev/titan-discoveryMaintained by the Omnitron team. Independent npm package.
titan-discovery does not export decorators — it integrates
transparently with Netron via the module's enableNetronIntegration
option. Service announce / lookup is automatic.
titan-events
@omnitron-dev/titan-eventsMaintained by the Omnitron team. Independent npm package.
| Decorator | Targets | Effect |
|---|---|---|
@OnEvent(event, opts?) | method | Subscribe to a named event |
@OnceEvent(event, opts?) | method | Subscribe once, then unsubscribe |
@OnAnyEvent(opts?) | method | Subscribe to every event (firehose — use with care) |
@OnModuleEvent(module, event, opts?) | method | Subscribe across module boundaries |
@EmitEvent({ event, mapResult?, mapError? }) | method | Emits <event>.success after a return, <event>.error on a throw |
@ScheduleEvent(event, opts?) | method | Emit on a schedule (cron-like, via this module's scheduler) |
@BatchEvents(event, opts?) | method | Batch handler — invoked once per batch |
@EventEmitter(opts?) | class | Marks the class as a typed event source |
titan-health
@omnitron-dev/titan-healthMaintained by the Omnitron team. Independent npm package.
titan-health doesn't export decorators — indicators are
registered as classes via forFeature([...]).
titan-lock
@omnitron-dev/titan-lockMaintained by the Omnitron team. Independent npm package.
| Decorator | Targets | Effect |
|---|---|---|
@WithDistributedLock(key, ttlMs?) | method | Acquire a distributed Redis lock around the call |
@Lock(key, ttlMs?) | method | Alias of @WithDistributedLock |
The decorator releases the lock in finally — even on throw.
titan-metrics
@omnitron-dev/titan-metricsMaintained by the Omnitron team. Independent npm package.
| Decorator | Targets | Effect |
|---|---|---|
@Metrics(metricName?) | method | Auto-track rpc_requests_total, rpc_request_duration_seconds, rpc_errors_total around the call (optional name prefix) |
titan-notifications
@omnitron-dev/titan-notificationsMaintained by the Omnitron team. Independent npm package.
| Decorator | Targets | Effect |
|---|---|---|
@OnNotification(pattern, opts?) | method | Worker-side handler for notifications matching a pattern |
titan-pm
@omnitron-dev/titan-pmMaintained by the Omnitron team. Independent npm package.
| Decorator | Targets | Effect |
|---|---|---|
@Process(opts?) | class | Marks the class as a spawnable worker process |
@Public(opts?) | method/property | Expose method to the process supervisor / IPC |
@RateLimit(opts) | method | Worker-runtime rate limiter (pm-internal) |
@Cache(opts?) | method | Worker-runtime cache (pm-internal) |
@Validate(opts) | method | Worker-runtime validation (pm-internal) |
@Trace() | method | Add tracing span around method |
@Metric(name?) | method | Emit a metric per call |
@Supervisor(opts?) | class | Class describing a supervised hierarchy |
@Child(opts?) | property | Declare a child within a supervisor |
@Workflow() | class | Workflow orchestration class |
@Stage(opts?) | method | A stage within a workflow |
@Compensate(stageName) | method | Compensating action if a stage fails |
@Actor(opts?) | class | Actor-style stateful entity |
@CircuitBreaker(opts) | method | Circuit-breaker around external calls |
@Idempotent({ key, ttl? }) | method | Idempotency key-based deduplication |
@HealthCheck(opts?) | method | Marks a method as the process's health check |
@OnShutdown(opts?) | method | Runs the method during graceful worker shutdown |
titan-pmre-exports several decorator names (@Public,@RateLimit,@Cache,@Validate) for its worker runtime. They operate within the worker process, not on the supervisor's Netron surface. Don't confuse them with the same-named decorators in@omnitron-dev/titan/titan-ratelimit/titan-cache.
A
@SelfHeal(action)decorator is defined in the package but is not re-exported from the@omnitron-dev/titan-pmbarrel — it isn't importable from the package root.
titan-ratelimit
@omnitron-dev/titan-ratelimitMaintained by the Omnitron team. Independent npm package.
| Decorator | Targets | Effect |
|---|---|---|
@RateLimit(opts?) | method | Apply rate limit to the method (uses key derived from opts) |
@Throttle(rps) | method | Convenience for "N requests per second" |
titan-redis
@omnitron-dev/titan-redisMaintained by the Omnitron team. Independent npm package.
titan-redis ships only injection decorators — @InjectRedis()
for the default client and @InjectRedisManager() for the manager
(plus @RedisCache / @RedisLock / @RedisRateLimit helpers). For
token-based injection use REDIS_MANAGER or
getRedisClientToken(name). See titan-redis.
titan-scheduler
@omnitron-dev/titan-schedulerMaintained by the Omnitron team. Independent npm package.
| Decorator | Targets | Effect |
|---|---|---|
@Cron(expression, opts?) | method | Run on a cron schedule |
@Interval(ms, opts?) | method | Run every ms milliseconds |
@Timeout(ms, opts?) | method | Run once after ms milliseconds |
@Schedulable() | class | Marks the class for scheduler discovery (when not auto-found) |
titan-scheduler.@Timeoutschedules a one-shot deferred call. Core@Timeoutaborts a method if it doesn't return in time. Different decorators with the same name — distinguish by import.
titan-telemetry-relay
@omnitron-dev/titan-telemetry-relayMaintained by the Omnitron team. Independent npm package.
No decorators — instantiate TelemetryRelayService and wire it
in directly.
Name collisions — which one to import
When the same decorator name lives in multiple packages, import path resolves the ambiguity. Quick table:
| Name | Available in | Default expectation |
|---|---|---|
@Public | @omnitron-dev/titan/decorators (expose) + titan-auth (auth bypass) + titan-pm (worker IPC) | Pick by purpose; compose if needed |
@Validate | @omnitron-dev/titan/decorators + titan-pm | Core for Netron methods; pm for workers |
@Timeout | @omnitron-dev/titan/decorators (abort) + titan-scheduler (one-shot) | Read the verb — abort vs defer |
@RateLimit | @omnitron-dev/titan/decorators (composable) + titan-ratelimit + titan-pm | Use titan-ratelimit for service methods |
@Cache | @omnitron-dev/titan/decorators (composable) + titan-pm (worker) | Compare with @Cacheable from titan-cache |
When in doubt, the import path is authoritative.
Composability rules
Decorators apply outside-in — outermost runs last. The framework order normally arranges:
@Public() // Netron exposure (outermost)
@RequireAuth() // auth check
@RateLimit({ ... }) // rate limit
@Validate({ input }) // input validation
@Metrics() // instrumentation
@WithDistributedLock(key) // lock
async create(input) {/*…*/}
The runtime order at call-time:
- enter Public → enter auth → enter rate limit → enter validate → enter metrics → enter lock → method body → exit lock → exit metrics → exit validate → exit rate limit → exit auth → exit Public
This is the only order that makes sense:
- Exposure outside auth (auth runs after the request enters the surface).
- Rate limit before validate (cheaper to reject early).
- Metrics around the lock (so you measure lock contention).
Anti-patterns
@Cacheableon a non-idempotent method. Cache returns the same value forever; the side-effect runs once. Use@CachePutor@CacheInvalidateif you have side-effects.@RateLimitwithout a per-caller key. All callers share one bucket. Include the user / IP / api-key in the key template.@WithDistributedLockwithout a TTL. A crashed holder pins the lock forever. Always set a TTL ≥ the longest credible work time.@OnEventhandler that throws. Throws don't propagate through the bus by default; the bus may log and continue. Wrap in try/catch if you need to ensure recovery.- Two
@Public()decorators on the same method. Each from a different package — they don't conflict, but the duplication is noise. Pick one (the one you import explicitly) or import a re-export alias.
See also
- Decorators / Index — framework-level guide
- Decorators / Lifecycle
- Decorators / Method traits
- Tokens & RPC reference — companion lookup for DI tokens
- Options patterns — module configuration recipes