# Getting Started with Acequia

Acequia is a peer-to-peer platform where browsers communicate directly through route handlers — like Express, but running in the browser. You register routes, peers discover each other, and `fetch()` calls are transparently routed to the right peer over the best available transport.

In this tutorial you'll build a small app where two browser tabs discover each other, exchange messages in real time, and call each other's route handlers. The whole thing takes about 30 minutes.

---

## Prerequisites

- **Node.js 18+** and **yarn** (or npm)
- **git**
- Two terminal windows
- A modern browser (Chrome, Firefox, or Safari)

---

## Step 1: Build and Start (~5 min)

Acequia has three sibling repositories that work together:

| Repo | What it does |
|------|-------------|
| `acequia2/` | Browser client library (built with Rollup, copied to both servers) |
| `localDiscovery/` | WebRTC signaling and peer discovery (port 31313) |
| `localWebDAV/` | File server, auth, and WebDAV storage (port 3333) |

All three live under the same parent directory. Build the client library first, then start both servers:

```bash
# Build the client library
cd acequia2 && yarn install && yarn build
```

### Configure the browser

The browser needs to know where the discovery server is. Create `localWebDAV/public/config.local.js`:

```javascript
export const config = {
  acequia: {
    discoveryServer: 31313,
  },
}
```

Without this file, the browser assumes discovery is on the same port as the WebDAV server and peers won't find each other.

### Start both servers

```bash
# Terminal 1: Start the discovery server
cd localDiscovery && yarn install && yarn copyAcequiaBuild && node server.mjs
# → listening on port 31313
```

```bash
# Terminal 2: Start the WebDAV server
cd localWebDAV && yarn install && yarn copyAcequiaBuild && node server.mjs
# → listening on port 3333
```

Both servers should print startup messages without errors. Leave them running.

---

## Step 2: Set Up Your Server (~2 min)

Open **http://localhost:3333/** in your browser.

Since this is a fresh server, you'll see the **Welcome to Acequia** setup page:

1. **Click "Become Site Administrator."** This generates a cryptographic identity for you (stored in IndexedDB) and registers you as the site owner.

2. After the page reloads, you'll see that the `localhost` subdomain doesn't exist yet. **Click "Create Subdomain"** to create it.

You now have a working Acequia server with a `localhost` subdomain. The `acequia/localhost/` directory was created automatically — that's where your apps will live.

> Your identity (keys, IDs, handle) was created automatically. See [Identity and Authentication](platform/identity-and-auth.md) when you're ready to learn about the identity model.

---

## Step 3: Create Your First App (~10 min)

Create a directory for the app:

```bash
mkdir -p localWebDAV/acequia/localhost/HelloAcequia
```

### index.html

Create `localWebDAV/acequia/localhost/HelloAcequia/index.html`:

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello Acequia</title>
    <style>
        body { font-family: system-ui, sans-serif; max-width: 600px; margin: 2rem auto; padding: 0 1rem; }
        #messages { border: 1px solid #ccc; padding: 1rem; min-height: 200px; margin: 1rem 0; }
        .msg { padding: 0.25rem 0; }
        .msg.local { color: #666; }
        .controls { display: flex; gap: 0.5rem; }
        .controls input { flex: 1; padding: 0.5rem; }
        button { padding: 0.5rem 1rem; cursor: pointer; }
    </style>
</head>
<body>
    <h1>Hello Acequia</h1>
    <p>Peers: <strong id="peerCount">0</strong> | Name: <strong id="myName">...</strong></p>

    <button id="pingBtn" disabled>Ping Peers</button>
    <div id="messages"></div>
    <div class="controls">
        <input id="chatInput" type="text" placeholder="Type a message..." disabled>
        <button id="sendBtn" disabled>Send</button>
    </div>

    <script type="module" src="main.js"></script>
</body>
</html>
```

### main.js

Create `localWebDAV/acequia/localhost/HelloAcequia/main.js`:

```javascript
import acequia from '/acequia.js'

// Wait for the framework to initialize
await acequia.acequiaReady()

// Read name from URL, e.g. ?name=Alice
const params = new URLSearchParams(window.location.search)
const displayName = params.get('name') || 'Peer-' + Math.random().toString(36).slice(2, 6)

document.getElementById('myName').textContent = displayName

// Join a group — all peers in the same group can discover each other
const group = new acequia.groups.Group('hello-acequia', {
    displayName,
    onPeersList: (peers) => {
        // peers is an array of { instanceId, displayName, ... }
        const others = peers.filter(p => p.instanceId !== acequia.getInstanceId())
        document.getElementById('peerCount').textContent = others.length
        document.getElementById('pingBtn').disabled = others.length === 0
    },
})
await group.ready

// --- Routes: request/response between peers ---

const instanceId = acequia.getInstanceId()

group.registerRoutes([{
    pattern: `/${instanceId}/ping`,
    handler: async (request) => ({
        status: 200,
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({
            pong: true,
            from: displayName,
            time: new Date().toLocaleTimeString(),
        }),
    }),
}])

// Ping button — calls every peer's /ping route
document.getElementById('pingBtn').addEventListener('click', async () => {
    const peers = group.getPeersList().filter(p => p.instanceId !== instanceId)
    for (const peer of peers) {
        try {
            const url = `/groups/hello-acequia/${peer.instanceId}/ping`
            const res = await fetch(url)
            const data = await res.json()
            addMessage(`Ping response from ${data.from}: pong at ${data.time}`)
        } catch (err) {
            addMessage(`Ping to ${peer.displayName} failed: ${err.message}`)
        }
    }
})

// --- Event Bus: real-time pub/sub between peers ---

group.subscribe('chat:**', (event) => {
    if (!event.local) {
        addMessage(`${event.data.from}: ${event.data.text}`)
    }
})

// Send button and Enter key
const chatInput = document.getElementById('chatInput')
const sendBtn = document.getElementById('sendBtn')
chatInput.disabled = false
sendBtn.disabled = false

function sendMessage() {
    const text = chatInput.value.trim()
    if (!text) return
    group.publish('chat:message', { from: displayName, text })
    addMessage(`${displayName}: ${text}`, true)
    chatInput.value = ''
}

sendBtn.addEventListener('click', sendMessage)
chatInput.addEventListener('keydown', (e) => {
    if (e.key === 'Enter') sendMessage()
})

// --- UI helper ---

function addMessage(text, local = false) {
    const div = document.createElement('div')
    div.className = 'msg' + (local ? ' local' : '')
    div.textContent = text
    document.getElementById('messages').appendChild(div)
    div.scrollIntoView({ behavior: 'smooth' })
}
```

---

## Step 4: See It Work (~5 min)

Open two browser tabs:

- **Tab 1:** `http://localhost:3333/HelloAcequia/?name=Alice`
- **Tab 2:** `http://localhost:3333/HelloAcequia/?name=Bob`

Wait a few seconds for the peers to discover each other. Both tabs should show **Peers: 1**.

### Try it

1. **Chat:** Type a message in Tab 1 and press Enter. It appears in Tab 2 instantly. This uses the **Event Bus** — fire-and-forget pub/sub over the peer mesh.

2. **Ping:** Click "Ping Peers" in Tab 1. You'll see a response from Bob with a timestamp. This uses **route handlers** — Tab 1 called `fetch()`, the service worker found Tab 2's registered `/ping` route, forwarded the request, and returned the response.

### What happened under the hood

- `acequiaReady()` registered a service worker that intercepts `fetch()` calls
- `new Group('hello-acequia')` connected to the discovery server and found other peers in the same group
- `registerRoutes()` told the service worker "I handle requests to `/{instanceId}/ping`"
- When Tab 1 called `fetch('/groups/hello-acequia/{peerId}/ping')`, the service worker routed it to Tab 2 — over WebRTC if available, or via the discovery server's WebSocket relay as fallback
- `group.publish()` and `group.subscribe()` sent messages directly between peers on the Event Bus

You never had to think about WebSocket, WebRTC, or message encoding. The system chose the best transport automatically.

---

## Step 5: Modify and Experiment

Try these changes to build intuition:

- **Add data to ping:** Have the `/ping` handler return extra fields (e.g., `userAgent: navigator.userAgent`). Refresh and ping again.

- **Wildcard patterns:** Change the subscription from `'chat:**'` to `'chat:*'` — it still matches `chat:message` (one segment). Now try publishing to `'chat:general:hello'` — `'chat:*'` won't match but `'chat:**'` would. See [Event Bus](platform/event-bus.md) for the full pattern syntax.

- **Three peers:** Open a third tab as `?name=Charlie`. All three peers see each other's messages. The ping button calls each peer individually — route handlers are per-peer, while events fan out to all subscribers.

---

## What You've Learned

| Concept | What it does |
|---------|-------------|
| `acequia.acequiaReady()` | Initializes identity, keys, and the service worker |
| `new Group(name, opts)` | Joins a peer group — discovery and communication |
| `group.registerRoutes()` | Registers `fetch()`-compatible route handlers |
| `group.publish() / subscribe()` | Fire-and-forget pub/sub (Event Bus) |
| Transport selection | Automatic — WebRTC when available, WebSocket relay as fallback |

Acequia apps are plain HTML and JavaScript files served from the `acequia/` directory. No build step, no bundler, no framework required.

---

## Next Steps

Now that you have the basic loop working, explore the platform:

- **[Creating Acequia Apps](platform/creating-acequia-apps.md)** — Full reference for app structure, initialization patterns, and route registration
- **[Architecture Overview](platform/architecture-overview.md)** — The three transport strategies and how the service worker picks between them
- **[Identity and Authentication](platform/identity-and-auth.md)** — Users, devices, instances, and when auth matters
- **[Worker Pool](platform/worker-pool.md)** — Distribute work across peers with dispatch strategies and job tickets
- **[Group Shared State](platform/group-shared-state.md)** — Mutable state visible to all group members, with leader election
- **[Event Bus](platform/event-bus.md)** — Full topic matching, wildcards, and retained events
- **[Dev Environment](platform/dev-environment.md)** — Cloudflare tunnel setup, deployment options, and configuration details
