<!-- Markdown mirror of https://justscale.sh/docs/overview/introduction -->

# Introduction

A TypeScript framework where domain code describes what happens, not how.

## The Problem

Modern backend development has an infrastructure obsession. We spend more time wiring databases, managing connections, handling transactions, configuring authentication, and dealing with deployment than writing actual business logic.

Look at any backend codebase. The core logic — what the application *actually does* — is a small fraction of the code. The rest is infrastructure: how data gets stored, how services communicate, how errors propagate, how state survives restarts.

We've accepted this as normal. It isn't.

## The T-Shirt Test

There's a running joke about programmer t-shirts with silly pseudocode:

python

```python
while alive:
  if not coffee:
    get_coffee()
  drink_coffee()
  work()
```

We laugh because "real code doesn't work like this." But why doesn't it? The logic is clear. The intent is obvious. We understand it instantly.

Now consider a poker game. The rules are simple: deal cards, bet in rounds, reveal the winner. Here's what that looks like in JustScale:

TypeScript

```typescript
// Pre-flop
await bettingRound('preflop');
const afterPreflop = lastStanding();
if (afterPreflop) return afterPreflop;

// Flop
deck.pop(); // burn
exports.communityCards.push(deck.pop()!, deck.pop()!, deck.pop()!);
await bettingRound('flop');
const afterFlop = lastStanding();
if (afterFlop) return afterFlop;

// Turn
deck.pop(); // burn
exports.communityCards.push(deck.pop()!);
await bettingRound('turn');
const afterTurn = lastStanding();
if (afterTurn) return afterTurn;

// River
deck.pop(); // burn
exports.communityCards.push(deck.pop()!);
await bettingRound('river');

// Showdown
const winner = determineWinner(communityCards, remaining);
```

💡Tip

This isn't pseudocode. This is from a real process that survives server restarts, works across multiple instances, and scales to thousands of concurrent games. Each `bettingRound` suspends the process while waiting for player actions — for minutes, hours, or until a timeout. The compiler transforms it into a resumable state machine.

JustScale's goal: **make the t-shirt code real.** Your domain logic should read like a description of what happens, not a manual for how infrastructure makes it happen.

## What Makes JustScale Different

### Durable Processes as Plain Code

Long-running workflows — subscriptions, order fulfillment, game sessions — are written as plain TypeScript. The compiler transforms them into state machines that persist to storage and resume after restarts.

TypeScript

```typescript
const r = race();
switch (true) {
  case signal(r, signals.paymentConfirmed):
    return { status: 'paid', txId: r.txId };
  case delay.days(r, 3):
    return { status: 'timeout' };
}
```

### ID-Free Domain

An ID is an infrastructure detail — how your storage tracks entities. In JustScale, domain code never sees string IDs. Persistent entities are references themselves.

TypeScript

```typescript
// Pass entities directly — no .id needed
await transfer(fromAccount, toAccount, amount);

// At boundaries, convert strings to typed refs
const user = User.ref`${userId}`;
```

### Type States as Contracts

The shape of your data tells you what you can do with it. A method that needs a locked entity says so in its signature — and the compiler enforces it.

TypeScript

```typescript
async markPaid(this: Lock<Persistent<Order>>) {
  this.status = 'paid'; // Lock removes readonly — safe to mutate
}

async loadItems(this: Persistent<Order>) {
  return this.payments.getItemsFor(this); // Read-only access
}
```

### Transport Agnostic

Controllers are entry points, not HTTP handlers. The same business logic works with HTTP, WebSocket, CLI, gRPC, or events — same DI, same middleware, same guards.

poker.controller.tsTypeScript

```typescript
import { createController } from '@justscale/core';
import { Ws } from '@justscale/websocket/builder';
import { PokerService } from './poker.service.js';

export const PokerController = createController('/poker', {
  inject: { poker: PokerService },

  routes: ({ poker }) => ({
    table: Ws('/:tableId')
      .message(Command)
      .handle(async ({ messages, send, params }) => {
        await poker.openTable(params.tableId);

        for await (const msg of messages) {
          switch (msg.type) {
            case 'sit':
              await poker.sitDown(params.tableId, msg.playerId, msg.seatNumber, msg.buyIn);
              break;
            case 'action':
              await poker.playerAction(msg.gameId, msg.playerId, msg.action, msg.amount);
              break;
          }
        }
      }),
  }),
});
```

## How It All Fits Together

JustScale is a full-stack backend framework with compile-time type safety. No decorators, no runtime reflection — plain TypeScript functions and objects. What you write is what runs.

- Dependency Injection — type-safe, compile-time validated. Missing dependencies are caught before your code runs.
- Models — domain entities with type-safe field builders, references, and methods that declare their data requirements.
- Repositories — abstract storage. Swap PostgreSQL for in-memory without touching domain code.
- Processes — durable workflows that survive restarts, with signals, race conditions, and timeouts.
- Features — composable modules bundling services, controllers, and configuration.

## Next Steps

- Installation
- Quick Start
- Philosophy
