# Server functions and the database

Your server is one file exporting one call:

```ts
import { defineServer, endpoint, mutation, query } from '@impetik/xeer/server';

export default defineServer({
  queries: { /* reads */ },
  mutations: { /* writes */ },
  endpoints: { /* raw HTTP under /api */ },
  authPolicies: { /* data-layer authorization */ },
});
```

All four keys are optional. The compiler reads this **statically** — it does not run your server to find
out what it does — which is what lets it generate types the client and your tests both see.

## Queries

A query reads. It gets a read-only view of the database and cannot write.

```ts
'notes.list': query({
  input: {},
  handler: (ctx) => ctx.db.table('notes').find({ where: { ownerId: ctx.auth.appUserId } }),
}),
```

The name is the record key, and it becomes the string you pass to `useQuery` on the client and `.query()`
in a test.

**Operation names must be namespaced and lowercase**: a namespace, a dot, then the rest.

```text
notes.list          ✓
board.cards         ✓
files.write         ✓
notes.list_pinned   ✓   underscores and dashes are fine
listNotes           ✗   no namespace, and uppercase
cards.forWorkspace  ✗   uppercase
```

Precisely: `^[a-z][a-z0-9_-]*\.[a-z][a-z0-9_.-]*$`. Anything else fails the build with `XE1303`. The
constraint exists so that an operation name is unambiguous in a URL, a log line, and a test transcript
without a per-call-site escaping decision.

## Mutations

A mutation writes. **The whole handler is one transaction**: every write it stages commits together at
the end, or nothing does. There is no `begin`, no `commit`, and no way to get a half-applied change.

```ts
'notes.remove': mutation({
  input: { id: 'string' },
  handler: async (ctx, input) => {
    const note = await ctx.db.table('notes').get(input.id);
    // Ownership is enforced here, on the server. A test proves it.
    if (!note || note.ownerId !== ctx.auth.appUserId) throw new Error('Note not found.');
    await ctx.db.table('notes').delete(input.id);
    return { id: input.id };
  },
}),
```

Reads inside a mutation see that mutation's own uncommitted writes, so you can insert a row and then
read it back in the same handler.

## Endpoints

An endpoint handles a raw HTTP request, for the cases RPC does not fit: a webhook, a redirect, a
non-JSON response.

```ts
'GET /api/status': endpoint(async (request, ctx) => {
  const note = await ctx.db.table('notes').first({ where: { ownerId: ctx.auth.appUserId } });
  return Response.json({ ok: true, hasNote: note !== null });
}),
```

The key is `"<METHOD> <path>"`. Methods are `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`, `HEAD`.
The path must be `/api` or start with `/api/`; segments are either literal (`[A-Za-z0-9_.-]+`) or a
parameter (`:name`). Two routes whose parameterized shapes would collide are a build error, not a
runtime surprise.

`endpoint()` takes the handler positionally, with an optional [auth
requirement](/guides/auth#operation-guards) second — not an options object:

```ts
'POST /api/hooks/stripe': endpoint(handler, authenticated()),
```

An endpoint's `ctx` is write-capable regardless of method, so a `GET` endpoint *can* write. It gets one
transaction per request exactly as a mutation does.

<div class="note">
<p><strong>Endpoint writes do not refresh open clients.</strong> The live-update stream is driven by
mutations. If your client writes by <code>fetch</code>-ing an endpoint, call
<a href="/guides/client#invalidatequeries"><code>invalidateQueries()</code></a> yourself afterwards.
See <a href="/guides/live-updates">Live updates</a>.</p>
</div>

## Input types

`input` is a tiny declarative schema, validated before your handler runs. Six type strings, plus nested
plain objects:

| Descriptor | Handler receives |
| --- | --- |
| `'string'` | `string` |
| `'number'` | `number` — finite; `NaN` and `Infinity` are refused |
| `'boolean'` | `boolean` |
| `'datetime'` | `Date` — a real `Date`, over the wire and back |
| `'bytes'` | `Uint8Array` |
| `'json'` | `XeerValue` — any JSON-shaped value |
| `{ a: 'string', b: { c: 'number' } }` | `{ a: string; b: { c: number } }` |

```ts
input: { title: 'string', dueAt: 'datetime', meta: { tags: 'json', pinned: 'boolean' } },
```

Two things this language deliberately does **not** have:

- **No optional, nullable, array, or enum notation.** Every declared field is required, and the object
  is exact: a missing key and an unknown key are both refused before your handler runs. For a list or an
  optional value, declare `'json'` and narrow it in the handler. For an enum, take `'string'` and check
  it — which is what `examples/team-board` does with its lane names.
- **No max/min/pattern.** Length limits belong on the table field (`maxLength`), and anything else is a
  line of handler code.

`input: {}` means "no input". You still pass `{}` from the client.

## The table API

`ctx.db.table(name)` returns a handle typed from your manifest. Seven methods, and that is all of them.

```ts
interface QueryTable {
  all(): Promise<Row[]>;
  get(id: string): Promise<Row | null>;
  find(options: { where: Where; limit?: number }): Promise<Row[]>;
  first(options: { where: Where }): Promise<Row | null>;
}

interface MutationTable extends QueryTable {
  insert(value: Insert): Promise<Row>;
  update(id: string, patch: Update): Promise<Row>;
  delete(id: string): Promise<void>;
}
```

A `Row` is your declared fields plus `id: string`, `createdAt: Date`, `updatedAt: Date`. `Insert` omits
those three — the runtime mints the id and both timestamps. `Update` makes every field optional and
merges shallowly over the current row.

```ts
const created = await ctx.db.table('notes').insert({ text: 'Water the plants', ownerId: ctx.auth.appUserId });
created.id;         // string
created.createdAt;  // Date

await ctx.db.table('notes').update(created.id, { text: 'Water the plants twice' });
await ctx.db.table('notes').delete(created.id);
```

`update` and `delete` on an id that does not exist throw `Record not found: <table>/<id>`.

### Filtering

`where` is an equality record, and it must match either `{ id }` or **a declared prefix of a declared
index**:

```json
"indexes": { "by_workspace_status": ["workspaceId", "status"] }
```

```ts
ctx.db.table('cards').find({ where: { workspaceId: 'design' } });                    // ok — prefix
ctx.db.table('cards').find({ where: { workspaceId: 'design', status: 'doing' } });   // ok — full
ctx.db.table('cards').find({ where: { status: 'doing' } });                          // refused
```

The type system rejects the third one in your editor, and the runtime refuses it too:
`Filter on cards must match the prefix of a declared index.`

There are no comparison operators — no `gt`, `in`, `like`, `not`. There is no `orderBy`: results are
always ordered by `createdAt` then `id`, ascending. There is no `count()`, and no offset or cursor
pagination. `limit` on `find` is the only knob.

That is a small API, and the reason to like it is that every query it can express is one an index
already serves. If you need a shape it cannot express, the answer is a new index in the manifest —
which is a reviewable change to a checked-in file, not a slow query nobody notices for a month.

### Row counts and budgets

`find` returns at most `limit`, defaulting to 100. `all()` reads up to your `queryRows`
[budget](/reference/manifest#budgets), which defaults to 1000. A `limit` outside `1 … queryRows` is
refused. Raise the budget in the manifest if your app genuinely needs more.

## `ctx`

`ctx.auth` and `ctx.env`, plus one property per [capability](/guides/capabilities) you declared — and
nothing else:

```ts
ctx.auth      // the verified identity of the caller
ctx.env       // this app's environment values for this environment
ctx.db        // the database    — only with `capabilities: ["database"]`
ctx.storage   // the object store — only with `capabilities: ["storage"]`
```

A capability you did not declare is not on the type, so touching it is a compile error (`XE1205`), not a
runtime failure. There is no `ctx.fetch`, no ambient client, and no way to reach a resource the manifest
does not name.

### `ctx.auth`

```ts
interface AuthIdentity {
  readonly appUserId: string;                 // the caller, scoped to this app
  readonly kind: 'guest' | 'user';
  readonly roles: readonly string[];
  readonly permissions: readonly string[];
  readonly workspaceIds: readonly string[];
  readonly sessionId?: string;
}
```

`appUserId` is the caller's identity **inside this app** — never a provider id, never an id shared with
another app. It is what you store as `ownerId`. `kind` distinguishes a provisioned user from an
unclaimed visitor. `workspaceIds` is the membership the platform asserted; your code cannot add to it.

This object is derived from a cryptographically verified assertion on every request, not from anything
the browser sent. See [Auth](/guides/auth).

<div class="note">
<p><code>ctx.auth.id</code> exists as a deprecated alias of <code>appUserId</code> and is always equal to
it. New code should use <code>appUserId</code>. <code>ctx.auth.profile</code> is declared in the type but
is not populated by the runtime — do not build on it.</p>
</div>

### `ctx.env`

`Readonly<Record<string, string | undefined>>` — this app's own environment values, and nothing else. An
unset variable is `undefined`, never `''`. See [Environment variables and
secrets](/guides/env).

### `ctx.storage`

The object store, when you have declared the `storage` capability. A query gets `get`, `head`, and
`list`; a mutation or endpoint also gets `put` and `delete` — the same read/write split `ctx.db` has, and
calling `put` from a query is a compile error rather than a runtime one.

```ts
'files.stat': query({
  input: { key: 'string' },
  handler: async (ctx, input) => {
    const object = await ctx.storage.head(input.key);   // null when there is nothing there
    return object === null ? null : { size: object.size, contentType: object.contentType };
  },
}),
```

An upload arrives as an ordinary endpoint body — `await request.arrayBuffer()` — because there is no
signed-upload URL: every byte passes through a handler you wrote. The full surface, the key rules, a
complete browser-upload walkthrough, and the byte budgets are in [Storage](/guides/storage).

## Refusing a call

There is no error helper to import. Throw:

```ts
if (!note || note.ownerId !== ctx.auth.appUserId) throw new Error('Note not found.');
```

The caller gets a structured failure rather than a stack trace:

| Where it threw | Status | `code` |
| --- | --- | --- |
| query or mutation handler | 400 | `operation_failed` |
| endpoint handler | 500 | `endpoint_failed` |
| a policy or guard denied it | 400 | `operation_failed` |
| handler exceeded its deadline | 504 | `handler_deadline_exceeded` |
| request body over `requestBytes` | 413 | `request_too_large` |

On the client that arrives as a rejected promise carrying a `XeerRuntimeError`; in a test,
`expectFailure()` hands you `{ status, code, message, errorId }`. Because a denial and a hand-written
refusal look the same from outside, an attacker cannot tell "not yours" from "does not exist" — which is
usually what you want, and is why the example above says *Note not found* rather than *not your note*.

## Authorization

Two mechanisms, at two different levels. Use both.

**Table policies** (`authPolicies`) apply to every read and write of a table, in the data layer:

```ts
import { defineServer, ownedTable, workspaceTable } from '@impetik/xeer/server';

export default defineServer({
  authPolicies: { drafts: ownedTable(), cards: workspaceTable() },
  // …
});
```

**Operation guards** (`auth`) run before a handler:

```ts
import { authenticated, memberOf, mutation } from '@impetik/xeer/server';

'cards.add': mutation({
  input: { title: 'string', workspaceId: 'string' },
  auth: [authenticated(), memberOf('workspaceId')],
  handler: (ctx, input) => ctx.db.table('cards').insert(input),
}),
```

<div class="note">
<p><strong>A policy filters; a guard refuses.</strong> Under <code>workspaceTable()</code> alone, a
non-member reading a workspace gets an empty list. Add <code>memberOf('workspaceId')</code> and they get
an error instead. Both are safe — neither leaks a row — but they are different products, so choose
deliberately: guards suit writes, where “you may not do this” is the honest answer, and bare policies
suit reads, where an empty list is usually what a UI wants.</p>
</div>

Both are covered in [Auth](/guides/auth), which also shows how to prove they work.

## A complete server

```ts
import {
  authenticated, defineServer, endpoint, memberOf, mutation, query, workspaceTable,
} from '@impetik/xeer/server';

export default defineServer({
  authPolicies: { cards: workspaceTable() },

  queries: {
    'board.cards': query({
      input: { workspaceId: 'string' },
      // No guard. `{ workspaceId }` is a prefix of `by_workspace_status`, and the workspace policy
      // narrows the result further — a non-member reads `[]`, never another workspace's rows.
      handler: (ctx, input) => ctx.db.table('cards')
        .find({ where: { workspaceId: input.workspaceId }, limit: 200 }),
    }),
  },

  mutations: {
    'cards.add': mutation({
      input: { title: 'string', workspaceId: 'string' },
      auth: [authenticated(), memberOf('workspaceId')],
      handler: (ctx, input) => ctx.db.table('cards')
        .insert({ title: input.title, status: 'todo', workspaceId: input.workspaceId }),
    }),
    'cards.move': mutation({
      input: { id: 'string', status: 'string' },
      auth: authenticated(),
      handler: async (ctx, input) => {
        if (!['todo', 'doing', 'done'].includes(input.status)) throw new Error('Unknown lane.');
        // The workspace policy already refuses a card the caller cannot see, so this is
        // authorization *and* a 404 in one line.
        return ctx.db.table('cards').update(input.id, { status: input.status });
      },
    }),
  },

  endpoints: {
    'GET /api/health': endpoint(async (_request, ctx) => Response.json({
      ok: true, workspaces: ctx.auth.workspaceIds.length,
    })),
  },
});
```

## Next

- **[The client](/guides/client)** — calling all of this from a component.
- **[Auth](/guides/auth)** — policies, guards, and proving them.
- **[Testing](/guides/testing)** — asserting the refusals above.
- **[Capabilities](/guides/capabilities)** — what `ctx.db` is and why it is opt-in.
