lightsoutAlpha

class-bright-line

a class written where none of the four criteria that license one hold

Agent checkadvisory by defaultbasecode

The argument

When to Use a Class — The Bright Line

Default to functions. Create a class if and only if at least one of these is true:

#CriterionExample
aMutable state persists across method callsRateLimiter (remaining tokens), a cache, a connection pool
b3+ operations share injected config/dependenciesHttpClient (baseUrl, retries, credentials injected once, used by every method)
cMultiple implementations of a shared interfaceFileSource / S3Source behind one RecordSource contract
dThe framework requires itNestJS services, resolvers, guards (DI needs classes)

If none apply: functions in a module. Gut-check: is "how many of these exist right now?" a meaningful question? Two HttpClients pointed at different APIs — meaningful → class. Two formatDates — nonsensical → function.

Functional vs Class-Based

Prefer functions by default. Create a class only per the bright-line criteria in classes.md (persistent state, 3+ operations sharing injected deps, interface polymorphism, framework mandate). Static-only classes are banned.

The proof

failsrc/pricing/PriceFormatter.ts
interface ConstructorParams {
	locale: string;
}

// None of the four criteria hold: nothing persists between calls, one operation
// uses the injected value, there is no second implementation and no framework
// asked for a class. "How many PriceFormatters exist?" is not a question.
export class PriceFormatter {
	private readonly locale: string;

	constructor({ locale }: ConstructorParams) {
		this.locale = locale;
	}

	format({ amount }: { amount: number }): string {
		return amount.toLocaleString(this.locale);
	}
}
passsrc/rate/RateLimiter.ts
interface ConstructorParams {
	tokens: number;
}

// Criterion (a): the remaining tokens persist across calls, so "how many rate
// limiters exist right now?" is a question with a meaningful answer.
export class RateLimiter {
	private remaining: number;

	constructor({ tokens }: ConstructorParams) {
		this.remaining = tokens;
	}

	take(): boolean {
		const allowed = this.remaining > 0;

		if (allowed) {
			this.remaining -= 1;
		}

		return allowed;
	}

	refill({ tokens }: { tokens: number }): void {
		this.remaining = tokens;
	}
}

Turn it down

Both lines go in your lightsout.config.json.

"standards-checks": { "class-bright-line": "advisory" }
"standards-checks": { "class-bright-line": "off" }