TJS Interactive Examples

Try these examples in the playground! Each demonstrates a key TJS feature.

In this section

Types by Example

TJS types ARE example values - self-documenting and runtime-checkable:

// The example IS the type AND the documentation
function greet(name: 'World', times: 3): '' {
  let result = ''
  for (let i = 0; i < times; i++) {
    result = result + `Hello, ${name}! `
  }
  return result.trim()
}

// greet.__tjs shows: name: string, times: number -> string
console.log(greet('TJS', 2)) // "Hello, TJS! Hello, TJS!"

Type Declarations with Defaults

Define reusable types with the Type keyword:

// Type with default value
Type Name = 'Anonymous'
Type Count = 0
Type PositiveAge = +18  // positive number

// Use types as parameter annotations
function introduce(name: Name, age: PositiveAge): '' {
  return `Hi, I'm ${name} and I'm ${age} years old`
}

console.log(introduce('Alice', 30))
console.log(Name.default)  // 'Anonymous'

Generic Types

Runtime-checkable generics:

// Define a generic Box<T> type
Generic Box<T> {
  description: 'a boxed value'
  predicate(obj, T) {
    return typeof obj === 'object' &&
           obj !== null &&
           'value' in obj &&
           T(obj.value)
  }
}

// Instantiate with different types
const StringBox = Box('')
const NumberBox = Box(0)

console.log(StringBox.check({ value: 'hello' }))  // true
console.log(StringBox.check({ value: 123 }))      // false
console.log(NumberBox.check({ value: 42 }))       // true

Compile-Time Tests

Tests run during transpilation and evaporate from output:

Type Email {
  example: 'test@example.com'
  predicate(x) {
    return typeof x === 'string' && x.includes('@')
  }
}

// This test runs at transpile time, not runtime
test 'email validation' {
  if (!Email.check('user@example.com')) {
    throw new Error('valid email should pass')
  }
  if (Email.check('not-an-email')) {
    throw new Error('invalid email should fail')
  }
}

// The test code is stripped from the transpiled output
function sendEmail(to: Email): { sent: true } {
  return { sent: true }
}

Safety Markers

Control validation with (!) and (?):

// Default: safe function with validation
function safeAdd(a: 0, b: 0): 0 {
  return a + b
}

// (!) Unsafe: skip input validation for performance
function fastAdd(! a: 0, b: 0): 0 {
  return a + b
}

// (?) Force safe: always validate even if module is unsafe
function alwaysSafe(? data: { x: 0 }): 0 {
  return data.x * 2
}

console.log(safeAdd(1, 2))   // 3
console.log(fastAdd(1, 2))   // 3 (faster, no validation)

SafeFunction and Eval

Safe replacements for new Function() and eval(). See Safe Eval.

Monadic Error Handling

Errors are values, not exceptions:

function divide(a: 0, b: 0): 0 {
  if (b === 0) {
    return error('Division by zero')
  }
  return a / b
}

const result = divide(10, 0)

if (isError(result)) {
  console.log('Error:', result.message)
} else {
  console.log('Result:', result)
}

// Errors propagate automatically through function chains

Native Type Checking

Check platform types pragmatically:

// typeOf returns constructor names for objects
console.log(typeOf(new Map())) // 'Map'
console.log(typeOf(new Date())) // 'Date'
console.log(typeOf([1, 2, 3])) // 'array'
console.log(typeOf(null)) // 'null'

// isNativeType checks prototype chain
console.log(isNativeType(new TypeError('oops'), 'Error')) // true
console.log(isNativeType(new Map(), 'Map')) // true

Union Types

Use || for union types:

Union Status 'task status' {
  'pending' | 'active' | 'done'
}

function updateTask(id: '', status: Status): { updated: true } {
  console.log(`Task ${id} -> ${status}`)
  return { updated: true }
}

updateTask('task-1', 'active')

Unbundled Integration

TJS runs directly in the browser with no build step. Compare React (which requires bundling) with tosijs (which doesn't):

React Todo App

This would be easier with JSX, but we're not supporting JSX build magic (yet).

import { useState, createElement as h } from 'react'
import { createRoot } from 'react-dom/client'

// Without JSX, we use createElement (aliased as h for brevity)
// h(type, props, ...children)

function TodoApp() {
  const [items, setItems] = useState(['bathe the cat', 'buy milk'])
  const [newItem, setNewItem] = useState('')

  const addItem = () => {
    if (newItem !== '') {
      setItems([...items, newItem])
      setNewItem('')
    }
  }

  return h(
    'div',
    null,
    h('h1', null, 'To Do'),
    h(
      'ul',
      null,
      items.map((item, i) => h('li', { key: i }, item))
    ),
    h(
      'label',
      null,
      'New item',
      h('input', {
        placeholder: 'enter thing to do',
        value: newItem,
        onChange: (e) => setNewItem(e.target.value),
      }),
      h(
        'button',
        {
          disabled: !newItem,
          onClick: addItem,
        },
        'Add'
      )
    )
  )
}

const root = document.createElement('div')
root.setAttribute('id', 'root')
document.body.append(root)
createRoot(root).render(h(TodoApp))

tosijs Todo App

import { elements, tosi } from 'tosijs'

const { todoApp } = tosi({
  todoApp: {
    items: ['bathe the cat', 'buy milk'],
    newItem: '',
    addItem() {
      if (todoApp.newItem !== '') {
        todoApp.items.push(String(todoApp.newItem))
        todoApp.newItem = ''
      }
    },
  },
})

const { h1, ul, template, li, label, input, button } = elements

document.body.append(
  h1('To Do'),
  ul(
    {
      bindList: {
        value: todoApp.items,
      },
    },
    template(li({ bindText: '^' }))
  ),
  label(
    'New item',
    input({ placeholder: 'enter thing to do', bindValue: todoApp.newItem }),
    button({ bindEnabled: todoApp.newItem, onClick: todoApp.addItem }, 'Add')
  )
)

The tosijs version:

Running TJS

# CLI commands
tjs check file.tjs      # Type check
tjs run file.tjs        # Execute
tjs emit file.tjs       # Output JavaScript
tjs emit --debug file.tjs  # Include source locations

# Or use the Bun plugin for native support
bun --preload ./src/bun-plugin/tjs-plugin.ts file.tjs