Safety & Validation
Three levels of runtime validation. Choose per-file or per-function.
safety inputs
/#
## The Problem
JavaScript functions silently accept wrong types:
function greet(name) { return `Hello, ${name}!` }
greet(42) // "Hello, 42!" — no error, just wrong
TypeScript catches this at compile time but the checks
vanish at runtime. Production gets no protection.
## TJS Safety Levels
The `safety` directive sets the default for the whole file:
| Directive | Validates | Use case |
|-----------|-----------|----------|
| `safety none` | Nothing | Metadata only, zero overhead |
| `safety inputs` | Function inputs | Default — catches bad callers |
| `safety all` | Inputs + outputs | Debug mode, catches internal bugs |
Per-function overrides:
- `!` = unsafe (skip validation): `function fast(! x: 0) { }`
- `?` = safe (force validation): `function careful(? x: 0) { }`
#/
// --- Input validation catches bad callers ---
function sayHello(name: 'World'): 'Hello, World!' {
return `Hello, ${name}!`
}
test 'valid input works normally' {
expect(sayHello('Alice')).toBe('Hello, Alice!')
}
test 'wrong type returns an error, not garbage' {
const result = sayHello(42)
// Not "Hello, 42!" — an actual error value
expect(result instanceof Error).toBe(true)
}
test 'null returns an error' {
const result = sayHello(null)
expect(result instanceof Error).toBe(true)
}
// --- Numeric narrowing catches subtle bugs ---
function setAge(age: +0): +0 {
return age
}
test 'positive integer accepted' {
expect(setAge(25)).toBe(25)
}
test 'negative rejected' {
expect(setAge(-1) instanceof Error).toBe(true)
}
test 'float rejected (age must be integer)' {
expect(setAge(25.5) instanceof Error).toBe(true)
}
// --- Unsafe functions skip validation (fast path) ---
function fastAdd(! a: 0, b: 0): 0 {
return a + b
}
test 'unsafe function skips checks (trusted callers only)' {
expect(fastAdd(3, 4)).toBe(7)
// No type error even with wrong type — validation skipped
expect(fastAdd('a', 'b')).toBe('ab')
}
// --- Errors propagate through function chains ---
function cleanName(name: ''): '' {
return name.trim()
}
function upperCase(name: ''): '' {
return name.toUpperCase()
}
test 'errors propagate automatically' {
// cleanName(42) returns error -> upperCase receives error -> returns error
const result = upperCase(cleanName(42))
expect(result instanceof Error).toBe(true)
}
test 'valid input flows through the chain' {
expect(upperCase(cleanName('alice'))).toBe('ALICE')
}
// --- Try without catch: convert exceptions to errors ---
function parseJSON(s: '{}'):! {} {
try {
return JSON.parse(s)
}
}
test 'valid JSON parses normally' {
const result = parseJSON('{"ok":true}')
expect(result.ok).toBe(true)
}
test 'invalid JSON returns error instead of throwing' {
const result = parseJSON('not json')
expect(result instanceof Error).toBe(true)
// No try/catch needed by the caller!
}
console.log('Valid:', sayHello('World'))
console.log('Invalid:', sayHello(42))
console.log('Parse ok:', parseJSON('{"x":1}'))
console.log('Parse bad:', parseJSON('nope'))