@opra/core 0.1.0 → 0.2.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.
Files changed (81) hide show
  1. package/cjs/enums/http-headers.enum.js +3 -2
  2. package/cjs/implementation/adapter-utils/entity-resource-execute.util.js +36 -34
  3. package/cjs/implementation/adapter-utils/resource-prepare.util.js +1 -1
  4. package/cjs/implementation/adapter.js +14 -19
  5. package/cjs/implementation/express-adapter.js +3 -0
  6. package/cjs/implementation/headers-map.js +18 -0
  7. package/cjs/implementation/http-adapter.js +65 -79
  8. package/cjs/implementation/query-context.js +12 -19
  9. package/cjs/index.js +0 -2
  10. package/cjs/services/json-data-service.js +367 -131
  11. package/cjs/utils/path-to-tree.js +7 -5
  12. package/esm/enums/http-headers.enum.d.ts +3 -2
  13. package/esm/enums/http-headers.enum.js +3 -2
  14. package/esm/implementation/adapter-utils/entity-resource-execute.util.js +35 -33
  15. package/esm/implementation/adapter-utils/resource-execute.util.d.ts +2 -2
  16. package/esm/implementation/adapter-utils/resource-prepare.util.d.ts +2 -2
  17. package/esm/implementation/adapter-utils/resource-prepare.util.js +1 -1
  18. package/esm/implementation/adapter.d.ts +2 -2
  19. package/esm/implementation/adapter.js +12 -17
  20. package/esm/implementation/express-adapter.js +2 -0
  21. package/esm/implementation/headers-map.d.ts +5 -0
  22. package/esm/implementation/headers-map.js +14 -0
  23. package/esm/implementation/http-adapter.d.ts +6 -6
  24. package/esm/implementation/http-adapter.js +61 -75
  25. package/esm/implementation/query-context.d.ts +9 -15
  26. package/esm/implementation/query-context.js +11 -17
  27. package/esm/index.d.ts +0 -2
  28. package/esm/index.js +0 -2
  29. package/esm/interfaces/entity-service.interface.d.ts +8 -6
  30. package/esm/services/json-data-service.d.ts +56 -39
  31. package/esm/services/json-data-service.js +365 -129
  32. package/esm/types.d.ts +0 -3
  33. package/esm/utils/path-to-tree.d.ts +1 -1
  34. package/esm/utils/path-to-tree.js +7 -5
  35. package/i18n/en/error.json +5 -5
  36. package/package.json +11 -8
  37. package/cjs/exception/api-exception.js +0 -68
  38. package/cjs/exception/http-errors/bad-request.error.js +0 -26
  39. package/cjs/exception/http-errors/failed-dependency.error.js +0 -25
  40. package/cjs/exception/http-errors/forbidden.error.js +0 -27
  41. package/cjs/exception/http-errors/internal-server.error.js +0 -27
  42. package/cjs/exception/http-errors/method-not-allowed.error.js +0 -26
  43. package/cjs/exception/http-errors/not-acceptable.error.js +0 -26
  44. package/cjs/exception/http-errors/not-found.error.js +0 -29
  45. package/cjs/exception/http-errors/unauthorized.error.js +0 -26
  46. package/cjs/exception/http-errors/unprocessable-entity.error.js +0 -25
  47. package/cjs/exception/index.js +0 -15
  48. package/cjs/exception/resource-errors/resource-conflict.error.js +0 -19
  49. package/cjs/exception/resource-errors/resource-not-found.error.js +0 -19
  50. package/cjs/exception/wrap-error.js +0 -17
  51. package/cjs/interfaces/query.interface.js +0 -207
  52. package/esm/exception/api-exception.d.ts +0 -40
  53. package/esm/exception/api-exception.js +0 -64
  54. package/esm/exception/http-errors/bad-request.error.d.ts +0 -10
  55. package/esm/exception/http-errors/bad-request.error.js +0 -22
  56. package/esm/exception/http-errors/failed-dependency.error.d.ts +0 -9
  57. package/esm/exception/http-errors/failed-dependency.error.js +0 -21
  58. package/esm/exception/http-errors/forbidden.error.d.ts +0 -11
  59. package/esm/exception/http-errors/forbidden.error.js +0 -23
  60. package/esm/exception/http-errors/internal-server.error.d.ts +0 -9
  61. package/esm/exception/http-errors/internal-server.error.js +0 -22
  62. package/esm/exception/http-errors/method-not-allowed.error.d.ts +0 -10
  63. package/esm/exception/http-errors/method-not-allowed.error.js +0 -22
  64. package/esm/exception/http-errors/not-acceptable.error.d.ts +0 -10
  65. package/esm/exception/http-errors/not-acceptable.error.js +0 -22
  66. package/esm/exception/http-errors/not-found.error.d.ts +0 -13
  67. package/esm/exception/http-errors/not-found.error.js +0 -25
  68. package/esm/exception/http-errors/unauthorized.error.d.ts +0 -10
  69. package/esm/exception/http-errors/unauthorized.error.js +0 -22
  70. package/esm/exception/http-errors/unprocessable-entity.error.d.ts +0 -9
  71. package/esm/exception/http-errors/unprocessable-entity.error.js +0 -21
  72. package/esm/exception/index.d.ts +0 -12
  73. package/esm/exception/index.js +0 -12
  74. package/esm/exception/resource-errors/resource-conflict.error.d.ts +0 -4
  75. package/esm/exception/resource-errors/resource-conflict.error.js +0 -15
  76. package/esm/exception/resource-errors/resource-not-found.error.d.ts +0 -4
  77. package/esm/exception/resource-errors/resource-not-found.error.js +0 -15
  78. package/esm/exception/wrap-error.d.ts +0 -2
  79. package/esm/exception/wrap-error.js +0 -13
  80. package/esm/interfaces/query.interface.d.ts +0 -115
  81. package/esm/interfaces/query.interface.js +0 -203
@@ -1,44 +1,43 @@
1
+ import { ForbiddenError, ResourceNotFoundError } from '@opra/exception';
1
2
  import { translate } from '@opra/i18n';
2
- import { ComplexType } from '@opra/schema';
3
+ import { ComplexType, OpraCountCollectionQuery } from '@opra/schema';
3
4
  import { HttpHeaders } from '../../enums/index.js';
4
- import { ForbiddenError, ResourceNotFoundError } from '../../exception/index.js';
5
- import { OpraQuery } from '../../interfaces/query.interface.js';
6
5
  export async function entityResourceExecute(service, resource, context) {
7
6
  const { query } = context;
8
- if (OpraQuery.isSearchQuery(query)) {
7
+ if (query.kind === 'SearchCollectionQuery') {
9
8
  const promises = [];
10
9
  let search;
11
- let count;
12
- promises.push(executeFn(service, resource, context, query.queryType)
10
+ promises.push(executeFn(service, resource, context)
13
11
  .then(v => search = v));
14
- if (query.count) {
15
- promises.push(executeFn(service, resource, context, 'count')
16
- .then(v => count = v));
12
+ if (query.count && resource.metadata.methods.count) {
13
+ const ctx = {
14
+ query: new OpraCountCollectionQuery(query.resource, { filter: query.filter }),
15
+ resultPath: ''
16
+ };
17
+ Object.setPrototypeOf(ctx, context);
18
+ promises.push(executeFn(service, resource, ctx));
17
19
  }
18
20
  await Promise.all(promises);
19
- context.response.value = {
20
- ...search,
21
- ...count
22
- };
21
+ context.response = search;
23
22
  return;
24
23
  }
25
- context.response.value = await executeFn(service, resource, context, query.queryType);
24
+ context.response = await executeFn(service, resource, context);
26
25
  }
27
- async function executeFn(service, resource, context, queryType) {
28
- const resolverInfo = resource.metadata.methods?.[queryType];
29
- if (!resolverInfo.handler)
26
+ async function executeFn(service, resource, context) {
27
+ const method = context.query.method;
28
+ const resolverInfo = resource.metadata.methods?.[method];
29
+ if (!(resolverInfo && resolverInfo.handler))
30
30
  throw new ForbiddenError({
31
- message: translate('RESOLVER_FORBIDDEN', { queryType }),
31
+ message: translate('RESOLVER_FORBIDDEN', { method }, `The resource endpoint does not accept '{{method}}' operations`),
32
32
  severity: 'error',
33
33
  code: 'RESOLVER_FORBIDDEN'
34
34
  });
35
35
  let result = await resolverInfo.handler(context);
36
- switch (queryType) {
36
+ switch (method) {
37
37
  case 'search':
38
- context.response.headers.set(HttpHeaders.X_Opra_Schema, '/$schema/types/' + resource.dataType.name);
39
- return {
40
- items: Array.isArray(result) ? result : (context.response.value ? [result] : [])
41
- };
38
+ const items = Array.isArray(result) ? result : (context.response ? [result] : []);
39
+ context.responseHeaders.set(HttpHeaders.X_Opra_Schema, resource.dataType.name);
40
+ return items;
42
41
  case 'get':
43
42
  case 'update':
44
43
  if (!result) {
@@ -47,18 +46,22 @@ async function executeFn(service, resource, context, queryType) {
47
46
  }
48
47
  break;
49
48
  case 'count':
50
- return { count: result || 0 };
49
+ context.responseHeaders.set(HttpHeaders.X_Opra_Count, result);
50
+ return;
51
51
  case 'delete':
52
52
  case 'deleteMany':
53
53
  case 'updateMany':
54
- let affectedRecords;
54
+ let affected;
55
55
  if (typeof result === 'number')
56
- affectedRecords = result;
56
+ affected = result;
57
57
  if (typeof result === 'boolean')
58
- affectedRecords = result ? 1 : 0;
58
+ affected = result ? 1 : 0;
59
59
  if (typeof result === 'object')
60
- affectedRecords = result.affectedRows || result.affectedRecords;
61
- return { affectedRecords };
60
+ affected = result.affectedRows || result.affected;
61
+ return {
62
+ operation: context.query.method,
63
+ affected
64
+ };
62
65
  }
63
66
  if (!result)
64
67
  return;
@@ -72,9 +75,8 @@ async function executeFn(service, resource, context, queryType) {
72
75
  result = result && typeof result === 'object' && result[field];
73
76
  }
74
77
  }
75
- if (queryType === 'create')
76
- context.response.status = 201;
77
- if (dataType)
78
- context.response.headers.set(HttpHeaders.X_Opra_Schema, '/$schema/types/' + dataType.name);
78
+ if (method === 'create')
79
+ context.status = 201;
80
+ context.responseHeaders.set(HttpHeaders.X_Opra_Schema, resource.dataType.name);
79
81
  return result;
80
82
  }
@@ -1,3 +1,3 @@
1
- import { BaseResource, OpraService } from '@opra/schema';
1
+ import { OpraResource, OpraService } from '@opra/schema';
2
2
  import { QueryContext } from '../query-context.js';
3
- export declare function resourceExecute(service: OpraService, resource: BaseResource, context: QueryContext): Promise<void>;
3
+ export declare function resourceExecute(service: OpraService, resource: OpraResource, context: QueryContext): Promise<void>;
@@ -1,3 +1,3 @@
1
- import { BaseResource } from '@opra/schema';
1
+ import { OpraResource } from '@opra/schema';
2
2
  import { QueryContext } from '../query-context.js';
3
- export declare function resourcePrepare(resource: BaseResource, context: QueryContext): Promise<void>;
3
+ export declare function resourcePrepare(resource: OpraResource, context: QueryContext): Promise<void>;
@@ -1,6 +1,6 @@
1
1
  export async function resourcePrepare(resource, context) {
2
2
  const { query } = context;
3
- const fn = resource.metadata['pre_' + query.queryType];
3
+ const fn = resource.metadata['pre_' + query.method];
4
4
  if (fn && typeof fn === 'function') {
5
5
  await fn(context);
6
6
  }
@@ -1,6 +1,6 @@
1
+ import { OpraException } from '@opra/exception';
1
2
  import { FallbackLng, I18n, LanguageResource } from '@opra/i18n';
2
3
  import { OpraService } from '@opra/schema';
3
- import { ApiException } from '../exception/index.js';
4
4
  import { IExecutionContext } from '../interfaces/execution-context.interface.js';
5
5
  import { QueryContext } from './query-context.js';
6
6
  export declare namespace OpraAdapter {
@@ -49,7 +49,7 @@ export declare abstract class OpraAdapter<TExecutionContext extends IExecutionCo
49
49
  });
50
50
  protected abstract prepareRequests(executionContext: TExecutionContext): QueryContext[];
51
51
  protected abstract sendResponse(executionContext: TExecutionContext, queryContexts: QueryContext[]): Promise<void>;
52
- protected abstract sendError(executionContext: TExecutionContext, error: ApiException): Promise<void>;
52
+ protected abstract sendError(executionContext: TExecutionContext, error: OpraException): Promise<void>;
53
53
  protected abstract isBatch(executionContext: TExecutionContext): boolean;
54
54
  protected handler(executionContext: TExecutionContext): Promise<void>;
55
55
  protected _getSchemaExecute(ctx: QueryContext): Promise<void>;
@@ -1,8 +1,6 @@
1
1
  import { AsyncEventEmitter } from 'strict-typed-events';
2
+ import { BadRequestError, FailedDependencyError, wrapException } from '@opra/exception';
2
3
  import { I18n } from '@opra/i18n';
3
- import { HttpHeaders } from '../enums/index.js';
4
- import { BadRequestError, FailedDependencyError } from '../exception/index.js';
5
- import { wrapError } from '../exception/wrap-error.js';
6
4
  import { createI18n } from '../utils/create-i18n.js';
7
5
  import { resourceExecute } from './adapter-utils/resource-execute.util.js';
8
6
  import { resourcePrepare } from './adapter-utils/resource-prepare.util.js';
@@ -37,12 +35,12 @@ export class OpraAdapter {
37
35
  // If previous request in bucket had an error and executed an update
38
36
  // we do not execute next requests
39
37
  if (stop) {
40
- context.response.errors.push(new FailedDependencyError());
38
+ context.errors.push(new FailedDependencyError());
41
39
  continue;
42
40
  }
43
41
  try {
44
42
  const promise = (async () => {
45
- if (context.query.queryType === 'schema') {
43
+ if (context.query.method === 'metadata') {
46
44
  await this._getSchemaExecute(context);
47
45
  return;
48
46
  }
@@ -56,7 +54,7 @@ export class OpraAdapter {
56
54
  context.userContext = userContext;
57
55
  await resourceExecute(this.service, resource, context);
58
56
  })().catch(e => {
59
- context.response.errors.push(e);
57
+ context.errors.push(e);
60
58
  });
61
59
  if (exclusive)
62
60
  await promise;
@@ -67,13 +65,13 @@ export class OpraAdapter {
67
65
  // todo execute sub property queries
68
66
  }
69
67
  catch (e) {
70
- context.response.errors.unshift(e);
68
+ context.errors.unshift(e);
71
69
  }
72
- if (context.response.errors.length) {
70
+ if (context.errors.length) {
73
71
  // noinspection SuspiciousTypeOfGuard
74
- context.response.errors = context.response.errors.map(e => wrapError(e));
72
+ context.errors = context.errors.map(e => wrapException(e));
75
73
  if (exclusive)
76
- stop = stop || !!context.response.errors.find(e => !(e.response.severity === 'warning' || e.response.severity === 'info'));
74
+ stop = stop || !!context.errors.find(e => !(e.issue.severity === 'warning' || e.issue.severity === 'info'));
77
75
  }
78
76
  }
79
77
  if (promises)
@@ -82,7 +80,7 @@ export class OpraAdapter {
82
80
  }
83
81
  catch (e) {
84
82
  failed = true;
85
- const error = wrapError(e);
83
+ const error = wrapException(e);
86
84
  await this.sendError(executionContext, error);
87
85
  }
88
86
  finally {
@@ -98,28 +96,25 @@ export class OpraAdapter {
98
96
  async _getSchemaExecute(ctx) {
99
97
  const query = ctx.query;
100
98
  let out;
101
- if (query.resourcePath.length > 2)
99
+ if (query.resourcePath && query.resourcePath.length > 2)
102
100
  throw new BadRequestError();
103
101
  if (query.resourcePath?.length) {
104
102
  if (query.resourcePath[0] === 'resources') {
105
103
  const resource = this.service.getResource(query.resourcePath[1]);
106
104
  out = resource.getSchema(true);
107
105
  query.resourcePath[1] = resource.name;
108
- ctx.response.headers.set(HttpHeaders.X_Opra_Schema, 'http://www.oprajs.com/reference/v1/schema#' + resource.kind);
109
106
  }
110
107
  else if (query.resourcePath[0] === 'types') {
111
108
  const dataType = this.service.getDataType(query.resourcePath[1]);
112
109
  out = dataType.getSchema(true);
113
110
  query.resourcePath[1] = dataType.name;
114
- ctx.response.headers.set(HttpHeaders.X_Opra_Schema, 'http://www.oprajs.com/reference/v1/schema#' + dataType.kind);
115
111
  }
116
112
  else
117
113
  throw new BadRequestError();
118
- ctx.response.value = out;
114
+ ctx.response = out;
119
115
  return;
120
116
  }
121
- ctx.response.headers.set(HttpHeaders.X_Opra_Schema, 'http://www.oprajs.com/reference/v1/schema');
122
- ctx.response.value = this.service.getSchema(true);
117
+ ctx.response = this.service.getSchema(true);
123
118
  }
124
119
  static async initI18n(options) {
125
120
  if (options?.i18n instanceof I18n)
@@ -1,3 +1,4 @@
1
+ import bodyParser from 'body-parser';
1
2
  import { AsyncEventEmitter } from 'strict-typed-events';
2
3
  import { normalizePath } from '@opra/url';
3
4
  import { OpraHttpAdapter } from './http-adapter.js';
@@ -9,6 +10,7 @@ export class OpraExpressAdapter extends OpraHttpAdapter {
9
10
  i18n
10
11
  });
11
12
  const prefix = '/' + normalizePath(options?.prefix, true);
13
+ app.use(prefix, bodyParser.json());
12
14
  app.use(prefix, (request, response, next) => {
13
15
  (async () => {
14
16
  const executionContext = new ExpressExecutionContext(request, response);
@@ -0,0 +1,5 @@
1
+ import { ResponsiveMap } from '@opra/schema';
2
+ export declare class HeadersMap extends ResponsiveMap<string, string> {
3
+ constructor(data?: any);
4
+ toObject(): Record<string, string>;
5
+ }
@@ -0,0 +1,14 @@
1
+ import { ResponsiveMap } from '@opra/schema';
2
+ import { HttpHeaders } from '../enums/index.js';
3
+ export class HeadersMap extends ResponsiveMap {
4
+ constructor(data) {
5
+ super(data, Array.from(Object.values(HttpHeaders)));
6
+ }
7
+ toObject() {
8
+ return Array.from(this.keys()).sort()
9
+ .reduce((a, k) => {
10
+ a[k] = this.get(k);
11
+ return a;
12
+ }, {});
13
+ }
14
+ }
@@ -1,7 +1,7 @@
1
+ import { OpraException } from '@opra/exception';
2
+ import { OpraAnyQuery, OpraGetMetadataQuery } from '@opra/schema';
1
3
  import { OpraURL } from '@opra/url';
2
- import { ApiException } from '../exception/index.js';
3
4
  import { IHttpExecutionContext } from '../interfaces/execution-context.interface.js';
4
- import { OpraGetSchemaQuery, OpraQuery } from '../interfaces/query.interface.js';
5
5
  import { OpraAdapter } from './adapter.js';
6
6
  import { QueryContext } from './query-context.js';
7
7
  export declare namespace OpraHttpAdapter {
@@ -11,17 +11,17 @@ export declare namespace OpraHttpAdapter {
11
11
  }
12
12
  interface PreparedOutput {
13
13
  status: number;
14
- headers?: Record<string, string>;
14
+ headers: Record<string, string>;
15
15
  body?: any;
16
16
  }
17
17
  export declare class OpraHttpAdapter<TExecutionContext extends IHttpExecutionContext> extends OpraAdapter<IHttpExecutionContext> {
18
18
  protected prepareRequests(executionContext: TExecutionContext): QueryContext[];
19
19
  prepareRequest(executionContext: IHttpExecutionContext, url: OpraURL, method: string, headers: Map<string, string>, body?: any): QueryContext;
20
- buildGetSchemaQuery(url: OpraURL): OpraGetSchemaQuery;
21
- buildQuery(url: OpraURL, method: string, body?: any): OpraQuery | undefined;
20
+ buildGGetMetadataQuery(url: OpraURL): OpraGetMetadataQuery;
21
+ buildQuery(url: OpraURL, method: string, body?: any): OpraAnyQuery | undefined;
22
22
  protected sendResponse(executionContext: TExecutionContext, queryContexts: QueryContext[]): Promise<void>;
23
23
  protected isBatch(executionContext: TExecutionContext): boolean;
24
24
  protected createOutput(ctx: QueryContext): PreparedOutput;
25
- protected sendError(executionContext: TExecutionContext, error: ApiException): Promise<void>;
25
+ protected sendError(executionContext: TExecutionContext, error: OpraException): Promise<void>;
26
26
  }
27
27
  export {};
@@ -1,10 +1,9 @@
1
- import { ComplexType, ContainerResource, EntityResource, OpraSchema, ResponsiveMap } from '@opra/schema';
1
+ import { BadRequestError, InternalServerError, IssueSeverity, MethodNotAllowedError, NotFoundError, OpraException, wrapException } from '@opra/exception';
2
+ import { ComplexType, ContainerResource, EntityResource, OpraCreateInstanceQuery, OpraDeleteCollectionQuery, OpraDeleteInstanceQuery, OpraGetFieldQuery, OpraGetInstanceQuery, OpraGetMetadataQuery, OpraSchema, OpraSearchCollectionQuery, OpraUpdateCollectionQuery, OpraUpdateInstanceQuery, } from '@opra/schema';
2
3
  import { OpraURL } from '@opra/url';
3
4
  import { HttpHeaders, HttpStatus } from '../enums/index.js';
4
- import { BadRequestError, InternalServerError, MethodNotAllowedError, NotFoundError, } from '../exception/index.js';
5
- import { wrapError } from '../exception/wrap-error.js';
6
- import { OpraQuery } from '../interfaces/query.interface.js';
7
5
  import { OpraAdapter } from './adapter.js';
6
+ import { HeadersMap } from './headers-map.js';
8
7
  import { QueryContext } from './query-context.js';
9
8
  export class OpraHttpAdapter extends OpraAdapter {
10
9
  prepareRequests(executionContext) {
@@ -15,7 +14,7 @@ export class OpraHttpAdapter extends OpraAdapter {
15
14
  }
16
15
  const url = new OpraURL(req.getUrl());
17
16
  return [
18
- this.prepareRequest(executionContext, url, req.getMethod(), new ResponsiveMap(req.getHeaders()), req.getBody())
17
+ this.prepareRequest(executionContext, url, req.getMethod(), new HeadersMap(req.getHeaders()), req.getBody())
19
18
  ];
20
19
  }
21
20
  prepareRequest(executionContext, url, method, headers, body) {
@@ -32,12 +31,12 @@ export class OpraHttpAdapter extends OpraAdapter {
32
31
  service: this.service,
33
32
  executionContext,
34
33
  query,
35
- headers,
34
+ headers: new HeadersMap(),
36
35
  params: url.searchParams,
37
36
  continueOnError: query.operation === 'read'
38
37
  });
39
38
  }
40
- buildGetSchemaQuery(url) {
39
+ buildGGetMetadataQuery(url) {
41
40
  const pathLen = url.path.size;
42
41
  const resourcePath = [];
43
42
  let pathIndex = 0;
@@ -45,7 +44,7 @@ export class OpraHttpAdapter extends OpraAdapter {
45
44
  const p = url.path.get(pathIndex++);
46
45
  if (p.key)
47
46
  throw new BadRequestError();
48
- if (p.resource !== '$schema') {
47
+ if (p.resource !== '$metadata') {
49
48
  if (pathIndex === 1)
50
49
  resourcePath.push('resources');
51
50
  resourcePath.push(p.resource);
@@ -55,8 +54,9 @@ export class OpraHttpAdapter extends OpraAdapter {
55
54
  pick: url.searchParams.get('$pick'),
56
55
  omit: url.searchParams.get('$omit'),
57
56
  include: url.searchParams.get('$include'),
57
+ resourcePath
58
58
  };
59
- return OpraQuery.forGetSchema(resourcePath, opts);
59
+ return new OpraGetMetadataQuery(opts);
60
60
  }
61
61
  buildQuery(url, method, body) {
62
62
  let container = this.service;
@@ -65,10 +65,10 @@ export class OpraHttpAdapter extends OpraAdapter {
65
65
  // Check if requesting metadata
66
66
  for (let i = 0; i < pathLen; i++) {
67
67
  const p = url.path.get(i);
68
- if (p.resource === '$schema') {
68
+ if (p.resource === '$metadata') {
69
69
  if (method !== 'GET')
70
70
  return;
71
- return this.buildGetSchemaQuery(url);
71
+ return this.buildGGetMetadataQuery(url);
72
72
  }
73
73
  }
74
74
  let pathIndex = 0;
@@ -89,7 +89,7 @@ export class OpraHttpAdapter extends OpraAdapter {
89
89
  switch (method) {
90
90
  case 'GET': {
91
91
  if (scope === 'collection') {
92
- query = OpraQuery.forSearch(resource, {
92
+ query = new OpraSearchCollectionQuery(resource, {
93
93
  filter: url.searchParams.get('$filter'),
94
94
  limit: url.searchParams.get('$limit'),
95
95
  skip: url.searchParams.get('$skip'),
@@ -102,51 +102,39 @@ export class OpraHttpAdapter extends OpraAdapter {
102
102
  });
103
103
  }
104
104
  else {
105
- query = OpraQuery.forGetEntity(resource, p.key, {
105
+ query = new OpraGetInstanceQuery(resource, p.key, {
106
106
  pick: url.searchParams.get('$pick'),
107
107
  omit: url.searchParams.get('$omit'),
108
108
  include: url.searchParams.get('$include')
109
109
  });
110
110
  // Move through properties
111
- let nested;
112
- let path = resource.name;
111
+ let dataType = resource.dataType;
112
+ const curPath = [];
113
+ let parent = query;
113
114
  while (pathIndex < pathLen) {
114
- const dataType = nested
115
- ? this.service.getDataType(nested.property.type || 'string')
116
- : query.resource.dataType;
117
115
  if (!(dataType instanceof ComplexType))
118
- throw new Error(`"${path}" is not a ComplexType and has no fields.`);
116
+ throw new TypeError(`"${resource.name}.${curPath.join()}" is not a ComplexType and has no fields.`);
119
117
  p = url.path.get(pathIndex++);
120
- path += '.' + p.resource;
121
- const prop = dataType.fields.get(p.resource);
122
- if (!prop)
123
- throw new NotFoundError({ message: `Invalid or unknown resource path (${path})` });
124
- const q = OpraQuery.forGetProperty(prop);
125
- if (nested) {
126
- nested.nested = q;
127
- }
128
- else {
129
- query.nested = q;
130
- }
131
- nested = q;
118
+ curPath.push(p.resource);
119
+ const field = dataType.getField(p.resource);
120
+ parent.nested = new OpraGetFieldQuery(parent, field.name);
121
+ parent = parent.nested;
122
+ dataType = parent.dataType;
132
123
  }
133
124
  }
134
125
  break;
135
126
  }
136
127
  case 'DELETE': {
137
- if (scope === 'collection') {
138
- query = OpraQuery.forDeleteMany(resource, {
128
+ query = scope === 'collection'
129
+ ? new OpraDeleteCollectionQuery(resource, {
139
130
  filter: url.searchParams.get('$filter'),
140
- });
141
- }
142
- else {
143
- query = OpraQuery.forDelete(resource, p.key);
144
- }
131
+ })
132
+ : new OpraDeleteInstanceQuery(resource, p.key);
145
133
  break;
146
134
  }
147
135
  case 'POST': {
148
136
  if (scope === 'collection') {
149
- query = OpraQuery.forCreate(resource, body, {
137
+ query = new OpraCreateInstanceQuery(resource, body, {
150
138
  pick: url.searchParams.get('$pick'),
151
139
  omit: url.searchParams.get('$omit'),
152
140
  include: url.searchParams.get('$include')
@@ -155,18 +143,15 @@ export class OpraHttpAdapter extends OpraAdapter {
155
143
  break;
156
144
  }
157
145
  case 'PATCH': {
158
- if (scope === 'collection') {
159
- query = OpraQuery.forUpdateMany(resource, body, {
146
+ query = scope === 'collection'
147
+ ? new OpraUpdateCollectionQuery(resource, body, {
160
148
  filter: url.searchParams.get('$filter')
161
- });
162
- }
163
- else {
164
- query = OpraQuery.forUpdate(resource, p.key, body, {
149
+ })
150
+ : new OpraUpdateInstanceQuery(resource, p.key, body, {
165
151
  pick: url.searchParams.get('$pick'),
166
152
  omit: url.searchParams.get('$omit'),
167
153
  include: url.searchParams.get('$include')
168
154
  });
169
- }
170
155
  break;
171
156
  }
172
157
  }
@@ -176,7 +161,9 @@ export class OpraHttpAdapter extends OpraAdapter {
176
161
  throw new InternalServerError();
177
162
  }
178
163
  catch (e) {
179
- throw BadRequestError.wrap(e);
164
+ if (e instanceof OpraException)
165
+ throw e;
166
+ throw new BadRequestError(e);
180
167
  }
181
168
  }
182
169
  async sendResponse(executionContext, queryContexts) {
@@ -189,19 +176,12 @@ export class OpraHttpAdapter extends OpraAdapter {
189
176
  // this.writeError([], new InternalServerError({message: 'Not implemented yet'}));
190
177
  return;
191
178
  }
192
- if (!outputPackets.length) {
193
- const err = new NotFoundError();
194
- outputPackets.push({
195
- status: err.status,
196
- body: {
197
- errors: [err.response]
198
- }
199
- });
200
- }
179
+ if (!outputPackets.length)
180
+ return this.sendError(executionContext, new NotFoundError());
201
181
  const out = outputPackets[0];
202
182
  const resp = executionContext.getResponseWrapper();
203
183
  resp.setStatus(out.status);
204
- resp.setHeader(HttpHeaders.Content_Type, 'application/json');
184
+ resp.setHeader(HttpHeaders.Content_Type, 'application/opra+json');
205
185
  resp.setHeader(HttpHeaders.Cache_Control, 'no-cache');
206
186
  resp.setHeader(HttpHeaders.Pragma, 'no-cache');
207
187
  resp.setHeader(HttpHeaders.Expires, '-1');
@@ -219,34 +199,35 @@ export class OpraHttpAdapter extends OpraAdapter {
219
199
  }
220
200
  createOutput(ctx) {
221
201
  const { query } = ctx;
222
- let status = ctx.response.status;
223
- let body = ctx.response.value || {};
224
- const errors = ctx.response.errors?.map(e => wrapError(e));
202
+ let body;
203
+ let status = ctx.status || 0;
204
+ const errors = ctx.errors.map(e => wrapException(e));
225
205
  if (errors && errors.length) {
226
- if (!status || status < 400) {
227
- status = 0;
228
- for (const e of errors) {
229
- status = Math.max(status, e.status || status);
230
- }
206
+ // Sort errors from fatal to info
207
+ errors.sort((a, b) => {
208
+ const i = IssueSeverity.Keys.indexOf(a.issue.severity) - IssueSeverity.Keys.indexOf(b.issue.severity);
209
+ if (i === 0)
210
+ return b.status - a.status;
211
+ return i;
212
+ });
213
+ if (!status || status < HttpStatus.BAD_REQUEST) {
214
+ status = errors[0].status;
231
215
  if (status < HttpStatus.BAD_REQUEST)
232
216
  status = HttpStatus.INTERNAL_SERVER_ERROR;
233
217
  }
234
- body.errors = errors.map(e => e.response);
218
+ body = {
219
+ operation: ctx.query.method,
220
+ errors: errors.map(e => e.issue)
221
+ };
235
222
  }
236
223
  else {
237
- delete body.errors;
224
+ body = ctx.response;
238
225
  status = status || (query.operation === 'create' ? HttpStatus.CREATED : HttpStatus.OK);
239
226
  }
240
- // Convert headers map to object
241
- const headers = Array.from(ctx.response.headers.keys()).map(k => k.toLowerCase()).sort()
242
- .reduce((a, k) => {
243
- a[k] = ctx.response.headers.get(k);
244
- return a;
245
- }, {});
246
227
  body = this.i18n.deep(body);
247
228
  return {
248
229
  status,
249
- headers,
230
+ headers: ctx.responseHeaders.toObject(),
250
231
  body
251
232
  };
252
233
  }
@@ -258,6 +239,11 @@ export class OpraHttpAdapter extends OpraAdapter {
258
239
  resp.setHeader(HttpHeaders.Pragma, 'no-cache');
259
240
  resp.setHeader(HttpHeaders.Expires, '-1');
260
241
  resp.setHeader(HttpHeaders.X_Opra_Version, OpraSchema.Version);
261
- resp.send(JSON.stringify(error.response));
242
+ const issue = this.i18n.deep(error.issue);
243
+ const body = {
244
+ operation: 'unknown',
245
+ errors: [issue]
246
+ };
247
+ resp.send(JSON.stringify(body));
262
248
  }
263
249
  }
@@ -1,31 +1,25 @@
1
- import { OpraService, ResponsiveMap } from '@opra/schema';
1
+ import { OpraException } from '@opra/exception';
2
+ import { OpraAnyQuery, OpraService } from '@opra/schema';
2
3
  import { SearchParams } from '@opra/url';
3
4
  import { HttpStatus } from '../enums/index.js';
4
- import { ApiException } from '../exception/index.js';
5
5
  import { ContextType, IExecutionContext, IHttpExecutionContext } from '../interfaces/execution-context.interface.js';
6
- import { OpraQuery } from '../interfaces/query.interface.js';
6
+ import { HeadersMap } from './headers-map.js';
7
7
  export declare type QueryContextArgs = Pick<QueryContext, 'service' | 'executionContext' | 'query' | 'params' | 'headers' | 'userContext' | 'parentValue' | 'continueOnError'>;
8
8
  export declare class QueryContext {
9
9
  readonly service: OpraService;
10
10
  readonly executionContext: IExecutionContext;
11
- readonly query: OpraQuery;
11
+ readonly query: OpraAnyQuery;
12
12
  readonly params: SearchParams;
13
- readonly headers: Map<string, string>;
13
+ readonly headers: HeadersMap;
14
14
  readonly parentValue?: any;
15
15
  readonly resultPath: string;
16
- readonly response: QueryResponse;
16
+ readonly responseHeaders: HeadersMap;
17
+ response?: any;
18
+ errors: OpraException[];
19
+ status?: HttpStatus;
17
20
  userContext?: any;
18
21
  continueOnError?: boolean;
19
22
  constructor(args: QueryContextArgs);
20
23
  get type(): ContextType;
21
24
  switchToHttp(): IHttpExecutionContext;
22
25
  }
23
- export declare type QueryResponseArgs = Pick<QueryResponse, 'status' | 'value' | 'total'>;
24
- export declare class QueryResponse {
25
- headers: ResponsiveMap<string, string>;
26
- errors: ApiException[];
27
- status?: HttpStatus;
28
- value?: any;
29
- total?: number;
30
- constructor(args?: QueryResponseArgs);
31
- }