@opra/core 0.26.3 → 0.26.5
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/http/http-adapter-host.js +80 -92
- package/cjs/index.js +1 -0
- package/cjs/platform-adapter.host.js +20 -4
- package/cjs/services/api-service.js +16 -0
- package/esm/http/http-adapter-host.js +81 -93
- package/esm/index.js +1 -0
- package/esm/platform-adapter.host.js +21 -5
- package/esm/services/api-service.js +12 -0
- package/i18n/en/error.json +2 -1
- package/package.json +5 -5
- package/types/augmentation/collection.augmentation.d.ts +32 -18
- package/types/augmentation/container.augmentation.d.ts +4 -3
- package/types/augmentation/singleton.augmentation.d.ts +20 -12
- package/types/augmentation/storage.augmentation.d.ts +16 -9
- package/types/index.d.ts +1 -0
- package/types/platform-adapter.host.d.ts +8 -3
- package/types/request.d.ts +2 -4
- package/types/request.host.d.ts +2 -0
- package/types/services/api-service.d.ts +6 -0
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.HttpAdapterHost = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
+
const assert_1 = tslib_1.__importDefault(require("assert"));
|
|
5
6
|
const promises_1 = tslib_1.__importDefault(require("fs/promises"));
|
|
6
7
|
const os_1 = tslib_1.__importDefault(require("os"));
|
|
7
|
-
const
|
|
8
|
+
const vg = tslib_1.__importStar(require("valgen"));
|
|
8
9
|
const common_1 = require("@opra/common");
|
|
9
10
|
const execution_context_host_js_1 = require("../execution-context.host.js");
|
|
10
11
|
const platform_adapter_host_js_1 = require("../platform-adapter.host.js");
|
|
@@ -102,7 +103,7 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
102
103
|
catch (e) {
|
|
103
104
|
if (e instanceof common_1.OpraException)
|
|
104
105
|
throw e;
|
|
105
|
-
if (e instanceof
|
|
106
|
+
if (e instanceof vg.ValidationError) {
|
|
106
107
|
throw new common_1.InternalServerError({
|
|
107
108
|
message: (0, common_1.translate)('error:RESPONSE_VALIDATION,', 'Response validation failed'),
|
|
108
109
|
code: 'RESPONSE_VALIDATION',
|
|
@@ -120,7 +121,7 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
120
121
|
let resource = this.api.root;
|
|
121
122
|
let request;
|
|
122
123
|
// Walk through container
|
|
123
|
-
while (resource instanceof common_1.Container) {
|
|
124
|
+
while (resource instanceof common_1.Container && i < parsedUrl.path.length) {
|
|
124
125
|
p = parsedUrl.path[i];
|
|
125
126
|
const r = resource.resources.get(p.resource);
|
|
126
127
|
if (r) {
|
|
@@ -167,14 +168,12 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
167
168
|
}
|
|
168
169
|
async _parseRequestAction(executionContext, resource, urlPath, searchParams) {
|
|
169
170
|
const p = urlPath[0];
|
|
170
|
-
const { controller, endpoint, handler } = await this.
|
|
171
|
+
const { controller, endpoint, handler } = await this.getActionHandler(resource, p.resource);
|
|
171
172
|
const { incoming } = executionContext.switchToHttp();
|
|
172
173
|
const contentId = incoming.headers['content-id'];
|
|
173
174
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
174
175
|
return new request_host_js_1.RequestHost({
|
|
175
176
|
endpoint,
|
|
176
|
-
operation: 'action',
|
|
177
|
-
action: endpoint.name,
|
|
178
177
|
controller,
|
|
179
178
|
handler,
|
|
180
179
|
http: incoming,
|
|
@@ -194,11 +193,10 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
194
193
|
const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'create');
|
|
195
194
|
const jsonReader = (0, json_body_loader_js_1.jsonBodyLoader)({ limit: endpoint.inputMaxContentSize }, endpoint);
|
|
196
195
|
let data = await jsonReader(incoming);
|
|
197
|
-
data = endpoint.
|
|
196
|
+
data = endpoint.decodeInput(data, { coerce: true });
|
|
198
197
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
199
198
|
return new request_host_js_1.RequestHost({
|
|
200
199
|
endpoint,
|
|
201
|
-
operation: 'create',
|
|
202
200
|
controller,
|
|
203
201
|
handler,
|
|
204
202
|
http: incoming,
|
|
@@ -220,7 +218,6 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
220
218
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
221
219
|
return new request_host_js_1.RequestHost({
|
|
222
220
|
endpoint,
|
|
223
|
-
operation: 'delete',
|
|
224
221
|
controller,
|
|
225
222
|
handler,
|
|
226
223
|
http: incoming,
|
|
@@ -233,7 +230,6 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
233
230
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
234
231
|
return new request_host_js_1.RequestHost({
|
|
235
232
|
endpoint,
|
|
236
|
-
operation: 'deleteMany',
|
|
237
233
|
controller,
|
|
238
234
|
handler,
|
|
239
235
|
http: incoming,
|
|
@@ -250,7 +246,6 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
250
246
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
251
247
|
return new request_host_js_1.RequestHost({
|
|
252
248
|
endpoint,
|
|
253
|
-
operation: 'get',
|
|
254
249
|
controller,
|
|
255
250
|
handler,
|
|
256
251
|
http: incoming,
|
|
@@ -268,7 +263,6 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
268
263
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
269
264
|
return new request_host_js_1.RequestHost({
|
|
270
265
|
endpoint,
|
|
271
|
-
operation: 'findMany',
|
|
272
266
|
controller,
|
|
273
267
|
handler,
|
|
274
268
|
http: incoming,
|
|
@@ -288,11 +282,10 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
288
282
|
const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'update');
|
|
289
283
|
const jsonReader = (0, json_body_loader_js_1.jsonBodyLoader)({ limit: endpoint.inputMaxContentSize }, endpoint);
|
|
290
284
|
let data = await jsonReader(incoming);
|
|
291
|
-
data = endpoint.
|
|
285
|
+
data = endpoint.decodeInput(data, { coerce: true });
|
|
292
286
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
293
287
|
return new request_host_js_1.RequestHost({
|
|
294
288
|
endpoint,
|
|
295
|
-
operation: 'update',
|
|
296
289
|
controller,
|
|
297
290
|
handler,
|
|
298
291
|
http: incoming,
|
|
@@ -310,11 +303,10 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
310
303
|
const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'updateMany');
|
|
311
304
|
const jsonReader = (0, json_body_loader_js_1.jsonBodyLoader)({ limit: endpoint.inputMaxContentSize }, endpoint);
|
|
312
305
|
let data = await jsonReader(incoming);
|
|
313
|
-
data = endpoint.
|
|
306
|
+
data = endpoint.decodeInput(data, { coerce: true });
|
|
314
307
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
315
308
|
return new request_host_js_1.RequestHost({
|
|
316
309
|
endpoint,
|
|
317
|
-
operation: 'updateMany',
|
|
318
310
|
controller,
|
|
319
311
|
handler,
|
|
320
312
|
http: incoming,
|
|
@@ -342,11 +334,10 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
342
334
|
const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'create');
|
|
343
335
|
const jsonReader = (0, json_body_loader_js_1.jsonBodyLoader)({ limit: endpoint.inputMaxContentSize }, endpoint);
|
|
344
336
|
let data = await jsonReader(incoming);
|
|
345
|
-
data = endpoint.
|
|
337
|
+
data = endpoint.decodeInput(data, { coerce: true });
|
|
346
338
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
347
339
|
return new request_host_js_1.RequestHost({
|
|
348
340
|
endpoint,
|
|
349
|
-
operation: 'create',
|
|
350
341
|
controller,
|
|
351
342
|
handler,
|
|
352
343
|
http: incoming,
|
|
@@ -365,7 +356,6 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
365
356
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
366
357
|
return new request_host_js_1.RequestHost({
|
|
367
358
|
endpoint,
|
|
368
|
-
operation: 'delete',
|
|
369
359
|
controller,
|
|
370
360
|
handler,
|
|
371
361
|
http: incoming,
|
|
@@ -378,7 +368,6 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
378
368
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
379
369
|
return new request_host_js_1.RequestHost({
|
|
380
370
|
endpoint,
|
|
381
|
-
operation: 'get',
|
|
382
371
|
controller,
|
|
383
372
|
handler,
|
|
384
373
|
http: incoming,
|
|
@@ -395,11 +384,10 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
395
384
|
const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'update');
|
|
396
385
|
const jsonReader = (0, json_body_loader_js_1.jsonBodyLoader)({ limit: endpoint.inputMaxContentSize }, endpoint);
|
|
397
386
|
let data = await jsonReader(incoming);
|
|
398
|
-
data = endpoint.
|
|
387
|
+
data = endpoint.decodeInput(data, { coerce: true });
|
|
399
388
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
400
389
|
return new request_host_js_1.RequestHost({
|
|
401
390
|
endpoint,
|
|
402
|
-
operation: 'update',
|
|
403
391
|
controller,
|
|
404
392
|
handler,
|
|
405
393
|
http: incoming,
|
|
@@ -428,7 +416,6 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
428
416
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
429
417
|
return new request_host_js_1.RequestHost({
|
|
430
418
|
endpoint,
|
|
431
|
-
operation: 'get',
|
|
432
419
|
controller,
|
|
433
420
|
handler,
|
|
434
421
|
http: incoming,
|
|
@@ -442,7 +429,6 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
442
429
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
443
430
|
return new request_host_js_1.RequestHost({
|
|
444
431
|
endpoint,
|
|
445
|
-
operation: 'delete',
|
|
446
432
|
controller,
|
|
447
433
|
handler,
|
|
448
434
|
http: incoming,
|
|
@@ -468,7 +454,6 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
468
454
|
});
|
|
469
455
|
return new request_host_js_1.RequestHost({
|
|
470
456
|
endpoint,
|
|
471
|
-
operation: 'post',
|
|
472
457
|
controller,
|
|
473
458
|
handler,
|
|
474
459
|
http: incoming,
|
|
@@ -529,9 +514,12 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
529
514
|
value = await handler.call(request.controller, context);
|
|
530
515
|
if (response.value == null)
|
|
531
516
|
response.value = value;
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
517
|
+
// Normalize response value
|
|
518
|
+
if (request.endpoint.kind === 'operation' &&
|
|
519
|
+
(resource instanceof common_1.Collection || resource instanceof common_1.Singleton || resource instanceof common_1.Storage)) {
|
|
520
|
+
const { endpoint } = request;
|
|
521
|
+
const operationName = endpoint.name;
|
|
522
|
+
if (operationName === 'delete' || operationName === 'deleteMany' || operationName === 'updateMany') {
|
|
535
523
|
let affected = 0;
|
|
536
524
|
if (typeof value === 'number')
|
|
537
525
|
affected = value;
|
|
@@ -539,21 +527,27 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
539
527
|
affected = value ? 1 : 0;
|
|
540
528
|
if (typeof value === 'object')
|
|
541
529
|
affected = value.affected || value.affectedRows ||
|
|
542
|
-
(
|
|
530
|
+
(operationName === 'updateMany' ? value.updated : value.deleted);
|
|
543
531
|
response.value = affected;
|
|
532
|
+
return;
|
|
544
533
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
}
|
|
534
|
+
if (resource instanceof common_1.Storage)
|
|
535
|
+
return;
|
|
536
|
+
// "get" and "update" endpoints must return the entity instance, otherwise it means resource not found
|
|
537
|
+
if (value == null && (operationName === 'get' || operationName === 'update'))
|
|
538
|
+
throw new common_1.ResourceNotFoundError(resource.name, request.key);
|
|
539
|
+
// "findMany" endpoint should return array of entity instances
|
|
540
|
+
if (operationName === 'findMany')
|
|
541
|
+
value = (value == null ? [] : Array.isArray(value) ? value : [value]);
|
|
542
|
+
else
|
|
543
|
+
value = value == null ? {} : Array.isArray(value) ? value[0] : value;
|
|
544
|
+
value = endpoint.encodeReturning(value, { coerce: true });
|
|
545
|
+
response.value = value;
|
|
546
|
+
return;
|
|
556
547
|
}
|
|
548
|
+
const { endpoint } = request;
|
|
549
|
+
if (response.value)
|
|
550
|
+
response.value = endpoint.encodeReturning(response.value, { coerce: true });
|
|
557
551
|
}
|
|
558
552
|
catch (error) {
|
|
559
553
|
response.errors.push(error);
|
|
@@ -561,68 +555,62 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
|
|
|
561
555
|
}
|
|
562
556
|
async sendResponse(context) {
|
|
563
557
|
const { request, response } = context;
|
|
558
|
+
const { endpoint, resource } = request;
|
|
564
559
|
const outgoing = response.switchToHttp();
|
|
565
|
-
if (
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
560
|
+
if (endpoint.kind === 'operation' &&
|
|
561
|
+
(resource instanceof common_1.Collection || resource instanceof common_1.Singleton ||
|
|
562
|
+
(resource instanceof common_1.Storage && endpoint.name !== 'get'))) {
|
|
563
|
+
const returnType = endpoint.returnType;
|
|
564
|
+
const operationName = endpoint.name;
|
|
565
|
+
const body = {
|
|
566
|
+
context: resource.getFullPath(),
|
|
567
|
+
operation: operationName
|
|
568
|
+
};
|
|
569
|
+
if (operationName === 'delete' || operationName === 'deleteMany' || operationName === 'updateMany') {
|
|
570
|
+
body.affected = response.value;
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
if (!response.value)
|
|
574
|
+
throw new common_1.InternalServerError(`"${request.endpoint.name}" endpoint should return value`);
|
|
575
|
+
(0, assert_1.default)(returnType instanceof common_1.ComplexType);
|
|
576
|
+
body.type = returnType.name || '#anonymous';
|
|
577
|
+
body.data = this.i18n.deep(response.value);
|
|
578
|
+
if (operationName === 'create')
|
|
579
|
+
outgoing.statusCode = 201;
|
|
580
|
+
if (operationName === 'create' || operationName === 'update')
|
|
581
|
+
body.affected = 1;
|
|
582
|
+
else if (operationName === 'get' || operationName === 'update')
|
|
583
|
+
body.key = request.key;
|
|
584
|
+
if (operationName === 'findMany' && response.count != null && response.count >= 0)
|
|
585
|
+
body.totalCount = response.count;
|
|
582
586
|
}
|
|
587
|
+
outgoing.statusCode = outgoing.statusCode || common_1.HttpStatusCodes.OK;
|
|
588
|
+
outgoing.setHeader(common_1.HttpHeaderCodes.Content_Type, 'application/opra+json; charset=utf-8');
|
|
589
|
+
outgoing.send(JSON.stringify(body));
|
|
583
590
|
outgoing.end();
|
|
584
591
|
return;
|
|
585
592
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
if (request.operation === 'action')
|
|
590
|
-
responseObject.action = request.action;
|
|
591
|
-
else
|
|
592
|
-
responseObject.operation = request.operation;
|
|
593
|
-
const returnType = request.endpoint.returnType;
|
|
594
|
-
let responseValue = response.value;
|
|
595
|
-
if (returnType) {
|
|
596
|
-
responseObject.type = returnType.name || '#anonymous';
|
|
597
|
-
if (response.value != null)
|
|
598
|
-
responseValue = responseObject.data = request.endpoint.encode(response.value, { coerce: true });
|
|
599
|
-
}
|
|
600
|
-
if (request.operation === 'action') {
|
|
601
|
-
if (responseValue != null)
|
|
602
|
-
responseObject.data = responseValue;
|
|
593
|
+
// Storage "get" resource
|
|
594
|
+
if (endpoint.kind === 'action') {
|
|
595
|
+
//
|
|
603
596
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
597
|
+
outgoing.statusCode = outgoing.statusCode || common_1.HttpStatusCodes.OK;
|
|
598
|
+
if (response.value != null) {
|
|
599
|
+
if (typeof response.value === 'string') {
|
|
600
|
+
if (!outgoing.hasHeader('content-type'))
|
|
601
|
+
outgoing.setHeader('content-type', 'text/plain');
|
|
602
|
+
outgoing.send(response.value);
|
|
603
|
+
}
|
|
604
|
+
else if (Buffer.isBuffer(response.value) || (0, common_1.isReadable)(response.value)) {
|
|
605
|
+
if (!outgoing.hasHeader('content-type'))
|
|
606
|
+
outgoing.setHeader('content-type', 'application/octet-stream');
|
|
607
|
+
outgoing.send(response.value);
|
|
608
608
|
}
|
|
609
609
|
else {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
if (request.operation === 'create')
|
|
613
|
-
outgoing.statusCode = 201;
|
|
614
|
-
if (request.operation === 'create' || request.operation === 'update')
|
|
615
|
-
responseObject.affected = 1;
|
|
616
|
-
else if (request.operation === 'get' || request.operation === 'update')
|
|
617
|
-
responseObject.key = request.key;
|
|
618
|
-
if (request.operation === 'findMany' && response.count != null && response.count >= 0)
|
|
619
|
-
responseObject.totalCount = response.count;
|
|
610
|
+
outgoing.setHeader('content-type', 'application/json; charset=utf-8');
|
|
611
|
+
outgoing.send(JSON.stringify(response.value));
|
|
620
612
|
}
|
|
621
|
-
outgoing.statusCode = outgoing.statusCode || common_1.HttpStatusCodes.OK;
|
|
622
613
|
}
|
|
623
|
-
const body = this.i18n.deep(responseObject);
|
|
624
|
-
outgoing.setHeader(common_1.HttpHeaderCodes.Content_Type, 'application/opra+json; charset=utf-8');
|
|
625
|
-
outgoing.send(JSON.stringify(body));
|
|
626
614
|
outgoing.end();
|
|
627
615
|
}
|
|
628
616
|
async handleError(context) {
|
package/cjs/index.js
CHANGED
|
@@ -24,3 +24,4 @@ tslib_1.__exportStar(require("./interfaces/interceptor.interface.js"), exports);
|
|
|
24
24
|
tslib_1.__exportStar(require("./interfaces/logger.interface.js"), exports);
|
|
25
25
|
tslib_1.__exportStar(require("./interfaces/request-handler.interface.js"), exports);
|
|
26
26
|
tslib_1.__exportStar(require("./services/logger.js"), exports);
|
|
27
|
+
tslib_1.__exportStar(require("./services/api-service.js"), exports);
|
|
@@ -102,20 +102,36 @@ class PlatformAdapterHost extends strict_typed_events_1.AsyncEventEmitter {
|
|
|
102
102
|
}
|
|
103
103
|
return controller;
|
|
104
104
|
}
|
|
105
|
-
async
|
|
105
|
+
async getActionHandler(resource, name) {
|
|
106
106
|
resource = typeof resource === 'object' && resource instanceof common_1.Resource
|
|
107
107
|
? resource
|
|
108
108
|
: this.api.getResource(resource);
|
|
109
109
|
const controller = await this.getController(resource);
|
|
110
|
-
const endpoint =
|
|
111
|
-
|
|
110
|
+
const endpoint = resource.actions.get(name);
|
|
111
|
+
if (endpoint) {
|
|
112
|
+
const handler = typeof controller[endpoint.name] === 'function' ? controller[endpoint.name] : undefined;
|
|
113
|
+
if (handler)
|
|
114
|
+
return { controller, endpoint, handler };
|
|
115
|
+
}
|
|
116
|
+
throw new common_1.BadRequestError({
|
|
117
|
+
message: (0, common_1.translate)('ACTION_NOT_FOUND', { resource: resource.name, action: name }, `The {{resource}} resource doesn't have an action named '{{action}}'`),
|
|
118
|
+
severity: 'error',
|
|
119
|
+
code: 'ACTION_NOT_FOUND'
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
async getOperationHandler(resource, name) {
|
|
123
|
+
resource = typeof resource === 'object' && resource instanceof common_1.Resource
|
|
124
|
+
? resource
|
|
125
|
+
: this.api.getResource(resource);
|
|
126
|
+
const controller = await this.getController(resource);
|
|
127
|
+
const endpoint = resource instanceof common_1.CrudResource && resource.operations.get(name);
|
|
112
128
|
if (endpoint) {
|
|
113
129
|
const handler = typeof controller[endpoint.name] === 'function' ? controller[endpoint.name] : undefined;
|
|
114
130
|
if (handler)
|
|
115
131
|
return { controller, endpoint, handler };
|
|
116
132
|
}
|
|
117
133
|
throw new common_1.ForbiddenError({
|
|
118
|
-
message: (0, common_1.translate)('OPERATION_FORBIDDEN', { resource: resource.name,
|
|
134
|
+
message: (0, common_1.translate)('OPERATION_FORBIDDEN', { resource: resource.name, operation: name }, `'The {{resource}} resource does not accept '{{operation}}' operations`),
|
|
119
135
|
severity: 'error',
|
|
120
136
|
code: 'OPERATION_FORBIDDEN'
|
|
121
137
|
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ApiService = void 0;
|
|
4
|
+
class ApiService {
|
|
5
|
+
get context() {
|
|
6
|
+
if (!this._context)
|
|
7
|
+
throw new Error(`No context assigned for ${Object.getPrototypeOf(this).name}`);
|
|
8
|
+
return this._context;
|
|
9
|
+
}
|
|
10
|
+
forContext(context) {
|
|
11
|
+
const instance = { context };
|
|
12
|
+
Object.setPrototypeOf(instance, this);
|
|
13
|
+
return instance;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.ApiService = ApiService;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import assert from 'assert';
|
|
1
2
|
import fs from 'fs/promises';
|
|
2
3
|
import os from 'os';
|
|
3
|
-
import * as
|
|
4
|
-
import { BadRequestError, Collection, Container, HttpHeaderCodes, HttpStatusCodes, InternalServerError, isReadable, IssueSeverity, MethodNotAllowedError, OpraException, OpraSchema, OpraURL, ResourceNotFoundError, Singleton, Storage, translate, uid, wrapException } from '@opra/common';
|
|
4
|
+
import * as vg from 'valgen';
|
|
5
|
+
import { BadRequestError, Collection, ComplexType, Container, HttpHeaderCodes, HttpStatusCodes, InternalServerError, isReadable, IssueSeverity, MethodNotAllowedError, OpraException, OpraSchema, OpraURL, ResourceNotFoundError, Singleton, Storage, translate, uid, wrapException } from '@opra/common';
|
|
5
6
|
import { ExecutionContextHost } from '../execution-context.host.js';
|
|
6
7
|
import { PlatformAdapterHost } from '../platform-adapter.host.js';
|
|
7
8
|
import { RequestHost } from '../request.host.js';
|
|
@@ -98,7 +99,7 @@ export class HttpAdapterHost extends PlatformAdapterHost {
|
|
|
98
99
|
catch (e) {
|
|
99
100
|
if (e instanceof OpraException)
|
|
100
101
|
throw e;
|
|
101
|
-
if (e instanceof
|
|
102
|
+
if (e instanceof vg.ValidationError) {
|
|
102
103
|
throw new InternalServerError({
|
|
103
104
|
message: translate('error:RESPONSE_VALIDATION,', 'Response validation failed'),
|
|
104
105
|
code: 'RESPONSE_VALIDATION',
|
|
@@ -116,7 +117,7 @@ export class HttpAdapterHost extends PlatformAdapterHost {
|
|
|
116
117
|
let resource = this.api.root;
|
|
117
118
|
let request;
|
|
118
119
|
// Walk through container
|
|
119
|
-
while (resource instanceof Container) {
|
|
120
|
+
while (resource instanceof Container && i < parsedUrl.path.length) {
|
|
120
121
|
p = parsedUrl.path[i];
|
|
121
122
|
const r = resource.resources.get(p.resource);
|
|
122
123
|
if (r) {
|
|
@@ -163,14 +164,12 @@ export class HttpAdapterHost extends PlatformAdapterHost {
|
|
|
163
164
|
}
|
|
164
165
|
async _parseRequestAction(executionContext, resource, urlPath, searchParams) {
|
|
165
166
|
const p = urlPath[0];
|
|
166
|
-
const { controller, endpoint, handler } = await this.
|
|
167
|
+
const { controller, endpoint, handler } = await this.getActionHandler(resource, p.resource);
|
|
167
168
|
const { incoming } = executionContext.switchToHttp();
|
|
168
169
|
const contentId = incoming.headers['content-id'];
|
|
169
170
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
170
171
|
return new RequestHost({
|
|
171
172
|
endpoint,
|
|
172
|
-
operation: 'action',
|
|
173
|
-
action: endpoint.name,
|
|
174
173
|
controller,
|
|
175
174
|
handler,
|
|
176
175
|
http: incoming,
|
|
@@ -190,11 +189,10 @@ export class HttpAdapterHost extends PlatformAdapterHost {
|
|
|
190
189
|
const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'create');
|
|
191
190
|
const jsonReader = jsonBodyLoader({ limit: endpoint.inputMaxContentSize }, endpoint);
|
|
192
191
|
let data = await jsonReader(incoming);
|
|
193
|
-
data = endpoint.
|
|
192
|
+
data = endpoint.decodeInput(data, { coerce: true });
|
|
194
193
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
195
194
|
return new RequestHost({
|
|
196
195
|
endpoint,
|
|
197
|
-
operation: 'create',
|
|
198
196
|
controller,
|
|
199
197
|
handler,
|
|
200
198
|
http: incoming,
|
|
@@ -216,7 +214,6 @@ export class HttpAdapterHost extends PlatformAdapterHost {
|
|
|
216
214
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
217
215
|
return new RequestHost({
|
|
218
216
|
endpoint,
|
|
219
|
-
operation: 'delete',
|
|
220
217
|
controller,
|
|
221
218
|
handler,
|
|
222
219
|
http: incoming,
|
|
@@ -229,7 +226,6 @@ export class HttpAdapterHost extends PlatformAdapterHost {
|
|
|
229
226
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
230
227
|
return new RequestHost({
|
|
231
228
|
endpoint,
|
|
232
|
-
operation: 'deleteMany',
|
|
233
229
|
controller,
|
|
234
230
|
handler,
|
|
235
231
|
http: incoming,
|
|
@@ -246,7 +242,6 @@ export class HttpAdapterHost extends PlatformAdapterHost {
|
|
|
246
242
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
247
243
|
return new RequestHost({
|
|
248
244
|
endpoint,
|
|
249
|
-
operation: 'get',
|
|
250
245
|
controller,
|
|
251
246
|
handler,
|
|
252
247
|
http: incoming,
|
|
@@ -264,7 +259,6 @@ export class HttpAdapterHost extends PlatformAdapterHost {
|
|
|
264
259
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
265
260
|
return new RequestHost({
|
|
266
261
|
endpoint,
|
|
267
|
-
operation: 'findMany',
|
|
268
262
|
controller,
|
|
269
263
|
handler,
|
|
270
264
|
http: incoming,
|
|
@@ -284,11 +278,10 @@ export class HttpAdapterHost extends PlatformAdapterHost {
|
|
|
284
278
|
const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'update');
|
|
285
279
|
const jsonReader = jsonBodyLoader({ limit: endpoint.inputMaxContentSize }, endpoint);
|
|
286
280
|
let data = await jsonReader(incoming);
|
|
287
|
-
data = endpoint.
|
|
281
|
+
data = endpoint.decodeInput(data, { coerce: true });
|
|
288
282
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
289
283
|
return new RequestHost({
|
|
290
284
|
endpoint,
|
|
291
|
-
operation: 'update',
|
|
292
285
|
controller,
|
|
293
286
|
handler,
|
|
294
287
|
http: incoming,
|
|
@@ -306,11 +299,10 @@ export class HttpAdapterHost extends PlatformAdapterHost {
|
|
|
306
299
|
const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'updateMany');
|
|
307
300
|
const jsonReader = jsonBodyLoader({ limit: endpoint.inputMaxContentSize }, endpoint);
|
|
308
301
|
let data = await jsonReader(incoming);
|
|
309
|
-
data = endpoint.
|
|
302
|
+
data = endpoint.decodeInput(data, { coerce: true });
|
|
310
303
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
311
304
|
return new RequestHost({
|
|
312
305
|
endpoint,
|
|
313
|
-
operation: 'updateMany',
|
|
314
306
|
controller,
|
|
315
307
|
handler,
|
|
316
308
|
http: incoming,
|
|
@@ -338,11 +330,10 @@ export class HttpAdapterHost extends PlatformAdapterHost {
|
|
|
338
330
|
const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'create');
|
|
339
331
|
const jsonReader = jsonBodyLoader({ limit: endpoint.inputMaxContentSize }, endpoint);
|
|
340
332
|
let data = await jsonReader(incoming);
|
|
341
|
-
data = endpoint.
|
|
333
|
+
data = endpoint.decodeInput(data, { coerce: true });
|
|
342
334
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
343
335
|
return new RequestHost({
|
|
344
336
|
endpoint,
|
|
345
|
-
operation: 'create',
|
|
346
337
|
controller,
|
|
347
338
|
handler,
|
|
348
339
|
http: incoming,
|
|
@@ -361,7 +352,6 @@ export class HttpAdapterHost extends PlatformAdapterHost {
|
|
|
361
352
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
362
353
|
return new RequestHost({
|
|
363
354
|
endpoint,
|
|
364
|
-
operation: 'delete',
|
|
365
355
|
controller,
|
|
366
356
|
handler,
|
|
367
357
|
http: incoming,
|
|
@@ -374,7 +364,6 @@ export class HttpAdapterHost extends PlatformAdapterHost {
|
|
|
374
364
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
375
365
|
return new RequestHost({
|
|
376
366
|
endpoint,
|
|
377
|
-
operation: 'get',
|
|
378
367
|
controller,
|
|
379
368
|
handler,
|
|
380
369
|
http: incoming,
|
|
@@ -391,11 +380,10 @@ export class HttpAdapterHost extends PlatformAdapterHost {
|
|
|
391
380
|
const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'update');
|
|
392
381
|
const jsonReader = jsonBodyLoader({ limit: endpoint.inputMaxContentSize }, endpoint);
|
|
393
382
|
let data = await jsonReader(incoming);
|
|
394
|
-
data = endpoint.
|
|
383
|
+
data = endpoint.decodeInput(data, { coerce: true });
|
|
395
384
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
396
385
|
return new RequestHost({
|
|
397
386
|
endpoint,
|
|
398
|
-
operation: 'update',
|
|
399
387
|
controller,
|
|
400
388
|
handler,
|
|
401
389
|
http: incoming,
|
|
@@ -424,7 +412,6 @@ export class HttpAdapterHost extends PlatformAdapterHost {
|
|
|
424
412
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
425
413
|
return new RequestHost({
|
|
426
414
|
endpoint,
|
|
427
|
-
operation: 'get',
|
|
428
415
|
controller,
|
|
429
416
|
handler,
|
|
430
417
|
http: incoming,
|
|
@@ -438,7 +425,6 @@ export class HttpAdapterHost extends PlatformAdapterHost {
|
|
|
438
425
|
const params = this.parseParameters(endpoint.parameters, p, searchParams);
|
|
439
426
|
return new RequestHost({
|
|
440
427
|
endpoint,
|
|
441
|
-
operation: 'delete',
|
|
442
428
|
controller,
|
|
443
429
|
handler,
|
|
444
430
|
http: incoming,
|
|
@@ -464,7 +450,6 @@ export class HttpAdapterHost extends PlatformAdapterHost {
|
|
|
464
450
|
});
|
|
465
451
|
return new RequestHost({
|
|
466
452
|
endpoint,
|
|
467
|
-
operation: 'post',
|
|
468
453
|
controller,
|
|
469
454
|
handler,
|
|
470
455
|
http: incoming,
|
|
@@ -525,9 +510,12 @@ export class HttpAdapterHost extends PlatformAdapterHost {
|
|
|
525
510
|
value = await handler.call(request.controller, context);
|
|
526
511
|
if (response.value == null)
|
|
527
512
|
response.value = value;
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
513
|
+
// Normalize response value
|
|
514
|
+
if (request.endpoint.kind === 'operation' &&
|
|
515
|
+
(resource instanceof Collection || resource instanceof Singleton || resource instanceof Storage)) {
|
|
516
|
+
const { endpoint } = request;
|
|
517
|
+
const operationName = endpoint.name;
|
|
518
|
+
if (operationName === 'delete' || operationName === 'deleteMany' || operationName === 'updateMany') {
|
|
531
519
|
let affected = 0;
|
|
532
520
|
if (typeof value === 'number')
|
|
533
521
|
affected = value;
|
|
@@ -535,21 +523,27 @@ export class HttpAdapterHost extends PlatformAdapterHost {
|
|
|
535
523
|
affected = value ? 1 : 0;
|
|
536
524
|
if (typeof value === 'object')
|
|
537
525
|
affected = value.affected || value.affectedRows ||
|
|
538
|
-
(
|
|
526
|
+
(operationName === 'updateMany' ? value.updated : value.deleted);
|
|
539
527
|
response.value = affected;
|
|
528
|
+
return;
|
|
540
529
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
}
|
|
530
|
+
if (resource instanceof Storage)
|
|
531
|
+
return;
|
|
532
|
+
// "get" and "update" endpoints must return the entity instance, otherwise it means resource not found
|
|
533
|
+
if (value == null && (operationName === 'get' || operationName === 'update'))
|
|
534
|
+
throw new ResourceNotFoundError(resource.name, request.key);
|
|
535
|
+
// "findMany" endpoint should return array of entity instances
|
|
536
|
+
if (operationName === 'findMany')
|
|
537
|
+
value = (value == null ? [] : Array.isArray(value) ? value : [value]);
|
|
538
|
+
else
|
|
539
|
+
value = value == null ? {} : Array.isArray(value) ? value[0] : value;
|
|
540
|
+
value = endpoint.encodeReturning(value, { coerce: true });
|
|
541
|
+
response.value = value;
|
|
542
|
+
return;
|
|
552
543
|
}
|
|
544
|
+
const { endpoint } = request;
|
|
545
|
+
if (response.value)
|
|
546
|
+
response.value = endpoint.encodeReturning(response.value, { coerce: true });
|
|
553
547
|
}
|
|
554
548
|
catch (error) {
|
|
555
549
|
response.errors.push(error);
|
|
@@ -557,68 +551,62 @@ export class HttpAdapterHost extends PlatformAdapterHost {
|
|
|
557
551
|
}
|
|
558
552
|
async sendResponse(context) {
|
|
559
553
|
const { request, response } = context;
|
|
554
|
+
const { endpoint, resource } = request;
|
|
560
555
|
const outgoing = response.switchToHttp();
|
|
561
|
-
if (
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
556
|
+
if (endpoint.kind === 'operation' &&
|
|
557
|
+
(resource instanceof Collection || resource instanceof Singleton ||
|
|
558
|
+
(resource instanceof Storage && endpoint.name !== 'get'))) {
|
|
559
|
+
const returnType = endpoint.returnType;
|
|
560
|
+
const operationName = endpoint.name;
|
|
561
|
+
const body = {
|
|
562
|
+
context: resource.getFullPath(),
|
|
563
|
+
operation: operationName
|
|
564
|
+
};
|
|
565
|
+
if (operationName === 'delete' || operationName === 'deleteMany' || operationName === 'updateMany') {
|
|
566
|
+
body.affected = response.value;
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
if (!response.value)
|
|
570
|
+
throw new InternalServerError(`"${request.endpoint.name}" endpoint should return value`);
|
|
571
|
+
assert(returnType instanceof ComplexType);
|
|
572
|
+
body.type = returnType.name || '#anonymous';
|
|
573
|
+
body.data = this.i18n.deep(response.value);
|
|
574
|
+
if (operationName === 'create')
|
|
575
|
+
outgoing.statusCode = 201;
|
|
576
|
+
if (operationName === 'create' || operationName === 'update')
|
|
577
|
+
body.affected = 1;
|
|
578
|
+
else if (operationName === 'get' || operationName === 'update')
|
|
579
|
+
body.key = request.key;
|
|
580
|
+
if (operationName === 'findMany' && response.count != null && response.count >= 0)
|
|
581
|
+
body.totalCount = response.count;
|
|
578
582
|
}
|
|
583
|
+
outgoing.statusCode = outgoing.statusCode || HttpStatusCodes.OK;
|
|
584
|
+
outgoing.setHeader(HttpHeaderCodes.Content_Type, 'application/opra+json; charset=utf-8');
|
|
585
|
+
outgoing.send(JSON.stringify(body));
|
|
579
586
|
outgoing.end();
|
|
580
587
|
return;
|
|
581
588
|
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
if (request.operation === 'action')
|
|
586
|
-
responseObject.action = request.action;
|
|
587
|
-
else
|
|
588
|
-
responseObject.operation = request.operation;
|
|
589
|
-
const returnType = request.endpoint.returnType;
|
|
590
|
-
let responseValue = response.value;
|
|
591
|
-
if (returnType) {
|
|
592
|
-
responseObject.type = returnType.name || '#anonymous';
|
|
593
|
-
if (response.value != null)
|
|
594
|
-
responseValue = responseObject.data = request.endpoint.encode(response.value, { coerce: true });
|
|
595
|
-
}
|
|
596
|
-
if (request.operation === 'action') {
|
|
597
|
-
if (responseValue != null)
|
|
598
|
-
responseObject.data = responseValue;
|
|
589
|
+
// Storage "get" resource
|
|
590
|
+
if (endpoint.kind === 'action') {
|
|
591
|
+
//
|
|
599
592
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
593
|
+
outgoing.statusCode = outgoing.statusCode || HttpStatusCodes.OK;
|
|
594
|
+
if (response.value != null) {
|
|
595
|
+
if (typeof response.value === 'string') {
|
|
596
|
+
if (!outgoing.hasHeader('content-type'))
|
|
597
|
+
outgoing.setHeader('content-type', 'text/plain');
|
|
598
|
+
outgoing.send(response.value);
|
|
599
|
+
}
|
|
600
|
+
else if (Buffer.isBuffer(response.value) || isReadable(response.value)) {
|
|
601
|
+
if (!outgoing.hasHeader('content-type'))
|
|
602
|
+
outgoing.setHeader('content-type', 'application/octet-stream');
|
|
603
|
+
outgoing.send(response.value);
|
|
604
604
|
}
|
|
605
605
|
else {
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
if (request.operation === 'create')
|
|
609
|
-
outgoing.statusCode = 201;
|
|
610
|
-
if (request.operation === 'create' || request.operation === 'update')
|
|
611
|
-
responseObject.affected = 1;
|
|
612
|
-
else if (request.operation === 'get' || request.operation === 'update')
|
|
613
|
-
responseObject.key = request.key;
|
|
614
|
-
if (request.operation === 'findMany' && response.count != null && response.count >= 0)
|
|
615
|
-
responseObject.totalCount = response.count;
|
|
606
|
+
outgoing.setHeader('content-type', 'application/json; charset=utf-8');
|
|
607
|
+
outgoing.send(JSON.stringify(response.value));
|
|
616
608
|
}
|
|
617
|
-
outgoing.statusCode = outgoing.statusCode || HttpStatusCodes.OK;
|
|
618
609
|
}
|
|
619
|
-
const body = this.i18n.deep(responseObject);
|
|
620
|
-
outgoing.setHeader(HttpHeaderCodes.Content_Type, 'application/opra+json; charset=utf-8');
|
|
621
|
-
outgoing.send(JSON.stringify(body));
|
|
622
610
|
outgoing.end();
|
|
623
611
|
}
|
|
624
612
|
async handleError(context) {
|
package/esm/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import { pascalCase } from 'putil-varhelpers';
|
|
3
3
|
import { AsyncEventEmitter } from 'strict-typed-events';
|
|
4
|
-
import { Container, CrudResource, ForbiddenError, getStackFileName, I18n, Resource, translate } from '@opra/common';
|
|
4
|
+
import { BadRequestError, Container, CrudResource, ForbiddenError, getStackFileName, I18n, Resource, translate } from '@opra/common';
|
|
5
5
|
import { Logger } from './services/logger.js';
|
|
6
6
|
const resourceInitialized = Symbol.for('opra.resource.initialized');
|
|
7
7
|
/**
|
|
@@ -98,20 +98,36 @@ export class PlatformAdapterHost extends AsyncEventEmitter {
|
|
|
98
98
|
}
|
|
99
99
|
return controller;
|
|
100
100
|
}
|
|
101
|
-
async
|
|
101
|
+
async getActionHandler(resource, name) {
|
|
102
102
|
resource = typeof resource === 'object' && resource instanceof Resource
|
|
103
103
|
? resource
|
|
104
104
|
: this.api.getResource(resource);
|
|
105
105
|
const controller = await this.getController(resource);
|
|
106
|
-
const endpoint =
|
|
107
|
-
|
|
106
|
+
const endpoint = resource.actions.get(name);
|
|
107
|
+
if (endpoint) {
|
|
108
|
+
const handler = typeof controller[endpoint.name] === 'function' ? controller[endpoint.name] : undefined;
|
|
109
|
+
if (handler)
|
|
110
|
+
return { controller, endpoint, handler };
|
|
111
|
+
}
|
|
112
|
+
throw new BadRequestError({
|
|
113
|
+
message: translate('ACTION_NOT_FOUND', { resource: resource.name, action: name }, `The {{resource}} resource doesn't have an action named '{{action}}'`),
|
|
114
|
+
severity: 'error',
|
|
115
|
+
code: 'ACTION_NOT_FOUND'
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
async getOperationHandler(resource, name) {
|
|
119
|
+
resource = typeof resource === 'object' && resource instanceof Resource
|
|
120
|
+
? resource
|
|
121
|
+
: this.api.getResource(resource);
|
|
122
|
+
const controller = await this.getController(resource);
|
|
123
|
+
const endpoint = resource instanceof CrudResource && resource.operations.get(name);
|
|
108
124
|
if (endpoint) {
|
|
109
125
|
const handler = typeof controller[endpoint.name] === 'function' ? controller[endpoint.name] : undefined;
|
|
110
126
|
if (handler)
|
|
111
127
|
return { controller, endpoint, handler };
|
|
112
128
|
}
|
|
113
129
|
throw new ForbiddenError({
|
|
114
|
-
message: translate('OPERATION_FORBIDDEN', { resource: resource.name,
|
|
130
|
+
message: translate('OPERATION_FORBIDDEN', { resource: resource.name, operation: name }, `'The {{resource}} resource does not accept '{{operation}}' operations`),
|
|
115
131
|
severity: 'error',
|
|
116
132
|
code: 'OPERATION_FORBIDDEN'
|
|
117
133
|
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export class ApiService {
|
|
2
|
+
get context() {
|
|
3
|
+
if (!this._context)
|
|
4
|
+
throw new Error(`No context assigned for ${Object.getPrototypeOf(this).name}`);
|
|
5
|
+
return this._context;
|
|
6
|
+
}
|
|
7
|
+
forContext(context) {
|
|
8
|
+
const instance = { context };
|
|
9
|
+
Object.setPrototypeOf(instance, this);
|
|
10
|
+
return instance;
|
|
11
|
+
}
|
|
12
|
+
}
|
package/i18n/en/error.json
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"RESPONSE_VALIDATION": "Response validation failed",
|
|
13
13
|
"RESOURCE_NOT_FOUND": "Resource not found",
|
|
14
14
|
"RESOURCE_CONFLICT": "There is already an other {{resource}} resource with same field values ({{fields}})",
|
|
15
|
-
"OPERATION_FORBIDDEN": "The {{resource}}
|
|
15
|
+
"OPERATION_FORBIDDEN": "The {{resource}} resource does not accept '{{operation}}' operations",
|
|
16
|
+
"ACTION_NOT_FOUND": "The {{resource}} resource doesn't have an action named '{{action}}'",
|
|
16
17
|
"UNKNOWN_FIELD": "Unknown field '{{field}}'",
|
|
17
18
|
"UNACCEPTED_SORT_FIELD": "Field '{{field}}' is not available for sort operation",
|
|
18
19
|
"UNACCEPTED_FILTER_FIELD": "Field '{{field}}' is not available for filter operation",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opra/core",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.5",
|
|
4
4
|
"description": "Opra schema package",
|
|
5
5
|
"author": "Panates",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,14 +27,14 @@
|
|
|
27
27
|
"clean:cover": "rimraf ../../coverage/core"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@opra/common": "^0.26.
|
|
30
|
+
"@opra/common": "^0.26.5",
|
|
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
35
|
"cookie-signature": "^1.2.1",
|
|
36
36
|
"encodeurl": "^1.0.2",
|
|
37
|
-
"formidable": "^3.5.
|
|
37
|
+
"formidable": "^3.5.1",
|
|
38
38
|
"fresh": "^0.5.2",
|
|
39
39
|
"mime-types": "^2.1.35",
|
|
40
40
|
"power-tasks": "^1.7.2",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"@types/cookie": "^0.5.2",
|
|
57
57
|
"@types/cookie-signature": "^1.1.0",
|
|
58
58
|
"@types/encodeurl": "^1.0.0",
|
|
59
|
-
"@types/express": "^4.17.
|
|
59
|
+
"@types/express": "^4.17.19",
|
|
60
60
|
"@types/formidable": "^3.4.3",
|
|
61
61
|
"@types/fresh": "^0.5.0",
|
|
62
62
|
"@types/mime-types": "^2.1.2",
|
|
@@ -94,4 +94,4 @@
|
|
|
94
94
|
"swagger",
|
|
95
95
|
"raml"
|
|
96
96
|
]
|
|
97
|
-
}
|
|
97
|
+
}
|
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { StrictOmit } from 'ts-gems';
|
|
2
|
+
import type { Action as _Action, Operation as _Operation, PartialOutput } from '@opra/common';
|
|
2
3
|
import type { Request as _Request } from '../request.js';
|
|
3
4
|
import type { RequestContext } from '../request-context';
|
|
4
5
|
declare module "@opra/common" {
|
|
5
6
|
namespace Collection {
|
|
6
7
|
namespace Action {
|
|
7
|
-
interface Request extends _Request {
|
|
8
|
-
|
|
9
|
-
action: string;
|
|
8
|
+
interface Request extends StrictOmit<_Request, 'endpoint'> {
|
|
9
|
+
endpoint: _Action;
|
|
10
10
|
}
|
|
11
11
|
interface Context extends Resource.Context {
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
namespace Create {
|
|
15
|
-
interface Request extends _Request {
|
|
16
|
-
|
|
15
|
+
interface Request extends StrictOmit<_Request, 'endpoint'> {
|
|
16
|
+
endpoint: _Operation & {
|
|
17
|
+
name: 'create';
|
|
18
|
+
};
|
|
17
19
|
data: any;
|
|
18
20
|
params: {
|
|
19
21
|
pick?: string[];
|
|
@@ -27,8 +29,10 @@ declare module "@opra/common" {
|
|
|
27
29
|
}
|
|
28
30
|
}
|
|
29
31
|
namespace Delete {
|
|
30
|
-
interface Request extends _Request {
|
|
31
|
-
|
|
32
|
+
interface Request extends StrictOmit<_Request, 'endpoint'> {
|
|
33
|
+
endpoint: _Operation & {
|
|
34
|
+
name: 'delete';
|
|
35
|
+
};
|
|
32
36
|
key: any;
|
|
33
37
|
}
|
|
34
38
|
interface Context extends RequestContext {
|
|
@@ -36,8 +40,10 @@ declare module "@opra/common" {
|
|
|
36
40
|
}
|
|
37
41
|
}
|
|
38
42
|
namespace DeleteMany {
|
|
39
|
-
interface Request extends _Request {
|
|
40
|
-
|
|
43
|
+
interface Request extends StrictOmit<_Request, 'endpoint'> {
|
|
44
|
+
endpoint: _Operation & {
|
|
45
|
+
name: 'deleteMany';
|
|
46
|
+
};
|
|
41
47
|
params: {
|
|
42
48
|
filter?: any;
|
|
43
49
|
[key: string]: any;
|
|
@@ -48,8 +54,10 @@ declare module "@opra/common" {
|
|
|
48
54
|
}
|
|
49
55
|
}
|
|
50
56
|
namespace FindMany {
|
|
51
|
-
interface Request extends _Request {
|
|
52
|
-
|
|
57
|
+
interface Request extends StrictOmit<_Request, 'endpoint'> {
|
|
58
|
+
endpoint: _Operation & {
|
|
59
|
+
name: 'findMany';
|
|
60
|
+
};
|
|
53
61
|
params: {
|
|
54
62
|
filter?: any;
|
|
55
63
|
pick?: string[];
|
|
@@ -68,8 +76,10 @@ declare module "@opra/common" {
|
|
|
68
76
|
}
|
|
69
77
|
}
|
|
70
78
|
namespace Get {
|
|
71
|
-
interface Request extends _Request {
|
|
72
|
-
|
|
79
|
+
interface Request extends StrictOmit<_Request, 'endpoint'> {
|
|
80
|
+
endpoint: _Operation & {
|
|
81
|
+
name: 'get';
|
|
82
|
+
};
|
|
73
83
|
key: any;
|
|
74
84
|
params: {
|
|
75
85
|
pick?: string[];
|
|
@@ -83,8 +93,10 @@ declare module "@opra/common" {
|
|
|
83
93
|
}
|
|
84
94
|
}
|
|
85
95
|
namespace Update {
|
|
86
|
-
interface Request extends _Request {
|
|
87
|
-
|
|
96
|
+
interface Request extends StrictOmit<_Request, 'endpoint'> {
|
|
97
|
+
endpoint: _Operation & {
|
|
98
|
+
name: 'update';
|
|
99
|
+
};
|
|
88
100
|
key: any;
|
|
89
101
|
data: any;
|
|
90
102
|
params: {
|
|
@@ -99,8 +111,10 @@ declare module "@opra/common" {
|
|
|
99
111
|
}
|
|
100
112
|
}
|
|
101
113
|
namespace UpdateMany {
|
|
102
|
-
interface Request extends _Request {
|
|
103
|
-
|
|
114
|
+
interface Request extends StrictOmit<_Request, 'endpoint'> {
|
|
115
|
+
endpoint: _Operation & {
|
|
116
|
+
name: 'updateMany';
|
|
117
|
+
};
|
|
104
118
|
data: any;
|
|
105
119
|
params: {
|
|
106
120
|
filter?: any;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { StrictOmit } from 'ts-gems';
|
|
2
|
+
import { Action as _Action } from '@opra/common';
|
|
1
3
|
import type { Request as _Request } from '../request.js';
|
|
2
4
|
declare module "@opra/common" {
|
|
3
5
|
namespace Container {
|
|
4
6
|
namespace Action {
|
|
5
|
-
interface Request extends _Request {
|
|
6
|
-
|
|
7
|
-
action: string;
|
|
7
|
+
interface Request extends StrictOmit<_Request, 'endpoint'> {
|
|
8
|
+
endpoint: _Action;
|
|
8
9
|
}
|
|
9
10
|
interface Context extends Resource.Context {
|
|
10
11
|
}
|
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { StrictOmit } from 'ts-gems';
|
|
2
|
+
import type { Action as _Action, Operation as _Operation, PartialOutput } from '@opra/common';
|
|
2
3
|
import type { Request as _Request } from '../request.js';
|
|
3
4
|
import type { RequestContext } from '../request-context.js';
|
|
4
5
|
declare module "@opra/common" {
|
|
5
6
|
namespace Singleton {
|
|
6
7
|
namespace Action {
|
|
7
|
-
interface Request extends _Request {
|
|
8
|
-
|
|
9
|
-
action: string;
|
|
8
|
+
interface Request extends StrictOmit<_Request, 'endpoint'> {
|
|
9
|
+
endpoint: _Action;
|
|
10
10
|
}
|
|
11
11
|
interface Context extends Resource.Context {
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
namespace Create {
|
|
15
|
-
interface Request extends _Request {
|
|
16
|
-
|
|
15
|
+
interface Request extends StrictOmit<_Request, 'endpoint'> {
|
|
16
|
+
endpoint: _Operation & {
|
|
17
|
+
name: 'create';
|
|
18
|
+
};
|
|
17
19
|
data: any;
|
|
18
20
|
params: {
|
|
19
21
|
pick?: string[];
|
|
@@ -26,16 +28,20 @@ declare module "@opra/common" {
|
|
|
26
28
|
}
|
|
27
29
|
}
|
|
28
30
|
namespace Delete {
|
|
29
|
-
interface Request extends _Request {
|
|
30
|
-
|
|
31
|
+
interface Request extends StrictOmit<_Request, 'endpoint'> {
|
|
32
|
+
endpoint: _Operation & {
|
|
33
|
+
name: 'delete';
|
|
34
|
+
};
|
|
31
35
|
}
|
|
32
36
|
interface Context extends RequestContext {
|
|
33
37
|
request: Request;
|
|
34
38
|
}
|
|
35
39
|
}
|
|
36
40
|
namespace Get {
|
|
37
|
-
interface Request extends _Request {
|
|
38
|
-
|
|
41
|
+
interface Request extends StrictOmit<_Request, 'endpoint'> {
|
|
42
|
+
endpoint: _Operation & {
|
|
43
|
+
name: 'get';
|
|
44
|
+
};
|
|
39
45
|
params: {
|
|
40
46
|
pick?: string[];
|
|
41
47
|
omit?: string[];
|
|
@@ -47,8 +53,10 @@ declare module "@opra/common" {
|
|
|
47
53
|
}
|
|
48
54
|
}
|
|
49
55
|
namespace Update {
|
|
50
|
-
interface Request extends _Request {
|
|
51
|
-
|
|
56
|
+
interface Request extends StrictOmit<_Request, 'endpoint'> {
|
|
57
|
+
endpoint: _Operation & {
|
|
58
|
+
name: 'update';
|
|
59
|
+
};
|
|
52
60
|
data: any;
|
|
53
61
|
params: {
|
|
54
62
|
pick?: string[];
|
|
@@ -1,19 +1,22 @@
|
|
|
1
|
+
import { StrictOmit } from 'ts-gems';
|
|
2
|
+
import type { Action as _Action, Operation as _Operation } from '@opra/common';
|
|
1
3
|
import type { MultipartIterator } from '../http/helpers/multipart-helper';
|
|
2
4
|
import type { Request as _Request } from '../request.js';
|
|
3
5
|
import type { RequestContext } from '../request-context.js';
|
|
4
6
|
declare module "@opra/common" {
|
|
5
7
|
namespace Storage {
|
|
6
8
|
namespace Action {
|
|
7
|
-
interface Request extends _Request {
|
|
8
|
-
|
|
9
|
-
action: string;
|
|
9
|
+
interface Request extends StrictOmit<_Request, 'endpoint'> {
|
|
10
|
+
endpoint: _Action;
|
|
10
11
|
}
|
|
11
12
|
interface Context extends Resource.Context {
|
|
12
13
|
}
|
|
13
14
|
}
|
|
14
15
|
namespace Delete {
|
|
15
|
-
interface Request extends _Request {
|
|
16
|
-
|
|
16
|
+
interface Request extends StrictOmit<_Request, 'endpoint'> {
|
|
17
|
+
endpoint: _Operation & {
|
|
18
|
+
name: 'delete';
|
|
19
|
+
};
|
|
17
20
|
path?: string;
|
|
18
21
|
}
|
|
19
22
|
interface Context extends RequestContext {
|
|
@@ -21,8 +24,10 @@ declare module "@opra/common" {
|
|
|
21
24
|
}
|
|
22
25
|
}
|
|
23
26
|
namespace Get {
|
|
24
|
-
interface Request extends _Request {
|
|
25
|
-
|
|
27
|
+
interface Request extends StrictOmit<_Request, 'endpoint'> {
|
|
28
|
+
endpoint: _Operation & {
|
|
29
|
+
name: 'get';
|
|
30
|
+
};
|
|
26
31
|
path?: string;
|
|
27
32
|
}
|
|
28
33
|
interface Context extends RequestContext {
|
|
@@ -30,8 +35,10 @@ declare module "@opra/common" {
|
|
|
30
35
|
}
|
|
31
36
|
}
|
|
32
37
|
namespace Post {
|
|
33
|
-
interface Request extends _Request {
|
|
34
|
-
|
|
38
|
+
interface Request extends StrictOmit<_Request, 'endpoint'> {
|
|
39
|
+
endpoint: _Operation & {
|
|
40
|
+
name: 'post';
|
|
41
|
+
};
|
|
35
42
|
path?: string;
|
|
36
43
|
parts: MultipartIterator;
|
|
37
44
|
}
|
package/types/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AsyncEventEmitter } from 'strict-typed-events';
|
|
2
|
-
import {
|
|
2
|
+
import { Action, ApiDocument, I18n, Operation, Resource } from '@opra/common';
|
|
3
3
|
import { ExecutionContext } from './execution-context.js';
|
|
4
4
|
import { Interceptor } from './interfaces/interceptor.interface.js';
|
|
5
5
|
import type { PlatformAdapter, Protocol } from './platform-adapter.js';
|
|
@@ -27,8 +27,13 @@ export declare abstract class PlatformAdapterHost extends AsyncEventEmitter impl
|
|
|
27
27
|
*/
|
|
28
28
|
protected init(api: ApiDocument, options?: PlatformAdapter.Options): Promise<void>;
|
|
29
29
|
getController(resource: Resource | string): Promise<any>;
|
|
30
|
-
|
|
31
|
-
endpoint:
|
|
30
|
+
getActionHandler(resource: Resource | string, name: string): Promise<{
|
|
31
|
+
endpoint: Action;
|
|
32
|
+
controller: any;
|
|
33
|
+
handler: Function;
|
|
34
|
+
}>;
|
|
35
|
+
getOperationHandler(resource: Resource | string, name: string): Promise<{
|
|
36
|
+
endpoint: Operation;
|
|
32
37
|
controller: any;
|
|
33
38
|
handler: Function;
|
|
34
39
|
}>;
|
package/types/request.d.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Action, Operation, Resource } from '@opra/common';
|
|
2
2
|
import { HttpServerRequest } from './http/http-server-request.js';
|
|
3
3
|
export interface Request {
|
|
4
4
|
resource: Resource;
|
|
5
|
-
endpoint:
|
|
6
|
-
operation: string;
|
|
5
|
+
endpoint: Action | Operation;
|
|
7
6
|
key?: any;
|
|
8
|
-
action?: string;
|
|
9
7
|
controller: Object;
|
|
10
8
|
handler: Function;
|
|
11
9
|
contentId?: string;
|
package/types/request.host.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { PartialSome, StrictOmit } from 'ts-gems';
|
|
2
2
|
import type { Resource } from '@opra/common';
|
|
3
|
+
import { Action, Operation } from '@opra/common';
|
|
3
4
|
import type { HttpServerRequest } from './http/http-server-request.js';
|
|
4
5
|
import type { Request } from './request.js';
|
|
5
6
|
export declare namespace RequestHost {
|
|
@@ -14,6 +15,7 @@ export interface RequestHost extends Request {
|
|
|
14
15
|
}
|
|
15
16
|
export declare class RequestHost implements Request {
|
|
16
17
|
controller: any;
|
|
18
|
+
endpoint: Action | Operation;
|
|
17
19
|
http?: HttpServerRequest;
|
|
18
20
|
key?: any;
|
|
19
21
|
params: Record<string, any>;
|