Skip to content

Commit 38ace6d

Browse files
authored
Merge pull request #369 from HL7/async-pattern-alignment
Align async export operations to the simplified async interaction pattern
2 parents 07e999c + c0b8b82 commit 38ace6d

6 files changed

Lines changed: 297 additions & 189 deletions

input/fsh/operations.fsh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ Instance: SQLQueryExport
444444
Usage: #definition
445445
InstanceOf: OperationDefinition
446446
Title: "SQLQuery Export"
447-
Description: "Export SQLQuery Library results asynchronously using the FHIR Asynchronous Bulk Data Request Pattern."
447+
Description: "Export SQLQuery Library results asynchronously using the FHIR Asynchronous Interaction Request Pattern."
448448

449449
* id = "SQLQueryExport"
450450
* url = "http://sql-on-fhir.org/OperationDefinition/$sqlquery-export"

input/pagecontent/OperationDefinition-SQLQueryExport-intro.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Export SQLQuery Library results asynchronously using the FHIR Asynchronous Bulk Data Request Pattern.
1+
Export SQLQuery Library results asynchronously using the FHIR Asynchronous Interaction Request Pattern.
22

33
**Use Cases:**
44

@@ -19,8 +19,9 @@ Export SQLQuery Library results asynchronously using the FHIR Asynchronous Bulk
1919

2020
1. Client sends request with `Prefer: respond-async` header
2121
2. Server returns `202 Accepted` with `Content-Location` polling URL
22-
3. Client polls for status until the poll returns `200 OK` with the manifest in the body
23-
4. Client downloads exported files from the `output.location` URLs in the manifest
22+
3. Client polls for status until the poll returns `303 See Other` with the result URL in the `Location` header
23+
4. Client fetches the result URL, which returns the manifest with `200 OK`
24+
5. Client downloads exported files from the `output.location` URLs in the manifest
2425

2526
This operation combines the query source and parameter binding from
2627
[`$sqlquery-run`](OperationDefinition-SQLQueryRun.html) with the asynchronous

input/pagecontent/OperationDefinition-SQLQueryExport-notes.md

Lines changed: 110 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,17 @@
44

55
#### Asynchronous Pattern
66

7-
This operation follows the [FHIR Asynchronous Bulk Data Request Pattern](https://www.hl7.org/fhir/async-bulk.html); the completion response is specified once in [Common Operation Behavior Asynchronous Delivery](operations-common.html#asynchronous-delivery):
7+
This operation follows the [FHIR Asynchronous Interaction Request Pattern](https://build.fhir.org/ig/HL7/api-incubator-ig/branches/simplified-async-interaction/async-interaction.html); the asynchronous flow is specified once in [Common Operation Behavior - Asynchronous Delivery](operations-common.html#asynchronous-delivery):
88

99
1. Client sends request with `Prefer: respond-async` header and query source parameters
1010
2. Server returns `202 Accepted` with `Content-Location` header pointing to status URL
1111
3. Client polls the status URL for export progress
1212
4. Server responds with `202 Accepted` while export is in progress (MAY include interim results)
13-
5. Upon completion, the status poll returns `200 OK` with the manifest `Parameters` resource (`exportId`, `status`, `output`, …) **in the response body**
14-
6. Client downloads the exported files from the `output.location` URLs in the manifest
13+
5. Once the export has finished (successfully or not), the status poll returns `303 See Other` with a `Location` header carrying the result URL and an empty body
14+
6. Client fetches the result URL; a successful export returns `200 OK` with the manifest `Parameters` resource (`exportId`, `status`, `output`, …), and a failed export returns the error status code with an `OperationOutcome`
15+
7. Client downloads the exported files from the `output.location` URLs in the manifest
1516

16-
**Note**: This operation uses a FHIR `Parameters` resource as the manifest instead of the Bulk Data JSON manifest object to:
17-
18-
- Provide structured status reporting and metadata
19-
- Allow extensible output metadata specific to export operations
20-
- Maintain consistency with other FHIR operations
21-
22-
**Note**: Completion is signalled by `200 OK` with the manifest in the body of the status-poll response, as in the FHIR Asynchronous Bulk Data Request Pattern. The operation does not use a `303 See Other` redirect to a separate result resource, so standard Bulk Data clients interoperate without special handling.
17+
The result resource for this operation is the manifest `Parameters` resource described under [Output Parameters](#output-parameters). Clients MUST treat the status and result URLs as opaque values.
2318

2419
##### Async Flow Diagram
2520

@@ -43,11 +38,17 @@ sequenceDiagram
4338
rect rgb(240, 255, 240)
4439
Note over C,S: Step 3: Completion
4540
C->>S: GET /status/abc123
41+
S-->>C: 303 See Other<br/>Location: /result/abc123<br/>(empty body)
42+
end
43+
44+
rect rgb(255, 255, 240)
45+
Note over C,S: Step 4: Result fetch
46+
C->>S: GET /result/abc123
4647
S-->>C: 200 OK<br/>Body: Parameters{status: completed, output: [{name, location}]}
4748
end
4849
4950
rect rgb(255, 248, 240)
50-
Note over C,S: Step 4: Download
51+
Note over C,S: Step 5: Download
5152
C->>S: GET /export/abc123/bp-results.csv
5253
S-->>C: 200 OK<br/>Content-Type: text/csv<br/>Body: patient_id,systolic,...
5354
end
@@ -74,6 +75,8 @@ sequenceDiagram
7475
participant S as Server
7576
7677
C->>S: GET /status/abc123
78+
S-->>C: 303 See Other<br/>Location: /result/abc123<br/>(empty body)
79+
C->>S: GET /result/abc123
7780
S-->>C: 500 Internal Server Error<br/>Body: OperationOutcome{severity: error, diagnostics: ...}
7881
```
7982

@@ -101,16 +104,20 @@ Optional filtering parameters:
101104

102105
##### Status Request
103106

104-
- `Accept` (recommended) - Specifies the format of the status response, including the completion (`200 OK`) response that carries the manifest
107+
- `Accept` (recommended) - Specifies the format of interim status responses and error responses on the status URL; the completing poll returns `303 See Other` with an empty body
108+
109+
##### Result Request
110+
111+
- `Accept` (recommended) - Specifies the representation of the result: the manifest `Parameters` resource on success, or the `OperationOutcome` on failure
105112

106113
##### Header Scope
107114

108-
Each status-poll request's headers apply to **that poll's response**. Because
109-
completion is delivered as `200 OK` with the manifest in the body of the
110-
status-poll response (there is no separate result resource), the `Accept` header
111-
sent on the completing poll governs the representation of the manifest. This
112-
allows a client to negotiate a different representation for interim status
113-
responses (e.g. minimal JSON) than for the final manifest if it chooses.
115+
Each request's headers apply to **that request's response**. Because
116+
completion is delivered as `303 See Other` with an empty body, the `Accept`
117+
header that governs the representation of the manifest is the one sent on the
118+
result `GET`, not the one sent on the completing status poll. This allows a
119+
client to negotiate a different representation for interim status responses
120+
(e.g. minimal JSON) than for the final manifest if it chooses.
114121

115122
#### Parameters
116123

@@ -241,9 +248,10 @@ SQLQuery profile for the binding rules and the mapping from
241248

242249
#### Output Parameters
243250

244-
Output parameters appear in the **completion response** — the `200 OK`
245-
status-poll response that carries the manifest. They are not present in the
246-
`202 Accepted` responses returned while the export is still in progress.
251+
Output parameters form the **manifest** - the `Parameters` resource returned
252+
with `200 OK` from the result URL after the completing poll's `303 See Other`
253+
redirect. They are not present in the `202 Accepted` responses returned while
254+
the export is still in progress.
247255

248256
##### Export Identifiers
249257

@@ -310,21 +318,21 @@ For large exports, servers MAY partition the output into multiple files. When pa
310318

311319
```json
312320
{
313-
"name": "output",
314-
"part": [
315-
{
316-
"name": "name",
317-
"valueString": "patient_bp_results"
318-
},
319-
{
320-
"name": "location",
321-
"valueUri": "https://example.com/export/123/patient_bp_results.part1.csv"
322-
},
323-
{
324-
"name": "location",
325-
"valueUri": "https://example.com/export/123/patient_bp_results.part2.csv"
326-
}
327-
]
321+
"name": "output",
322+
"part": [
323+
{
324+
"name": "name",
325+
"valueString": "patient_bp_results"
326+
},
327+
{
328+
"name": "location",
329+
"valueUri": "https://example.com/export/123/patient_bp_results.part1.csv"
330+
},
331+
{
332+
"name": "location",
333+
"valueUri": "https://example.com/export/123/patient_bp_results.part2.csv"
334+
}
335+
]
328336
}
329337
```
330338

@@ -336,14 +344,16 @@ Clients MUST download all parts to obtain the complete dataset.
336344

337345
The $sqlquery-export operation uses standard HTTP status codes to indicate the outcome:
338346

339-
| Status Code | Description | When to Use |
340-
| ------------------------- | -------------------- | -------------------------------------------------------------------- |
341-
| 202 Accepted | In Progress | Export request accepted or still in progress during polling |
342-
| 200 OK | Complete | Export complete; the status-poll response body carries the manifest |
343-
| 400 Bad Request | Client Error | Invalid parameters, unsupported parameters, missing required headers |
344-
| 404 Not Found | Not Found | SQLQuery Library not found, or cancelled export status URL |
345-
| 422 Unprocessable Entity | Business Logic Error | Valid request but query is invalid or cannot be executed |
346-
| 500 Internal Server Error | Server Error | Unexpected server error; on a status poll, indicates operation failure |
347+
| Status Code | Description | When to Use |
348+
| ------------------------- | -------------------- | ----------------------------------------------------------------------------------- |
349+
| 202 Accepted | In Progress | Export request accepted, still in progress during polling, or cancellation accepted |
350+
| 303 See Other | Job Finished | Export finished (successfully or not); `Location` header carries the result URL |
351+
| 200 OK | Result Available | Result URL returns the manifest `Parameters`; download URLs return the files |
352+
| 400 Bad Request | Client Error | Invalid parameters, unsupported parameters, missing required headers |
353+
| 404 Not Found | Not Found | SQLQuery Library not found, or cancelled export status URL |
354+
| 422 Unprocessable Entity | Business Logic Error | Valid request but query is invalid or cannot be executed |
355+
| 429 Too Many Requests | Excessive Polling | Client is polling too frequently; back off exponentially, guided by `Retry-After` |
356+
| 500 Internal Server Error | Server Error | Unexpected server error; on the result URL, the failure outcome of the export |
347357

348358
{:.table-data}
349359

@@ -455,43 +465,51 @@ Content-Type: application/fhir+json
455465

456466
1. **Kick-off Request**: Client sends `POST Library/$sqlquery-export` with `Prefer: respond-async` header and one or more `query` parameters.
457467
2. **Kick-off Response**: Server responds with:
458-
- `202 Accepted` status code
459-
- `Content-Location` header with the absolute URL for subsequent status requests (polling location)
460-
- Parameters resource with `status` parameter set to `accepted` and `location` parameter
461-
- If request is not valid or cannot be processed, server responds with `400 Bad Request` and `OperationOutcome` resource in the body.
468+
- `202 Accepted` status code
469+
- `Content-Location` header with the absolute URL for subsequent status requests (polling location)
470+
- Parameters resource with `status` parameter set to `accepted` and `location` parameter
471+
- If request is not valid or cannot be processed, server responds with `400 Bad Request` and `OperationOutcome` resource in the body.
462472
3. **Status Polling**: Client polls the polling location to get status of the export:
463-
- **In Progress**: `202 Accepted` with optional Parameters resource for interim status
464-
- **Progress Updates**: Server MAY include `X-Progress` header to indicate completion percentage
465-
- **Retry-After**: Server SHOULD include `Retry-After` header to indicate when to retry
466-
- **Interim Results**: Server MAY include partial/interim results in response body (implementation-defined)
467-
4. **Completion**: When the export is ready, the status poll returns:
468-
- `200 OK` status code
469-
- A `Parameters` resource in the body containing `status` = `completed`, the
470-
export metadata, and the `output` entries with their download `location`s
471-
- This is the same manifest a synchronous call would return; there is no
472-
`303 See Other` redirect and no separate result URL
473-
5. **Error Handling**: If the export fails, the status poll returns the relevant
474-
error status code (e.g. `500 Internal Server Error`) with an
475-
`OperationOutcome` body. Polling-transport errors and operation failures are
476-
distinguished by the status code on the poll response itself.
473+
- **In Progress**: `202 Accepted` with optional Parameters resource for interim status
474+
- **Progress Updates**: Server MAY include `X-Progress` header to indicate completion percentage
475+
- **Retry-After**: Server SHOULD include `Retry-After` header to indicate when to retry
476+
- **Interim Results**: Server MAY include partial/interim results in response body (implementation-defined)
477+
- **Excessive Polling**: Server MAY respond with `429 Too Many Requests`; clients SHOULD apply exponential backoff
478+
4. **Completion**: When the export has finished - whether it succeeded or
479+
failed - the status poll returns:
480+
- `303 See Other` status code
481+
- `Location` header with the absolute result URL
482+
- An empty body
483+
- The status endpoint reflects polling machinery only; it never communicates
484+
the job's outcome. Clients MUST treat the status and result URLs as opaque
485+
values.
486+
5. **Result Retrieval**: Client fetches the result URL with `GET`:
487+
- **Success**: `200 OK` with the manifest `Parameters` resource in the body
488+
containing `status` = `completed`, the export metadata, and the `output`
489+
entries with their download `location`s
490+
- **Failure**: the relevant error status code (e.g.
491+
`500 Internal Server Error`) with an `OperationOutcome` body explaining
492+
the failure; repeated fetches return the same outcome within the validity
493+
window
477494
6. **Cancellation** (Recommended):
478495
Servers SHOULD support export cancellation via DELETE request to the status URL:
479-
- Client sends `DELETE` request to the status polling URL
480-
- Server responds with `202 Accepted`
481-
- Subsequent status requests return `404 Not Found`
482-
- Server SHOULD clean up any partial results
496+
- Client sends `DELETE` request to the status polling URL
497+
- Server responds with `202 Accepted`
498+
- Subsequent status requests return `404 Not Found`
499+
- Server SHOULD clean up any partial results
483500
7. **Result Lifetime**:
484-
The completed status URL (which returns the manifest) and the
501+
The result URL (which returns the manifest) and the
485502
`output.location` download URLs SHALL remain valid for at least 24 hours after
486503
export completion:
487-
- Servers SHOULD support multiple retrievals of the completed manifest
488-
- Servers MAY include an `Expires` header to indicate when the URLs expire
489-
- Clients should retrieve results promptly but can retry within the validity window
504+
- Servers SHOULD support multiple retrievals of the result
505+
- Servers MAY include an `Expires` header to indicate when the URLs expire
506+
- Clients should retrieve results promptly but can retry within the validity window
490507
8. **Access Control**:
491-
Servers SHALL protect status and download URLs with appropriate access controls:
492-
- Same authorization context as the original request, OR
493-
- Non-guessable URLs (e.g., cryptographically random tokens)
494-
- Unauthorized access attempts return `401 Unauthorized` or `403 Forbidden`
508+
Servers SHALL protect status, result, and download URLs with appropriate access controls:
509+
- Same authorization context as the original request (servers SHOULD limit
510+
access to the client that initiated the export), OR
511+
- Non-guessable URLs (e.g., cryptographically random tokens)
512+
- Unauthorized access attempts return `401 Unauthorized` or `403 Forbidden`
495513
9. **File Download**: Client downloads the output from URLs in the `output.location` parameters.
496514

497515
#### Examples
@@ -703,7 +721,23 @@ Accept: application/fhir+json
703721
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc...
704722
```
705723

706-
The status poll returns `200 OK` with the manifest in the body; there is no redirect:
724+
The export has finished, so the status poll returns `303 See Other` with the result URL in the `Location` header and no body:
725+
726+
```http
727+
HTTP/1.1 303 See Other
728+
Location: https://example.com/fhir/export/550e8400-e29b-41d4-a716-446655440000/result
729+
```
730+
731+
**Step 6: Fetch the Result**
732+
733+
Client fetches the result URL; the manifest `Parameters` resource is returned:
734+
735+
```http
736+
GET /fhir/export/550e8400-e29b-41d4-a716-446655440000/result HTTP/1.1
737+
Host: example.com
738+
Accept: application/fhir+json
739+
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc...
740+
```
707741

708742
```http
709743
HTTP/1.1 200 OK
@@ -758,7 +792,7 @@ Expires: Wed, 04 Mar 2026 14:30:42 GMT
758792
}
759793
```
760794

761-
**Step 6: Download Files**
795+
**Step 7: Download Files**
762796

763797
Client downloads each file:
764798

0 commit comments

Comments
 (0)