# Response lifecycle (https://survey.dimah.dev/docs/responses)



`startResponse` copies `publishedJson` into `response.definition`. Every draft
write and submit check remains tied to that snapshot—not the current survey
draft and not a later publish.

<Callout>
  A response is both the answer data and the SurveyJS definition needed to
  interpret it.
</Callout>

## States and transitions [#states-and-transitions]

| Status      | Meaning                                 | Allowed next actions             |
| ----------- | --------------------------------------- | -------------------------------- |
| `draft`     | Answers may be incomplete               | Partial save, submit, or abandon |
| `submitted` | The stored definition accepted the data | Reopen or idempotent resubmit    |
| `abandoned` | Closed without submission               | Reopen                           |

| Operation         | Effect                                                                |
| ----------------- | --------------------------------------------------------------------- |
| `startResponse`   | Create a draft or return the response selected by collection settings |
| `savePartial`     | Replace `data` on a draft                                             |
| `submitResponse`  | Validate the snapshot and store the accepted data                     |
| `abandonResponse` | Close a draft without changing `definition` or `data`                 |
| `reopenResponse`  | Move a submitted or abandoned response back to draft                  |

`savePartial` and `abandonResponse` throw `RESPONSE_CLOSED` unless the row is a
draft. `reopenResponse` throws `RESPONSE_CLOSED` on a draft, and when
`settings.reopen` is `false`.

## Save a draft [#save-a-draft]

Partial save replaces the entire `data` object; it is not a key patch.

With the default `sanitizePartial: "clear"`, the server loads the stored
definition, assigns the posted data, runs `clearIncorrectValues(true)`, and
persists the resulting `survey.data`. It does not run required-question
validation, so an incomplete draft remains valid.

`"replace"` stores the payload as sent.

Questions with `choicesByUrl` keep their posted value through the clear. The
server does not fetch that URL.

## Submit against the snapshot [#submit-against-the-snapshot]

The default `validateResult` loads `response.definition`, removes values that
cannot be assigned, runs SurveyJS `validate()`, and returns the resulting
`survey.data` to persist. An invalid result throws `VALIDATION_FAILED` with the
failing question names in `questions`.

Replace `validateResult` for application-specific checks. It still receives the
stored definition. Return the object to persist, return nothing to keep the
input, or throw an `APIError` to reject the submit. See
[Configuration](https://survey.dimah.dev/docs/configuration.md).

A repeated submit of a row that is already `submitted` returns that row when
the cleaned payload matches stored `data`. Omitting `data` also returns the
stored row. A different payload fails with `RESPONSE_CLOSED`.

## Lifecycle hooks [#lifecycle-hooks]

`onStart` runs only before a new insert; a resumed response skips both start
hooks. `onSubmit` runs after validation and before the submitted write.
Throwing from either `on*` callback aborts the write.

`afterStart` and `afterSubmit` run after the row has been stored. A failure
there leaves the write in place. An idempotent resubmit does not run submit
hooks again.

## Handle concurrent writes [#handle-concurrent-writes]

Partial save, submit, abandon, and reopen accept `expectedUpdatedAt` from the
last read. The comparison happens inside the write. A mismatch fails with
`STALE_UPDATE`.

`useSurveyResponse` sends the token it last read. On `STALE_UPDATE` it sets
`stale` and leaves the model mounted. Call `reload` to read the stored snapshot
again.

Omit the token only when a last-write-wins update is intentional.

## Read response lists [#read-response-lists]

`listResponses` defaults to a summary. Summary rows omit `definition` and
`data`. `include: "full"` returns the complete analytics snapshot and is only
available to the editor audience.

Both lists default to 50 rows and cap at 100. Results sort by `updatedAt`
descending. `total` ignores `limit` and `offset`.

`submittedFrom` and `submittedTo` are inclusive bounds on `submittedAt`. Rows
with no submit time are excluded. `updatedAfter` is an exclusive lower bound on
`updatedAt`.

Read full rows against `definition` on that row. Do not join them back to the
live survey document.
