@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,556 +0,0 @@
|
|
|
1
|
-
import { TangoError } from '../errors/TangoError';
|
|
2
|
-
import { isArrayBuffer, isBlob, isReadableStream } from '../runtime/index';
|
|
3
|
-
import { TangoHeaders } from '../http/TangoHeaders';
|
|
4
|
-
import { TangoBody } from './TangoBody';
|
|
5
|
-
export class TangoResponse {
|
|
6
|
-
static BRAND = 'tango.http.response';
|
|
7
|
-
tangoBody;
|
|
8
|
-
okValue;
|
|
9
|
-
__tangoBrand = TangoResponse.BRAND;
|
|
10
|
-
headers;
|
|
11
|
-
redirected;
|
|
12
|
-
status;
|
|
13
|
-
statusText;
|
|
14
|
-
type;
|
|
15
|
-
url;
|
|
16
|
-
body;
|
|
17
|
-
static isTangoResponse(value) {
|
|
18
|
-
return (typeof value === 'object' &&
|
|
19
|
-
value !== null &&
|
|
20
|
-
value.__tangoBrand === TangoResponse.BRAND);
|
|
21
|
-
}
|
|
22
|
-
// bodySource proxy for legacy convenience, but prefer using TangoBody/headers directly.
|
|
23
|
-
get bodySource() {
|
|
24
|
-
return this.tangoBody.bodySource;
|
|
25
|
-
}
|
|
26
|
-
constructor(init = {}) {
|
|
27
|
-
this.headers = new TangoHeaders(init.headers);
|
|
28
|
-
this.redirected = Boolean(init.redirected);
|
|
29
|
-
this.status = typeof init.status === 'number' ? init.status : 200;
|
|
30
|
-
this.statusText = init.statusText || '';
|
|
31
|
-
this.type = init.type || 'default';
|
|
32
|
-
this.url = init.url || '';
|
|
33
|
-
this.okValue = typeof init.ok === 'boolean' ? init.ok : undefined;
|
|
34
|
-
this.tangoBody = new TangoBody(init.body ?? null, this.headers);
|
|
35
|
-
this.body = isReadableStream(this.tangoBody.bodySource) ? this.tangoBody.bodySource : null;
|
|
36
|
-
}
|
|
37
|
-
get ok() {
|
|
38
|
-
if (typeof this.okValue === 'boolean')
|
|
39
|
-
return this.okValue;
|
|
40
|
-
return this.status >= 200 && this.status < 300;
|
|
41
|
-
}
|
|
42
|
-
get bodyUsed() {
|
|
43
|
-
return this.tangoBody.bodyUsed;
|
|
44
|
-
}
|
|
45
|
-
setHeader(name, value) {
|
|
46
|
-
this.headers.set(name, value);
|
|
47
|
-
}
|
|
48
|
-
appendHeader(name, value) {
|
|
49
|
-
this.headers.append(name, value);
|
|
50
|
-
}
|
|
51
|
-
getHeader(name) {
|
|
52
|
-
return this.headers.get(name);
|
|
53
|
-
}
|
|
54
|
-
hasHeader(name) {
|
|
55
|
-
return this.headers.has(name);
|
|
56
|
-
}
|
|
57
|
-
deleteHeader(name) {
|
|
58
|
-
this.headers.delete(name);
|
|
59
|
-
}
|
|
60
|
-
vary(...fields) {
|
|
61
|
-
this.headers.vary(...fields);
|
|
62
|
-
}
|
|
63
|
-
setCookie(name, value, options) {
|
|
64
|
-
this.headers.setCookie(name, value, options);
|
|
65
|
-
}
|
|
66
|
-
appendCookie(name, value, options) {
|
|
67
|
-
this.headers.appendCookie(name, value, options);
|
|
68
|
-
}
|
|
69
|
-
deleteCookie(name, options) {
|
|
70
|
-
this.headers.deleteCookie(name, options);
|
|
71
|
-
}
|
|
72
|
-
cacheControl(control) {
|
|
73
|
-
this.headers.cacheControl(control);
|
|
74
|
-
}
|
|
75
|
-
location(url) {
|
|
76
|
-
this.headers.location(url);
|
|
77
|
-
}
|
|
78
|
-
contentType(mime) {
|
|
79
|
-
this.headers.contentType(mime);
|
|
80
|
-
}
|
|
81
|
-
// ---- Trace & Correlation helper methods ----
|
|
82
|
-
/**
|
|
83
|
-
* Set the X-Request-Id header (request correlation).
|
|
84
|
-
* Returns this for fluent chaining.
|
|
85
|
-
*/
|
|
86
|
-
withRequestId(requestId) {
|
|
87
|
-
if (requestId != null && typeof requestId === 'string' && requestId !== '') {
|
|
88
|
-
this.headers.set('X-Request-Id', requestId);
|
|
89
|
-
}
|
|
90
|
-
return this;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Set the traceparent header (W3C Trace Context propagation).
|
|
94
|
-
* Returns this for fluent chaining.
|
|
95
|
-
*/
|
|
96
|
-
withTraceparent(traceparent) {
|
|
97
|
-
if (traceparent != null && typeof traceparent === 'string' && traceparent !== '') {
|
|
98
|
-
this.headers.set('traceparent', traceparent);
|
|
99
|
-
}
|
|
100
|
-
return this;
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Set the Server-Timing header.
|
|
104
|
-
* Accepts a string or array of timing metrics.
|
|
105
|
-
* Returns this for fluent chaining.
|
|
106
|
-
*/
|
|
107
|
-
withServerTiming(timing) {
|
|
108
|
-
if (Array.isArray(timing)) {
|
|
109
|
-
this.headers.set('Server-Timing', timing.join(', '));
|
|
110
|
-
}
|
|
111
|
-
else if (typeof timing === 'string') {
|
|
112
|
-
this.headers.set('Server-Timing', timing);
|
|
113
|
-
}
|
|
114
|
-
return this;
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Set the X-Response-Time header (in ms).
|
|
118
|
-
* Numeric or formatted string (e.g. "76ms").
|
|
119
|
-
* Returns this for fluent chaining.
|
|
120
|
-
*/
|
|
121
|
-
withResponseTime(time) {
|
|
122
|
-
if (typeof time === 'number') {
|
|
123
|
-
this.headers.set('X-Response-Time', `${time}ms`);
|
|
124
|
-
}
|
|
125
|
-
else if (typeof time === 'string') {
|
|
126
|
-
this.headers.set('X-Response-Time', time);
|
|
127
|
-
}
|
|
128
|
-
return this;
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Propagate common tracing/correlation headers from provided Headers, TangoHeaders, or plain object.
|
|
132
|
-
* Known headers: x-request-id, traceparent, server-timing
|
|
133
|
-
* Returns this for fluent chaining.
|
|
134
|
-
*/
|
|
135
|
-
propagateTraceHeaders(input) {
|
|
136
|
-
const incoming = new TangoHeaders(input);
|
|
137
|
-
const traceHeaderNames = [
|
|
138
|
-
'x-request-id',
|
|
139
|
-
'traceparent',
|
|
140
|
-
'server-timing',
|
|
141
|
-
// If you want to propagate response time, add 'x-response-time',
|
|
142
|
-
];
|
|
143
|
-
for (const name of traceHeaderNames) {
|
|
144
|
-
const value = incoming.get(name);
|
|
145
|
-
if (value != null)
|
|
146
|
-
this.headers.set(name, value);
|
|
147
|
-
}
|
|
148
|
-
return this;
|
|
149
|
-
}
|
|
150
|
-
static json(data, init) {
|
|
151
|
-
const headers = new TangoHeaders(init?.headers);
|
|
152
|
-
if (!headers.has('Content-Type')) {
|
|
153
|
-
headers.set('Content-Type', 'application/json; charset=utf-8');
|
|
154
|
-
}
|
|
155
|
-
const body = JSON.stringify(data);
|
|
156
|
-
if (!headers.has('Content-Length')) {
|
|
157
|
-
headers.set('Content-Length', new TextEncoder().encode(body).length.toString());
|
|
158
|
-
}
|
|
159
|
-
return new TangoResponse({
|
|
160
|
-
...init,
|
|
161
|
-
body,
|
|
162
|
-
headers,
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
static text(text, init) {
|
|
166
|
-
const headers = new TangoHeaders(init?.headers);
|
|
167
|
-
if (!headers.has('Content-Type')) {
|
|
168
|
-
headers.set('Content-Type', 'text/plain; charset=utf-8');
|
|
169
|
-
}
|
|
170
|
-
if (!headers.has('Content-Length')) {
|
|
171
|
-
headers.set('Content-Length', new TextEncoder().encode(text).length.toString());
|
|
172
|
-
}
|
|
173
|
-
return new TangoResponse({
|
|
174
|
-
...init,
|
|
175
|
-
body: text,
|
|
176
|
-
headers,
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
static html(html, init) {
|
|
180
|
-
const headers = new TangoHeaders(init?.headers);
|
|
181
|
-
if (!headers.has('Content-Type')) {
|
|
182
|
-
headers.set('Content-Type', 'text/html; charset=utf-8');
|
|
183
|
-
}
|
|
184
|
-
if (!headers.has('Content-Length')) {
|
|
185
|
-
headers.set('Content-Length', new TextEncoder().encode(html).length.toString());
|
|
186
|
-
}
|
|
187
|
-
return new TangoResponse({
|
|
188
|
-
...init,
|
|
189
|
-
body: html,
|
|
190
|
-
headers,
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
static stream(stream, init) {
|
|
194
|
-
return new TangoResponse({
|
|
195
|
-
...init,
|
|
196
|
-
body: stream,
|
|
197
|
-
headers: new TangoHeaders(init?.headers),
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
static redirect(url, status = 302, init) {
|
|
201
|
-
const headers = new TangoHeaders(init?.headers);
|
|
202
|
-
headers.set('Location', url);
|
|
203
|
-
return new TangoResponse({
|
|
204
|
-
...init,
|
|
205
|
-
body: undefined,
|
|
206
|
-
status,
|
|
207
|
-
headers,
|
|
208
|
-
redirected: true,
|
|
209
|
-
url,
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
static noContent(init) {
|
|
213
|
-
const headers = new TangoHeaders(init?.headers);
|
|
214
|
-
return new TangoResponse({
|
|
215
|
-
...init,
|
|
216
|
-
body: undefined,
|
|
217
|
-
status: 204,
|
|
218
|
-
headers,
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
static created(location, body, init) {
|
|
222
|
-
const headers = new TangoHeaders(init?.headers);
|
|
223
|
-
if (location) {
|
|
224
|
-
headers.set('Location', location);
|
|
225
|
-
}
|
|
226
|
-
let respBody;
|
|
227
|
-
if (body !== undefined) {
|
|
228
|
-
respBody = JSON.stringify(body);
|
|
229
|
-
if (!headers.has('Content-Type')) {
|
|
230
|
-
headers.set('Content-Type', 'application/json; charset=utf-8');
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
if (typeof respBody === 'string' && !headers.has('Content-Length')) {
|
|
234
|
-
headers.set('Content-Length', new TextEncoder().encode(respBody).length.toString());
|
|
235
|
-
}
|
|
236
|
-
else if (isArrayBuffer(respBody) && !headers.has('Content-Length')) {
|
|
237
|
-
headers.set('Content-Length', respBody.byteLength.toString());
|
|
238
|
-
}
|
|
239
|
-
else if (isBlob(respBody) && !headers.has('Content-Length')) {
|
|
240
|
-
headers.set('Content-Length', respBody.size.toString());
|
|
241
|
-
}
|
|
242
|
-
return new TangoResponse({
|
|
243
|
-
...init,
|
|
244
|
-
body: respBody,
|
|
245
|
-
status: 201,
|
|
246
|
-
headers,
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
static error(error, init) {
|
|
250
|
-
let code;
|
|
251
|
-
let message;
|
|
252
|
-
let details = undefined;
|
|
253
|
-
let fields = undefined;
|
|
254
|
-
let status = init?.status ?? 500;
|
|
255
|
-
if (TangoError.isTangoError(error)) {
|
|
256
|
-
const envelope = error.toErrorEnvelope();
|
|
257
|
-
code = envelope.error.code;
|
|
258
|
-
message = envelope.error.message;
|
|
259
|
-
details = envelope.error.details;
|
|
260
|
-
fields = envelope.error.fields;
|
|
261
|
-
status = error.status;
|
|
262
|
-
}
|
|
263
|
-
else {
|
|
264
|
-
code = error.code;
|
|
265
|
-
message = error.message;
|
|
266
|
-
details = error.details;
|
|
267
|
-
fields = error.fields;
|
|
268
|
-
}
|
|
269
|
-
return TangoResponse.problem({
|
|
270
|
-
code,
|
|
271
|
-
message,
|
|
272
|
-
details,
|
|
273
|
-
fields,
|
|
274
|
-
}, {
|
|
275
|
-
...init,
|
|
276
|
-
status,
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
static badRequest(detail, init) {
|
|
280
|
-
if (TangoError.isTangoError(detail) || TangoError.isProblemDetails(detail)) {
|
|
281
|
-
return TangoResponse.error(detail, { ...init, status: 400 });
|
|
282
|
-
}
|
|
283
|
-
if (typeof detail === 'string') {
|
|
284
|
-
return TangoResponse.error({
|
|
285
|
-
code: 'bad_request',
|
|
286
|
-
message: detail,
|
|
287
|
-
}, { ...init, status: 400 });
|
|
288
|
-
}
|
|
289
|
-
return TangoResponse.error({
|
|
290
|
-
code: 'bad_request',
|
|
291
|
-
message: 'Bad Request',
|
|
292
|
-
}, { ...init, status: 400 });
|
|
293
|
-
}
|
|
294
|
-
static unauthorized(detail, init) {
|
|
295
|
-
if (TangoError.isTangoError(detail) || TangoError.isProblemDetails(detail)) {
|
|
296
|
-
return TangoResponse.error(detail, { ...init, status: 401 });
|
|
297
|
-
}
|
|
298
|
-
if (typeof detail === 'string') {
|
|
299
|
-
return TangoResponse.error({
|
|
300
|
-
code: 'unauthorized',
|
|
301
|
-
message: detail,
|
|
302
|
-
}, { ...init, status: 401 });
|
|
303
|
-
}
|
|
304
|
-
return TangoResponse.error({
|
|
305
|
-
code: 'unauthorized',
|
|
306
|
-
message: 'Unauthorized',
|
|
307
|
-
}, { ...init, status: 401 });
|
|
308
|
-
}
|
|
309
|
-
static forbidden(detail, init) {
|
|
310
|
-
if (TangoError.isTangoError(detail) || TangoError.isProblemDetails(detail)) {
|
|
311
|
-
return TangoResponse.error(detail, { ...init, status: 403 });
|
|
312
|
-
}
|
|
313
|
-
if (typeof detail === 'string') {
|
|
314
|
-
return TangoResponse.error({
|
|
315
|
-
code: 'forbidden',
|
|
316
|
-
message: detail,
|
|
317
|
-
}, { ...init, status: 403 });
|
|
318
|
-
}
|
|
319
|
-
return TangoResponse.error({
|
|
320
|
-
code: 'forbidden',
|
|
321
|
-
message: 'Forbidden',
|
|
322
|
-
}, { ...init, status: 403 });
|
|
323
|
-
}
|
|
324
|
-
static notFound(detail, init) {
|
|
325
|
-
if (TangoError.isTangoError(detail) || TangoError.isProblemDetails(detail)) {
|
|
326
|
-
return TangoResponse.error(detail, { ...init, status: 404 });
|
|
327
|
-
}
|
|
328
|
-
if (typeof detail === 'string') {
|
|
329
|
-
return TangoResponse.error({
|
|
330
|
-
code: 'not_found',
|
|
331
|
-
message: detail,
|
|
332
|
-
}, { ...init, status: 404 });
|
|
333
|
-
}
|
|
334
|
-
return TangoResponse.error({
|
|
335
|
-
code: 'not_found',
|
|
336
|
-
message: 'Not Found',
|
|
337
|
-
}, { ...init, status: 404 });
|
|
338
|
-
}
|
|
339
|
-
static conflict(detail, init) {
|
|
340
|
-
if (TangoError.isTangoError(detail) || TangoError.isProblemDetails(detail)) {
|
|
341
|
-
return TangoResponse.error(detail, { ...init, status: 409 });
|
|
342
|
-
}
|
|
343
|
-
if (typeof detail === 'string') {
|
|
344
|
-
return TangoResponse.error({
|
|
345
|
-
code: 'conflict',
|
|
346
|
-
message: detail,
|
|
347
|
-
}, { ...init, status: 409 });
|
|
348
|
-
}
|
|
349
|
-
return TangoResponse.error({
|
|
350
|
-
code: 'conflict',
|
|
351
|
-
message: 'Conflict',
|
|
352
|
-
}, { ...init, status: 409 });
|
|
353
|
-
}
|
|
354
|
-
static unprocessableEntity(detail, init) {
|
|
355
|
-
if (TangoError.isTangoError(detail) || TangoError.isProblemDetails(detail)) {
|
|
356
|
-
return TangoResponse.error(detail, { ...init, status: 422 });
|
|
357
|
-
}
|
|
358
|
-
if (typeof detail === 'string') {
|
|
359
|
-
return TangoResponse.error({
|
|
360
|
-
code: 'unprocessable_entity',
|
|
361
|
-
message: detail,
|
|
362
|
-
}, { ...init, status: 422 });
|
|
363
|
-
}
|
|
364
|
-
return TangoResponse.error({
|
|
365
|
-
code: 'unprocessable_entity',
|
|
366
|
-
message: 'Unprocessable Entity',
|
|
367
|
-
}, { ...init, status: 422 });
|
|
368
|
-
}
|
|
369
|
-
static tooManyRequests(detail, init) {
|
|
370
|
-
if (TangoError.isTangoError(detail) || TangoError.isProblemDetails(detail)) {
|
|
371
|
-
return TangoResponse.error(detail, { ...init, status: 429 });
|
|
372
|
-
}
|
|
373
|
-
if (typeof detail === 'string') {
|
|
374
|
-
return TangoResponse.error({
|
|
375
|
-
code: 'too_many_requests',
|
|
376
|
-
message: detail,
|
|
377
|
-
}, { ...init, status: 429 });
|
|
378
|
-
}
|
|
379
|
-
return TangoResponse.error({
|
|
380
|
-
code: 'too_many_requests',
|
|
381
|
-
message: 'Too Many Requests',
|
|
382
|
-
}, { ...init, status: 429 });
|
|
383
|
-
}
|
|
384
|
-
static problem(problem, init) {
|
|
385
|
-
let status = init?.status ?? 500;
|
|
386
|
-
const headers = new TangoHeaders(init?.headers);
|
|
387
|
-
if (!headers.has('Content-Type')) {
|
|
388
|
-
headers.set('Content-Type', 'application/problem+json; charset=utf-8');
|
|
389
|
-
}
|
|
390
|
-
let code = 'error';
|
|
391
|
-
let message = 'An error occurred';
|
|
392
|
-
let details = undefined;
|
|
393
|
-
let fields;
|
|
394
|
-
if (TangoError.isTangoError(problem)) {
|
|
395
|
-
const envelope = problem.toErrorEnvelope();
|
|
396
|
-
status = problem.status;
|
|
397
|
-
code = envelope.error.code;
|
|
398
|
-
message = envelope.error.message;
|
|
399
|
-
details = envelope.error.details;
|
|
400
|
-
fields = envelope.error.fields;
|
|
401
|
-
}
|
|
402
|
-
else if (TangoError.isProblemDetails(problem)) {
|
|
403
|
-
code = problem.code;
|
|
404
|
-
message = problem.message;
|
|
405
|
-
details = problem.details;
|
|
406
|
-
fields = problem.fields;
|
|
407
|
-
}
|
|
408
|
-
else if (typeof problem === 'string') {
|
|
409
|
-
message = problem;
|
|
410
|
-
}
|
|
411
|
-
else if (problem && typeof problem === 'object') {
|
|
412
|
-
const extracted = problem;
|
|
413
|
-
if (typeof extracted.code === 'string')
|
|
414
|
-
code = extracted.code;
|
|
415
|
-
if (typeof extracted.message === 'string')
|
|
416
|
-
message = extracted.message;
|
|
417
|
-
details = extracted.details;
|
|
418
|
-
fields = extracted.fields;
|
|
419
|
-
}
|
|
420
|
-
const envelope = {
|
|
421
|
-
error: {
|
|
422
|
-
code,
|
|
423
|
-
message,
|
|
424
|
-
...(details !== undefined ? { details } : {}),
|
|
425
|
-
...(fields ? { fields } : {}),
|
|
426
|
-
},
|
|
427
|
-
};
|
|
428
|
-
const body = JSON.stringify(envelope);
|
|
429
|
-
if (!headers.has('Content-Length')) {
|
|
430
|
-
headers.set('Content-Length', new TextEncoder().encode(body).length.toString());
|
|
431
|
-
}
|
|
432
|
-
return new TangoResponse({
|
|
433
|
-
...init,
|
|
434
|
-
headers,
|
|
435
|
-
status,
|
|
436
|
-
body,
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
clone() {
|
|
440
|
-
if (this.bodyUsed) {
|
|
441
|
-
throw new TypeError('Body has already been used');
|
|
442
|
-
}
|
|
443
|
-
const clonedBody = this.tangoBody.clone();
|
|
444
|
-
return new TangoResponse({
|
|
445
|
-
body: clonedBody.bodySource,
|
|
446
|
-
headers: this.headers.clone(),
|
|
447
|
-
ok: this.okValue,
|
|
448
|
-
redirected: this.redirected,
|
|
449
|
-
status: this.status,
|
|
450
|
-
statusText: this.statusText,
|
|
451
|
-
type: this.type,
|
|
452
|
-
url: this.url,
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
//---- Spec Response interface fields and methods ----
|
|
456
|
-
async arrayBuffer() {
|
|
457
|
-
return this.tangoBody.arrayBuffer();
|
|
458
|
-
}
|
|
459
|
-
async blob() {
|
|
460
|
-
return this.tangoBody.blob();
|
|
461
|
-
}
|
|
462
|
-
async bytes() {
|
|
463
|
-
return this.tangoBody.bytes();
|
|
464
|
-
}
|
|
465
|
-
async formData() {
|
|
466
|
-
return this.tangoBody.formData();
|
|
467
|
-
}
|
|
468
|
-
async json() {
|
|
469
|
-
return this.tangoBody.json();
|
|
470
|
-
}
|
|
471
|
-
async text() {
|
|
472
|
-
return this.tangoBody.text();
|
|
473
|
-
}
|
|
474
|
-
/**
|
|
475
|
-
* Returns a response for serving a file.
|
|
476
|
-
*/
|
|
477
|
-
static file(file, opts) {
|
|
478
|
-
const headers = new TangoHeaders(opts?.init?.headers ?? {});
|
|
479
|
-
if (opts?.filename) {
|
|
480
|
-
// Serve as an attachment by default, but not 'download'
|
|
481
|
-
headers.setContentDispositionInline(opts.filename);
|
|
482
|
-
}
|
|
483
|
-
if (opts?.contentType && !headers.has('Content-Type')) {
|
|
484
|
-
headers.set('Content-Type', opts.contentType);
|
|
485
|
-
}
|
|
486
|
-
else if (!headers.has('Content-Type')) {
|
|
487
|
-
headers.setContentTypeByFile(file, opts?.filename);
|
|
488
|
-
}
|
|
489
|
-
if (!headers.has('Content-Length')) {
|
|
490
|
-
headers.setContentLengthFromBody(file);
|
|
491
|
-
}
|
|
492
|
-
return new TangoResponse({
|
|
493
|
-
...opts?.init,
|
|
494
|
-
body: file,
|
|
495
|
-
headers,
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
|
-
/**
|
|
499
|
-
* Returns a response that prompts the user to download the file.
|
|
500
|
-
*/
|
|
501
|
-
static download(file, opts) {
|
|
502
|
-
const headers = new TangoHeaders(opts?.init?.headers ?? {});
|
|
503
|
-
if (opts?.filename) {
|
|
504
|
-
headers.setContentDispositionAttachment(opts.filename);
|
|
505
|
-
}
|
|
506
|
-
else {
|
|
507
|
-
headers.set('Content-Disposition', 'attachment');
|
|
508
|
-
}
|
|
509
|
-
if (opts?.contentType && !headers.has('Content-Type')) {
|
|
510
|
-
headers.set('Content-Type', opts.contentType);
|
|
511
|
-
}
|
|
512
|
-
else if (!headers.has('Content-Type')) {
|
|
513
|
-
headers.setContentTypeByFile(file, opts?.filename);
|
|
514
|
-
}
|
|
515
|
-
if (!headers.has('Content-Length')) {
|
|
516
|
-
headers.setContentLengthFromBody(file);
|
|
517
|
-
}
|
|
518
|
-
return new TangoResponse({
|
|
519
|
-
...opts?.init,
|
|
520
|
-
body: file,
|
|
521
|
-
headers,
|
|
522
|
-
});
|
|
523
|
-
}
|
|
524
|
-
/**
|
|
525
|
-
* Returns a plain object debug representation of this response: { status, headers: { ... }, bodyType: ... }.
|
|
526
|
-
* Intended for testing and debug tooling.
|
|
527
|
-
*/
|
|
528
|
-
toJSON() {
|
|
529
|
-
return {
|
|
530
|
-
status: this.status,
|
|
531
|
-
headers: Object.fromEntries(this.headers.entries()),
|
|
532
|
-
bodyType: this.tangoBody ? this.tangoBody.bodyType : typeof this.bodySource,
|
|
533
|
-
};
|
|
534
|
-
}
|
|
535
|
-
/**
|
|
536
|
-
* Returns the original body for test/debug purposes *without* consuming the stream.
|
|
537
|
-
*
|
|
538
|
-
* @throws {Error} if called in a production environment. Provided only for test or debug.
|
|
539
|
-
* @remarks
|
|
540
|
-
* This method gives direct access to the original body as provided to the constructor,
|
|
541
|
-
* before consumption. It is primarily intended for use in test code, introspection, or
|
|
542
|
-
* advanced debug tools. **Do not rely on this in production.**
|
|
543
|
-
* In production (`process.env.NODE_ENV === 'production'`), using this method will throw.
|
|
544
|
-
*
|
|
545
|
-
* This method is _not_ part of the web Response interface, and is intentionally private/internal.
|
|
546
|
-
*/
|
|
547
|
-
__peekBodyForTestOnly() {
|
|
548
|
-
// Strong guard against accidental shipping in production
|
|
549
|
-
if (typeof process !== 'undefined' &&
|
|
550
|
-
process.env &&
|
|
551
|
-
(process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'prod')) {
|
|
552
|
-
throw new Error('peekBody() is not available in production builds. For test/debug use only.');
|
|
553
|
-
}
|
|
554
|
-
return this.tangoBody.bodySource;
|
|
555
|
-
}
|
|
556
|
-
}
|