# @post

Sends the value of a publisher as the body of a `POST` request and stores the response in an `ApiResult<T>` property. Pass an [Endpoint](/docs/misc/endpoint) and a [DataProviderKey](/docs/misc/dataProviderKey) for the body; the request runs on connect and again whenever the body changes. [@put](/docs/decorators/put) and [@patch](/docs/decorators/patch) work the same way.

## Import

<sonic-code language="typescript">
  <template>
import { post, type ApiResult } from "@supersoniks/concorde/decorators";
import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
import { Endpoint } from "@supersoniks/concorde/utils/endpoint";

type SignUp = { email: string; password: string };
type CrewMember = { id: number; email: string };

const signUpKey = new DataProviderKey&lt;SignUp&gt;("crewSignUp");

@post(new Endpoint&lt;CrewMember&gt;("api/register"), signUpKey)
@state()
payload?: ApiResult&lt;CrewMember&gt;;
  </template>
</sonic-code>

## Basic usage

The property is `undefined` until a response arrives, then holds `request` · `response` · the typed `result`. Each body mutation sends a new request; mutations in the same frame are coalesced into one. The demo posts to the local mock `POST /docs-mock-api/api/register`.

<sonic-code>
  <template>
    <docs-demo-sources for="demo-api-post"></docs-demo-sources>
    <demo-api-post></demo-api-post>
  </template>
</sonic-code>

## Method form

On a method, the decorator calls it with the full `ApiResult<T>` after each request instead of assigning a property.

<sonic-code>
  <template>
    <docs-demo-sources for="demo-api-post-method"></docs-demo-sources>
    <demo-api-post-method></demo-api-post-method>
  </template>
</sonic-code>

## Sending again

Keep the decorator in a constant to call `send(component)`. It reads the current body and returns a `Promise<ApiResult<T> | undefined>` resolved once the request completes. Use it for "Save" or "Retry" buttons.

<sonic-code language="typescript">
  <template>
const signUp = post(new Endpoint&lt;CrewMember&gt;("api/register"), signUpKey);

@signUp
@state()
payload?: ApiResult&lt;CrewMember&gt;;

async retry() {
  const payload = await signUp.send(this);
  console.log(payload?.result?.id);
}
  </template>
</sonic-code>

The `send` method decorator does the same around a method. It runs after the method by default (waiting for a returned Promise); `{ when: "before" }` sends first.

<sonic-code language="typescript">
  <template>
import { send } from "@supersoniks/concorde/decorators";

@send(signUp)
async confirmCheckIn() {
  // Validate the check-in form, then the POST is sent.
}
  </template>
</sonic-code>

## Manual trigger

Set `autoPostOnBodyMutation: false` when the body is edited field by field (a check-in form) and the request must only go out on `send()` or `triggerKey`. A `triggerKey` publisher lets another component resend: calling `invalidate()` on it sends the current body.

<sonic-code language="typescript">
  <template>
const submitKey = new DataProviderKey&lt;number&gt;("checkInSubmit");

@post(checkInEndpoint, checkInKey, {
  autoPostOnBodyMutation: false,
  triggerKey: submitKey,
})
@state()
payload?: ApiResult&lt;CheckIn&gt;;

// Anywhere: dp(submitKey).invalidate();
  </template>
</sonic-code>

By default (`skipIfBodyMissing: true`) nothing is sent while the body is `null` or `undefined`. `refetchEveryMs` repeats the request at a fixed interval after each response; use it only when repeating the operation is safe.

## API configuration

Without a configuration key, `serviceURL` and credentials come from the HTML scope, as with [@get](/docs/decorators/get). Pass a `DataProviderKey<APIConfiguration>` as third argument to read them from a publisher; options then move to the fourth argument. See [API configuration](/docs/misc/api-configuration).

<sonic-code language="typescript">
  <template>
@post(signUpEndpoint, signUpKey, apiConfigurationKey, { skipIfBodyMissing: false })
  </template>
</sonic-code>

## Dynamic path

Placeholders in the endpoint (`${flight}`) are read from the host component; changing the property sends the same body to the new URL. See [Dynamic paths](/docs/misc/dynamic-path).

<sonic-code>
  <template>
    <docs-demo-sources for="demo-api-post-dynamic"></docs-demo-sources>
    <demo-api-post-dynamic></demo-api-post-dynamic>
  </template>
</sonic-code>

## API reference

`post(endpoint, bodyKey, configurationKey?, options?)` returns a decorator for a property or a method, with a `send(component)` method.

| Argument | Type | Description |
|----------|------|-------------|
| `endpoint` | `Endpoint<T>` | Path and response type. Supports `${prop}` placeholders |
| `bodyKey` | `DataProviderKey<B>` | Publisher read as request body |
| `configurationKey` | `DataProviderKey<APIConfiguration>` (optional) | API configuration publisher. Default: HTML scope |
| `options.autoPostOnBodyMutation` | `boolean` (default `true`) | Send on connect and after each body mutation |
| `options.skipIfBodyMissing` | `boolean` (default `true`) | Skip while the body is `null` or `undefined` |
| `options.triggerKey` | `DataProviderKey<unknown>` | Publisher whose `invalidate()` resends |
| `options.refetchEveryMs` | `number` (default `0`, off) | Resend interval in ms after each response |
| `options.skipEmptyPlaceholder` | `boolean` (default `false`) | Treat a placeholder resolved to `""` as not ready |
| `send(handle, { when })` | `"after"` (default) or `"before"` | Method decorator sending after or before the method |
