Full-Stack Demo: Todo API

Complete REST-style Todo API with persistence

TjsCompat
/#
## Todo API Service

A REST-style API for todo management.
Demonstrates a more complete service pattern.
#/

// Simulated persistence layer
const todos = new Map()
let nextId = 1

// Types are inferred from function signatures below
// Todo: { id: 0, title: '', completed: false, createdAt: '' }
// CreateInput: { title: 'Buy milk' }
// UpdateInput: { id: 1, title: 'Buy milk', completed: false }

// POST /todos - Create
export function createTodo(input: { title: 'New todo' }):! { id: 0, title: '', completed: false, createdAt: '' } {
  const todo = {
    id: nextId++,
    title: input.title,
    completed: false,
    createdAt: new Date().toISOString()
  }
  todos.set(todo.id, todo)
  return todo
}

// GET /todos/:id - Read one (returns empty if not found)
export function getTodo(input: { id: 1 }):! { id: 0, title: '', completed: false, createdAt: '' } {
  return todos.get(input.id) || { id: 0, title: '', completed: false, createdAt: '' }
}

// GET /todos - Read all (with optional filter)
export function listTodos(input = { completed: false }):! { todos: [{ id: 0, title: '', completed: false, createdAt: '' }] } {
  let items = [...todos.values()]

  if (input.completed !== undefined) {
    items = items.filter(t => t.completed === input.completed)
  }

  return { todos: items }
}

// PUT /todos/:id - Update (returns empty if not found)
export function updateTodo(input: { id: 1, title: '', completed: false }):! { id: 0, title: '', completed: false, createdAt: '' } {
  const existing = todos.get(input.id)
  if (!existing) return { id: 0, title: '', completed: false, createdAt: '' }

  const updated = {
    ...existing,
    title: input.title ?? existing.title,
    completed: input.completed ?? existing.completed
  }
  todos.set(input.id, updated)
  return updated
}

// DELETE /todos/:id - Delete
export function deleteTodo(input: { id: 1 }):! { deleted: true } {
  const existed = todos.has(input.id)
  todos.delete(input.id)
  return { deleted: existed }
}

// PATCH /todos/:id/toggle - Toggle completion (returns empty if not found)
export function toggleTodo(input: { id: 1 }):! { id: 0, title: '', completed: false, createdAt: '' } {
  const todo = todos.get(input.id)
  if (!todo) return { id: 0, title: '', completed: false, createdAt: '' }

  todo.completed = !todo.completed
  return todo
}

// DELETE /todos/completed - Clear completed
export function clearCompleted(input: {}):! { cleared: 0 } {
  let cleared = 0
  for (const [id, todo] of todos) {
    if (todo.completed) {
      todos.delete(id)
      cleared++
    }
  }
  return { cleared }
}

// Tests
test('CRUD operations work') {
  const todo = createTodo({ title: 'Test todo' })
  expect(todo.id).toBeGreaterThan(0)
  expect(todo.completed).toBe(false)

  const fetched = getTodo({ id: todo.id })
  expect(fetched?.title).toBe('Test todo')

  const toggled = toggleTodo({ id: todo.id })
  expect(toggled?.completed).toBe(true)

  const deleted = deleteTodo({ id: todo.id })
  expect(deleted.deleted).toBe(true)
}

// Demo
console.log('=== Todo API Demo ===\\n')

// Create todos
createTodo({ title: 'Learn TJS' })
createTodo({ title: 'Build something cool' })
createTodo({ title: 'Ship it' })

console.log('Created 3 todos')
console.log('All:', listTodos({}))

// Complete first one
const first = listTodos({}).todos[0]
toggleTodo({ id: first.id })
console.log('\\nToggled first todo')
console.log('Completed:', listTodos({ completed: true }))
console.log('Pending:', listTodos({ completed: false }))

// Clear completed
console.log('\\nClearing completed...')
console.log(clearCompleted({}))
console.log('Remaining:', listTodos({}))