Skip to content

Commit f53bafa

Browse files
authored
feat(server): describe batch binary responses with a dedicated content type (#1761)
Batch responses that use the length-prefixed binary framing — streaming mode, and buffered mode when a sub-response carries binary — now advertise `application/vnd.orpc.batch` instead of falling back to `application/octet-stream`. Batch traffic is now distinguishable from ordinary file transfers in proxy logs, CDN rules, and dev tools, where before it was indistinguishable from any other opaque byte stream. `application/octet-stream` means "unknown bytes" and was never chosen for batch — it is what the adapter fills in for any `ReadableStream` body with no content type set. The framing is a format oRPC defines, so it gets a name. ## Compatibility Nothing changes on the wire. Clients resolve the batch body from the `standard-server` hint (`octet-stream` / `file`), not from `content-type`, so old clients read new servers and new clients read old servers. Compression behavior is also unchanged: the new type sits outside the compressible allow-list, exactly like the old one, so batch bodies are still passed through untouched. A `+json` suffix was considered and rejected — it would assert the whole body is JSON (it is binary length prefixes wrapping JSON, sometimes with raw binary appended), and it would pull streaming batches into `CompressionStream`, which does not sync-flush per chunk and would hold frames back instead of delivering them as each sub-request completes. The all-JSON buffered path is untouched and still serializes as `application/json`. In streaming mode the plugin's content type wins over the `headers` option, matching buffered mode, which already hardcodes the type on the `Blob`. ## Testing Both framing paths assert the new header. Full root suite passes (2772 passed, 41 skipped), along with `pnpm type:check` across all packages and `pnpm lint`.
1 parent 4dc0866 commit f53bafa

2 files changed

Lines changed: 17 additions & 2 deletions

File tree

packages/server/src/plugins/batch.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ describe('batchHandlerPlugin', () => {
118118

119119
expect(response!.status).toBe(207)
120120
expect(response!.headers.get('standard-server')).toEqual('file')
121+
expect(response!.headers.get('content-type')).toEqual('application/vnd.orpc.batch')
121122

122123
const buffer = new Uint8Array(await response!.arrayBuffer())
123124
expect(buffer.length).toBeGreaterThan(4)
@@ -212,6 +213,7 @@ describe('batchHandlerPlugin', () => {
212213
expect(matched).toBe(true)
213214
expect(response!.status).toBe(207)
214215
expect(response!.headers.get('standard-server')).toEqual('octet-stream')
216+
expect(response!.headers.get('content-type')).toEqual('application/vnd.orpc.batch')
215217

216218
expect(handlerFn).toHaveBeenCalledTimes(0)
217219

packages/server/src/plugins/batch.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ import { toArray, value } from '@orpc/shared'
88
import { flattenStandardHeader, parseStandardUrl } from '@standardserver/core'
99
import { encodePeerMessage, isClientPeerSendMessage, ServerPeer } from '@standardserver/peer'
1010

11+
/**
12+
* Content type for batch responses that use the length-prefixed binary framing
13+
* (streaming mode, and buffered mode when any sub-response contains binary).
14+
*
15+
* Decoding is driven by the `standard-server` body hint, not this header,
16+
* so it only serves to describe the payload to logs, proxies, and dev tools.
17+
*/
18+
export const BATCH_CONTENT_TYPE = 'application/vnd.orpc.batch'
19+
1120
export interface BatchHandlerPluginOptions<T extends Context> {
1221
/**
1322
* The max size of the batch allowed.
@@ -216,7 +225,7 @@ export class BatchHandlerPlugin<T extends Context> implements StandardHandlerPlu
216225
response: {
217226
status,
218227
headers,
219-
body: new Blob(chunks, { type: 'application/octet-stream' }),
228+
body: new Blob(chunks, { type: BATCH_CONTENT_TYPE }),
220229
},
221230
}
222231
}
@@ -294,7 +303,11 @@ export class BatchHandlerPlugin<T extends Context> implements StandardHandlerPlu
294303

295304
return {
296305
matched: true,
297-
response: { status, headers, body: stream },
306+
response: {
307+
status,
308+
headers: { ...headers, 'content-type': BATCH_CONTENT_TYPE },
309+
body: stream,
310+
},
298311
}
299312
}
300313

0 commit comments

Comments
 (0)