@opra/core 1.0.0-alpha.23 → 1.0.0-alpha.24

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/cjs/constants.js CHANGED
@@ -1,5 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.kAssetCache = exports.kHandler = void 0;
4
- exports.kHandler = Symbol.for('kHandler');
3
+ exports.kAssetCache = void 0;
5
4
  exports.kAssetCache = Symbol.for('kAssetCache');
@@ -8,6 +8,7 @@ const strict_typed_events_1 = require("strict-typed-events");
8
8
  class ExecutionContext extends strict_typed_events_1.AsyncEventEmitter {
9
9
  constructor(init) {
10
10
  super();
11
+ this.errors = [];
11
12
  this.document = init.document;
12
13
  this.protocol = init.protocol;
13
14
  this.platform = init.platform;
@@ -5,11 +5,11 @@ const tslib_1 = require("tslib");
5
5
  const common_1 = require("@opra/common");
6
6
  const express_1 = require("express");
7
7
  const nodePath = tslib_1.__importStar(require("path"));
8
- const constants_js_1 = require("../constants.js");
9
8
  const http_adapter_js_1 = require("./http-adapter.js");
10
9
  const http_context_js_1 = require("./http-context.js");
11
10
  const http_incoming_interface_js_1 = require("./interfaces/http-incoming.interface.js");
12
11
  const http_outgoing_interface_js_1 = require("./interfaces/http-outgoing.interface.js");
12
+ const wrap_exception_1 = require("./utils/wrap-exception");
13
13
  class ExpressAdapter extends http_adapter_js_1.HttpAdapter {
14
14
  constructor(app, document, options) {
15
15
  super(document, options);
@@ -40,7 +40,8 @@ class ExpressAdapter extends http_adapter_js_1.HttpAdapter {
40
40
  await resource.onShutdown.call(instance, resource);
41
41
  }
42
42
  catch (e) {
43
- this.logger.error(e);
43
+ if (this.listenerCount('error'))
44
+ this.emit('error', (0, wrap_exception_1.wrapException)(e));
44
45
  }
45
46
  }
46
47
  }
@@ -80,7 +81,7 @@ class ExpressAdapter extends http_adapter_js_1.HttpAdapter {
80
81
  /** Add an endpoint that returns document schema */
81
82
  router.get('/\\$schema', (_req, _res, next) => {
82
83
  const context = createContext(_req, _res);
83
- this[constants_js_1.kHandler].sendDocumentSchema(context).catch(next);
84
+ this.handler.sendDocumentSchema(context).catch(next);
84
85
  });
85
86
  /** Add operation endpoints */
86
87
  if (this.api.controllers.size) {
@@ -100,13 +101,13 @@ class ExpressAdapter extends http_adapter_js_1.HttpAdapter {
100
101
  operation,
101
102
  operationHandler,
102
103
  });
103
- this[constants_js_1.kHandler]
104
+ this.handler
104
105
  .handleRequest(context)
105
106
  .then(() => {
106
107
  if (!_res.headersSent)
107
108
  _next();
108
109
  })
109
- .catch((e) => this.logger.fatal(e));
110
+ .catch((e) => this.emit('error', e));
110
111
  });
111
112
  }
112
113
  if (controller.controllers.size) {
@@ -119,19 +120,15 @@ class ExpressAdapter extends http_adapter_js_1.HttpAdapter {
119
120
  }
120
121
  /** Add an endpoint that returns 404 error at last */
121
122
  router.use('*', (_req, _res, next) => {
122
- const res = http_outgoing_interface_js_1.HttpOutgoing.from(_res);
123
- // const url = new URL(_req.originalUrl, '')
124
- this[constants_js_1.kHandler]
125
- .sendErrorResponse(res, [
126
- new common_1.NotFoundError({
127
- message: `No endpoint found for [${_req.method}]${_req.baseUrl}`,
128
- details: {
129
- path: _req.baseUrl,
130
- method: _req.method,
131
- },
132
- }),
133
- ])
134
- .catch(next);
123
+ const context = createContext(_req, _res);
124
+ context.errors.push(new common_1.NotFoundError({
125
+ message: `No endpoint found at [${_req.method}]${_req.baseUrl}`,
126
+ details: {
127
+ path: _req.baseUrl,
128
+ method: _req.method,
129
+ },
130
+ }));
131
+ this.handler.sendResponse(context).catch(next);
135
132
  });
136
133
  }
137
134
  _createControllers(controller) {
@@ -2,9 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.HttpAdapter = void 0;
4
4
  const common_1 = require("@opra/common");
5
- const constants_js_1 = require("../constants.js");
6
5
  const platform_adapter_js_1 = require("../platform-adapter.js");
7
- const http_handler_js_1 = require("./impl/http-handler.js");
6
+ const http_handler_js_1 = require("./http-handler.js");
8
7
  /**
9
8
  *
10
9
  * @class HttpAdapter
@@ -15,10 +14,8 @@ class HttpAdapter extends platform_adapter_js_1.PlatformAdapter {
15
14
  this.protocol = 'http';
16
15
  if (!(document.api instanceof common_1.HttpApi))
17
16
  throw new TypeError(`The document does not expose an HTTP Api`);
18
- this[constants_js_1.kHandler] = new http_handler_js_1.HttpHandler(this);
17
+ this.handler = new http_handler_js_1.HttpHandler(this);
19
18
  this.interceptors = [...(options?.interceptors || [])];
20
- if (options?.onRequest)
21
- this.on('request', options.onRequest);
22
19
  }
23
20
  get api() {
24
21
  return this.document.api;
@@ -29,6 +29,10 @@ class HttpContext extends execution_context_js_1.ExecutionContext {
29
29
  this.pathParams = init.pathParams || {};
30
30
  this.queryParams = init.queryParams || {};
31
31
  this._body = init.body;
32
+ this.on('finish', () => {
33
+ if (this._multipartReader)
34
+ this._multipartReader.purge().catch(() => undefined);
35
+ });
32
36
  }
33
37
  get isMultipart() {
34
38
  return !!this.request.is('multipart');
@@ -38,21 +42,21 @@ class HttpContext extends execution_context_js_1.ExecutionContext {
38
42
  throw new common_1.InternalServerError('Request content is not a multipart content');
39
43
  if (this._multipartReader)
40
44
  return this._multipartReader;
41
- const { request, mediaType } = this;
45
+ const { mediaType } = this;
42
46
  if (mediaType?.contentType) {
43
47
  const arr = Array.isArray(mediaType.contentType) ? mediaType.contentType : [mediaType.contentType];
44
48
  const contentType = arr.find(ct => type_is_1.default.is(ct, ['multipart']));
45
49
  if (!contentType)
46
50
  throw new common_1.NotAcceptableError('This endpoint does not accept multipart requests');
47
51
  }
48
- const reader = new multipart_reader_js_1.MultipartReader(request, {
49
- maxFields: mediaType?.maxFields,
50
- maxFieldsSize: mediaType?.maxFieldsSize,
51
- maxFiles: mediaType?.maxFiles,
52
- maxFileSize: mediaType?.maxFileSize,
53
- maxTotalFileSize: mediaType?.maxTotalFileSize,
54
- minFileSize: mediaType?.minFileSize,
55
- });
52
+ const reader = new multipart_reader_js_1.MultipartReader(this, {
53
+ limits: {
54
+ fields: mediaType?.maxFields,
55
+ fieldSize: mediaType?.maxFieldsSize,
56
+ files: mediaType?.maxFiles,
57
+ fileSize: mediaType?.maxFileSize,
58
+ },
59
+ }, mediaType);
56
60
  this._multipartReader = reader;
57
61
  return reader;
58
62
  }
@@ -70,7 +74,7 @@ class HttpContext extends execution_context_js_1.ExecutionContext {
70
74
  if (mediaType && multipartFields?.length) {
71
75
  const fieldsFound = new Map();
72
76
  for (const item of parts) {
73
- const field = mediaType.findMultipartField(item.fieldName, item.type);
77
+ const field = mediaType.findMultipartField(item.field, item.kind);
74
78
  if (field) {
75
79
  fieldsFound.set(field, true);
76
80
  this._body.push(item);
@@ -9,15 +9,15 @@ const content_type_1 = require("content-type");
9
9
  const fast_tokenizer_1 = require("fast-tokenizer");
10
10
  const ts_gems_1 = require("ts-gems");
11
11
  const valgen_1 = require("valgen");
12
- const constants_js_1 = require("../../constants.js");
13
- const wrap_exception_js_1 = require("../utils/wrap-exception.js");
12
+ const constants_1 = require("../constants");
13
+ const wrap_exception_1 = require("./utils/wrap-exception");
14
14
  /**
15
15
  * @class HttpHandler
16
16
  */
17
17
  class HttpHandler {
18
18
  constructor(adapter) {
19
19
  this.adapter = adapter;
20
- this[constants_js_1.kAssetCache] = adapter[constants_js_1.kAssetCache];
20
+ this[constants_1.kAssetCache] = adapter[constants_1.kAssetCache];
21
21
  }
22
22
  /**
23
23
  * Main http request handler
@@ -42,7 +42,7 @@ class HttpHandler {
42
42
  throw e;
43
43
  if (e instanceof valgen_1.ValidationError) {
44
44
  throw new common_1.BadRequestError({
45
- message: (0, common_1.translate)('error:RESPONSE_VALIDATION,', 'Response validation failed'),
45
+ message: 'Response validation failed',
46
46
  code: 'RESPONSE_VALIDATION',
47
47
  details: e.issues,
48
48
  }, e);
@@ -68,19 +68,17 @@ class HttpHandler {
68
68
  let e = error;
69
69
  if (e instanceof valgen_1.ValidationError) {
70
70
  e = new common_1.InternalServerError({
71
- message: (0, common_1.translate)('error:RESPONSE_VALIDATION,', 'Response validation failed'),
71
+ message: 'Response validation failed',
72
72
  code: 'RESPONSE_VALIDATION',
73
73
  details: e.issues,
74
74
  }, e);
75
75
  }
76
76
  else
77
- e = (0, wrap_exception_js_1.wrapException)(e);
78
- response.status(e.statusCode || e.status || common_1.HttpStatusCode.INTERNAL_SERVER_ERROR);
79
- response.contentType(common_1.MimeTypes.opra_response_json);
80
- await this._sendResponse(context, new common_1.OperationResult({ errors: [e.toJSON()] })).finally(() => {
81
- if (!response.finished)
82
- response.end();
83
- });
77
+ e = (0, wrap_exception_1.wrapException)(e);
78
+ if (this.onError)
79
+ await this.onError(context, error);
80
+ context.errors.push(e);
81
+ await this.sendResponse(context);
84
82
  }
85
83
  finally {
86
84
  await context.emitAsync('finish');
@@ -121,10 +119,10 @@ class HttpHandler {
121
119
  };
122
120
  /** prepare decoders */
123
121
  const getDecoder = (prm) => {
124
- let decode = this[constants_js_1.kAssetCache].get(prm, 'decode');
122
+ let decode = this[constants_1.kAssetCache].get(prm, 'decode');
125
123
  if (!decode) {
126
124
  decode = prm.type?.generateCodec('decode', { ignoreReadonlyFields: true }) || valgen_1.vg.isAny();
127
- this[constants_js_1.kAssetCache].set(prm, 'decode', decode);
125
+ this[constants_1.kAssetCache].set(prm, 'decode', decode);
128
126
  }
129
127
  return decode;
130
128
  };
@@ -214,6 +212,7 @@ class HttpHandler {
214
212
  }
215
213
  }
216
214
  for (const prm of paramsLeft) {
215
+ key = String(prm.name);
217
216
  // Throw error for required parameters
218
217
  if (prm.required) {
219
218
  const decode = getDecoder(prm);
@@ -265,7 +264,7 @@ class HttpHandler {
265
264
  const responseValue = await context.operationHandler.call(context.controllerInstance, context);
266
265
  const { response } = context;
267
266
  if (!response.writableEnded) {
268
- await this._sendResponse(context, responseValue).finally(() => {
267
+ await this.sendResponse(context, responseValue).finally(() => {
269
268
  if (!response.writableEnded)
270
269
  response.end();
271
270
  });
@@ -277,23 +276,25 @@ class HttpHandler {
277
276
  * @param responseValue
278
277
  * @protected
279
278
  */
280
- async _sendResponse(context, responseValue) {
279
+ async sendResponse(context, responseValue) {
280
+ if (context.errors.length)
281
+ return this.sendErrorResponse(context, context.errors);
281
282
  const { response } = context;
282
283
  const { document } = this.adapter;
283
284
  const responseArgs = this._determineResponseArgs(context, responseValue);
284
285
  const { operationResponse, statusCode } = responseArgs;
285
286
  let { contentType, body } = responseArgs;
286
287
  const operationResultType = document.node.getDataType(common_1.OperationResult);
287
- let operationResultEncoder = this[constants_js_1.kAssetCache].get(operationResultType, 'encode');
288
+ let operationResultEncoder = this[constants_1.kAssetCache].get(operationResultType, 'encode');
288
289
  if (!operationResultEncoder) {
289
290
  operationResultEncoder = operationResultType.generateCodec('encode', { ignoreWriteonlyFields: true });
290
- this[constants_js_1.kAssetCache].set(operationResultType, 'encode', operationResultEncoder);
291
+ this[constants_1.kAssetCache].set(operationResultType, 'encode', operationResultEncoder);
291
292
  }
292
293
  /** Validate response */
293
294
  if (operationResponse?.type) {
294
295
  if (!(body == null && statusCode === common_1.HttpStatusCode.NO_CONTENT)) {
295
296
  /** Generate encoder */
296
- let encode = this[constants_js_1.kAssetCache].get(operationResponse, 'encode');
297
+ let encode = this[constants_1.kAssetCache].get(operationResponse, 'encode');
297
298
  if (!encode) {
298
299
  encode = operationResponse.type.generateCodec('encode', {
299
300
  partial: operationResponse.partial,
@@ -303,7 +304,7 @@ class HttpHandler {
303
304
  if (operationResponse) {
304
305
  if (operationResponse.isArray)
305
306
  encode = valgen_1.vg.isArray(encode);
306
- this[constants_js_1.kAssetCache].set(operationResponse, 'encode', encode);
307
+ this[constants_1.kAssetCache].set(operationResponse, 'encode', encode);
307
308
  }
308
309
  }
309
310
  /** Encode body */
@@ -368,6 +369,57 @@ class HttpHandler {
368
369
  x = String(body);
369
370
  response.end(x);
370
371
  }
372
+ async sendErrorResponse(context, errors) {
373
+ const { response } = context;
374
+ if (response.headersSent) {
375
+ response.end();
376
+ return;
377
+ }
378
+ errors = errors || context.errors;
379
+ const wrappedErrors = errors.map(wrap_exception_1.wrapException);
380
+ if (!wrappedErrors.length)
381
+ wrappedErrors.push(new common_1.InternalServerError());
382
+ // Sort errors from fatal to info
383
+ wrappedErrors.sort((a, b) => {
384
+ const i = common_1.IssueSeverity.Keys.indexOf(a.severity) - common_1.IssueSeverity.Keys.indexOf(b.severity);
385
+ if (i === 0)
386
+ return b.status - a.status;
387
+ return i;
388
+ });
389
+ context.errors = wrappedErrors;
390
+ let status = response.statusCode || 0;
391
+ if (!status || status < Number(common_1.HttpStatusCode.BAD_REQUEST)) {
392
+ status = wrappedErrors[0].status;
393
+ if (status < Number(common_1.HttpStatusCode.BAD_REQUEST))
394
+ status = common_1.HttpStatusCode.INTERNAL_SERVER_ERROR;
395
+ }
396
+ response.statusCode = status;
397
+ this.adapter.emitAsync('error', wrappedErrors[0], context).catch(() => undefined);
398
+ const { document } = this.adapter;
399
+ const dt = document.node.getComplexType('OperationResult');
400
+ let encode = this[constants_1.kAssetCache].get(dt, 'encode');
401
+ if (!encode) {
402
+ encode = dt.generateCodec('encode', { ignoreWriteonlyFields: true });
403
+ this[constants_1.kAssetCache].set(dt, 'encode', encode);
404
+ }
405
+ const { i18n } = this.adapter;
406
+ const bodyObject = new common_1.OperationResult({
407
+ errors: wrappedErrors.map(x => {
408
+ const o = x.toJSON();
409
+ if (!(process.env.NODE_ENV === 'dev' || process.env.NODE_ENV === 'development'))
410
+ delete o.stack;
411
+ return i18n.deep(o);
412
+ }),
413
+ });
414
+ const body = encode(bodyObject);
415
+ response.setHeader(common_1.HttpHeaderCodes.Content_Type, common_1.MimeTypes.opra_response_json + '; charset=utf-8');
416
+ response.setHeader(common_1.HttpHeaderCodes.Cache_Control, 'no-cache');
417
+ response.setHeader(common_1.HttpHeaderCodes.Pragma, 'no-cache');
418
+ response.setHeader(common_1.HttpHeaderCodes.Expires, '-1');
419
+ response.setHeader(common_1.HttpHeaderCodes.X_Opra_Version, common_1.OpraSchema.SpecVersion);
420
+ response.send(JSON.stringify(body));
421
+ response.end();
422
+ }
371
423
  /**
372
424
  *
373
425
  * @param context
@@ -390,7 +442,7 @@ class HttpHandler {
390
442
  }
391
443
  let operationResponse;
392
444
  const cacheKey = `HttpOperationResponse:${statusCode}${contentType ? ':' + contentType : ''}`;
393
- let responseArgs = this[constants_js_1.kAssetCache].get(response, cacheKey);
445
+ let responseArgs = this[constants_1.kAssetCache].get(response, cacheKey);
394
446
  if (!responseArgs) {
395
447
  responseArgs = { statusCode, contentType };
396
448
  if (operation.responses.length) {
@@ -436,7 +488,7 @@ class HttpHandler {
436
488
  }
437
489
  if (!hasBody)
438
490
  delete responseArgs.contentType;
439
- this[constants_js_1.kAssetCache].set(response, cacheKey, { ...responseArgs });
491
+ this[constants_1.kAssetCache].set(response, cacheKey, { ...responseArgs });
440
492
  }
441
493
  /** Fix response value according to composition */
442
494
  const composition = operationResponse?.owner.composition;
@@ -502,85 +554,20 @@ class HttpHandler {
502
554
  const documentId = searchParams.get('id');
503
555
  const doc = documentId ? document.findDocument(documentId) : document;
504
556
  if (!doc) {
505
- return this.sendErrorResponse(response, [
506
- new common_1.BadRequestError({
507
- message: `Document with given id [${documentId}] does not exists`,
508
- }),
509
- ]);
557
+ context.errors.push(new common_1.BadRequestError({
558
+ message: `Document with given id [${documentId}] does not exists`,
559
+ }));
560
+ return this.sendResponse(context);
510
561
  }
511
562
  /** Check if response cache exists */
512
- let responseBody = this[constants_js_1.kAssetCache].get(doc, `$schema`);
563
+ let responseBody = this[constants_1.kAssetCache].get(doc, `$schema`);
513
564
  /** Create response if response cache does not exists */
514
565
  if (!responseBody) {
515
566
  const schema = doc.export();
516
567
  responseBody = JSON.stringify(schema);
517
- this[constants_js_1.kAssetCache].set(doc, `$schema`, responseBody);
568
+ this[constants_1.kAssetCache].set(doc, `$schema`, responseBody);
518
569
  }
519
570
  response.end(responseBody);
520
571
  }
521
- async sendErrorResponse(response, errors) {
522
- if (response.headersSent) {
523
- response.end();
524
- return;
525
- }
526
- if (!errors.length)
527
- errors.push((0, wrap_exception_js_1.wrapException)({ status: response.statusCode || 500 }));
528
- const { logger } = this.adapter;
529
- errors.forEach(x => {
530
- if (x instanceof common_1.OpraException) {
531
- switch (x.severity) {
532
- case 'fatal':
533
- logger.fatal(x);
534
- break;
535
- case 'warning':
536
- logger.warn(x);
537
- break;
538
- default:
539
- logger.error(x);
540
- }
541
- }
542
- else
543
- logger.fatal(x);
544
- });
545
- const wrappedErrors = errors.map(wrap_exception_js_1.wrapException);
546
- // Sort errors from fatal to info
547
- wrappedErrors.sort((a, b) => {
548
- const i = common_1.IssueSeverity.Keys.indexOf(a.severity) - common_1.IssueSeverity.Keys.indexOf(b.severity);
549
- if (i === 0)
550
- return b.status - a.status;
551
- return i;
552
- });
553
- let status = response.statusCode || 0;
554
- if (!status || status < Number(common_1.HttpStatusCode.BAD_REQUEST)) {
555
- status = wrappedErrors[0].status;
556
- if (status < Number(common_1.HttpStatusCode.BAD_REQUEST))
557
- status = common_1.HttpStatusCode.INTERNAL_SERVER_ERROR;
558
- }
559
- response.statusCode = status;
560
- const { document } = this.adapter;
561
- const dt = document.node.getComplexType('OperationResult');
562
- let encode = this[constants_js_1.kAssetCache].get(dt, 'encode');
563
- if (!encode) {
564
- encode = dt.generateCodec('encode', { ignoreWriteonlyFields: true });
565
- this[constants_js_1.kAssetCache].set(dt, 'encode', encode);
566
- }
567
- const { i18n } = this.adapter;
568
- const bodyObject = new common_1.OperationResult({
569
- errors: wrappedErrors.map(x => {
570
- const o = x.toJSON();
571
- if (!(process.env.NODE_ENV === 'dev' || process.env.NODE_ENV === 'development'))
572
- delete o.stack;
573
- return i18n.deep(o);
574
- }),
575
- });
576
- const body = encode(bodyObject);
577
- response.setHeader(common_1.HttpHeaderCodes.Content_Type, common_1.MimeTypes.opra_response_json + '; charset=utf-8');
578
- response.setHeader(common_1.HttpHeaderCodes.Cache_Control, 'no-cache');
579
- response.setHeader(common_1.HttpHeaderCodes.Pragma, 'no-cache');
580
- response.setHeader(common_1.HttpHeaderCodes.Expires, '-1');
581
- response.setHeader(common_1.HttpHeaderCodes.X_Opra_Version, common_1.OpraSchema.SpecVersion);
582
- response.send(JSON.stringify(body));
583
- response.end();
584
- }
585
572
  }
586
573
  exports.HttpHandler = HttpHandler;