@opra/core 0.26.4 → 0.27.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.
@@ -7,7 +7,6 @@ class ExecutionContextHost extends strict_typed_events_1.AsyncEventEmitter {
7
7
  super();
8
8
  this.api = api;
9
9
  this.platform = platform;
10
- this.errors = [];
11
10
  this.ws = protocol.ws;
12
11
  this.rpc = protocol.rpc;
13
12
  if (protocol.http) {
@@ -4,7 +4,8 @@ exports.HttpAdapterHost = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const promises_1 = tslib_1.__importDefault(require("fs/promises"));
6
6
  const os_1 = tslib_1.__importDefault(require("os"));
7
- const valgen = tslib_1.__importStar(require("valgen"));
7
+ const type_is_1 = tslib_1.__importDefault(require("type-is"));
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");
@@ -32,54 +33,47 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
32
33
  async handleHttp(incoming, outgoing) {
33
34
  const context = new execution_context_host_js_1.ExecutionContextHost(this.api, this.platform, { http: { incoming, outgoing } });
34
35
  try {
35
- try {
36
- /* istanbul ignore next */
37
- if (!this._api)
38
- throw new common_1.InternalServerError(`${Object.getPrototypeOf(this).constructor.name} has not been initialized yet`);
39
- outgoing.setHeader(common_1.HttpHeaderCodes.X_Opra_Version, common_1.OpraSchema.SpecVersion);
40
- // Expose headers if cors enabled
41
- if (outgoing.getHeader(common_1.HttpHeaderCodes.Access_Control_Allow_Origin)) {
42
- // Expose X-Opra-* headers
43
- outgoing.appendHeader(common_1.HttpHeaderCodes.Access_Control_Expose_Headers, Object.values(common_1.HttpHeaderCodes)
44
- .filter(k => k.toLowerCase().startsWith('x-opra-')));
36
+ /* istanbul ignore next */
37
+ if (!this._api)
38
+ throw new common_1.InternalServerError(`${Object.getPrototypeOf(this).constructor.name} has not been initialized yet`);
39
+ outgoing.setHeader(common_1.HttpHeaderCodes.X_Opra_Version, common_1.OpraSchema.SpecVersion);
40
+ // Expose headers if cors enabled
41
+ if (outgoing.getHeader(common_1.HttpHeaderCodes.Access_Control_Allow_Origin)) {
42
+ // Expose X-Opra-* headers
43
+ outgoing.appendHeader(common_1.HttpHeaderCodes.Access_Control_Expose_Headers, Object.values(common_1.HttpHeaderCodes)
44
+ .filter(k => k.toLowerCase().startsWith('x-opra-')));
45
+ }
46
+ const { parsedUrl } = incoming;
47
+ if (!parsedUrl.path.length) {
48
+ if (incoming.method === 'GET') {
49
+ outgoing.setHeader('content-type', 'application/json');
50
+ outgoing.end(JSON.stringify(this.api.exportSchema({ webSafe: true })));
51
+ return;
45
52
  }
46
- const { parsedUrl } = incoming;
47
- if (!parsedUrl.path.length) {
48
- if (incoming.method === 'GET') {
49
- outgoing.setHeader('content-type', 'application/json');
50
- outgoing.end(JSON.stringify(this.api.exportSchema({ webSafe: true })));
51
- return;
52
- }
53
- // Process Batch
54
- if (incoming.method === 'POST' && incoming.headers['content-type'] === 'multipart/mixed') {
55
- // todo Process Batch
56
- }
57
- throw new common_1.BadRequestError();
53
+ // Process Batch
54
+ if (incoming.method === 'POST' && incoming.headers['content-type'] === 'multipart/mixed') {
55
+ // todo Process Batch
58
56
  }
59
- let i = 0;
60
- let requestProcessed = false;
61
- const next = async () => {
62
- const interceptor = this._interceptors[i++];
63
- if (interceptor) {
64
- await interceptor(context, next);
65
- await next();
66
- }
67
- else if (!requestProcessed) {
68
- requestProcessed = true;
69
- await this.handleExecution(context);
70
- }
71
- };
72
- await next();
73
- }
74
- catch (error) {
75
- context.errors.push((0, common_1.wrapException)(error));
76
- }
77
- // If no response returned to the client we send an error
78
- if (!outgoing.writableEnded) {
79
- if (!context.errors.length)
80
- context.errors.push(new common_1.BadRequestError(`Server can not process this request`));
81
- await this.handleError(context);
57
+ throw new common_1.BadRequestError();
82
58
  }
59
+ let i = 0;
60
+ let requestProcessed = false;
61
+ const next = async () => {
62
+ const interceptor = this._interceptors[i++];
63
+ if (interceptor) {
64
+ await interceptor(context, next);
65
+ await next();
66
+ }
67
+ else if (!requestProcessed) {
68
+ requestProcessed = true;
69
+ await this.handleExecution(context);
70
+ }
71
+ };
72
+ await next();
73
+ }
74
+ catch (error) {
75
+ if (!outgoing.writableEnded)
76
+ await this.sendErrorResponse(context, [error]);
83
77
  }
84
78
  finally {
85
79
  await context.emitAsync('finish');
@@ -87,22 +81,33 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
87
81
  }
88
82
  async handleExecution(executionContext) {
89
83
  // Parse incoming message and create Request object
90
- const request = await this.parseRequest(executionContext);
91
- const { outgoing } = executionContext.switchToHttp();
92
- const response = new response_host_js_1.ResponseHost({ http: outgoing });
93
- const context = request_context_js_1.RequestContext.from(executionContext, request, response);
94
- await this.executeRequest(context);
95
- if (response.errors.length) {
96
- context.errors.push(...response.errors);
97
- return;
84
+ let request;
85
+ try {
86
+ request = await this.parseRequest(executionContext);
87
+ }
88
+ catch (e) {
89
+ if (e instanceof common_1.OpraException)
90
+ throw e;
91
+ if (e instanceof vg.ValidationError) {
92
+ throw new common_1.BadRequestError({
93
+ message: (0, common_1.translate)('error:RESPONSE_VALIDATION,', 'Response validation failed'),
94
+ code: 'RESPONSE_VALIDATION',
95
+ details: e.issues
96
+ }, e);
97
+ }
98
+ throw new common_1.BadRequestError(e);
98
99
  }
99
100
  try {
101
+ const { outgoing } = executionContext.switchToHttp();
102
+ const response = new response_host_js_1.ResponseHost({ http: outgoing });
103
+ const context = request_context_js_1.RequestContext.from(executionContext, request, response);
104
+ await this.executeRequest(context);
100
105
  await this.sendResponse(context);
101
106
  }
102
107
  catch (e) {
103
108
  if (e instanceof common_1.OpraException)
104
109
  throw e;
105
- if (e instanceof valgen.ValidationError) {
110
+ if (e instanceof vg.ValidationError) {
106
111
  throw new common_1.InternalServerError({
107
112
  message: (0, common_1.translate)('error:RESPONSE_VALIDATION,', 'Response validation failed'),
108
113
  code: 'RESPONSE_VALIDATION',
@@ -150,7 +155,7 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
150
155
  return request;
151
156
  }
152
157
  if (resource instanceof common_1.Storage)
153
- request = await this._parseRequestStorage(executionContext, resource, urlPath.slice(1), searchParams);
158
+ request = await this._parseRequestStorage(executionContext, resource, searchParams);
154
159
  else if (urlPath.length === 1) { // Collection and Singleton resources should be last element in path
155
160
  if (resource instanceof common_1.Collection)
156
161
  request = await this._parseRequestCollection(executionContext, resource, urlPath, searchParams);
@@ -167,14 +172,12 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
167
172
  }
168
173
  async _parseRequestAction(executionContext, resource, urlPath, searchParams) {
169
174
  const p = urlPath[0];
170
- const { controller, endpoint, handler } = await this.getOperationHandler(resource, p.resource);
175
+ const { controller, endpoint, handler } = await this.getActionHandler(resource, p.resource);
171
176
  const { incoming } = executionContext.switchToHttp();
172
177
  const contentId = incoming.headers['content-id'];
173
- const params = this.parseParameters(endpoint.parameters, p, searchParams);
178
+ const params = this.parseParameters(endpoint.parameters, searchParams);
174
179
  return new request_host_js_1.RequestHost({
175
180
  endpoint,
176
- operation: 'action',
177
- action: endpoint.name,
178
181
  controller,
179
182
  handler,
180
183
  http: incoming,
@@ -194,11 +197,10 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
194
197
  const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'create');
195
198
  const jsonReader = (0, json_body_loader_js_1.jsonBodyLoader)({ limit: endpoint.inputMaxContentSize }, endpoint);
196
199
  let data = await jsonReader(incoming);
197
- data = endpoint.decode(data, { coerce: true });
198
- const params = this.parseParameters(endpoint.parameters, p, searchParams);
200
+ data = endpoint.decodeInput(data, { coerce: true });
201
+ const params = this.parseParameters(endpoint.parameters, searchParams);
199
202
  return new request_host_js_1.RequestHost({
200
203
  endpoint,
201
- operation: 'create',
202
204
  controller,
203
205
  handler,
204
206
  http: incoming,
@@ -217,10 +219,9 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
217
219
  case 'DELETE': {
218
220
  if (p.key != null) {
219
221
  const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'delete');
220
- const params = this.parseParameters(endpoint.parameters, p, searchParams);
222
+ const params = this.parseParameters(endpoint.parameters, searchParams);
221
223
  return new request_host_js_1.RequestHost({
222
224
  endpoint,
223
- operation: 'delete',
224
225
  controller,
225
226
  handler,
226
227
  http: incoming,
@@ -230,10 +231,9 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
230
231
  });
231
232
  }
232
233
  const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'deleteMany');
233
- const params = this.parseParameters(endpoint.parameters, p, searchParams);
234
+ const params = this.parseParameters(endpoint.parameters, searchParams);
234
235
  return new request_host_js_1.RequestHost({
235
236
  endpoint,
236
- operation: 'deleteMany',
237
237
  controller,
238
238
  handler,
239
239
  http: incoming,
@@ -247,10 +247,9 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
247
247
  case 'GET': {
248
248
  if (p.key != null) {
249
249
  const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'get');
250
- const params = this.parseParameters(endpoint.parameters, p, searchParams);
250
+ const params = this.parseParameters(endpoint.parameters, searchParams);
251
251
  return new request_host_js_1.RequestHost({
252
252
  endpoint,
253
- operation: 'get',
254
253
  controller,
255
254
  handler,
256
255
  http: incoming,
@@ -265,10 +264,9 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
265
264
  });
266
265
  }
267
266
  const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'findMany');
268
- const params = this.parseParameters(endpoint.parameters, p, searchParams);
267
+ const params = this.parseParameters(endpoint.parameters, searchParams);
269
268
  return new request_host_js_1.RequestHost({
270
269
  endpoint,
271
- operation: 'findMany',
272
270
  controller,
273
271
  handler,
274
272
  http: incoming,
@@ -288,11 +286,10 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
288
286
  const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'update');
289
287
  const jsonReader = (0, json_body_loader_js_1.jsonBodyLoader)({ limit: endpoint.inputMaxContentSize }, endpoint);
290
288
  let data = await jsonReader(incoming);
291
- data = endpoint.decode(data, { coerce: true });
292
- const params = this.parseParameters(endpoint.parameters, p, searchParams);
289
+ data = endpoint.decodeInput(data, { coerce: true });
290
+ const params = this.parseParameters(endpoint.parameters, searchParams);
293
291
  return new request_host_js_1.RequestHost({
294
292
  endpoint,
295
- operation: 'update',
296
293
  controller,
297
294
  handler,
298
295
  http: incoming,
@@ -310,11 +307,10 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
310
307
  const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'updateMany');
311
308
  const jsonReader = (0, json_body_loader_js_1.jsonBodyLoader)({ limit: endpoint.inputMaxContentSize }, endpoint);
312
309
  let data = await jsonReader(incoming);
313
- data = endpoint.decode(data, { coerce: true });
314
- const params = this.parseParameters(endpoint.parameters, p, searchParams);
310
+ data = endpoint.decodeInput(data, { coerce: true });
311
+ const params = this.parseParameters(endpoint.parameters, searchParams);
315
312
  return new request_host_js_1.RequestHost({
316
313
  endpoint,
317
- operation: 'updateMany',
318
314
  controller,
319
315
  handler,
320
316
  http: incoming,
@@ -336,17 +332,15 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
336
332
  if ((incoming.method === 'POST' || incoming.method === 'PATCH') && !incoming.is('json'))
337
333
  throw new common_1.BadRequestError({ message: 'Unsupported Content-Type' });
338
334
  const contentId = incoming.headers['content-id'];
339
- const p = urlPath[0];
340
335
  switch (incoming.method) {
341
336
  case 'POST': {
342
337
  const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'create');
343
338
  const jsonReader = (0, json_body_loader_js_1.jsonBodyLoader)({ limit: endpoint.inputMaxContentSize }, endpoint);
344
339
  let data = await jsonReader(incoming);
345
- data = endpoint.decode(data, { coerce: true });
346
- const params = this.parseParameters(endpoint.parameters, p, searchParams);
340
+ data = endpoint.decodeInput(data, { coerce: true });
341
+ const params = this.parseParameters(endpoint.parameters, searchParams);
347
342
  return new request_host_js_1.RequestHost({
348
343
  endpoint,
349
- operation: 'create',
350
344
  controller,
351
345
  handler,
352
346
  http: incoming,
@@ -362,10 +356,9 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
362
356
  }
363
357
  case 'DELETE': {
364
358
  const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'delete');
365
- const params = this.parseParameters(endpoint.parameters, p, searchParams);
359
+ const params = this.parseParameters(endpoint.parameters, searchParams);
366
360
  return new request_host_js_1.RequestHost({
367
361
  endpoint,
368
- operation: 'delete',
369
362
  controller,
370
363
  handler,
371
364
  http: incoming,
@@ -375,10 +368,9 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
375
368
  }
376
369
  case 'GET': {
377
370
  const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'get');
378
- const params = this.parseParameters(endpoint.parameters, p, searchParams);
371
+ const params = this.parseParameters(endpoint.parameters, searchParams);
379
372
  return new request_host_js_1.RequestHost({
380
373
  endpoint,
381
- operation: 'get',
382
374
  controller,
383
375
  handler,
384
376
  http: incoming,
@@ -395,11 +387,10 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
395
387
  const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'update');
396
388
  const jsonReader = (0, json_body_loader_js_1.jsonBodyLoader)({ limit: endpoint.inputMaxContentSize }, endpoint);
397
389
  let data = await jsonReader(incoming);
398
- data = endpoint.decode(data, { coerce: true });
399
- const params = this.parseParameters(endpoint.parameters, p, searchParams);
390
+ data = endpoint.decodeInput(data, { coerce: true });
391
+ const params = this.parseParameters(endpoint.parameters, searchParams);
400
392
  return new request_host_js_1.RequestHost({
401
393
  endpoint,
402
- operation: 'update',
403
394
  controller,
404
395
  handler,
405
396
  http: incoming,
@@ -418,17 +409,15 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
418
409
  message: `Singleton resource doesn't accept http "${incoming.method}" method`
419
410
  });
420
411
  }
421
- async _parseRequestStorage(executionContext, resource, urlPath, searchParams) {
412
+ async _parseRequestStorage(executionContext, resource, searchParams) {
422
413
  const { incoming } = executionContext.switchToHttp();
423
414
  const contentId = incoming.headers['content-id'];
424
- const p = urlPath[0];
425
415
  switch (incoming.method) {
426
416
  case 'GET': {
427
417
  const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'get');
428
- const params = this.parseParameters(endpoint.parameters, p, searchParams);
418
+ const params = this.parseParameters(endpoint.parameters, searchParams);
429
419
  return new request_host_js_1.RequestHost({
430
420
  endpoint,
431
- operation: 'get',
432
421
  controller,
433
422
  handler,
434
423
  http: incoming,
@@ -439,10 +428,9 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
439
428
  }
440
429
  case 'DELETE': {
441
430
  const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'delete');
442
- const params = this.parseParameters(endpoint.parameters, p, searchParams);
431
+ const params = this.parseParameters(endpoint.parameters, searchParams);
443
432
  return new request_host_js_1.RequestHost({
444
433
  endpoint,
445
- operation: 'delete',
446
434
  controller,
447
435
  handler,
448
436
  http: incoming,
@@ -453,7 +441,7 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
453
441
  }
454
442
  case 'POST': {
455
443
  const { controller, endpoint, handler } = await this.getOperationHandler(resource, 'post');
456
- const params = this.parseParameters(endpoint.parameters, p, searchParams);
444
+ const params = this.parseParameters(endpoint.parameters, searchParams);
457
445
  await promises_1.default.mkdir(this._tempDir, { recursive: true });
458
446
  const multipartIterator = new multipart_helper_js_1.MultipartIterator(incoming, {
459
447
  ...endpoint.options,
@@ -468,7 +456,6 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
468
456
  });
469
457
  return new request_host_js_1.RequestHost({
470
458
  endpoint,
471
- operation: 'post',
472
459
  controller,
473
460
  handler,
474
461
  http: incoming,
@@ -483,13 +470,15 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
483
470
  message: `Storage resource doesn't accept http "${incoming.method}" method`
484
471
  });
485
472
  }
486
- parseParameters(paramDefs, pathComponent, searchParams) {
473
+ parseParameters(paramDefs, searchParams) {
487
474
  const out = {};
488
475
  // Parse known parameters
489
476
  for (const [k, prm] of paramDefs.entries()) {
490
477
  const decode = prm.getDecoder();
491
478
  let v = searchParams?.getAll(k);
492
479
  try {
480
+ if (!v.length && prm.default != null)
481
+ v = [prm.default];
493
482
  if (!prm.isArray) {
494
483
  v = v[0];
495
484
  v = decode(v, { coerce: true });
@@ -522,38 +511,51 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
522
511
  async executeRequest(context) {
523
512
  const { request } = context;
524
513
  const { response } = context;
525
- const { resource, handler } = request;
514
+ const { endpoint, resource, handler } = request;
526
515
  // Call endpoint handler method
527
516
  let value;
528
517
  try {
529
518
  value = await handler.call(request.controller, context);
530
519
  if (response.value == null)
531
520
  response.value = value;
532
- if (request.resource instanceof common_1.Collection || request.resource instanceof common_1.Singleton) {
533
- const { operation } = request;
534
- if (operation === 'delete' || operation === 'deleteMany' || operation === 'updateMany') {
535
- let affected = 0;
536
- if (typeof value === 'number')
537
- affected = value;
538
- if (typeof value === 'boolean')
539
- affected = value ? 1 : 0;
540
- if (typeof value === 'object')
541
- affected = value.affected || value.affectedRows ||
542
- (operation === 'updateMany' ? value.updated : value.deleted);
543
- response.value = affected;
521
+ // Normalize response value
522
+ if (endpoint.kind === 'operation') {
523
+ if (resource instanceof common_1.Storage && endpoint.name === 'post') {
524
+ // Count file parts
525
+ value = context.request.parts.items.reduce((n, item) => item.file ? n + 1 : n, 0);
544
526
  }
545
- else {
527
+ if (resource instanceof common_1.Collection || resource instanceof common_1.Singleton || resource instanceof common_1.Storage) {
528
+ const operationName = endpoint.name;
529
+ if (operationName === 'delete' || operationName === 'deleteMany' ||
530
+ operationName === 'updateMany' || operationName === 'post') {
531
+ let affected = 0;
532
+ if (typeof value === 'number')
533
+ affected = value;
534
+ else if (typeof value === 'boolean')
535
+ affected = value ? 1 : 0;
536
+ else if (typeof value === 'object')
537
+ affected = value.affected || value.affectedRows ||
538
+ (operationName === 'updateMany' ? value.updated : value.deleted);
539
+ response.value = affected;
540
+ return;
541
+ }
542
+ if (resource instanceof common_1.Storage)
543
+ return;
546
544
  // "get" and "update" endpoints must return the entity instance, otherwise it means resource not found
547
- if (value == null && (request.operation === 'get' || request.operation === 'update'))
545
+ if (value == null && (operationName === 'get' || operationName === 'update'))
548
546
  throw new common_1.ResourceNotFoundError(resource.name, request.key);
549
547
  // "findMany" endpoint should return array of entity instances
550
- if (request.operation === 'findMany')
551
- value = value == null ? [] : Array.isArray(value) ? value : [value];
548
+ if (operationName === 'findMany')
549
+ value = (value == null ? [] : Array.isArray(value) ? value : [value]);
552
550
  else
553
551
  value = value == null ? {} : Array.isArray(value) ? value[0] : value;
552
+ value = endpoint.encodeReturning(value, { coerce: true });
554
553
  response.value = value;
554
+ return;
555
555
  }
556
556
  }
557
+ if (response.value)
558
+ response.value = endpoint.encodeReturning(response.value, { coerce: true });
557
559
  }
558
560
  catch (error) {
559
561
  response.errors.push(error);
@@ -561,77 +563,96 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
561
563
  }
562
564
  async sendResponse(context) {
563
565
  const { request, response } = context;
566
+ const { endpoint, resource } = request;
564
567
  const outgoing = response.switchToHttp();
565
- if (request.resource instanceof common_1.Storage) {
566
- outgoing.statusCode = outgoing.statusCode || common_1.HttpStatusCodes.OK;
567
- if (response.value != null) {
568
- if (typeof response.value === 'string') {
569
- if (!outgoing.hasHeader('content-type'))
570
- outgoing.setHeader('content-type', 'text/plain');
571
- outgoing.send(response.value);
572
- }
573
- else if (Buffer.isBuffer(response.value) || (0, common_1.isReadable)(response.value)) {
574
- if (!outgoing.hasHeader('content-type'))
575
- outgoing.setHeader('content-type', 'application/octet-stream');
576
- outgoing.send(response.value);
568
+ if (response.errors?.length || (outgoing.statusCode >= 400 && outgoing.statusCode <= 599))
569
+ return this.sendErrorResponse(context, response.errors || []);
570
+ // if response redirected we do not send any response
571
+ if (outgoing.statusCode >= 300 && outgoing.statusCode < 400) {
572
+ outgoing.end();
573
+ return;
574
+ }
575
+ let contentType = String(outgoing.getHeader('content-type') || '');
576
+ const returnType = endpoint.returnType;
577
+ if (endpoint.kind === 'action' && !contentType && endpoint.returnMime && response.value) {
578
+ contentType = endpoint.returnMime;
579
+ outgoing.setHeader('Content-Type', contentType);
580
+ }
581
+ // OperationResult response
582
+ if ((endpoint.kind === 'operation' &&
583
+ (resource instanceof common_1.Collection || resource instanceof common_1.Singleton ||
584
+ (resource instanceof common_1.Storage && endpoint.name !== 'get'))) ||
585
+ (endpoint.kind === 'action' &&
586
+ (!contentType || type_is_1.default.is(contentType, ['application/opra+json'])))) {
587
+ const incoming = context.switchToHttp().incoming;
588
+ const apiUrl = new common_1.OpraURL(incoming.baseUrl, incoming.protocol + '://' + incoming.get('host')).toString();
589
+ const body = new common_1.OperationResult({
590
+ context: endpoint.getFullPath(false),
591
+ contextUrl: apiUrl + '/#' + endpoint.getFullPath(true)
592
+ });
593
+ const operationName = endpoint.kind === 'operation' ? endpoint.name : '';
594
+ if (operationName === 'delete' || operationName === 'deleteMany' ||
595
+ operationName === 'updateMany' || operationName === 'post')
596
+ body.affected = response.value;
597
+ else {
598
+ outgoing.statusCode = outgoing.statusCode || common_1.HttpStatusCodes.OK;
599
+ if (operationName === 'create')
600
+ outgoing.statusCode = 201;
601
+ if (operationName === 'update' || operationName === 'create')
602
+ body.affected = response.value ? 1 : 0;
603
+ if (operationName === 'findMany') {
604
+ body.count = response.value.length;
605
+ body.totalMatches = response.totalMatches;
577
606
  }
578
- else {
579
- outgoing.setHeader('content-type', 'application/json; charset=utf-8');
580
- outgoing.send(JSON.stringify(response.value));
607
+ if (returnType) {
608
+ if (response.value == null)
609
+ throw new common_1.InternalServerError(`"${request.endpoint.name}" endpoint should return value`);
610
+ if (returnType.name) {
611
+ const ns = this.api.getDataTypeNs(returnType);
612
+ // const isOpraSpec = returnType.document.url?.startsWith('https://oprajs.com/spec/v1.0')
613
+ body.type = (ns ? ns + ':' : '') + returnType.name;
614
+ body.typeUrl =
615
+ (ns
616
+ ? new common_1.OpraURL('/#/types/' + returnType.name, returnType.document.url || 'http://tempuri.org').toString()
617
+ : apiUrl + '/#/types/' + returnType.name);
618
+ }
619
+ else
620
+ body.typeUrl = body.contextUrl + '/type';
621
+ body.payload = this.i18n.deep(response.value);
581
622
  }
582
623
  }
624
+ outgoing.setHeader(common_1.HttpHeaderCodes.Content_Type, 'application/opra+json; charset=utf-8');
625
+ outgoing.send(JSON.stringify(body));
583
626
  outgoing.end();
584
627
  return;
585
628
  }
586
- const responseObject = {
587
- context: request.resource.getFullPath()
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;
603
- }
604
- else if (request.resource instanceof common_1.Collection || request.resource instanceof common_1.Singleton) {
605
- if (request.operation === 'delete' || request.operation === 'deleteMany' ||
606
- request.operation === 'updateMany') {
607
- responseObject.affected = responseValue || 0;
629
+ outgoing.statusCode = outgoing.statusCode || common_1.HttpStatusCodes.OK;
630
+ if (response.value != null) {
631
+ if (typeof response.value === 'string') {
632
+ if (!contentType)
633
+ outgoing.setHeader('content-type', 'text/plain');
634
+ outgoing.send(response.value);
635
+ }
636
+ else if (Buffer.isBuffer(response.value) || (0, common_1.isReadable)(response.value)) {
637
+ if (!contentType)
638
+ outgoing.setHeader('content-type', 'application/octet-stream');
639
+ outgoing.send(response.value);
608
640
  }
609
641
  else {
610
- if (!responseValue)
611
- throw new common_1.InternalServerError(`"${request.operation}" endpoint should return value`);
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;
642
+ outgoing.setHeader('content-type', 'application/json; charset=utf-8');
643
+ outgoing.send(JSON.stringify(response.value));
620
644
  }
621
- outgoing.statusCode = outgoing.statusCode || common_1.HttpStatusCodes.OK;
622
645
  }
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
646
  outgoing.end();
627
647
  }
628
- async handleError(context) {
629
- const { errors } = context;
648
+ async sendErrorResponse(context, errors) {
630
649
  const { outgoing } = context.switchToHttp();
631
650
  if (outgoing.headersSent) {
632
651
  outgoing.end();
633
652
  return;
634
653
  }
654
+ if (!errors.length)
655
+ errors.push((0, common_1.wrapException)({ status: outgoing.statusCode || 500 }));
635
656
  errors.forEach(x => {
636
657
  if (x instanceof common_1.OpraException) {
637
658
  switch (x.severity) {
@@ -663,9 +684,9 @@ class HttpAdapterHost extends platform_adapter_host_js_1.PlatformAdapterHost {
663
684
  status = common_1.HttpStatusCodes.INTERNAL_SERVER_ERROR;
664
685
  }
665
686
  outgoing.statusCode = status;
666
- const body = {
687
+ const body = new common_1.OperationResult({
667
688
  errors: wrappedErrors.map(x => this._i18n.deep(x.toJSON()))
668
- };
689
+ });
669
690
  outgoing.setHeader(common_1.HttpHeaderCodes.Content_Type, 'application/opra+json; charset=utf-8');
670
691
  outgoing.setHeader(common_1.HttpHeaderCodes.Cache_Control, 'no-cache');
671
692
  outgoing.setHeader(common_1.HttpHeaderCodes.Pragma, 'no-cache');