Lightweight TypeScript 5 standard decorators to enforce preconditions (before), postconditions (after), and invariants (constant). Includes method, setter, and class-level decorators with friendly tracing.
Use via URL imports or local module. Example URL import (replace with your repo path):
import{always,setFailureMode}from"./mod.ts";This project targets Deno and TypeScript 5 standard decorators.
- Guard a business invariant on a method
classTicketCounter{available=100;
@always({before: (count: number)=>Number.isInteger(count)&&count>0,constant: (self: TicketCounter)=>self.available>=0,after: (remaining: number)=>remaining>=0,})sell(count: number){this.available-=count;returnthis.available;}}- Harden a setter
classCustomerProfile{
#creditLimit =5_000;getcreditLimit(){returnthis.#creditLimit;}
@always({before: (value: number)=>Number.isFinite(value)&&value>=0,constant: (self: CustomerProfile)=>self.creditLimit>=0,trace: false,})setcreditLimit(value: number){this.#creditLimit =Math.round(value);}}- Await async predicates
classInvoiceService{outstanding=newSet<string>();constructor(privatereadonlysend: (id: string,)=>Promise<{status: string;id: string}>,){}
@always({before: async(invoiceId: string)=>invoiceId.trim().length>0,constant: async(self: InvoiceService)=>self.outstanding.size<=50,after: async(receipt: {status: string})=>receipt.status==="captured",trace: false,})asynccapture(invoiceId: string){this.outstanding.add(invoiceId);constreceipt=awaitthis.send(invoiceId);this.outstanding.delete(invoiceId);returnreceipt;}}- Class-level invariant (constant)
@always({constant: (self: BankAccount)=>self.balance>=0})classBankAccount{balance=0;deposit(amount: number){this.balance+=amount;}withdraw(amount: number){this.balance-=amount;}}Decorator factory: always(specOrInvariant) where specOrInvariant is:
Method/Setter spec
before(...args) => boolean | Promise<boolean>— preconditionafter(result, ...args) => boolean | Promise<boolean>— postconditionconstant(self) => boolean | Promise<boolean>— checks object state before and aftertrace: boolean | (info) => void— tracing (true by default)failureMode: "log" | "throw"— override failure behavior for this spec- Aliases:
requires->before,ensures->after
Class spec
{ constant(self) }(alias:invariant)
Asynchronous predicates are awaited automatically. If any predicate returns a
promise, the decorated method or setter will produce a promise as well. Class
decorators still expect their invariant (constant) to resolve synchronously so
the constructor can fail fast when a new instance violates the contract.
- Failures log by default; call
setFailureMode("throw")to switch to throwing globally. - Toggle per decorator with the
failureModefield above.
- Enabled by default. Pass
trace: falseto disable, or a function to capture events. - Trace event info includes
{ kind, class, name, args, result }depending on context.
- Before:
Before failed: ClassName.method(args) - After:
After failed: ClassName.method(args) -> result - Constant before/after:
Constant failed before/after: ClassName.method(args) -> result
- Run tests with Deno:
deno task testTests live in *_test.ts files and use @std/assert.
- Uses TypeScript 5 standard decorators (
context.kindfor class/method/setter). - Class-level invariant wraps all instance methods and setters.
- No side effects on import; module exports
{ always, setFailureMode }only.
MIT