@opra/core 0.20.3 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/adapter/adapter.js +2 -2
- package/cjs/adapter/http/express-adapter.js +29 -7
- package/cjs/adapter/http/helpers/common.js +66 -0
- package/cjs/adapter/http/helpers/convert-to-headers.js +65 -0
- package/cjs/adapter/http/helpers/convert-to-raw-headers.js +25 -0
- package/cjs/adapter/http/helpers/match-known-fields.js +47 -0
- package/cjs/adapter/http/http-adapter.js +82 -440
- package/cjs/adapter/http/impl/http-incoming-message-host.js +127 -0
- package/cjs/adapter/http/impl/http-outgoing-message-host.js +210 -0
- package/cjs/adapter/http/impl/http-server-request.js +125 -0
- package/cjs/adapter/http/impl/http-server-response.js +226 -0
- package/cjs/adapter/http/request-parsers/batch-request-parser.js +169 -0
- package/cjs/adapter/http/request-parsers/parse-collection-request.js +165 -0
- package/cjs/adapter/http/request-parsers/parse-request.js +24 -0
- package/cjs/adapter/http/request-parsers/parse-singleton-request.js +96 -0
- package/cjs/adapter/request-context.host.js +17 -3
- package/cjs/adapter/request.host.js +6 -3
- package/cjs/adapter/response.host.js +5 -3
- package/cjs/index.js +4 -2
- package/esm/adapter/adapter.js +2 -2
- package/esm/adapter/http/express-adapter.js +6 -6
- package/esm/adapter/http/helpers/common.js +60 -0
- package/esm/adapter/http/helpers/convert-to-headers.js +60 -0
- package/esm/adapter/http/helpers/convert-to-raw-headers.js +21 -0
- package/esm/adapter/http/helpers/match-known-fields.js +43 -0
- package/esm/adapter/http/http-adapter.js +83 -441
- package/esm/adapter/http/impl/http-incoming-message-host.js +122 -0
- package/esm/adapter/http/impl/http-outgoing-message-host.js +205 -0
- package/esm/adapter/http/impl/http-server-request.js +121 -0
- package/esm/adapter/http/impl/http-server-response.js +222 -0
- package/esm/adapter/http/request-parsers/batch-request-parser.js +169 -0
- package/esm/adapter/http/request-parsers/parse-collection-request.js +161 -0
- package/esm/adapter/http/request-parsers/parse-request.js +20 -0
- package/esm/adapter/http/request-parsers/parse-singleton-request.js +92 -0
- package/esm/adapter/request-context.host.js +17 -3
- package/esm/adapter/request.host.js +6 -3
- package/esm/adapter/response.host.js +5 -3
- package/esm/index.js +4 -2
- package/package.json +19 -15
- package/types/adapter/adapter.d.ts +1 -1
- package/types/adapter/http/helpers/common.d.ts +17 -0
- package/types/adapter/http/helpers/convert-to-headers.d.ts +2 -0
- package/types/adapter/http/helpers/convert-to-raw-headers.d.ts +3 -0
- package/types/adapter/http/helpers/match-known-fields.d.ts +6 -0
- package/types/adapter/http/http-adapter.d.ts +7 -12
- package/types/adapter/http/impl/http-incoming-message-host.d.ts +58 -0
- package/types/adapter/http/impl/http-outgoing-message-host.d.ts +72 -0
- package/types/adapter/http/{http-request-message.d.ts → impl/http-server-request.d.ts} +52 -85
- package/types/adapter/http/impl/http-server-response.d.ts +137 -0
- package/types/adapter/http/request-parsers/batch-request-parser.d.ts +0 -0
- package/types/adapter/http/request-parsers/parse-collection-request.d.ts +4 -0
- package/types/adapter/http/request-parsers/parse-request.d.ts +4 -0
- package/types/adapter/http/request-parsers/parse-singleton-request.d.ts +4 -0
- package/types/adapter/interfaces/request-context.interface.d.ts +14 -10
- package/types/adapter/interfaces/request.interface.d.ts +3 -2
- package/types/adapter/interfaces/response.interface.d.ts +2 -2
- package/types/adapter/request-context.host.d.ts +9 -6
- package/types/adapter/request.host.d.ts +8 -4
- package/types/adapter/response.host.d.ts +6 -4
- package/types/index.d.ts +4 -2
- package/cjs/adapter/http/http-message.host.js +0 -251
- package/cjs/adapter/http/http-request-context.host.js +0 -28
- package/cjs/adapter/http/http-request-message.js +0 -152
- package/cjs/adapter/http/http-request.host.js +0 -14
- package/cjs/adapter/http/http-response-message.js +0 -238
- package/cjs/adapter/http/http-response.host.js +0 -14
- package/esm/adapter/http/http-message.host.js +0 -246
- package/esm/adapter/http/http-request-context.host.js +0 -24
- package/esm/adapter/http/http-request-message.js +0 -148
- package/esm/adapter/http/http-request.host.js +0 -10
- package/esm/adapter/http/http-response-message.js +0 -233
- package/esm/adapter/http/http-response.host.js +0 -10
- package/types/adapter/http/http-message.host.d.ts +0 -122
- package/types/adapter/http/http-request-context.host.d.ts +0 -16
- package/types/adapter/http/http-request.host.d.ts +0 -7
- package/types/adapter/http/http-response-message.d.ts +0 -321
- 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
|
-
|
|
29
|
+
if (this._http)
|
|
30
|
+
return this._http;
|
|
31
|
+
throw new TypeError('Not executing in an "Http" context');
|
|
22
32
|
}
|
|
23
33
|
switchToWs() {
|
|
24
|
-
|
|
34
|
+
if (this._ws)
|
|
35
|
+
return this._ws;
|
|
36
|
+
throw new TypeError('Not executing in an "WebSocket" context');
|
|
25
37
|
}
|
|
26
38
|
switchToRpc() {
|
|
27
|
-
|
|
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
|
-
|
|
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"
|
|
16
|
+
throw new TypeError('Not executing in an "WebSocket" context');
|
|
14
17
|
}
|
|
15
18
|
switchToRpc() {
|
|
16
|
-
throw new TypeError('Not executing in an "RPC"
|
|
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
|
-
|
|
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"
|
|
16
|
+
throw new TypeError('Not executing in an "WebSocket" context');
|
|
15
17
|
}
|
|
16
18
|
switchToRpc() {
|
|
17
|
-
throw new TypeError('Not executing in an "RPC"
|
|
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
|
|
11
|
-
tslib_1.__exportStar(require("./adapter/http/http-response
|
|
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);
|
package/esm/adapter/adapter.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
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,
|
|
15
|
+
app.use(prefix, express.json());
|
|
15
16
|
app.use(prefix, (req, res, next) => {
|
|
16
|
-
req.
|
|
17
|
-
|
|
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
|
+
}
|