@blueprint-ts/core 4.1.0-beta.1 → 4.1.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/docs/services/requests/file-uploads.md +65 -2
- package/docs/services/requests/request-bodies.md +28 -0
- package/package.json +1 -1
- package/src/requests/bodies/BinaryBody.ts +31 -0
- package/src/requests/contracts/BodyContract.ts +3 -1
- package/src/requests/contracts/HeadersContract.ts +4 -0
- package/src/requests/drivers/contracts/ResponseHandlerContract.ts +2 -2
- package/src/requests/drivers/fetch/FetchDriver.ts +2 -2
- package/src/requests/drivers/fetch/FetchResponse.ts +2 -2
- package/src/requests/drivers/xhr/XMLHttpRequestResponse.ts +5 -5
- package/src/requests/factories/BinaryBodyFactory.ts +13 -0
- package/src/requests/index.ts +9 -2
- package/src/requests/responses/BaseResponse.ts +2 -2
- package/tests/service/requests/BodiesAndFactories.test.ts +24 -0
- package/tests/service/requests/fetch/FetchDriver.test.ts +21 -3
- package/tests/service/requests/xhr/XMLHttpRequestDriver.test.ts +22 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
## 4.1.0-beta.2 - 2026-03-25 (beta)
|
|
2
|
+
|
|
3
|
+
# [4.1.0-beta.2](/compare/4.1.0-beta.1...4.1.0-beta.2) (2026-03-25)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* Tighten response header typing f767572
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* Support Blob and ArrayBuffer bodies a265df3
|
|
1
14
|
## 4.1.0-beta.1 - 2026-03-21 (beta)
|
|
2
15
|
|
|
3
16
|
# [4.1.0-beta.1](/compare/v4.0.0...v4.1.0-beta.1) (2026-03-21)
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
# File Uploads
|
|
2
2
|
|
|
3
|
-
Use `FormDataFactory`
|
|
4
|
-
upload progress for a
|
|
3
|
+
Use `FormDataFactory` for multipart uploads with fields, and use `BinaryBodyFactory` when the endpoint expects a raw
|
|
4
|
+
binary body such as a chunk `PUT`. Use `XMLHttpRequestDriver` when the consuming application needs upload progress for a
|
|
5
|
+
progress bar.
|
|
6
|
+
|
|
7
|
+
Choose the body factory based on the wire format your endpoint expects:
|
|
8
|
+
|
|
9
|
+
- Use `FormDataFactory` when the request includes normal fields plus one or more files.
|
|
10
|
+
- Use `BinaryBodyFactory` when the request body itself is the file or chunk.
|
|
11
|
+
- Use `FetchDriver` if you just need to send the upload.
|
|
12
|
+
- Use `XMLHttpRequestDriver` if you also need upload progress events.
|
|
5
13
|
|
|
6
14
|
## Request Definition
|
|
7
15
|
|
|
@@ -92,10 +100,65 @@ await request.setBody({
|
|
|
92
100
|
}).send()
|
|
93
101
|
```
|
|
94
102
|
|
|
103
|
+
## Raw Chunk Uploads
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import {
|
|
107
|
+
BaseRequest,
|
|
108
|
+
BinaryBodyFactory,
|
|
109
|
+
JsonResponse,
|
|
110
|
+
RequestMethodEnum,
|
|
111
|
+
XMLHttpRequestDriver
|
|
112
|
+
} from '@blueprint-ts/core/requests'
|
|
113
|
+
|
|
114
|
+
interface UploadPartResponse {
|
|
115
|
+
etag: string
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
class UploadPartRequest extends BaseRequest<
|
|
119
|
+
boolean,
|
|
120
|
+
{ message: string },
|
|
121
|
+
UploadPartResponse,
|
|
122
|
+
JsonResponse<UploadPartResponse>,
|
|
123
|
+
Uint8Array
|
|
124
|
+
> {
|
|
125
|
+
public method(): RequestMethodEnum {
|
|
126
|
+
return RequestMethodEnum.PUT
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
public url(): string {
|
|
130
|
+
return '/api/v1/uploads/part'
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public getResponse(): JsonResponse<UploadPartResponse> {
|
|
134
|
+
return new JsonResponse<UploadPartResponse>()
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
public override getRequestBodyFactory() {
|
|
138
|
+
return new BinaryBodyFactory<Uint8Array>('application/octet-stream')
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
public override requestHeaders() {
|
|
142
|
+
return {
|
|
143
|
+
'X-Part-Number': '1'
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
protected override getRequestDriver() {
|
|
148
|
+
return new XMLHttpRequestDriver()
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
In this example, `setBody(...)` can receive a `Blob`, `ArrayBuffer`, `Uint8Array`, or another typed-array/data-view
|
|
154
|
+
payload supported by `BinaryBodyFactory`.
|
|
155
|
+
|
|
95
156
|
## Notes
|
|
96
157
|
|
|
97
158
|
- Upload progress requires `XMLHttpRequestDriver`. The default `FetchDriver` does not emit upload progress events.
|
|
98
159
|
- Define `XMLHttpRequestDriver` inside the upload request class when that request should always support progress.
|
|
160
|
+
- `BinaryBodyFactory` only sets `Content-Type` automatically when the body is a `Blob` with a non-empty `type`.
|
|
161
|
+
For `ArrayBuffer` and typed-array uploads, pass the expected content type explicitly.
|
|
99
162
|
- `XMLHttpRequestDriver` supports the same `corsWithCredentials` and `headers` options as `FetchDriver`, including
|
|
100
163
|
header callbacks.
|
|
101
164
|
- Request-defined drivers do not automatically inherit config from the globally registered driver.
|
|
@@ -56,6 +56,34 @@ public override getRequestBodyFactory() {
|
|
|
56
56
|
|
|
57
57
|
If you want to show upload progress for multipart file uploads, see [File Uploads](/services/requests/file-uploads).
|
|
58
58
|
|
|
59
|
+
## Raw Binary Bodies
|
|
60
|
+
|
|
61
|
+
Use `BinaryBodyFactory` when the request body should be sent as raw binary instead of multipart form data. This is a
|
|
62
|
+
better fit for chunk uploads, binary artifact pushes, and endpoints that expect the request body as-is:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { BinaryBodyFactory } from '@blueprint-ts/core/requests'
|
|
66
|
+
|
|
67
|
+
public override getRequestBodyFactory() {
|
|
68
|
+
return new BinaryBodyFactory<ArrayBuffer>('application/octet-stream')
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`BinaryBodyFactory` supports:
|
|
73
|
+
|
|
74
|
+
- `Blob`
|
|
75
|
+
- `ArrayBuffer`
|
|
76
|
+
- typed-array and view values such as `Uint8Array` or `DataView`
|
|
77
|
+
|
|
78
|
+
`Content-Type` resolution works like this:
|
|
79
|
+
|
|
80
|
+
- If you pass a content type to `BinaryBodyFactory`, Blueprint sends that `Content-Type` header.
|
|
81
|
+
- Otherwise, if the body is a `Blob` with a non-empty `type`, Blueprint uses `Blob.type`.
|
|
82
|
+
- Otherwise, Blueprint does not add a `Content-Type` header for you.
|
|
83
|
+
|
|
84
|
+
`BinaryBodyFactory` works with both `FetchDriver` and `XMLHttpRequestDriver`. Choose `XMLHttpRequestDriver` only when
|
|
85
|
+
the consuming application needs upload progress events.
|
|
86
|
+
|
|
59
87
|
## Custom Body Factories
|
|
60
88
|
|
|
61
89
|
You can implement your own body factory by returning a `BodyContract` with custom headers and serialization logic.
|
package/package.json
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type BodyContent, type BodyContract } from '../contracts/BodyContract'
|
|
2
|
+
import { type HeadersContract } from '../contracts/HeadersContract'
|
|
3
|
+
|
|
4
|
+
export class BinaryBody<RequestBody extends Exclude<BodyContent, string | FormData>> implements BodyContract {
|
|
5
|
+
public constructor(
|
|
6
|
+
protected data: RequestBody,
|
|
7
|
+
protected contentType?: string
|
|
8
|
+
) {}
|
|
9
|
+
|
|
10
|
+
public getHeaders(): HeadersContract {
|
|
11
|
+
const contentType = this.resolveContentType()
|
|
12
|
+
|
|
13
|
+
return contentType === undefined ? {} : { 'Content-Type': contentType }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public getContent(): RequestBody {
|
|
17
|
+
return this.data
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
protected resolveContentType(): string | undefined {
|
|
21
|
+
if (this.contentType !== undefined) {
|
|
22
|
+
return this.contentType
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (typeof Blob !== 'undefined' && this.data instanceof Blob && this.data.type !== '') {
|
|
26
|
+
return this.data.type
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return undefined
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { type HeadersContract } from './HeadersContract'
|
|
2
2
|
|
|
3
|
+
export type BodyContent = string | FormData | Blob | ArrayBuffer | ArrayBufferView<ArrayBuffer>
|
|
4
|
+
|
|
3
5
|
export interface BodyContract {
|
|
4
|
-
getContent():
|
|
6
|
+
getContent(): BodyContent
|
|
5
7
|
|
|
6
8
|
getHeaders(): HeadersContract
|
|
7
9
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ResolvedHeadersContract } from '../../contracts/HeadersContract'
|
|
2
2
|
|
|
3
3
|
export interface ResponseHandlerContract {
|
|
4
4
|
getStatusCode(): number | undefined
|
|
5
|
-
getHeaders():
|
|
5
|
+
getHeaders(): ResolvedHeadersContract
|
|
6
6
|
getRawResponse(): Response
|
|
7
7
|
json<ResponseBodyInterface>(): Promise<ResponseBodyInterface>
|
|
8
8
|
text(): Promise<string>
|
|
@@ -2,7 +2,7 @@ import { ResponseException } from '../../exceptions/ResponseException'
|
|
|
2
2
|
import { FetchResponse } from './FetchResponse'
|
|
3
3
|
import { RequestMethodEnum } from '../../RequestMethod.enum'
|
|
4
4
|
import { type HeadersContract, type HeaderValue } from '../../contracts/HeadersContract'
|
|
5
|
-
import { type BodyContract } from '../../contracts/BodyContract'
|
|
5
|
+
import { type BodyContent, type BodyContract } from '../../contracts/BodyContract'
|
|
6
6
|
import { type RequestDriverContract } from '../../contracts/RequestDriverContract'
|
|
7
7
|
import { type DriverConfigContract } from '../../contracts/DriverConfigContract'
|
|
8
8
|
import { type ResponseHandlerContract } from '../contracts/ResponseHandlerContract'
|
|
@@ -17,7 +17,7 @@ interface FetchDriverConfig {
|
|
|
17
17
|
headers: HeadersContract
|
|
18
18
|
credentials?: FetchDriverCredentialConfigEnum | undefined
|
|
19
19
|
signal?: AbortSignal | undefined
|
|
20
|
-
body?:
|
|
20
|
+
body?: BodyContent | URLSearchParams | undefined
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export class FetchDriver implements RequestDriverContract {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ResolvedHeadersContract } from '../../contracts/HeadersContract'
|
|
2
2
|
import { type ResponseHandlerContract } from '../contracts/ResponseHandlerContract'
|
|
3
3
|
|
|
4
4
|
export class FetchResponse implements ResponseHandlerContract {
|
|
@@ -8,7 +8,7 @@ export class FetchResponse implements ResponseHandlerContract {
|
|
|
8
8
|
return this.response.status
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
public getHeaders():
|
|
11
|
+
public getHeaders(): ResolvedHeadersContract {
|
|
12
12
|
return Object.fromEntries(this.response.headers)
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ResolvedHeadersContract } from '../../contracts/HeadersContract'
|
|
2
2
|
import { type ResponseHandlerContract } from '../contracts/ResponseHandlerContract'
|
|
3
3
|
|
|
4
4
|
export class XMLHttpRequestResponse implements ResponseHandlerContract {
|
|
5
5
|
protected response: Response
|
|
6
|
-
protected headers:
|
|
6
|
+
protected headers: ResolvedHeadersContract
|
|
7
7
|
|
|
8
8
|
public constructor(protected request: XMLHttpRequest) {
|
|
9
9
|
this.headers = this.parseHeaders(request.getAllResponseHeaders())
|
|
@@ -18,7 +18,7 @@ export class XMLHttpRequestResponse implements ResponseHandlerContract {
|
|
|
18
18
|
return this.request.status
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
public getHeaders():
|
|
21
|
+
public getHeaders(): ResolvedHeadersContract {
|
|
22
22
|
return this.headers
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -65,8 +65,8 @@ export class XMLHttpRequestResponse implements ResponseHandlerContract {
|
|
|
65
65
|
)
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
protected parseHeaders(rawHeaders: string):
|
|
69
|
-
const headers:
|
|
68
|
+
protected parseHeaders(rawHeaders: string): ResolvedHeadersContract {
|
|
69
|
+
const headers: ResolvedHeadersContract = {}
|
|
70
70
|
const lines = rawHeaders.trim()
|
|
71
71
|
|
|
72
72
|
if (lines.length === 0) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BinaryBody } from '../bodies/BinaryBody'
|
|
2
|
+
import { type BodyFactoryContract } from '../contracts/BodyFactoryContract'
|
|
3
|
+
import { type BodyContent, type BodyContract } from '../contracts/BodyContract'
|
|
4
|
+
|
|
5
|
+
export type BinaryBodyContent = Exclude<BodyContent, string | FormData>
|
|
6
|
+
|
|
7
|
+
export class BinaryBodyFactory<RequestBodyInterface extends BinaryBodyContent> implements BodyFactoryContract<RequestBodyInterface> {
|
|
8
|
+
public constructor(protected contentType?: string) {}
|
|
9
|
+
|
|
10
|
+
public make(body: RequestBodyInterface): BodyContract {
|
|
11
|
+
return new BinaryBody<RequestBodyInterface>(body, this.contentType)
|
|
12
|
+
}
|
|
13
|
+
}
|
package/src/requests/index.ts
CHANGED
|
@@ -9,9 +9,11 @@ import { RequestErrorRouter } from './RequestErrorRouter'
|
|
|
9
9
|
import { RequestEvents } from './RequestEvents.enum'
|
|
10
10
|
import { RequestMethodEnum } from './RequestMethod.enum'
|
|
11
11
|
import { RequestConcurrencyMode } from './RequestConcurrencyMode.enum'
|
|
12
|
+
import { BinaryBody } from './bodies/BinaryBody'
|
|
12
13
|
import { JsonBodyFactory } from './factories/JsonBodyFactory'
|
|
14
|
+
import { BinaryBodyFactory, type BinaryBodyContent } from './factories/BinaryBodyFactory'
|
|
13
15
|
import { FormDataFactory } from './factories/FormDataFactory'
|
|
14
|
-
import { type BodyContract } from './contracts/BodyContract'
|
|
16
|
+
import { type BodyContent, type BodyContract } from './contracts/BodyContract'
|
|
15
17
|
import { type RequestLoaderContract } from './contracts/RequestLoaderContract'
|
|
16
18
|
import { type RequestDriverContract } from './contracts/RequestDriverContract'
|
|
17
19
|
import { type RequestLoaderFactoryContract } from './contracts/RequestLoaderFactoryContract'
|
|
@@ -21,7 +23,7 @@ import { type ResponseHandlerContract } from './drivers/contracts/ResponseHandle
|
|
|
21
23
|
import { type BaseRequestContract } from './contracts/BaseRequestContract'
|
|
22
24
|
import { ResponseException } from './exceptions/ResponseException'
|
|
23
25
|
import { StaleResponseException } from './exceptions/StaleResponseException'
|
|
24
|
-
import { type HeaderValue, type HeadersContract } from './contracts/HeadersContract'
|
|
26
|
+
import { type HeaderValue, type HeadersContract, type ResolvedHeadersContract } from './contracts/HeadersContract'
|
|
25
27
|
import { type RequestConcurrencyOptions } from './types/RequestConcurrencyOptions'
|
|
26
28
|
import { type RequestUploadProgress } from './types/RequestUploadProgress'
|
|
27
29
|
import { XMLHttpRequestDriver } from './drivers/xhr/XMLHttpRequestDriver'
|
|
@@ -40,12 +42,16 @@ export {
|
|
|
40
42
|
RequestConcurrencyMode,
|
|
41
43
|
ResponseException,
|
|
42
44
|
StaleResponseException,
|
|
45
|
+
BinaryBody,
|
|
43
46
|
JsonBodyFactory,
|
|
47
|
+
BinaryBodyFactory,
|
|
44
48
|
FormDataFactory,
|
|
45
49
|
XMLHttpRequestDriver
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
export type {
|
|
53
|
+
BodyContent,
|
|
54
|
+
BinaryBodyContent,
|
|
49
55
|
RequestDriverContract,
|
|
50
56
|
RequestLoaderContract,
|
|
51
57
|
BodyContract,
|
|
@@ -56,6 +62,7 @@ export type {
|
|
|
56
62
|
BaseRequestContract,
|
|
57
63
|
HeaderValue,
|
|
58
64
|
HeadersContract,
|
|
65
|
+
ResolvedHeadersContract,
|
|
59
66
|
RequestConcurrencyOptions,
|
|
60
67
|
RequestUploadProgress
|
|
61
68
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ResponseHandlerContract } from '../drivers/contracts/ResponseHandlerContract'
|
|
2
|
-
import { type
|
|
2
|
+
import { type ResolvedHeadersContract } from '../contracts/HeadersContract'
|
|
3
3
|
import { type ResponseContract } from '../contracts/ResponseContract'
|
|
4
4
|
|
|
5
5
|
export abstract class BaseResponse<ResponseInterface> implements ResponseContract<ResponseInterface> {
|
|
@@ -27,7 +27,7 @@ export abstract class BaseResponse<ResponseInterface> implements ResponseContrac
|
|
|
27
27
|
return this.response?.getStatusCode()
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
public getHeaders():
|
|
30
|
+
public getHeaders(): ResolvedHeadersContract | undefined {
|
|
31
31
|
return this.response?.getHeaders()
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { BinaryBody } from '../../../src/requests/bodies/BinaryBody'
|
|
2
3
|
import { JsonBody } from '../../../src/requests/bodies/JsonBody'
|
|
4
|
+
import { BinaryBodyFactory } from '../../../src/requests/factories/BinaryBodyFactory'
|
|
3
5
|
import { JsonBodyFactory } from '../../../src/requests/factories/JsonBodyFactory'
|
|
4
6
|
import { FormDataFactory } from '../../../src/requests/factories/FormDataFactory'
|
|
5
7
|
import { FormDataBody } from '../../../src/requests/bodies/FormDataBody'
|
|
@@ -19,6 +21,28 @@ describe('Request bodies and factories', () => {
|
|
|
19
21
|
expect(body).toBeInstanceOf(JsonBody)
|
|
20
22
|
})
|
|
21
23
|
|
|
24
|
+
it('BinaryBody returns explicit content type and binary content', () => {
|
|
25
|
+
const body = new BinaryBody(new Uint8Array([1, 2, 3]), 'application/octet-stream')
|
|
26
|
+
|
|
27
|
+
expect(body.getHeaders()).toEqual({ 'Content-Type': 'application/octet-stream' })
|
|
28
|
+
expect(body.getContent()).toEqual(new Uint8Array([1, 2, 3]))
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('BinaryBody uses Blob mime type when no explicit content type is given', () => {
|
|
32
|
+
const blob = new Blob(['hello'], { type: 'application/custom-binary' })
|
|
33
|
+
const body = new BinaryBody(blob)
|
|
34
|
+
|
|
35
|
+
expect(body.getHeaders()).toEqual({ 'Content-Type': 'application/custom-binary' })
|
|
36
|
+
expect(body.getContent()).toBe(blob)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('BinaryBodyFactory returns BinaryBody', () => {
|
|
40
|
+
const factory = new BinaryBodyFactory<Uint8Array>('application/octet-stream')
|
|
41
|
+
const body = factory.make(new Uint8Array([4, 5, 6]))
|
|
42
|
+
|
|
43
|
+
expect(body).toBeInstanceOf(BinaryBody)
|
|
44
|
+
})
|
|
45
|
+
|
|
22
46
|
it('FormDataFactory returns FormDataBody', () => {
|
|
23
47
|
const factory = new FormDataFactory<{ name: string }>()
|
|
24
48
|
const body = factory.make({ name: 'alice' })
|
|
@@ -3,10 +3,10 @@ import { FetchDriver } from '../../../../src/requests/drivers/fetch/FetchDriver'
|
|
|
3
3
|
import { FetchResponse } from '../../../../src/requests/drivers/fetch/FetchResponse'
|
|
4
4
|
import { RequestMethodEnum } from '../../../../src/requests/RequestMethod.enum'
|
|
5
5
|
import { ResponseException } from '../../../../src/requests/exceptions/ResponseException'
|
|
6
|
-
import type { BodyContract } from '../../../../src/requests/contracts/BodyContract'
|
|
6
|
+
import type { BodyContent, BodyContract } from '../../../../src/requests/contracts/BodyContract'
|
|
7
7
|
|
|
8
|
-
const createBody = (content: string): BodyContract => ({
|
|
9
|
-
getHeaders: () =>
|
|
8
|
+
const createBody = (content: BodyContent, headers: Record<string, string> = { 'Content-Type': 'application/json' }): BodyContract => ({
|
|
9
|
+
getHeaders: () => headers,
|
|
10
10
|
getContent: () => content,
|
|
11
11
|
})
|
|
12
12
|
|
|
@@ -65,6 +65,24 @@ describe('FetchDriver', () => {
|
|
|
65
65
|
expect(config.body).toBeUndefined()
|
|
66
66
|
})
|
|
67
67
|
|
|
68
|
+
it('passes Blob bodies through to fetch unchanged', async () => {
|
|
69
|
+
const response = new Response('ok', { status: 200 })
|
|
70
|
+
;(global.fetch as unknown as ReturnType<typeof vi.fn>).mockResolvedValue(response)
|
|
71
|
+
|
|
72
|
+
const blob = new Blob(['chunk'], { type: 'application/octet-stream' })
|
|
73
|
+
const driver = new FetchDriver()
|
|
74
|
+
|
|
75
|
+
await driver.send(
|
|
76
|
+
'https://example.com',
|
|
77
|
+
RequestMethodEnum.PUT,
|
|
78
|
+
{},
|
|
79
|
+
createBody(blob, { 'Content-Type': 'application/octet-stream' })
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
const [, config] = (global.fetch as unknown as ReturnType<typeof vi.fn>).mock.calls[0]
|
|
83
|
+
expect(config.body).toBe(blob)
|
|
84
|
+
})
|
|
85
|
+
|
|
68
86
|
it('throws ResponseException when response is not ok', async () => {
|
|
69
87
|
const response = new Response('fail', { status: 500 })
|
|
70
88
|
;(global.fetch as unknown as ReturnType<typeof vi.fn>).mockResolvedValue(response)
|
|
@@ -3,10 +3,10 @@ import { XMLHttpRequestDriver } from '../../../../src/requests/drivers/xhr/XMLHt
|
|
|
3
3
|
import { XMLHttpRequestResponse } from '../../../../src/requests/drivers/xhr/XMLHttpRequestResponse'
|
|
4
4
|
import { RequestMethodEnum } from '../../../../src/requests/RequestMethod.enum'
|
|
5
5
|
import { ResponseException } from '../../../../src/requests/exceptions/ResponseException'
|
|
6
|
-
import type { BodyContract } from '../../../../src/requests/contracts/BodyContract'
|
|
6
|
+
import type { BodyContent, BodyContract } from '../../../../src/requests/contracts/BodyContract'
|
|
7
7
|
|
|
8
|
-
const createBody = (content: string): BodyContract => ({
|
|
9
|
-
getHeaders: () =>
|
|
8
|
+
const createBody = (content: BodyContent, headers: Record<string, string> = { 'Content-Type': 'application/json' }): BodyContract => ({
|
|
9
|
+
getHeaders: () => headers,
|
|
10
10
|
getContent: () => content,
|
|
11
11
|
})
|
|
12
12
|
|
|
@@ -148,6 +148,25 @@ describe('XMLHttpRequestDriver', () => {
|
|
|
148
148
|
expect(request.sentBody).toBeUndefined()
|
|
149
149
|
})
|
|
150
150
|
|
|
151
|
+
it('passes typed array bodies through to xhr unchanged', async () => {
|
|
152
|
+
const driver = new XMLHttpRequestDriver()
|
|
153
|
+
const chunk = new Uint8Array([1, 2, 3, 4])
|
|
154
|
+
|
|
155
|
+
const promise = driver.send(
|
|
156
|
+
'https://example.com',
|
|
157
|
+
RequestMethodEnum.PUT,
|
|
158
|
+
{},
|
|
159
|
+
createBody(chunk, { 'Content-Type': 'application/octet-stream' })
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
const request = MockXMLHttpRequest.instances[0]
|
|
163
|
+
request.triggerLoad()
|
|
164
|
+
|
|
165
|
+
await promise
|
|
166
|
+
|
|
167
|
+
expect(request.sentBody).toBe(chunk)
|
|
168
|
+
})
|
|
169
|
+
|
|
151
170
|
it('throws ResponseException when the response status is not ok', async () => {
|
|
152
171
|
const driver = new XMLHttpRequestDriver()
|
|
153
172
|
|