# Type naming

The SDK's TypeScript codegen emits two type variants per
resource — a request shape (for `create()` / `update()` bodies)
and a response shape (for `get()` / `list()` output). They differ
in which fields they include.

## Writable suffix for request shapes

Request types carry a `Writable` suffix. The codegen
auto-excludes any field marked `readOnly` in the OpenAPI spec —
these are the server-derived fields (`id`, `slug`, `created_at`,
`updated_at`, `status`, etc. — enumerated in the next section).

```typescript
import type {
  DatalakeRequestWritable,    // POST/PUT body
  DatalakeResponse,           // GET output
} from '@alvera-ai/platform-sdk'
```

Same convention for every resource:

```
   <Resource>RequestWritable        POST/PUT body (request shape)
   <Resource>Response               GET output (response shape with
                                      server-derived fields included)
```

## Server-derived fields auto-excluded from Writable

Every resource carries a consistent set of fields the platform
server derives on create. These appear in `<Resource>Response`
types but are NEVER part of `<Resource>RequestWritable` types —
the codegen auto-excludes them based on `readOnly` markers in
the OpenAPI spec:

```
   id              UUID, allocated on create
   slug            derived from `name` via the platform's
                     slug-generation (lowercase + hyphenate +
                     de-duplicate within scope)
   created_at      ISO 8601 timestamp
   updated_at      ISO 8601 timestamp
   status          resource-specific lifecycle enum
                     (e.g. "new" → "ready" → ... per the
                     resource's MD §5)
   status_reason   nullable; populated when status enters a
                     terminal-failure state
```

Including any of these in a `create()` / `update()` body triggers
a layer-1 422 with an "unknown key" detail. The typed `*RequestWritable`
doesn't expose these fields, so TypeScript prevents it at
compile time when consumers use the typed methods.

### Which identifier keys which call — `id` vs `slug` vs `name`

A resource carries up to three server-derived identifiers, and
they are **not** interchangeable as call arguments:

```
   id      UUID — the API path key for CRUD. `.get` / `.update` /
             `.delete` on every datalake-scoped resource take the
             `id`, and cross-resource references (`tool_id`,
             `updater_tool_id`, `sender_tool_ids`, …) are `id`s too.
   slug    the human handle — the path key for runtime / operations
             verbs (workflows `execute` / `run` / logs, datalakes
             `migrate`, data-activation-client ingest / run, interop
             `run`). NOT accepted by a CRUD `.get`.
   name    generic tables only — the physical Postgres table name
             (`alvera_custom_<slug>`) a DAC ingests into. NOT an API
             path key; CRUD still keys on the UUID `id`. See
             `generic_tables.md` §2.
```

Passing a slug (or any non-UUID) to a CRUD `.get` / `.update` /
`.delete` 422s with an `Invalid format. Expected :uuid` detail.

### Never pre-compute the slug client-side

Slugs (and the generic-table `name`) are derived from `name` /
`title` server-side and de-duplicated within scope (per-resource —
usually datalake-scoped for datalake-owned resources, tenant-scoped
for tenant-level resources). A client-computed slug that conflicts
with an existing row gets a different suffix server-side, leaving
the client's local state out of sync.

The canonical pattern — capture both returned identifiers and use
each for the calls it keys:

```typescript
// 1. create — every response carries `id`; only SOME carry `slug`.
const { data: created } = await api.tools.create(
  tenantSlug, datalakeSlug, body,
)
const toolId = created.id!      // <-- CRUD + cross-resource refs

// A tool has NO slug, and neither does a generic table — they are
// addressed by id and by name respectively. The slug-bearing responses
// are the ones whose ops verbs take a slug in the path: datalakes,
// workflows, data activation clients, AI agents and interoperability
// contracts. Reach for `.slug` only on those, and
// note every id and slug is optional on the wire, so a strict consumer
// needs the `!` (or a guard) that the cookbooks use.

// 2a. CRUD reads/updates key on the UUID id
await api.tools.get(tenantSlug, datalakeSlug, toolId)

// 2b. downstream resources reference the tool by ID, never by name or
//     slug — see action_status_updaters.md for the full body:
//       updater_tool_id: toolId
```

Never:

```typescript
// ❌ wrong — client-computed slug can drift from server-side
const toolSlug = body.name.toLowerCase().replace(/\s+/g, '-')
```

### Per-resource extras

Some resources have additional server-derived fields beyond
the universal set (e.g. managed AWS Lambda tools' `body.stack_status`
+ `body.error`; data-activation-client `last_run_at`).
Each resource's MD §3 (Field ownership) lists its full
server-derived set.

## No client-side validators

The SDK ships **types only** — there is no paired runtime validator to
import (`v<Resource>RequestWritable` no longer exists). Validation is
server-authoritative: submit the body and handle the server's 422
`AlveraApiError` if it's malformed (see `errors.md`).

## Enum types

Named string-literal enums (e.g. `ToolIntent`, `ActionType`) are
emitted as TypeScript-native `enum`s (`enums: 'typescript'` in the
codegen config) — a runtime object the CLI can iterate via
`Object.values(...)`. Import the enum by name and reference a
member; inline (anonymous) enums at call sites still emit as plain
union types:

```typescript
import { ToolIntent } from '@alvera-ai/platform-sdk'

ToolIntent.DATA_EXCHANGE   // wire value: "data_exchange"
ToolIntent.SMS             // wire value: "sms"
```

The wire value is always snake_case; the TS-side identifier is
UPPER_SNAKE for readability. Each resource's MD names its
relevant enum types.

## List response types — and the Flop query every list takes

List endpoints return a typed envelope: `<Resource>List-
Response` with `data: <Resource>Response[]` and `meta:
PaginationMeta`:

```typescript
import type { ToolListResponse } from '@alvera-ai/platform-sdk'

const { data } = await api.tools.list(tenantSlug, datalakeSlug)
// data is ToolListResponse: { data: ToolResponse[], meta: PaginationMeta }
```

**Every curated `.list` wrapper takes an optional trailing `query`**
— the full Flop surface the platform declares. Without it you get
page 1 with server defaults, and in a UI that reads logs or
history, older rows silently drown under newer ones:

```typescript
await api.tools.list(tenantSlug, datalakeSlug, {
  page: 2,
  page_size: 50,                       // 1-100
  order_by: ['inserted_at'],           // must be Flop-sortable fields
  order_directions: ['desc'],
  filters: [{ field: 'intent', op: '==', value: 'sms' }],
})
```

`meta` is `{ page, page_size, total_count, total_pages }` — compute
"has more" as `meta.page < meta.total_pages`. The SDK serializes
arrays in the bracket style the server parses (`order_by[]=…`,
`filters[][field]=…`); never hand-roll the query string. This
applies to every resource `.list`, plus `tenants.list`,
`dataActivationClients.logs.list`, `workflows.batchLogs.list`, and
`workflows.workflowLogs.list` (worked example: `workflows.md` §6).
The one exception is `invitations.list` — its endpoint declares no
query params.
