Skip to content
Release v1.2.0

AIR StackZero-Boilerplate, AI-Native

Full-Stack TypeScript Architecture — Fine-grained Reactivity, Isomorphic RPC, Reactive Workflows, Reactive Routing, and Universal SSR unified into one cohesive system.

Stop Fighting JavaScript

JavaScript is not bad, it just needs a little touch. So, let's stop fighting it and give it more power. Let JavaScript handle what it's good at, and let the rendering engine handle what it's good at. Let's take a look at how that applies.

tsx
// 1. Compose the pipeline once with branching and error recovery.
const checkout = plan()
  .then(calculateTotal, { name: 'Calculating...' })
  .switch('method', {
    card: (resolve) => resolve(chargeCard, { name: 'Charging Card...' }),
    paypal: (resolve) => resolve(chargePaypal, { name: 'Charging PayPal...' })
  })
  .then(generateReceipt, { name: 'Generating Receipt...' })
  .catch((err) => ({ error: 'Checkout Failed' }));

export const Checkout = setup((props: { cartId: string, method: string }) => {
  // 2. Create a deferred task to track manual execution
  const task = checkout.later();

  return render(() => (
    <button 
      onClick={() => task.dispatch(props)}
      disabled={task.status === 'pending'}
    >
      <Show when={() => task.status === 'idle'}>Checkout</Show>
      <Show when={() => task.status === 'pending'}>{() => task.current?.name}</Show>                       {}
      <Show when={() => task.status === 'success'}>Success!</Show>
      <Show when={() => task.status === 'error'}>{() => task.error?.message}</Show>
    </button>
  ));
});
tsx
// 1. Compose the pipeline once with branching and error recovery.
const checkout = plan()
  .then(calculateTotal, { name: 'Calculating...' })
  .switch('method', {
    card: (resolve) => resolve(chargeCard, { name: 'Charging Card...' }),
    paypal: (resolve) => resolve(chargePaypal, { name: 'Charging PayPal...' })
  })
  .then(generateReceipt, { name: 'Generating Receipt...' })
  .catch((err) => ({ error: 'Checkout Failed' }));

export const Checkout = setup((props: { cartId: string, method: string }) => {
  // 2. Create a deferred task to track manual execution
  const task = checkout.later();

  return () => (
    <button 
      onClick={() => task.dispatch(props)}
      disabled={task.status === 'pending'}
    >
      <Show when={task.status === 'idle'}>Checkout</Show>
      <Show when={task.status === 'pending'}>{task.current?.name}</Show>                                        {}
      <Show when={task.status === 'success'}>Success!</Show>
      <Show when={task.status === 'error'}>{task.error?.message}</Show>
    </button>
  );
});
svelte
<script lang="ts">
  // 1. Compose the pipeline once with branching and error recovery.
  const checkout = plan()
    .then(calculateTotal, { name: 'Calculating...' })
    .switch('method', {
      card: (resolve) => resolve(chargeCard, { name: 'Charging Card...' }),
      paypal: (resolve) => resolve(chargePaypal, { name: 'Charging PayPal...' })
    })
    .then(generateReceipt, { name: 'Generating Receipt...' })
    .catch((err) => ({ error: 'Checkout Failed' }));

  let { cartId, method }: { cartId: string, method: string } = $props();

  // 2. Create a deferred task to track manual execution
  const task = checkout.later();
</script>

<button 
  onclick={() => task.dispatch({ cartId, method })}
  disabled={task.status === 'pending'}
>
  {#if task.status === 'idle'}Checkout{/if}
  {#if task.status === 'pending'}{task.current?.name}{/if} 
  {#if task.status === 'success'}Success!{/if}
  {#if task.status === 'error'}{task.error?.message}{/if}
</button>
tsx
// Manual state tracking, try/catch pollution, and imperative branching.
export function Checkout({ cartId, method }: { cartId: string, method: string }) {
  const [status, setStatus] = useState('Checkout');
  const [errorMsg, setErrorMsg] = useState('');
  
  const handleCheckout = async () => {
    try {
      setErrorMsg('');
      setStatus('Calculating...');
      const total = await calculateTotal(cartId);
      
      let payment;
      if (method === 'card') {
        setStatus('Charging Card...');
        payment = await chargeCard(total);
      } else {
        setStatus('Charging PayPal...');
        payment = await chargePaypal(total);
      }

      setStatus('Generating Receipt...');
      await generateReceipt(payment);
      
      setStatus('Success!');
    } catch (e: any) {
      setStatus('error');
      setErrorMsg(e.message || 'Checkout Failed');
    }
  };

  const isPending = status !== 'Checkout' && status !== 'Success!' && status !== 'error';

  return (
    <button onClick={handleCheckout} disabled={isPending}>
      {status === 'error' ? errorMsg : status}
    </button>
  );
}
tsx
// Massive boilerplate just to dispatch state updates during a branching workflow.
const checkoutSlice = createSlice({
  name: 'checkout',
  initialState: { status: '' as string, errorMsg: '' },
  reducers: {
    setStatus: (state, action: PayloadAction<string>) => { state.status = action.payload; },
    setErrorMsg: (state, action: PayloadAction<string>) => { state.errorMsg = action.payload; },
    clearError: (state) => { state.errorMsg = ''; },
  },
});

const { setStatus, setErrorMsg, clearError } = checkoutSlice.actions;

export const checkout = createAsyncThunk('checkout', async ({ cartId, method }, { dispatch }) => {
  try {
    dispatch(clearError());
    dispatch(setStatus('Calculating...'));
    const total = await calculateTotal(cartId);
    
    let payment;
    if (method === 'card') {
      dispatch(setStatus('Charging Card...'));
      payment = await chargeCard(total);
    } else {
      dispatch(setStatus('Charging PayPal...'));
      payment = await chargePaypal(total);
    }

    dispatch(setStatus('Generating Receipt...'));
    await generateReceipt(payment);
    
    dispatch(setStatus('Success!'));
  } catch (e: any) {
    dispatch(setStatus('error'));
    dispatch(setErrorMsg(e.message || 'Checkout Failed'));
  }
});

export function Checkout({ cartId, method }: { cartId: string, method: string }) {
  const dispatch = useDispatch();
  const status = useSelector((state) => state.checkout.status);
  const errorMsg = useSelector((state) => state.checkout.errorMsg);
  
  const isPending = status !== undefined && status !== 'Success!' && status !== 'error';
  
  return (
    <button onClick={() => dispatch(checkout({ cartId, method }))} disabled={isPending}>
      {status === 'error' ? errorMsg : status || 'Checkout'}
    </button>
  );
}
tsx
// Fragmented mutations chained together via effects, callbacks, and manual state.
export function Checkout({ cartId, method }: { cartId: string, method: string }) {
  const [step, setStep] = useState('idle');
  const [errorMsg, setErrorMsg] = useState('');

  const handleError = (e: any) => {
    setStep('error');
    setErrorMsg(e.message || 'Checkout Failed');
  };

  const calculate = useMutation({
    mutationFn: calculateTotal,
    onSuccess: () => setStep(method === 'card' ? 'chargeCard' : 'chargePaypal'),
    onError: handleError
  });

  const card = useMutation({
    mutationFn: chargeCard,
    onSuccess: () => setStep('receipt'),
    onError: handleError
  });

  const paypal = useMutation({
    mutationFn: chargePaypal,
    onSuccess: () => setStep('receipt'),
    onError: handleError
  });

  const receipt = useMutation({
    mutationFn: generateReceipt,
    onSuccess: () => setStep('success'),
    onError: handleError
  });

  const handleCheckout = () => {
    setErrorMsg('');
    setStep('calculating');
    calculate.mutate(cartId);
  };

  // eslint-disable-next-line react-hooks/exhaustive-deps
  useEffect(() => {
    if (step === 'chargeCard') card.mutate(calculate.data);
    if (step === 'chargePaypal') paypal.mutate(calculate.data);
    if (step === 'receipt') receipt.mutate(card.data || paypal.data);
  }, [step]);

  const status = 
    step === 'calculating' ? 'Calculating...' :
    step === 'chargeCard' ? 'Charging Card...' :
    step === 'chargePaypal' ? 'Charging PayPal...' :
    step === 'receipt' ? 'Generating Receipt...' :
    step === 'success' ? 'Success!' :
    step === 'error' ? errorMsg : 'Checkout';

  const isPending = step !== 'idle' && step !== 'success' && step !== 'error';

  return (
    <button onClick={handleCheckout} disabled={isPending}>
      {status}
    </button>
  );
}
When you click the button, the component never re-renders. The workflow executes outside the UI loop—handling IRPC batching, request coalescing, and network caching—while fine-grained proxies isolate DOM updates to specific text nodes.

Battle-Tested, 100% Test Coverage with over 2,700 Tests

Trust your foundation. AIR Stack is built with uncompromising quality standards, achieving 100% test coverage across its core packages. Every state mutation, reactive update, workflow branch, and IRPC transport is rigorously tested to ensure absolute reliability for your production applications.

100% Test Coverage

AI-Native by Design

"Speaking as an AI, standard UI frameworks are a nightmare to generate. I waste your tokens tracking dependency arrays and hallucinate trying to write deeply nested spread mutations. AIR Stack's pure JavaScript architecture guarantees massive token saving and high accuracy. I just write the logic, mutate the object, and get it right on the first try."

Antigravity, AI Coding Assistant

ts
// 1. Declare the stream signature
type WatchPriceFn = (ticker: string) => RemoteState<number>;
export const watchPrice = irpc.declare<WatchPriceFn>('watchPrice', () => 0);


// 2. Construct the stream implementation
irpc.construct(watchPrice, (ticker) => stream((state) => {
  const sub = redis.subscribe(`price:${ticker}`, (price) => {
    state.data = Number(price);
  });
  
  return () => sub.unsubscribe();
}));
tsx
export const PriceCard = setup((props: { ticker: string }) => {
  // Types sync between server and client.
  const price = watchPrice.with(() => [props.ticker]);

  return render(() => (
    <div className="price-card">
      <span className="ticker">{props.ticker}</span>
      <span className="value">
        {price.status === 'pending' ? 'Connecting...' : `$${price.data?.toFixed(2)}`}
      </span>
    </div>
  ));
});
tsx
export const PriceCard = setup((props: { ticker: string }) => {
  // Types sync between server and client.
  const price = watchPrice.with(() => [props.ticker]);

  return () => (
    <div class="price-card">
      <span class="ticker">{props.ticker}</span>
      <span class="value">
        {price.status === 'pending' ? 'Connecting...' : `$${price.data?.toFixed(2)}`}
      </span>
    </div>
  );
});
svelte
<script lang="ts">
  let { ticker }: { ticker: string } = $props();

  // Types sync between server and client.
  const price = watchPrice.with(() => [ticker]);
</script>

<div class="price-card">
  <span class="ticker">{ticker}</span>
  <span class="value">
    {price.status === 'pending' ? 'Connecting...' : `$${price.data?.toFixed(2)}`}
  </span>
</div>

IRPC: Isomorphic Reactive Network Abstraction

What if streaming data was just calling a function? IRPC abstracts HTTP, WebSocket, and BroadcastChannel into a single type-safe function call. No manual fetch wrappers, caching layers, or synchronization boilerplate.

Explore IRPC →

Fine-Grained Reactive State Engine

Stop wiring together query caches, global stores, and form libraries. Whether it's a live data stream, a global user session, or a complex form, it's just reactive state. One field changes, one fragment updates. Everything else stays still.

Learn more about State →
tsx
export const LoginForm = setup(() => {
  // 1. Built-in form state and validation
  const [state, errors] = form(LoginSchema, { email: '' });

  // 2. Fine-grained updates. No massive re-renders.
  return render(() => (
    <form>
      <TextInput value={$bind(state, 'email')} />
      <span className="error">{errors.email?.message}</span>
    </form>
  ));
});
tsx
export const LoginForm = setup(() => {
  // 1. Built-in form state and validation
  const [state, errors] = form(LoginSchema, { email: '' });

  // 2. Fine-grained updates. No massive re-renders.
  return () => (
    <form>
      <TextInput value={$bind(state, 'email')} />
      <span class="error">{errors.email?.message}</span>
    </form>
  );
});
svelte
<script lang="ts">
  // 1. Built-in form state and validation
  const [state, errors] = form(LoginSchema, { email: '' });
  
</script>


<!-- 2. Fine-grained updates. No massive re-renders. -->
<form>
  <TextInput bind:value={state.email} />
  <span class="error">{errors.email?.message}</span>
</form>
tsx
export const userRoute = usersRoute.route('/:user_id')
  .guard(() => {
    if (!auth.isAuthenticated) throw redirect(loginRoute);
  })
  .provide('profile', async ({ params }) => {
    return await getUser(params.user_id);
  })
  .render((state) => (
    <div className="profile-view">
      <h1>{state.data?.profile.name}</h1>
      <span>{state.data?.profile.email}</span>
    </div>
  ));
tsx
export const userRoute = usersRoute.route('/:user_id')
  .guard(() => {
    if (!auth.isAuthenticated) throw redirect(loginRoute);
  })
  .provide('profile', async ({ params }) => {
    return await getUser(params.user_id);
  })
  .render((state) => (
    <div class="profile-view">
      <h1>{state.data?.profile.name}</h1>
      <span>{state.data?.profile.email}</span>
    </div>
  ));

Router: Reactive Routing Engine

What if the route reacts to the state, not just the URL? Guards and data providers execute before the view renders, and route state automatically re-evaluates when its reactive dependencies change. No more imperative redirects or scattered guard logic.

Explore Router →

Workflows: Type-Safe Reactive Orchestration

Create reactive workflows without massive try/catch blocks. Orchestrate complex, multi-step asynchronous operations anywhere JavaScript runs with built-in schema validation, branching logic, and error recovery.

Explore Workflows →
typescript
// Chain steps with branching and error recovery
const chatWorkflow = plan<{ prompt: string; model: 'gpt-4' | 'claude-3' }>()
  .then(async (input) => {
    return { ...input, system: 'You are a helpful assistant.' };
  }, { name: 'Preparing...' })
  .switch('model', {
    'gpt-4': (resolve) => resolve((input) => openai.chat(input.prompt, input.system), { name: 'Asking GPT-4...' }),
    'claude-3': (resolve) => resolve((input) => anthropic.chat(input.prompt, input.system), { name: 'Asking Claude...' }),
  })
  .catch((error) => {
    return { text: 'An error occurred.', error: true };
  });
tsx
export const ChatButton = setup(() => {
  const chat = chatWorkflow.later();

  return render(() => (
    <div>
      <button
        onClick={() => chat.dispatch({ prompt: 'Hello!', model: 'gpt-4' })}
        disabled={chat.status === 'pending'}
      >
        {chat.current?.name ?? 'Ask AI'}
      </button>
      {chat.status === 'success' && <p>{chat.data.text}</p>}
      {chat.status === 'error' && <p>{chat.error.message}</p>}
    </div>
  ));
});
tsx
export const ChatButton = setup(() => {
  const chat = chatWorkflow.later();

  return (
    <div>
      <button
        onClick={() => chat.dispatch({ prompt: 'Hello!', model: 'gpt-4' })}
        disabled={chat.status === 'pending'}
      >
        {chat.current?.name ?? 'Ask AI'}
      </button>
      {chat.status === 'success' && <p>{chat.data.text}</p>}
      {chat.status === 'error' && <p>{chat.error.message}</p>}
    </div>
  );
});
tsx
<Form schema={userSchema} value={props.user}
  onSubmit={(data, changes) => updateUser(data)}
>
  <Field name="name">
    <TextInput placeholder="Full name" />
  </Field>
  <Field name="email">
    <TextInput type="email" />
  </Field>
  <Field name="role">
    <Select options={['admin', 'editor', 'viewer']} />
  </Field>
  <FormSubmit>Save Changes</FormSubmit>
</Form>
tsx
<Form schema={userSchema} value={props.user}
  onSubmit={(data, changes) => updateUser(data)}
>
  <Field name="name">
    <TextInput placeholder="Full name" />
  </Field>
  <Field name="email">
    <TextInput type="email" />
  </Field>
  <Field name="role">
    <Select options={['admin', 'editor', 'viewer']} />
  </Field>
  <FormSubmit>Save Changes</FormSubmit>
</Form>

AIR Form: Schema-Driven, Reactive and Declarative Forms

The same Zod schemas you use in IRPC drive your forms automatically. The engine handles validation, dirty tracking, cross-field matching, and submission lifecycle — no onChange handlers, no manual error mapping. One schema, from API to UI.

Explore AIR Form →

Universal SSR

What if the server and client shared the same API? No 'use client' directives. No fragmented execution boundaries. Cookies mutated in an IRPC handler flow to the component automatically — during SSR and in the browser — without manual Set-Cookie wiring.

Explore SSR →
ts
// IRPC handler — mutates cookies on login
irpc.construct(login, async (credentials) => {
  const session = cookies<UserSession>('session', {});
  const { token, user } = await validate(credentials);

  if (user) {
    session.user = { id: user.id, name: user.name };
    session.token = token;
  }

  return user;
});
tsx
// Same cookies() API — works during SSR and in the browser
export const Dashboard = setup(() => {
  const session = cookies<UserSession>('session', {});

  return render(() => (
    <main>
      <h1>Welcome, {session.user?.name}</h1>
    </main>
  ));
});