safe-ts
SKILL.md
Safe TypeScript
Build highly predictable, robust, and performant TypeScript/Node.js applications with a "zero technical debt" policy.
Retrieval-First Development
Always verify standards against the reference documentation before implementing.
| Resource | URL / Path |
|---|---|
| Safety & Control Flow | ./references/safety.md |
| Performance Patterns | ./references/performance.md |
| Developer Experience | ./references/dx.md |
Review the relevant documentation when writing new logic or performing code reviews.
When to Use
- Writing new TypeScript logic from scratch
- Refactoring existing TS/JS code to improve safety, performance, or memory stability
- Reviewing PRs for code quality and strict compiler standard adherence
- Optimizing memory allocations or V8 hot paths (e.g., GC pause mitigation)
- Implementing strict error handling without
throw/try-catch
Reference Documentation
./references/safety.md- Control flow limits, bounded Promises, assertions, Result types./references/performance.md- Object pools, TypedArrays, monomorphic shapes./references/dx.md- Naming conventions, options structs, strict compiler flags, zero dependencies
Search: no recursion, AbortSignal, ObjectPool, Result<T,E>, noUncheckedIndexedAccess, Zod
Core Principles
Apply Safe TypeScript For
| Need | Example |
|---|---|
| Predictable Execution | Bounded for loops, bounded Promises via AbortSignal.timeout() |
| Memory Stability | Pre-allocating arrays/pools at startup, Uint8Array, in-place object mutation |
| Operational Reliability | Returning explicit Result<T, E> types, never throwing operational errors |
| Maintainability | Maximum 70 lines per function, max 100 columns per line, options interfaces |
Do NOT Use
- Unbounded
while(true)loops orPromises without timeouts - Dynamic memory allocations (
new Object(),[],{}) inside hot paths (triggers GC pauses) - Unchecked array indices or implicit
anytypes - Expected control flow via
throwandcatch(Exceptions are for bugs/panics only) Proxy,Reflect, or runtime decorator magic
Quick Reference
Bounded Asynchronous Pattern (No Leaked Promises)
// Always bound asynchronous operations with a timeout
async function fetchWithBounds(url: string, timeoutMs: number): Promise<Result<Response, Error>> {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(new Error("Timeout")), timeoutMs);
try {
const res = await fetch(url, { signal: controller.signal });
if (!res.ok) return { ok: false, error: new Error(`HTTP ${res.status}`) };
return { ok: true, value: res };
} catch (err) {
// Only catch native exceptions/abort errors to wrap them into Results
return { ok: false, error: err instanceof Error ? err : new Error(String(err)) };
} finally {
clearTimeout(timeoutId);
}
}
Allocation-Free Hot Path (Object Pooling)
// Pre-allocate at startup to avoid GC pauses during execution
class BufferPool {
private pool: Uint8Array[];
constructor(size: number, bufferSize: number) {
this.pool = Array.from({ length: size }, () => new Uint8Array(bufferSize));
}
acquire(): Uint8Array | null {
return this.pool.pop() || null;
}
release(buf: Uint8Array): void {
// Reset state before returning to pool
buf.fill(0);
this.pool.push(buf);
}
}
const pool = new BufferPool(100, 1024);
function processData(target: Uint8Array): Result<void, Error> {
// Acquire from pool instead of `new Uint8Array(1024)`
const buf = pool.acquire();
if (!buf) return { ok: false, error: new Error("Pool exhausted") };
try {
// ... mutate target or buf in-place
return { ok: true, value: undefined };
} finally {
pool.release(buf);
}
}
Result Types over Exceptions
// Define explicit union returns instead of throwing
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
function parseData(input: string): Result<ParsedData, ValidationError> {
if (!input) return { ok: false, error: new ValidationError("Empty input") };
// ...
return { ok: true, value: data };
}
Critical Rules
- No Recursion - Keep control flow simple and execution bounds completely static.
- Fixed Upper Bounds - All loops, arrays, and Promises must be bounded (e.g., by size or timeouts).
- No Dynamic Memory After Init - Allocate all significant memory (Object Pools/Arrays) at startup.
- Short Functions - Hard limit of 70 lines per function. Push
ifs up, pushfors down. - Check All Returns - Never ignore the result of a
Result<T, E>or aPromise. Await everything. - Explicit Panics Only -
throwonly for programmer errors/broken invariants (like assertion failures). Use standard explicitResultreturns for operational issues. - Strict Compilation - Must use
strict: true,noUncheckedIndexedAccess: true, andnoImplicitReturns: true. - Options Interfaces - Use explicit options interfaces for configuration instead of multiple boolean/primitive arguments.
- Zero Dependencies - Strictly avoid third-party NPM dependencies outside standard library tools or extensively vetted utilities (like
zod). - Strict Naming - Add units or qualifiers at the end of variables (e.g.,
timeoutMs,latencyMaxMs).
Anti-Patterns (NEVER)
- Using
anyor explicit type assertions (as Type) to bypass the compiler - Throwing exceptions for expected business logic errors (e.g.,
throw new Error("Invalid User")) - Using unbounded
while(true)loops ornew Promise(() => {})without a reject mechanism - Accessing arrays without verifying the index exists (requires
noUncheckedIndexedAccess) - Returning implicitly or ignoring awaited variables (
void myAsyncFunc()) - Magic metaprogramming (
Proxy,Reflect, dynamically adding/deleting object propertiesdelete obj.prop)
Credits
Weekly Installs
12
Repository
thedumptruck/skillsFirst Seen
14 days ago
Security Audits
Installed on
gemini-cli4
opencode4
antigravity4
github-copilot4
codex4
kimi-cli4