Tutorial 1: Getting Started with USL¶
Duration: 30 minutes
Prerequisites: Basic programming knowledge
What you'll build: A simple task management application with user authentication
Overview¶
In this tutorial, you'll learn the fundamentals of USL by building a working task manager. You'll:
- Define domain entities (User, Task)
- Create authorization policies
- Implement state transitions
- Generate production-ready code
Installation¶
First, install the USL compiler:
Verify installation:
Project Setup¶
Create a new USL project:
This creates:
task-manager/
├── task-manager.usl # Main specification
├── usl.toml # Project configuration
└── .gitignore
Step 1: Define Domain Entities¶
Open task-manager.usl and define your domain model:
domain TaskManager {
// User entity
entity User {
id: UserId @primary
email: Email @unique
name: String
createdAt: Timestamp
}
// Task entity
entity Task {
id: TaskId @primary
title: String
description: String
ownerId: UserId
status: TaskStatus
createdAt: Timestamp
// Business rule: completed tasks must have a title
invariant completed_has_title {
this.status == TaskStatus.Completed -> len(this.title) > 0
}
}
// Task status enumeration
enum TaskStatus {
Todo
InProgress
Completed
}
}
Key Concepts¶
- Entities: Core domain objects with identity
- @primary: Marks the unique identifier field
- @unique: Enforces uniqueness constraint
- Invariants: Business rules that must always hold
Type Safety
USL's type system catches errors at compile time. UserId, TaskId, and Email are custom types that prevent mixing different ID types.
Step 2: Create Authorization Policies¶
Add access control rules below your domain definition:
policy TaskPolicy {
actor user: User
// Anyone can create their own tasks
rule create_task {
true // No restrictions on task creation
}
// Only task owners can edit their tasks
rule edit_task(task: Task) {
user.id == task.ownerId
}
// Only task owners can delete their tasks
rule delete_task(task: Task) {
user.id == task.ownerId
}
// Everyone can view tasks (for now)
rule view_task(task: Task) {
true
}
}
Policy Structure¶
- actor: The user performing the action
- rule: A named authorization check
- Parameters: Rules can reference entities to make decisions
Totality Checking
USL ensures all service actions are covered by policy rules. Missing rules cause compilation errors.
Step 3: Model Task Lifecycle¶
Define how tasks transition between states:
behavior TaskLifecycle for Task {
initial state Todo
state Todo {
on start -> InProgress
effects {
this.status = TaskStatus.InProgress
}
}
state InProgress {
on complete -> Completed
requires len(this.title) > 0
effects {
this.status = TaskStatus.Completed
}
on abandon -> Todo
effects {
this.status = TaskStatus.Todo
}
}
state Completed {
on reopen -> Todo
effects {
this.status = TaskStatus.Todo
}
}
}
Behavior Features¶
- initial state: Where new tasks begin
- transitions: Allowed state changes
- requires: Preconditions (guards)
- effects: State modifications
Formal Verification
USL proves that all transitions preserve invariants. If completed_has_title could be violated, compilation fails.
Step 4: Define Service API¶
Create the external API for your application:
service TaskService {
action createTask(title: String, description: String) -> Task
enforces TaskPolicy.create_task
effects { Write(Task) }
implementation {
let task = Task {
id: generateId(),
title: title,
description: description,
ownerId: context.user.id,
status: TaskStatus.Todo,
createdAt: now()
}
store(task)
return task
}
action updateTask(taskId: TaskId, newTitle: String, newDescription: String) -> Task
enforces TaskPolicy.edit_task(task)
effects { Write(Task) }
implementation {
let task = load(Task, taskId)
task.title = newTitle
task.description = newDescription
store(task)
return task
}
action deleteTask(taskId: TaskId) -> Void
enforces TaskPolicy.delete_task(task)
effects { Delete(Task) }
implementation {
let task = load(Task, taskId)
delete(task)
}
action getTask(taskId: TaskId) -> Task
enforces TaskPolicy.view_task(task)
effects { Read(Task) }
implementation {
return load(Task, taskId)
}
action listTasks() -> List[Task]
enforces TaskPolicy.view_task(task)
effects { Read(Task) }
implementation {
return loadAll(Task)
}
}
Service Components¶
- action: External operation
- enforces: Policy rule to check
- effects: Side effects declaration
- implementation: Business logic
Step 5: Compile and Generate Code¶
Compile your USL specification:
Generate TypeScript backend code:
This creates:
generated/
├── entities/
│ ├── User.ts
│ └── Task.ts
├── policies/
│ └── TaskPolicy.ts
├── services/
│ └── TaskService.ts
└── api/
└── openapi.yaml
Step 6: Run Your Application¶
Install dependencies:
Start the development server:
Your API is now running at http://localhost:3000!
Test the API¶
Create a task:
curl -X POST http://localhost:3000/api/tasks \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"title": "Learn USL",
"description": "Complete the getting started tutorial"
}'
List tasks:
What You've Learned¶
✅ Domain modeling with entities and invariants
✅ Policy-driven security with compile-time checks
✅ State machine behavior with formal verification
✅ Service layer design with effect tracking
✅ Code generation for production deployment
Next Steps¶
Continue to Tutorial 2: Domain Modeling to learn:
- Advanced type system features
- Complex invariants
- Relationships between entities
- Computed fields and validations
Troubleshooting¶
Compilation Errors¶
If you see USL-REF-001: Undefined entity:
- Check spelling of entity names
- Ensure entities are defined before use
Policy Errors¶
If you see USL-POL-008: Policy not total:
- Add rules for all service actions
- Use
deny { false }to explicitly deny actions
Generation Errors¶
If code generation fails:
- Verify
usl.tomlhas correct target configuration - Check write permissions in output directory