@opra/core 0.0.12 → 0.0.13

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 (69) hide show
  1. package/cjs/constants.js +2 -1
  2. package/cjs/decorators/{entity-resource.decorator.js → api-entity-resource.decorator.js} +0 -0
  3. package/cjs/decorators/api-resolver.decorator.js +13 -0
  4. package/cjs/exception/http-errors/not-acceptable.error.js +26 -0
  5. package/cjs/exception/index.js +2 -0
  6. package/cjs/exception/resource-errors/resource-not-found.error.js +19 -0
  7. package/cjs/implementation/adapter/adapter.js +31 -1
  8. package/cjs/implementation/adapter/http-adapter.js +117 -90
  9. package/cjs/implementation/data-type/complex-type.js +14 -0
  10. package/cjs/implementation/data-type/data-type.js +12 -0
  11. package/cjs/implementation/data-type/entity-type.js +4 -3
  12. package/cjs/implementation/opra-document.js +2 -6
  13. package/cjs/implementation/opra-service.js +22 -0
  14. package/cjs/implementation/resource/entity-resource-handler.js +35 -12
  15. package/cjs/implementation/resource/resource-handler.js +14 -0
  16. package/cjs/implementation/schema-generator.js +32 -11
  17. package/cjs/index.js +1 -1
  18. package/cjs/interfaces/{opra-schema.metadata.js → metadata/api-resolver.metadata.js} +0 -0
  19. package/cjs/interfaces/metadata/opra-schema.metadata.js +11 -0
  20. package/cjs/interfaces/query.interface.js +19 -2
  21. package/cjs/services/json-data-service.js +235 -4
  22. package/cjs/utils/create-i18n.js +1 -1
  23. package/cjs/utils/get-caller-file.util.js +6 -1
  24. package/esm/constants.d.ts +1 -0
  25. package/esm/constants.js +1 -0
  26. package/esm/decorators/{entity-resource.decorator.d.ts → api-entity-resource.decorator.d.ts} +1 -1
  27. package/esm/decorators/{entity-resource.decorator.js → api-entity-resource.decorator.js} +0 -0
  28. package/esm/decorators/api-resolver.decorator.d.ts +2 -0
  29. package/esm/decorators/api-resolver.decorator.js +9 -0
  30. package/esm/exception/http-errors/not-acceptable.error.d.ts +10 -0
  31. package/esm/exception/http-errors/not-acceptable.error.js +22 -0
  32. package/esm/exception/index.d.ts +2 -0
  33. package/esm/exception/index.js +2 -0
  34. package/esm/exception/resource-errors/resource-not-found.error.d.ts +4 -0
  35. package/esm/exception/resource-errors/resource-not-found.error.js +15 -0
  36. package/esm/implementation/adapter/adapter.d.ts +1 -5
  37. package/esm/implementation/adapter/adapter.js +32 -2
  38. package/esm/implementation/adapter/http-adapter.d.ts +2 -1
  39. package/esm/implementation/adapter/http-adapter.js +118 -91
  40. package/esm/implementation/data-type/complex-type.d.ts +1 -0
  41. package/esm/implementation/data-type/complex-type.js +13 -0
  42. package/esm/implementation/data-type/data-type.d.ts +2 -0
  43. package/esm/implementation/data-type/data-type.js +12 -0
  44. package/esm/implementation/data-type/entity-type.js +3 -3
  45. package/esm/implementation/opra-document.js +2 -6
  46. package/esm/implementation/opra-service.d.ts +1 -0
  47. package/esm/implementation/opra-service.js +21 -0
  48. package/esm/implementation/resource/entity-resource-handler.d.ts +1 -1
  49. package/esm/implementation/resource/entity-resource-handler.js +35 -12
  50. package/esm/implementation/resource/resource-handler.d.ts +3 -3
  51. package/esm/implementation/resource/resource-handler.js +13 -0
  52. package/esm/implementation/schema-generator.js +33 -12
  53. package/esm/index.d.ts +1 -1
  54. package/esm/index.js +1 -1
  55. package/esm/interfaces/metadata/api-resolver.metadata.d.ts +3 -0
  56. package/esm/interfaces/{opra-schema.metadata.js → metadata/api-resolver.metadata.js} +0 -0
  57. package/esm/interfaces/metadata/opra-schema.metadata.d.ts +7 -0
  58. package/esm/interfaces/metadata/opra-schema.metadata.js +10 -0
  59. package/esm/interfaces/query.interface.d.ts +12 -5
  60. package/esm/interfaces/query.interface.js +19 -2
  61. package/esm/services/json-data-service.d.ts +66 -7
  62. package/esm/services/json-data-service.js +235 -4
  63. package/esm/types.d.ts +10 -1
  64. package/esm/utils/create-i18n.js +1 -1
  65. package/esm/utils/get-caller-file.util.d.ts +1 -1
  66. package/esm/utils/get-caller-file.util.js +6 -1
  67. package/i18n/en/error.json +3 -0
  68. package/package.json +6 -7
  69. package/esm/interfaces/opra-schema.metadata.d.ts +0 -14
package/cjs/constants.js CHANGED
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RESOURCE_METADATA = exports.OpraVersion = void 0;
3
+ exports.RESOLVER_METADATA = exports.RESOURCE_METADATA = exports.OpraVersion = void 0;
4
4
  exports.OpraVersion = '1.0';
5
5
  exports.RESOURCE_METADATA = 'opra:resource.metadata';
6
+ exports.RESOLVER_METADATA = 'opra:resolver.metadata';
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ApiResolver = void 0;
4
+ const constants_js_1 = require("../constants.js");
5
+ function ApiResolver(options) {
6
+ return (target, propertyKey) => {
7
+ const metadata = {
8
+ ...options
9
+ };
10
+ Reflect.defineMetadata(constants_js_1.RESOLVER_METADATA, metadata, target, propertyKey);
11
+ };
12
+ }
13
+ exports.ApiResolver = ApiResolver;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NotAcceptableError = void 0;
4
+ const i18n_1 = require("@opra/i18n");
5
+ const index_js_1 = require("../../enums/index.js");
6
+ const api_exception_js_1 = require("../api-exception.js");
7
+ /**
8
+ * 406 Not Acceptable
9
+ * This response is sent when the web server, after performing server-driven content negotiation,
10
+ * doesn't find any content that conforms to the criteria given by the user agent.
11
+ */
12
+ class NotAcceptableError extends api_exception_js_1.ApiException {
13
+ constructor(response, cause) {
14
+ super(response, cause);
15
+ this.status = index_js_1.HttpStatus.NOT_ACCEPTABLE;
16
+ }
17
+ _initResponse(response) {
18
+ super._initResponse({
19
+ message: (0, i18n_1.translate)('error:NOT_ACCEPTABLE', 'Not Acceptable'),
20
+ severity: 'error',
21
+ code: 'NOT_ACCEPTABLE',
22
+ ...response
23
+ });
24
+ }
25
+ }
26
+ exports.NotAcceptableError = NotAcceptableError;
@@ -7,7 +7,9 @@ tslib_1.__exportStar(require("./http-errors/failed-dependency.error.js"), export
7
7
  tslib_1.__exportStar(require("./http-errors/forbidden.error.js"), exports);
8
8
  tslib_1.__exportStar(require("./http-errors/internal-server.error.js"), exports);
9
9
  tslib_1.__exportStar(require("./http-errors/method-not-allowed.error.js"), exports);
10
+ tslib_1.__exportStar(require("./http-errors/not-acceptable.error.js"), exports);
10
11
  tslib_1.__exportStar(require("./http-errors/not-found.error.js"), exports);
11
12
  tslib_1.__exportStar(require("./http-errors/unauthorized.error.js"), exports);
12
13
  tslib_1.__exportStar(require("./http-errors/unprocessable-entity.error.js"), exports);
13
14
  tslib_1.__exportStar(require("./resource-errors/resource-conflict.error.js"), exports);
15
+ tslib_1.__exportStar(require("./resource-errors/resource-not-found.error.js"), exports);
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ResourceNotFoundError = void 0;
4
+ const i18n_1 = require("@opra/i18n");
5
+ const index_js_1 = require("../../enums/index.js");
6
+ const api_exception_js_1 = require("../api-exception.js");
7
+ class ResourceNotFoundError extends api_exception_js_1.ApiException {
8
+ constructor(resource, keyValue, cause) {
9
+ super({
10
+ message: (0, i18n_1.translate)(`error:RESOURCE_NOT_FOUND`, { resource: resource + '@' + keyValue }),
11
+ severity: 'error',
12
+ code: 'RESOURCE_NOT_FOUND',
13
+ subject: resource,
14
+ key: keyValue
15
+ }, cause);
16
+ this.status = index_js_1.HttpStatus.NOT_FOUND;
17
+ }
18
+ }
19
+ exports.ResourceNotFoundError = ResourceNotFoundError;
@@ -41,8 +41,12 @@ class OpraAdapter {
41
41
  continue;
42
42
  }
43
43
  try {
44
- const resource = context.query.resource;
45
44
  const promise = (async () => {
45
+ if (context.query.queryType === 'metadata') {
46
+ await this._metadataExecute(context);
47
+ return;
48
+ }
49
+ const resource = context.query.resource;
46
50
  await resource.prepare(context);
47
51
  if (this.userContextResolver && !userContext)
48
52
  userContext = this.userContextResolver({
@@ -91,6 +95,32 @@ class OpraAdapter {
91
95
  }
92
96
  }
93
97
  }
98
+ async _metadataExecute(ctx) {
99
+ const query = ctx.query;
100
+ let out;
101
+ if (query.resourcePath.length > 2)
102
+ throw new index_js_1.BadRequestError();
103
+ if (query.resourcePath?.length) {
104
+ if (query.resourcePath[0] === 'resources') {
105
+ const resource = this.service.getResource(query.resourcePath[1]);
106
+ out = resource.getMetadata();
107
+ query.resourcePath[1] = resource.name;
108
+ }
109
+ else if (query.resourcePath[0] === 'types') {
110
+ const dataType = this.service.getDataType(query.resourcePath[1]);
111
+ out = dataType.getMetadata();
112
+ query.resourcePath[1] = dataType.name;
113
+ }
114
+ else
115
+ throw new index_js_1.BadRequestError();
116
+ ctx.response.value = {
117
+ '@opra:metadata': '/$metadata/' + query.resourcePath.join('/'),
118
+ ...out
119
+ };
120
+ return;
121
+ }
122
+ ctx.response.value = this.service.getMetadata();
123
+ }
94
124
  static async initI18n(options) {
95
125
  if (options?.i18n instanceof i18n_1.I18n)
96
126
  return options.i18n;
@@ -33,7 +33,7 @@ class OpraHttpAdapter extends adapter_js_1.OpraAdapter {
33
33
  const query = this.buildQuery(url, method, body);
34
34
  if (!query)
35
35
  throw new index_js_2.MethodNotAllowedError({
36
- message: `Method "${method}" is not allowed by target resource`
36
+ message: `Method "${method}" is not allowed by target endpoint`
37
37
  });
38
38
  return new query_context_js_1.QueryContext({
39
39
  service: this.service,
@@ -44,119 +44,146 @@ class OpraHttpAdapter extends adapter_js_1.OpraAdapter {
44
44
  continueOnError: query.operation === 'read'
45
45
  });
46
46
  }
47
+ buildMetadataQuery(url) {
48
+ const pathLen = url.path.size;
49
+ const resourcePath = [];
50
+ let pathIndex = 0;
51
+ while (pathIndex < pathLen) {
52
+ const p = url.path.get(pathIndex++);
53
+ if (p.key)
54
+ throw new index_js_2.BadRequestError();
55
+ if (p.resource !== '$metadata') {
56
+ if (pathIndex === 1)
57
+ resourcePath.push('resources');
58
+ resourcePath.push(p.resource);
59
+ }
60
+ }
61
+ const opts = {
62
+ pick: url.searchParams.get('$pick'),
63
+ omit: url.searchParams.get('$omit'),
64
+ include: url.searchParams.get('$include'),
65
+ };
66
+ return query_interface_js_1.OpraQuery.forGetMetadata(resourcePath, opts);
67
+ }
47
68
  buildQuery(url, method, body) {
48
69
  let container = this.service;
49
70
  try {
50
- let pathIndex = 0;
51
71
  const pathLen = url.path.size;
72
+ // Check if requesting metadata
73
+ for (let i = 0; i < pathLen; i++) {
74
+ const p = url.path.get(i);
75
+ if (p.resource === '$metadata') {
76
+ if (method !== 'GET')
77
+ return;
78
+ return this.buildMetadataQuery(url);
79
+ }
80
+ }
81
+ let pathIndex = 0;
52
82
  while (pathIndex < pathLen) {
53
83
  let p = url.path.get(pathIndex++);
54
84
  const resource = container.getResource(p.resource);
55
85
  // Move through path directories (containers)
56
86
  if (resource instanceof container_resource_handler_js_1.ContainerResourceHandler) {
57
87
  container = resource;
88
+ continue;
58
89
  }
59
- else {
60
- method = method.toUpperCase();
61
- if (resource instanceof entity_resource_handler_js_1.EntityResourceHandler) {
62
- const scope = p.key ? 'instance' : 'collection';
63
- if (pathIndex < pathLen && !(method === 'GET' && scope === 'instance'))
64
- return;
65
- let query;
66
- switch (method) {
67
- case 'GET': {
68
- if (scope === 'collection') {
69
- query = query_interface_js_1.OpraQuery.forSearch(resource, {
70
- filter: url.searchParams.get('$filter'),
71
- limit: url.searchParams.get('$limit'),
72
- skip: url.searchParams.get('$skip'),
73
- distinct: url.searchParams.get('$distinct'),
74
- count: url.searchParams.get('$count'),
75
- sort: url.searchParams.get('$sort'),
76
- pick: url.searchParams.get('$pick'),
77
- omit: url.searchParams.get('$omit'),
78
- include: url.searchParams.get('$include'),
79
- });
80
- }
81
- else {
82
- query = query_interface_js_1.OpraQuery.forGet(resource, p.key, {
83
- pick: url.searchParams.get('$pick'),
84
- omit: url.searchParams.get('$omit'),
85
- include: url.searchParams.get('$include')
86
- });
87
- // Move through properties
88
- let nested;
89
- let path = resource.name;
90
- while (pathIndex < pathLen) {
91
- const dataType = nested
92
- ? this.service.getDataType(nested.property.type || 'string')
93
- : query.resource.dataType;
94
- if (!(dataType instanceof complex_type_js_1.ComplexType))
95
- throw new Error(`"${path}" is not a ComplexType and has no properties.`);
96
- p = url.path.get(pathIndex++);
97
- path += '.' + p.resource;
98
- const prop = dataType.properties?.[p.resource];
99
- if (!prop)
100
- throw new index_js_2.NotFoundError({ message: `Invalid or unknown resource path (${path})` });
101
- const q = query_interface_js_1.OpraQuery.forGetProperty(prop);
102
- if (nested) {
103
- nested.nested = q;
104
- }
105
- else {
106
- query.nested = q;
107
- }
108
- nested = q;
90
+ method = method.toUpperCase();
91
+ if (resource instanceof entity_resource_handler_js_1.EntityResourceHandler) {
92
+ const scope = p.key ? 'instance' : 'collection';
93
+ if (pathIndex < pathLen && !(method === 'GET' && scope === 'instance'))
94
+ return;
95
+ let query;
96
+ switch (method) {
97
+ case 'GET': {
98
+ if (scope === 'collection') {
99
+ query = query_interface_js_1.OpraQuery.forSearch(resource, {
100
+ filter: url.searchParams.get('$filter'),
101
+ limit: url.searchParams.get('$limit'),
102
+ skip: url.searchParams.get('$skip'),
103
+ distinct: url.searchParams.get('$distinct'),
104
+ count: url.searchParams.get('$count'),
105
+ sort: url.searchParams.get('$sort'),
106
+ pick: url.searchParams.get('$pick'),
107
+ omit: url.searchParams.get('$omit'),
108
+ include: url.searchParams.get('$include'),
109
+ });
110
+ }
111
+ else {
112
+ query = query_interface_js_1.OpraQuery.forGetEntity(resource, p.key, {
113
+ pick: url.searchParams.get('$pick'),
114
+ omit: url.searchParams.get('$omit'),
115
+ include: url.searchParams.get('$include')
116
+ });
117
+ // Move through properties
118
+ let nested;
119
+ let path = resource.name;
120
+ while (pathIndex < pathLen) {
121
+ const dataType = nested
122
+ ? this.service.getDataType(nested.property.type || 'string')
123
+ : query.resource.dataType;
124
+ if (!(dataType instanceof complex_type_js_1.ComplexType))
125
+ throw new Error(`"${path}" is not a ComplexType and has no properties.`);
126
+ p = url.path.get(pathIndex++);
127
+ path += '.' + p.resource;
128
+ const prop = dataType.properties?.[p.resource];
129
+ if (!prop)
130
+ throw new index_js_2.NotFoundError({ message: `Invalid or unknown resource path (${path})` });
131
+ const q = query_interface_js_1.OpraQuery.forGetProperty(prop);
132
+ if (nested) {
133
+ nested.nested = q;
134
+ }
135
+ else {
136
+ query.nested = q;
109
137
  }
138
+ nested = q;
110
139
  }
111
- break;
112
140
  }
113
- case 'DELETE': {
114
- if (scope === 'collection') {
115
- query = query_interface_js_1.OpraQuery.forDeleteMany(resource, {
116
- filter: url.searchParams.get('$filter'),
117
- });
118
- }
119
- else {
120
- query = query_interface_js_1.OpraQuery.forDelete(resource, p.key);
121
- }
122
- break;
141
+ break;
142
+ }
143
+ case 'DELETE': {
144
+ if (scope === 'collection') {
145
+ query = query_interface_js_1.OpraQuery.forDeleteMany(resource, {
146
+ filter: url.searchParams.get('$filter'),
147
+ });
123
148
  }
124
- case 'POST': {
125
- if (scope === 'collection') {
126
- query = query_interface_js_1.OpraQuery.forCreate(resource, body, {
127
- pick: url.searchParams.get('$pick'),
128
- omit: url.searchParams.get('$omit'),
129
- include: url.searchParams.get('$include')
130
- });
131
- }
132
- break;
149
+ else {
150
+ query = query_interface_js_1.OpraQuery.forDelete(resource, p.key);
133
151
  }
134
- case 'PATCH': {
135
- if (scope === 'collection') {
136
- query = query_interface_js_1.OpraQuery.forUpdateMany(resource, body, {
137
- filter: url.searchParams.get('$filter')
138
- });
139
- }
140
- else {
141
- query = query_interface_js_1.OpraQuery.forUpdate(resource, p.key, body, {
142
- pick: url.searchParams.get('$pick'),
143
- omit: url.searchParams.get('$omit'),
144
- include: url.searchParams.get('$include')
145
- });
146
- }
147
- break;
152
+ break;
153
+ }
154
+ case 'POST': {
155
+ if (scope === 'collection') {
156
+ query = query_interface_js_1.OpraQuery.forCreate(resource, body, {
157
+ pick: url.searchParams.get('$pick'),
158
+ omit: url.searchParams.get('$omit'),
159
+ include: url.searchParams.get('$include')
160
+ });
161
+ }
162
+ break;
163
+ }
164
+ case 'PATCH': {
165
+ if (scope === 'collection') {
166
+ query = query_interface_js_1.OpraQuery.forUpdateMany(resource, body, {
167
+ filter: url.searchParams.get('$filter')
168
+ });
169
+ }
170
+ else {
171
+ query = query_interface_js_1.OpraQuery.forUpdate(resource, p.key, body, {
172
+ pick: url.searchParams.get('$pick'),
173
+ omit: url.searchParams.get('$omit'),
174
+ include: url.searchParams.get('$include')
175
+ });
148
176
  }
177
+ break;
149
178
  }
150
- return query;
151
179
  }
180
+ return query;
152
181
  }
153
182
  }
154
183
  throw new index_js_2.InternalServerError();
155
184
  }
156
185
  catch (e) {
157
- if (e instanceof index_js_2.ApiException)
158
- throw e;
159
- throw new index_js_2.BadRequestError({ message: e.message });
186
+ throw index_js_2.BadRequestError.wrap(e);
160
187
  }
161
188
  }
162
189
  async sendResponse(executionContext, queryContexts) {
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ComplexType = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const putil_merge_1 = tslib_1.__importDefault(require("putil-merge"));
4
6
  const responsive_object_js_1 = require("../../utils/responsive-object.js");
5
7
  const terminal_utils_js_1 = require("../../utils/terminal-utils.js");
6
8
  const data_type_js_1 = require("./data-type.js");
@@ -28,6 +30,18 @@ class ComplexType extends data_type_js_1.DataType {
28
30
  throw new Error(`"${this.name}" type has no property named "${name}"`);
29
31
  return t;
30
32
  }
33
+ getMetadata() {
34
+ const out = super.getMetadata();
35
+ if (this.additionalProperties)
36
+ out.additionalProperties = this.additionalProperties;
37
+ if (this.ownProperties) {
38
+ out.properties = {};
39
+ for (const [k, prop] of Object.entries(this.ownProperties)) {
40
+ out.properties[k] = (0, putil_merge_1.default)({}, prop, { deep: true });
41
+ }
42
+ }
43
+ return out;
44
+ }
31
45
  toString() {
32
46
  return `[${Object.getPrototypeOf(this).constructor.name} ${this.name}]`;
33
47
  }
@@ -25,11 +25,23 @@ class DataType {
25
25
  get description() {
26
26
  return this._args.description;
27
27
  }
28
+ get abstract() {
29
+ return !!this._args.abstract;
30
+ }
28
31
  get ctor() {
29
32
  return this._args.ctor;
30
33
  }
31
34
  is(typeName) {
32
35
  return this.name === typeName || !!(this.base && this.base.is(typeName));
33
36
  }
37
+ getMetadata() {
38
+ return {
39
+ kind: this.kind,
40
+ base: this.base?.name,
41
+ abstract: this.abstract ? true : undefined,
42
+ name: this.name,
43
+ description: this.description,
44
+ };
45
+ }
34
46
  }
35
47
  exports.DataType = DataType;
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EntityType = void 0;
4
- const optionals_1 = require("@opra/optionals");
4
+ const tslib_1 = require("tslib");
5
+ const Optionals = tslib_1.__importStar(require("@opra/optionals"));
5
6
  const complex_type_js_1 = require("./complex-type.js");
6
7
  class EntityType extends complex_type_js_1.ComplexType {
7
8
  constructor(owner, args, base) {
@@ -10,8 +11,8 @@ class EntityType extends complex_type_js_1.ComplexType {
10
11
  }, base);
11
12
  this._args.kind = 'EntityType';
12
13
  // Try to determine primary key info from SQB
13
- if (args.ctor) {
14
- const sqbEntity = optionals_1.SqbConnect.EntityMetadata.get(args.ctor);
14
+ if (Optionals.SqbConnect && args.ctor) {
15
+ const sqbEntity = Optionals.SqbConnect.EntityMetadata.get(args.ctor);
15
16
  if (sqbEntity?.indexes) {
16
17
  const primaryIndex = sqbEntity.indexes.find(x => x.primary);
17
18
  if (primaryIndex) {
@@ -74,7 +74,8 @@ class OpraDocument {
74
74
  recursiveSet.add(schema.name);
75
75
  let baseType;
76
76
  if (schema.base) {
77
- if (!this.types[schema.base]) {
77
+ baseType = this.types[schema.base];
78
+ if (!baseType) {
78
79
  const baseSchema = dataTypes.find(dt => dt.name.toLowerCase() === schema.base?.toLowerCase()) ||
79
80
  internal_data_types_js_1.internalDataTypes.get(schema.base.toLowerCase());
80
81
  if (!baseSchema)
@@ -106,11 +107,6 @@ class OpraDocument {
106
107
  return dataType;
107
108
  };
108
109
  dataTypes.forEach(dataType => processDataType(dataType));
109
- // Sort data types by name
110
- const newTypes = (0, responsive_object_js_1.Responsive)();
111
- Object.keys(this.types).sort()
112
- .forEach(name => newTypes[name] = this.types[name]);
113
- this._types = newTypes;
114
110
  }
115
111
  }
116
112
  exports.OpraDocument = OpraDocument;
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OpraService = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const putil_merge_1 = tslib_1.__importDefault(require("putil-merge"));
4
6
  const schema_1 = require("@opra/schema");
7
+ const constants_js_1 = require("../constants.js");
8
+ const internal_data_types_js_1 = require("../utils/internal-data-types.js");
5
9
  const responsive_object_js_1 = require("../utils/responsive-object.js");
6
10
  const entity_type_js_1 = require("./data-type/entity-type.js");
7
11
  const opra_document_js_1 = require("./opra-document.js");
@@ -32,6 +36,24 @@ class OpraService extends opra_document_js_1.OpraDocument {
32
36
  throw new Error(`"${name}" is not an EntityResource`);
33
37
  return t;
34
38
  }
39
+ getMetadata() {
40
+ const out = {
41
+ '@opra:metadata': '/$metadata',
42
+ version: constants_js_1.OpraVersion,
43
+ servers: this.servers?.map(x => (0, putil_merge_1.default)({}, x, { deep: true })),
44
+ info: (0, putil_merge_1.default)({}, this.info, { deep: true }),
45
+ types: [],
46
+ resources: []
47
+ };
48
+ for (const [k, dataType] of Object.entries(this.types)) {
49
+ if (!internal_data_types_js_1.internalDataTypes.has(k))
50
+ out.types.push(dataType.getMetadata());
51
+ }
52
+ for (const resource of Object.values(this.resources)) {
53
+ out.resources.push(resource.getMetadata());
54
+ }
55
+ return out;
56
+ }
35
57
  _addResources(resources) {
36
58
  for (const r of resources) {
37
59
  if (schema_1.OpraSchema.isEntityResource(r)) {
@@ -3,11 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EntityResourceHandler = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const lodash_1 = tslib_1.__importDefault(require("lodash"));
6
+ const i18n_1 = require("@opra/i18n");
7
+ const index_js_1 = require("../../exception/index.js");
6
8
  const query_interface_js_1 = require("../../interfaces/query.interface.js");
9
+ const complex_type_js_1 = require("../data-type/complex-type.js");
7
10
  const entity_type_js_1 = require("../data-type/entity-type.js");
8
11
  const resource_handler_js_1 = require("./resource-handler.js");
9
- var isSearchQuery = query_interface_js_1.OpraQuery.isSearchQuery;
10
- const index_js_1 = require("../../exception/index.js");
11
12
  class EntityResourceHandler extends resource_handler_js_1.ResourceHandler {
12
13
  service;
13
14
  dataType;
@@ -24,7 +25,7 @@ class EntityResourceHandler extends resource_handler_js_1.ResourceHandler {
24
25
  }
25
26
  async execute(ctx) {
26
27
  const { query } = ctx;
27
- if (isSearchQuery(query)) {
28
+ if (query_interface_js_1.OpraQuery.isSearchQuery(query)) {
28
29
  const promises = [];
29
30
  let search;
30
31
  let count;
@@ -43,12 +44,28 @@ class EntityResourceHandler extends resource_handler_js_1.ResourceHandler {
43
44
  }
44
45
  ctx.response.value = await this._executeFn(ctx, query.queryType);
45
46
  }
46
- async _executeFn(ctx, fnName) {
47
- const fn = this._args[fnName];
48
- let result = typeof fn === 'function' ? (await fn(ctx)) : undefined;
49
- switch (fnName) {
47
+ async _executeFn(ctx, queryType) {
48
+ const resolverInfo = this._args.resolvers?.[queryType];
49
+ if (resolverInfo.forbidden || !resolverInfo.handler)
50
+ throw new index_js_1.ForbiddenError({
51
+ message: (0, i18n_1.translate)('RESOLVER_FORBIDDEN', { queryType }),
52
+ severity: 'error',
53
+ code: 'RESOLVER_FORBIDDEN'
54
+ });
55
+ let result = await resolverInfo.handler(ctx);
56
+ switch (queryType) {
50
57
  case 'search':
51
- return { items: Array.isArray(result) ? result : (ctx.response.value ? [result] : []) };
58
+ return {
59
+ '@opra:metadata': '/$metadata/types/' + this.dataType.name,
60
+ items: Array.isArray(result) ? result : (ctx.response.value ? [result] : [])
61
+ };
62
+ case 'get':
63
+ case 'update':
64
+ if (!result) {
65
+ const query = ctx.query;
66
+ throw new index_js_1.ResourceNotFoundError(this.name, query.keyValue);
67
+ }
68
+ break;
52
69
  case 'count':
53
70
  return { count: result || 0 };
54
71
  case 'delete':
@@ -63,18 +80,24 @@ class EntityResourceHandler extends resource_handler_js_1.ResourceHandler {
63
80
  affectedRecords = result.affectedRows || result.affectedRecords;
64
81
  return { affectedRecords };
65
82
  }
66
- result = Array.isArray(result) ? result[0] : result;
67
83
  if (!result)
68
- throw new index_js_1.UnprocessableEntityError();
84
+ return;
85
+ result = Array.isArray(result) ? result[0] : result;
86
+ let dataType = this.dataType;
69
87
  if (ctx.resultPath) {
70
88
  const pathArray = ctx.resultPath.split('.');
71
89
  for (const property of pathArray) {
90
+ const prop = dataType instanceof complex_type_js_1.ComplexType ? dataType.properties?.[property] : undefined;
91
+ dataType = prop && prop.type ? this.service.types[prop.type || 'string'] : undefined;
72
92
  result = result && typeof result === 'object' && result[property];
73
93
  }
74
94
  }
75
- if (fnName === 'create')
95
+ if (queryType === 'create')
76
96
  ctx.response.status = 201;
77
- return result;
97
+ return {
98
+ '@opra:metadata': dataType ? '/$metadata/types/' + dataType.name : '__unknown__',
99
+ ...result
100
+ };
78
101
  }
79
102
  }
80
103
  exports.EntityResourceHandler = EntityResourceHandler;
@@ -1,9 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ResourceHandler = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const putil_merge_1 = tslib_1.__importDefault(require("putil-merge"));
4
6
  const terminal_utils_js_1 = require("../../utils/terminal-utils.js");
5
7
  class ResourceHandler {
6
8
  _args;
9
+ path;
7
10
  constructor(args) {
8
11
  this._args = args;
9
12
  }
@@ -23,6 +26,17 @@ class ResourceHandler {
23
26
  await fn(ctx);
24
27
  }
25
28
  }
29
+ getMetadata() {
30
+ return (0, putil_merge_1.default)({}, {
31
+ ...this._args,
32
+ instance: undefined,
33
+ }, {
34
+ deep: true,
35
+ filter: (source, key) => {
36
+ return (key !== 'instance' && typeof source[key] !== 'function' && source[key] != null);
37
+ }
38
+ });
39
+ }
26
40
  [terminal_utils_js_1.nodeInspectCustom]() {
27
41
  return `[${terminal_utils_js_1.colorFgYellow + Object.getPrototypeOf(this).constructor.name + terminal_utils_js_1.colorReset}` +
28
42
  ` ${terminal_utils_js_1.colorFgMagenta + this.name + terminal_utils_js_1.colorReset}]`;