@utiliread/http 1.27.0 → 1.27.1

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.
@@ -1,318 +1,318 @@
1
- import { HttpError, headerNames, statusCodes } from "@utiliread/http";
2
- import { HttpResponse, HttpResponseOfT } from "./http-response";
3
-
4
- import { EventAggregator } from "./event-aggregator";
5
- import { Fetch } from "./http";
6
- import { Http } from "./http";
7
- import { TimeoutError } from "./errors/timeout-error";
8
-
9
- export class HttpBuilder<TResult = void> {
10
- private _ensureSuccessStatusCode = true;
11
- private _onSend = new EventAggregator<[Message]>();
12
- private _onSent = new EventAggregator<[HttpResponse, Message]>();
13
-
14
- constructor(
15
- public message: Message,
16
- public options: RequestOptions,
17
- /** @internal */ public http: Http
18
- ) {}
19
-
20
- onSend(callback: (request: Message) => void | Promise<void>) {
21
- this._onSend.subscribe(callback);
22
- return this;
23
- }
24
-
25
- onSent(
26
- callback: (response: HttpResponse, request: Message) => void | Promise<void>
27
- ) {
28
- this._onSent.subscribe(callback);
29
- return this;
30
- }
31
-
32
- useHandler<T>(handler: (response: HttpResponse) => Promise<T>) {
33
- return new HttpBuilderOfT<T>(this, handler);
34
- }
35
-
36
- async send(abortSignal?: AbortSignal) {
37
- if (this.message.contentType) {
38
- this.message.headers.set("Content-Type", this.message.contentType);
39
- }
40
-
41
- // Resolve the final url and assign it to the message
42
- // This makes the final url apper in onSend, onSent, and on Received handlers
43
- this.message.url = this.getUrl();
44
-
45
- await this._onSend.publish(this.message);
46
- await this.http._onSend.publish(this.message);
47
-
48
- const init: RequestInit = {
49
- method: this.message.method,
50
- body: this.message.content,
51
- headers: this.message.headers,
52
- mode: this.message.mode,
53
- };
54
-
55
- if (abortSignal || this.options.timeout) {
56
- var outerController = new AbortController();
57
- if (abortSignal) {
58
- abortSignal.addEventListener("abort", () => {
59
- outerController.abort();
60
- });
61
- }
62
-
63
- init.signal = outerController.signal;
64
- }
65
-
66
- const fetchResponsePromise = this.options.fetch(this.message.url, init);
67
- let fetchResponse: Response;
68
-
69
- if (this.options.timeout) {
70
- fetchResponse = await Promise.race([
71
- fetchResponsePromise,
72
- new Promise<Response>((_, reject) =>
73
- setTimeout(() => {
74
- outerController.abort();
75
- reject(new TimeoutError());
76
- }, this.options.timeout)
77
- ),
78
- ]);
79
- } else {
80
- fetchResponse = await fetchResponsePromise;
81
- }
82
-
83
- const httpResponse = new HttpResponse(fetchResponse);
84
-
85
- if (this._ensureSuccessStatusCode) {
86
- httpResponse.ensureSuccessfulStatusCode();
87
- }
88
-
89
- await this._onSent.publish(httpResponse, this.message);
90
- await this.http._onSent.publish(httpResponse, this.message);
91
-
92
- return httpResponse;
93
- }
94
-
95
- async transfer(abortSignal?: AbortSignal): Promise<TResult> {
96
- await this.ensureSuccessStatusCode(true).send(abortSignal);
97
- return undefined as any as TResult;
98
- }
99
-
100
- getUrl() {
101
- let baseUrl = this.options.baseUrl;
102
- if (!baseUrl) {
103
- return this.message.url;
104
- }
105
-
106
- if (baseUrl.endsWith("/")) {
107
- baseUrl = baseUrl.substring(0, baseUrl.length - 1);
108
- }
109
-
110
- if (this.message.url.startsWith("/")) {
111
- return baseUrl + this.message.url;
112
- } else if (
113
- this.message.url.length > 0 &&
114
- !this.message.url.startsWith("?")
115
- ) {
116
- return baseUrl + "/" + this.message.url;
117
- } else {
118
- return baseUrl + this.message.url;
119
- }
120
- }
121
-
122
- ensureSuccessStatusCode(ensureSuccessStatusCode?: boolean) {
123
- this._ensureSuccessStatusCode =
124
- ensureSuccessStatusCode === false ? false : true;
125
-
126
- return this;
127
- }
128
-
129
- useCors(mode: RequestMode) {
130
- this.message.mode = mode;
131
- return this;
132
- }
133
-
134
- useTimeout(timeout: number | null) {
135
- this.options.timeout = timeout || undefined;
136
- return this;
137
- }
138
-
139
- // Content Extensions
140
-
141
- with(content: any, contentType?: string) {
142
- this.message.content = content;
143
- this.message.contentType = contentType;
144
- return this;
145
- }
146
-
147
- withForm(content: FormData) {
148
- this.message.content = content;
149
- this.message.contentType = undefined;
150
- return this;
151
- }
152
-
153
- // Modifier Extensions
154
-
155
- addHeader(name: string, value: string) {
156
- this.message.headers.append(name, value);
157
- return this;
158
- }
159
-
160
- // Expect Extensions
161
-
162
- expectString() {
163
- return this.useHandler((response) => {
164
- return response.rawResponse.text();
165
- });
166
- }
167
-
168
- expectBinary() {
169
- return this.useHandler((response) => {
170
- return response.rawResponse.arrayBuffer();
171
- });
172
- }
173
-
174
- /** Expect the 201 created response
175
- * @returns a builder returning the value of the location header
176
- */
177
- expectCreated(): HttpBuilderOfT<string>;
178
- /** Expect the 201 created response
179
- * @returns a builder returning the parsed id from the location header
180
- */
181
- expectCreated<T>(idParser: (location: string) => T): HttpBuilderOfT<T>;
182
- expectCreated<T>(idParser?: (location: string) => T) {
183
- return this.useHandler((response) => {
184
- if (response.statusCode !== statusCodes.status201Created) {
185
- throw new HttpError(response.statusCode, response);
186
- }
187
- const location = response.headers.get(headerNames.location);
188
- if (!location) {
189
- throw new HttpError(response.statusCode, response);
190
- }
191
-
192
- const id = idParser ? idParser(location) : location;
193
- return Promise.resolve(id);
194
- });
195
- }
196
- }
197
-
198
- export class HttpBuilderOfT<T> extends HttpBuilder<T> {
199
- private _onReceived = new EventAggregator<[HttpResponseOfT<T>, Message, T]>();
200
-
201
- constructor(
202
- private inner: HttpBuilder,
203
- private handler: (response: HttpResponse) => Promise<T>
204
- ) {
205
- super(inner.message, inner.options, inner.http);
206
- }
207
-
208
- onSend(callback: (request: Message) => void | Promise<void>) {
209
- this.inner.onSend(callback);
210
- return this;
211
- }
212
-
213
- onSent(
214
- callback: (response: HttpResponse, request: Message) => void | Promise<void>
215
- ) {
216
- this.inner.onSent(callback);
217
- return this;
218
- }
219
-
220
- ensureSuccessStatusCode(ensureSuccessStatusCode?: boolean) {
221
- this.inner.ensureSuccessStatusCode(ensureSuccessStatusCode);
222
- return this;
223
- }
224
-
225
- useCors(mode: RequestMode) {
226
- this.inner.useCors(mode);
227
- return this;
228
- }
229
-
230
- useTimeout(timeout: number) {
231
- this.inner.useTimeout(timeout);
232
- return this;
233
- }
234
-
235
- allowEmptyResponse() {
236
- if (this._onReceived.any) {
237
- throw new Error(
238
- "onReceived() must be called after allowEmptyResponse() because the callback type changes"
239
- );
240
- }
241
-
242
- return new HttpBuilderOfT<T | null>(this.inner, (response) => {
243
- if (response.statusCode === 204) {
244
- return Promise.resolve(null);
245
- }
246
-
247
- return this.handler(response);
248
- });
249
- }
250
-
251
- onReceived(
252
- callback: (
253
- response: HttpResponseOfT<T>,
254
- request: Message,
255
- value: T
256
- ) => void | Promise<void>
257
- ) {
258
- this._onReceived.subscribe(callback);
259
- return this;
260
- }
261
-
262
- send(abortSignal?: AbortSignal) {
263
- const responsePromise = this.inner
264
- .send(abortSignal)
265
- .then((x) => new HttpResponseOfT<T>(x.rawResponse, this.handler));
266
-
267
- return asSendPromise(responsePromise, () =>
268
- responsePromise.then((response) => this.handleReceive(response))
269
- );
270
- }
271
-
272
- transfer(abortSignal?: AbortSignal): Promise<T> {
273
- return this.send(abortSignal).then((response) =>
274
- this.handleReceive(response)
275
- );
276
- }
277
-
278
- private async handleReceive(response: HttpResponseOfT<T>) {
279
- const request = this.message;
280
- const value = await response.receive();
281
-
282
- await this._onReceived.publish(response, request, value);
283
- await this.http._onReceived.publish(response, request, value);
284
-
285
- return value;
286
- }
287
- }
288
-
289
- export type HttpMethod = "HEAD" | "POST" | "GET" | "PUT" | "PATCH" | "DELETE";
290
-
291
- export interface Message {
292
- method: HttpMethod;
293
- url: string;
294
- headers: Headers;
295
- content?: any;
296
- contentType?: string;
297
- mode?: RequestMode;
298
- properties: { [key: string]: any };
299
- }
300
-
301
- export interface RequestOptions {
302
- fetch: Fetch;
303
- timeout?: number;
304
- baseUrl?: string;
305
- }
306
-
307
- export interface SendPromise<T> extends Promise<HttpResponseOfT<T>> {
308
- thenReceive(): Promise<T>;
309
- }
310
-
311
- function asSendPromise<T>(
312
- responsePromise: Promise<HttpResponse>,
313
- thenReceive: () => Promise<T>
314
- ): SendPromise<T> {
315
- const sendPromise = responsePromise as SendPromise<T>;
316
- sendPromise.thenReceive = thenReceive;
317
- return sendPromise;
318
- }
1
+ import { HttpError, headerNames, statusCodes } from "@utiliread/http";
2
+ import { HttpResponse, HttpResponseOfT } from "./http-response";
3
+
4
+ import { EventAggregator } from "./event-aggregator";
5
+ import { Fetch } from "./http";
6
+ import { Http } from "./http";
7
+ import { TimeoutError } from "./errors/timeout-error";
8
+
9
+ export class HttpBuilder<TResult = void> {
10
+ private _ensureSuccessStatusCode = true;
11
+ private _onSend = new EventAggregator<[Message]>();
12
+ private _onSent = new EventAggregator<[HttpResponse, Message]>();
13
+
14
+ constructor(
15
+ public message: Message,
16
+ public options: RequestOptions,
17
+ /** @internal */ public http: Http
18
+ ) {}
19
+
20
+ onSend(callback: (request: Message) => void | Promise<void>) {
21
+ this._onSend.subscribe(callback);
22
+ return this;
23
+ }
24
+
25
+ onSent(
26
+ callback: (response: HttpResponse, request: Message) => void | Promise<void>
27
+ ) {
28
+ this._onSent.subscribe(callback);
29
+ return this;
30
+ }
31
+
32
+ useHandler<T>(handler: (response: HttpResponse) => Promise<T>) {
33
+ return new HttpBuilderOfT<T>(this, handler);
34
+ }
35
+
36
+ async send(abortSignal?: AbortSignal) {
37
+ if (this.message.contentType) {
38
+ this.message.headers.set("Content-Type", this.message.contentType);
39
+ }
40
+
41
+ // Resolve the final url and assign it to the message
42
+ // This makes the final url apper in onSend, onSent, and on Received handlers
43
+ this.message.url = this.getUrl();
44
+
45
+ await this._onSend.publish(this.message);
46
+ await this.http._onSend.publish(this.message);
47
+
48
+ const init: RequestInit = {
49
+ method: this.message.method,
50
+ body: this.message.content,
51
+ headers: this.message.headers,
52
+ mode: this.message.mode,
53
+ };
54
+
55
+ if (abortSignal || this.options.timeout) {
56
+ var outerController = new AbortController();
57
+ if (abortSignal) {
58
+ abortSignal.addEventListener("abort", () => {
59
+ outerController.abort();
60
+ });
61
+ }
62
+
63
+ init.signal = outerController.signal;
64
+ }
65
+
66
+ const fetchResponsePromise = this.options.fetch(this.message.url, init);
67
+ let fetchResponse: Response;
68
+
69
+ if (this.options.timeout) {
70
+ fetchResponse = await Promise.race([
71
+ fetchResponsePromise,
72
+ new Promise<Response>((_, reject) =>
73
+ setTimeout(() => {
74
+ outerController.abort();
75
+ reject(new TimeoutError());
76
+ }, this.options.timeout)
77
+ ),
78
+ ]);
79
+ } else {
80
+ fetchResponse = await fetchResponsePromise;
81
+ }
82
+
83
+ const httpResponse = new HttpResponse(fetchResponse);
84
+
85
+ if (this._ensureSuccessStatusCode) {
86
+ httpResponse.ensureSuccessfulStatusCode();
87
+ }
88
+
89
+ await this._onSent.publish(httpResponse, this.message);
90
+ await this.http._onSent.publish(httpResponse, this.message);
91
+
92
+ return httpResponse;
93
+ }
94
+
95
+ async transfer(abortSignal?: AbortSignal): Promise<TResult> {
96
+ await this.ensureSuccessStatusCode(true).send(abortSignal);
97
+ return undefined as any as TResult;
98
+ }
99
+
100
+ getUrl() {
101
+ let baseUrl = this.options.baseUrl;
102
+ if (!baseUrl) {
103
+ return this.message.url;
104
+ }
105
+
106
+ if (baseUrl.endsWith("/")) {
107
+ baseUrl = baseUrl.substring(0, baseUrl.length - 1);
108
+ }
109
+
110
+ if (this.message.url.startsWith("/")) {
111
+ return baseUrl + this.message.url;
112
+ } else if (
113
+ this.message.url.length > 0 &&
114
+ !this.message.url.startsWith("?")
115
+ ) {
116
+ return baseUrl + "/" + this.message.url;
117
+ } else {
118
+ return baseUrl + this.message.url;
119
+ }
120
+ }
121
+
122
+ ensureSuccessStatusCode(ensureSuccessStatusCode?: boolean) {
123
+ this._ensureSuccessStatusCode =
124
+ ensureSuccessStatusCode === false ? false : true;
125
+
126
+ return this;
127
+ }
128
+
129
+ useCors(mode: RequestMode) {
130
+ this.message.mode = mode;
131
+ return this;
132
+ }
133
+
134
+ useTimeout(timeout: number | null) {
135
+ this.options.timeout = timeout || undefined;
136
+ return this;
137
+ }
138
+
139
+ // Content Extensions
140
+
141
+ with(content: any, contentType?: string) {
142
+ this.message.content = content;
143
+ this.message.contentType = contentType;
144
+ return this;
145
+ }
146
+
147
+ withForm(content: FormData) {
148
+ this.message.content = content;
149
+ this.message.contentType = undefined;
150
+ return this;
151
+ }
152
+
153
+ // Modifier Extensions
154
+
155
+ addHeader(name: string, value: string) {
156
+ this.message.headers.append(name, value);
157
+ return this;
158
+ }
159
+
160
+ // Expect Extensions
161
+
162
+ expectString() {
163
+ return this.useHandler((response) => {
164
+ return response.rawResponse.text();
165
+ });
166
+ }
167
+
168
+ expectBinary() {
169
+ return this.useHandler((response) => {
170
+ return response.rawResponse.arrayBuffer();
171
+ });
172
+ }
173
+
174
+ /** Expect the 201 created response
175
+ * @returns a builder returning the value of the location header
176
+ */
177
+ expectCreated(): HttpBuilderOfT<string>;
178
+ /** Expect the 201 created response
179
+ * @returns a builder returning the parsed id from the location header
180
+ */
181
+ expectCreated<T>(idParser: (location: string) => T): HttpBuilderOfT<T>;
182
+ expectCreated<T>(idParser?: (location: string) => T) {
183
+ return this.useHandler((response) => {
184
+ if (response.statusCode !== statusCodes.status201Created) {
185
+ throw new HttpError(response.statusCode, response);
186
+ }
187
+ const location = response.headers.get(headerNames.location);
188
+ if (!location) {
189
+ throw new HttpError(response.statusCode, response);
190
+ }
191
+
192
+ const id = idParser ? idParser(location) : location;
193
+ return Promise.resolve(id);
194
+ });
195
+ }
196
+ }
197
+
198
+ export class HttpBuilderOfT<T> extends HttpBuilder<T> {
199
+ private _onReceived = new EventAggregator<[HttpResponseOfT<T>, Message, T]>();
200
+
201
+ constructor(
202
+ private inner: HttpBuilder,
203
+ private handler: (response: HttpResponse) => Promise<T>
204
+ ) {
205
+ super(inner.message, inner.options, inner.http);
206
+ }
207
+
208
+ onSend(callback: (request: Message) => void | Promise<void>) {
209
+ this.inner.onSend(callback);
210
+ return this;
211
+ }
212
+
213
+ onSent(
214
+ callback: (response: HttpResponse, request: Message) => void | Promise<void>
215
+ ) {
216
+ this.inner.onSent(callback);
217
+ return this;
218
+ }
219
+
220
+ ensureSuccessStatusCode(ensureSuccessStatusCode?: boolean) {
221
+ this.inner.ensureSuccessStatusCode(ensureSuccessStatusCode);
222
+ return this;
223
+ }
224
+
225
+ useCors(mode: RequestMode) {
226
+ this.inner.useCors(mode);
227
+ return this;
228
+ }
229
+
230
+ useTimeout(timeout: number) {
231
+ this.inner.useTimeout(timeout);
232
+ return this;
233
+ }
234
+
235
+ allowEmptyResponse() {
236
+ if (this._onReceived.any) {
237
+ throw new Error(
238
+ "onReceived() must be called after allowEmptyResponse() because the callback type changes"
239
+ );
240
+ }
241
+
242
+ return new HttpBuilderOfT<T | null>(this.inner, (response) => {
243
+ if (response.statusCode === 204) {
244
+ return Promise.resolve(null);
245
+ }
246
+
247
+ return this.handler(response);
248
+ });
249
+ }
250
+
251
+ onReceived(
252
+ callback: (
253
+ response: HttpResponseOfT<T>,
254
+ request: Message,
255
+ value: T
256
+ ) => void | Promise<void>
257
+ ) {
258
+ this._onReceived.subscribe(callback);
259
+ return this;
260
+ }
261
+
262
+ send(abortSignal?: AbortSignal) {
263
+ const responsePromise = this.inner
264
+ .send(abortSignal)
265
+ .then((x) => new HttpResponseOfT<T>(x.rawResponse, this.handler));
266
+
267
+ return asSendPromise(responsePromise, () =>
268
+ responsePromise.then((response) => this.handleReceive(response))
269
+ );
270
+ }
271
+
272
+ transfer(abortSignal?: AbortSignal): Promise<T> {
273
+ return this.send(abortSignal).then((response) =>
274
+ this.handleReceive(response)
275
+ );
276
+ }
277
+
278
+ private async handleReceive(response: HttpResponseOfT<T>) {
279
+ const request = this.message;
280
+ const value = await response.receive();
281
+
282
+ await this._onReceived.publish(response, request, value);
283
+ await this.http._onReceived.publish(response, request, value);
284
+
285
+ return value;
286
+ }
287
+ }
288
+
289
+ export type HttpMethod = "HEAD" | "POST" | "GET" | "PUT" | "PATCH" | "DELETE";
290
+
291
+ export interface Message {
292
+ method: HttpMethod;
293
+ url: string;
294
+ headers: Headers;
295
+ content?: any;
296
+ contentType?: string;
297
+ mode?: RequestMode;
298
+ properties: { [key: string]: any };
299
+ }
300
+
301
+ export interface RequestOptions {
302
+ fetch: Fetch;
303
+ timeout?: number;
304
+ baseUrl?: string;
305
+ }
306
+
307
+ export interface SendPromise<T> extends Promise<HttpResponseOfT<T>> {
308
+ thenReceive(): Promise<T>;
309
+ }
310
+
311
+ function asSendPromise<T>(
312
+ responsePromise: Promise<HttpResponse>,
313
+ thenReceive: () => Promise<T>
314
+ ): SendPromise<T> {
315
+ const sendPromise = responsePromise as SendPromise<T>;
316
+ sendPromise.thenReceive = thenReceive;
317
+ return sendPromise;
318
+ }