@web-ts-toolkit/express-response-handler 0.31.0 → 0.31.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.
package/create-handler.js CHANGED
@@ -26,67 +26,340 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  mod
27
27
  ));
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/create-handler.ts
29
31
  var create_handler_exports = {};
30
32
  __export(create_handler_exports, {
31
33
  createHandler: () => createHandler
32
34
  });
33
35
  module.exports = __toCommonJS(create_handler_exports);
34
36
  var import_assert = __toESM(require("assert"));
37
+ var import_utils3 = require("@web-ts-toolkit/utils");
38
+
39
+ // src/responses/csv.ts
40
+ var import_format = require("@fast-csv/format");
35
41
  var import_utils = require("@web-ts-toolkit/utils");
36
- var import_csv = require("./responses/csv");
37
- var import_responses = require("./responses");
38
- var import_http_response = require("./http-response");
39
- var import_error_format = require("./error-format");
40
- var import_error_formats = require("./error-formats");
41
- const promisify = (fn) => (value) => Promise.resolve().then(() => fn(value));
42
- const invokePostHook = (hook, value) => {
42
+ var csvResponseBrand = /* @__PURE__ */ Symbol.for("@web-ts-toolkit/express-response-handler.csv-response");
43
+ var isCSVResponse = (value) => {
44
+ if (value instanceof CSVResponse) {
45
+ return true;
46
+ }
47
+ if (typeof value !== "object" || value === null) {
48
+ return false;
49
+ }
50
+ const candidate = value;
51
+ return candidate[csvResponseBrand] === true && typeof candidate.streamCsv === "function";
52
+ };
53
+ var _a;
54
+ _a = csvResponseBrand;
55
+ var CSVResponse = class {
56
+ constructor(dataset = [], options = {}) {
57
+ this[_a] = true;
58
+ this.dataset = (0, import_utils.castArray)(dataset);
59
+ this.filename = options.filename || "download.csv";
60
+ this.processor = options.processor || ((value) => value);
61
+ if ((0, import_utils.isBoolean)(options.headers)) {
62
+ this.headers = options.headers;
63
+ } else if (this.dataset.length > 0) {
64
+ this.headers = (0, import_utils.isPlainObject)(this.dataset[0]);
65
+ }
66
+ }
67
+ streamCsv(res) {
68
+ const stream = (0, import_format.format)({ headers: this.headers });
69
+ res.set("Content-Type", "text/csv");
70
+ res.set("Content-Disposition", `attachment;filename=${this.filename}`);
71
+ stream.pipe(res);
72
+ stream.on("error", (error) => {
73
+ if (res.destroy) {
74
+ res.destroy(error);
75
+ return;
76
+ }
77
+ res.end();
78
+ });
79
+ stream.on("end", () => res.end());
80
+ this.dataset.forEach((value) => stream.write(this.processor(value)));
81
+ stream.end();
82
+ }
83
+ };
84
+
85
+ // src/responses/index.ts
86
+ var responseBrand = /* @__PURE__ */ Symbol.for("@web-ts-toolkit/express-response-handler.response");
87
+ var isResponse = (value) => {
88
+ if (value instanceof Response) {
89
+ return true;
90
+ }
91
+ if (typeof value !== "object" || value === null) {
92
+ return false;
93
+ }
94
+ const candidate = value;
95
+ return candidate[responseBrand] === true && typeof candidate.statusCode === "number" && "data" in candidate;
96
+ };
97
+ var _a2;
98
+ _a2 = responseBrand;
99
+ var Response = class {
100
+ constructor(statusCode = 200, data) {
101
+ this[_a2] = true;
102
+ this.statusCode = statusCode;
103
+ this.data = data;
104
+ }
105
+ };
106
+
107
+ // src/responses/success.ts
108
+ var OK = class extends Response {
109
+ constructor(data) {
110
+ super(200, data);
111
+ }
112
+ };
113
+ var Created = class extends Response {
114
+ constructor(data) {
115
+ super(201, data);
116
+ }
117
+ };
118
+ var Accepted = class extends Response {
119
+ constructor(data) {
120
+ super(202, data);
121
+ }
122
+ };
123
+ var NonAuthoritativeInfo = class extends Response {
124
+ constructor(data) {
125
+ super(203, data);
126
+ }
127
+ };
128
+ var NoContent = class extends Response {
129
+ constructor() {
130
+ super(204, void 0);
131
+ }
132
+ };
133
+ var ResetContent = class extends Response {
134
+ constructor(data) {
135
+ super(205, data);
136
+ }
137
+ };
138
+ var PartialContent = class extends Response {
139
+ constructor(data) {
140
+ super(206, data);
141
+ }
142
+ };
143
+ var MultiStatus = class extends Response {
144
+ constructor(data) {
145
+ super(207, data);
146
+ }
147
+ };
148
+ var AlreadyReported = class extends Response {
149
+ constructor(data) {
150
+ super(208, data);
151
+ }
152
+ };
153
+ var IMUsed = class extends Response {
154
+ constructor(data) {
155
+ super(226, data);
156
+ }
157
+ };
158
+
159
+ // src/http-response.ts
160
+ var import_http_errors = require("@web-ts-toolkit/http-errors");
161
+ var HttpResponse = {
162
+ ok: (...args) => new OK(...args),
163
+ created: (...args) => new Created(...args),
164
+ accepted: (...args) => new Accepted(...args),
165
+ nonAuthoritativeInfo: (...args) => new NonAuthoritativeInfo(...args),
166
+ noContent: (...args) => new NoContent(...args),
167
+ resetContent: (...args) => new ResetContent(...args),
168
+ partialContent: (...args) => new PartialContent(...args),
169
+ multiStatus: (...args) => new MultiStatus(...args),
170
+ alreadyReported: (...args) => new AlreadyReported(...args),
171
+ imUsed: (...args) => new IMUsed(...args),
172
+ badRequest: (...args) => new import_http_errors.BadRequestError(...args),
173
+ unauthorized: (...args) => new import_http_errors.UnauthorizedError(...args),
174
+ forbidden: (...args) => new import_http_errors.ForbiddenError(...args),
175
+ notFound: (...args) => new import_http_errors.NotFoundError(...args),
176
+ methodNotAllowed: (...args) => new import_http_errors.MethodNotAllowedError(...args),
177
+ notAcceptable: (...args) => new import_http_errors.NotAcceptableError(...args),
178
+ proxyAuthRequired: (...args) => new import_http_errors.ProxyAuthRequiredError(...args),
179
+ requestTimeout: (...args) => new import_http_errors.RequestTimeoutError(...args),
180
+ conflict: (...args) => new import_http_errors.ConflictError(...args),
181
+ gone: (...args) => new import_http_errors.GoneError(...args),
182
+ lengthRequired: (...args) => new import_http_errors.LengthRequiredError(...args),
183
+ preconditionFailed: (...args) => new import_http_errors.PreconditionFailedError(...args),
184
+ payloadTooLarge: (...args) => new import_http_errors.PayloadTooLargeError(...args),
185
+ uriTooLong: (...args) => new import_http_errors.UriTooLongError(...args),
186
+ unsupportedMediaType: (...args) => new import_http_errors.UnsupportedMediaTypeError(...args),
187
+ requestedRangeNotSatisfiable: (...args) => new import_http_errors.RequestedRangeNotSatisfiableError(...args),
188
+ expectationFailed: (...args) => new import_http_errors.ExpectationFailedError(...args),
189
+ teapot: (...args) => new import_http_errors.TeapotError(...args),
190
+ misdirectedRequest: (...args) => new import_http_errors.MisdirectedRequestError(...args),
191
+ unprocessableEntity: (...args) => new import_http_errors.UnprocessableEntityError(...args),
192
+ locked: (...args) => new import_http_errors.LockedError(...args),
193
+ failedDependency: (...args) => new import_http_errors.FailedDependencyError(...args),
194
+ upgradeRequired: (...args) => new import_http_errors.UpgradeRequiredError(...args),
195
+ preconditionRequired: (...args) => new import_http_errors.PreconditionRequiredError(...args),
196
+ tooManyRequests: (...args) => new import_http_errors.TooManyRequestsError(...args),
197
+ requestHeaderFieldsTooLarge: (...args) => new import_http_errors.RequestHeaderFieldsTooLargeError(...args),
198
+ unavailableForLegalReasons: (...args) => new import_http_errors.UnavailableForLegalReasonsError(...args),
199
+ json: (...args) => new OK(...args),
200
+ csv: (...args) => new CSVResponse(...args)
201
+ };
202
+
203
+ // src/error-format.ts
204
+ var import_http_errors2 = require("@web-ts-toolkit/http-errors");
205
+ var import_utils2 = require("@web-ts-toolkit/utils");
206
+ var toStatusCode = (status, code, fallbackStatusCode) => {
207
+ if (typeof status === "number") {
208
+ return status;
209
+ }
210
+ return typeof code === "number" ? code : fallbackStatusCode;
211
+ };
212
+ var toOptionalString = (value) => typeof value === "string" ? value : void 0;
213
+ var toMetadata = (value) => {
214
+ return (0, import_utils2.toStringRecord)(value);
215
+ };
216
+ var toArray = (value) => {
217
+ if (value === void 0 || value === null) {
218
+ return void 0;
219
+ }
220
+ return (0, import_utils2.isArray)(value) ? value : [value];
221
+ };
222
+ var toHttpErrorShape = (error, fallbackDomain) => ({
223
+ statusCode: error.statusCode ?? 500,
224
+ status: error.status,
225
+ message: error.message ?? "",
226
+ reason: error.reason,
227
+ domain: error.domain ?? fallbackDomain,
228
+ metadata: toMetadata(error.metadata),
229
+ details: toArray(error.details),
230
+ errors: error.errors,
231
+ type: error.type,
232
+ title: error.title,
233
+ instance: error.instance
234
+ });
235
+ var toProblemDetailsSource = (result) => {
236
+ if ((0, import_utils2.isPlainObject)(result) && (0, import_utils2.isPlainObject)(result.error)) {
237
+ return result.error;
238
+ }
239
+ return (0, import_utils2.isPlainObject)(result) ? result : void 0;
240
+ };
241
+ var defaultErrorMessageProvider = function(error) {
242
+ const errorLike = error;
243
+ return errorLike.message ?? errorLike._message ?? String(error);
244
+ };
245
+ var toSimpleErrorPayload = (result) => (0, import_utils2.isString)(result) ? { message: result } : { ...result };
246
+ var toStructuredHttpErrorPayload = (error, errorDomain) => (0, import_http_errors2.toAip193ErrorPayload)(toHttpErrorShape(error, errorDomain), errorDomain);
247
+ var toRfc9457HttpErrorPayload = (error, errorDomain) => (0, import_http_errors2.toRfc9457ErrorPayload)(toHttpErrorShape(error, errorDomain));
248
+ var toStructuredGenericErrorPayload = (result, errorDomain) => {
249
+ if ((0, import_utils2.isPlainObject)(result) && (0, import_utils2.isPlainObject)(result.error)) {
250
+ const error = result.error;
251
+ const statusCode = typeof error.code === "number" ? error.code : 422;
252
+ const status = typeof error.status === "string" ? error.status : (0, import_http_errors2.getCanonicalStatus)(statusCode);
253
+ const message = typeof error.message === "string" ? error.message : "";
254
+ const details = toArray(error.details);
255
+ return {
256
+ error: {
257
+ code: statusCode,
258
+ status,
259
+ message,
260
+ ...details ? { details } : {}
261
+ }
262
+ };
263
+ }
264
+ if ((0, import_utils2.isPlainObject)(result)) {
265
+ const statusCode = typeof result.code === "number" ? result.code : 422;
266
+ const status = typeof result.status === "string" ? result.status : (0, import_http_errors2.getCanonicalStatus)(statusCode);
267
+ const message = typeof result.message === "string" ? result.message : "";
268
+ const details = toArray(result.details) || [(0, import_http_errors2.createAip193ErrorInfoDetail)(status, errorDomain)];
269
+ return {
270
+ error: {
271
+ code: statusCode,
272
+ status,
273
+ message,
274
+ ...details ? { details } : {}
275
+ }
276
+ };
277
+ }
278
+ return {
279
+ error: {
280
+ code: 422,
281
+ status: (0, import_http_errors2.getCanonicalStatus)(422),
282
+ message: String(result),
283
+ details: [(0, import_http_errors2.createAip193ErrorInfoDetail)((0, import_http_errors2.getCanonicalStatus)(422), errorDomain)]
284
+ }
285
+ };
286
+ };
287
+ var toRfc9457GenericErrorPayload = (result) => {
288
+ const problem = toProblemDetailsSource(result);
289
+ if (problem) {
290
+ const statusCode = toStatusCode(problem.status, problem.code, 422);
291
+ return (0, import_http_errors2.toRfc9457ErrorPayload)({
292
+ statusCode,
293
+ message: toOptionalString(problem.detail) ?? toOptionalString(problem.message) ?? "",
294
+ errors: problem.errors,
295
+ type: toOptionalString(problem.type),
296
+ title: toOptionalString(problem.title),
297
+ instance: toOptionalString(problem.instance)
298
+ });
299
+ }
300
+ return (0, import_http_errors2.toRfc9457ErrorPayload)({
301
+ statusCode: 422,
302
+ message: String(result)
303
+ });
304
+ };
305
+
306
+ // src/error-formats.ts
307
+ var ErrorFormats = {
308
+ simple: "simple",
309
+ aip193: "aip193",
310
+ rfc9457: "rfc9457"
311
+ };
312
+
313
+ // src/create-handler.ts
314
+ var promisify = (fn) => (value) => Promise.resolve().then(() => fn(value));
315
+ var invokePostHook = (hook, value) => {
43
316
  void hook(value).catch(() => void 0);
44
317
  };
45
- const RFC_9457_CONTENT_TYPE = "application/problem+json";
46
- const shouldSkipResponse = (res, event) => res.headersSent || event.canceled;
47
- const sendProblemJson = (res, statusCode, payload, contentType) => {
318
+ var RFC_9457_CONTENT_TYPE = "application/problem+json";
319
+ var shouldSkipResponse = (res, event) => res.headersSent || event.canceled;
320
+ var sendProblemJson = (res, statusCode, payload, contentType) => {
48
321
  res.status(statusCode);
49
322
  res.set("Content-Type", contentType);
50
323
  res.send(payload);
51
324
  };
52
- const sendHttpErrorByFormat = {
53
- [import_error_formats.ErrorFormats.simple]: (res, error) => {
325
+ var sendHttpErrorByFormat = {
326
+ [ErrorFormats.simple]: (res, error) => {
54
327
  const payload = { message: error.message ?? "" };
55
328
  if (error.errors !== void 0) {
56
329
  payload.errors = error.errors;
57
330
  }
58
331
  res.status(error.statusCode ?? 500).send(payload);
59
332
  },
60
- [import_error_formats.ErrorFormats.aip193]: (res, error, domain) => {
61
- res.status(error.statusCode ?? 500).send((0, import_error_format.toStructuredHttpErrorPayload)(error, domain));
333
+ [ErrorFormats.aip193]: (res, error, domain) => {
334
+ res.status(error.statusCode ?? 500).send(toStructuredHttpErrorPayload(error, domain));
62
335
  },
63
- [import_error_formats.ErrorFormats.rfc9457]: (res, error, domain) => {
64
- sendProblemJson(res, error.statusCode ?? 500, (0, import_error_format.toRfc9457HttpErrorPayload)(error, domain), RFC_9457_CONTENT_TYPE);
336
+ [ErrorFormats.rfc9457]: (res, error, domain) => {
337
+ sendProblemJson(res, error.statusCode ?? 500, toRfc9457HttpErrorPayload(error, domain), RFC_9457_CONTENT_TYPE);
65
338
  }
66
339
  };
67
- const sendGenericErrorByFormat = {
68
- [import_error_formats.ErrorFormats.simple]: (res, result) => {
69
- res.status(422).send((0, import_error_format.toSimpleErrorPayload)(result));
340
+ var sendGenericErrorByFormat = {
341
+ [ErrorFormats.simple]: (res, result) => {
342
+ res.status(422).send(toSimpleErrorPayload(result));
70
343
  },
71
- [import_error_formats.ErrorFormats.aip193]: (res, result, domain) => {
72
- const payload = (0, import_error_format.toStructuredGenericErrorPayload)(result, domain);
344
+ [ErrorFormats.aip193]: (res, result, domain) => {
345
+ const payload = toStructuredGenericErrorPayload(result, domain);
73
346
  res.status(payload.error.code).send(payload);
74
347
  },
75
- [import_error_formats.ErrorFormats.rfc9457]: (res, result) => {
76
- const payload = (0, import_error_format.toRfc9457GenericErrorPayload)(result);
348
+ [ErrorFormats.rfc9457]: (res, result) => {
349
+ const payload = toRfc9457GenericErrorPayload(result);
77
350
  sendProblemJson(res, payload.status ?? 422, payload, RFC_9457_CONTENT_TYPE);
78
351
  }
79
352
  };
80
- const assertMiddleware = (fn) => {
81
- import_assert.default.ok((0, import_utils.isFunction)(fn), "middleware handler must be a function");
353
+ var assertMiddleware = (fn) => {
354
+ import_assert.default.ok((0, import_utils3.isFunction)(fn), "middleware handler must be a function");
82
355
  };
83
- const normalizeMiddlewareList = (fns) => {
356
+ var normalizeMiddlewareList = (fns) => {
84
357
  import_assert.default.ok(fns.length > 0, "at least one middleware handler is required");
85
358
  if (fns.length > 1) {
86
359
  fns.forEach(assertMiddleware);
87
360
  return fns;
88
361
  }
89
- if ((0, import_utils.isArray)(fns[0])) {
362
+ if ((0, import_utils3.isArray)(fns[0])) {
90
363
  import_assert.default.ok(fns[0].length > 0, "at least one middleware handler is required");
91
364
  fns[0].forEach(assertMiddleware);
92
365
  return fns[0];
@@ -95,10 +368,10 @@ const normalizeMiddlewareList = (fns) => {
95
368
  return [fns[0]];
96
369
  };
97
370
  function createHandler(options = {}) {
98
- const errorFormat = options.errorFormat ?? import_error_formats.ErrorFormats.simple;
371
+ const errorFormat = options.errorFormat ?? ErrorFormats.simple;
99
372
  const errorDomain = options.errorDomain ?? "express-response-handler";
100
373
  const rfc9457ContentType = options.rfc9457ContentType ?? RFC_9457_CONTENT_TYPE;
101
- let errorMessageProvider = import_error_format.defaultErrorMessageProvider;
374
+ let errorMessageProvider = defaultErrorMessageProvider;
102
375
  let preJson = null;
103
376
  let postJson = null;
104
377
  let preError = null;
@@ -113,7 +386,7 @@ function createHandler(options = {}) {
113
386
  rebuild();
114
387
  return;
115
388
  }
116
- import_assert.default.ok((0, import_utils.isFunction)(fn), `${name} hook must be a function`);
389
+ import_assert.default.ok((0, import_utils3.isFunction)(fn), `${name} hook must be a function`);
117
390
  setState(fn, promisify(fn));
118
391
  rebuild();
119
392
  };
@@ -124,11 +397,11 @@ function createHandler(options = {}) {
124
397
  if (data === void 0) {
125
398
  return;
126
399
  }
127
- if (data instanceof import_responses.Response) {
400
+ if (isResponse(data)) {
128
401
  res.status(data.statusCode).json(data.data);
129
402
  return;
130
403
  }
131
- if (data instanceof import_csv.CSVResponse) {
404
+ if (isCSVResponse(data)) {
132
405
  data.streamCsv(res);
133
406
  return;
134
407
  }
@@ -140,11 +413,11 @@ function createHandler(options = {}) {
140
413
  }
141
414
  const error = err;
142
415
  if (error.statusCode) {
143
- if (errorFormat === import_error_formats.ErrorFormats.rfc9457) {
416
+ if (errorFormat === ErrorFormats.rfc9457) {
144
417
  sendProblemJson(
145
418
  res,
146
419
  error.statusCode ?? 500,
147
- (0, import_error_format.toRfc9457HttpErrorPayload)(error, errorDomain),
420
+ toRfc9457HttpErrorPayload(error, errorDomain),
148
421
  rfc9457ContentType
149
422
  );
150
423
  return;
@@ -153,8 +426,8 @@ function createHandler(options = {}) {
153
426
  return;
154
427
  }
155
428
  const result = errorMessageProvider(err);
156
- if (errorFormat === import_error_formats.ErrorFormats.rfc9457) {
157
- const payload = (0, import_error_format.toRfc9457GenericErrorPayload)(result);
429
+ if (errorFormat === ErrorFormats.rfc9457) {
430
+ const payload = toRfc9457GenericErrorPayload(result);
158
431
  sendProblemJson(res, payload.status ?? 422, payload, rfc9457ContentType);
159
432
  return;
160
433
  }
@@ -222,7 +495,7 @@ function createHandler(options = {}) {
222
495
  sendError(res, event.nextError, event);
223
496
  return;
224
497
  }
225
- if ((0, import_utils.isPromise)(result)) {
498
+ if ((0, import_utils3.isPromise)(result)) {
226
499
  handlePromise(res, result, event);
227
500
  return;
228
501
  }
@@ -264,13 +537,13 @@ function createHandler(options = {}) {
264
537
  handleResponse,
265
538
  handleResult,
266
539
  handlePromise,
267
- HttpResponse: import_http_response.HttpResponse,
540
+ HttpResponse,
268
541
  createHandler,
269
542
  get errorMessageProvider() {
270
543
  return errorMessageProvider;
271
544
  },
272
545
  set errorMessageProvider(fn) {
273
- import_assert.default.ok((0, import_utils.isFunction)(fn), "error message provider must be a function");
546
+ import_assert.default.ok((0, import_utils3.isFunction)(fn), "error message provider must be a function");
274
547
  errorMessageProvider = fn;
275
548
  },
276
549
  get preJson() {