@master4n/decorators
Advanced tools
+213
| # @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 |
+989
-11
@@ -1,8 +0,134 @@ | ||
| /*** | ||
| * Decorator can set a class property value from YAML file | ||
| * @param ymlKey a key from yaml file. | ||
| * @param ymlValue a default direct value. | ||
| import winston from 'winston'; | ||
| /** | ||
| * Thrown when a required configuration value is missing and no default was | ||
| * provided. Surfaces at construction time (with `@Configured`) so that | ||
| * misconfiguration fails loud at startup instead of silently yielding | ||
| * `undefined` deep inside the app. | ||
| */ | ||
| declare function Value(ymlKey: string, ymlValue?: any): (target: any, propertyKey: string) => void; | ||
| declare class MissingConfigError extends Error { | ||
| constructor(message: string); | ||
| } | ||
| /** Names of properties decorated with `@Secret`, for log redaction. */ | ||
| declare function getSecretKeys(): readonly string[]; | ||
| /** | ||
| * Inject a value read from your config files (YAML/JSON via `node-config`). | ||
| * | ||
| * Replaces a manual `config.get(...)` + try/catch + default-fallback block. | ||
| * | ||
| * @param key dotted config path, e.g. `"db.url"`. | ||
| * @param fallback default used when the key is missing. If omitted entirely, | ||
| * a missing key throws {@link MissingConfigError} (fail loud at startup). | ||
| * | ||
| * @example | ||
| * \@Configured | ||
| * class DbConfig { | ||
| * \@Value('db.url', 'sqlite://memory') url!: string; | ||
| * \@Value('db.poolSize') poolSize!: number; // required: throws if absent | ||
| * } | ||
| */ | ||
| declare function Value(key: string, fallback?: unknown): (target: object, propertyKey: string | symbol) => void; | ||
| /** | ||
| * Inject a value from `process.env`, coerced to the type of the default. | ||
| * | ||
| * Replaces `const x = process.env.X; this.x = x ? Number(x) : 5432;` style code. | ||
| * | ||
| * @param name environment variable name, e.g. `"DB_PORT"`. | ||
| * @param fallback default used when the variable is unset. Its type drives | ||
| * coercion (number/boolean/array). If omitted, an unset variable throws | ||
| * {@link MissingConfigError}. | ||
| * | ||
| * @example | ||
| * \@Configured | ||
| * class Server { | ||
| * \@Env('PORT', 3000) port!: number; // "3000" -> 3000 | ||
| * \@Env('DEBUG', false) debug!: boolean; // "true" -> true | ||
| * } | ||
| */ | ||
| declare function Env(name: string, fallback?: unknown): (target: object, propertyKey: string | symbol) => void; | ||
| /** | ||
| * Like {@link Env}, but marks the property name as a secret so it is redacted by | ||
| * `redact()` and the `redactFormat()` winston format (used by this package's | ||
| * logger). The value is read from `process.env` exactly like `@Env`. | ||
| * | ||
| * @example | ||
| * \@Configured | ||
| * class Auth { | ||
| * \@Secret('JWT_SECRET') jwtSecret!: string; | ||
| * } | ||
| * // redact(new Auth()) -> { jwtSecret: '[REDACTED]' } | ||
| */ | ||
| declare function Secret(name: string, fallback?: unknown): (target: object, propertyKey: string | symbol) => void; | ||
| /** | ||
| * Inject a config subtree/object by path (required). Useful for grabbing a | ||
| * whole section, e.g. `@Config('redis') redis!: RedisOptions`. | ||
| */ | ||
| declare function Config(path: string): (target: object, propertyKey: string | symbol) => void; | ||
| /** | ||
| * Inject a constant/literal default into a property. Equivalent to assigning the | ||
| * value in a constructor, but declarative. | ||
| * | ||
| * @example | ||
| * \@Configured | ||
| * class Feature { | ||
| * \@Default(42) answer!: number; | ||
| * } | ||
| */ | ||
| declare function Default(value: unknown): (target: object, propertyKey: string | symbol) => void; | ||
| /** | ||
| * Class decorator that materializes every injection-decorated property as an | ||
| * **own** instance property after construction. This is the robust mode: it | ||
| * works whether the consumer compiles with `useDefineForClassFields` true or | ||
| * false, because the own property overwrites any field-initializer shadow. | ||
| * | ||
| * Required values are resolved eagerly here, so missing config throws at | ||
| * construction time (fail loud at startup) rather than on first access. | ||
| * | ||
| * @example | ||
| * \@Configured | ||
| * class AppConfig { | ||
| * \@Value('app.name') name!: string; | ||
| * \@Env('PORT', 3000) port!: number; | ||
| * } | ||
| */ | ||
| declare function Configured<T extends new (...args: any[]) => object>(ctor: T): T; | ||
| /** | ||
| * Common sensitive field names redacted by default, in addition to any | ||
| * properties marked with `@Secret`. Matching is case-insensitive and ignores | ||
| * `_`/`-` separators (so `apiKey`, `api_key`, `API-KEY` all match `apikey`). | ||
| */ | ||
| declare const DEFAULT_SENSITIVE_KEYS: readonly string[]; | ||
| interface RedactOptions { | ||
| /** Extra sensitive key names to redact (case/separator-insensitive). */ | ||
| keys?: string[]; | ||
| /** Replacement for redacted values. Default `'[REDACTED]'`. */ | ||
| mask?: string; | ||
| /** Max recursion depth; deeper values are replaced with `'[Truncated]'` | ||
| * (never passed through raw, so deep secrets can't leak). Default 12. */ | ||
| maxDepth?: number; | ||
| } | ||
| /** | ||
| * Return a deep copy of `value` with the values of sensitive keys replaced by a | ||
| * mask. Sensitive keys = `@Secret`-marked property names ∪ {@link | ||
| * DEFAULT_SENSITIVE_KEYS} ∪ `options.keys`. Handles nested objects/arrays and | ||
| * circular references; passes `Date`/`RegExp` and primitives through. | ||
| * | ||
| * @example | ||
| * logger.info('config', redact(appConfig)); // password/jwtSecret -> [REDACTED] | ||
| */ | ||
| declare function redact<T>(value: T, options?: RedactOptions): T; | ||
| /** | ||
| * A winston format that redacts sensitive fields from log metadata. Add it to | ||
| * your logger's `format.combine(...)` so structured fields like `password` or | ||
| * any `@Secret` property never reach your transports in clear text. | ||
| * | ||
| * The package's own logger already uses this. | ||
| * | ||
| * @example | ||
| * winston.createLogger({ format: winston.format.combine(redactFormat(), ...) }); | ||
| */ | ||
| declare function redactFormat(options?: RedactOptions): winston.Logform.Format; | ||
| /** | ||
| * Decorator can set a class property as UUIDv4 value | ||
@@ -12,9 +138,17 @@ */ | ||
| /** | ||
| * Decorator will allow defined and non null values only. | ||
| * Method decorator: rejects `null`/`undefined` arguments. | ||
| * | ||
| * Throws {@link ValidationError} naming the offending argument index. (As of | ||
| * 2.0.0 this throws; in 1.x it only logged. See KNOWN_ISSUES.md history.) | ||
| */ | ||
| declare function NotNull(target: any, key: string, descriptor: PropertyDescriptor): PropertyDescriptor; | ||
| /** | ||
| * Decorator for date validation. | ||
| * Method decorator: validates that the first argument is a valid | ||
| * `{ DD, MM, YYYY }` date object. Throws {@link ValidationError} when it is | ||
| * present but invalid (an `undefined` first argument is allowed). | ||
| * | ||
| * Fixed in 2.0.0: previously a no-op because it reassigned `target[key]` | ||
| * instead of returning the descriptor (see KNOWN_ISSUES.md). | ||
| */ | ||
| declare function ValidDate(target: any, key: string | symbol): void; | ||
| declare function ValidDate(target: any, key: string, descriptor: PropertyDescriptor): PropertyDescriptor; | ||
| /** | ||
@@ -25,7 +159,851 @@ * Decorator for static property of class. | ||
| declare function Counter(target: any, propertyKey: string): void; | ||
| interface LogOptions { | ||
| /** Also log the (redacted) arguments on entry. Default false. */ | ||
| args?: boolean; | ||
| /** Also log the (redacted) return value on exit. Default false. */ | ||
| result?: boolean; | ||
| /** Log level to use. Default 'info'. */ | ||
| level?: 'info' | 'debug' | 'verbose' | 'warn' | 'error'; | ||
| /** Redaction options applied to logged args/result (see `redact`). */ | ||
| redact?: RedactOptions; | ||
| } | ||
| /** | ||
| * Decorator logging in and out of method. | ||
| * Method decorator: logs entry and exit. With options it can also log the | ||
| * **redacted** arguments and/or return value — secrets (`@Secret` fields and | ||
| * {@link DEFAULT_SENSITIVE_KEYS}) are masked before logging. | ||
| * | ||
| * `@Log()` with no options keeps the original entry/exit-only behavior. | ||
| * | ||
| * @example | ||
| * class Api { | ||
| * \@Log({ args: true, result: true }) | ||
| * charge(card: { number: string; cvv: string }, amount: number) { ... } | ||
| * // logs: Entering charge args=[{"number":"...","cvv":"[REDACTED]"}, 100] | ||
| * } | ||
| */ | ||
| declare function Log(): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| declare function Log(options?: LogOptions): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| export { Counter, GenerateID, Log, NotNull, ValidDate, Value }; | ||
| /** Thrown by validation decorators (`@NotNull`, `@ValidDate`) on invalid input. */ | ||
| declare class ValidationError extends Error { | ||
| constructor(message: string); | ||
| } | ||
| /** Thrown by access decorators (`@Role`, `@Authorize`) when access is denied. */ | ||
| declare class ForbiddenError extends Error { | ||
| constructor(message: string); | ||
| } | ||
| /** Thrown by `@Timeout` when a method exceeds its time budget. */ | ||
| declare class TimeoutError extends Error { | ||
| constructor(message: string); | ||
| } | ||
| /** Thrown by `@RateLimit` when the call rate exceeds the configured limit. */ | ||
| declare class RateLimitError extends Error { | ||
| constructor(message: string); | ||
| } | ||
| /** Thrown by `@CircuitBreaker` while the circuit is open. */ | ||
| declare class CircuitOpenError extends Error { | ||
| constructor(message: string); | ||
| } | ||
| /** Thrown by `@Guardrail` when a method's output fails its validation. */ | ||
| declare class GuardrailError extends Error { | ||
| constructor(message: string); | ||
| } | ||
| type MethodDecorator = (target: any, methodName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| /** | ||
| * Reject an **async** method if it doesn't settle within `ms`. Returns a | ||
| * `Promise` that rejects with {@link TimeoutError}. Synchronous methods can't be | ||
| * interrupted in JS, so this only applies to promise-returning methods. | ||
| * | ||
| * @example | ||
| * class Api { | ||
| * \@Timeout(5000) | ||
| * async fetch() { ... } // rejects with TimeoutError after 5s | ||
| * } | ||
| */ | ||
| declare function Timeout(ms: number, options?: { | ||
| message?: string; | ||
| }): MethodDecorator; | ||
| /** | ||
| * Run the method at most once per instance; cache and return that first result | ||
| * forever. (Unlike `@Memoize`, arguments are ignored — a single slot.) | ||
| */ | ||
| declare function Once(_t: any, methodName: string, descriptor: PropertyDescriptor): PropertyDescriptor; | ||
| /** | ||
| * Memoize results with a time-to-live, per instance, keyed by the JSON of the | ||
| * arguments. (Use `@Memoize` for a permanent cache; `@Cache(ttl)` expires.) | ||
| */ | ||
| declare function Cache(ttlMs: number): MethodDecorator; | ||
| /** | ||
| * Coalesce concurrent identical async calls (single-flight). While a call with | ||
| * the same arguments is in flight, callers share the same promise instead of | ||
| * starting another. Great for de-duplicating bursts of identical requests. | ||
| */ | ||
| declare function Dedupe(_t: any, methodName: string, descriptor: PropertyDescriptor): PropertyDescriptor; | ||
| /** | ||
| * On error, return a fallback instead of throwing. `fallback` may be a value or | ||
| * a function `(error) => value`. Handles sync throws and async rejections. | ||
| * | ||
| * Place `@Fallback` **outermost** (top of the stack) so it catches only after | ||
| * `@Retry`/`@CircuitBreaker`/`@Timeout` have run. Putting it inside them swallows | ||
| * the error first, so those never see a failure and never retry/trip. | ||
| */ | ||
| declare function Fallback(fallback: unknown | ((error: unknown) => unknown)): MethodDecorator; | ||
| /** | ||
| * Throttle the call **rate**: allow at most `limit` calls per rolling | ||
| * `intervalMs`, per instance. Excess calls throw {@link RateLimitError}. | ||
| */ | ||
| declare function RateLimit(limit: number, intervalMs: number): MethodDecorator; | ||
| /** | ||
| * Limit concurrent executions of an **async** method to `max` per instance; | ||
| * queue the rest (FIFO). The decorated method always returns a promise. | ||
| */ | ||
| declare function Concurrency(max: number): MethodDecorator; | ||
| /** | ||
| * Circuit breaker: after `failureThreshold` consecutive failures, "open" the | ||
| * circuit and fail fast with {@link CircuitOpenError} for `resetMs`. After that | ||
| * one trial call is allowed (half-open); success closes the circuit. Per instance. | ||
| */ | ||
| declare function CircuitBreaker(options?: { | ||
| failureThreshold?: number; | ||
| resetMs?: number; | ||
| }): MethodDecorator; | ||
| /** | ||
| * Debounce a **void / fire-and-forget** method: collapse rapid calls and invoke | ||
| * once, `ms` after the last call (trailing edge). The return value is discarded, | ||
| * so use this only on methods whose result you don't consume (event handlers). | ||
| */ | ||
| declare function Debounce(ms: number): MethodDecorator; | ||
| /** | ||
| * Throttle a **void / fire-and-forget** method: invoke immediately, then ignore | ||
| * further calls for `ms` (leading edge). The return value is discarded. | ||
| */ | ||
| declare function Throttle(ms: number): MethodDecorator; | ||
| interface PatternOptions { | ||
| /** Custom error message thrown on a non-matching assignment. */ | ||
| message?: string; | ||
| /** | ||
| * Test `String(value)` instead of requiring a `string`. Lets you validate | ||
| * numbers and other stringifiable values against the pattern. Default false. | ||
| */ | ||
| coerce?: boolean; | ||
| } | ||
| /** | ||
| * Property decorator: only allows assigning values that match `regex`. A | ||
| * non-matching assignment throws {@link ValidationError}; the property keeps its | ||
| * previous value. `null`/`undefined` are allowed (treat the field as optional / | ||
| * clearable) — combine with your own required check if needed. | ||
| * | ||
| * Composes with `@Min`/`@Max`/`@Range` on the same property. Put `@Configured` | ||
| * on the class for this to work under any `useDefineForClassFields` setting | ||
| * (otherwise it needs `useDefineForClassFields: false`). | ||
| * | ||
| * @example | ||
| * \@Configured | ||
| * class User { | ||
| * \@Pattern(/^[^@\s]+@[^@\s]+\.[^@\s]+$/, { message: 'invalid email' }) | ||
| * email!: string; | ||
| * | ||
| * \@Pattern(/^\d{6}$/, { coerce: true }) // accepts 560001 or "560001" | ||
| * pincode!: string; | ||
| * } | ||
| */ | ||
| declare function Pattern(regex: RegExp, options?: PatternOptions): (target: object, propertyKey: string | symbol) => void; | ||
| interface ConstraintOptions { | ||
| /** Custom error message thrown when the constraint is violated. */ | ||
| message?: string; | ||
| } | ||
| /** | ||
| * Property decorator: rejects assignments below `min`. For numbers this is the | ||
| * value; for strings and arrays it is the length. `null`/`undefined` are | ||
| * allowed. Composes with `@Max`/`@Pattern`. Use with `@Configured` for | ||
| * robustness under any `useDefineForClassFields` setting. | ||
| * | ||
| * @example | ||
| * class User { | ||
| * \@Min(3) username!: string; // length >= 3 | ||
| * \@Min(18) age!: number; // value >= 18 | ||
| * } | ||
| */ | ||
| declare function Min(min: number, options?: ConstraintOptions): (target: object, propertyKey: string | symbol) => void; | ||
| /** | ||
| * Property decorator: rejects assignments above `max`. For numbers this is the | ||
| * value; for strings and arrays it is the length. `null`/`undefined` are | ||
| * allowed. Composes with `@Min`/`@Pattern`. | ||
| * | ||
| * @example | ||
| * class User { | ||
| * \@Max(20) username!: string; // length <= 20 | ||
| * \@Max(120) age!: number; // value <= 120 | ||
| * } | ||
| */ | ||
| declare function Max(max: number, options?: ConstraintOptions): (target: object, propertyKey: string | symbol) => void; | ||
| /** | ||
| * Property decorator: rejects assignments outside `[min, max]` (inclusive). For | ||
| * numbers this is the value; for strings and arrays it is the length. | ||
| * `null`/`undefined` are allowed. Equivalent to `@Min(min)` + `@Max(max)`. | ||
| * | ||
| * @example | ||
| * class User { | ||
| * \@Range(3, 20) username!: string; // length 3..20 | ||
| * \@Range(0, 100) score!: number; // value 0..100 | ||
| * } | ||
| */ | ||
| declare function Range(min: number, max: number, options?: ConstraintOptions): (target: object, propertyKey: string | symbol) => void; | ||
| /** | ||
| * Property decorator: trims whitespace from assigned strings. Non-strings and | ||
| * `null`/`undefined` pass through unchanged. Runs before validators, so e.g. | ||
| * `@Trim @Min(3)` checks the trimmed length. | ||
| */ | ||
| declare function Trim(target: object, propertyKey: string | symbol): void; | ||
| /** Property decorator: lowercases assigned strings. */ | ||
| declare function Lowercase(target: object, propertyKey: string | symbol): void; | ||
| /** Property decorator: uppercases assigned strings. */ | ||
| declare function Uppercase(target: object, propertyKey: string | symbol): void; | ||
| /** | ||
| * Property decorator: coerces assigned values to `type`. Useful at boundaries | ||
| * where everything arrives as a string (query params, env, form data). | ||
| * `null`/`undefined` pass through. | ||
| * | ||
| * @example | ||
| * class Q { | ||
| * \@Coerce('number') page!: number; // "2" -> 2 | ||
| * \@Coerce('boolean') verbose!: boolean; // "true" -> true | ||
| * } | ||
| */ | ||
| declare function Coerce(type: 'number' | 'boolean' | 'string'): (target: object, propertyKey: string | symbol) => void; | ||
| /** | ||
| * Property decorator: clamps an assigned number into `[min, max]`. Non-numbers | ||
| * and `null`/`undefined` pass through. | ||
| * | ||
| * @example | ||
| * class Settings { | ||
| * \@Clamp(0, 100) volume!: number; // 150 -> 100, -5 -> 0 | ||
| * } | ||
| */ | ||
| declare function Clamp(min: number, max: number): (target: object, propertyKey: string | symbol) => void; | ||
| interface MessageOption { | ||
| message?: string; | ||
| } | ||
| /** Property decorator: assigned value must be a valid email address. */ | ||
| declare function Email(options?: MessageOption): (target: object, propertyKey: string | symbol) => void; | ||
| /** Property decorator: assigned value must be a parseable URL. */ | ||
| declare function URL(options?: MessageOption): (target: object, propertyKey: string | symbol) => void; | ||
| /** Property decorator: assigned value must be a UUID (v1–v5). */ | ||
| declare function UUID(options?: MessageOption): (target: object, propertyKey: string | symbol) => void; | ||
| /** | ||
| * Property decorator: assigned value must be one of `allowed`. | ||
| * | ||
| * @example | ||
| * class Job { \@Enum(['queued', 'running', 'done']) status!: string; } | ||
| */ | ||
| declare function Enum<T>(allowed: readonly T[], options?: MessageOption): (target: object, propertyKey: string | symbol) => void; | ||
| /** | ||
| * Property decorator: rejects empty values — `null`, `undefined`, `''`, or `[]`. | ||
| * (Unlike most validators, this also rejects null/undefined: "non-empty" implies | ||
| * presence.) | ||
| */ | ||
| declare function NonEmpty(options?: MessageOption): (target: object, propertyKey: string | symbol) => void; | ||
| /** Property decorator: assigned number must be an integer. */ | ||
| declare function Integer(options?: MessageOption): (target: object, propertyKey: string | symbol) => void; | ||
| /** Property decorator: assigned number must be greater than zero. */ | ||
| declare function Positive(options?: MessageOption): (target: object, propertyKey: string | symbol) => void; | ||
| interface Msg { | ||
| message?: string; | ||
| } | ||
| /** String must contain at least one non-whitespace char (rejects null/blank). */ | ||
| declare function NotBlank(options?: Msg): (t: object, k: string | symbol) => void; | ||
| /** String/array length must be within `[min, max]`. */ | ||
| declare function Size(min: number, max: number, options?: Msg): (t: object, k: string | symbol) => void; | ||
| /** Number must be < 0. */ | ||
| declare const Negative: (options?: Msg) => (t: object, k: string | symbol) => void; | ||
| /** Number must be >= 0. */ | ||
| declare const PositiveOrZero: (options?: Msg) => (t: object, k: string | symbol) => void; | ||
| /** Number must be <= 0. */ | ||
| declare const NegativeOrZero: (options?: Msg) => (t: object, k: string | symbol) => void; | ||
| /** Date (or date string/number) must be strictly in the past. */ | ||
| declare const Past: (options?: Msg) => (t: object, k: string | symbol) => void; | ||
| /** Date must be strictly in the future. */ | ||
| declare const Future: (options?: Msg) => (t: object, k: string | symbol) => void; | ||
| /** Date must be in the past or now. */ | ||
| declare const PastOrPresent: (options?: Msg) => (t: object, k: string | symbol) => void; | ||
| /** Date must be now or in the future. */ | ||
| declare const FutureOrPresent: (options?: Msg) => (t: object, k: string | symbol) => void; | ||
| /** Value must be boolean `true`. */ | ||
| declare function AssertTrue(options?: Msg): (t: object, k: string | symbol) => void; | ||
| /** Value must be boolean `false`. */ | ||
| declare function AssertFalse(options?: Msg): (t: object, k: string | symbol) => void; | ||
| /** | ||
| * Number must have at most `integer` integer digits and `fraction` fractional | ||
| * digits. | ||
| */ | ||
| declare function Digits(integer: number, fraction: number, options?: Msg): (t: object, k: string | symbol) => void; | ||
| /** Context handed to resolvers/predicates so the app can locate the principal. */ | ||
| interface AccessContext { | ||
| /** The instance the method was called on (`this`). */ | ||
| instance: unknown; | ||
| /** The method name being guarded. */ | ||
| methodName: string; | ||
| /** The arguments the method was called with. */ | ||
| args: unknown[]; | ||
| } | ||
| type RoleResolver = (ctx: AccessContext) => string[] | Promise<string[]>; | ||
| /** | ||
| * Register how `@Role` discovers the current principal's roles. Call once at | ||
| * startup. The library is auth-agnostic: derive roles from `ctx.instance` | ||
| * (e.g. `this.request.user`), `ctx.args`, or any ambient context you keep. | ||
| * | ||
| * @example | ||
| * setRoleResolver((ctx) => (ctx.instance as any).user?.roles ?? []); | ||
| */ | ||
| declare function setRoleResolver(resolver: RoleResolver): void; | ||
| /** | ||
| * Method decorator: allows the call only if the principal has at least one of | ||
| * the given roles. Throws {@link ForbiddenError} otherwise. Requires | ||
| * {@link setRoleResolver} to be configured. | ||
| * | ||
| * Works with sync or async resolvers; with an async resolver the guarded call | ||
| * resolves to a promise. | ||
| * | ||
| * @example | ||
| * class AdminApi { | ||
| * \@Role('admin', 'owner') | ||
| * deleteUser(id: string) { ... } | ||
| * } | ||
| */ | ||
| declare function Role(...allowed: string[]): (_target: any, methodName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| /** | ||
| * Method decorator: allows the call only if `predicate(ctx)` is truthy. A | ||
| * flexible escape hatch for permission checks, ownership checks, feature flags, | ||
| * etc. Throws {@link ForbiddenError} when the predicate is falsy. | ||
| * | ||
| * @example | ||
| * class DocApi { | ||
| * \@Authorize((ctx) => (ctx.instance as any).user?.can('edit')) | ||
| * edit(docId: string) { ... } | ||
| * } | ||
| */ | ||
| declare function Authorize(predicate: (ctx: AccessContext) => boolean | Promise<boolean>, message?: string): (_target: any, methodName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| /** | ||
| * Method decorator: retry the method on failure, up to `attempts` total tries. | ||
| * Works for sync and async (promise-returning) methods. `delayMs` (async only) | ||
| * waits between attempts. Re-throws the last error if all attempts fail. | ||
| * | ||
| * @example | ||
| * class Api { | ||
| * \@Retry(3, { delayMs: 200 }) | ||
| * async fetch() { ... } | ||
| * } | ||
| */ | ||
| declare function Retry(attempts?: number, options?: { | ||
| delayMs?: number; | ||
| }): (_target: any, _methodName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| /** | ||
| * Method decorator: memoize results keyed by the JSON of the arguments, per | ||
| * instance. Removes hand-rolled caching maps. Best for pure, cheap-to-key methods. | ||
| * | ||
| * @example | ||
| * class Calc { | ||
| * \@Memoize | ||
| * fib(n: number): number { ... } | ||
| * } | ||
| */ | ||
| declare function Memoize(_target: any, _methodName: string, descriptor: PropertyDescriptor): PropertyDescriptor; | ||
| /** | ||
| * Method decorator: log a one-time deprecation warning the first time the | ||
| * method is called. | ||
| * | ||
| * @example | ||
| * class Api { | ||
| * \@Deprecated('Use fetchV2() instead.') | ||
| * fetch() { ... } | ||
| * } | ||
| */ | ||
| declare function Deprecated(message?: string): (_target: any, methodName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| /** | ||
| * Method decorator: log how long the method takes. Handles sync and async | ||
| * methods (async is measured to settlement). | ||
| * | ||
| * @example | ||
| * class Job { | ||
| * \@Measure | ||
| * run() { ... } | ||
| * } | ||
| */ | ||
| declare function Measure(_target: any, methodName: string, descriptor: PropertyDescriptor): PropertyDescriptor; | ||
| /** The correlation id of the current `@Trace` scope, if any. */ | ||
| declare function getTraceId(): string | undefined; | ||
| interface TraceOptions { | ||
| /** Log the (redacted) arguments on entry. Default true. */ | ||
| args?: boolean; | ||
| /** Log the (redacted) result on exit. Default false. */ | ||
| result?: boolean; | ||
| /** Redaction options for logged args/result. */ | ||
| redact?: RedactOptions; | ||
| } | ||
| /** | ||
| * Method decorator: structured entry/exit/error tracing with a **correlation id** | ||
| * threaded through nested calls (via AsyncLocalStorage). All nested `@Trace`d | ||
| * calls share the outermost trace id, so an agent can follow one request through | ||
| * the call tree. Args/results are redacted. Use `getTraceId()` to tag your own | ||
| * logs with the same id. | ||
| * | ||
| * @example | ||
| * class Service { | ||
| * \@Trace({ result: true }) | ||
| * async handle(req: Request) { ... } | ||
| * } | ||
| * // [3f9a1c2b] -> handle args=[...] | ||
| * // [3f9a1c2b] <- handle (12ms) result=... | ||
| * | ||
| * @remarks Redaction is **key-based**: object fields named like secrets | ||
| * (`@Secret` names + {@link DEFAULT_SENSITIVE_KEYS}) are masked. Positional | ||
| * **primitive** arguments (e.g. a raw token string) and **error messages** are | ||
| * logged as-is — do not pass raw secrets positionally, and avoid embedding them | ||
| * in thrown error messages. Set `args: false` to omit argument logging entirely. | ||
| */ | ||
| declare function Trace(options?: TraceOptions): (_t: any, methodName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| interface AuditContext { | ||
| instance: unknown; | ||
| methodName: string; | ||
| args: unknown[]; | ||
| } | ||
| type AuditResolver = (ctx: AuditContext) => string; | ||
| /** | ||
| * Register how `@Audit` resolves the acting principal (the "who"). Without it, | ||
| * audited actions are logged with actor `unknown`. | ||
| * | ||
| * @example | ||
| * setAuditResolver((ctx) => (ctx.instance as any).user?.id ?? 'system'); | ||
| */ | ||
| declare function setAuditResolver(resolver: AuditResolver): void; | ||
| /** | ||
| * Method decorator: emit an audit log line (actor, action, redacted args) when | ||
| * the method is called. Provide a `setAuditResolver` to capture *who*. | ||
| * | ||
| * @example | ||
| * class Admin { | ||
| * \@Audit('user.delete') | ||
| * deleteUser(id: string) { ... } | ||
| * } | ||
| * | ||
| * @remarks Argument redaction is key-based (object fields only); positional | ||
| * primitive arguments are logged as-is — don't pass raw secrets positionally. | ||
| */ | ||
| declare function Audit(action?: string, options?: { | ||
| redact?: RedactOptions; | ||
| }): (_t: any, methodName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| /** | ||
| * Method decorator: log errors (with redacted args and the stack) and **rethrow** | ||
| * — observability without swallowing. Handles sync and async. Pair with | ||
| * `@Fallback` if you also want to recover. | ||
| * | ||
| * @remarks Argument redaction is key-based (object fields only). The error | ||
| * **stack/message is logged as-is** (its diagnostic value depends on it) — if | ||
| * your code may put secrets in error messages, scrub them at the throw site. | ||
| */ | ||
| declare function LogErrors(options?: { | ||
| redact?: RedactOptions; | ||
| }): (_t: any, methodName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| /** | ||
| * Decorator-based REST controllers for Express (and any Express-compatible | ||
| * router). Decorate a class with `@Controller`, its methods with `@Get`/`@Post`/…, | ||
| * and their parameters with `@Param`/`@Query`/`@Body`/… — then wire everything | ||
| * into your app with {@link registerControllers}. | ||
| * | ||
| * Framework-agnostic: it depends only on the structural shape of Express's | ||
| * `app`, `req`, and `res`, so there is no `express` runtime dependency. | ||
| */ | ||
| /** Minimal Express-compatible request shape. */ | ||
| interface HttpRequest { | ||
| params: Record<string, string>; | ||
| query: Record<string, unknown>; | ||
| body: unknown; | ||
| headers: Record<string, unknown>; | ||
| cookies?: Record<string, unknown>; | ||
| [key: string]: unknown; | ||
| } | ||
| /** Minimal Express-compatible response shape. */ | ||
| interface HttpResponse { | ||
| status(code: number): HttpResponse; | ||
| set(field: string, value: string): HttpResponse; | ||
| json(body: unknown): unknown; | ||
| send(body: unknown): unknown; | ||
| end(): unknown; | ||
| redirect(status: number, url: string): unknown; | ||
| headersSent?: boolean; | ||
| [key: string]: unknown; | ||
| } | ||
| type RequestHandler = (req: HttpRequest, res: HttpResponse, next: (error?: unknown) => void) => unknown; | ||
| /** Minimal Express-compatible app/router shape (`app.get(path, ...handlers)`). */ | ||
| type HttpApp = Record<string, (path: string, ...handlers: RequestHandler[]) => unknown>; | ||
| /** | ||
| * Marks a class as a REST controller with an optional base path. Alias: | ||
| * `@RestController`. | ||
| * | ||
| * @example | ||
| * \@Controller('/users') | ||
| * class UserController { ... } | ||
| */ | ||
| declare function Controller(basePath?: string): (ctor: new (...args: any[]) => object) => void; | ||
| /** Alias for {@link Controller}. */ | ||
| declare const RestController: typeof Controller; | ||
| /** Route an HTTP GET. Alias: `@GetMapping`. */ | ||
| declare const Get: (path?: string) => (target: object, handlerName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| /** Route an HTTP POST. Alias: `@PostMapping`. */ | ||
| declare const Post: (path?: string) => (target: object, handlerName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| /** Route an HTTP PUT. Alias: `@PutMapping`. */ | ||
| declare const Put: (path?: string) => (target: object, handlerName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| /** Route an HTTP PATCH. Alias: `@PatchMapping`. */ | ||
| declare const Patch: (path?: string) => (target: object, handlerName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| /** Route an HTTP DELETE. Alias: `@DeleteMapping`. */ | ||
| declare const Delete: (path?: string) => (target: object, handlerName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| /** Route an HTTP OPTIONS. */ | ||
| declare const Options: (path?: string) => (target: object, handlerName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| /** Route an HTTP HEAD. */ | ||
| declare const Head: (path?: string) => (target: object, handlerName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| /** Route all HTTP methods for the path. */ | ||
| declare const All: (path?: string) => (target: object, handlerName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| declare const GetMapping: (path?: string) => (target: object, handlerName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| declare const PostMapping: (path?: string) => (target: object, handlerName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| declare const PutMapping: (path?: string) => (target: object, handlerName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| declare const PatchMapping: (path?: string) => (target: object, handlerName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| declare const DeleteMapping: (path?: string) => (target: object, handlerName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| /** | ||
| * `@RequestMapping(path, method?)` — defaults to matching all | ||
| * methods when `method` is omitted. | ||
| */ | ||
| declare function RequestMapping(path?: string, method?: string): (target: object, handlerName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| /** Inject a route path variable (`req.params[name]`, or all params). Alias: `@PathVariable`. */ | ||
| declare const Param: (name?: string) => (target: object, handlerName: string, index: number) => void; | ||
| /** Inject a query-string value (`req.query[name]`, or all). Alias: `@RequestParam`. */ | ||
| declare const Query: (name?: string) => (target: object, handlerName: string, index: number) => void; | ||
| /** Inject the request body (`req.body`, or `req.body[name]`). Alias: `@RequestBody`. */ | ||
| declare const Body: (name?: string) => (target: object, handlerName: string, index: number) => void; | ||
| /** Inject a request header (`req.headers[name]`, or all). Alias: `@RequestHeader`. */ | ||
| declare const Header: (name?: string) => (target: object, handlerName: string, index: number) => void; | ||
| /** Inject a cookie (`req.cookies[name]`, or all). */ | ||
| declare const Cookie: (name?: string) => (target: object, handlerName: string, index: number) => void; | ||
| /** Inject the raw request object. */ | ||
| declare const Req: (name?: string) => (target: object, handlerName: string, index: number) => void; | ||
| /** Inject the raw response object. When used, you manage the response yourself. */ | ||
| declare const Res: (name?: string) => (target: object, handlerName: string, index: number) => void; | ||
| /** Inject Express's `next` function. */ | ||
| declare const Next: (name?: string) => (target: object, handlerName: string, index: number) => void; | ||
| declare const PathVariable: (name?: string) => (target: object, handlerName: string, index: number) => void; | ||
| declare const RequestParam: (name?: string) => (target: object, handlerName: string, index: number) => void; | ||
| declare const RequestBody: (name?: string) => (target: object, handlerName: string, index: number) => void; | ||
| declare const RequestHeader: (name?: string) => (target: object, handlerName: string, index: number) => void; | ||
| /** Set the success HTTP status code for a route. Alias: `@ResponseStatus`. */ | ||
| declare function HttpCode(code: number): (target: object, handlerName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| declare const ResponseStatus: typeof HttpCode; | ||
| /** Set the response `Content-Type`. Alias: `@Produces`. */ | ||
| declare function ContentType(type: string): (target: object, handlerName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| declare const Produces: typeof ContentType; | ||
| /** Redirect to `url` (default 302) instead of returning a body. */ | ||
| declare function Redirect(url: string, status?: number): (target: object, handlerName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| /** | ||
| * Attach Express middleware. As a **class** decorator it applies to every route; | ||
| * as a **method** decorator it applies to that route only. | ||
| * | ||
| * @example | ||
| * \@Use(authMiddleware) | ||
| * \@Controller('/admin') | ||
| * class AdminController { | ||
| * \@Use(rateLimitMiddleware) | ||
| * \@Get('/stats') stats() { ... } | ||
| * } | ||
| */ | ||
| declare function Use(...middleware: RequestHandler[]): (target: any, handlerName?: string, _descriptor?: PropertyDescriptor) => void; | ||
| /** | ||
| * Wire decorated controllers into an Express-compatible `app` (or `Router`). | ||
| * Accepts controller instances or zero-arg classes. | ||
| * | ||
| * @example | ||
| * const app = express(); | ||
| * app.use(express.json()); | ||
| * registerControllers(app, [new UserController()]); | ||
| */ | ||
| declare function registerControllers(app: HttpApp, controllers: Array<object | (new () => object)>): void; | ||
| interface ToStringOptions { | ||
| /** Only include these fields. */ | ||
| only?: string[]; | ||
| /** Exclude these fields. */ | ||
| exclude?: string[]; | ||
| /** Redaction options for sensitive fields (always redacted via `redact`). */ | ||
| redact?: RedactOptions; | ||
| } | ||
| /** | ||
| * Class decorator: adds a `toString()` that lists the instance's fields — | ||
| * **with secrets redacted** (`@Secret` names + {@link DEFAULT_SENSITIVE_KEYS}), | ||
| * which plain serialization does not do. | ||
| * | ||
| * @example | ||
| * \@ToString() | ||
| * class User { name = 'a'; password = 'x'; } | ||
| * String(new User()); // "User(name=a, password=[REDACTED])" | ||
| */ | ||
| declare function ToString(options?: ToStringOptions): (ctor: new (...args: any[]) => object) => void; | ||
| /** | ||
| * Class decorator: adds `equals(other)` comparing fields (all own fields, or the | ||
| * given `keys`). Same-constructor, shallow `===` per field. | ||
| */ | ||
| declare function Equals(...keys: string[]): (ctor: new (...args: any[]) => object) => void; | ||
| /** | ||
| * Class decorator: adds `with(patch)` returning a shallow copy with `patch` | ||
| * applied — ideal for immutable updates. If the source is frozen | ||
| * (`@Immutable`), the copy is frozen too. | ||
| * | ||
| * @example | ||
| * const next = user.with({ name: 'b' }); | ||
| */ | ||
| declare function With(ctor: new (...args: any[]) => object): void; | ||
| /** | ||
| * Class decorator: `@Data` — `@ToString` + `equals()` + `with()`. | ||
| */ | ||
| declare function Data(ctor: new (...args: any[]) => object): void; | ||
| /** | ||
| * Class decorator: freeze every instance after construction (`Object.freeze`), | ||
| * making it immutable. Pair with `@With` for copy-on-write. | ||
| * | ||
| * Note: don't combine with decorators that mutate the instance after | ||
| * construction (e.g. `@Configured`) — freezing blocks them. Pairs cleanly with | ||
| * `@ToString`/`@Equals`/`@With`. | ||
| */ | ||
| declare function Immutable<T extends new (...args: any[]) => object>(ctor: T): T; | ||
| /** | ||
| * Field decorator: the property may be assigned once (e.g. in the constructor); | ||
| * any later reassignment throws {@link ValidationError} — like a `final` field. | ||
| * Use with `@Configured` for robustness under modern class-field semantics. | ||
| */ | ||
| declare function Readonly(target: object, propertyKey: string | symbol): void; | ||
| /** | ||
| * Method decorator: serialize concurrent **async** calls per instance — each | ||
| * call waits for the previous to settle (a mutex). The | ||
| * decorated method returns a promise. | ||
| */ | ||
| declare function Synchronized(_t: any, _methodName: string, descriptor: PropertyDescriptor): PropertyDescriptor; | ||
| /** A typed fluent builder for `T`. */ | ||
| type BuilderOf<T> = { | ||
| [K in keyof T]-?: (value: T[K]) => BuilderOf<T>; | ||
| } & { | ||
| build(): T; | ||
| }; | ||
| /** | ||
| * Typed fluent builder for a class — `builder(User).name('a').age(5).build()`. | ||
| * This needs no codegen and is fully typed via {@link BuilderOf}. | ||
| * (`@Builder` also adds a runtime `.builder()` static for JS callers.) | ||
| */ | ||
| declare function builder<T extends object>(ctor: new () => T): BuilderOf<T>; | ||
| /** | ||
| * Class decorator: adds a static `builder()` returning a fluent builder. For | ||
| * full typing in TypeScript, prefer the standalone {@link builder} function | ||
| * (legacy class decorators can't augment the static type). | ||
| */ | ||
| declare function Builder<T extends new () => object>(ctor: T): T; | ||
| /** | ||
| * A JSON-Schema-style description of a tool's input. Shaped to drop straight | ||
| * into LLM tool/function-calling APIs (OpenAI `parameters`, Anthropic | ||
| * `input_schema`). | ||
| */ | ||
| interface ToolParameters { | ||
| type: 'object'; | ||
| properties?: Record<string, unknown>; | ||
| required?: string[]; | ||
| [key: string]: unknown; | ||
| } | ||
| interface ToolOptions { | ||
| /** What the tool does — shown to the LLM. Required. */ | ||
| description: string; | ||
| /** Tool name exposed to the LLM. Defaults to the method name. */ | ||
| name?: string; | ||
| /** | ||
| * Explicit input schema. **Not** inferred from the method signature (TypeScript | ||
| * parameter types are erased at runtime), so provide it yourself. | ||
| */ | ||
| parameters?: ToolParameters; | ||
| } | ||
| /** The LLM-facing manifest entry for a registered tool. */ | ||
| interface ToolManifest { | ||
| name: string; | ||
| description: string; | ||
| parameters: ToolParameters; | ||
| } | ||
| /** | ||
| * Mark a method as an **AI tool** an agent/LLM can call. Registers its name, | ||
| * description, and (explicit) input schema; the method itself is unchanged and | ||
| * still callable normally. Use {@link getTools} to build the LLM tool list and | ||
| * {@link invokeTool} to dispatch a tool call back to the method. | ||
| * | ||
| * The method should accept a single arguments object matching `parameters`. | ||
| * | ||
| * Tool names are registered in a **process-global** registry and must be unique: | ||
| * a duplicate name (including two classes relying on the same default method | ||
| * name) overwrites the earlier entry, so `invokeTool` would dispatch to the | ||
| * last-registered method. Give colliding tools explicit, unique `name`s. | ||
| * | ||
| * @example | ||
| * class Weather { | ||
| * \@Tool({ | ||
| * description: 'Get the current temperature for a city', | ||
| * parameters: { | ||
| * type: 'object', | ||
| * properties: { city: { type: 'string' } }, | ||
| * required: ['city'], | ||
| * }, | ||
| * }) | ||
| * getTemperature(args: { city: string }) { ... } | ||
| * } | ||
| * | ||
| * // const tools = getTools(); // -> pass to the LLM | ||
| * // invokeTool(new Weather(), 'getTemperature', { city: 'Pune' }); | ||
| */ | ||
| declare function Tool(options: ToolOptions): (_t: any, methodName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| /** | ||
| * The manifest of all registered `@Tool`s — `{ name, description, parameters }` | ||
| * — ready to pass to an LLM's tool/function-calling API. | ||
| */ | ||
| declare function getTools(): ToolManifest[]; | ||
| /** | ||
| * Dispatch a tool call to its method on `instance`, passing `args` as the single | ||
| * argument. Throws if the tool name is unknown or the method is missing. | ||
| * | ||
| * @example | ||
| * // From an LLM tool_call: | ||
| * const result = invokeTool(service, call.name, call.arguments); | ||
| */ | ||
| declare function invokeTool(instance: any, name: string, args?: unknown): unknown; | ||
| /** Clear the tool registry (primarily for tests). */ | ||
| declare function clearTools(): void; | ||
| /** | ||
| * Method decorator: validate the **arguments** before the method runs. `check` | ||
| * receives the argument array and returns a boolean (or throws); a falsy result | ||
| * throws {@link ValidationError}. Perfect for guarding LLM-tool inputs. | ||
| * | ||
| * @example | ||
| * \@Validate((args) => typeof args[0] === 'string' && args[0].length > 0) | ||
| * search(query: string) { ... } | ||
| */ | ||
| declare function Validate(check: (args: unknown[]) => boolean, options?: { | ||
| message?: string; | ||
| }): (_t: any, methodName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| /** | ||
| * Method decorator: validate the **output** with `check`. On a falsy/throwing | ||
| * result it retries up to `retries` times, then throws {@link GuardrailError}. | ||
| * Ideal for asserting an LLM produced well-formed output. Sync/async aware. | ||
| * | ||
| * @example | ||
| * \@Guardrail((out: { json: unknown }) => out.json !== undefined, { retries: 2 }) | ||
| * async ask(prompt: string) { ... } | ||
| */ | ||
| declare function Guardrail<R = unknown>(check: (result: R) => boolean, options?: { | ||
| retries?: number; | ||
| message?: string; | ||
| }): (_t: any, methodName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| /** | ||
| * Method decorator: cache the result by an **idempotency key** (per instance). | ||
| * Repeat calls with the same key return the stored result without re-running — | ||
| * unlike `@Cache` there's no TTL, and unlike `@Dedupe` it persists past the | ||
| * in-flight window. Failed async calls are not cached. | ||
| * | ||
| * @param keyFn derive the key from the args (default: JSON of the args). | ||
| * | ||
| * @example | ||
| * \@Idempotent((req) => req.requestId) | ||
| * async charge(req: { requestId: string; amount: number }) { ... } | ||
| */ | ||
| declare function Idempotent(keyFn?: (...args: any[]) => string): (_t: any, _methodName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| interface MeterStat { | ||
| calls: number; | ||
| errors: number; | ||
| totalMs: number; | ||
| avgMs: number; | ||
| } | ||
| /** | ||
| * Method decorator: record call count, error count, and timing under `name` | ||
| * (default: the method name). Read it back with {@link getMetrics} — handy for | ||
| * an agent to see how often and how expensively each tool is used. Sync/async. | ||
| */ | ||
| declare function Meter(name?: string): (_t: any, methodName: string, descriptor: PropertyDescriptor) => PropertyDescriptor; | ||
| /** Snapshot of all `@Meter` metrics, keyed by meter name. */ | ||
| declare function getMetrics(): Record<string, MeterStat>; | ||
| /** Reset all `@Meter` metrics (primarily for tests). */ | ||
| declare function resetMetrics(): void; | ||
| /** | ||
| * Method decorator: auto-bind the method to its instance, so `this` is always | ||
| * correct even when the method is passed as a callback. No more `.bind(this)`. | ||
| * | ||
| * @example | ||
| * class C { \@Bind handle() { return this.value; } } | ||
| * const { handle } = new C(); handle(); // works | ||
| */ | ||
| declare function Bind(_target: object, propertyKey: string, descriptor: PropertyDescriptor): PropertyDescriptor; | ||
| /** | ||
| * Property decorator: lazily compute the value on first access via `factory`, | ||
| * then cache it. Replaces the "private backing field + getter" pattern. Use with | ||
| * `@Configured` for robustness under modern class-field semantics. | ||
| * | ||
| * @example | ||
| * class C { \@Lazy((self) => expensive(self)) result!: Result; } | ||
| */ | ||
| declare function Lazy<T = unknown>(factory: (instance: any) => T): (target: object, propertyKey: string | symbol) => void; | ||
| /** | ||
| * Class decorator: `Object.seal` every instance after construction — existing | ||
| * properties stay writable, but none can be added or removed. (Use `@Immutable` | ||
| * to also freeze values.) | ||
| */ | ||
| declare function Sealed<T extends new (...args: any[]) => object>(ctor: T): T; | ||
| /** | ||
| * Class decorator: copy the members of each `source` (object or class prototype) | ||
| * onto the class — multiple inheritance without the boilerplate. | ||
| * | ||
| * @example | ||
| * \@Mixin(Timestamped, SoftDeletable) | ||
| * class Entity {} | ||
| */ | ||
| declare function Mixin(...sources: Array<object | (new (...args: any[]) => object)>): (ctor: new (...args: any[]) => object) => void; | ||
| /** | ||
| * Property decorator: call `handler(newValue, oldValue, instance)` whenever the | ||
| * property changes to a different value. A tiny reactive hook with no library. | ||
| * Use with `@Configured` for modern class-field robustness. | ||
| * | ||
| * The **first** assignment establishes the initial value and does not fire (there | ||
| * is no prior value to change from); subsequent changes fire. | ||
| * | ||
| * @example | ||
| * class Form { \@OnChange((v) => save(v)) draft = ''; } | ||
| */ | ||
| declare function OnChange<T = unknown>(handler: (newValue: T, oldValue: T, instance: any) => void): (target: object, propertyKey: string | symbol) => void; | ||
| /** Parse `.env`-style content into key/value pairs. */ | ||
| declare function parseEnv(content: string): Record<string, string>; | ||
| /** | ||
| * Load variables from a `.env` file into `process.env`. Zero-dependency: it does | ||
| * not pull in `dotenv`. By default it only fills variables that are not already | ||
| * set, and is a no-op when the file is missing. | ||
| * | ||
| * `@Configured` calls this once automatically (with defaults) before resolving | ||
| * `@Env`/`@Secret` properties, so `.env` files "just work". Call it explicitly | ||
| * if you need a custom path, override semantics, or earlier loading. | ||
| * | ||
| * @param options.path path to the env file (default `.env`). | ||
| * @param options.override overwrite already-set `process.env` values (default false). | ||
| */ | ||
| declare function loadEnv(options?: { | ||
| path?: string; | ||
| override?: boolean; | ||
| }): void; | ||
| export { All, AssertFalse, AssertTrue, Audit, Authorize, Bind, Body, Builder, Cache, CircuitBreaker, CircuitOpenError, Clamp, Coerce, Concurrency, Config, Configured, ContentType, Controller, Cookie, Counter, DEFAULT_SENSITIVE_KEYS, Data, Debounce, Dedupe, Default, Delete, DeleteMapping, Deprecated, Digits, Email, Enum, Env, Equals, Fallback, ForbiddenError, Future, FutureOrPresent, GenerateID, Get, GetMapping, Guardrail, GuardrailError, Head, Header, HttpCode, Idempotent, Immutable, Integer, Lazy, Log, LogErrors, Lowercase, Max, Measure, Memoize, Meter, Min, MissingConfigError, Mixin, Negative, NegativeOrZero, Next, NonEmpty, NotBlank, NotNull, OnChange, Once, Options, Param, Past, PastOrPresent, Patch, PatchMapping, PathVariable, Pattern, Positive, PositiveOrZero, Post, PostMapping, Produces, Put, PutMapping, Query, Range, RateLimit, RateLimitError, Readonly, Redirect, Req, RequestBody, RequestHeader, RequestMapping, RequestParam, Res, ResponseStatus, RestController, Retry, Role, Sealed, Secret, Size, Synchronized, Throttle, Timeout, TimeoutError, ToString, Tool, Trace, Trim, URL, UUID, Uppercase, Use, ValidDate, Validate, ValidationError, Value, With, builder, clearTools, getMetrics, getSecretKeys, getTools, getTraceId, invokeTool, loadEnv, parseEnv, redact, redactFormat, registerControllers, resetMetrics, setAuditResolver, setRoleResolver }; | ||
| export type { AccessContext, AuditContext, BuilderOf, ConstraintOptions, HttpApp, HttpRequest, HttpResponse, LogOptions, PatternOptions, RedactOptions, RequestHandler, ToStringOptions, ToolManifest, ToolOptions, ToolParameters, TraceOptions }; |
+1
-1
| MIT License | ||
| Copyright (c) 2023 Master4Novice | ||
| Copyright (c) 2026 Master4Novice | ||
@@ -5,0 +5,0 @@ Permission is hereby granted, free of charge, to any person obtaining a copy |
+38
-23
| { | ||
| "name": "@master4n/decorators", | ||
| "version": "1.1.5", | ||
| "description": "Common Decorators Library", | ||
| "version": "2.0.0", | ||
| "description": "AI-friendly TypeScript decorators for Node/backend apps, in ten families: Inject (config & value injection), Guard (validation), Shape (transforms), Shield (access control & secret redaction), Flow (resilience — retry/timeout/circuit-breaker/rate-limit/cache), Insight (observability), Model (data classes), Route (Express REST controllers), Agent (LLM tools, guardrails, idempotency), and Craft (class/method ergonomics). One decorator replaces a block of code.", | ||
| "main": "./commonjs/index.cjs", | ||
@@ -14,18 +14,38 @@ "module": "./esm/index.js", | ||
| }, | ||
| "scripts": { | ||
| "clean": "rimraf dist", | ||
| "lint": "eslint src/**/*.ts", | ||
| "format": "prettier --write src/**/*.ts", | ||
| "build": "npm run clean && rollup --config", | ||
| "test": "jest" | ||
| }, | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/Master4Novice/common.git" | ||
| "url": "git+https://github.com/Master4Novice/decorators.git" | ||
| }, | ||
| "keywords": [ | ||
| "decorators", | ||
| "typescript", | ||
| "nodejs", | ||
| "config", | ||
| "configuration", | ||
| "value-injection", | ||
| "dependency-injection", | ||
| "env", | ||
| "dotenv", | ||
| "yaml", | ||
| "yml", | ||
| "property" | ||
| "validation", | ||
| "transform", | ||
| "express", | ||
| "rest-api", | ||
| "controller", | ||
| "routing", | ||
| "retry", | ||
| "timeout", | ||
| "circuit-breaker", | ||
| "rate-limit", | ||
| "cache", | ||
| "memoize", | ||
| "debounce", | ||
| "observability", | ||
| "redaction", | ||
| "llm", | ||
| "tools", | ||
| "agent", | ||
| "ai-agents", | ||
| "ai-friendly", | ||
| "boilerplate" | ||
| ], | ||
@@ -35,17 +55,12 @@ "author": "dwivna", | ||
| "bugs": { | ||
| "url": "https://github.com/Master4Novice/common/issues" | ||
| "url": "https://github.com/Master4Novice/decorators/issues" | ||
| }, | ||
| "homepage": "https://github.com/Master4Novice/common#readme", | ||
| "devDependencies": { | ||
| "@types/config": "^3.3.3" | ||
| "homepage": "https://github.com/Master4Novice/decorators#readme", | ||
| "engines": { | ||
| "node": ">=20" | ||
| }, | ||
| "publishConfig": { | ||
| "directory": "dist", | ||
| "access": "public" | ||
| }, | ||
| "dependencies": { | ||
| "@types/uuid": "^9.0.7", | ||
| "config": "^3.3.9", | ||
| "js-yaml": "^4.1.0", | ||
| "uuid": "^9.0.1" | ||
| "js-yaml": "^4.1.1", | ||
| "winston": "^3.11.0" | ||
| }, | ||
@@ -52,0 +67,0 @@ "contributors": [ |
+541
-28
| # @master4n/decorators | ||
|  | ||
|  | ||
|  | ||
| [](https://github.com/Master4Novice/decorators/actions/workflows/ci.yml) | ||
| [](https://www.npmjs.com/package/@master4n/decorators) | ||
|  | ||
|  | ||
|  | ||
|  | ||
| **AI-friendly TypeScript decorators for Node/backend apps.** One self-documenting | ||
| decorator replaces a block of boilerplate. Designed so coding agents emit one | ||
| correct line instead of ten repetitive ones. | ||
| ## Decorator families | ||
| Every decorator belongs to one of ten families — a quick mental map for picking | ||
| the right one: | ||
| | Family | Purpose | Examples | | ||
| | ------ | ------- | -------- | | ||
| | **Inject** | pull values into fields | `@Value` `@Env` `@Secret` `@Config` `@Default` `@Configured` | | ||
| | **Guard** | reject invalid input (throws) | `@NotNull` `@Pattern` `@Min` `@Max` `@Range` `@Email` `@URL` `@UUID` `@Enum` `@Size` `@NotBlank` `@Past` `@Future` `@AssertTrue` `@Digits` … | | ||
| | **Shape** | normalize values on assign | `@Trim` `@Lowercase` `@Uppercase` `@Coerce` `@Clamp` | | ||
| | **Shield** | access control & secret redaction | `@Role` `@Authorize` `@Secret` + `redact()` | | ||
| | **Flow** | resilience & control flow | `@Timeout` `@Retry` `@Cache` `@Dedupe` `@Fallback` `@RateLimit` `@Concurrency` `@CircuitBreaker` `@Debounce` `@Throttle` `@Once` | | ||
| | **Insight** | observability | `@Log` `@Trace` `@Audit` `@LogErrors` `@Measure` `@Deprecated` | | ||
| | **Model** | data/domain classes | `@Data` `@ToString` `@Equals` `@With` `@Immutable` `@Readonly` `@Builder` `@GenerateID` `@Counter` `@Synchronized` | | ||
| | **Route** | HTTP REST controllers | `@Controller` `@Get` `@Post` `@Param` `@Query` `@Body` `@HttpCode` `@Use` … | | ||
| | **Agent** | LLM tools & safety | `@Tool` `@Validate` `@Guardrail` `@Idempotent` `@Meter` + `getTools()` / `invokeTool()` / `getMetrics()` | | ||
| | **Craft** | class & method ergonomics | `@Bind` `@Lazy` `@Sealed` `@Mixin` `@OnChange` | | ||
| ## Recipes for AI agents | ||
| Real, copy-paste compositions. Each replaces a page of hand-written plumbing with | ||
| a stack of declarations. | ||
| ### A bullet-proof DTO (Shape + Guard) | ||
| ```ts | ||
| import { Configured, Trim, Lowercase, Email, NotBlank, Size, Coerce, Range } from '@master4n/decorators'; | ||
| @Configured | ||
| class SignupDto { | ||
| @Trim @Lowercase @Email() email!: string; // normalized, then validated | ||
| @Trim @NotBlank() @Size(3, 20) username!: string; | ||
| @Coerce('number') @Range(18, 120) age!: number; // "21" -> 21, bounded | ||
| } | ||
| // Assigning a bad value throws ValidationError at the source — no manual checks. | ||
| ``` | ||
| ### A resilient upstream client (Flow) | ||
| ```ts | ||
| import { Fallback, CircuitBreaker, Retry, Timeout, Cache } from '@master4n/decorators'; | ||
| class UserApi { | ||
| @Fallback(null) // OUTERMOST = last resort: catch after everything else | ||
| @CircuitBreaker({ failureThreshold: 5, resetMs: 30_000 }) | ||
| @Retry(3, { delayMs: 200 }) // retry the timed call | ||
| @Timeout(5_000) | ||
| @Cache(60_000) // INNERMOST: memoize the actual fetch | ||
| async getUser(id: string) { | ||
| return (await fetch(`/users/${id}`)).json(); | ||
| } | ||
| } | ||
| ``` | ||
| > **Decorator order matters.** Stacks apply **bottom-up**: the decorator nearest | ||
| > the method wraps the original first, and the **top** decorator runs first / sees | ||
| > the final outcome. So put recovery (`@Fallback`) **outermost (top)** — otherwise | ||
| > it swallows the error before `@Retry`/`@CircuitBreaker` ever see a failure, and | ||
| > retries silently never happen. | ||
| ### A safe, observable AI tool (Agent + Insight) | ||
| ```ts | ||
| import { Tool, Validate, Idempotent, Guardrail, Meter, Trace, getTools, invokeTool, getMetrics } from '@master4n/decorators'; | ||
| class BookingTools { | ||
| @Tool({ | ||
| description: 'Book a room for a guest', | ||
| parameters: { | ||
| type: 'object', | ||
| properties: { guest: { type: 'string' }, nights: { type: 'number' } }, | ||
| required: ['guest', 'nights'], | ||
| }, | ||
| }) | ||
| @Trace() // correlation-id traced | ||
| @Meter('book_room') // counts + timing -> getMetrics() | ||
| @Idempotent((args) => `${args.guest}:${args.nights}`) // safe to retry | ||
| @Validate((args) => (args[0] as any)?.nights > 0) // reject bad tool input | ||
| @Guardrail((res: { confirmed: boolean }) => res.confirmed, { retries: 1 }) // verify output | ||
| async bookRoom(args: { guest: string; nights: number }) { | ||
| return { confirmed: true, ref: 'BK-123' }; | ||
| } | ||
| } | ||
| const svc = new BookingTools(); | ||
| const tools = getTools(); // -> hand to the LLM | ||
| // model picks a tool ... | ||
| await invokeTool(svc, 'bookRoom', { guest: 'Asha', nights: 2 }); | ||
| getMetrics().book_room; // { calls, errors, avgMs, ... } | ||
| ``` | ||
| ### An immutable domain model (Model) | ||
| ```ts | ||
| import { Data, Immutable, builder } from '@master4n/decorators'; | ||
| @Immutable | ||
| @Data // toString + equals + with | ||
| class Money { constructor(public amount = 0, public currency = 'INR') {} } | ||
| const a = new Money(100, 'INR'); | ||
| const b = (a as any).with({ amount: 250 }); // frozen copy, original untouched | ||
| const c = builder(Money).amount(50).currency('USD').build(); // typed builder | ||
| ``` | ||
| ### Less boilerplate (Craft) | ||
| ```ts | ||
| import { Configured, Bind, Lazy, OnChange } from '@master4n/decorators'; | ||
| @Configured // required for property decorators under modern TS | ||
| class Editor { | ||
| @Lazy((self) => buildHeavyIndex(self)) index!: Index; // computed once, on first read | ||
| @OnChange((v) => autosave(v)) content = ''; // reacts to real changes | ||
| @Bind onClick() { return this.content; } // safe to detach | ||
| } | ||
| ``` | ||
| > `@Lazy` and `@OnChange` are **property** decorators — like all of them, add | ||
| > `@Configured` to the class when you compile with `useDefineForClassFields: true` | ||
| > (the modern default), or they silently no-op (see [TypeScript setup](#typescript-setup)). | ||
| > `@Bind` is a method decorator and needs no `@Configured`. | ||
| ## Installation | ||
@@ -13,46 +143,429 @@ | ||
| ## Usage | ||
| ## TypeScript setup | ||
| ### As a library | ||
| This is a legacy-decorator library. A complete, known-good `tsconfig.json`: | ||
| With CommonJS in JavaScript, | ||
| ```json | ||
| { | ||
| "compilerOptions": { | ||
| "target": "ES2022", | ||
| "module": "NodeNext", | ||
| "moduleResolution": "NodeNext", | ||
| "strict": true, | ||
| "experimentalDecorators": true, | ||
| "emitDecoratorMetadata": true | ||
| } | ||
| } | ||
| ``` | ||
| ```js | ||
| const { Value } = require("@master4n/decorators"); | ||
| > **The one rule that matters: put `@Configured` on any class that uses a | ||
| > _property_ decorator** (`@Value`/`@Env`/`@Secret`/`@Config`/`@Default`, every | ||
| > Guard like `@Email`/`@Min`/`@Pattern`, every Shape like `@Trim`, and Craft's | ||
| > `@Lazy`/`@OnChange`/`@Readonly`). With modern TS (`useDefineForClassFields: true`, | ||
| > the default for `target >= ES2022`) a class field shadows the decorator's | ||
| > prototype accessor, so **without `@Configured` those decorators silently | ||
| > no-op** — no error, just nothing happens. `@Configured` materializes them as | ||
| > own instance properties so they work under any setting. **Method** and | ||
| > **class** decorators (`@Get`, `@Retry`, `@Bind`, `@Data`, `@Tool`, …) don't | ||
| > need it. | ||
| ## Inject — config & value injection (flagship) | ||
| Stop hand-writing `process.env.X ?? config.get(...) ?? default` plus type | ||
| coercion, for every field. | ||
| ```ts | ||
| // BEFORE — written by hand (or by an agent) for every single field: | ||
| class DbConfig { | ||
| url: string; | ||
| port: number; | ||
| ssl: boolean; | ||
| constructor() { | ||
| this.url = process.env.DB_URL ?? config.get('db.url') ?? 'sqlite://memory'; | ||
| const p = process.env.DB_PORT; | ||
| this.port = p ? parseInt(p, 10) : 5432; // string -> number | ||
| this.ssl = (process.env.DB_SSL ?? 'false') === 'true'; // string -> boolean | ||
| } | ||
| } | ||
| ``` | ||
| With ESM or TypeScript, | ||
| ```ts | ||
| // AFTER — declarative, coerced, and fails loud on missing required keys: | ||
| import { Configured, Value, Env, Secret } from '@master4n/decorators'; | ||
| @Configured | ||
| class DbConfig { | ||
| @Value('db.url', 'sqlite://memory') url!: string; | ||
| @Env('DB_PORT', 5432) port!: number; // "5432" -> 5432 | ||
| @Env('DB_SSL', false) ssl!: boolean; // "true" -> true | ||
| @Secret('DB_PASSWORD') password!: string; // required; tracked for redaction | ||
| } | ||
| ``` | ||
| > **Sources:** `@Value`/`@Config` read YAML/JSON via [`node-config`]; `@Env`/`@Secret` | ||
| > read `process.env`. Node does **not** load `.env` files automatically — if you keep | ||
| > config in a `.env` file, load it first (`node --env-file=.env ...` or the `dotenv` | ||
| > package) so the variables are present in `process.env`. | ||
| ### Injection decorators | ||
| | Decorator | Source | Notes | | ||
| | ------------------------ | ------------------------------- | ---------------------------------------------------------------- | | ||
| | `@Value(key, default?)` | config files (YAML/JSON) | via [`node-config`]. Required (throws) when no default is given. | | ||
| | `@Env(name, default?)` | `process.env` | coerces to the default's type (number/boolean/array). | | ||
| | `@Secret(name, default?)`| `process.env` | like `@Env`; marks the field as secret so `redact()` / `redactFormat()` mask it (see [Secret redaction](#secret-redaction)). | | ||
| | `@Config(path)` | config files | injects a whole config subtree/object (required). | | ||
| | `@Default(value)` | literal | injects a constant. | | ||
| | `@Configured` | _class decorator_ | materializes the above as own instance props (robust mode). | | ||
| Missing required values throw `MissingConfigError`. With `@Configured`, that | ||
| happens at construction — so misconfiguration fails at startup, not deep in a | ||
| request. | ||
| ### Secret redaction | ||
| `@Secret` doesn't just track names — it makes those values disappear from logs. | ||
| - `redact(value, options?)` returns a deep copy with sensitive values masked | ||
| (`'[REDACTED]'`). Sensitive = `@Secret`-marked property names ∪ a built-in list | ||
| (`DEFAULT_SENSITIVE_KEYS`: `password`, `token`, `apiKey`, `authorization`, …) ∪ | ||
| `options.keys`. Matching is case- and `_`/`-`-insensitive; nested objects, | ||
| arrays, and circular references are handled. | ||
| - `redactFormat(options?)` is a winston format. This package's own logger already | ||
| uses it; add it to your logger's `format.combine(...)` to protect your logs too. | ||
| > **Redaction is key-based** — it masks object *fields* whose **name** is | ||
| > sensitive. It cannot mask a secret passed as a positional **primitive** | ||
| > (e.g. `login(rawToken)`) or one embedded in an error message/stack. Pass | ||
| > secrets as named object fields, and don't put them in error messages. | ||
| > | ||
| > `@Secret` field names are registered **process-globally**, so `redact()` masks | ||
| > that field name everywhere — pick distinctive secret field names (`jwtSecret`, | ||
| > not `id`) to avoid over-masking unrelated fields. | ||
| ```ts | ||
| import { Value } from "@master4n/decorators"; | ||
| import { redact, redactFormat } from '@master4n/decorators'; | ||
| import winston from 'winston'; | ||
| logger.info('Loaded config', redact(appConfig)); // jwtSecret -> [REDACTED] | ||
| const logger = winston.createLogger({ | ||
| format: winston.format.combine(redactFormat(), winston.format.json()), | ||
| }); | ||
| ``` | ||
| ```js | ||
| class Token { | ||
| @Value("fix.token") | ||
| fixToken: string; | ||
| ## Guard & Shield — validation & access | ||
| Guards throw on invalid input — misuse fails fast instead of slipping through. | ||
| | Decorator | Target | Throws | Description | | ||
| | -------------------------- | ------ | ----------------- | ------------------------------------------------------ | | ||
| | `@NotNull` | method | `ValidationError` | rejects `null`/`undefined` arguments. | | ||
| | `@ValidDate` | method | `ValidationError` | first arg must be a valid `{ DD, MM, YYYY }` date. | | ||
| | `@Pattern(regex, opts?)` | property | `ValidationError` | only allows assigning values that match the regex. | | ||
| | `@Min(n)` / `@Max(n)` | property | `ValidationError` | string/array **length** ≥ n / ≤ n, or **number** value. | | ||
| | `@Range(min, max)` | property | `ValidationError` | inclusive bounds on string/array length or number value. | | ||
| | `@Email` `@URL` `@UUID` | property | `ValidationError` | format checks for email / URL / UUID. | | ||
| | `@Enum(values)` | property | `ValidationError` | value must be one of `values`. | | ||
| | `@NonEmpty` | property | `ValidationError` | rejects `null`/`undefined`/`''`/`[]`. | | ||
| | `@Integer` `@Positive` | property | `ValidationError` | number must be an integer / greater than zero. | | ||
| | `@NotBlank` | property | `ValidationError` | string with a non-whitespace char (asserts presence). | | ||
| | `@Size(min, max)` | property | `ValidationError` | string/array length bounds. | | ||
| | `@Negative` `@PositiveOrZero` `@NegativeOrZero` | property | `ValidationError` | number sign constraints. | | ||
| | `@Past` `@Future` `@PastOrPresent` `@FutureOrPresent` | property | `ValidationError` | date is before/after now. | | ||
| | `@AssertTrue` `@AssertFalse` | property | `ValidationError` | boolean must be true / false. | | ||
| | `@Digits(int, frac)` | property | `ValidationError` | max integer + fractional digits. | | ||
| **Transforms** normalize the value on assignment (and run *before* validators, | ||
| whatever the stacking order): | ||
| | Decorator | Effect | | ||
| | --------------- | ------------------------------------------------- | | ||
| | `@Trim` | trim whitespace from assigned strings. | | ||
| | `@Lowercase` / `@Uppercase` | change case of assigned strings. | | ||
| | `@Coerce(type)` | coerce to `'number'`/`'boolean'`/`'string'`. | | ||
| | `@Clamp(min, max)` | clamp an assigned number into `[min, max]`. | | ||
| ```ts | ||
| @Configured | ||
| class Signup { | ||
| @Trim @Lowercase @Email() email!: string; // " A@B.CO " -> "a@b.co", validated | ||
| @Coerce('number') @Range(18, 120) age!: number; // "21" -> 21, bounded | ||
| } | ||
| ``` | ||
| | `@Role(...roles)` | method | `ForbiddenError` | allows only if the principal has one of the roles. | | ||
| | `@Authorize(predicate)` | method | `ForbiddenError` | allows only if `predicate(ctx)` is truthy. | | ||
| ## Summary | ||
| `@Role`/`@Authorize` are auth-agnostic. Register how to find the principal once: | ||
| This package contains decorators for any applications. Kindly set true for the below property in your tsconfig.json. | ||
| ```ts | ||
| import { Role, Authorize, setRoleResolver } from '@master4n/decorators'; | ||
| ```json | ||
| "experimentalDecorators": true, | ||
| "emitDecoratorMetadata": true, | ||
| setRoleResolver((ctx) => (ctx.instance as any).user?.roles ?? []); | ||
| class AdminApi { | ||
| @Role('admin', 'owner') | ||
| deleteUser(id: string) { /* ... */ } | ||
| @Authorize((ctx) => (ctx.instance as any).user?.can('billing')) | ||
| refund(orderId: string) { /* ... */ } | ||
| } | ||
| ``` | ||
| ## Available Decorators | ||
| Resolvers/predicates may be async (the guarded call then returns a promise). | ||
| | Decorator | Type | Description | | ||
| | ---------- | ------------------------- | --------------------------------------------------------------- | | ||
| | Value | Class Property | Read the value from yaml file and assigned it to class property | | ||
| | GenerateID | Class Property | Assigne unique UUID value to class property | | ||
| | NotNull | Method Parameter | Check if parameter is not null and undefined | | ||
| | Counter | Static Property Parameter | Create static counter | | ||
| | Log | Method Parameter | Log in and out of method | | ||
| `@Pattern` guards a **property**: assignments that don't match the regex throw and | ||
| the previous value is kept. Add `@Configured` so it works under any | ||
| `useDefineForClassFields` setting (like the injection decorators). | ||
| ```ts | ||
| import { Configured, Pattern } from '@master4n/decorators'; | ||
| @Configured | ||
| class User { | ||
| @Pattern(/^[^@\s]+@[^@\s]+\.[^@\s]+$/, { message: 'invalid email' }) | ||
| email!: string; | ||
| @Pattern(/^\d{6}$/, { coerce: true }) // accepts 560001 or "560001" | ||
| pincode!: string; | ||
| } | ||
| new User().email = 'not-an-email'; // throws ValidationError | ||
| ``` | ||
| `@Min`/`@Max`/`@Range` are polymorphic — they check **string/array length** or a | ||
| **number's value** — and compose with each other and `@Pattern`: | ||
| ```ts | ||
| @Configured | ||
| class Account { | ||
| @Pattern(/^[a-z0-9_]+$/) @Min(3) @Max(20) | ||
| username!: string; // lowercase, 3–20 chars | ||
| @Range(0, 100) score!: number; // 0..100 | ||
| } | ||
| ``` | ||
| ## Utility decorators | ||
| | Decorator | Target | Description | | ||
| | ------------------ | --------------- | ------------------------------------------------------ | | ||
| | `@GenerateID` | class property | assigns a lazy UUIDv4 (via `crypto.randomUUID()`). | | ||
| | `@Counter` | static property | auto-incrementing counter on each read. | | ||
| | `@Log(opts?)` | method | logs entry/exit; `{ args, result }` also log **redacted** args/return; `{ level }` sets the level. | | ||
| | `@Retry(n, opts?)` | method | retries on failure (sync/async); `opts.delayMs` for async. | | ||
| | `@Memoize` | method | caches results by argument JSON, per instance. | | ||
| | `@Deprecated(msg)` | method | logs a one-time deprecation warning. | | ||
| | `@Measure` | method | logs execution time (sync/async). | | ||
| ## Model — data classes | ||
| | Decorator | Adds | | ||
| | ---------------- | ------------------------------------------------------------------------- | | ||
| | `@ToString(opts?)` | a `toString()` listing fields — **with `@Secret`/sensitive fields redacted**. `only`/`exclude` options. | | ||
| | `@Equals(...keys?)`| an `equals(other)` (same-constructor, field-wise). | | ||
| | `@With` | `with(patch)` → shallow copy with overrides (frozen-preserving). | | ||
| | `@Data` | `@ToString` + `equals()` + `with()` in one. | | ||
| | `@Immutable` | `Object.freeze` each instance (immutable value object). Pairs with `@With`. | | ||
| | `@Readonly` | field: assignable once, then throws (like `final`). | | ||
| | `@Synchronized` | method: serialize concurrent async calls per instance (mutex). | | ||
| | `@Builder` / `builder(Class)` | fluent builder. `builder()` is **fully typed** (no codegen). | | ||
| ```ts | ||
| import { Data, Immutable, With, builder } from '@master4n/decorators'; | ||
| @Immutable | ||
| @Data // toString + equals + with | ||
| class Money { constructor(public amount = 0, public currency = 'INR') {} } | ||
| const a = new Money(100, 'INR'); | ||
| const b = (a as any).with({ amount: 250 }); // frozen copy | ||
| const c = builder(Money).amount(50).currency('USD').build(); // typed builder | ||
| ``` | ||
| ## Route — REST controllers | ||
| Build Express routes declaratively — `@Controller` + `@GetMapping` + | ||
| `@PathVariable`/`@RequestParam`/`@RequestBody` — then wire them in with | ||
| `registerControllers`. Framework-agnostic (no `express` dependency); works with | ||
| any Express-compatible `app`/`Router`. | ||
| ```ts | ||
| import express from 'express'; | ||
| import { | ||
| Controller, Get, Post, Param, Query, Body, HttpCode, Use, registerControllers, | ||
| } from '@master4n/decorators'; | ||
| @Use(authMiddleware) // controller-level middleware | ||
| @Controller('/users') | ||
| class UserController { | ||
| @Get('/:id') | ||
| getUser(@Param('id') id: string, @Query('expand') expand?: string) { | ||
| return this.service.find(id, expand); // returned value -> res.json(...) (200) | ||
| } | ||
| @Post('/') | ||
| @HttpCode(201) | ||
| create(@Body() dto: CreateUserDto) { | ||
| return this.service.create(dto); // -> 201 + JSON | ||
| } | ||
| } | ||
| const app = express(); | ||
| app.use(express.json()); | ||
| registerControllers(app, [new UserController()]); | ||
| ``` | ||
| Returned values are sent as JSON with the configured status; throw and it's | ||
| routed to `next(err)`. Inject `@Res()` to take over the response yourself. | ||
| | Concise | Alias | Purpose | | ||
| | ------------------ | ------------------- | ----------------------------------------- | | ||
| | `@Controller(base)`| `@RestController` | class: base path + controller middleware. | | ||
| | `@Get` `@Post` `@Put` `@Patch` `@Delete` `@Options` `@Head` `@All` | `@GetMapping` … `@RequestMapping` | route a method. | | ||
| | `@Param(n)` | `@PathVariable` | path variable. | | ||
| | `@Query(n)` | `@RequestParam` | query-string value. | | ||
| | `@Body(n?)` | `@RequestBody` | request body (or one field). | | ||
| | `@Header(n)` | `@RequestHeader` | request header. | | ||
| | `@Cookie(n)` `@Req()` `@Res()` `@Next()` | — | cookie / raw req / res / next. | | ||
| | `@HttpCode(code)` | `@ResponseStatus` | success status code. | | ||
| | `@ContentType(t)` | `@Produces` | response content-type. | | ||
| | `@Redirect(url)` `@Use(...mw)` | — | redirect / attach middleware (class or route). | | ||
| ## Agent — LLM tools | ||
| Expose class methods as LLM-callable tools, then dispatch the model's tool call | ||
| back to the method — the whole agent loop, declaratively. | ||
| ```ts | ||
| import { Tool, getTools, invokeTool } from '@master4n/decorators'; | ||
| class WeatherService { | ||
| @Tool({ | ||
| description: 'Get the current temperature for a city', | ||
| parameters: { | ||
| type: 'object', | ||
| properties: { city: { type: 'string' } }, | ||
| required: ['city'], | ||
| }, | ||
| }) | ||
| getTemperature(args: { city: string }) { /* ... */ } | ||
| } | ||
| const tools = getTools(); | ||
| // -> [{ name: 'getTemperature', description: '...', parameters: {...} }] | ||
| // Pass `tools` to your LLM (OpenAI `tools`/`parameters`, Anthropic `input_schema`). | ||
| // When the model returns a tool call: | ||
| const result = invokeTool(new WeatherService(), call.name, call.arguments); | ||
| ``` | ||
| `parameters` is an **explicit** JSON Schema — TypeScript parameter types are | ||
| erased at runtime, so the library does not (and cannot honestly) infer it. | ||
| > Tool names live in a **process-global** registry and must be unique — a | ||
| > duplicate name overwrites the earlier one (so `invokeTool` would target the | ||
| > wrong method). Give colliding tools an explicit unique `name`. | ||
| ### Agent power-ups (method decorators) | ||
| Wrap the methods an agent calls so they're validated, safe to retry, verified, | ||
| and measured: | ||
| | Decorator | Does | | ||
| | ------------------------ | -------------------------------------------------------------------- | | ||
| | `@Validate(check)` | reject bad **input** args before running (throws `ValidationError`). | | ||
| | `@Guardrail(check, opts?)` | verify the **output**; retry up to `opts.retries`, else `GuardrailError`. | | ||
| | `@Idempotent(keyFn?)` | cache the result by an idempotency key — safe to retry (no TTL; failures aren't cached). | | ||
| | `@Meter(name?)` | record calls / errors / timing; read with `getMetrics()`. | | ||
| ## Craft — class & method ergonomics | ||
| Kill the small repeated boilerplate. | ||
| | Decorator | Does | | ||
| | ----------------- | -------------------------------------------------------------------------- | | ||
| | `@Bind` | auto-bind a method to its instance — no more `.bind(this)` for callbacks. | | ||
| | `@Lazy(factory)` | compute a property once on first access, then cache. | | ||
| | `@Sealed` | `Object.seal` each instance (no added/removed props; values stay writable). | | ||
| | `@Mixin(...src)` | copy members from other objects/classes onto the class. | | ||
| | `@OnChange(fn)` | run `fn(new, old, instance)` when a property actually changes (first set initializes silently). | | ||
| ## Insight — observability | ||
| | Decorator | Description | | ||
| | ----------------- | ----------------------------------------------------------------------- | | ||
| | `@Trace(opts?)` | structured entry/exit/error logs with a **correlation id** threaded through nested calls (AsyncLocalStorage). Args/results redacted. `getTraceId()` reads the current id. | | ||
| | `@Audit(action?)` | logs `actor` + `action` + redacted args. Set the "who" via `setAuditResolver`. | | ||
| | `@LogErrors()` | logs errors (redacted args + stack) and **rethrows** (sync/async). | | ||
| ```ts | ||
| import { Trace, Audit, setAuditResolver, getTraceId } from '@master4n/decorators'; | ||
| setAuditResolver((ctx) => (ctx.instance as any).user?.id ?? 'system'); | ||
| class OrderService { | ||
| @Trace({ result: true }) // one trace id flows through the whole call tree | ||
| @Audit('order.refund') | ||
| async refund(orderId: string) { /* getTraceId() to tag your own logs */ } | ||
| } | ||
| ``` | ||
| ## Flow — resilience & control flow | ||
| | Decorator | Description | | ||
| | -------------------------- | ----------------------------------------------------------------- | | ||
| | `@Timeout(ms)` | reject an **async** method with `TimeoutError` if it exceeds `ms`. | | ||
| | `@Once` | run once per instance; cache that result forever. | | ||
| | `@Cache(ttlMs)` | memoize with a TTL (vs `@Memoize`, which never expires). | | ||
| | `@Dedupe` | coalesce concurrent identical **async** calls (single-flight). | | ||
| | `@Fallback(value\|fn)` | on error, return a fallback instead of throwing (sync/async). | | ||
| | `@RateLimit(limit, ms)` | throw `RateLimitError` past `limit` calls per rolling `ms`. | | ||
| | `@Concurrency(max)` | cap concurrent **async** executions; queue the rest. | | ||
| | `@CircuitBreaker(opts)` | open after N failures, fast-fail with `CircuitOpenError`, auto-reset. | | ||
| | `@Debounce(ms)` | **void** methods: collapse rapid calls, run on the trailing edge. | | ||
| | `@Throttle(ms)` | **void** methods: run on the leading edge, ignore for `ms`. | | ||
| ```ts | ||
| import { Timeout, Retry, CircuitBreaker, Fallback } from '@master4n/decorators'; | ||
| class Upstream { | ||
| @CircuitBreaker({ failureThreshold: 5, resetMs: 30_000 }) | ||
| @Retry(3, { delayMs: 200 }) | ||
| @Timeout(5_000) | ||
| @Fallback(null) // last resort: return null instead of throwing | ||
| async fetchUser(id: string) { /* ... */ } | ||
| } | ||
| ``` | ||
| ```ts | ||
| import { GenerateID, Counter, Log, Retry, Memoize } from '@master4n/decorators'; | ||
| class Job { | ||
| @GenerateID id!: string; // unique per instance | ||
| @Counter static runs: number; // increments on each read | ||
| @Retry(3, { delayMs: 200 }) | ||
| @Log({ args: true, result: true }) // logged args/result are redacted | ||
| async run() { /* ... */ } | ||
| @Memoize | ||
| score(input: string): number { /* expensive, pure */ return input.length; } | ||
| } | ||
| ``` | ||
| ## Breaking change in 2.0.0 | ||
| `@NotNull` now **throws** `ValidationError` for `null`/`undefined` arguments (it | ||
| only logged in 1.x). `@ValidDate` is fixed (it was a no-op) and now throws on an | ||
| invalid date. See [KNOWN_ISSUES.md](./KNOWN_ISSUES.md) for the history. | ||
| ## Changelog | ||
| See [CHANGELOG.md](./CHANGELOG.md). | ||
| ## Credits | ||
| These definitions were written by [Master4Novice](https://github.com/Master4Novice). | ||
| Written by [Master4Novice](https://github.com/Master4Novice). | ||
| [`node-config`]: https://github.com/node-config/node-config |
| export { Value, GenerateID, NotNull, ValidDate, Counter, Log } from './services/property.js'; |
| /*** | ||
| * Decorator can set a class property value from YAML file | ||
| * @param ymlKey a key from yaml file. | ||
| * @param ymlValue a default direct value. | ||
| */ | ||
| export declare function Value(ymlKey: string, ymlValue?: any): (target: any, propertyKey: string) => void; | ||
| /** | ||
| * Decorator can set a class property as UUIDv4 value | ||
| */ | ||
| export declare function GenerateID(target: any, key: string): void; | ||
| /** | ||
| * Decorator will allow defined and non null values only. | ||
| */ | ||
| export declare function NotNull(target: any, key: string, descriptor: PropertyDescriptor): PropertyDescriptor; | ||
| /** | ||
| * Decorator for date validation. | ||
| */ | ||
| export declare function ValidDate(target: any, key: string | symbol): void; | ||
| /** | ||
| * Decorator for static property of class. | ||
| * It will make static property work as counter | ||
| */ | ||
| export declare function Counter(target: any, propertyKey: string): void; | ||
| /** | ||
| * Decorator logging in and out of method. | ||
| */ | ||
| export declare function Log(): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor; |
| import winston from 'winston'; | ||
| export declare const logger: winston.Logger; |
| /** | ||
| * Read a value of a key from YAML file. | ||
| * @param key | ||
| * @param value | ||
| */ | ||
| export declare function readYMLKey(key: string, value?: any): any; |
| export { Value, GenerateID, NotNull, ValidDate, Counter, Log } from './services/property.js'; |
| /*** | ||
| * Decorator can set a class property value from YAML file | ||
| * @param ymlKey a key from yaml file. | ||
| * @param ymlValue a default direct value. | ||
| */ | ||
| export declare function Value(ymlKey: string, ymlValue?: any): (target: any, propertyKey: string) => void; | ||
| /** | ||
| * Decorator can set a class property as UUIDv4 value | ||
| */ | ||
| export declare function GenerateID(target: any, key: string): void; | ||
| /** | ||
| * Decorator will allow defined and non null values only. | ||
| */ | ||
| export declare function NotNull(target: any, key: string, descriptor: PropertyDescriptor): PropertyDescriptor; | ||
| /** | ||
| * Decorator for date validation. | ||
| */ | ||
| export declare function ValidDate(target: any, key: string | symbol): void; | ||
| /** | ||
| * Decorator for static property of class. | ||
| * It will make static property work as counter | ||
| */ | ||
| export declare function Counter(target: any, propertyKey: string): void; | ||
| /** | ||
| * Decorator logging in and out of method. | ||
| */ | ||
| export declare function Log(): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor; |
| import winston from 'winston'; | ||
| export declare const logger: winston.Logger; |
| /** | ||
| * Read a value of a key from YAML file. | ||
| * @param key | ||
| * @param value | ||
| */ | ||
| export declare function readYMLKey(key: string, value?: any): any; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
410668
2110.51%3
-25%0
-100%6253
5110.83%0
-100%0
-100%571
884.48%9
-43.75%12
200%1
Infinity%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
Updated