# TypeScript AST Transformers Guide
This guide covers two main transformers that enable reactive programming
patterns:
1. **OpaqueRef Transformer** - Transforms operations on `OpaqueRef` types to use
reactive primitives
2. **Schema Transformer** - Converts TypeScript type definitions to JSON Schema
at compile time
## Core Concept: Opt-in Transformation
Both transformers require the `/// ` directive at the top of files
to be transformed:
```typescript
///
import { cell, derive, ifElse, toSchema } from "commontools";
```
Without this directive, the transformers will not modify your code.
## OpaqueRef Transformer
### What is OpaqueRef?
OpaqueRef is a type representing reactive values in CommonTools. It wraps:
- The actual value type (e.g., `string`, `number`, `{ name: string }`)
- Methods for reactivity (`.get()`, `.set()`, etc.) used inside
lift/handler/derive functions
```typescript
const count = Cell.of(0); // count is OpaqueRef
```
### Important Scope Limitation
**OpaqueRef transformations only apply within JSX expressions.** Statement-level
transformations (like if statements, loops, etc.) are not supported because they
require complex control flow analysis and handling of side effects.
```typescript
// ✅ Transformed - JSX expression context
{count + 1}
; // →
{derive(count, _v => _v + 1)}
// ❌ Not transformed - statement context
if (count > 5) { // Statements with OpaqueRef are not transformed
console.log("High");
}
```
### Core Transformation Patterns (JSX Expression Context Only)
The following transformations apply **only within JSX expressions**. OpaqueRef
operations in regular TypeScript statements are not transformed.
#### 1. Binary Operations in JSX
Operations on OpaqueRef values inside JSX are wrapped in `derive()`:
```typescript
// Input - JSX context
```
Supported operators: `+`, `-`, `*`, `/`, `%`, `>`, `<`, `>=`, `<=`, `==`, `===`,
`!=`, `!==`
#### 2. Ternary Conditionals in JSX
When a ternary operator's condition is an OpaqueRef inside JSX, it transforms to
`ifElse()`:
```typescript
// Input - JSX context
{isActive ? "on" : "off"}
// Output
{ifElse(isActive, "on", "off")}
```
Note: This transformation only occurs when the condition (`isActive`) is an
OpaqueRef type.
#### 3. Property Access and Method Calls in JSX
When accessing properties or calling methods on OpaqueRef values inside JSX:
```typescript
// Input - JSX context
```
Key principle: Direct property access on an OpaqueRef object returns another
OpaqueRef, while operations on the value require `derive()`.
#### 4. Direct OpaqueRef References in JSX
Direct OpaqueRef references inside JSX are preserved as-is, allowing the UI
framework to handle reactivity:
```typescript
// Input & Output (no transformation needed)
{count}
{user.name}
```
#### 5. Array and Object Literals in JSX
Each element/property is transformed independently when used in JSX:
```typescript
// Input - JSX context
// Output
_v + 1), derive(price, (_v) => _v * 2)]} />
_v + 1),
total: derive({ price, tax }, ({ price: _v1, tax: _v2 }) => _v1 * _v2),
}} />
```
### Current Limitations
1. **Statement-Level Transformations** - Not supported:
```typescript
// These patterns in regular statements are NOT transformed
if (count > 5) { ... } // ❌ If statements
while (count < 10) { ... } // ❌ Loops
const result = count + 1; // ❌ Variable declarations outside JSX
```
**Why:** Statement transformations require complex control flow analysis and
handling of side effects. OpaqueRef transformations are limited to JSX
expression contexts where the transformation is straightforward.
2. **Array Methods** - Not yet supported:
```typescript
const items = Cell.of([1, 2, 3]);
const doubled = items.map((x) => x * 2); // ❌ Not transformed
const filtered = items.filter((x) => x > 2); // ❌ Not transformed
```
**Why:** Array methods require special handling to maintain reactivity
through the callback function.
3. **Async Operations** - Not yet supported:
```typescript
const url = Cell.of("https://api.example.com");
const data = await fetch(url); // ❌ Not transformed
```
**Why:** Async operations with OpaqueRef require special handling for promise
resolution and error states.
4. **Destructuring** - Extracts values, losing reactivity:
```typescript
const user = Cell.of({ name: "John", age: 25 });
const { name, age } = user; // ❌ name and age are plain values, not OpaqueRef
```
**Why:** Destructuring extracts the current value, breaking the reactive
chain.
## Schema Transformer
### Basic Usage
Transform TypeScript interfaces to JSON Schema at compile time:
```typescript
///
import { toSchema } from "commontools";
interface User {
name: string;
age: number;
email?: string;
}
const userSchema = toSchema();
// This is transformed at compile time to:
const userSchema = {
type: "object",
properties: {
name: { type: "string" },
age: { type: "number" },
email: { type: "string" },
},
required: ["name", "age"],
} as const satisfies JSONSchema;
```
### Handler and Recipe Transformations
The schema transformer also converts `handler` and `recipe` calls with type
arguments:
```typescript
///
import { Cell, handler, recipe } from "commontools";
// Handler with type arguments
const myHandler = handler }>(
(event, state) => {
state.count.set(state.count.get() + 1);
},
);
// Recipe with type argument
export default recipe("Counter", (state) => {
return { [UI]:
),
items: state.items,
filter: state.filter,
};
},
);
```
## Notes on Implementation
- Start with the simplest transformations first
- Ensure each phase is fully tested before moving to the next
- Consider performance implications of transformation patterns
- Maintain clear separation between transformer concerns
- Document edge cases and limitations clearly