HttpBundleObservable
HttpBundleObservable is the object returned by client.bundle() and client.transaction(). It groups multiple HttpRequestObservable instances into a single POST /$bundle request with Content-Type: multipart/mixed, distributes each parsed sub-response back to its originating request, and exposes the full response array via getResponses().
Package: @opra/client
Extends: Observable<HttpResponse[]>
Overview
const r1 = client.get<Customer>('customers@1');
const r2 = client.get<Order[]>('orders');
const [c1, c2] = await client.bundle([r1, r2]).getResponses();
// Each HttpRequestObservable also resolves individually:
const customer = await r1.getBody();
const orders = await r2.getBody();
When requests are bound to a bundle they do not fire their own network calls — the bundle's single multipart/mixed POST drives them all. The individual getBody() / getResponse() calls block until the bundle completes and then resolve from a cached ReplaySubject, so you can await them before or after calling getResponses().
Methods
param(name, value?)
Sets a URL query parameter on the /$bundle request. Chainable.
param(name: string, value: any): this
param(params: Record<string, any>): this
Used internally by client.transaction() to append ?transaction=true. You can call it directly for any other query parameter the server supports.
client.bundle([r1, r2]).param('timeout', 5000).getResponses();
getResponses()
Sends the bundle request and resolves with the array of sub-responses in the same order as the input requests.
getResponses(): Promise<HttpResponse[]>
Each HttpResponse has the status, headers, and body of the corresponding sub-request. If a sub-request returned an error status, its slot in the array carries that error response — getResponses() itself does not throw for per-part errors.
const [res1, res2] = await client.bundle([r1, r2]).getResponses();
if (!res1.ok) console.error('r1 failed:', res1.status);
How the wire protocol works
HttpBundleObservable serializes each request using serializeHttpRequest() to produce raw HTTP/1.1 bytes. These become the parts of a multipart/mixed body sent as a single POST /$bundle. Each part carries an X-Request-Id header. The server echoes the X-Request-Id in the response part so the client can match sub-responses back to their originating requests.
POST /api/$bundle HTTP/1.1
Content-Type: multipart/mixed; boundary=---b
-----b
Content-Type: application/http
X-Request-Id: <uuid-1>
GET /api/customers@1 HTTP/1.1
-----b
Content-Type: application/http
X-Request-Id: <uuid-2>
GET /api/orders HTTP/1.1
-----b--
Transactions
Use client.transaction() instead of client.bundle() to wrap all sub-requests in a single database transaction:
const r1 = client.post('orders', { productId: 'p1', qty: 2 });
const r2 = client.patch('inventory/p1', { reserve: 2 });
await client.transaction([r1, r2]).getResponses();
// If either sub-request fails, the server rolls back both.
transaction() is equivalent to:
client.bundle([r1, r2]).param('transaction', true)
Subscribing as an Observable
Because HttpBundleObservable extends Observable<HttpResponse[]>, you can also use it with RxJS:
import { switchMap } from 'rxjs';
trigger$.pipe(
switchMap(() => client.bundle([r1, r2]))
).subscribe(responses => console.log(responses));
See also
HttpClientBase.bundle()— creates anHttpBundleObservableHttpClientBase.transaction()— creates a transactional bundleHttpRequestObservable.requestId— the ID used for per-part correlation- Batch Requests — server-side batch and transaction support