# @master4n/decorators

> AI-friendly TypeScript decorators for Node/backend apps. One self-documenting
> decorator replaces a block of boilerplate. Flagship: config &
> value injection. Backend/class-based code (not React function components).

## Install & setup

```
npm install @master4n/decorators
```

tsconfig.json (legacy decorators — same flags NestJS/Angular/TypeORM use):

```json
{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true } }
```

Rule for injection: put `@Configured` on the class. Then injection works under
any `target` / `useDefineForClassFields` setting. Without `@Configured`, injection
uses prototype accessors that only work when `useDefineForClassFields` is `false`.

## Config & value injection (flagship)

- `@Value(key: string, default?)` — value from config files (YAML/JSON via node-config).
  Required (throws `MissingConfigError`) when no default is passed.
- `@Env(name: string, default?)` — value from `process.env`, coerced to the type of
  the default (number, boolean, comma-split array). Required when no default.
- `@Secret(name: string, default?)` — like `@Env`; marks the property name as secret so
  `redact()` / `redactFormat()` (and the package logger) mask it. `getSecretKeys()` lists them.
- `@Config(path: string)` — inject a whole config subtree/object (required).
- `@Default(value)` — inject a literal constant.
- `@Configured` — class decorator; materializes the above as own instance properties
  at construction. Required values resolve eagerly → misconfig fails at startup.

Helpers: `loadEnv({ path?, override? })` loads a `.env` file into `process.env`
(zero-dependency; only fills unset vars by default). `@Configured` calls it once
automatically. `parseEnv(content)` parses `.env` text. `MissingConfigError` is the
thrown error type. `getSecretKeys()` returns `@Secret` property names.

## Secret redaction

- `redact(value, { keys?, mask?, maxDepth? })` — deep copy with sensitive values masked.
  Sensitive keys = `@Secret` property names ∪ `DEFAULT_SENSITIVE_KEYS` (password, token,
  apikey, authorization, jwt, ssn, ...) ∪ `keys`. Case/`_`/`-`-insensitive; handles nested
  objects, arrays, Maps/Sets, circular refs; passes Date/primitives through. Default mask
  `[REDACTED]`. Beyond `maxDepth` (default 12) values become `'[Truncated]'` — never raw, so
  deep secrets can't leak.
- `redactFormat({ ... })` — a winston format that masks sensitive log fields. The package
  logger already uses it; add it to your own `winston.format.combine(...)`.
- `DEFAULT_SENSITIVE_KEYS` — the built-in sensitive key list.

Note: `@Env`/`@Secret` read `process.env`. Node does not auto-load `.env` files;
`@Configured` auto-calls `loadEnv()`, or run `node --env-file=.env`, or call `loadEnv()`.

### Example

```ts
import { Configured, Value, Env, Secret } from '@master4n/decorators';

@Configured
class AppConfig {
  @Value('app.name')        name!: string;     // required, from config file
  @Env('PORT', 3000)        port!: number;     // "3000" -> 3000
  @Env('DEBUG', false)      debug!: boolean;    // "true" -> true
  @Secret('JWT_SECRET')     jwtSecret!: string; // required, redaction-tracked
}
```

## Validation & access decorators (throw on invalid input)

- `@NotNull` (method) — throws `ValidationError` if any argument is null/undefined.
  (BREAKING in 2.0.0: 1.x only logged.)
- `@ValidDate` (method) — throws `ValidationError` if the first arg is not a valid
  `{ DD, MM, YYYY }` date (undefined is allowed). Fixed in 2.0.0 (was a no-op in 1.x).
- `@Pattern(regex, { message?, coerce? })` (property) — only allows assigning values matching
  the regex; a non-matching assignment throws `ValidationError` and keeps the old value.
  null/undefined allowed. `coerce: true` tests `String(value)`. Use with `@Configured` for
  robustness under useDefineForClassFields: true.
- `@Min(n, { message? })`, `@Max(n, { message? })`, `@Range(min, max, { message? })` (property) —
  reject out-of-bounds assignments (throw `ValidationError`). Polymorphic: compares a number's
  value, or a string/array's length. null/undefined allowed. Compose with each other and
  `@Pattern` on the same property. Use with `@Configured` for modern class-field robustness.
- `@Email()`, `@URL()`, `@UUID()` (property) — format validation; throw `ValidationError`.
- `@Enum(values, { message? })` (property) — value must be one of `values`.
- `@NonEmpty({ message? })` (property) — rejects null/undefined/''/[] (implies presence).
- `@Integer()`, `@Positive()` (property) — number must be an integer / > 0.
- More value guards (property; skip null/undefined except @NotBlank): `@NotBlank()`,
  `@Size(min, max)`, `@Negative()`, `@PositiveOrZero()`, `@NegativeOrZero()`, `@Past()`,
  `@Future()`, `@PastOrPresent()`, `@FutureOrPresent()`, `@AssertTrue()`, `@AssertFalse()`,
  `@Digits(integer, fraction)`. All take an optional `{ message }` and compose with the others.

## Transform decorators (property; normalize value on assignment, before validators)

- `@Trim`, `@Lowercase`, `@Uppercase` — string normalization (non-strings pass through).
- `@Coerce('number'|'boolean'|'string')` — coerce assigned value to the type.
- `@Clamp(min, max)` — clamp an assigned number into [min, max].
- Transforms always run before validators regardless of decorator stacking order. Compose with
  `@Pattern`/`@Min`/`@Email`/etc. on the same property. Use `@Configured` for modern robustness.
- `@Role(...roles: string[])` (method) — throws `ForbiddenError` unless the principal has
  one of the roles. Configure via `setRoleResolver((ctx) => string[] | Promise<string[]>)`.
- `@Authorize(predicate, message?)` (method) — throws `ForbiddenError` unless `predicate(ctx)`
  is truthy. `ctx` is `{ instance, methodName, args }`. Predicate may be async.
- Error types: `ValidationError`, `ForbiddenError`.

## Utility decorators

- `@GenerateID` (property) — lazily assigns a UUIDv4 (node:crypto.randomUUID).
- `@Counter` (static property) — auto-incrementing counter on each read.
- `@Log(options?)` (method) — logs entry/exit. Options: `{ args?: boolean }` logs redacted
  arguments, `{ result?: boolean }` logs the redacted return value, `{ level? }` sets the log
  level, `{ redact? }` passes RedactOptions. Args/result are masked via `redact()`. Async-aware.
- `@Retry(attempts=3, { delayMs? })` (method) — retries on failure; sync and async.
- `@Memoize` (method) — caches results by JSON of args, per instance (never expires).
- `@Deprecated(message?)` (method) — logs a one-time deprecation warning.
- `@Measure` (method) — logs execution time; sync and async.

## Resilience & control flow (method decorators)

- `@Timeout(ms, { message? })` — async only; rejects with `TimeoutError` if not settled in ms.
- `@Once` — run once per instance; cache result forever (args ignored).
- `@Cache(ttlMs)` — memoize per instance keyed by args, expiring after ttlMs.
- `@Dedupe` — single-flight: identical concurrent async calls share one promise.
- `@Fallback(valueOrFn)` — on sync throw / async reject, return the fallback (fn gets the error).
- `@RateLimit(limit, intervalMs)` — throws/rejects `RateLimitError` past limit calls per window.
- `@Concurrency(max)` — cap concurrent async executions per instance; queue the rest (returns promise).
- `@CircuitBreaker({ failureThreshold=5, resetMs=30000 })` — open after N failures; fast-fail with
  `CircuitOpenError`; half-open trial after resetMs.
- `@Debounce(ms)` — void/fire-and-forget methods only; trailing-edge.
- `@Throttle(ms)` — void/fire-and-forget methods only; leading-edge.
- Errors: `TimeoutError`, `RateLimitError`, `CircuitOpenError`.
- ORDER MATTERS: stacks apply bottom-up; the top decorator runs first / sees the final outcome.
  Put recovery (`@Fallback`) OUTERMOST (top), above `@Retry`/`@CircuitBreaker`/`@Timeout` — placed
  inside them it swallows the error first and retries never happen.

## Model — data-class decorators

- `@ToString({ only?, exclude?, redact? })` (class) — adds toString(); redacts @Secret/sensitive
  fields.
- `@Equals(...keys?)` (class) — adds equals(other); same-constructor, field-wise ===.
- `@With` (class) — adds with(patch) -> shallow copy with overrides; preserves frozen-ness.
- `@Data` (class) — @ToString + equals() + with().
- `@Immutable` (class) — Object.freeze each instance (immutable value object). Don't combine with
  @Configured (freeze blocks post-construction mutation).
- `@Readonly` (field) — assignable once then throws ValidationError (like final).
- `@Synchronized` (method) — serialize concurrent async calls per instance (mutex; returns promise).
- `@Builder` (class) adds a runtime static builder(); `builder(Class)` is the typed standalone
  fluent builder: builder(User).name('a').age(5).build(). Types: ToStringOptions, BuilderOf<T>.

## Route — REST controllers (legacy decorators required)

Declarative Express routing. Decorate a class + methods + parameters, then call
`registerControllers(app, [controllerInstanceOrClass, ...])`. No express runtime dependency
(structural HttpApp/HttpRequest/HttpResponse types). Returned values -> res.json/send with the
status; thrown errors -> next(err); inject `@Res()` to own the response.

- Class: `@Controller(basePath='')` (alias `@RestController`).
- Methods: `@Get/@Post/@Put/@Patch/@Delete/@Options/@Head/@All(path='/')`
  (aliases `@GetMapping`…; `@RequestMapping(path, method?)`).
- Params (parameter decorators): `@Param(name?)`=`@PathVariable`, `@Query(name?)`=`@RequestParam`,
  `@Body(name?)`=`@RequestBody`, `@Header(name?)`=`@RequestHeader`, `@Cookie(name?)`,
  `@Req()`, `@Res()`, `@Next()`.
- Modifiers: `@HttpCode(code)`=`@ResponseStatus`, `@ContentType(t)`=`@Produces`,
  `@Redirect(url, status=302)`, `@Use(...middleware)` (class-level = all routes, method-level = one route).
- Wiring: `registerControllers(app, controllers)`. Types: HttpRequest, HttpResponse, HttpApp, RequestHandler.

## AI tools (the agent loop)

- `@Tool({ description, name?, parameters? })` (method) — register a method as an LLM-callable
  tool. `parameters` is an explicit JSON Schema (runtime types are erased, so it is NOT inferred).
  The method stays normally callable.
- `getTools()` — returns `[{ name, description, parameters }]`, ready for an LLM's tool list
  (OpenAI `tools`/`parameters`, Anthropic `input_schema`).
- `invokeTool(instance, name, args)` — dispatch a model tool call to the method on `instance`.
- `clearTools()` — clear the registry (tests). Types: `ToolOptions`, `ToolManifest`, `ToolParameters`.
- Tool names are process-global and must be unique (a duplicate overwrites the earlier entry).

## Agent power-ups (method decorators — wrap the methods an agent calls)

- `@Validate(check, { message? })` — `check(args)` returns boolean (or throws); falsy -> `ValidationError`
  before the method runs. Guards LLM-tool inputs.
- `@Guardrail(check, { retries?, message? })` — validate the (awaited) output; retry up to `retries`,
  else throw `GuardrailError`. Sync/async. Verifies model output.
- `@Idempotent(keyFn?)` — cache result by idempotency key (per instance); repeat calls return the
  stored result. No TTL (vs @Cache); persists past in-flight (vs @Dedupe); failed async not cached.
- `@Meter(name?)` — record calls/errors/timing; `getMetrics()` returns `{ [name]: { calls, errors,
  totalMs, avgMs } }`; `resetMetrics()` clears. Sync/async.

## Craft — class & method ergonomics

- `@Bind` (method) — auto-bind to instance; safe to pass as a detached callback.
- `@Lazy(factory)` (property) — compute once on first access, then cache. @Configured-safe.
- `@Sealed` (class) — Object.seal each instance (no add/remove props; values stay writable).
- `@Mixin(...sources)` (class) — copy members from objects/classes onto the class prototype.
- `@OnChange(handler)` (property) — fire handler(new, old, instance) on a real change; the first
  assignment initializes silently. @Configured-safe.

## Observability (method decorators)

- `@Trace({ args?=true, result?=false, redact? })` — structured entry/exit/error logging with a
  correlation id threaded through nested calls (AsyncLocalStorage); args/results redacted.
  `getTraceId()` returns the current scope's id for correlating your own logs.
- `@Audit(action?, { redact? })` — logs actor + action + redacted args. Configure the actor via
  `setAuditResolver((ctx) => string)` (ctx = { instance, methodName, args }).
- `@LogErrors({ redact? })` — logs errors (redacted args + stack) and rethrows (sync/async).

## Notes for agents

- Backend/class-based only. Decorators do not apply to React function components.
- Prefer `@Configured` + injection decorators over hand-written `process.env` / `config.get`
  blocks with manual coercion and defaults.
- Required values (no default) throw `MissingConfigError`; provide a default to make optional.
- Repository: https://github.com/Master4Novice/decorators · License: MIT
