Master-level TypeScript: decorators, mixins, and branded types for ultimate type safety.
Decorators, mixins, and branded/nominal types for enterprise-grade TypeScript.
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
}function log(target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(\`Calling \${key} with\`, args);
const result = original.apply(this, args);
console.log(\`Result:\`, result);
return result;
};
return descriptor;
}
class Calculator {
@log
add(a: number, b: number) { return a + b; }
}function observable(target: any, key: string) {
let value = target[key];
Object.defineProperty(target, key, {
get: () => value,
set: (newValue) => {
console.log(\`Setting \${key} to \${newValue}\`);
value = newValue;
}
});
}function required(target: any, key: string, index: number) {
const requiredParams: number[] =
Reflect.getMetadata("required", target, key) || [];
requiredParams.push(index);
Reflect.defineMetadata("required", requiredParams, target, key);
}function minLength(min: number) {
return function(target: any, key: string) {
let value: string;
Object.defineProperty(target, key, {
get: () => value,
set: (newValue: string) => {
if (newValue.length < min) {
throw new Error(\`Min length is \${min}\`);
}
value = newValue;
}
});
};
}Class decorator: sealed
Method decorator: logging calls
Property decorator: observable
Decorator factory: minLength(3)
Decorators complete!
function dec(constructor: Function) { }function dec(target, key, descriptor) { }function dec(target, key) { }function dec(arg) { return function(target) { } }@decorator above class/method/propertyReview feedback below
type Constructor = new (...args: any[]) => T;
function Timestamped(Base: TBase) {
return class extends Base {
timestamp = Date.now();
};
} function Activatable(Base: TBase) {
return class extends Base {
isActive = false;
activate() { this.isActive = true; }
deactivate() { this.isActive = false; }
};
} interface Nameable { name: string; }
function Tagged>(Base: TBase) {
return class extends Base {
tag = \`[\${this.name}]\`;
};
} class User {
name: string;
constructor(name: string) { this.name = name; }
}
const TimestampedActivatableUser =
Timestamped(Activatable(User));
const user = new TimestampedActivatableUser("Alice");
user.activate();
console.log(user.timestamp, user.isActive);interface Disposable {
dispose(): void;
isDisposed: boolean;
}
function DisposableMixin(Base: TBase) {
return class extends Base implements Disposable {
isDisposed = false;
dispose() {
this.isDisposed = true;
console.log("Disposed");
}
};
} Timestamped mixin: added timestamp
Activatable mixin: activate/deactivate
Composed: TimestampedActivatableUser
Constrained mixin: requires Nameable
Mixins complete!
type Constructor<T = {}> = new (...args: any[]) => Tfunction Mix<T extends Constructor>(Base: T) { return class extends Base { } }T extends Constructor<RequiredInterface>Mixin1(Mixin2(BaseClass))Review feedback below
type Brand = K & { __brand: T };
type USD = Brand;
type EUR = Brand;
const usd = 100 as USD;
const eur = 85 as EUR;
function addUSD(a: USD, b: USD): USD {
return (a + b) as USD;
}
// addUSD(usd, eur); // Error! Can't mix currencies type Email = Brand;
type URL = Brand;
type UUID = Brand;
function validateEmail(input: string): Email | null {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(input) ? input as Email : null;
}
function sendEmail(to: Email, subject: string) {
console.log(\`Sending to \${to}\`);
} type UserId = Brand;
type PostId = Brand;
type CommentId = Brand;
function getUser(id: UserId) { ... }
function getPost(id: PostId) { ... }
const userId = "user-123" as UserId;
const postId = "post-456" as PostId;
getUser(userId); // OK
// getUser(postId); // Error! Type mismatch type Percentage = Brand;
type PositiveInt = Brand;
function toPercentage(n: number): Percentage | null {
return n >= 0 && n <= 100 ? n as Percentage : null;
}
function toPositiveInt(n: number): PositiveInt | null {
return Number.isInteger(n) && n > 0 ? n as PositiveInt : null;
} // More robust pattern with private constructor
declare const validatedSymbol: unique symbol;
type Validated = T & { [validatedSymbol]: true };
type ValidatedUser = Validated<{
name: string;
email: string;
}>;
function validateUser(input: unknown): ValidatedUser | null {
// validation logic
if (isValidUser(input)) {
return input as ValidatedUser;
}
return null;
} USD branded: cannot mix with EUR
Email branded: validated string
UserId/PostId: distinct entity IDs
Percentage: validated 0-100 range
Nominal types prevent misuse!
type Brand<K, T> = K & { __brand: T }type USD = Brand<number, "USD">const usd = 100 as USDBrandedType | nullReview feedback below