# Mutations

Every resource exposes the same three mutation methods on the typed client:
`create(body)`, `update(id, body)`, and `delete(id)`. Two rules cover almost
everything:

1. **`update()` replaces the whole resource.** There is no partial update —
   resupply the full body with your change applied. A tiny, explicitly-listed
   set of methods are the exception (below).
2. **`create()` returns the full record**, including server-derived fields
   (`id`, `slug`, `status`, timestamps). Use that returned record for
   downstream references — never pre-compute a server-derived field yourself.

Non-CRUD methods are out of scope for this page — RESTful sub-actions
(`.testInvocation`, `.refresh`), artifact issuers
(`.createUploadLink`, `.createDownloadLink`), and async triggers (`.run`,
`.execute`, `.ingest`) each document their own semantics in their resource's MD.

## `create()` — make a new record

`create(body)` returns the full `<Resource>Response`, including every
server-derived field (see `type_naming.md` "Server-derived fields"). Read the
ids and slugs you need off that response.

## `update()` — replace the whole record

`update(id, body)` replaces the resource wholesale — it is **not** a partial
update. To change one field, read the current record, change the field in
memory, and send the entire body back:

```typescript
// 1. read current
const { data: current } = await api.tools.get(
  tenantSlug, datalakeSlug, toolId,
)

// 2. change in memory
const next = { ...current, status: 'inactive' }

// 3. send the whole body back. The cast is required — see below.
const { data: updated } = await api.tools.update(
  tenantSlug, datalakeSlug, toolId, next as unknown as ToolRequestWritable,
)
```

Omitting a required field on `update()` is rejected with a `422 AlveraApiError`
on that field — even when its value is unchanged from the stored row — because
the call replaces the whole resource (see `errors.md`).

## Partial-update exceptions

A small set of methods accept a narrow partial body instead of replacing the
whole resource. Each is named here and documents its partial-body contract in
its TypeScript method's JSDoc and its per-resource MD §5 (Lifecycle):

| SDK method                                       | Resource MD               |
|--------------------------------------------------|---------------------------|
| `api.authenticatedLinks.updateMessageTracking`   | `authenticated_links.md`  |

For every other method, the default holds: `update()` replaces the whole
resource.

> This is the canonical index of partial-update methods. If a future method
> joins, it lands here. If you find a method whose JSDoc claims partial-body
> behavior but it isn't listed here, this index is stale and needs a refresh.

## The discriminator can't change on update

For polymorphic resources (tools, a datalake's cloud storage, …), the body's
discriminator value must match the stored record's on `update()`. The platform
rejects a discriminator change with a `422` on the discriminator field. To
change body type, `delete()` and `create()` fresh — you cannot morph it in
place.

## Polymorphic discriminator naming

Resources with polymorphic bodies carry an explicit discriminator field. Its
**wire name** is what the SDK type expects:

| Resource                       | Discriminator (wire)     |
|--------------------------------|--------------------------|
| Tool body                      | `tool_body_type`         |
| Datalake cloud-storage embed   | `cloud_storage_type`     |

The wire name is what appears in your request body's JSON and in the
`<Resource>RequestWritable` TypeScript types. Each resource's MD §1 (Wire
shape) names its discriminator and lists every branch's TypeScript type.

## `delete()` — usually synchronous

`delete(id)` removes the record. A few resources have semantic guards (e.g. you
cannot delete a default-flagged data activation client, or a datalake that
still has dependents) that surface as `422`s with `source.pointer` naming the
field that's blocking the delete.

For asynchronous deletes (e.g. AWS Lambda tools' out-of-band cleanup), poll the
resource's status until it's gone — see `async.md`.

## The parent scope comes from the method arguments, not the body

Every resource method takes its parent scope as leading arguments —
`tenantSlug` and `datalakeSlug` — which become the URL path:

```
/api/v1/tenants/{tenant_slug}/datalakes/{datalake_slug}/<resource>[/{id}]
```

A body often also carries a `datalake_id` (and sometimes `tenant_id`) naming
the same parent. **The method arguments win.** If the body names a different
parent than the arguments, the body field is silently ignored and the record
lands under the argument-named parent. The practical consequences:

- **You can't "redirect" a write with a body field.** To target a different
  datalake, change the `datalakeSlug` argument.
- **Reusing a body across datalakes is safe.** A `datalake_id` you captured
  from an earlier call can ride along on a write under any scope — the
  arguments govern where it lands.
- **A `datalake_id` mismatch never 422s** — the platform just ignores the body
  value.

## No bulk mutations on standard CRUD

Each `create()` / `update()` / `delete()` touches exactly one resource — there
are no batch creates or updates on the standard methods. Some ingestion paths
(data-activation-client bulk runs and file uploads) ARE batch by design; those
are documented in `data_activation_clients.md`.
