`PostStrategy` sends a file as a single `multipart/form-data` request using presigned form
fields (S3 POST policy style). It is the simplest option and is **not** resumable — a failure
restarts from zero.

```ts
import { PostStrategy } from '@gentleduck/upload/strategies'

strategies.set(PostStrategy())
```

`PostStrategy()` takes no runtime arguments; the four type parameters are inferred from the
registry.

## Intent shape

Your `createIntent` returns `PostStrategy.Intent`:

```ts
type Intent = {
  strategy: 'post'
  fileId: string
  url: string                       // presigned POST endpoint (form action)
  fields: Record<string, string>    // presigned policy fields (key, policy, signature…)
  expiresAt?: string                // optional expiry hint
}
```

## Cursor shape

There is nothing to resume, so the cursor is empty:

```ts
type Cursor = Record<string, never>
type Cursors = { post?: PostStrategy.Cursor }
```

## How it runs

`start()` hands the file and presigned fields to `ctx.transport.postForm`, which builds the
`FormData` (fields first, file last, as S3 requires), POSTs it, and streams progress:

```ts
async start(ctx) {
  await ctx.transport.postForm({
    url: ctx.intent.url,
    file: ctx.file,
    fields: ctx.intent.fields,
    filename: ctx.file.name,
    signal: ctx.signal,
    onProgress: (uploadedBytes, totalBytes) => ctx.reportProgress({ uploadedBytes, totalBytes }),
  })
}
```

If the intent is missing its `url`, the strategy throws a validation error.

## When to use

* Small to medium files where resumability isn't needed.
* Backends that issue S3-style presigned POST policies.
* The simplest possible upload path.

For large files or unreliable connections, reach for the
[Multipart Strategy](/duck-upload/strategies/multipart) instead. A common pattern is to register
both and let `createIntent` choose per file by size.