@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,161 @@
|
|
|
1
|
+
import { BadRequestError, MethodNotAllowedError } from '@opra/common';
|
|
2
|
+
import { RequestHost } from '../../request.host.js';
|
|
3
|
+
export async function parseCollectionRequest(incoming, resource, url) {
|
|
4
|
+
if ((incoming.method === 'POST' || incoming.method === 'PATCH') &&
|
|
5
|
+
incoming.headers['content-type'] !== 'application/json')
|
|
6
|
+
throw new BadRequestError({ message: 'Unsupported Content-Type' });
|
|
7
|
+
url.searchParams.define({
|
|
8
|
+
'$search': { codec: 'string' },
|
|
9
|
+
'$pick': { codec: 'string', array: 'strict' },
|
|
10
|
+
'$omit': { codec: 'string', array: 'strict' },
|
|
11
|
+
'$include': { codec: 'string', array: 'strict' },
|
|
12
|
+
'$sort': { codec: 'string', array: 'strict' },
|
|
13
|
+
'$filter': { codec: 'filter' },
|
|
14
|
+
'$limit': { codec: 'number' },
|
|
15
|
+
'$skip': { codec: 'number' },
|
|
16
|
+
'$distinct': { codec: 'boolean' },
|
|
17
|
+
'$count': { codec: 'boolean' },
|
|
18
|
+
});
|
|
19
|
+
url.parse(incoming.url || '');
|
|
20
|
+
const contentId = incoming.headers['content-id'];
|
|
21
|
+
const p = url.path.get(0);
|
|
22
|
+
const params = url.searchParams;
|
|
23
|
+
switch (incoming.method) {
|
|
24
|
+
case 'POST': {
|
|
25
|
+
if (!p.key) {
|
|
26
|
+
const pick = params.get('$pick');
|
|
27
|
+
const omit = params.get('$omit');
|
|
28
|
+
const include = params.get('$include');
|
|
29
|
+
return new RequestHost({
|
|
30
|
+
http: incoming,
|
|
31
|
+
kind: 'CollectionCreateRequest',
|
|
32
|
+
contentId,
|
|
33
|
+
resource,
|
|
34
|
+
operation: 'create',
|
|
35
|
+
crud: 'create',
|
|
36
|
+
many: false,
|
|
37
|
+
args: {
|
|
38
|
+
data: incoming.body,
|
|
39
|
+
pick: pick && resource.normalizeFieldPath(pick),
|
|
40
|
+
omit: omit && resource.normalizeFieldPath(omit),
|
|
41
|
+
include: include && resource.normalizeFieldPath(include)
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
case 'DELETE': {
|
|
48
|
+
if (p.key) {
|
|
49
|
+
return new RequestHost({
|
|
50
|
+
http: incoming,
|
|
51
|
+
kind: 'CollectionDeleteRequest',
|
|
52
|
+
contentId,
|
|
53
|
+
resource,
|
|
54
|
+
operation: 'delete',
|
|
55
|
+
crud: 'delete',
|
|
56
|
+
many: false,
|
|
57
|
+
args: {
|
|
58
|
+
key: resource.parseKeyValue(p.key)
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const filter = resource.normalizeFilter(params.get('$filter'));
|
|
63
|
+
return new RequestHost({
|
|
64
|
+
http: incoming,
|
|
65
|
+
kind: 'CollectionDeleteManyRequest',
|
|
66
|
+
contentId,
|
|
67
|
+
resource,
|
|
68
|
+
operation: 'deleteMany',
|
|
69
|
+
crud: 'delete',
|
|
70
|
+
many: true,
|
|
71
|
+
args: {
|
|
72
|
+
filter
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
case 'GET': {
|
|
77
|
+
const pick = params.get('$pick');
|
|
78
|
+
const omit = params.get('$omit');
|
|
79
|
+
const include = params.get('$include');
|
|
80
|
+
if (p.key) {
|
|
81
|
+
return new RequestHost({
|
|
82
|
+
http: incoming,
|
|
83
|
+
kind: 'CollectionGetRequest',
|
|
84
|
+
contentId,
|
|
85
|
+
resource,
|
|
86
|
+
operation: 'get',
|
|
87
|
+
crud: 'read',
|
|
88
|
+
many: false,
|
|
89
|
+
args: {
|
|
90
|
+
key: resource.parseKeyValue(p.key),
|
|
91
|
+
pick: pick && resource.normalizeFieldPath(pick),
|
|
92
|
+
omit: omit && resource.normalizeFieldPath(omit),
|
|
93
|
+
include: include && resource.normalizeFieldPath(include)
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
const filter = resource.normalizeFilter(params.get('$filter'));
|
|
98
|
+
const sort = params.get('$sort');
|
|
99
|
+
return new RequestHost({
|
|
100
|
+
http: incoming,
|
|
101
|
+
kind: 'CollectionFindManyRequest',
|
|
102
|
+
contentId,
|
|
103
|
+
resource,
|
|
104
|
+
operation: 'findMany',
|
|
105
|
+
crud: 'read',
|
|
106
|
+
many: true,
|
|
107
|
+
args: {
|
|
108
|
+
pick: pick && resource.normalizeFieldPath(pick),
|
|
109
|
+
omit: omit && resource.normalizeFieldPath(omit),
|
|
110
|
+
include: include && resource.normalizeFieldPath(include),
|
|
111
|
+
sort: sort && resource.normalizeSortFields(sort),
|
|
112
|
+
filter,
|
|
113
|
+
limit: params.get('$limit'),
|
|
114
|
+
skip: params.get('$skip'),
|
|
115
|
+
distinct: params.get('$distinct'),
|
|
116
|
+
count: params.get('$count'),
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
case 'PATCH': {
|
|
121
|
+
if (p.key) {
|
|
122
|
+
const pick = params.get('$pick');
|
|
123
|
+
const omit = params.get('$omit');
|
|
124
|
+
const include = params.get('$include');
|
|
125
|
+
return new RequestHost({
|
|
126
|
+
http: incoming,
|
|
127
|
+
kind: 'CollectionUpdateRequest',
|
|
128
|
+
contentId,
|
|
129
|
+
resource,
|
|
130
|
+
operation: 'update',
|
|
131
|
+
crud: 'update',
|
|
132
|
+
many: false,
|
|
133
|
+
args: {
|
|
134
|
+
key: resource.parseKeyValue(p.key),
|
|
135
|
+
data: incoming.body,
|
|
136
|
+
pick: pick && resource.normalizeFieldPath(pick),
|
|
137
|
+
omit: omit && resource.normalizeFieldPath(omit),
|
|
138
|
+
include: include && resource.normalizeFieldPath(include),
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const filter = resource.normalizeFilter(params.get('$filter'));
|
|
143
|
+
return new RequestHost({
|
|
144
|
+
http: incoming,
|
|
145
|
+
kind: 'CollectionUpdateManyRequest',
|
|
146
|
+
contentId,
|
|
147
|
+
resource,
|
|
148
|
+
operation: 'updateMany',
|
|
149
|
+
crud: 'update',
|
|
150
|
+
many: true,
|
|
151
|
+
args: {
|
|
152
|
+
data: incoming.body,
|
|
153
|
+
filter,
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
throw new MethodNotAllowedError({
|
|
159
|
+
message: `Collection resource does not accept http "${incoming.method}" method`
|
|
160
|
+
});
|
|
161
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { BadRequestError, Collection, OpraURL, Singleton } from '@opra/common';
|
|
2
|
+
import { parseCollectionRequest } from './parse-collection-request.js';
|
|
3
|
+
import { parseSingletonRequest } from './parse-singleton-request.js';
|
|
4
|
+
export async function parseRequest(api, incoming) {
|
|
5
|
+
const url = new OpraURL(incoming.url);
|
|
6
|
+
if (!url.path.size) {
|
|
7
|
+
// Batch
|
|
8
|
+
if (incoming.headers['content-type'] === 'multipart/mixed') {
|
|
9
|
+
// todo
|
|
10
|
+
}
|
|
11
|
+
throw new BadRequestError();
|
|
12
|
+
}
|
|
13
|
+
const p = url.path.get(0);
|
|
14
|
+
const resource = api.getResource(p.resource);
|
|
15
|
+
if (resource instanceof Collection)
|
|
16
|
+
return await parseCollectionRequest(incoming, resource, url);
|
|
17
|
+
if (resource instanceof Singleton)
|
|
18
|
+
return await parseSingletonRequest(incoming, resource, url);
|
|
19
|
+
throw new BadRequestError();
|
|
20
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { BadRequestError, MethodNotAllowedError } from '@opra/common';
|
|
2
|
+
import { RequestHost } from '../../request.host.js';
|
|
3
|
+
export async function parseSingletonRequest(incoming, resource, url) {
|
|
4
|
+
if ((incoming.method === 'POST' || incoming.method === 'PATCH') &&
|
|
5
|
+
incoming.headers['content-type'] !== 'application/json')
|
|
6
|
+
throw new BadRequestError({ message: 'Unsupported Content-Type' });
|
|
7
|
+
url.searchParams.define({
|
|
8
|
+
'$pick': { codec: 'string', array: 'strict' },
|
|
9
|
+
'$omit': { codec: 'string', array: 'strict' },
|
|
10
|
+
'$include': { codec: 'string', array: 'strict' }
|
|
11
|
+
});
|
|
12
|
+
url.parse(incoming.url || '');
|
|
13
|
+
const contentId = incoming.headers['content-id'];
|
|
14
|
+
const params = url.searchParams;
|
|
15
|
+
switch (incoming.method) {
|
|
16
|
+
case 'POST': {
|
|
17
|
+
const pick = params.get('$pick');
|
|
18
|
+
const omit = params.get('$omit');
|
|
19
|
+
const include = params.get('$include');
|
|
20
|
+
return new RequestHost({
|
|
21
|
+
http: incoming,
|
|
22
|
+
kind: 'SingletonCreateRequest',
|
|
23
|
+
contentId,
|
|
24
|
+
resource,
|
|
25
|
+
operation: 'create',
|
|
26
|
+
crud: 'create',
|
|
27
|
+
many: false,
|
|
28
|
+
args: {
|
|
29
|
+
data: incoming.body,
|
|
30
|
+
pick: pick && resource.normalizeFieldPath(pick),
|
|
31
|
+
omit: omit && resource.normalizeFieldPath(omit),
|
|
32
|
+
include: include && resource.normalizeFieldPath(include),
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
case 'DELETE': {
|
|
37
|
+
return new RequestHost({
|
|
38
|
+
http: incoming,
|
|
39
|
+
kind: 'SingletonDeleteRequest',
|
|
40
|
+
contentId,
|
|
41
|
+
resource,
|
|
42
|
+
operation: 'delete',
|
|
43
|
+
crud: 'delete',
|
|
44
|
+
many: false,
|
|
45
|
+
args: {}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
case 'GET': {
|
|
49
|
+
const pick = params.get('$pick');
|
|
50
|
+
const omit = params.get('$omit');
|
|
51
|
+
const include = params.get('$include');
|
|
52
|
+
return new RequestHost({
|
|
53
|
+
http: incoming,
|
|
54
|
+
kind: 'SingletonGetRequest',
|
|
55
|
+
contentId,
|
|
56
|
+
resource,
|
|
57
|
+
operation: 'get',
|
|
58
|
+
crud: 'read',
|
|
59
|
+
many: false,
|
|
60
|
+
args: {
|
|
61
|
+
pick: pick && resource.normalizeFieldPath(pick),
|
|
62
|
+
omit: omit && resource.normalizeFieldPath(omit),
|
|
63
|
+
include: include && resource.normalizeFieldPath(include),
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
case 'PATCH': {
|
|
68
|
+
const pick = params.get('$pick');
|
|
69
|
+
const omit = params.get('$omit');
|
|
70
|
+
const include = params.get('$include');
|
|
71
|
+
return new RequestHost({
|
|
72
|
+
http: incoming,
|
|
73
|
+
kind: 'SingletonUpdateRequest',
|
|
74
|
+
contentId,
|
|
75
|
+
resource,
|
|
76
|
+
operation: 'update',
|
|
77
|
+
crud: 'update',
|
|
78
|
+
many: false,
|
|
79
|
+
args: {
|
|
80
|
+
data: incoming.body,
|
|
81
|
+
pick: pick && resource.normalizeFieldPath(pick),
|
|
82
|
+
omit: omit && resource.normalizeFieldPath(omit),
|
|
83
|
+
include: include && resource.normalizeFieldPath(include),
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
default:
|
|
88
|
+
throw new MethodNotAllowedError({
|
|
89
|
+
message: `Singleton resource does not accept http "${incoming.method}" method`
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -7,6 +7,14 @@ export class RequestContextHost extends AsyncEventEmitter {
|
|
|
7
7
|
this.api = api;
|
|
8
8
|
this._request = _request;
|
|
9
9
|
this._response = _response;
|
|
10
|
+
if (this.protocol === 'http') {
|
|
11
|
+
this._http = {
|
|
12
|
+
platform,
|
|
13
|
+
request: this._request.switchToHttp(),
|
|
14
|
+
response: this._response.switchToHttp(),
|
|
15
|
+
switchToContext: () => this
|
|
16
|
+
};
|
|
17
|
+
}
|
|
10
18
|
}
|
|
11
19
|
get request() {
|
|
12
20
|
return this._request;
|
|
@@ -15,12 +23,18 @@ export class RequestContextHost extends AsyncEventEmitter {
|
|
|
15
23
|
return this._response;
|
|
16
24
|
}
|
|
17
25
|
switchToHttp() {
|
|
18
|
-
|
|
26
|
+
if (this._http)
|
|
27
|
+
return this._http;
|
|
28
|
+
throw new TypeError('Not executing in an "Http" context');
|
|
19
29
|
}
|
|
20
30
|
switchToWs() {
|
|
21
|
-
|
|
31
|
+
if (this._ws)
|
|
32
|
+
return this._ws;
|
|
33
|
+
throw new TypeError('Not executing in an "WebSocket" context');
|
|
22
34
|
}
|
|
23
35
|
switchToRpc() {
|
|
24
|
-
|
|
36
|
+
if (this._rpc)
|
|
37
|
+
return this._rpc;
|
|
38
|
+
throw new TypeError('Not executing in an "RPC" context');
|
|
25
39
|
}
|
|
26
40
|
}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
export class RequestHost {
|
|
2
2
|
constructor(init) {
|
|
3
|
+
this.contentId = '';
|
|
3
4
|
Object.assign(this, init);
|
|
4
5
|
this.resourceKind = this.resource.kind;
|
|
5
6
|
}
|
|
6
7
|
switchToHttp() {
|
|
7
|
-
|
|
8
|
+
if (this.http)
|
|
9
|
+
return this.http;
|
|
10
|
+
throw new TypeError('Not executing in an "Http" context');
|
|
8
11
|
}
|
|
9
12
|
switchToWs() {
|
|
10
|
-
throw new TypeError('Not executing in an "WebSocket"
|
|
13
|
+
throw new TypeError('Not executing in an "WebSocket" context');
|
|
11
14
|
}
|
|
12
15
|
switchToRpc() {
|
|
13
|
-
throw new TypeError('Not executing in an "RPC"
|
|
16
|
+
throw new TypeError('Not executing in an "RPC" context');
|
|
14
17
|
}
|
|
15
18
|
}
|
|
@@ -5,12 +5,14 @@ export class ResponseHost {
|
|
|
5
5
|
this.errors = this.errors || [];
|
|
6
6
|
}
|
|
7
7
|
switchToHttp() {
|
|
8
|
-
|
|
8
|
+
if (this.http)
|
|
9
|
+
return this.http;
|
|
10
|
+
throw new TypeError('Not executing in an "Http" context');
|
|
9
11
|
}
|
|
10
12
|
switchToWs() {
|
|
11
|
-
throw new TypeError('Not executing in an "WebSocket"
|
|
13
|
+
throw new TypeError('Not executing in an "WebSocket" context');
|
|
12
14
|
}
|
|
13
15
|
switchToRpc() {
|
|
14
|
-
throw new TypeError('Not executing in an "RPC"
|
|
16
|
+
throw new TypeError('Not executing in an "RPC" context');
|
|
15
17
|
}
|
|
16
18
|
}
|
package/esm/index.js
CHANGED
|
@@ -4,8 +4,10 @@ export * from './types.js';
|
|
|
4
4
|
export * from './adapter/adapter.js';
|
|
5
5
|
export * from './adapter/http/express-adapter.js';
|
|
6
6
|
export * from './adapter/http/http-adapter.js';
|
|
7
|
-
export * from './adapter/http/http-request
|
|
8
|
-
export * from './adapter/http/http-response
|
|
7
|
+
export * from './adapter/http/impl/http-server-request.js';
|
|
8
|
+
export * from './adapter/http/impl/http-server-response.js';
|
|
9
|
+
export * from './adapter/http/impl/http-incoming-message-host.js';
|
|
10
|
+
export * from './adapter/http/impl/http-outgoing-message-host.js';
|
|
9
11
|
export * from './adapter/interfaces/request-context.interface.js';
|
|
10
12
|
export * from './adapter/interfaces/logger.interface.js';
|
|
11
13
|
export * from './adapter/interfaces/request.interface.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opra/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Opra schema package",
|
|
5
5
|
"author": "Panates",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,23 +27,24 @@
|
|
|
27
27
|
"clean:cover": "rimraf ../../coverage/core"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@opra/common": "^0.
|
|
30
|
+
"@opra/common": "^0.22.0",
|
|
31
31
|
"accepts": "^1.3.8",
|
|
32
32
|
"content-disposition": "^0.5.4",
|
|
33
33
|
"content-type": "^1.0.5",
|
|
34
34
|
"cookie": "^0.5.0",
|
|
35
|
-
"
|
|
36
|
-
"
|
|
35
|
+
"cookie-signature": "^1.2.1",
|
|
36
|
+
"encodeurl": "^1.0.2",
|
|
37
|
+
"formidable": "^3.5.0",
|
|
38
|
+
"fresh": "^0.5.2",
|
|
37
39
|
"mime-types": "^2.1.35",
|
|
38
40
|
"power-tasks": "^1.7.0",
|
|
39
|
-
"putil-isplainobject": "^1.1.5",
|
|
40
|
-
"putil-merge": "^3.10.3",
|
|
41
41
|
"putil-varhelpers": "^1.6.5",
|
|
42
|
+
"range-parser": "^1.2.1",
|
|
42
43
|
"strict-typed-events": "^2.3.1",
|
|
43
|
-
"type-is": "^1.6.18"
|
|
44
|
+
"type-is": "^1.6.18",
|
|
45
|
+
"vary": "^1.1.2"
|
|
44
46
|
},
|
|
45
47
|
"peerDependencies": {
|
|
46
|
-
"body-parser": "^1.20.2",
|
|
47
48
|
"express": "^4.x.x || ^5.x.x"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
@@ -52,11 +53,15 @@
|
|
|
52
53
|
"@types/content-disposition": "^0.5.5",
|
|
53
54
|
"@types/content-type": "^1.1.5",
|
|
54
55
|
"@types/cookie": "^0.5.1",
|
|
55
|
-
"@types/
|
|
56
|
+
"@types/cookie-signature": "^1.1.0",
|
|
57
|
+
"@types/encodeurl": "^1.0.0",
|
|
56
58
|
"@types/express": "^4.17.17",
|
|
59
|
+
"@types/formidable": "^3.4.0",
|
|
60
|
+
"@types/fresh": "^0.5.0",
|
|
57
61
|
"@types/mime-types": "^2.1.1",
|
|
62
|
+
"@types/range-parser": "^1.2.4",
|
|
58
63
|
"@types/type-is": "^1.6.3",
|
|
59
|
-
"
|
|
64
|
+
"@types/vary": "^1.1.0",
|
|
60
65
|
"crypto-browserify": "^3.12.0",
|
|
61
66
|
"path-browserify": "^1.0.1",
|
|
62
67
|
"ts-gems": "^2.4.0"
|
|
@@ -88,11 +93,10 @@
|
|
|
88
93
|
"opra",
|
|
89
94
|
"rest",
|
|
90
95
|
"api",
|
|
91
|
-
"
|
|
92
|
-
"open",
|
|
93
|
-
"swagger",
|
|
94
|
-
"raml",
|
|
96
|
+
"openapi",
|
|
95
97
|
"http",
|
|
96
|
-
"web"
|
|
98
|
+
"web",
|
|
99
|
+
"swagger",
|
|
100
|
+
"raml"
|
|
97
101
|
]
|
|
98
102
|
}
|
|
@@ -44,7 +44,7 @@ export declare namespace OpraAdapter {
|
|
|
44
44
|
*/
|
|
45
45
|
export declare abstract class OpraAdapter extends AsyncEventEmitter {
|
|
46
46
|
readonly api: ApiDocument;
|
|
47
|
-
protected
|
|
47
|
+
protected _apiRoot: ApiDocument;
|
|
48
48
|
i18n: I18n;
|
|
49
49
|
logger?: ILogger;
|
|
50
50
|
protected constructor(api: ApiDocument);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifies that the given val is a valid HTTP token
|
|
3
|
+
* per the rules defined in RFC 7230
|
|
4
|
+
* See https://tools.ietf.org/html/rfc7230#section-3.2.6
|
|
5
|
+
*
|
|
6
|
+
* https://github.com/nodejs/node/blob/main/lib/_http_common.js
|
|
7
|
+
*/
|
|
8
|
+
export declare function checkIsHttpToken(val: any): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* This function removes unnecessary frames from Node.js core errors.
|
|
11
|
+
*
|
|
12
|
+
* https://github.com/nodejs/node/blob/main/lib/internal/errors.js
|
|
13
|
+
*/
|
|
14
|
+
export declare function hideStackFrames(fn: Function): Function;
|
|
15
|
+
export declare const validateHeaderName: Function;
|
|
16
|
+
export declare const validateHeaderValue: Function;
|
|
17
|
+
export declare function validateString(value: any, name?: string): void;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const NO_DUPLICATES_FIELD = 0;
|
|
2
|
+
export declare const COMMA_DELIMITED_FIELD = 1;
|
|
3
|
+
export declare const SEMICOLON_DELIMITED_FIELD = 2;
|
|
4
|
+
export declare const ARRAY_FIELD = 3;
|
|
5
|
+
export type FIELD_FLAG = typeof NO_DUPLICATES_FIELD | typeof COMMA_DELIMITED_FIELD | typeof SEMICOLON_DELIMITED_FIELD | typeof ARRAY_FIELD;
|
|
6
|
+
export declare function matchKnownFields(field: string): [string, FIELD_FLAG];
|