# Architecture (/reference/concepts/architecture)



Layer Diagram [#layer-diagram]

```
headless.ly       → Tenant composition, SDK, RPC client (capnweb)
objects.do        → Managed Digital Object service, verb conjugation, events
digital-objects   → Pure schemas, zero deps, Noun() function, 35 core entities
.do services      → payments.do, oauth.do, events.do, database.do, functions.do
@dotdo/do         → THE Durable Object: StorageHandler, EventsStore, WebSocket
@dotdo/db         → ParqueDB: hybrid relational-document-graph on Parquet
Cloudflare        → Workers, Durable Objects, R2, KV, AI
```

Each layer depends only on the layer below it. `digital-objects` has zero dependencies and defines the 35 core entities as pure schemas. `objects.do` adds runtime behavior (verb execution, event emission). `headless.ly` composes tenants and exposes the SDK.

Storage Model: ParqueDB [#storage-model-parquedb]

ParqueDB is a hybrid relational-document-graph database built on Apache Parquet:

| Mode           | Capability                                     | Implementation                                       |
| -------------- | ---------------------------------------------- | ---------------------------------------------------- |
| **Relational** | Typed schemas, foreign keys, joins             | Column definitions with type constraints             |
| **Document**   | Flexible fields, nested JSON, schema evolution | `json` columns with path-based indexing              |
| **Graph**      | Bidirectional relationships, traversal         | Relationship indexes from `->` and `<-` declarations |
| **Columnar**   | Predicate pushdown, bloom filters, compression | Apache Parquet file format on R2                     |

Parquet Encoding [#parquet-encoding]

Each entity type maps to a Parquet file with columns derived from its Noun definition:

| Noun Type     | Parquet Type                | Encoding                  |
| ------------- | --------------------------- | ------------------------- |
| `string`      | `BYTE_ARRAY` (UTF8)         | Dictionary + RLE          |
| `number`      | `DOUBLE`                    | Plain                     |
| `boolean`     | `BOOLEAN`                   | RLE                       |
| `datetime`    | `INT96` or `INT64` (micros) | Plain                     |
| `id`          | `BYTE_ARRAY` (UTF8)         | Dictionary                |
| `json`        | `BYTE_ARRAY` (UTF8)         | Plain (JSON string)       |
| Enum          | `BYTE_ARRAY` (UTF8)         | Dictionary (closed set)   |
| Indexed (`#`) | Any                         | Bloom filter + dictionary |

Write Path [#write-path]

```
Client SDK
  │
  ▼
Cloudflare Worker (Hono)
  │  Route: POST /~:tenant/Entity
  ▼
Durable Object (per tenant)
  │  1. BEFORE hooks (validation, transform)
  │  2. Append event to immutable log
  │  3. Update materialized state in SQLite WAL
  │  4. Execute AFTER hooks (side effects)
  │  5. Broadcast via WebSocket
  ▼
Background flush
  │  SQLite WAL → Parquet → R2
  ▼
Iceberg R2 Lakehouse
     Event log archived for analytics
```

All writes go through a single Durable Object per tenant. The DO provides serializable consistency -- no two writes to the same tenant execute concurrently. SQLite WAL provides fast local reads and writes; Parquet files on R2 provide durable columnar storage.

Write Guarantees [#write-guarantees]

| Guarantee                | Mechanism                            |
| ------------------------ | ------------------------------------ |
| Serializable consistency | Single DO per tenant                 |
| Durability               | SQLite WAL + R2 Parquet flush        |
| Atomicity                | DO transaction boundary              |
| Event ordering           | Monotonic version numbers per entity |

Read Path [#read-path]

```
Client SDK
  │
  ▼
Cloudflare Worker (Hono)
  │  Route: GET /~:tenant/Entity/:id
  ▼
Cache Layer (KV + CDN)
  │  Hit? → return cached response
  │  Miss? ↓
  ▼
R2 (Parquet files)
  │  Predicate pushdown, bloom filter skip
  ▼
Response
  │  Cache populated for next read
  ▼
Client
```

Reads prefer the cached path. Parquet's columnar format enables efficient predicate pushdown -- only the columns and row groups matching the query are read from R2. Bloom filters on indexed (`#`) columns enable fast existence checks without scanning.

Read Consistency [#read-consistency]

| Mode              | Consistency           | Source           |
| ----------------- | --------------------- | ---------------- |
| Default           | Eventually consistent | R2 + CDN cache   |
| `strong: true`    | Strongly consistent   | Direct DO read   |
| `asOf: timestamp` | Point-in-time         | Event log replay |

```typescript
import { Contact } from '@headlessly/crm'

// Eventually consistent (fast, cached)
const contacts = await Contact.find({ stage: 'Lead' })

// Strongly consistent (reads from DO)
const contact = await Contact.get('contact_fX9bL5nRd', { strong: true })

// Point-in-time (replays events)
const historical = await Contact.get('contact_fX9bL5nRd', {
  asOf: '2026-01-15T10:00:00Z',
})
```

Multi-Tenancy [#multi-tenancy]

Every tenant gets their own Durable Object with complete data isolation. No shared state between tenants.

Tenant Addressing [#tenant-addressing]

Tenants are addressed via the `~` path prefix:

```
https://{context}.headless.ly/~{tenant}/{Entity}
https://{context}.headless.ly/~{tenant}/{Entity}/{id}
https://{context}.headless.ly/~{tenant}/{Entity}/{id}/{verb}
```

```bash
