forgedb 0.3.0

ForgeDB — an application database generator. Compiles a declarative .forge schema into tailored Rust database code, a TypeScript SDK, and a REST API.
Documentation
---
title: "Modifiers"
description: "The .forge field modifiers — + auto-generate, & unique, ^ indexed, and ? nullable — with placement and type rules."
purpose: "reference"
---
Modifiers are sigils on a field's type. Three are **prefix** modifiers (`+`, `&`,
`^`) placed before the type name; one is the **nullable** modifier (`?`) placed after.

| Symbol | Name | Position | Meaning | Valid on |
|--------|------|----------|---------|----------|
| `+` | auto-generate | prefix | Fill on create (`uuid`/`timestamp` today; integer is a marker) | `u32`, `u64`, `uuid`, `timestamp` only |
| `&` | unique | prefix | Value must be unique (enforced) | any type |
| `^` | indexed | prefix | Build an index for fast lookups | any type |
| `?` | nullable | postfix (or prefix) | Value may be absent | any type |

## `+` — auto-generate

`+` marks a field ForgeDB fills for you. On **create** (`db.create_<model>`, and the REST
`POST /api/<model>` that routes through it), an omitted or nil `+uuid` gets a fresh
`Uuid::new_v4()` and an omitted or zero `+timestamp` gets the current time, so callers
never supply them. Valid **only on** `u32`, `u64`, `uuid`, and `timestamp`:

```forge
id: +uuid            // fresh UUID on create when omitted/nil
seq: +u64            // primary-key marker — NOT yet auto-incremented (see below)
created_at: +timestamp   // current time on create when omitted/zero
```

<Callout type="warning" title="`+` is type-restricted">
`+` on any other type — e.g. `name: +string` — is a fatal parse error. Only the four
generatable types above are allowed.
</Callout>

<Callout type="warning" title="Integer `+` is a marker, not yet auto-incremented">
`+u32`/`+u64` currently only *mark* the primary-key / auto field — the generator does not
synthesize a value for them, so a create must still supply the integer id. A correct
integer auto-increment (a monotonic, restart-safe, reuse-free counter that also holds across
transactions and the multi-process coordinator) is a design problem, not a quick patch, and
is being worked through as an RFC — it is deliberately not shipped as a subtly-wrong version
that could return `id = 0` or reuse a deleted id. `+uuid` and `+timestamp` are fully
synthesized today; if you need an auto-assigned key now, use `+uuid`. See
[RFC #187](https://github.com/hoodiecollin/forgedb/issues/187).
</Callout>

## `&` — unique

`&` marks a column whose values must be unique across all rows. Uniqueness is **enforced**:
on `insert`/`update` the generated code probes the field's index and rejects a duplicate
(HTTP 409). Applies to any type.

```forge
email: &string @email
slug: &string
```

`&` builds an index too, so a unique field is also fast to look up.

## `^` — indexed

`^` builds a secondary index on the field for fast `find_by_*` lookups and for
index-served REST filters. Applies to any indexable type (not `json`).

```forge
slug: ^string
views: ^u64
status: ^OrderStatus
```

Combine `&` and `^` freely; since `&` already indexes, `^&` mainly documents intent.
See [indexes & projections](/docs/schema/indexes-and-projections/) for hash-vs-ordered
index behavior.

## Combining prefix modifiers

Any combination of `+`, `&`, `^` is allowed, in any order, all before the type —
subject to the rule that `+` is valid only on `u32`/`u64`/`uuid`/`timestamp`:

```forge
id: +&^uuid               // auto-gen + unique + indexed (auto-gen needs an eligible type)
email: ^&string @email    // indexed + unique
```

## `?` — nullable

`?` makes a field optional (may be absent / `NULL`). It is a **postfix** modifier, placed
after the type, and this is the idiomatic form. A prefix form also parses:

```forge
bio: string?         // nullable string (idiomatic)
age: i32?            // nullable int
avatar: ?string      // prefix form — also parses
published_at: timestamp?
```

`?` composes with the prefix modifiers:

```forge
external_id: +uuid?  // auto-generate and nullable
```

On a relation, prefix `?` declares an **optional foreign key**
(`editor: ?User`). See [relations](/docs/schema/relations/).

<Callout type="note" title="Nullable storage keeps absent distinct from zero">
A nullable fixed-width field carries a 1-byte presence tag, so `None` and a stored `0`
(or `Value::Null`, or an empty string) round-trip as different values.
</Callout>