@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.
@@ -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 valgen = tslib_1.__importStar(require("valgen"));
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 valgen.ValidationError) {
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.getOperationHandler(resource, p.resource);
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.decode(data, { coerce: true });
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.decode(data, { coerce: true });
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.decode(data, { coerce: true });
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.decode(data, { coerce: true });
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.decode(data, { coerce: true });
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
- 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') {
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
- (operation === 'updateMany' ? value.updated : value.deleted);
530
+ (operationName === 'updateMany' ? value.updated : value.deleted);
543
531
  response.value = affected;
532
+ return;
544
533
  }
545
- else {
546
- // "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'))
548
- throw new common_1.ResourceNotFoundError(resource.name, request.key);
549
- // "findMany" endpoint should return array of entity instances
550
- if (request.operation === 'findMany')
551
- value = value == null ? [] : Array.isArray(value) ? value : [value];
552
- else
553
- value = value == null ? {} : Array.isArray(value) ? value[0] : value;
554
- response.value = value;
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 (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);
577
- }
578
- else {
579
- outgoing.setHeader('content-type', 'application/json; charset=utf-8');
580
- outgoing.send(JSON.stringify(response.value));
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
- 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;
593
+ // Storage "get" resource
594
+ if (endpoint.kind === 'action') {
595
+ //
603
596
  }
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;
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
- 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;
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 getOperationHandler(resource, operationOrAction) {
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 = (resource instanceof common_1.CrudResource && resource.operations.get(operationOrAction)) ||
111
- resource.actions.get(operationOrAction);
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, endpoint: operationOrAction }, `'{{resource}}' does not accept '{{endpoint}}' operations`),
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 valgen from 'valgen';
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 valgen.ValidationError) {
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.getOperationHandler(resource, p.resource);
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.decode(data, { coerce: true });
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.decode(data, { coerce: true });
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.decode(data, { coerce: true });
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.decode(data, { coerce: true });
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.decode(data, { coerce: true });
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
- if (request.resource instanceof Collection || request.resource instanceof Singleton) {
529
- const { operation } = request;
530
- if (operation === 'delete' || operation === 'deleteMany' || operation === 'updateMany') {
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
- (operation === 'updateMany' ? value.updated : value.deleted);
526
+ (operationName === 'updateMany' ? value.updated : value.deleted);
539
527
  response.value = affected;
528
+ return;
540
529
  }
541
- else {
542
- // "get" and "update" endpoints must return the entity instance, otherwise it means resource not found
543
- if (value == null && (request.operation === 'get' || request.operation === 'update'))
544
- throw new ResourceNotFoundError(resource.name, request.key);
545
- // "findMany" endpoint should return array of entity instances
546
- if (request.operation === 'findMany')
547
- value = value == null ? [] : Array.isArray(value) ? value : [value];
548
- else
549
- value = value == null ? {} : Array.isArray(value) ? value[0] : value;
550
- response.value = value;
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 (request.resource instanceof Storage) {
562
- outgoing.statusCode = outgoing.statusCode || HttpStatusCodes.OK;
563
- if (response.value != null) {
564
- if (typeof response.value === 'string') {
565
- if (!outgoing.hasHeader('content-type'))
566
- outgoing.setHeader('content-type', 'text/plain');
567
- outgoing.send(response.value);
568
- }
569
- else if (Buffer.isBuffer(response.value) || isReadable(response.value)) {
570
- if (!outgoing.hasHeader('content-type'))
571
- outgoing.setHeader('content-type', 'application/octet-stream');
572
- outgoing.send(response.value);
573
- }
574
- else {
575
- outgoing.setHeader('content-type', 'application/json; charset=utf-8');
576
- outgoing.send(JSON.stringify(response.value));
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
- const responseObject = {
583
- context: request.resource.getFullPath()
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
- else if (request.resource instanceof Collection || request.resource instanceof Singleton) {
601
- if (request.operation === 'delete' || request.operation === 'deleteMany' ||
602
- request.operation === 'updateMany') {
603
- responseObject.affected = responseValue || 0;
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
- if (!responseValue)
607
- throw new InternalServerError(`"${request.operation}" endpoint should return value`);
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
@@ -21,3 +21,4 @@ export * from './interfaces/interceptor.interface.js';
21
21
  export * from './interfaces/logger.interface.js';
22
22
  export * from './interfaces/request-handler.interface.js';
23
23
  export * from './services/logger.js';
24
+ export * from './services/api-service.js';
@@ -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 getOperationHandler(resource, operationOrAction) {
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 = (resource instanceof CrudResource && resource.operations.get(operationOrAction)) ||
107
- resource.actions.get(operationOrAction);
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, endpoint: operationOrAction }, `'{{resource}}' does not accept '{{endpoint}}' operations`),
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
+ }
@@ -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}} endpoint does not accept '{{endpoint}}' operations",
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",
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.3",
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.0",
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.18",
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 type { PartialOutput } from '@opra/common';
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
- operation: 'action';
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
- operation: 'create';
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
- operation: 'delete';
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
- operation: 'deleteMany';
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
- operation: 'findMany';
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
- operation: 'get';
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
- operation: 'update';
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
- operation: 'updateMany';
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
- operation: 'action';
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 type { PartialOutput } from '@opra/common';
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
- operation: 'action';
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
- operation: 'create';
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
- operation: 'delete';
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
- operation: 'get';
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
- operation: 'update';
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
- operation: 'action';
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
- operation: 'delete';
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
- operation: 'get';
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
- operation: 'post';
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
@@ -21,3 +21,4 @@ export * from './interfaces/interceptor.interface.js';
21
21
  export * from './interfaces/logger.interface.js';
22
22
  export * from './interfaces/request-handler.interface.js';
23
23
  export * from './services/logger.js';
24
+ export * from './services/api-service.js';
@@ -1,5 +1,5 @@
1
1
  import { AsyncEventEmitter } from 'strict-typed-events';
2
- import { ApiDocument, Endpoint, I18n, Resource } from '@opra/common';
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
- getOperationHandler(resource: Resource | string, operationOrAction: string): Promise<{
31
- endpoint: 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
  }>;
@@ -1,11 +1,9 @@
1
- import { Endpoint, Resource } from '@opra/common';
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: 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;
@@ -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>;
@@ -0,0 +1,6 @@
1
+ import { RequestContext } from '../request-context.js';
2
+ export declare abstract class ApiService {
3
+ protected _context: RequestContext;
4
+ get context(): RequestContext;
5
+ forContext(context: RequestContext): typeof this;
6
+ }