@opra/core 1.0.0-alpha.2 → 1.0.0-alpha.21

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.
Files changed (54) hide show
  1. package/cjs/augmentation/18n.augmentation.js +1 -1
  2. package/cjs/execution-context.js +0 -1
  3. package/cjs/http/express-adapter.js +6 -17
  4. package/cjs/http/http-context.js +6 -3
  5. package/cjs/http/impl/http-handler.js +73 -62
  6. package/cjs/http/impl/http-incoming.host.js +3 -3
  7. package/cjs/http/impl/http-outgoing.host.js +2 -2
  8. package/cjs/http/impl/multipart-reader.js +4 -10
  9. package/cjs/http/impl/node-incoming-message.host.js +5 -3
  10. package/cjs/http/interfaces/node-incoming-message.interface.js +3 -2
  11. package/cjs/http/utils/body-reader.js +6 -5
  12. package/cjs/http/utils/common.js +6 -5
  13. package/cjs/http/utils/concat-readable.js +1 -2
  14. package/cjs/http/utils/convert-to-headers.js +2 -3
  15. package/cjs/http/utils/convert-to-raw-headers.js +1 -2
  16. package/cjs/http/utils/match-known-fields.js +2 -2
  17. package/cjs/http/utils/wrap-exception.js +1 -2
  18. package/cjs/index.js +3 -3
  19. package/cjs/platform-adapter.js +1 -1
  20. package/cjs/type-guards.js +4 -5
  21. package/esm/augmentation/18n.augmentation.js +1 -1
  22. package/esm/execution-context.js +0 -1
  23. package/esm/http/express-adapter.js +6 -17
  24. package/esm/http/http-context.js +6 -3
  25. package/esm/http/impl/http-handler.js +73 -62
  26. package/esm/http/impl/http-incoming.host.js +3 -3
  27. package/esm/http/impl/http-outgoing.host.js +2 -2
  28. package/esm/http/impl/multipart-reader.js +4 -10
  29. package/esm/http/impl/node-incoming-message.host.js +5 -3
  30. package/esm/http/interfaces/node-incoming-message.interface.js +3 -2
  31. package/esm/http/utils/body-reader.js +6 -5
  32. package/esm/http/utils/common.js +2 -1
  33. package/esm/index.js +3 -3
  34. package/esm/platform-adapter.js +1 -1
  35. package/i18n/i18n/en/error.json +21 -0
  36. package/package.json +13 -7
  37. package/types/augmentation/18n.augmentation.d.ts +1 -1
  38. package/types/execution-context.d.ts +1 -3
  39. package/types/http/express-adapter.d.ts +1 -1
  40. package/types/http/http-context.d.ts +2 -2
  41. package/types/http/impl/http-incoming.host.d.ts +1 -2
  42. package/types/http/impl/http-outgoing.host.d.ts +1 -1
  43. package/types/http/impl/multipart-reader.d.ts +0 -1
  44. package/types/http/impl/node-incoming-message.host.d.ts +2 -6
  45. package/types/http/impl/node-outgoing-message.host.d.ts +2 -5
  46. package/types/http/interfaces/http-incoming.interface.d.ts +1 -2
  47. package/types/http/interfaces/http-outgoing.interface.d.ts +1 -1
  48. package/types/http/interfaces/node-incoming-message.interface.d.ts +0 -2
  49. package/types/http/interfaces/node-outgoing-message.interface.d.ts +0 -2
  50. package/types/http/utils/body-reader.d.ts +1 -4
  51. package/types/http/utils/concat-readable.d.ts +0 -1
  52. package/types/http/utils/convert-to-raw-headers.d.ts +0 -1
  53. package/types/index.d.ts +3 -3
  54. package/types/platform-adapter.d.ts +2 -2
@@ -4,7 +4,10 @@
4
4
  https://github.com/nodejs/
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.validateString = exports.validateHeaderValue = exports.validateHeaderName = exports.hideStackFrames = exports.checkIsHttpToken = void 0;
7
+ exports.validateHeaderValue = exports.validateHeaderName = void 0;
8
+ exports.checkIsHttpToken = checkIsHttpToken;
9
+ exports.hideStackFrames = hideStackFrames;
10
+ exports.validateString = validateString;
8
11
  const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/;
9
12
  const nodeInternalPrefix = '__node_internal_';
10
13
  /**
@@ -17,7 +20,6 @@ const nodeInternalPrefix = '__node_internal_';
17
20
  function checkIsHttpToken(val) {
18
21
  return typeof val === 'string' && tokenRegExp.exec(val) !== null;
19
22
  }
20
- exports.checkIsHttpToken = checkIsHttpToken;
21
23
  const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
22
24
  /**
23
25
  * True if val contains an invalid field-vchar
@@ -44,7 +46,6 @@ function hideStackFrames(fn) {
44
46
  Object.defineProperty(fn, 'name', { __proto__: null, value: hidden });
45
47
  return fn;
46
48
  }
47
- exports.hideStackFrames = hideStackFrames;
48
49
  exports.validateHeaderName = hideStackFrames((name, label) => {
49
50
  // noinspection SuspiciousTypeOfGuard
50
51
  if (typeof name !== 'string' || !name || !checkIsHttpToken(name)) {
@@ -60,7 +61,7 @@ exports.validateHeaderValue = hideStackFrames((name, value) => {
60
61
  }
61
62
  });
62
63
  function validateString(value, name) {
63
- if (typeof value !== 'string')
64
+ if (typeof value !== 'string') {
64
65
  throw new TypeError(`Invalid ${name ? name + ' ' : ''}argument. Value must be a string`);
66
+ }
65
67
  }
66
- exports.validateString = validateString;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.concatReadable = void 0;
3
+ exports.concatReadable = concatReadable;
4
4
  const stream_1 = require("stream");
5
5
  function concatReadable(...streams) {
6
6
  const out = new stream_1.PassThrough();
@@ -17,4 +17,3 @@ function concatReadable(...streams) {
17
17
  pipeNext();
18
18
  return out;
19
19
  }
20
- exports.concatReadable = concatReadable;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.convertToHeadersDistinct = exports.convertToHeaders = void 0;
3
+ exports.convertToHeaders = convertToHeaders;
4
+ exports.convertToHeadersDistinct = convertToHeadersDistinct;
4
5
  const match_known_fields_js_1 = require("./match-known-fields.js");
5
6
  function convertToHeaders(src, dst, joinDuplicateHeaders) {
6
7
  for (let n = 0; n < src.length; n += 2) {
@@ -8,7 +9,6 @@ function convertToHeaders(src, dst, joinDuplicateHeaders) {
8
9
  }
9
10
  return dst;
10
11
  }
11
- exports.convertToHeaders = convertToHeaders;
12
12
  function convertToHeadersDistinct(src, dst) {
13
13
  const count = src.length % 2;
14
14
  for (let n = 0; n < count; n += 2) {
@@ -16,7 +16,6 @@ function convertToHeadersDistinct(src, dst) {
16
16
  }
17
17
  return dst;
18
18
  }
19
- exports.convertToHeadersDistinct = convertToHeadersDistinct;
20
19
  function addHeaderLine(field, value, dest, joinDuplicateHeaders) {
21
20
  if (value == null)
22
21
  return;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.convertToRawHeaders = void 0;
3
+ exports.convertToRawHeaders = convertToRawHeaders;
4
4
  const match_known_fields_js_1 = require("./match-known-fields.js");
5
5
  function convertToRawHeaders(src) {
6
6
  return Object.entries(src).reduce((a, [field, v]) => {
@@ -21,4 +21,3 @@ function convertToRawHeaders(src) {
21
21
  return a;
22
22
  }, []);
23
23
  }
24
- exports.convertToRawHeaders = convertToRawHeaders;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.matchKnownFields = exports.ARRAY_FIELD = exports.SEMICOLON_DELIMITED_FIELD = exports.COMMA_DELIMITED_FIELD = exports.NO_DUPLICATES_FIELD = void 0;
3
+ exports.ARRAY_FIELD = exports.SEMICOLON_DELIMITED_FIELD = exports.COMMA_DELIMITED_FIELD = exports.NO_DUPLICATES_FIELD = void 0;
4
+ exports.matchKnownFields = matchKnownFields;
4
5
  const common_1 = require("@opra/common");
5
6
  exports.NO_DUPLICATES_FIELD = 0;
6
7
  exports.COMMA_DELIMITED_FIELD = 1;
@@ -46,4 +47,3 @@ function matchKnownFields(field) {
46
47
  const x = KNOWN_FIELDS[field.toLowerCase()];
47
48
  return x ? x : [field, exports.COMMA_DELIMITED_FIELD];
48
49
  }
49
- exports.matchKnownFields = matchKnownFields;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.wrapException = void 0;
3
+ exports.wrapException = wrapException;
4
4
  const common_1 = require("@opra/common");
5
5
  function wrapException(error) {
6
6
  if (error instanceof common_1.OpraHttpError)
@@ -31,4 +31,3 @@ function wrapException(error) {
31
31
  return new common_1.InternalServerError(error);
32
32
  }
33
33
  }
34
- exports.wrapException = wrapException;
package/cjs/index.js CHANGED
@@ -10,20 +10,20 @@ const HttpOutgoingHost_ = tslib_1.__importStar(require("./http/impl/http-outgoin
10
10
  const NodeIncomingMessageHost_ = tslib_1.__importStar(require("./http/impl/node-incoming-message.host.js"));
11
11
  const NodeOutgoingMessageHost_ = tslib_1.__importStar(require("./http/impl/node-outgoing-message.host.js"));
12
12
  tslib_1.__exportStar(require("./execution-context.js"), exports);
13
- tslib_1.__exportStar(require("./platform-adapter.js"), exports);
14
- tslib_1.__exportStar(require("./type-guards.js"), exports);
15
13
  tslib_1.__exportStar(require("./helpers/logger.js"), exports);
16
14
  tslib_1.__exportStar(require("./helpers/service-base.js"), exports);
17
15
  tslib_1.__exportStar(require("./http/express-adapter.js"), exports);
18
16
  tslib_1.__exportStar(require("./http/http-adapter.js"), exports);
19
17
  tslib_1.__exportStar(require("./http/http-context.js"), exports);
18
+ tslib_1.__exportStar(require("./http/impl/multipart-reader.js"), exports);
20
19
  tslib_1.__exportStar(require("./http/interfaces/http-incoming.interface.js"), exports);
21
20
  tslib_1.__exportStar(require("./http/interfaces/http-outgoing.interface.js"), exports);
22
21
  tslib_1.__exportStar(require("./http/interfaces/node-incoming-message.interface.js"), exports);
23
22
  tslib_1.__exportStar(require("./http/interfaces/node-outgoing-message.interface.js"), exports);
24
- tslib_1.__exportStar(require("./http/impl/multipart-reader.js"), exports);
25
23
  tslib_1.__exportStar(require("./http/utils/wrap-exception.js"), exports);
26
24
  tslib_1.__exportStar(require("./interfaces/logger.interface.js"), exports);
25
+ tslib_1.__exportStar(require("./platform-adapter.js"), exports);
26
+ tslib_1.__exportStar(require("./type-guards.js"), exports);
27
27
  var classes;
28
28
  (function (classes) {
29
29
  classes.HttpIncomingHost = HttpIncomingHost_.HttpIncomingHost;
@@ -2,8 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PlatformAdapter = void 0;
4
4
  require("./augmentation/18n.augmentation.js");
5
- const strict_typed_events_1 = require("strict-typed-events");
6
5
  const common_1 = require("@opra/common");
6
+ const strict_typed_events_1 = require("strict-typed-events");
7
7
  const constants_js_1 = require("./constants.js");
8
8
  const logger_js_1 = require("./helpers/logger.js");
9
9
  const asset_cache_js_1 = require("./http/impl/asset-cache.js");
@@ -1,23 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isHttpOutgoing = exports.isNodeOutgoingMessage = exports.isHttpIncoming = exports.isNodeIncomingMessage = void 0;
3
+ exports.isNodeIncomingMessage = isNodeIncomingMessage;
4
+ exports.isHttpIncoming = isHttpIncoming;
5
+ exports.isNodeOutgoingMessage = isNodeOutgoingMessage;
6
+ exports.isHttpOutgoing = isHttpOutgoing;
4
7
  const common_1 = require("@opra/common");
5
8
  function isNodeIncomingMessage(v) {
6
9
  return v && typeof v.method === 'string' && Array.isArray(v.rawHeaders) && (0, common_1.isReadable)(v);
7
10
  }
8
- exports.isNodeIncomingMessage = isNodeIncomingMessage;
9
11
  function isHttpIncoming(v) {
10
12
  return (isNodeIncomingMessage(v) &&
11
13
  typeof v.header === 'function' &&
12
14
  typeof v.acceptsLanguages === 'function' &&
13
15
  typeof v.readBody === 'function');
14
16
  }
15
- exports.isHttpIncoming = isHttpIncoming;
16
17
  function isNodeOutgoingMessage(v) {
17
18
  return v && typeof v.getHeaders === 'function' && (0, common_1.isStream)(v);
18
19
  }
19
- exports.isNodeOutgoingMessage = isNodeOutgoingMessage;
20
20
  function isHttpOutgoing(v) {
21
21
  return isNodeOutgoingMessage(v) && typeof v.clearCookie === 'function' && typeof v.cookie === 'function';
22
22
  }
23
- exports.isHttpOutgoing = isHttpOutgoing;
@@ -1,6 +1,6 @@
1
+ import { getStackFileName, I18n as I18n_ } from '@opra/common';
1
2
  import fs from 'fs';
2
3
  import path from 'path';
3
- import { getStackFileName, I18n as I18n_ } from '@opra/common';
4
4
  I18n_.load = async function (options) {
5
5
  const opts = {
6
6
  ...options,
@@ -8,7 +8,6 @@ export class ExecutionContext extends AsyncEventEmitter {
8
8
  this.document = init.document;
9
9
  this.protocol = init.protocol;
10
10
  this.platform = init.platform;
11
- this.platformArgs = init.platformArgs;
12
11
  }
13
12
  addListener(event, listener) {
14
13
  return super.addListener(event, listener);
@@ -1,6 +1,6 @@
1
+ import { HttpApi, NotFoundError } from '@opra/common';
1
2
  import { Router } from 'express';
2
3
  import * as nodePath from 'path';
3
- import { HttpApi, NotFoundError } from '@opra/common';
4
4
  import { kHandler } from '../constants.js';
5
5
  import { HttpAdapter } from './http-adapter.js';
6
6
  import { HttpContext } from './http-context.js';
@@ -31,13 +31,14 @@ export class ExpressAdapter extends HttpAdapter {
31
31
  }
32
32
  if (resource.onShutdown) {
33
33
  const instance = this._controllerInstances.get(resource) || resource.instance;
34
- if (instance)
34
+ if (instance) {
35
35
  try {
36
36
  await resource.onShutdown.call(instance, resource);
37
37
  }
38
38
  catch (e) {
39
39
  this.logger.error(e);
40
40
  }
41
+ }
41
42
  }
42
43
  };
43
44
  for (const c of this.api.controllers.values())
@@ -61,14 +62,9 @@ export class ExpressAdapter extends HttpAdapter {
61
62
  const createContext = (_req, _res, args) => {
62
63
  const request = HttpIncoming.from(_req);
63
64
  const response = HttpOutgoing.from(_res);
64
- const platformArgs = {
65
- request: _req,
66
- response: _res,
67
- };
68
65
  return new HttpContext({
69
66
  adapter: this,
70
67
  platform: this.platform,
71
- platformArgs,
72
68
  request,
73
69
  response,
74
70
  controller: args?.controller,
@@ -78,16 +74,9 @@ export class ExpressAdapter extends HttpAdapter {
78
74
  });
79
75
  };
80
76
  /** Add an endpoint that returns document schema */
81
- router.get('*', (_req, _res, next) => {
82
- if (_req.url.includes('/$schema')) {
83
- const url = (_req.url.includes('?') ? _req.url.substring(0, _req.url.indexOf('?')) : _req.url).toLowerCase();
84
- if (url === '/$schema') {
85
- const context = createContext(_req, _res);
86
- this[kHandler].sendDocumentSchema(context).catch(next);
87
- return;
88
- }
89
- }
90
- next();
77
+ router.get('/\\$schema', (_req, _res, next) => {
78
+ const context = createContext(_req, _res);
79
+ this[kHandler].sendDocumentSchema(context).catch(next);
91
80
  });
92
81
  /** Add operation endpoints */
93
82
  if (this.api.controllers.size) {
@@ -1,6 +1,6 @@
1
- import { vg } from 'valgen';
2
1
  import typeIs from '@browsery/type-is';
3
2
  import { BadRequestError, InternalServerError, NotAcceptableError, } from '@opra/common';
3
+ import { vg } from 'valgen';
4
4
  import { kAssetCache } from '../constants.js';
5
5
  import { ExecutionContext } from '../execution-context.js';
6
6
  import { MultipartReader } from './impl/multipart-reader.js';
@@ -74,10 +74,11 @@ export class HttpContext extends ExecutionContext {
74
74
  }
75
75
  /** Check required fields */
76
76
  for (const field of multipartFields) {
77
- if (field.required && !fieldsFound.get(field))
77
+ if (field.required && !fieldsFound.get(field)) {
78
78
  throw new BadRequestError({
79
79
  message: `Multipart field (${field.fieldName}) is required`,
80
80
  });
81
+ }
81
82
  }
82
83
  }
83
84
  return this._body;
@@ -85,8 +86,9 @@ export class HttpContext extends ExecutionContext {
85
86
  this._body = await this.request.readBody({ limit: operation.requestBody?.maxContentSize });
86
87
  if (this._body != null) {
87
88
  // Convert Buffer to string if media is text
88
- if (Buffer.isBuffer(this._body) && request.is(['json', 'xml', 'txt', 'text']))
89
+ if (Buffer.isBuffer(this._body) && request.is(['json', 'xml', 'txt', 'text'])) {
89
90
  this._body = this._body.toString('utf-8');
91
+ }
90
92
  // Transform text to Object if media is JSON
91
93
  if (typeof this._body === 'string' && request.is(['json']))
92
94
  this._body = JSON.parse(this._body);
@@ -100,6 +102,7 @@ export class HttpContext extends ExecutionContext {
100
102
  mediaType.type?.generateCodec('decode', {
101
103
  partial: operation.requestBody?.partial,
102
104
  projection: '*',
105
+ ignoreReadonlyFields: true,
103
106
  }) || vg.isAny();
104
107
  this.adapter[kAssetCache].set(mediaType, 'decode', decode);
105
108
  }
@@ -1,10 +1,10 @@
1
+ import * as process from 'node:process';
2
+ import typeIs from '@browsery/type-is';
3
+ import { BadRequestError, HttpHeaderCodes, HttpStatusCode, InternalServerError, isBlob, isReadableStream, IssueSeverity, MethodNotAllowedError, MimeTypes, OperationResult, OpraException, OpraSchema, translate, } from '@opra/common';
1
4
  import { parse as parseContentType } from 'content-type';
2
5
  import { splitString } from 'fast-tokenizer';
3
- import * as process from 'node:process';
4
6
  import { asMutable } from 'ts-gems';
5
7
  import { toArray, ValidationError, vg } from 'valgen';
6
- import typeIs from '@browsery/type-is';
7
- import { BadRequestError, HttpHeaderCodes, HttpStatusCode, InternalServerError, isBlob, isReadableStream, IssueSeverity, MethodNotAllowedError, MimeTypes, OperationResult, OpraException, OpraSchema, translate, } from '@opra/common';
8
8
  import { kAssetCache } from '../../constants.js';
9
9
  import { wrapException } from '../utils/wrap-exception.js';
10
10
  /**
@@ -60,7 +60,8 @@ export class HttpHandler {
60
60
  else
61
61
  await this._executeRequest(context);
62
62
  }
63
- catch (e) {
63
+ catch (error) {
64
+ let e = error;
64
65
  if (e instanceof ValidationError) {
65
66
  e = new InternalServerError({
66
67
  message: translate('error:RESPONSE_VALIDATION,', 'Response validation failed'),
@@ -72,11 +73,10 @@ export class HttpHandler {
72
73
  e = wrapException(e);
73
74
  response.status(e.statusCode || e.status || HttpStatusCode.INTERNAL_SERVER_ERROR);
74
75
  response.contentType(MimeTypes.opra_response_json);
75
- await this._sendResponse(context, new OperationResult({ errors: [e] })).finally(() => {
76
+ await this._sendResponse(context, new OperationResult({ errors: [e.toJSON()] })).finally(() => {
76
77
  if (!response.finished)
77
78
  response.end();
78
79
  });
79
- // if (!outgoing.writableEnded) await this._sendErrorResponse(context.response, [error]);
80
80
  }
81
81
  finally {
82
82
  await context.emitAsync('finish');
@@ -109,17 +109,17 @@ export class HttpHandler {
109
109
  */
110
110
  async _parseParameters(context) {
111
111
  const { operation, request } = context;
112
- let prmName = '';
112
+ let key = '';
113
113
  try {
114
114
  const onFail = (issue) => {
115
- issue.location = prmName;
115
+ issue.location = key;
116
116
  return issue;
117
117
  };
118
118
  /** prepare decoders */
119
119
  const getDecoder = (prm) => {
120
120
  let decode = this[kAssetCache].get(prm, 'decode');
121
121
  if (!decode) {
122
- decode = prm.type?.generateCodec('decode') || vg.isAny();
122
+ decode = prm.type?.generateCodec('decode', { ignoreReadonlyFields: true }) || vg.isAny();
123
123
  this[kAssetCache].set(prm, 'decode', decode);
124
124
  }
125
125
  return decode;
@@ -127,9 +127,9 @@ export class HttpHandler {
127
127
  const paramsLeft = new Set([...operation.parameters, ...operation.owner.parameters]);
128
128
  /** parse cookie parameters */
129
129
  if (request.cookies) {
130
- for (prmName of Object.keys(request.cookies)) {
131
- const oprPrm = operation.findParameter(prmName, 'cookie');
132
- const cntPrm = operation.owner.findParameter(prmName, 'cookie');
130
+ for (key of Object.keys(request.cookies)) {
131
+ const oprPrm = operation.findParameter(key, 'cookie');
132
+ const cntPrm = operation.owner.findParameter(key, 'cookie');
133
133
  const prm = oprPrm || cntPrm;
134
134
  if (!prm)
135
135
  continue;
@@ -138,16 +138,17 @@ export class HttpHandler {
138
138
  if (cntPrm)
139
139
  paramsLeft.delete(cntPrm);
140
140
  const decode = getDecoder(prm);
141
- const v = decode(request.cookies[prmName], { coerce: true, label: prmName, onFail });
141
+ const v = decode(request.cookies[key], { coerce: true, label: key, onFail });
142
+ const prmName = typeof prm.name === 'string' ? prm.name : key;
142
143
  if (v !== undefined)
143
144
  context.cookies[prmName] = v;
144
145
  }
145
146
  }
146
147
  /** parse headers */
147
148
  if (request.headers) {
148
- for (prmName of Object.keys(request.headers)) {
149
- const oprPrm = operation.findParameter(prmName, 'header');
150
- const cntPrm = operation.owner.findParameter(prmName, 'header');
149
+ for (key of Object.keys(request.headers)) {
150
+ const oprPrm = operation.findParameter(key, 'header');
151
+ const cntPrm = operation.owner.findParameter(key, 'header');
151
152
  const prm = oprPrm || cntPrm;
152
153
  if (!prm)
153
154
  continue;
@@ -156,16 +157,17 @@ export class HttpHandler {
156
157
  if (cntPrm)
157
158
  paramsLeft.delete(cntPrm);
158
159
  const decode = getDecoder(prm);
159
- const v = decode(request.headers[prmName], { coerce: true, label: prmName, onFail });
160
+ const v = decode(request.headers[key], { coerce: true, label: key, onFail });
161
+ const prmName = typeof prm.name === 'string' ? prm.name : key;
160
162
  if (v !== undefined)
161
163
  context.headers[prmName] = v;
162
164
  }
163
165
  }
164
166
  /** parse path parameters */
165
167
  if (request.params) {
166
- for (prmName of Object.keys(request.params)) {
167
- const oprPrm = operation.findParameter(prmName, 'path');
168
- const cntPrm = operation.owner.findParameter(prmName, 'path');
168
+ for (key of Object.keys(request.params)) {
169
+ const oprPrm = operation.findParameter(key, 'path');
170
+ const cntPrm = operation.owner.findParameter(key, 'path');
169
171
  const prm = oprPrm || cntPrm;
170
172
  if (!prm)
171
173
  continue;
@@ -174,17 +176,17 @@ export class HttpHandler {
174
176
  if (cntPrm)
175
177
  paramsLeft.delete(cntPrm);
176
178
  const decode = getDecoder(prm);
177
- const v = decode(request.params[prmName], { coerce: true, label: prmName, onFail });
179
+ const v = decode(request.params[key], { coerce: true, label: key, onFail });
178
180
  if (v !== undefined)
179
- context.pathParams[prmName] = v;
181
+ context.pathParams[key] = v;
180
182
  }
181
183
  }
182
184
  /** parse query parameters */
183
185
  const url = new URL(request.originalUrl || request.url || '/', 'http://tempuri.org');
184
186
  const { searchParams } = url;
185
- for (prmName of searchParams.keys()) {
186
- const oprPrm = operation.findParameter(prmName, 'query');
187
- const cntPrm = operation.owner.findParameter(prmName, 'query');
187
+ for (key of searchParams.keys()) {
188
+ const oprPrm = operation.findParameter(key, 'query');
189
+ const cntPrm = operation.owner.findParameter(key, 'query');
188
190
  const prm = oprPrm || cntPrm;
189
191
  if (!prm)
190
192
  continue;
@@ -193,15 +195,16 @@ export class HttpHandler {
193
195
  if (cntPrm)
194
196
  paramsLeft.delete(cntPrm);
195
197
  const decode = getDecoder(prm);
196
- let values = searchParams?.getAll(prmName);
198
+ let values = searchParams?.getAll(key);
199
+ const prmName = typeof prm.name === 'string' ? prm.name : key;
197
200
  if (values?.length && prm.isArray) {
198
201
  values = values.map(v => splitString(v, { delimiters: prm.arraySeparator, quotes: true })).flat();
199
- values = values.map(v => decode(v, { coerce: true, label: prmName, onFail }));
202
+ values = values.map(v => decode(v, { coerce: true, label: key, onFail }));
200
203
  if (values.length)
201
204
  context.queryParams[prmName] = values;
202
205
  }
203
206
  else {
204
- const v = decode(values[0], { coerce: true, label: prmName, onFail });
207
+ const v = decode(values[0], { coerce: true, label: key, onFail });
205
208
  if (values.length)
206
209
  context.queryParams[prmName] = v;
207
210
  }
@@ -216,8 +219,8 @@ export class HttpHandler {
216
219
  }
217
220
  catch (e) {
218
221
  if (e instanceof ValidationError) {
219
- e = new BadRequestError({
220
- message: `Invalid parameter (${prmName}) value. ` + e.message,
222
+ throw new BadRequestError({
223
+ message: `Invalid parameter (${key}) value. ` + e.message,
221
224
  code: 'REQUEST_VALIDATION',
222
225
  details: e.issues,
223
226
  }, e);
@@ -237,9 +240,8 @@ export class HttpHandler {
237
240
  let contentType = request.header('content-type');
238
241
  if (contentType) {
239
242
  contentType = parseContentType(contentType).type;
240
- mediaType = operation.requestBody.content.find(mc => {
241
- return (mc.contentType && typeIs.is(contentType, Array.isArray(mc.contentType) ? mc.contentType : [mc.contentType]));
242
- });
243
+ mediaType = operation.requestBody.content.find(mc => mc.contentType &&
244
+ typeIs.is(contentType, Array.isArray(mc.contentType) ? mc.contentType : [mc.contentType]));
243
245
  }
244
246
  if (!mediaType) {
245
247
  const contentTypes = operation.requestBody.content.map(mc => mc.contentType).flat();
@@ -258,11 +260,12 @@ export class HttpHandler {
258
260
  throw new MethodNotAllowedError();
259
261
  const responseValue = await context.operationHandler.call(context.controllerInstance, context);
260
262
  const { response } = context;
261
- if (!response.writableEnded)
263
+ if (!response.writableEnded) {
262
264
  await this._sendResponse(context, responseValue).finally(() => {
263
265
  if (!response.writableEnded)
264
266
  response.end();
265
267
  });
268
+ }
266
269
  }
267
270
  /**
268
271
  *
@@ -279,7 +282,7 @@ export class HttpHandler {
279
282
  const operationResultType = document.node.getDataType(OperationResult);
280
283
  let operationResultEncoder = this[kAssetCache].get(operationResultType, 'encode');
281
284
  if (!operationResultEncoder) {
282
- operationResultEncoder = operationResultType.generateCodec('encode');
285
+ operationResultEncoder = operationResultType.generateCodec('encode', { ignoreWriteonlyFields: true });
283
286
  this[kAssetCache].set(operationResultType, 'encode', operationResultEncoder);
284
287
  }
285
288
  /** Validate response */
@@ -291,6 +294,7 @@ export class HttpHandler {
291
294
  encode = operationResponse.type.generateCodec('encode', {
292
295
  partial: operationResponse.partial,
293
296
  projection: '*',
297
+ ignoreWriteonlyFields: true,
294
298
  });
295
299
  if (operationResponse) {
296
300
  if (operationResponse.isArray)
@@ -317,7 +321,9 @@ export class HttpHandler {
317
321
  else
318
322
  body = encode(body);
319
323
  }
320
- if (body instanceof OperationResult && operationResponse.type) {
324
+ if (body instanceof OperationResult &&
325
+ operationResponse.type &&
326
+ operationResponse.type !== document.node.getDataType(OperationResult)) {
321
327
  body.type = operationResponse.type.name ? operationResponse.type.name : '#embedded';
322
328
  }
323
329
  }
@@ -387,8 +393,9 @@ export class HttpHandler {
387
393
  /** Filter available HttpOperationResponse instances according to status code. */
388
394
  const filteredResponses = operation.responses.filter(r => r.statusCode.find(sc => sc.start <= statusCode && sc.end >= statusCode));
389
395
  /** Throw InternalServerError if controller returns non-configured status code */
390
- if (!filteredResponses.length && statusCode < 400)
396
+ if (!filteredResponses.length && statusCode < 400) {
391
397
  throw new InternalServerError(`No responses defined for status code ${statusCode} in operation "${operation.name}"`);
398
+ }
392
399
  /** We search for content-type in filtered HttpOperationResponse array */
393
400
  if (filteredResponses.length) {
394
401
  /** If no response returned, and content-type has not been set (No response wants to be returned by operation) */
@@ -401,8 +408,9 @@ export class HttpHandler {
401
408
  if (contentType) {
402
409
  // Find HttpEndpointResponse instance according to content-type header
403
410
  operationResponse = filteredResponses.find(r => typeIs.is(contentType, toArray(r.contentType)));
404
- if (!operationResponse)
411
+ if (!operationResponse) {
405
412
  throw new InternalServerError(`Operation didn't configured to return "${contentType}" content`);
413
+ }
406
414
  }
407
415
  else {
408
416
  /** Select first HttpOperationResponse if content-type header has not been set */
@@ -434,23 +442,26 @@ export class HttpHandler {
434
442
  case 'Entity.Get':
435
443
  case 'Entity.FindMany':
436
444
  case 'Entity.Update': {
437
- if (!(body instanceof OperationResult))
445
+ if (!(body instanceof OperationResult)) {
438
446
  body = new OperationResult({
439
447
  payload: body,
440
448
  });
449
+ }
441
450
  if ((composition === 'Entity.Create' || composition === 'Entity.Update') &&
442
451
  composition &&
443
- body.affected == null)
452
+ body.affected == null) {
444
453
  body.affected = 1;
454
+ }
445
455
  break;
446
456
  }
447
457
  case 'Entity.Delete':
448
458
  case 'Entity.DeleteMany':
449
459
  case 'Entity.UpdateMany': {
450
- if (!(body instanceof OperationResult))
460
+ if (!(body instanceof OperationResult)) {
451
461
  body = new OperationResult({
452
462
  affected: body,
453
463
  });
464
+ }
454
465
  body.affected =
455
466
  typeof body.affected === 'number'
456
467
  ? body.affected
@@ -461,15 +472,19 @@ export class HttpHandler {
461
472
  : undefined;
462
473
  break;
463
474
  }
475
+ default:
476
+ break;
464
477
  }
465
478
  }
466
- if (responseArgs.contentType && responseArgs.contentType !== parsedContentType?.type)
479
+ if (responseArgs.contentType && responseArgs.contentType !== parsedContentType?.type) {
467
480
  response.setHeader('content-type', responseArgs.contentType);
481
+ }
468
482
  if (responseArgs.contentType &&
469
483
  body != null &&
470
484
  !(body instanceof OperationResult) &&
471
- typeIs.is(responseArgs.contentType, [MimeTypes.opra_response_json]))
485
+ typeIs.is(responseArgs.contentType, [MimeTypes.opra_response_json])) {
472
486
  body = new OperationResult({ payload: body });
487
+ }
473
488
  if (hasBody)
474
489
  responseArgs.body = body;
475
490
  return responseArgs;
@@ -478,28 +493,24 @@ export class HttpHandler {
478
493
  const { request, response } = context;
479
494
  const { document } = this.adapter;
480
495
  response.setHeader('content-type', MimeTypes.json);
496
+ const url = new URL(request.originalUrl || request.url || '/', 'http://tempuri.org');
497
+ const { searchParams } = url;
498
+ const documentId = searchParams.get('id');
499
+ const doc = documentId ? document.findDocument(documentId) : document;
500
+ if (!doc) {
501
+ return this.sendErrorResponse(response, [
502
+ new BadRequestError({
503
+ message: `Document with given id [${documentId}] does not exists`,
504
+ }),
505
+ ]);
506
+ }
481
507
  /** Check if response cache exists */
482
- let responseBody = this[kAssetCache].get(document, '$schema-response');
508
+ let responseBody = this[kAssetCache].get(doc, `$schema`);
483
509
  /** Create response if response cache does not exists */
484
510
  if (!responseBody) {
485
- const url = new URL(request.originalUrl || request.url || '/', 'http://tempuri.org');
486
- const { searchParams } = url;
487
- // const nsPath = searchParams.get('ns');
488
- // if (nsPath) {
489
- // const arr = nsPath.split('/');
490
- // let doc = document;
491
- // for (const a of arr) {
492
- // }
493
- // }
494
- const schema = document.export({ references: searchParams.get('references') });
495
- const dt = document.node.getComplexType('OperationResult');
496
- let encode = this[kAssetCache].get(dt, 'encode');
497
- if (!encode) {
498
- encode = dt.generateCodec('encode');
499
- this[kAssetCache].set(dt, 'encode', encode);
500
- }
511
+ const schema = doc.export();
501
512
  responseBody = JSON.stringify(schema);
502
- this[kAssetCache].set(document, '$schema-response', responseBody);
513
+ this[kAssetCache].set(doc, `$schema`, responseBody);
503
514
  }
504
515
  response.end(responseBody);
505
516
  }
@@ -546,7 +557,7 @@ export class HttpHandler {
546
557
  const dt = document.node.getComplexType('OperationResult');
547
558
  let encode = this[kAssetCache].get(dt, 'encode');
548
559
  if (!encode) {
549
- encode = dt.generateCodec('encode');
560
+ encode = dt.generateCodec('encode', { ignoreWriteonlyFields: true });
550
561
  this[kAssetCache].set(dt, 'encode', encode);
551
562
  }
552
563
  const { i18n } = this.adapter;