@danceroutine/tango-core 0.1.0 → 1.0.0
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/LICENSE +21 -0
- package/README.md +82 -0
- package/dist/{TangoError-NPkVPfuH.js → TangoError-DdQVQNZU.js} +5 -1
- package/dist/TangoError-DdQVQNZU.js.map +1 -0
- package/dist/errors/AuthenticationError.d.ts +6 -1
- package/dist/errors/ConflictError.d.ts +6 -1
- package/dist/errors/NotFoundError.d.ts +6 -1
- package/dist/errors/PermissionDenied.d.ts +6 -1
- package/dist/errors/TangoError.d.ts +13 -1
- package/dist/errors/ValidationError.d.ts +5 -1
- package/dist/errors/factories/HttpErrorFactory.d.ts +20 -11
- package/dist/errors/factories/index.d.ts +1 -1
- package/dist/errors/index.d.ts +2 -2
- package/dist/errors/index.js +4 -4
- package/dist/{errors-CzSQXdgI.js → errors-_tWsmNyZ.js} +81 -34
- package/dist/errors-_tWsmNyZ.js.map +1 -0
- package/dist/http/TangoBody.d.ts +43 -18
- package/dist/http/TangoHeaders.d.ts +10 -5
- package/dist/http/TangoQueryParams.d.ts +69 -0
- package/dist/http/TangoRequest.d.ts +82 -0
- package/dist/http/TangoResponse.d.ts +184 -49
- package/dist/http/index.d.ts +2 -1
- package/dist/http/index.js +4 -4
- package/dist/{http-DOLwwAYt.js → http-D20MQa6p.js} +675 -295
- package/dist/http-D20MQa6p.js.map +1 -0
- package/dist/index.d.ts +9 -7
- package/dist/index.js +7 -6
- package/dist/logging/ConsoleLogger.d.ts +15 -0
- package/dist/logging/Logger.d.ts +13 -0
- package/dist/logging/getLogger.d.ts +23 -0
- package/dist/logging/index.d.ts +3 -0
- package/dist/logging/index.js +3 -0
- package/dist/logging-BWeD4HOO.js +48 -0
- package/dist/logging-BWeD4HOO.js.map +1 -0
- package/dist/runtime/binary/isArrayBuffer.d.ts +3 -0
- package/dist/runtime/binary/isBlob.d.ts +3 -0
- package/dist/runtime/binary/isUint8Array.d.ts +3 -0
- package/dist/runtime/date/isDate.d.ts +3 -0
- package/dist/runtime/error/isError.d.ts +3 -0
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +2 -2
- package/dist/runtime/object/index.d.ts +1 -0
- package/dist/runtime/object/isNil.d.ts +4 -0
- package/dist/runtime/object/isObject.d.ts +3 -0
- package/dist/runtime/web/isFile.d.ts +3 -0
- package/dist/runtime/web/isFormData.d.ts +3 -0
- package/dist/runtime/web/isReadableStream.d.ts +3 -0
- package/dist/runtime/web/isURLSearchParams.d.ts +3 -0
- package/dist/{runtime-DPpCYEe_.js → runtime-B8KkgD3R.js} +14 -4
- package/dist/runtime-B8KkgD3R.js.map +1 -0
- package/dist/sql/SqlDialect.d.ts +5 -0
- package/dist/sql/SqlIdentifierRole.d.ts +13 -0
- package/dist/sql/SqlSafetyEngine.d.ts +50 -0
- package/dist/sql/TrustedSqlFragment.d.ts +5 -0
- package/dist/sql/ValidatedSqlIdentifier.d.ts +7 -0
- package/dist/sql/index.d.ts +13 -0
- package/dist/sql/index.js +3 -0
- package/dist/sql/isTrustedSqlFragment.d.ts +5 -0
- package/dist/sql/quoteSqlIdentifier.d.ts +6 -0
- package/dist/sql/trustedSql.d.ts +5 -0
- package/dist/sql/validateSqlIdentifier.d.ts +6 -0
- package/dist/sql-D3frkfy-.js +116 -0
- package/dist/sql-D3frkfy-.js.map +1 -0
- package/package.json +65 -54
- package/dist/TangoError-NPkVPfuH.js.map +0 -1
- package/dist/errors/factories/HttpErrorFactory.js +0 -91
- package/dist/errors-CzSQXdgI.js.map +0 -1
- package/dist/http/TangoHeaders.js +0 -396
- package/dist/http/TangoResponse.js +0 -556
- package/dist/http-DOLwwAYt.js.map +0 -1
- package/dist/result/Err.d.ts +0 -21
- package/dist/result/Ok.d.ts +0 -21
- package/dist/result/Result.d.ts +0 -33
- package/dist/result/index.d.ts +0 -8
- package/dist/result/index.js +0 -3
- package/dist/result-CBqw9Hlg.js +0 -82
- package/dist/result-CBqw9Hlg.js.map +0 -1
- package/dist/runtime-DPpCYEe_.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { __export } from "./chunk-BkvOhyD0.js";
|
|
2
|
-
import { TangoError } from "./TangoError-
|
|
3
|
-
import { isArrayBuffer, isBlob, isFile, isFormData, isReadableStream, isURLSearchParams, isUint8Array } from "./runtime-
|
|
2
|
+
import { TangoError } from "./TangoError-DdQVQNZU.js";
|
|
3
|
+
import { isArrayBuffer, isBlob, isFile, isFormData, isNil, isReadableStream, isURLSearchParams, isUint8Array } from "./runtime-B8KkgD3R.js";
|
|
4
4
|
|
|
5
5
|
//#region src/http/TangoBody.ts
|
|
6
6
|
var TangoBody = class TangoBody {
|
|
@@ -14,18 +14,30 @@ var TangoBody = class TangoBody {
|
|
|
14
14
|
this.bodyUsedInternal = false;
|
|
15
15
|
this.headers = headers;
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Narrow an unknown value to `TangoBody`.
|
|
19
|
+
*/
|
|
17
20
|
static isTangoBody(value) {
|
|
18
21
|
return typeof value === "object" && value !== null && value.__tangoBrand === TangoBody.BRAND;
|
|
19
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Expose the original body source for cloning and adapter integration.
|
|
25
|
+
*/
|
|
20
26
|
get bodySource() {
|
|
21
27
|
return this.bodySourceInternal;
|
|
22
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Report whether a reader method has already consumed this body.
|
|
31
|
+
*/
|
|
23
32
|
get bodyUsed() {
|
|
24
33
|
return this.bodyUsedInternal;
|
|
25
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Describe the current body shape in a way that is useful for diagnostics.
|
|
37
|
+
*/
|
|
26
38
|
get bodyType() {
|
|
27
39
|
const body = this.bodySourceInternal;
|
|
28
|
-
if (body
|
|
40
|
+
if (isNil(body)) return "null";
|
|
29
41
|
if (typeof body === "string") return "string";
|
|
30
42
|
if (isArrayBuffer(body)) return "ArrayBuffer";
|
|
31
43
|
if (isUint8Array(body)) return "Uint8Array";
|
|
@@ -46,6 +58,97 @@ var TangoBody = class TangoBody {
|
|
|
46
58
|
return false;
|
|
47
59
|
}
|
|
48
60
|
/**
|
|
61
|
+
* Deep clone utility for body values. Preserves types and values as in the legacy implementation.
|
|
62
|
+
* If the source is a stream, the stream will be cloned if readable, otherwise throws.
|
|
63
|
+
*/
|
|
64
|
+
static deepCloneBody(body) {
|
|
65
|
+
if (isNil(body)) return null;
|
|
66
|
+
if (typeof body === "string") return body;
|
|
67
|
+
if (isArrayBuffer(body)) return body.slice(0);
|
|
68
|
+
if (isUint8Array(body)) return new Uint8Array(body);
|
|
69
|
+
if (isBlob(body)) return body.slice(0, body.size, body.type);
|
|
70
|
+
if (isFormData(body)) {
|
|
71
|
+
const cloned = new FormData();
|
|
72
|
+
for (const [k, v] of body) if (isFile(v)) {
|
|
73
|
+
const file = new File([v], v.name, {
|
|
74
|
+
type: v.type,
|
|
75
|
+
lastModified: v.lastModified
|
|
76
|
+
});
|
|
77
|
+
cloned.append(k, file);
|
|
78
|
+
} else cloned.append(k, v);
|
|
79
|
+
return cloned;
|
|
80
|
+
}
|
|
81
|
+
if (isReadableStream(body)) throw new TypeError("Cannot deep clone a ReadableStream directly; use TangoBody.clone()");
|
|
82
|
+
if (TangoBody.isJsonValue(body)) return JSON.parse(JSON.stringify(body));
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Read a `ReadableStream` into an `ArrayBuffer`.
|
|
87
|
+
*/
|
|
88
|
+
static async readStreamToArrayBuffer(stream) {
|
|
89
|
+
const chunks = [];
|
|
90
|
+
const reader = stream.getReader();
|
|
91
|
+
let total = 0;
|
|
92
|
+
while (true) {
|
|
93
|
+
const { done, value } = await reader.read();
|
|
94
|
+
if (done) break;
|
|
95
|
+
chunks.push(value);
|
|
96
|
+
total += value.length;
|
|
97
|
+
}
|
|
98
|
+
const joined = new Uint8Array(total);
|
|
99
|
+
let off = 0;
|
|
100
|
+
for (const chunk of chunks) {
|
|
101
|
+
joined.set(chunk, off);
|
|
102
|
+
off += chunk.length;
|
|
103
|
+
}
|
|
104
|
+
return joined.buffer.slice(0, joined.byteLength);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Read a `ReadableStream` into a `Uint8Array`.
|
|
108
|
+
*/
|
|
109
|
+
static async readStreamToUint8Array(stream) {
|
|
110
|
+
const buf = await TangoBody.readStreamToArrayBuffer(stream);
|
|
111
|
+
return new Uint8Array(buf);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Read a `ReadableStream` into UTF-8 text.
|
|
115
|
+
*/
|
|
116
|
+
static async readStreamToText(stream) {
|
|
117
|
+
const arr = await TangoBody.readStreamToUint8Array(stream);
|
|
118
|
+
return new TextDecoder().decode(arr);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Determine the content type for this body, if possible.
|
|
122
|
+
* Respects an explicitly passed header, otherwise infers from value type.
|
|
123
|
+
*/
|
|
124
|
+
static detectContentType(body, providedType) {
|
|
125
|
+
if (providedType) return providedType;
|
|
126
|
+
if (isNil(body)) return undefined;
|
|
127
|
+
if (typeof body === "string") return "text/plain; charset=utf-8";
|
|
128
|
+
if (isArrayBuffer(body) || isUint8Array(body)) return "application/octet-stream";
|
|
129
|
+
if (isBlob(body)) {
|
|
130
|
+
if (body.type) return body.type;
|
|
131
|
+
return "application/octet-stream";
|
|
132
|
+
}
|
|
133
|
+
if (isFormData(body)) return undefined;
|
|
134
|
+
if (typeof body === "object" && TangoBody.isJsonValue(body)) return "application/json; charset=utf-8";
|
|
135
|
+
if (isReadableStream(body)) return undefined;
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Attempt to determine the content length, in bytes, for this body.
|
|
140
|
+
* Only available for certain body types, otherwise returns undefined.
|
|
141
|
+
*/
|
|
142
|
+
static async getContentLength(body) {
|
|
143
|
+
if (isNil(body)) return 0;
|
|
144
|
+
if (typeof body === "string") return new TextEncoder().encode(body).length;
|
|
145
|
+
if (isUint8Array(body)) return body.byteLength;
|
|
146
|
+
if (isArrayBuffer(body)) return body.byteLength;
|
|
147
|
+
if (isBlob(body)) return body.size;
|
|
148
|
+
if (TangoBody.isJsonValue(body)) return new TextEncoder().encode(JSON.stringify(body)).length;
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
49
152
|
* Reads the body as an ArrayBuffer.
|
|
50
153
|
*/
|
|
51
154
|
async arrayBuffer() {
|
|
@@ -126,13 +229,12 @@ else throw new TypeError("Body is not valid for FormData");
|
|
|
126
229
|
for (const part of parts) {
|
|
127
230
|
const trimmed = part.trim();
|
|
128
231
|
if (!trimmed || trimmed === "--" || trimmed === "") continue;
|
|
129
|
-
const [rawHeaders, ...rawBodyParts] = trimmed.split(/\r?\n\r?\n/);
|
|
232
|
+
const [rawHeaders = "", ...rawBodyParts] = trimmed.split(/\r?\n\r?\n/);
|
|
130
233
|
const body = rawBodyParts.join("\n\n").replace(/\r?\n$/, "");
|
|
131
|
-
if (!rawHeaders) continue;
|
|
132
234
|
const dispositionMatch = /Content-Disposition:\s*form-data;\s*name="([^"]+)"/i.exec(rawHeaders);
|
|
133
235
|
if (!dispositionMatch) continue;
|
|
134
236
|
const name = dispositionMatch[1];
|
|
135
|
-
|
|
237
|
+
form.append(name, body);
|
|
136
238
|
}
|
|
137
239
|
return form;
|
|
138
240
|
}
|
|
@@ -178,31 +280,6 @@ else throw new TypeError("Body is not valid for FormData");
|
|
|
178
280
|
return this.bodySourceInternal;
|
|
179
281
|
}
|
|
180
282
|
/**
|
|
181
|
-
* Deep clone utility for body values. Preserves types and values as in the legacy implementation.
|
|
182
|
-
* If the source is a stream, the stream will be cloned if readable, otherwise throws.
|
|
183
|
-
*/
|
|
184
|
-
static deepCloneBody(body) {
|
|
185
|
-
if (body == null || typeof body === "undefined") return null;
|
|
186
|
-
if (typeof body === "string") return body;
|
|
187
|
-
if (isArrayBuffer(body)) return body.slice(0);
|
|
188
|
-
if (isUint8Array(body)) return new Uint8Array(body);
|
|
189
|
-
if (isBlob(body)) return body.slice(0, body.size, body.type);
|
|
190
|
-
if (isFormData(body)) {
|
|
191
|
-
const cloned = new FormData();
|
|
192
|
-
for (const [k, v] of body) if (isFile(v)) {
|
|
193
|
-
const file = new File([v], v.name, {
|
|
194
|
-
type: v.type,
|
|
195
|
-
lastModified: v.lastModified
|
|
196
|
-
});
|
|
197
|
-
cloned.append(k, file);
|
|
198
|
-
} else cloned.append(k, v);
|
|
199
|
-
return cloned;
|
|
200
|
-
}
|
|
201
|
-
if (isReadableStream(body)) throw new TypeError("Cannot deep clone a ReadableStream directly; use TangoBody.clone()");
|
|
202
|
-
if (TangoBody.isJsonValue(body)) return JSON.parse(JSON.stringify(body));
|
|
203
|
-
return null;
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
283
|
* Clone the body instance and stream if possible.
|
|
207
284
|
* If the source is a stream, the stream will be cloned if readable, otherwise throws.
|
|
208
285
|
*/
|
|
@@ -221,69 +298,10 @@ else throw new TypeError("Body is not valid for FormData");
|
|
|
221
298
|
* Helper for all readers. Only allows reading once.
|
|
222
299
|
*/
|
|
223
300
|
async consumeBody(parser) {
|
|
224
|
-
if (this.bodyUsedInternal)
|
|
301
|
+
if (this.bodyUsedInternal) throw new TypeError("Body has already been consumed.");
|
|
225
302
|
this.bodyUsedInternal = true;
|
|
226
303
|
return parser(this.bodySourceInternal);
|
|
227
304
|
}
|
|
228
|
-
static async readStreamToArrayBuffer(stream) {
|
|
229
|
-
const chunks = [];
|
|
230
|
-
const reader = stream.getReader();
|
|
231
|
-
let total = 0;
|
|
232
|
-
while (true) {
|
|
233
|
-
const { done, value } = await reader.read();
|
|
234
|
-
if (done) break;
|
|
235
|
-
if (value) {
|
|
236
|
-
chunks.push(value);
|
|
237
|
-
total += value.length;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
const joined = new Uint8Array(total);
|
|
241
|
-
let off = 0;
|
|
242
|
-
for (const chunk of chunks) {
|
|
243
|
-
joined.set(chunk, off);
|
|
244
|
-
off += chunk.length;
|
|
245
|
-
}
|
|
246
|
-
return joined.buffer.slice(0, joined.byteLength);
|
|
247
|
-
}
|
|
248
|
-
static async readStreamToUint8Array(stream) {
|
|
249
|
-
const buf = await TangoBody.readStreamToArrayBuffer(stream);
|
|
250
|
-
return new Uint8Array(buf);
|
|
251
|
-
}
|
|
252
|
-
static async readStreamToText(stream) {
|
|
253
|
-
const arr = await TangoBody.readStreamToUint8Array(stream);
|
|
254
|
-
return new TextDecoder().decode(arr);
|
|
255
|
-
}
|
|
256
|
-
/**
|
|
257
|
-
* Determine the content type for this body, if possible.
|
|
258
|
-
* Respects an explicitly passed header, otherwise infers from value type.
|
|
259
|
-
*/
|
|
260
|
-
static detectContentType(body, providedType) {
|
|
261
|
-
if (providedType) return providedType;
|
|
262
|
-
if (body == null) return undefined;
|
|
263
|
-
if (typeof body === "string") return "text/plain; charset=utf-8";
|
|
264
|
-
if (isArrayBuffer(body) || isUint8Array(body)) return "application/octet-stream";
|
|
265
|
-
if (isBlob(body)) {
|
|
266
|
-
if (body.type) return body.type;
|
|
267
|
-
return "application/octet-stream";
|
|
268
|
-
}
|
|
269
|
-
if (isFormData(body)) return undefined;
|
|
270
|
-
if (typeof body === "object" && TangoBody.isJsonValue(body)) return "application/json; charset=utf-8";
|
|
271
|
-
if (isReadableStream(body)) return undefined;
|
|
272
|
-
return undefined;
|
|
273
|
-
}
|
|
274
|
-
/**
|
|
275
|
-
* Attempt to determine the content length, in bytes, for this body.
|
|
276
|
-
* Only available for certain body types, otherwise returns undefined.
|
|
277
|
-
*/
|
|
278
|
-
static async getContentLength(body) {
|
|
279
|
-
if (body == null) return 0;
|
|
280
|
-
if (typeof body === "string") return new TextEncoder().encode(body).length;
|
|
281
|
-
if (isUint8Array(body)) return body.byteLength;
|
|
282
|
-
if (isArrayBuffer(body)) return body.byteLength;
|
|
283
|
-
if (isBlob(body)) return body.size;
|
|
284
|
-
if (TangoBody.isJsonValue(body)) return new TextEncoder().encode(JSON.stringify(body)).length;
|
|
285
|
-
return undefined;
|
|
286
|
-
}
|
|
287
305
|
};
|
|
288
306
|
|
|
289
307
|
//#endregion
|
|
@@ -291,13 +309,40 @@ else throw new TypeError("Body is not valid for FormData");
|
|
|
291
309
|
var TangoHeaders = class TangoHeaders extends Headers {
|
|
292
310
|
static BRAND = "tango.http.headers";
|
|
293
311
|
__tangoBrand = TangoHeaders.BRAND;
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
312
|
+
/**
|
|
313
|
+
* Narrow an unknown value to `TangoHeaders`.
|
|
314
|
+
*/
|
|
297
315
|
static isTangoHeaders(value) {
|
|
298
316
|
return typeof value === "object" && value !== null && value.__tangoBrand === TangoHeaders.BRAND;
|
|
299
317
|
}
|
|
300
318
|
/**
|
|
319
|
+
* Serialize a cookie for the Set-Cookie header line.
|
|
320
|
+
*/
|
|
321
|
+
static serializeCookie(name, value, options = {}) {
|
|
322
|
+
let cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value ?? "");
|
|
323
|
+
if (options.domain) cookie += `; Domain=${options.domain}`;
|
|
324
|
+
if (options.path) cookie += `; Path=${options.path}`;
|
|
325
|
+
else cookie += "; Path=/";
|
|
326
|
+
if (options.expires) cookie += `; Expires=${options.expires.toUTCString()}`;
|
|
327
|
+
if (typeof options.maxAge === "number") cookie += `; Max-Age=${options.maxAge}`;
|
|
328
|
+
if (options.secure) cookie += "; Secure";
|
|
329
|
+
if (options.httpOnly) cookie += "; HttpOnly";
|
|
330
|
+
if (options.sameSite) cookie += `; SameSite=${options.sameSite}`;
|
|
331
|
+
if (options.priority) cookie += `; Priority=${options.priority}`;
|
|
332
|
+
if (options.partitioned) cookie += "; Partitioned";
|
|
333
|
+
return cookie;
|
|
334
|
+
}
|
|
335
|
+
static hasNumberSize(value) {
|
|
336
|
+
return typeof value === "object" && value !== null && typeof value.size === "number";
|
|
337
|
+
}
|
|
338
|
+
static hasNumberLength(value) {
|
|
339
|
+
return typeof value === "object" && value !== null && typeof value.length === "number";
|
|
340
|
+
}
|
|
341
|
+
static isNodeBuffer(value) {
|
|
342
|
+
const maybeBuffer = Buffer;
|
|
343
|
+
return typeof Buffer !== "undefined" && typeof maybeBuffer.isBuffer === "function" && maybeBuffer.isBuffer(value);
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
301
346
|
* Sets the Content-Disposition header with type "inline" and the specified filename.
|
|
302
347
|
* This is useful to indicate that the content should be displayed inline in the browser,
|
|
303
348
|
* but with a suggested filename for saving.
|
|
@@ -318,19 +363,22 @@ var TangoHeaders = class TangoHeaders extends Headers {
|
|
|
318
363
|
const encoded = encodeURIComponent(filename);
|
|
319
364
|
this.set("Content-Disposition", `attachment; filename="${encoded}"; filename*=UTF-8''${encoded}`);
|
|
320
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* Create a copy that preserves all header names and values.
|
|
368
|
+
*/
|
|
321
369
|
clone() {
|
|
322
370
|
const copy = new TangoHeaders();
|
|
323
371
|
for (const [name, value] of this.entries()) copy.append(name, value);
|
|
324
372
|
return copy;
|
|
325
373
|
}
|
|
326
374
|
/**
|
|
327
|
-
* Set a header, replacing
|
|
375
|
+
* Set a header, replacing the existing value.
|
|
328
376
|
*/
|
|
329
377
|
setHeader(name, value) {
|
|
330
378
|
this.set(name, value);
|
|
331
379
|
}
|
|
332
380
|
/**
|
|
333
|
-
* Append a header, adding to
|
|
381
|
+
* Append a header, adding to existing value(s) for the name.
|
|
334
382
|
*/
|
|
335
383
|
appendHeader(name, value) {
|
|
336
384
|
this.append(name, value);
|
|
@@ -400,23 +448,6 @@ var TangoHeaders = class TangoHeaders extends Headers {
|
|
|
400
448
|
});
|
|
401
449
|
}
|
|
402
450
|
/**
|
|
403
|
-
* Serialize a cookie for the Set-Cookie header line.
|
|
404
|
-
*/
|
|
405
|
-
static serializeCookie(name, value, options = {}) {
|
|
406
|
-
let cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value ?? "");
|
|
407
|
-
if (options.domain) cookie += `; Domain=${options.domain}`;
|
|
408
|
-
if (options.path) cookie += `; Path=${options.path}`;
|
|
409
|
-
else cookie += "; Path=/";
|
|
410
|
-
if (options.expires) cookie += `; Expires=${options.expires.toUTCString()}`;
|
|
411
|
-
if (typeof options.maxAge === "number") cookie += `; Max-Age=${options.maxAge}`;
|
|
412
|
-
if (options.secure) cookie += "; Secure";
|
|
413
|
-
if (options.httpOnly) cookie += "; HttpOnly";
|
|
414
|
-
if (options.sameSite) cookie += `; SameSite=${options.sameSite}`;
|
|
415
|
-
if (options.priority) cookie += `; Priority=${options.priority}`;
|
|
416
|
-
if (options.partitioned) cookie += "; Partitioned";
|
|
417
|
-
return cookie;
|
|
418
|
-
}
|
|
419
|
-
/**
|
|
420
451
|
* Add or override Cache-Control header with helpers for common policies.
|
|
421
452
|
*/
|
|
422
453
|
cacheControl(control) {
|
|
@@ -451,7 +482,8 @@ else cookie += "; Path=/";
|
|
|
451
482
|
setContentTypeByFile(file, filename) {
|
|
452
483
|
if (this.has("Content-Type")) return;
|
|
453
484
|
if (typeof file === "string" && filename) {
|
|
454
|
-
const
|
|
485
|
+
const dotIndex = filename.lastIndexOf(".");
|
|
486
|
+
const ext = dotIndex >= 0 ? filename.slice(dotIndex + 1).toLowerCase() : "";
|
|
455
487
|
const map = {
|
|
456
488
|
txt: "text/plain",
|
|
457
489
|
text: "text/plain",
|
|
@@ -492,11 +524,11 @@ else this.set("Content-Type", "application/octet-stream");
|
|
|
492
524
|
let len;
|
|
493
525
|
if (typeof body === "string") len = new TextEncoder().encode(body).length;
|
|
494
526
|
else if (isArrayBuffer(body)) len = body.byteLength;
|
|
527
|
+
else if (TangoHeaders.isNodeBuffer(body)) len = body.length;
|
|
495
528
|
else if (isUint8Array(body)) len = body.byteLength;
|
|
496
529
|
else if (isBlob(body)) len = body.size;
|
|
497
|
-
else if (
|
|
498
|
-
else if (typeof body
|
|
499
|
-
else if (typeof body?.length === "number" && typeof body !== "string" && !isArrayBuffer(body) && !isUint8Array(body)) len = body.length;
|
|
530
|
+
else if (TangoHeaders.hasNumberSize(body)) len = body.size;
|
|
531
|
+
else if (TangoHeaders.hasNumberLength(body) && typeof body !== "string" && !isArrayBuffer(body) && !isUint8Array(body)) len = body.length;
|
|
500
532
|
if (typeof len === "number") this.set("Content-Length", len.toString());
|
|
501
533
|
}
|
|
502
534
|
/**
|
|
@@ -579,112 +611,314 @@ else this.set("Server-Timing", value);
|
|
|
579
611
|
};
|
|
580
612
|
|
|
581
613
|
//#endregion
|
|
582
|
-
//#region src/http/
|
|
583
|
-
var
|
|
584
|
-
static BRAND = "tango.http.
|
|
585
|
-
__tangoBrand =
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
const sourceRequest = typeof input === "string" ? undefined : input;
|
|
590
|
-
const method = (init.method ?? sourceRequest?.method ?? "GET").toUpperCase();
|
|
591
|
-
const headers = new Headers(init.headers ?? sourceRequest?.headers);
|
|
592
|
-
const normalizedBody = this.normalizeBody(init.body, headers, method);
|
|
593
|
-
const requestInit = {
|
|
594
|
-
method,
|
|
595
|
-
headers,
|
|
596
|
-
redirect: init.redirect ?? sourceRequest?.redirect,
|
|
597
|
-
cache: init.cache ?? sourceRequest?.cache,
|
|
598
|
-
credentials: init.credentials ?? sourceRequest?.credentials,
|
|
599
|
-
integrity: init.integrity ?? sourceRequest?.integrity,
|
|
600
|
-
keepalive: init.keepalive ?? sourceRequest?.keepalive,
|
|
601
|
-
mode: init.mode ?? sourceRequest?.mode,
|
|
602
|
-
referrer: init.referrer ?? sourceRequest?.referrer,
|
|
603
|
-
referrerPolicy: init.referrerPolicy ?? sourceRequest?.referrerPolicy,
|
|
604
|
-
signal: init.signal ?? sourceRequest?.signal
|
|
605
|
-
};
|
|
606
|
-
if (normalizedBody !== undefined) requestInit.body = normalizedBody;
|
|
607
|
-
this.request = new Request(input, requestInit);
|
|
608
|
-
this.bodySourceValue = normalizedBody ?? null;
|
|
609
|
-
}
|
|
610
|
-
static isTangoRequest(value) {
|
|
611
|
-
return typeof value === "object" && value !== null && value.__tangoBrand === TangoRequest.BRAND;
|
|
614
|
+
//#region src/http/TangoQueryParams.ts
|
|
615
|
+
var TangoQueryParams = class TangoQueryParams {
|
|
616
|
+
static BRAND = "tango.http.query_params";
|
|
617
|
+
__tangoBrand = TangoQueryParams.BRAND;
|
|
618
|
+
values;
|
|
619
|
+
constructor(values) {
|
|
620
|
+
this.values = values;
|
|
612
621
|
}
|
|
613
|
-
|
|
614
|
-
|
|
622
|
+
/**
|
|
623
|
+
* Narrow an unknown value to `TangoQueryParams`.
|
|
624
|
+
*/
|
|
625
|
+
static isTangoQueryParams(value) {
|
|
626
|
+
return typeof value === "object" && value !== null && value.__tangoBrand === TangoQueryParams.BRAND;
|
|
615
627
|
}
|
|
616
|
-
|
|
617
|
-
|
|
628
|
+
/**
|
|
629
|
+
* Build query params from a `URLSearchParams` instance.
|
|
630
|
+
*/
|
|
631
|
+
static fromURLSearchParams(params) {
|
|
632
|
+
const values = new Map();
|
|
633
|
+
for (const [key, value] of params.entries()) {
|
|
634
|
+
const current = values.get(key);
|
|
635
|
+
if (current) {
|
|
636
|
+
current.push(value);
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
values.set(key, [value]);
|
|
640
|
+
}
|
|
641
|
+
return new TangoQueryParams(values);
|
|
618
642
|
}
|
|
619
|
-
|
|
620
|
-
|
|
643
|
+
/**
|
|
644
|
+
* Build query params from framework record-style search params.
|
|
645
|
+
*/
|
|
646
|
+
static fromRecord(params) {
|
|
647
|
+
const values = new Map();
|
|
648
|
+
for (const [key, value] of Object.entries(params)) {
|
|
649
|
+
if (Array.isArray(value)) {
|
|
650
|
+
const normalized = value.filter((entry) => typeof entry === "string");
|
|
651
|
+
if (normalized.length > 0) values.set(key, normalized);
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
if (typeof value === "string") values.set(key, [value]);
|
|
655
|
+
}
|
|
656
|
+
return new TangoQueryParams(values);
|
|
621
657
|
}
|
|
658
|
+
/**
|
|
659
|
+
* Build query params from a full URL string or URL object.
|
|
660
|
+
*/
|
|
661
|
+
static fromURL(input) {
|
|
662
|
+
const url = typeof input === "string" ? new URL(input) : input;
|
|
663
|
+
return TangoQueryParams.fromURLSearchParams(url.searchParams);
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Build query params from a request-like object with a URL.
|
|
667
|
+
*/
|
|
668
|
+
static fromRequest(request) {
|
|
669
|
+
return TangoQueryParams.fromURL(request.url);
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Get the first value for a query param.
|
|
673
|
+
*/
|
|
674
|
+
get(name) {
|
|
675
|
+
return this.values.get(name)?.[0];
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Get all values for a query param.
|
|
679
|
+
*/
|
|
680
|
+
getAll(name) {
|
|
681
|
+
return [...this.values.get(name) ?? []];
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Check whether a query param exists.
|
|
685
|
+
*/
|
|
686
|
+
has(name) {
|
|
687
|
+
return (this.values.get(name)?.length ?? 0) > 0;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Iterate key -> values entries.
|
|
691
|
+
*/
|
|
692
|
+
*entries() {
|
|
693
|
+
for (const [key, values] of this.values.entries()) yield [key, [...values]];
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Iterate keys present in the query params.
|
|
697
|
+
*/
|
|
698
|
+
*keys() {
|
|
699
|
+
yield* this.values.keys();
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Convert back to a native `URLSearchParams` object.
|
|
703
|
+
*/
|
|
704
|
+
toURLSearchParams() {
|
|
705
|
+
const params = new URLSearchParams();
|
|
706
|
+
for (const [key, values] of this.values.entries()) for (const value of values) params.append(key, value);
|
|
707
|
+
return params;
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Get a trimmed value, omitting blank strings.
|
|
711
|
+
*/
|
|
712
|
+
getTrimmed(name) {
|
|
713
|
+
const value = this.get(name)?.trim();
|
|
714
|
+
return value ? value : undefined;
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Get the free-text search param using Tango's default key.
|
|
718
|
+
*/
|
|
719
|
+
getSearch(key = "search") {
|
|
720
|
+
return this.getTrimmed(key);
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Get the ordering param as trimmed field tokens.
|
|
724
|
+
*/
|
|
725
|
+
getOrdering(key = "ordering") {
|
|
726
|
+
const value = this.get(key);
|
|
727
|
+
if (!value) return [];
|
|
728
|
+
return value.split(",").map((token) => token.trim()).filter((token) => token.length > 0);
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
//#endregion
|
|
733
|
+
//#region src/http/TangoRequest.ts
|
|
734
|
+
var TangoRequest = class TangoRequest {
|
|
735
|
+
static BRAND = "tango.http.request";
|
|
736
|
+
__tangoBrand = TangoRequest.BRAND;
|
|
737
|
+
request;
|
|
738
|
+
bodySourceValue;
|
|
739
|
+
queryParamsValue;
|
|
740
|
+
constructor(input, init = {}) {
|
|
741
|
+
const sourceRequest = typeof input === "string" ? undefined : input;
|
|
742
|
+
const method = (init.method ?? sourceRequest?.method ?? "GET").toUpperCase();
|
|
743
|
+
const headers = new Headers(init.headers ?? sourceRequest?.headers);
|
|
744
|
+
const normalizedBody = this.normalizeBody(init.body, headers, method);
|
|
745
|
+
const requestInit = {
|
|
746
|
+
method,
|
|
747
|
+
headers,
|
|
748
|
+
redirect: init.redirect ?? sourceRequest?.redirect,
|
|
749
|
+
cache: init.cache ?? sourceRequest?.cache,
|
|
750
|
+
credentials: init.credentials ?? sourceRequest?.credentials,
|
|
751
|
+
integrity: init.integrity ?? sourceRequest?.integrity,
|
|
752
|
+
keepalive: init.keepalive ?? sourceRequest?.keepalive,
|
|
753
|
+
mode: init.mode ?? sourceRequest?.mode,
|
|
754
|
+
referrer: init.referrer ?? sourceRequest?.referrer,
|
|
755
|
+
referrerPolicy: init.referrerPolicy ?? sourceRequest?.referrerPolicy,
|
|
756
|
+
signal: init.signal ?? sourceRequest?.signal
|
|
757
|
+
};
|
|
758
|
+
if (normalizedBody !== undefined) {
|
|
759
|
+
requestInit.body = normalizedBody;
|
|
760
|
+
if (isReadableStream(normalizedBody)) requestInit.duplex = "half";
|
|
761
|
+
}
|
|
762
|
+
this.request = new Request(input, requestInit);
|
|
763
|
+
this.bodySourceValue = normalizedBody ?? null;
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Narrow an unknown value to `TangoRequest`.
|
|
767
|
+
*/
|
|
768
|
+
static isTangoRequest(value) {
|
|
769
|
+
return typeof value === "object" && value !== null && value.__tangoBrand === TangoRequest.BRAND;
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Expose the request cache mode from the underlying fetch request.
|
|
773
|
+
*/
|
|
774
|
+
get cache() {
|
|
775
|
+
return this.request.cache;
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Expose the request credentials mode from the underlying fetch request.
|
|
779
|
+
*/
|
|
780
|
+
get credentials() {
|
|
781
|
+
return this.request.credentials;
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Expose the request destination from the underlying fetch request.
|
|
785
|
+
*/
|
|
786
|
+
get destination() {
|
|
787
|
+
return this.request.destination;
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Expose the request headers from the underlying fetch request.
|
|
791
|
+
*/
|
|
622
792
|
get headers() {
|
|
623
793
|
return this.request.headers;
|
|
624
794
|
}
|
|
795
|
+
/**
|
|
796
|
+
* Expose the request integrity value from the underlying fetch request.
|
|
797
|
+
*/
|
|
625
798
|
get integrity() {
|
|
626
799
|
return this.request.integrity;
|
|
627
800
|
}
|
|
801
|
+
/**
|
|
802
|
+
* Expose the request keepalive flag from the underlying fetch request.
|
|
803
|
+
*/
|
|
628
804
|
get keepalive() {
|
|
629
805
|
return this.request.keepalive;
|
|
630
806
|
}
|
|
807
|
+
/**
|
|
808
|
+
* Expose the normalized HTTP method.
|
|
809
|
+
*/
|
|
631
810
|
get method() {
|
|
632
811
|
return this.request.method;
|
|
633
812
|
}
|
|
813
|
+
/**
|
|
814
|
+
* Expose the request mode from the underlying fetch request.
|
|
815
|
+
*/
|
|
634
816
|
get mode() {
|
|
635
817
|
return this.request.mode;
|
|
636
818
|
}
|
|
819
|
+
/**
|
|
820
|
+
* Expose the redirect policy from the underlying fetch request.
|
|
821
|
+
*/
|
|
637
822
|
get redirect() {
|
|
638
823
|
return this.request.redirect;
|
|
639
824
|
}
|
|
825
|
+
/**
|
|
826
|
+
* Expose the referrer from the underlying fetch request.
|
|
827
|
+
*/
|
|
640
828
|
get referrer() {
|
|
641
829
|
return this.request.referrer;
|
|
642
830
|
}
|
|
831
|
+
/**
|
|
832
|
+
* Expose the referrer policy from the underlying fetch request.
|
|
833
|
+
*/
|
|
643
834
|
get referrerPolicy() {
|
|
644
835
|
return this.request.referrerPolicy;
|
|
645
836
|
}
|
|
837
|
+
/**
|
|
838
|
+
* Expose the abort signal from the underlying fetch request.
|
|
839
|
+
*/
|
|
646
840
|
get signal() {
|
|
647
841
|
return this.request.signal;
|
|
648
842
|
}
|
|
843
|
+
/**
|
|
844
|
+
* Expose the absolute request URL.
|
|
845
|
+
*/
|
|
649
846
|
get url() {
|
|
650
847
|
return this.request.url;
|
|
651
848
|
}
|
|
849
|
+
/**
|
|
850
|
+
* Expose the readable request body stream when one exists.
|
|
851
|
+
*/
|
|
652
852
|
get body() {
|
|
653
853
|
return this.request.body;
|
|
654
854
|
}
|
|
855
|
+
/**
|
|
856
|
+
* Report whether the body has been consumed.
|
|
857
|
+
*/
|
|
655
858
|
get bodyUsed() {
|
|
656
859
|
return this.request.bodyUsed;
|
|
657
860
|
}
|
|
861
|
+
/**
|
|
862
|
+
* Expose the pre-normalized body value used to build the request.
|
|
863
|
+
*/
|
|
658
864
|
get bodySource() {
|
|
659
865
|
return this.bodySourceValue;
|
|
660
866
|
}
|
|
867
|
+
/**
|
|
868
|
+
* Expose normalized query parameters derived from the request URL.
|
|
869
|
+
*/
|
|
870
|
+
get queryParams() {
|
|
871
|
+
this.queryParamsValue ??= TangoQueryParams.fromURL(this.request.url);
|
|
872
|
+
return this.queryParamsValue;
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Read the request body as an array buffer.
|
|
876
|
+
*/
|
|
661
877
|
async arrayBuffer() {
|
|
662
878
|
return this.request.arrayBuffer();
|
|
663
879
|
}
|
|
880
|
+
/**
|
|
881
|
+
* Read the request body as a blob.
|
|
882
|
+
*/
|
|
664
883
|
async blob() {
|
|
665
884
|
return this.request.blob();
|
|
666
885
|
}
|
|
886
|
+
/**
|
|
887
|
+
* Read the request body as bytes, including runtimes without `Request.bytes()`.
|
|
888
|
+
*/
|
|
667
889
|
async bytes() {
|
|
668
890
|
const requestWithBytes = this.request;
|
|
669
891
|
if (typeof requestWithBytes.bytes === "function") return requestWithBytes.bytes();
|
|
670
892
|
const buffer = await this.request.arrayBuffer();
|
|
671
893
|
return new Uint8Array(buffer);
|
|
672
894
|
}
|
|
895
|
+
/**
|
|
896
|
+
* Read the request body as form data.
|
|
897
|
+
*/
|
|
673
898
|
async formData() {
|
|
674
899
|
return this.request.formData();
|
|
675
900
|
}
|
|
901
|
+
/**
|
|
902
|
+
* Parse the request body as JSON.
|
|
903
|
+
*/
|
|
676
904
|
async json() {
|
|
677
905
|
return this.request.json();
|
|
678
906
|
}
|
|
907
|
+
/**
|
|
908
|
+
* Read the request body as text.
|
|
909
|
+
*/
|
|
679
910
|
async text() {
|
|
680
911
|
return this.request.text();
|
|
681
912
|
}
|
|
913
|
+
/**
|
|
914
|
+
* Clone the request so downstream code can consume it independently.
|
|
915
|
+
*/
|
|
682
916
|
clone() {
|
|
683
917
|
return new TangoRequest(this.request.clone());
|
|
684
918
|
}
|
|
685
919
|
normalizeBody(body, headers, method) {
|
|
686
920
|
if (method === "GET" || method === "HEAD") return undefined;
|
|
687
|
-
if (body
|
|
921
|
+
if (isNil(body)) return undefined;
|
|
688
922
|
if (typeof body === "string") return body;
|
|
689
923
|
if (isArrayBuffer(body) || isUint8Array(body) || isBlob(body)) return body;
|
|
690
924
|
if (isURLSearchParams(body) || isFormData(body) || isReadableStream(body)) return body;
|
|
@@ -699,8 +933,6 @@ var TangoRequest = class TangoRequest {
|
|
|
699
933
|
var TangoResponse = class TangoResponse {
|
|
700
934
|
static BRAND = "tango.http.response";
|
|
701
935
|
__tangoBrand = TangoResponse.BRAND;
|
|
702
|
-
tangoBody;
|
|
703
|
-
okValue;
|
|
704
936
|
headers;
|
|
705
937
|
redirected;
|
|
706
938
|
status;
|
|
@@ -708,12 +940,8 @@ var TangoResponse = class TangoResponse {
|
|
|
708
940
|
type;
|
|
709
941
|
url;
|
|
710
942
|
body;
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
}
|
|
714
|
-
get bodySource() {
|
|
715
|
-
return this.tangoBody.bodySource;
|
|
716
|
-
}
|
|
943
|
+
tangoBody;
|
|
944
|
+
okValue;
|
|
717
945
|
constructor(init = {}) {
|
|
718
946
|
this.headers = new TangoHeaders(init.headers);
|
|
719
947
|
this.redirected = Boolean(init.redirected);
|
|
@@ -725,103 +953,15 @@ var TangoResponse = class TangoResponse {
|
|
|
725
953
|
this.tangoBody = new TangoBody(init.body ?? null, this.headers);
|
|
726
954
|
this.body = isReadableStream(this.tangoBody.bodySource) ? this.tangoBody.bodySource : null;
|
|
727
955
|
}
|
|
728
|
-
get ok() {
|
|
729
|
-
if (typeof this.okValue === "boolean") return this.okValue;
|
|
730
|
-
return this.status >= 200 && this.status < 300;
|
|
731
|
-
}
|
|
732
|
-
get bodyUsed() {
|
|
733
|
-
return this.tangoBody.bodyUsed;
|
|
734
|
-
}
|
|
735
|
-
setHeader(name, value) {
|
|
736
|
-
this.headers.set(name, value);
|
|
737
|
-
}
|
|
738
|
-
appendHeader(name, value) {
|
|
739
|
-
this.headers.append(name, value);
|
|
740
|
-
}
|
|
741
|
-
getHeader(name) {
|
|
742
|
-
return this.headers.get(name);
|
|
743
|
-
}
|
|
744
|
-
hasHeader(name) {
|
|
745
|
-
return this.headers.has(name);
|
|
746
|
-
}
|
|
747
|
-
deleteHeader(name) {
|
|
748
|
-
this.headers.delete(name);
|
|
749
|
-
}
|
|
750
|
-
vary(...fields) {
|
|
751
|
-
this.headers.vary(...fields);
|
|
752
|
-
}
|
|
753
|
-
setCookie(name, value, options) {
|
|
754
|
-
this.headers.setCookie(name, value, options);
|
|
755
|
-
}
|
|
756
|
-
appendCookie(name, value, options) {
|
|
757
|
-
this.headers.appendCookie(name, value, options);
|
|
758
|
-
}
|
|
759
|
-
deleteCookie(name, options) {
|
|
760
|
-
this.headers.deleteCookie(name, options);
|
|
761
|
-
}
|
|
762
|
-
cacheControl(control) {
|
|
763
|
-
this.headers.cacheControl(control);
|
|
764
|
-
}
|
|
765
|
-
location(url) {
|
|
766
|
-
this.headers.location(url);
|
|
767
|
-
}
|
|
768
|
-
contentType(mime) {
|
|
769
|
-
this.headers.contentType(mime);
|
|
770
|
-
}
|
|
771
|
-
/**
|
|
772
|
-
* Set the X-Request-Id header (request correlation).
|
|
773
|
-
* Returns this for fluent chaining.
|
|
774
|
-
*/
|
|
775
|
-
withRequestId(requestId) {
|
|
776
|
-
if (requestId != null && typeof requestId === "string" && requestId !== "") this.headers.set("X-Request-Id", requestId);
|
|
777
|
-
return this;
|
|
778
|
-
}
|
|
779
|
-
/**
|
|
780
|
-
* Set the traceparent header (W3C Trace Context propagation).
|
|
781
|
-
* Returns this for fluent chaining.
|
|
782
|
-
*/
|
|
783
|
-
withTraceparent(traceparent) {
|
|
784
|
-
if (traceparent != null && typeof traceparent === "string" && traceparent !== "") this.headers.set("traceparent", traceparent);
|
|
785
|
-
return this;
|
|
786
|
-
}
|
|
787
956
|
/**
|
|
788
|
-
*
|
|
789
|
-
* Accepts a string or array of timing metrics.
|
|
790
|
-
* Returns this for fluent chaining.
|
|
957
|
+
* Narrow an unknown value to `TangoResponse`.
|
|
791
958
|
*/
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
else if (typeof timing === "string") this.headers.set("Server-Timing", timing);
|
|
795
|
-
return this;
|
|
796
|
-
}
|
|
797
|
-
/**
|
|
798
|
-
* Set the X-Response-Time header (in ms).
|
|
799
|
-
* Numeric or formatted string (e.g. "76ms").
|
|
800
|
-
* Returns this for fluent chaining.
|
|
801
|
-
*/
|
|
802
|
-
withResponseTime(time) {
|
|
803
|
-
if (typeof time === "number") this.headers.set("X-Response-Time", `${time}ms`);
|
|
804
|
-
else if (typeof time === "string") this.headers.set("X-Response-Time", time);
|
|
805
|
-
return this;
|
|
959
|
+
static isTangoResponse(value) {
|
|
960
|
+
return typeof value === "object" && value !== null && value.__tangoBrand === TangoResponse.BRAND;
|
|
806
961
|
}
|
|
807
962
|
/**
|
|
808
|
-
*
|
|
809
|
-
* Known headers: x-request-id, traceparent, server-timing
|
|
810
|
-
* Returns this for fluent chaining.
|
|
963
|
+
* Create a JSON response with sensible content headers.
|
|
811
964
|
*/
|
|
812
|
-
propagateTraceHeaders(input) {
|
|
813
|
-
const incoming = new TangoHeaders(input);
|
|
814
|
-
const traceHeaderNames = [
|
|
815
|
-
"x-request-id",
|
|
816
|
-
"traceparent",
|
|
817
|
-
"server-timing"
|
|
818
|
-
];
|
|
819
|
-
for (const name of traceHeaderNames) {
|
|
820
|
-
const value = incoming.get(name);
|
|
821
|
-
if (value != null) this.headers.set(name, value);
|
|
822
|
-
}
|
|
823
|
-
return this;
|
|
824
|
-
}
|
|
825
965
|
static json(data, init) {
|
|
826
966
|
const headers = new TangoHeaders(init?.headers);
|
|
827
967
|
if (!headers.has("Content-Type")) headers.set("Content-Type", "application/json; charset=utf-8");
|
|
@@ -833,6 +973,9 @@ else if (typeof time === "string") this.headers.set("X-Response-Time", time);
|
|
|
833
973
|
headers
|
|
834
974
|
});
|
|
835
975
|
}
|
|
976
|
+
/**
|
|
977
|
+
* Create a plain-text response with sensible content headers.
|
|
978
|
+
*/
|
|
836
979
|
static text(text, init) {
|
|
837
980
|
const headers = new TangoHeaders(init?.headers);
|
|
838
981
|
if (!headers.has("Content-Type")) headers.set("Content-Type", "text/plain; charset=utf-8");
|
|
@@ -843,6 +986,9 @@ else if (typeof time === "string") this.headers.set("X-Response-Time", time);
|
|
|
843
986
|
headers
|
|
844
987
|
});
|
|
845
988
|
}
|
|
989
|
+
/**
|
|
990
|
+
* Create an HTML response with sensible content headers.
|
|
991
|
+
*/
|
|
846
992
|
static html(html, init) {
|
|
847
993
|
const headers = new TangoHeaders(init?.headers);
|
|
848
994
|
if (!headers.has("Content-Type")) headers.set("Content-Type", "text/html; charset=utf-8");
|
|
@@ -853,6 +999,9 @@ else if (typeof time === "string") this.headers.set("X-Response-Time", time);
|
|
|
853
999
|
headers
|
|
854
1000
|
});
|
|
855
1001
|
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Create a streaming response without buffering the payload in memory.
|
|
1004
|
+
*/
|
|
856
1005
|
static stream(stream, init) {
|
|
857
1006
|
return new TangoResponse({
|
|
858
1007
|
...init,
|
|
@@ -860,6 +1009,9 @@ else if (typeof time === "string") this.headers.set("X-Response-Time", time);
|
|
|
860
1009
|
headers: new TangoHeaders(init?.headers)
|
|
861
1010
|
});
|
|
862
1011
|
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Create a redirect response and set the `Location` header.
|
|
1014
|
+
*/
|
|
863
1015
|
static redirect(url, status = 302, init) {
|
|
864
1016
|
const headers = new TangoHeaders(init?.headers);
|
|
865
1017
|
headers.set("Location", url);
|
|
@@ -872,6 +1024,9 @@ else if (typeof time === "string") this.headers.set("X-Response-Time", time);
|
|
|
872
1024
|
url
|
|
873
1025
|
});
|
|
874
1026
|
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Create an empty `204 No Content` response.
|
|
1029
|
+
*/
|
|
875
1030
|
static noContent(init) {
|
|
876
1031
|
const headers = new TangoHeaders(init?.headers);
|
|
877
1032
|
return new TangoResponse({
|
|
@@ -881,6 +1036,9 @@ else if (typeof time === "string") this.headers.set("X-Response-Time", time);
|
|
|
881
1036
|
headers
|
|
882
1037
|
});
|
|
883
1038
|
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Create a `201 Created` response and optionally attach a location or body.
|
|
1041
|
+
*/
|
|
884
1042
|
static created(location, body, init) {
|
|
885
1043
|
const headers = new TangoHeaders(init?.headers);
|
|
886
1044
|
if (location) headers.set("Location", location);
|
|
@@ -890,8 +1048,6 @@ else if (typeof time === "string") this.headers.set("X-Response-Time", time);
|
|
|
890
1048
|
if (!headers.has("Content-Type")) headers.set("Content-Type", "application/json; charset=utf-8");
|
|
891
1049
|
}
|
|
892
1050
|
if (typeof respBody === "string" && !headers.has("Content-Length")) headers.set("Content-Length", new TextEncoder().encode(respBody).length.toString());
|
|
893
|
-
else if (isArrayBuffer(respBody) && !headers.has("Content-Length")) headers.set("Content-Length", respBody.byteLength.toString());
|
|
894
|
-
else if (isBlob(respBody) && !headers.has("Content-Length")) headers.set("Content-Length", respBody.size.toString());
|
|
895
1051
|
return new TangoResponse({
|
|
896
1052
|
...init,
|
|
897
1053
|
body: respBody,
|
|
@@ -899,11 +1055,26 @@ else if (isBlob(respBody) && !headers.has("Content-Length")) headers.set("Conten
|
|
|
899
1055
|
headers
|
|
900
1056
|
});
|
|
901
1057
|
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Create a `405 Method Not Allowed` response and optionally populate `Allow`.
|
|
1060
|
+
*/
|
|
1061
|
+
static methodNotAllowed(allow, detail = "Method not allowed.", init) {
|
|
1062
|
+
const headers = new TangoHeaders(init?.headers);
|
|
1063
|
+
if (allow && allow.length > 0) headers.set("Allow", allow.join(", "));
|
|
1064
|
+
return TangoResponse.json({ error: detail }, {
|
|
1065
|
+
...init,
|
|
1066
|
+
status: 405,
|
|
1067
|
+
headers
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Normalize a Tango error or problem-details object into an error response.
|
|
1072
|
+
*/
|
|
902
1073
|
static error(error, init) {
|
|
903
1074
|
let code;
|
|
904
1075
|
let message;
|
|
905
|
-
let details
|
|
906
|
-
let fields
|
|
1076
|
+
let details;
|
|
1077
|
+
let fields;
|
|
907
1078
|
let status = init?.status ?? 500;
|
|
908
1079
|
if (TangoError.isTangoError(error)) {
|
|
909
1080
|
const envelope = error.toErrorEnvelope();
|
|
@@ -928,6 +1099,9 @@ else if (isBlob(respBody) && !headers.has("Content-Length")) headers.set("Conten
|
|
|
928
1099
|
status
|
|
929
1100
|
});
|
|
930
1101
|
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Create a `400 Bad Request` response from a string or structured error.
|
|
1104
|
+
*/
|
|
931
1105
|
static badRequest(detail, init) {
|
|
932
1106
|
if (TangoError.isTangoError(detail) || TangoError.isProblemDetails(detail)) return TangoResponse.error(detail, {
|
|
933
1107
|
...init,
|
|
@@ -948,6 +1122,9 @@ else if (isBlob(respBody) && !headers.has("Content-Length")) headers.set("Conten
|
|
|
948
1122
|
status: 400
|
|
949
1123
|
});
|
|
950
1124
|
}
|
|
1125
|
+
/**
|
|
1126
|
+
* Create a `401 Unauthorized` response from a string or structured error.
|
|
1127
|
+
*/
|
|
951
1128
|
static unauthorized(detail, init) {
|
|
952
1129
|
if (TangoError.isTangoError(detail) || TangoError.isProblemDetails(detail)) return TangoResponse.error(detail, {
|
|
953
1130
|
...init,
|
|
@@ -968,6 +1145,9 @@ else if (isBlob(respBody) && !headers.has("Content-Length")) headers.set("Conten
|
|
|
968
1145
|
status: 401
|
|
969
1146
|
});
|
|
970
1147
|
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Create a `403 Forbidden` response from a string or structured error.
|
|
1150
|
+
*/
|
|
971
1151
|
static forbidden(detail, init) {
|
|
972
1152
|
if (TangoError.isTangoError(detail) || TangoError.isProblemDetails(detail)) return TangoResponse.error(detail, {
|
|
973
1153
|
...init,
|
|
@@ -988,6 +1168,9 @@ else if (isBlob(respBody) && !headers.has("Content-Length")) headers.set("Conten
|
|
|
988
1168
|
status: 403
|
|
989
1169
|
});
|
|
990
1170
|
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Create a `404 Not Found` response from a string or structured error.
|
|
1173
|
+
*/
|
|
991
1174
|
static notFound(detail, init) {
|
|
992
1175
|
if (TangoError.isTangoError(detail) || TangoError.isProblemDetails(detail)) return TangoResponse.error(detail, {
|
|
993
1176
|
...init,
|
|
@@ -1008,6 +1191,9 @@ else if (isBlob(respBody) && !headers.has("Content-Length")) headers.set("Conten
|
|
|
1008
1191
|
status: 404
|
|
1009
1192
|
});
|
|
1010
1193
|
}
|
|
1194
|
+
/**
|
|
1195
|
+
* Create a `409 Conflict` response from a string or structured error.
|
|
1196
|
+
*/
|
|
1011
1197
|
static conflict(detail, init) {
|
|
1012
1198
|
if (TangoError.isTangoError(detail) || TangoError.isProblemDetails(detail)) return TangoResponse.error(detail, {
|
|
1013
1199
|
...init,
|
|
@@ -1028,6 +1214,9 @@ else if (isBlob(respBody) && !headers.has("Content-Length")) headers.set("Conten
|
|
|
1028
1214
|
status: 409
|
|
1029
1215
|
});
|
|
1030
1216
|
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Create a `422 Unprocessable Entity` response from a string or structured error.
|
|
1219
|
+
*/
|
|
1031
1220
|
static unprocessableEntity(detail, init) {
|
|
1032
1221
|
if (TangoError.isTangoError(detail) || TangoError.isProblemDetails(detail)) return TangoResponse.error(detail, {
|
|
1033
1222
|
...init,
|
|
@@ -1048,6 +1237,9 @@ else if (isBlob(respBody) && !headers.has("Content-Length")) headers.set("Conten
|
|
|
1048
1237
|
status: 422
|
|
1049
1238
|
});
|
|
1050
1239
|
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Create a `429 Too Many Requests` response from a string or structured error.
|
|
1242
|
+
*/
|
|
1051
1243
|
static tooManyRequests(detail, init) {
|
|
1052
1244
|
if (TangoError.isTangoError(detail) || TangoError.isProblemDetails(detail)) return TangoResponse.error(detail, {
|
|
1053
1245
|
...init,
|
|
@@ -1068,6 +1260,9 @@ else if (isBlob(respBody) && !headers.has("Content-Length")) headers.set("Conten
|
|
|
1068
1260
|
status: 429
|
|
1069
1261
|
});
|
|
1070
1262
|
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Create a problem-details style error response with Tango's envelope shape.
|
|
1265
|
+
*/
|
|
1071
1266
|
static problem(problem, init) {
|
|
1072
1267
|
let status = init?.status ?? 500;
|
|
1073
1268
|
const headers = new TangoHeaders(init?.headers);
|
|
@@ -1091,15 +1286,13 @@ else if (isBlob(respBody) && !headers.has("Content-Length")) headers.set("Conten
|
|
|
1091
1286
|
} else if (typeof problem === "string") message = problem;
|
|
1092
1287
|
else if (problem && typeof problem === "object") {
|
|
1093
1288
|
const extracted = problem;
|
|
1094
|
-
if (typeof extracted.code === "string") code = extracted.code;
|
|
1095
|
-
if (typeof extracted.message === "string") message = extracted.message;
|
|
1096
1289
|
details = extracted.details;
|
|
1097
1290
|
fields = extracted.fields;
|
|
1098
1291
|
}
|
|
1099
1292
|
const envelope = { error: {
|
|
1100
1293
|
code,
|
|
1101
1294
|
message,
|
|
1102
|
-
...details
|
|
1295
|
+
...details === undefined ? {} : { details },
|
|
1103
1296
|
...fields ? { fields } : {}
|
|
1104
1297
|
} };
|
|
1105
1298
|
const body = JSON.stringify(envelope);
|
|
@@ -1111,6 +1304,189 @@ else if (problem && typeof problem === "object") {
|
|
|
1111
1304
|
body
|
|
1112
1305
|
});
|
|
1113
1306
|
}
|
|
1307
|
+
/**
|
|
1308
|
+
* Returns a response for serving a file.
|
|
1309
|
+
*/
|
|
1310
|
+
static file(file, opts) {
|
|
1311
|
+
const headers = new TangoHeaders(opts?.init?.headers ?? {});
|
|
1312
|
+
if (opts?.filename) headers.setContentDispositionInline(opts.filename);
|
|
1313
|
+
if (opts?.contentType && !headers.has("Content-Type")) headers.set("Content-Type", opts.contentType);
|
|
1314
|
+
else if (!headers.has("Content-Type")) headers.setContentTypeByFile(file, opts?.filename);
|
|
1315
|
+
if (!headers.has("Content-Length")) headers.setContentLengthFromBody(file);
|
|
1316
|
+
return new TangoResponse({
|
|
1317
|
+
...opts?.init,
|
|
1318
|
+
body: file,
|
|
1319
|
+
headers
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Returns a response that prompts the user to download the file.
|
|
1324
|
+
*/
|
|
1325
|
+
static download(file, opts) {
|
|
1326
|
+
const headers = new TangoHeaders(opts?.init?.headers ?? {});
|
|
1327
|
+
if (opts?.filename) headers.setContentDispositionAttachment(opts.filename);
|
|
1328
|
+
else headers.set("Content-Disposition", "attachment");
|
|
1329
|
+
if (opts?.contentType && !headers.has("Content-Type")) headers.set("Content-Type", opts.contentType);
|
|
1330
|
+
else if (!headers.has("Content-Type")) headers.setContentTypeByFile(file, opts?.filename);
|
|
1331
|
+
if (!headers.has("Content-Length")) headers.setContentLengthFromBody(file);
|
|
1332
|
+
return new TangoResponse({
|
|
1333
|
+
...opts?.init,
|
|
1334
|
+
body: file,
|
|
1335
|
+
headers
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
static normalizeWebBody(body) {
|
|
1339
|
+
if (isNil(body) || typeof body === "string" || isBlob(body) || isFormData(body) || isArrayBuffer(body) || isUint8Array(body) || isURLSearchParams(body) || isReadableStream(body)) return body;
|
|
1340
|
+
return JSON.stringify(body);
|
|
1341
|
+
}
|
|
1342
|
+
/**
|
|
1343
|
+
* Expose the original body source for cloning and adapter integration.
|
|
1344
|
+
*/
|
|
1345
|
+
get bodySource() {
|
|
1346
|
+
return this.tangoBody.bodySource;
|
|
1347
|
+
}
|
|
1348
|
+
/**
|
|
1349
|
+
* Report whether the status code falls inside the 2xx range.
|
|
1350
|
+
*/
|
|
1351
|
+
get ok() {
|
|
1352
|
+
if (typeof this.okValue === "boolean") return this.okValue;
|
|
1353
|
+
return this.status >= 200 && this.status < 300;
|
|
1354
|
+
}
|
|
1355
|
+
/**
|
|
1356
|
+
* Report whether the body has been consumed.
|
|
1357
|
+
*/
|
|
1358
|
+
get bodyUsed() {
|
|
1359
|
+
return this.tangoBody.bodyUsed;
|
|
1360
|
+
}
|
|
1361
|
+
/**
|
|
1362
|
+
* Replace a header value on the response.
|
|
1363
|
+
*/
|
|
1364
|
+
setHeader(name, value) {
|
|
1365
|
+
this.headers.set(name, value);
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Append another value for a repeated response header.
|
|
1369
|
+
*/
|
|
1370
|
+
appendHeader(name, value) {
|
|
1371
|
+
this.headers.append(name, value);
|
|
1372
|
+
}
|
|
1373
|
+
/**
|
|
1374
|
+
* Read a response header value.
|
|
1375
|
+
*/
|
|
1376
|
+
getHeader(name) {
|
|
1377
|
+
return this.headers.get(name);
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Check whether a response header is present.
|
|
1381
|
+
*/
|
|
1382
|
+
hasHeader(name) {
|
|
1383
|
+
return this.headers.has(name);
|
|
1384
|
+
}
|
|
1385
|
+
/**
|
|
1386
|
+
* Remove a response header.
|
|
1387
|
+
*/
|
|
1388
|
+
deleteHeader(name) {
|
|
1389
|
+
this.headers.delete(name);
|
|
1390
|
+
}
|
|
1391
|
+
/**
|
|
1392
|
+
* Merge one or more values into the `Vary` header.
|
|
1393
|
+
*/
|
|
1394
|
+
vary(...fields) {
|
|
1395
|
+
this.headers.vary(...fields);
|
|
1396
|
+
}
|
|
1397
|
+
/**
|
|
1398
|
+
* Add a `Set-Cookie` header that replaces prior application intent.
|
|
1399
|
+
*/
|
|
1400
|
+
setCookie(name, value, options) {
|
|
1401
|
+
this.headers.setCookie(name, value, options);
|
|
1402
|
+
}
|
|
1403
|
+
/**
|
|
1404
|
+
* Append another `Set-Cookie` header.
|
|
1405
|
+
*/
|
|
1406
|
+
appendCookie(name, value, options) {
|
|
1407
|
+
this.headers.appendCookie(name, value, options);
|
|
1408
|
+
}
|
|
1409
|
+
/**
|
|
1410
|
+
* Expire a cookie by issuing a matching deletion cookie header.
|
|
1411
|
+
*/
|
|
1412
|
+
deleteCookie(name, options) {
|
|
1413
|
+
this.headers.deleteCookie(name, options);
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Set the `Cache-Control` header through Tango's higher-level helper.
|
|
1417
|
+
*/
|
|
1418
|
+
cacheControl(control) {
|
|
1419
|
+
this.headers.cacheControl(control);
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Set the `Location` header on the response.
|
|
1423
|
+
*/
|
|
1424
|
+
location(url) {
|
|
1425
|
+
this.headers.location(url);
|
|
1426
|
+
}
|
|
1427
|
+
/**
|
|
1428
|
+
* Set the response content type.
|
|
1429
|
+
*/
|
|
1430
|
+
contentType(mime) {
|
|
1431
|
+
this.headers.contentType(mime);
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1434
|
+
* Set the X-Request-Id header (request correlation).
|
|
1435
|
+
* Returns this for fluent chaining.
|
|
1436
|
+
*/
|
|
1437
|
+
withRequestId(requestId) {
|
|
1438
|
+
if (!isNil(requestId) && typeof requestId === "string" && requestId !== "") this.headers.set("X-Request-Id", requestId);
|
|
1439
|
+
return this;
|
|
1440
|
+
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Set the traceparent header (W3C Trace Context propagation).
|
|
1443
|
+
* Returns this for fluent chaining.
|
|
1444
|
+
*/
|
|
1445
|
+
withTraceparent(traceparent) {
|
|
1446
|
+
if (!isNil(traceparent) && typeof traceparent === "string" && traceparent !== "") this.headers.set("traceparent", traceparent);
|
|
1447
|
+
return this;
|
|
1448
|
+
}
|
|
1449
|
+
/**
|
|
1450
|
+
* Set the Server-Timing header.
|
|
1451
|
+
* Accepts a string or array of timing metrics.
|
|
1452
|
+
* Returns this for fluent chaining.
|
|
1453
|
+
*/
|
|
1454
|
+
withServerTiming(timing) {
|
|
1455
|
+
if (Array.isArray(timing)) this.headers.set("Server-Timing", timing.join(", "));
|
|
1456
|
+
else if (typeof timing === "string") this.headers.set("Server-Timing", timing);
|
|
1457
|
+
return this;
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
* Set the X-Response-Time header (in ms).
|
|
1461
|
+
* Numeric or formatted string (e.g. "76ms").
|
|
1462
|
+
* Returns this for fluent chaining.
|
|
1463
|
+
*/
|
|
1464
|
+
withResponseTime(time) {
|
|
1465
|
+
if (typeof time === "number") this.headers.set("X-Response-Time", `${time}ms`);
|
|
1466
|
+
else if (typeof time === "string") this.headers.set("X-Response-Time", time);
|
|
1467
|
+
return this;
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Propagate common tracing/correlation headers from provided Headers, TangoHeaders, or plain object.
|
|
1471
|
+
* Known headers: x-request-id, traceparent, server-timing
|
|
1472
|
+
* Returns this for fluent chaining.
|
|
1473
|
+
*/
|
|
1474
|
+
propagateTraceHeaders(input) {
|
|
1475
|
+
const incoming = new TangoHeaders(input);
|
|
1476
|
+
const traceHeaderNames = [
|
|
1477
|
+
"x-request-id",
|
|
1478
|
+
"traceparent",
|
|
1479
|
+
"server-timing"
|
|
1480
|
+
];
|
|
1481
|
+
for (const name of traceHeaderNames) {
|
|
1482
|
+
const value = incoming.get(name);
|
|
1483
|
+
if (!isNil(value)) this.headers.set(name, value);
|
|
1484
|
+
}
|
|
1485
|
+
return this;
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* Clone the response so its body can be consumed independently.
|
|
1489
|
+
*/
|
|
1114
1490
|
clone() {
|
|
1115
1491
|
if (this.bodyUsed) throw new TypeError("Body has already been used");
|
|
1116
1492
|
const clonedBody = this.tangoBody.clone();
|
|
@@ -1125,54 +1501,57 @@ else if (problem && typeof problem === "object") {
|
|
|
1125
1501
|
url: this.url
|
|
1126
1502
|
});
|
|
1127
1503
|
}
|
|
1504
|
+
/**
|
|
1505
|
+
* Convert this Tango-owned response into a native web `Response`.
|
|
1506
|
+
*
|
|
1507
|
+
* Adapters use this at the host-framework boundary so Tango can standardize
|
|
1508
|
+
* on `TangoResponse` internally while Next.js and other hosts still receive
|
|
1509
|
+
* a platform-native response object.
|
|
1510
|
+
*/
|
|
1511
|
+
toWebResponse() {
|
|
1512
|
+
const responseForTransfer = !this.bodyUsed && isReadableStream(this.bodySource) ? this.clone() : this;
|
|
1513
|
+
const body = TangoResponse.normalizeWebBody(responseForTransfer.bodySource);
|
|
1514
|
+
return new Response(body, {
|
|
1515
|
+
headers: new Headers(responseForTransfer.headers),
|
|
1516
|
+
status: responseForTransfer.status,
|
|
1517
|
+
statusText: responseForTransfer.statusText
|
|
1518
|
+
});
|
|
1519
|
+
}
|
|
1520
|
+
/**
|
|
1521
|
+
* Read the response body as an array buffer.
|
|
1522
|
+
*/
|
|
1128
1523
|
async arrayBuffer() {
|
|
1129
1524
|
return this.tangoBody.arrayBuffer();
|
|
1130
1525
|
}
|
|
1526
|
+
/**
|
|
1527
|
+
* Read the response body as a blob.
|
|
1528
|
+
*/
|
|
1131
1529
|
async blob() {
|
|
1132
1530
|
return this.tangoBody.blob();
|
|
1133
1531
|
}
|
|
1532
|
+
/**
|
|
1533
|
+
* Read the response body as bytes.
|
|
1534
|
+
*/
|
|
1134
1535
|
async bytes() {
|
|
1135
1536
|
return this.tangoBody.bytes();
|
|
1136
1537
|
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Read the response body as form data.
|
|
1540
|
+
*/
|
|
1137
1541
|
async formData() {
|
|
1138
1542
|
return this.tangoBody.formData();
|
|
1139
1543
|
}
|
|
1140
|
-
async json() {
|
|
1141
|
-
return this.tangoBody.json();
|
|
1142
|
-
}
|
|
1143
|
-
async text() {
|
|
1144
|
-
return this.tangoBody.text();
|
|
1145
|
-
}
|
|
1146
1544
|
/**
|
|
1147
|
-
*
|
|
1545
|
+
* Parse the response body as JSON.
|
|
1148
1546
|
*/
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
if (opts?.filename) headers.setContentDispositionInline(opts.filename);
|
|
1152
|
-
if (opts?.contentType && !headers.has("Content-Type")) headers.set("Content-Type", opts.contentType);
|
|
1153
|
-
else if (!headers.has("Content-Type")) headers.setContentTypeByFile(file, opts?.filename);
|
|
1154
|
-
if (!headers.has("Content-Length")) headers.setContentLengthFromBody(file);
|
|
1155
|
-
return new TangoResponse({
|
|
1156
|
-
...opts?.init,
|
|
1157
|
-
body: file,
|
|
1158
|
-
headers
|
|
1159
|
-
});
|
|
1547
|
+
async json() {
|
|
1548
|
+
return this.tangoBody.json();
|
|
1160
1549
|
}
|
|
1161
1550
|
/**
|
|
1162
|
-
*
|
|
1551
|
+
* Read the response body as text.
|
|
1163
1552
|
*/
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
if (opts?.filename) headers.setContentDispositionAttachment(opts.filename);
|
|
1167
|
-
else headers.set("Content-Disposition", "attachment");
|
|
1168
|
-
if (opts?.contentType && !headers.has("Content-Type")) headers.set("Content-Type", opts.contentType);
|
|
1169
|
-
else if (!headers.has("Content-Type")) headers.setContentTypeByFile(file, opts?.filename);
|
|
1170
|
-
if (!headers.has("Content-Length")) headers.setContentLengthFromBody(file);
|
|
1171
|
-
return new TangoResponse({
|
|
1172
|
-
...opts?.init,
|
|
1173
|
-
body: file,
|
|
1174
|
-
headers
|
|
1175
|
-
});
|
|
1553
|
+
async text() {
|
|
1554
|
+
return this.tangoBody.text();
|
|
1176
1555
|
}
|
|
1177
1556
|
/**
|
|
1178
1557
|
* Returns a plain object debug representation of this response: { status, headers: { ... }, bodyType: ... }.
|
|
@@ -1182,7 +1561,7 @@ else if (!headers.has("Content-Type")) headers.setContentTypeByFile(file, opts?.
|
|
|
1182
1561
|
return {
|
|
1183
1562
|
status: this.status,
|
|
1184
1563
|
headers: Object.fromEntries(this.headers.entries()),
|
|
1185
|
-
bodyType: this.tangoBody
|
|
1564
|
+
bodyType: this.tangoBody.bodyType
|
|
1186
1565
|
};
|
|
1187
1566
|
}
|
|
1188
1567
|
/**
|
|
@@ -1209,10 +1588,11 @@ var http_exports = {};
|
|
|
1209
1588
|
__export(http_exports, {
|
|
1210
1589
|
TangoBody: () => TangoBody,
|
|
1211
1590
|
TangoHeaders: () => TangoHeaders,
|
|
1591
|
+
TangoQueryParams: () => TangoQueryParams,
|
|
1212
1592
|
TangoRequest: () => TangoRequest,
|
|
1213
1593
|
TangoResponse: () => TangoResponse
|
|
1214
1594
|
});
|
|
1215
1595
|
|
|
1216
1596
|
//#endregion
|
|
1217
|
-
export { TangoBody, TangoHeaders, TangoRequest, TangoResponse, http_exports };
|
|
1218
|
-
//# sourceMappingURL=http-
|
|
1597
|
+
export { TangoBody, TangoHeaders, TangoQueryParams, TangoRequest, TangoResponse, http_exports };
|
|
1598
|
+
//# sourceMappingURL=http-D20MQa6p.js.map
|