@opra/core 0.20.3 → 0.21.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.
Files changed (77) hide show
  1. package/cjs/adapter/adapter.js +2 -2
  2. package/cjs/adapter/http/express-adapter.js +29 -7
  3. package/cjs/adapter/http/helpers/common.js +66 -0
  4. package/cjs/adapter/http/helpers/convert-to-headers.js +65 -0
  5. package/cjs/adapter/http/helpers/convert-to-raw-headers.js +25 -0
  6. package/cjs/adapter/http/helpers/match-known-fields.js +47 -0
  7. package/cjs/adapter/http/http-adapter.js +76 -438
  8. package/cjs/adapter/http/impl/http-incoming-message-host.js +127 -0
  9. package/cjs/adapter/http/impl/http-outgoing-message-host.js +210 -0
  10. package/cjs/adapter/http/impl/http-server-request.js +125 -0
  11. package/cjs/adapter/http/impl/http-server-response.js +226 -0
  12. package/cjs/adapter/http/request-parsers/batch-request-parser.js +169 -0
  13. package/cjs/adapter/http/request-parsers/parse-collection-request.js +165 -0
  14. package/cjs/adapter/http/request-parsers/parse-request.js +24 -0
  15. package/cjs/adapter/http/request-parsers/parse-singleton-request.js +96 -0
  16. package/cjs/adapter/request-context.host.js +17 -3
  17. package/cjs/adapter/request.host.js +6 -3
  18. package/cjs/adapter/response.host.js +5 -3
  19. package/cjs/index.js +4 -2
  20. package/esm/adapter/adapter.js +2 -2
  21. package/esm/adapter/http/express-adapter.js +6 -6
  22. package/esm/adapter/http/helpers/common.js +60 -0
  23. package/esm/adapter/http/helpers/convert-to-headers.js +60 -0
  24. package/esm/adapter/http/helpers/convert-to-raw-headers.js +21 -0
  25. package/esm/adapter/http/helpers/match-known-fields.js +43 -0
  26. package/esm/adapter/http/http-adapter.js +77 -439
  27. package/esm/adapter/http/impl/http-incoming-message-host.js +122 -0
  28. package/esm/adapter/http/impl/http-outgoing-message-host.js +205 -0
  29. package/esm/adapter/http/impl/http-server-request.js +121 -0
  30. package/esm/adapter/http/impl/http-server-response.js +222 -0
  31. package/esm/adapter/http/request-parsers/batch-request-parser.js +169 -0
  32. package/esm/adapter/http/request-parsers/parse-collection-request.js +161 -0
  33. package/esm/adapter/http/request-parsers/parse-request.js +20 -0
  34. package/esm/adapter/http/request-parsers/parse-singleton-request.js +92 -0
  35. package/esm/adapter/request-context.host.js +17 -3
  36. package/esm/adapter/request.host.js +6 -3
  37. package/esm/adapter/response.host.js +5 -3
  38. package/esm/index.js +4 -2
  39. package/package.json +20 -16
  40. package/types/adapter/adapter.d.ts +1 -1
  41. package/types/adapter/http/helpers/common.d.ts +17 -0
  42. package/types/adapter/http/helpers/convert-to-headers.d.ts +2 -0
  43. package/types/adapter/http/helpers/convert-to-raw-headers.d.ts +3 -0
  44. package/types/adapter/http/helpers/match-known-fields.d.ts +6 -0
  45. package/types/adapter/http/http-adapter.d.ts +7 -12
  46. package/types/adapter/http/impl/http-incoming-message-host.d.ts +58 -0
  47. package/types/adapter/http/impl/http-outgoing-message-host.d.ts +72 -0
  48. package/types/adapter/http/{http-request-message.d.ts → impl/http-server-request.d.ts} +52 -85
  49. package/types/adapter/http/impl/http-server-response.d.ts +137 -0
  50. package/types/adapter/http/request-parsers/batch-request-parser.d.ts +0 -0
  51. package/types/adapter/http/request-parsers/parse-collection-request.d.ts +4 -0
  52. package/types/adapter/http/request-parsers/parse-request.d.ts +4 -0
  53. package/types/adapter/http/request-parsers/parse-singleton-request.d.ts +4 -0
  54. package/types/adapter/interfaces/request-context.interface.d.ts +14 -10
  55. package/types/adapter/interfaces/request.interface.d.ts +3 -2
  56. package/types/adapter/interfaces/response.interface.d.ts +2 -2
  57. package/types/adapter/request-context.host.d.ts +9 -6
  58. package/types/adapter/request.host.d.ts +8 -4
  59. package/types/adapter/response.host.d.ts +6 -4
  60. package/types/index.d.ts +4 -2
  61. package/cjs/adapter/http/http-message.host.js +0 -251
  62. package/cjs/adapter/http/http-request-context.host.js +0 -28
  63. package/cjs/adapter/http/http-request-message.js +0 -152
  64. package/cjs/adapter/http/http-request.host.js +0 -14
  65. package/cjs/adapter/http/http-response-message.js +0 -238
  66. package/cjs/adapter/http/http-response.host.js +0 -14
  67. package/esm/adapter/http/http-message.host.js +0 -246
  68. package/esm/adapter/http/http-request-context.host.js +0 -24
  69. package/esm/adapter/http/http-request-message.js +0 -148
  70. package/esm/adapter/http/http-request.host.js +0 -10
  71. package/esm/adapter/http/http-response-message.js +0 -233
  72. package/esm/adapter/http/http-response.host.js +0 -10
  73. package/types/adapter/http/http-message.host.d.ts +0 -122
  74. package/types/adapter/http/http-request-context.host.d.ts +0 -16
  75. package/types/adapter/http/http-request.host.d.ts +0 -7
  76. package/types/adapter/http/http-response-message.d.ts +0 -321
  77. package/types/adapter/http/http-response.host.d.ts +0 -7
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ // async parseMultiPart(
3
+ // context: TExecutionContext,
4
+ // url: OpraURL,
5
+ // headers: IncomingHttpHeaders,
6
+ // input: Readable,
7
+ // boundary: string
8
+ // ): Promise<BatchRequestContext> {
9
+ // return await new Promise((resolve, reject) => {
10
+ // let _resolved = false;
11
+ // const dicer = new Dicer({boundary});
12
+ // const doReject = (e) => {
13
+ // if (_resolved) return;
14
+ // _resolved = true;
15
+ // reject(e);
16
+ // taskQueue.clearQueue();
17
+ // dicer.destroy();
18
+ // }
19
+ // const taskQueue = new TaskQueue({concurrency: 1});
20
+ // taskQueue.on('error', doReject);
21
+ //
22
+ // const queries: SingleRequestContext[] = [];
23
+ // let partCounter = 0;
24
+ // dicer.on('error', doReject);
25
+ // dicer.on('part', part => {
26
+ // const partIndex = partCounter++;
27
+ // let header: any;
28
+ // const chunks: Buffer[] = [];
29
+ // part.on('error', doReject);
30
+ // part.on('header', (_header) => header = normalizeHeaders(_header));
31
+ // part.on('data', (chunk: Buffer) => chunks.push(chunk));
32
+ // part.on('end', () => {
33
+ // if (_resolved || !(header || chunks.length))
34
+ // return;
35
+ // const ct = header['content-type'];
36
+ // if (ct === 'application/http') {
37
+ // taskQueue.enqueue(async () => {
38
+ // const data = Buffer.concat(chunks);
39
+ // if (!(data && data.length))
40
+ // return;
41
+ // const r = HttpRequest.parse(data);
42
+ // await callMiddlewares(r, [jsonBodyParser]);
43
+ // const subUrl = new OpraURL(r.url);
44
+ // const contentId = header && header['content-id'];
45
+ // queries.push(this.parseSingleQuery({
46
+ // context,
47
+ // url: subUrl,
48
+ // method: r.method,
49
+ // headers: r.headers,
50
+ // body: r.body,
51
+ // contentId
52
+ // }));
53
+ // });
54
+ // } else doReject(new BadRequestError({
55
+ // message: 'Unaccepted "content-type" header in multipart data',
56
+ // details: {
57
+ // position: `${boundary}[${partIndex}]`
58
+ // }
59
+ // }))
60
+ // });
61
+ // });
62
+ // dicer.on('finish', () => {
63
+ // taskQueue.enqueue(() => {
64
+ // if (_resolved) return;
65
+ // _resolved = true;
66
+ // const batch = new BatchRequestContext({
67
+ // service: this.document,
68
+ // context,
69
+ // headers,
70
+ // queries,
71
+ // params: url.searchParams,
72
+ // continueOnError: false
73
+ // });
74
+ // resolve(batch);
75
+ // });
76
+ // });
77
+ // input.pipe(dicer);
78
+ // });
79
+ // }
80
+ // protected async sendBatchResponse(context: TExecutionContext, requestContext: BatchRequestContext) {
81
+ // const resp = context.getResponse();
82
+ // resp.setStatus(HttpStatus.OK);
83
+ // resp.setHeader(HttpHeaderCodes.Cache_Control, 'no-cache');
84
+ // resp.setHeader(HttpHeaderCodes.Pragma, 'no-cache');
85
+ // resp.setHeader(HttpHeaderCodes.Expires, '-1');
86
+ // if (requestContext.headers) {
87
+ // for (const [k, v] of Object.entries(requestContext.headers)) {
88
+ // if (v)
89
+ // resp.setHeader(k, v);
90
+ // }
91
+ // }
92
+ // const boundary = 'batch_' + uuid();
93
+ // resp.setHeader(HttpHeaderCodes.Content_Type, 'multipart/mixed;boundary=' + boundary);
94
+ // resp.setHeader(HttpHeaderCodes.X_Opra_Version, OpraSchema.Version);
95
+ //
96
+ // const bodyBuilder = new HttpMultipartData();
97
+ //
98
+ // const chunks: any[] = [];
99
+ // let msgIdx = 0;
100
+ // for (const ctx of requestContext.queries) {
101
+ // msgIdx++;
102
+ // const out = this.createOutput(ctx);
103
+ //
104
+ //
105
+ // // chunks.push('--' + boundary + CRLF);
106
+ // // let s = 'Content-Type: application/http' + CRLF +
107
+ // // 'Content-Transfer-Encoding: binary' + CRLF +
108
+ // // 'Content-ID:' + (ctx.contentId || msgIdx) + CRLF +
109
+ // // CRLF +
110
+ // // 'HTTP/1.1 ' + out.status + (HttpStatus[out.status] || 'Unknown') + CRLF;
111
+ //
112
+ // let body = out.body;
113
+ // const headers = out.headers || {};
114
+ // if (body) {
115
+ // const contentType = String(headers['content-type'] || '').split(/\s*;\s*/);
116
+ // let charset = '';
117
+ // if (Highland.isStream(body)) {
118
+ // const l = parseInt(String(headers['content-length']), 10);
119
+ // if (isNaN(l))
120
+ // throw new TypeError('"content-length" header required for streamed responses');
121
+ // } else if (typeof body === 'object') {
122
+ // if (typeof body.stream === 'function') { // File and Blob
123
+ // contentType[0] = body.type || 'binary';
124
+ // headers['content-length'] = String(body.size);
125
+ // body = body.stream();
126
+ // } else if (Buffer.isBuffer(body)) {
127
+ // headers['content-length'] = String(body.length);
128
+ // } else {
129
+ // contentType[0] = contentType[0] || 'application/json';
130
+ // charset = 'utf-8';
131
+ // body = Buffer.from(JSON.stringify(body), 'utf-8');
132
+ // headers['content-length'] = String(body.length);
133
+ // }
134
+ // } else {
135
+ // contentType[0] = contentType[0] || 'text/plain';
136
+ // charset = 'utf-8';
137
+ // body = Buffer.from(String(body), 'utf-8');
138
+ // headers['content-length'] = String(body.length);
139
+ // }
140
+ // if (contentType[0]) {
141
+ // if (charset) {
142
+ // const i = contentType.findIndex(x => CHARSET_PATTERN.test(String(x)));
143
+ // if (i > 0) contentType[i] = 'charset=' + charset;
144
+ // else contentType.join('charset=' + charset);
145
+ // }
146
+ // headers['content-type'] = contentType.join(';');
147
+ // }
148
+ // }
149
+ // for (const [k, v] of Object.entries(headers))
150
+ // s += k + ': ' + (Array.isArray(v) ? v.join(';') : v) + CRLF
151
+ //
152
+ // chunks.push(s + CRLF);
153
+ //
154
+ // if (body) {
155
+ // if (typeof body === 'string')
156
+ // chunks.push(body + CRLF + CRLF);
157
+ // else {
158
+ // chunks.push(body);
159
+ // chunks.push(CRLF + CRLF);
160
+ // }
161
+ // }
162
+ // }
163
+ //
164
+ // chunks.push('--' + boundary + '--' + CRLF);
165
+ //
166
+ // resp.setHeader('content-type', 'multipart/mixed;boundary=' + boundary);
167
+ // resp.send(Highland(chunks).flatten());
168
+ // resp.end();
169
+ // }
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseCollectionRequest = void 0;
4
+ const common_1 = require("@opra/common");
5
+ const request_host_js_1 = require("../../request.host.js");
6
+ async function parseCollectionRequest(incoming, resource, url) {
7
+ if ((incoming.method === 'POST' || incoming.method === 'PATCH') &&
8
+ incoming.headers['content-type'] !== 'application/json')
9
+ throw new common_1.BadRequestError({ message: 'Unsupported Content-Type' });
10
+ url.searchParams.define({
11
+ '$search': { codec: 'string' },
12
+ '$pick': { codec: 'string', array: 'strict' },
13
+ '$omit': { codec: 'string', array: 'strict' },
14
+ '$include': { codec: 'string', array: 'strict' },
15
+ '$sort': { codec: 'string', array: 'strict' },
16
+ '$filter': { codec: 'filter' },
17
+ '$limit': { codec: 'number' },
18
+ '$skip': { codec: 'number' },
19
+ '$distinct': { codec: 'boolean' },
20
+ '$count': { codec: 'boolean' },
21
+ });
22
+ url.parse(incoming.url || '');
23
+ const contentId = incoming.headers['content-id'];
24
+ const p = url.path.get(0);
25
+ const params = url.searchParams;
26
+ switch (incoming.method) {
27
+ case 'POST': {
28
+ if (!p.key) {
29
+ const pick = params.get('$pick');
30
+ const omit = params.get('$omit');
31
+ const include = params.get('$include');
32
+ return new request_host_js_1.RequestHost({
33
+ http: incoming,
34
+ kind: 'CollectionCreateRequest',
35
+ contentId,
36
+ resource,
37
+ operation: 'create',
38
+ crud: 'create',
39
+ many: false,
40
+ args: {
41
+ data: incoming.body,
42
+ pick: pick && resource.normalizeFieldPath(pick),
43
+ omit: omit && resource.normalizeFieldPath(omit),
44
+ include: include && resource.normalizeFieldPath(include)
45
+ }
46
+ });
47
+ }
48
+ break;
49
+ }
50
+ case 'DELETE': {
51
+ if (p.key) {
52
+ return new request_host_js_1.RequestHost({
53
+ http: incoming,
54
+ kind: 'CollectionDeleteRequest',
55
+ contentId,
56
+ resource,
57
+ operation: 'delete',
58
+ crud: 'delete',
59
+ many: false,
60
+ args: {
61
+ key: resource.parseKeyValue(p.key)
62
+ }
63
+ });
64
+ }
65
+ const filter = resource.normalizeFilter(params.get('$filter'));
66
+ return new request_host_js_1.RequestHost({
67
+ http: incoming,
68
+ kind: 'CollectionDeleteManyRequest',
69
+ contentId,
70
+ resource,
71
+ operation: 'deleteMany',
72
+ crud: 'delete',
73
+ many: true,
74
+ args: {
75
+ filter
76
+ }
77
+ });
78
+ }
79
+ case 'GET': {
80
+ const pick = params.get('$pick');
81
+ const omit = params.get('$omit');
82
+ const include = params.get('$include');
83
+ if (p.key) {
84
+ return new request_host_js_1.RequestHost({
85
+ http: incoming,
86
+ kind: 'CollectionGetRequest',
87
+ contentId,
88
+ resource,
89
+ operation: 'get',
90
+ crud: 'read',
91
+ many: false,
92
+ args: {
93
+ key: resource.parseKeyValue(p.key),
94
+ pick: pick && resource.normalizeFieldPath(pick),
95
+ omit: omit && resource.normalizeFieldPath(omit),
96
+ include: include && resource.normalizeFieldPath(include)
97
+ }
98
+ });
99
+ }
100
+ const filter = resource.normalizeFilter(params.get('$filter'));
101
+ const sort = params.get('$sort');
102
+ return new request_host_js_1.RequestHost({
103
+ http: incoming,
104
+ kind: 'CollectionFindManyRequest',
105
+ contentId,
106
+ resource,
107
+ operation: 'findMany',
108
+ crud: 'read',
109
+ many: true,
110
+ args: {
111
+ pick: pick && resource.normalizeFieldPath(pick),
112
+ omit: omit && resource.normalizeFieldPath(omit),
113
+ include: include && resource.normalizeFieldPath(include),
114
+ sort: sort && resource.normalizeSortFields(sort),
115
+ filter,
116
+ limit: params.get('$limit'),
117
+ skip: params.get('$skip'),
118
+ distinct: params.get('$distinct'),
119
+ count: params.get('$count'),
120
+ }
121
+ });
122
+ }
123
+ case 'PATCH': {
124
+ if (p.key) {
125
+ const pick = params.get('$pick');
126
+ const omit = params.get('$omit');
127
+ const include = params.get('$include');
128
+ return new request_host_js_1.RequestHost({
129
+ http: incoming,
130
+ kind: 'CollectionUpdateRequest',
131
+ contentId,
132
+ resource,
133
+ operation: 'update',
134
+ crud: 'update',
135
+ many: false,
136
+ args: {
137
+ key: resource.parseKeyValue(p.key),
138
+ data: incoming.body,
139
+ pick: pick && resource.normalizeFieldPath(pick),
140
+ omit: omit && resource.normalizeFieldPath(omit),
141
+ include: include && resource.normalizeFieldPath(include),
142
+ }
143
+ });
144
+ }
145
+ const filter = resource.normalizeFilter(params.get('$filter'));
146
+ return new request_host_js_1.RequestHost({
147
+ http: incoming,
148
+ kind: 'CollectionUpdateManyRequest',
149
+ contentId,
150
+ resource,
151
+ operation: 'updateMany',
152
+ crud: 'update',
153
+ many: true,
154
+ args: {
155
+ data: incoming.body,
156
+ filter,
157
+ }
158
+ });
159
+ }
160
+ }
161
+ throw new common_1.MethodNotAllowedError({
162
+ message: `Collection resource does not accept http "${incoming.method}" method`
163
+ });
164
+ }
165
+ exports.parseCollectionRequest = parseCollectionRequest;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseRequest = void 0;
4
+ const common_1 = require("@opra/common");
5
+ const parse_collection_request_js_1 = require("./parse-collection-request.js");
6
+ const parse_singleton_request_js_1 = require("./parse-singleton-request.js");
7
+ async function parseRequest(api, incoming) {
8
+ const url = new common_1.OpraURL(incoming.url);
9
+ if (!url.path.size) {
10
+ // Batch
11
+ if (incoming.headers['content-type'] === 'multipart/mixed') {
12
+ // todo
13
+ }
14
+ throw new common_1.BadRequestError();
15
+ }
16
+ const p = url.path.get(0);
17
+ const resource = api.getResource(p.resource);
18
+ if (resource instanceof common_1.Collection)
19
+ return await (0, parse_collection_request_js_1.parseCollectionRequest)(incoming, resource, url);
20
+ if (resource instanceof common_1.Singleton)
21
+ return await (0, parse_singleton_request_js_1.parseSingletonRequest)(incoming, resource, url);
22
+ throw new common_1.BadRequestError();
23
+ }
24
+ exports.parseRequest = parseRequest;
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseSingletonRequest = void 0;
4
+ const common_1 = require("@opra/common");
5
+ const request_host_js_1 = require("../../request.host.js");
6
+ async function parseSingletonRequest(incoming, resource, url) {
7
+ if ((incoming.method === 'POST' || incoming.method === 'PATCH') &&
8
+ incoming.headers['content-type'] !== 'application/json')
9
+ throw new common_1.BadRequestError({ message: 'Unsupported Content-Type' });
10
+ url.searchParams.define({
11
+ '$pick': { codec: 'string', array: 'strict' },
12
+ '$omit': { codec: 'string', array: 'strict' },
13
+ '$include': { codec: 'string', array: 'strict' }
14
+ });
15
+ url.parse(incoming.url || '');
16
+ const contentId = incoming.headers['content-id'];
17
+ const params = url.searchParams;
18
+ switch (incoming.method) {
19
+ case 'POST': {
20
+ const pick = params.get('$pick');
21
+ const omit = params.get('$omit');
22
+ const include = params.get('$include');
23
+ return new request_host_js_1.RequestHost({
24
+ http: incoming,
25
+ kind: 'SingletonCreateRequest',
26
+ contentId,
27
+ resource,
28
+ operation: 'create',
29
+ crud: 'create',
30
+ many: false,
31
+ args: {
32
+ data: incoming.body,
33
+ pick: pick && resource.normalizeFieldPath(pick),
34
+ omit: omit && resource.normalizeFieldPath(omit),
35
+ include: include && resource.normalizeFieldPath(include),
36
+ }
37
+ });
38
+ }
39
+ case 'DELETE': {
40
+ return new request_host_js_1.RequestHost({
41
+ http: incoming,
42
+ kind: 'SingletonDeleteRequest',
43
+ contentId,
44
+ resource,
45
+ operation: 'delete',
46
+ crud: 'delete',
47
+ many: false,
48
+ args: {}
49
+ });
50
+ }
51
+ case 'GET': {
52
+ const pick = params.get('$pick');
53
+ const omit = params.get('$omit');
54
+ const include = params.get('$include');
55
+ return new request_host_js_1.RequestHost({
56
+ http: incoming,
57
+ kind: 'SingletonGetRequest',
58
+ contentId,
59
+ resource,
60
+ operation: 'get',
61
+ crud: 'read',
62
+ many: false,
63
+ args: {
64
+ pick: pick && resource.normalizeFieldPath(pick),
65
+ omit: omit && resource.normalizeFieldPath(omit),
66
+ include: include && resource.normalizeFieldPath(include),
67
+ }
68
+ });
69
+ }
70
+ case 'PATCH': {
71
+ const pick = params.get('$pick');
72
+ const omit = params.get('$omit');
73
+ const include = params.get('$include');
74
+ return new request_host_js_1.RequestHost({
75
+ http: incoming,
76
+ kind: 'SingletonUpdateRequest',
77
+ contentId,
78
+ resource,
79
+ operation: 'update',
80
+ crud: 'update',
81
+ many: false,
82
+ args: {
83
+ data: incoming.body,
84
+ pick: pick && resource.normalizeFieldPath(pick),
85
+ omit: omit && resource.normalizeFieldPath(omit),
86
+ include: include && resource.normalizeFieldPath(include),
87
+ }
88
+ });
89
+ }
90
+ default:
91
+ throw new common_1.MethodNotAllowedError({
92
+ message: `Singleton resource does not accept http "${incoming.method}" method`
93
+ });
94
+ }
95
+ }
96
+ exports.parseSingletonRequest = parseSingletonRequest;
@@ -10,6 +10,14 @@ class RequestContextHost extends strict_typed_events_1.AsyncEventEmitter {
10
10
  this.api = api;
11
11
  this._request = _request;
12
12
  this._response = _response;
13
+ if (this.protocol === 'http') {
14
+ this._http = {
15
+ platform,
16
+ request: this._request.switchToHttp(),
17
+ response: this._response.switchToHttp(),
18
+ switchToContext: () => this
19
+ };
20
+ }
13
21
  }
14
22
  get request() {
15
23
  return this._request;
@@ -18,13 +26,19 @@ class RequestContextHost extends strict_typed_events_1.AsyncEventEmitter {
18
26
  return this._response;
19
27
  }
20
28
  switchToHttp() {
21
- throw new TypeError('Not executing in an "Http" protocol');
29
+ if (this._http)
30
+ return this._http;
31
+ throw new TypeError('Not executing in an "Http" context');
22
32
  }
23
33
  switchToWs() {
24
- throw new TypeError('Not executing in an "WebSocket" protocol');
34
+ if (this._ws)
35
+ return this._ws;
36
+ throw new TypeError('Not executing in an "WebSocket" context');
25
37
  }
26
38
  switchToRpc() {
27
- throw new TypeError('Not executing in an "RPC" protocol');
39
+ if (this._rpc)
40
+ return this._rpc;
41
+ throw new TypeError('Not executing in an "RPC" context');
28
42
  }
29
43
  }
30
44
  exports.RequestContextHost = RequestContextHost;
@@ -3,17 +3,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RequestHost = void 0;
4
4
  class RequestHost {
5
5
  constructor(init) {
6
+ this.contentId = '';
6
7
  Object.assign(this, init);
7
8
  this.resourceKind = this.resource.kind;
8
9
  }
9
10
  switchToHttp() {
10
- throw new TypeError('Not executing in an "Http" protocol');
11
+ if (this.http)
12
+ return this.http;
13
+ throw new TypeError('Not executing in an "Http" context');
11
14
  }
12
15
  switchToWs() {
13
- throw new TypeError('Not executing in an "WebSocket" protocol');
16
+ throw new TypeError('Not executing in an "WebSocket" context');
14
17
  }
15
18
  switchToRpc() {
16
- throw new TypeError('Not executing in an "RPC" protocol');
19
+ throw new TypeError('Not executing in an "RPC" context');
17
20
  }
18
21
  }
19
22
  exports.RequestHost = RequestHost;
@@ -8,13 +8,15 @@ class ResponseHost {
8
8
  this.errors = this.errors || [];
9
9
  }
10
10
  switchToHttp() {
11
- throw new TypeError('Not executing in an "Http" protocol');
11
+ if (this.http)
12
+ return this.http;
13
+ throw new TypeError('Not executing in an "Http" context');
12
14
  }
13
15
  switchToWs() {
14
- throw new TypeError('Not executing in an "WebSocket" protocol');
16
+ throw new TypeError('Not executing in an "WebSocket" context');
15
17
  }
16
18
  switchToRpc() {
17
- throw new TypeError('Not executing in an "RPC" protocol');
19
+ throw new TypeError('Not executing in an "RPC" context');
18
20
  }
19
21
  }
20
22
  exports.ResponseHost = ResponseHost;
package/cjs/index.js CHANGED
@@ -7,8 +7,10 @@ tslib_1.__exportStar(require("./types.js"), exports);
7
7
  tslib_1.__exportStar(require("./adapter/adapter.js"), exports);
8
8
  tslib_1.__exportStar(require("./adapter/http/express-adapter.js"), exports);
9
9
  tslib_1.__exportStar(require("./adapter/http/http-adapter.js"), exports);
10
- tslib_1.__exportStar(require("./adapter/http/http-request-message.js"), exports);
11
- tslib_1.__exportStar(require("./adapter/http/http-response-message.js"), exports);
10
+ tslib_1.__exportStar(require("./adapter/http/impl/http-server-request.js"), exports);
11
+ tslib_1.__exportStar(require("./adapter/http/impl/http-server-response.js"), exports);
12
+ tslib_1.__exportStar(require("./adapter/http/impl/http-incoming-message-host.js"), exports);
13
+ tslib_1.__exportStar(require("./adapter/http/impl/http-outgoing-message-host.js"), exports);
12
14
  tslib_1.__exportStar(require("./adapter/interfaces/request-context.interface.js"), exports);
13
15
  tslib_1.__exportStar(require("./adapter/interfaces/logger.interface.js"), exports);
14
16
  tslib_1.__exportStar(require("./adapter/interfaces/request.interface.js"), exports);
@@ -28,7 +28,7 @@ export class OpraAdapter extends AsyncEventEmitter {
28
28
  await this.i18n.init();
29
29
  if (options?.onRequest)
30
30
  this.on('request', options.onRequest);
31
- this._internalDoc = await DocumentFactory.createDocument({
31
+ this._apiRoot = await DocumentFactory.createDocument({
32
32
  version: OpraSchema.SpecVersion,
33
33
  info: {
34
34
  version: OpraSchema.SpecVersion,
@@ -83,7 +83,7 @@ export class OpraAdapter extends AsyncEventEmitter {
83
83
  if (typeof response.value === 'object')
84
84
  affected = response.value.affectedRows || response.value.affected;
85
85
  response.value = {
86
- operation: request.operation,
86
+ operation: request.crud,
87
87
  affected
88
88
  };
89
89
  }
@@ -1,21 +1,21 @@
1
- import bodyParser from 'body-parser';
2
1
  import { normalizePath } from '@opra/common';
3
2
  import { OpraHttpAdapter } from './http-adapter.js';
4
- const noOp = () => void 0;
3
+ import { HttpServerRequest } from './impl/http-server-request.js';
4
+ import { HttpServerResponse } from './impl/http-server-response.js';
5
5
  export class OpraExpressAdapter extends OpraHttpAdapter {
6
6
  constructor() {
7
7
  super(...arguments);
8
8
  this.platform = 'express';
9
9
  }
10
10
  static async create(app, document, options) {
11
+ const express = await import('express');
11
12
  const adapter = new OpraExpressAdapter(document);
12
13
  await adapter.init(options);
13
14
  const prefix = '/' + normalizePath(options?.prefix, true);
14
- app.use(prefix, bodyParser.json());
15
+ app.use(prefix, express.json());
15
16
  app.use(prefix, (req, res, next) => {
16
- req.end = noOp;
17
- req.send = noOp;
18
- adapter.handler(req, res).catch(e => next(e));
17
+ adapter.handler(HttpServerRequest.create(req), HttpServerResponse.create(res))
18
+ .catch(e => next(e));
19
19
  });
20
20
  return adapter;
21
21
  }
@@ -0,0 +1,60 @@
1
+ /*
2
+ This file contains code blocks from open source NodeJs project
3
+ https://github.com/nodejs/
4
+ */
5
+ const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/;
6
+ const nodeInternalPrefix = '__node_internal_';
7
+ /**
8
+ * Verifies that the given val is a valid HTTP token
9
+ * per the rules defined in RFC 7230
10
+ * See https://tools.ietf.org/html/rfc7230#section-3.2.6
11
+ *
12
+ * https://github.com/nodejs/node/blob/main/lib/_http_common.js
13
+ */
14
+ export function checkIsHttpToken(val) {
15
+ return typeof val === 'string' && tokenRegExp.exec(val) !== null;
16
+ }
17
+ const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
18
+ /**
19
+ * True if val contains an invalid field-vchar
20
+ * field-value = *( field-content / obs-fold )
21
+ * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
22
+ * field-vchar = VCHAR / obs-text
23
+ *
24
+ * https://github.com/nodejs/node/blob/main/lib/_http_common.js
25
+ */
26
+ function checkInvalidHeaderChar(val) {
27
+ // noinspection SuspiciousTypeOfGuard
28
+ return typeof val === 'string' && headerCharRegex.exec(val) !== null;
29
+ }
30
+ /**
31
+ * This function removes unnecessary frames from Node.js core errors.
32
+ *
33
+ * https://github.com/nodejs/node/blob/main/lib/internal/errors.js
34
+ */
35
+ export function hideStackFrames(fn) {
36
+ // We rename the functions that will be hidden to cut off the stacktrace
37
+ // at the outermost one
38
+ const hidden = nodeInternalPrefix + fn.name;
39
+ // @ts-ignore
40
+ Object.defineProperty(fn, 'name', { __proto__: null, value: hidden });
41
+ return fn;
42
+ }
43
+ export const validateHeaderName = hideStackFrames((name, label) => {
44
+ // noinspection SuspiciousTypeOfGuard
45
+ if (typeof name !== 'string' || !name || !checkIsHttpToken(name)) {
46
+ throw new TypeError(`${label || 'Header name'} must be a valid HTTP token ["${name}"]`);
47
+ }
48
+ });
49
+ export const validateHeaderValue = hideStackFrames((name, value) => {
50
+ if (value === undefined) {
51
+ throw new TypeError(`Invalid value "${value}" for header "${name}"`);
52
+ }
53
+ if (checkInvalidHeaderChar(value)) {
54
+ throw new TypeError(`Invalid character in header content ["${name}"]`);
55
+ }
56
+ });
57
+ export function validateString(value, name) {
58
+ if (typeof value !== 'string')
59
+ throw new TypeError(`Invalid ${name ? name + ' ' : ''}argument. Value must be a string`);
60
+ }