@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
@@ -6,7 +6,6 @@ const schema_1 = require("@opra/schema");
6
6
  const constants_js_1 = require("../constants.js");
7
7
  const class_utils_js_1 = require("../utils/class-utils.js");
8
8
  const internal_data_types_js_1 = require("../utils/internal-data-types.js");
9
- var isDataType = schema_1.OpraSchema.isDataType;
10
9
  const entityMethods = ['search', 'count', 'create', 'get', 'update', 'updateMany', 'delete', 'deleteMany'];
11
10
  class SchemaGenerator {
12
11
  _dataTypes = {};
@@ -58,7 +57,7 @@ class SchemaGenerator {
58
57
  }
59
58
  return this.addDataType(schema);
60
59
  }
61
- if (!isDataType(thunk))
60
+ if (!schema_1.OpraSchema.isDataType(thunk))
62
61
  throw new TypeError(`Invalid data type schema`);
63
62
  // Check if datatype previously added
64
63
  const currentSchema = this._dataTypes[thunk.name];
@@ -92,16 +91,25 @@ class SchemaGenerator {
92
91
  ...metadata,
93
92
  type,
94
93
  name,
95
- instance
94
+ instance,
95
+ resolvers: {}
96
96
  };
97
97
  if (schema_1.OpraSchema.isEntityResource(resourceSchema)) {
98
98
  for (const methodName of entityMethods) {
99
- let fn = instance['pre_' + methodName];
100
- if (typeof fn === 'function')
101
- resourceSchema['pre_' + methodName] = fn.bind(instance);
102
- fn = instance[methodName];
103
- if (typeof fn === 'function')
104
- resourceSchema[methodName] = fn.bind(instance);
99
+ let fn = instance[methodName];
100
+ if (typeof fn === 'function') {
101
+ const info = resourceSchema.resolvers[methodName] = {
102
+ ...Reflect.getMetadata(constants_js_1.RESOLVER_METADATA, proto, methodName)
103
+ };
104
+ if (!info.forbidden) {
105
+ info.handler = fn.bind(instance);
106
+ fn = instance['pre_' + methodName];
107
+ if (typeof fn === 'function')
108
+ resourceSchema['pre_' + methodName] = fn.bind(instance);
109
+ }
110
+ }
111
+ else
112
+ resourceSchema.resolvers[methodName] = { forbidden: true };
105
113
  }
106
114
  }
107
115
  }
@@ -148,8 +156,21 @@ class SchemaGenerator {
148
156
  await generator.addResource(resource);
149
157
  }
150
158
  }
151
- const types = Object.keys(generator._dataTypes).sort()
152
- .map(name => generator._dataTypes[name]);
159
+ const types = Object.keys(generator._dataTypes)
160
+ .map(name => generator._dataTypes[name])
161
+ .sort((a, b) => {
162
+ if (a.kind !== b.kind) {
163
+ if (a.kind === 'SimpleType')
164
+ return -1;
165
+ if (b.kind === 'SimpleType')
166
+ return 1;
167
+ if (a.kind === 'ComplexType')
168
+ return -1;
169
+ if (b.kind === 'ComplexType')
170
+ return 1;
171
+ }
172
+ return (a.name < b.name ? -1 : 1);
173
+ });
153
174
  const resources = Object.keys(generator._resources).sort()
154
175
  .map(name => generator._resources[name]);
155
176
  return {
package/cjs/index.js CHANGED
@@ -6,7 +6,7 @@ tslib_1.__exportStar(require("./constants.js"), exports);
6
6
  tslib_1.__exportStar(require("./types.js"), exports);
7
7
  tslib_1.__exportStar(require("./enums/index.js"), exports);
8
8
  tslib_1.__exportStar(require("./exception/index.js"), exports);
9
- tslib_1.__exportStar(require("./decorators/entity-resource.decorator.js"), exports);
9
+ tslib_1.__exportStar(require("./decorators/api-entity-resource.decorator.js"), exports);
10
10
  tslib_1.__exportStar(require("./interfaces/execution-context.interface.js"), exports);
11
11
  tslib_1.__exportStar(require("./interfaces/query.interface.js"), exports);
12
12
  tslib_1.__exportStar(require("./interfaces/resource-container.interface.js"), exports);
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /*
4
+ export type ResourceReadOperationMetadata = StrictOmit<OpraSchema.ResourceReadOperation, 'handler'>;
5
+ export type ResourceSearchOperationMetadata = StrictOmit<OpraSchema.ResourceSearchOperation, 'handler'>;
6
+ export type ResourceCreateOperationMetadata = StrictOmit<OpraSchema.ResourceCreateOperation, 'handler'>;
7
+ export type ResourceUpdateOperationMetadata = StrictOmit<OpraSchema.ResourceUpdateOperation, 'handler'>;
8
+ export type ResourcePatchOperationMetadata = StrictOmit<OpraSchema.ResourcePatchOperation, 'handler'>;
9
+ export type ResourceDeleteOperationMetadata = StrictOmit<OpraSchema.ResourceDeleteOperation, 'handler'>;
10
+ export type ResourceExecuteOperationMetadata = StrictOmit<OpraSchema.ResourceExecuteOperation, 'handler'>;
11
+ */
@@ -25,7 +25,24 @@ var OpraQuery;
25
25
  return out;
26
26
  }
27
27
  OpraQuery.forCreate = forCreate;
28
- function forGet(resource, key, options) {
28
+ function forGetMetadata(resourcePath, options) {
29
+ // if (options?.pick)
30
+ // options.pick = normalizePick(resource, options.pick);
31
+ // if (options?.omit)
32
+ // options.omit = normalizePick(resource, options.omit);
33
+ // if (options?.include)
34
+ // options.include = normalizePick(resource, options.include);
35
+ const out = {
36
+ queryType: 'metadata',
37
+ scope: 'instance',
38
+ resourcePath,
39
+ operation: 'read',
40
+ };
41
+ Object.assign(out, lodash_1.default.omit(options, Object.keys(out)));
42
+ return out;
43
+ }
44
+ OpraQuery.forGetMetadata = forGetMetadata;
45
+ function forGetEntity(resource, key, options) {
29
46
  if (options?.pick)
30
47
  options.pick = normalizePick(resource, options.pick);
31
48
  if (options?.omit)
@@ -43,7 +60,7 @@ var OpraQuery;
43
60
  Object.assign(out, lodash_1.default.omit(options, Object.keys(out)));
44
61
  return out;
45
62
  }
46
- OpraQuery.forGet = forGet;
63
+ OpraQuery.forGetEntity = forGetEntity;
47
64
  function forSearch(resource, options) {
48
65
  if (options?.pick)
49
66
  options.pick = normalizePick(resource, options.pick);
@@ -2,14 +2,245 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.JsonDataService = void 0;
4
4
  const tslib_1 = require("tslib");
5
+ const lodash_1 = tslib_1.__importDefault(require("lodash"));
5
6
  const rule_judgment_1 = tslib_1.__importDefault(require("rule-judgment"));
6
- const data_service_js_1 = require("./data-service.js");
7
+ const url_1 = require("@opra/url");
8
+ const index_js_1 = require("../exception/index.js");
7
9
  // Fix invalid importing (with ESM) of rule-judgment package
8
- const toArrayFilter = typeof rule_judgment_1.default === 'function' ? rule_judgment_1.default : rule_judgment_1.default.default;
9
- class JsonDataService extends data_service_js_1.DataService {
10
+ const createFilterFn = typeof rule_judgment_1.default === 'function'
11
+ ? rule_judgment_1.default
12
+ : rule_judgment_1.default.default;
13
+ class JsonDataService {
14
+ resourceName;
10
15
  data;
16
+ primaryKey;
11
17
  constructor(args) {
12
- super();
18
+ this.resourceName = args.resourceName;
19
+ this.data = args.data;
20
+ this.primaryKey = args.primaryKey;
21
+ }
22
+ processRequest(ctx) {
23
+ const prepared = JsonDataService.prepare(ctx.query);
24
+ const fn = this[prepared.method];
25
+ if (!fn)
26
+ throw new TypeError(`Unimplemented method (${prepared.method})`);
27
+ return fn.apply(this, prepared.args);
28
+ }
29
+ get(keyValue, options) {
30
+ const primaryKey = this.primaryKey;
31
+ let v = this.data.find(x => '' + x[primaryKey] === '' + keyValue);
32
+ v = JsonDataService.filterProperties(v, options?.pick, options?.omit, options?.include);
33
+ return v;
34
+ }
35
+ count(options) {
36
+ return this.search(options).length;
37
+ }
38
+ search(options) {
39
+ let out;
40
+ if (options?.filter) {
41
+ const filter = JsonDataService.convertFilter(options.filter);
42
+ const filterFn = createFilterFn(filter);
43
+ out = this.data.filter(filterFn);
44
+ }
45
+ else
46
+ out = this.data;
47
+ return out.map(v => JsonDataService.filterProperties(v, options?.pick, options?.omit, options?.include));
48
+ }
49
+ create(data, options) {
50
+ if (this.get(data[this.primaryKey]))
51
+ throw new index_js_1.ResourceConflictError(this.resourceName, this.primaryKey);
52
+ this.data.push(data);
53
+ return JsonDataService.filterProperties(data, options?.pick, options?.omit, options?.include);
54
+ }
55
+ update(keyValue, data, options) {
56
+ const primaryKey = this.primaryKey;
57
+ const i = this.data.findIndex(x => '' + x[primaryKey] === '' + keyValue);
58
+ if (i >= 0) {
59
+ data = Object.assign(this.data[i], data);
60
+ return JsonDataService.filterProperties(data, options?.pick, options?.omit, options?.include);
61
+ }
62
+ }
63
+ updateMany(data, options) {
64
+ const items = this.search({ filter: options?.filter });
65
+ for (let i = 0; i < items.length; i++) {
66
+ Object.assign(items[i], data);
67
+ }
68
+ return items.length;
69
+ }
70
+ delete(keyValue) {
71
+ const primaryKey = this.primaryKey;
72
+ const i = this.data.findIndex(x => '' + x[primaryKey] === '' + keyValue);
73
+ if (i >= 0) {
74
+ this.data = this.data.slice(i, 1);
75
+ return true;
76
+ }
77
+ return false;
78
+ }
79
+ deleteMany(options) {
80
+ const items = this.search({ filter: options?.filter });
81
+ this.data = this.data.filter(x => !items.includes(x));
82
+ return items.length;
83
+ }
84
+ static filterProperties(obj, pick, omit, include) {
85
+ if (!obj)
86
+ return;
87
+ return obj;
88
+ }
89
+ static prepare(query) {
90
+ switch (query.queryType) {
91
+ case 'create': {
92
+ const options = lodash_1.default.omitBy({
93
+ pick: query.pick?.length ? query.pick : undefined,
94
+ omit: query.omit?.length ? query.omit : undefined,
95
+ include: query.include?.length ? query.include : undefined,
96
+ }, lodash_1.default.isNil);
97
+ const { data } = query;
98
+ return {
99
+ method: query.queryType,
100
+ values: data,
101
+ options,
102
+ args: [data, options]
103
+ };
104
+ }
105
+ case 'get': {
106
+ const options = lodash_1.default.omitBy({
107
+ pick: query.pick?.length ? query.pick : undefined,
108
+ omit: query.omit?.length ? query.omit : undefined,
109
+ include: query.include?.length ? query.include : undefined,
110
+ }, lodash_1.default.isNil);
111
+ const keyValue = query.keyValue;
112
+ return {
113
+ method: query.queryType,
114
+ keyValue,
115
+ options,
116
+ args: [keyValue, options]
117
+ };
118
+ }
119
+ case 'search': {
120
+ const options = lodash_1.default.omitBy({
121
+ pick: query.pick?.length ? query.pick : undefined,
122
+ omit: query.omit?.length ? query.omit : undefined,
123
+ include: query.include?.length ? query.include : undefined,
124
+ sort: query.sort?.length ? query.sort : undefined,
125
+ limit: query.limit,
126
+ offset: query.skip,
127
+ distinct: query.distinct,
128
+ total: query.count,
129
+ filter: JsonDataService.convertFilter(query.filter)
130
+ }, lodash_1.default.isNil);
131
+ return {
132
+ method: query.queryType,
133
+ options,
134
+ args: [options]
135
+ };
136
+ }
137
+ case 'update': {
138
+ const options = lodash_1.default.omitBy({
139
+ pick: query.pick?.length ? query.pick : undefined,
140
+ omit: query.omit?.length ? query.omit : undefined,
141
+ include: query.include?.length ? query.include : undefined,
142
+ }, lodash_1.default.isNil);
143
+ const { data } = query;
144
+ const keyValue = query.keyValue;
145
+ return {
146
+ method: query.queryType,
147
+ keyValue: query.keyValue,
148
+ values: data,
149
+ options,
150
+ args: [keyValue, data, options]
151
+ };
152
+ }
153
+ case 'updateMany': {
154
+ const options = lodash_1.default.omitBy({
155
+ filter: JsonDataService.convertFilter(query.filter)
156
+ }, lodash_1.default.isNil);
157
+ const { data } = query;
158
+ return {
159
+ method: query.queryType,
160
+ options,
161
+ args: [data, options]
162
+ };
163
+ }
164
+ case 'delete': {
165
+ const options = {};
166
+ const keyValue = query.keyValue;
167
+ return {
168
+ method: query.queryType,
169
+ keyValue,
170
+ options,
171
+ args: [keyValue, options]
172
+ };
173
+ }
174
+ case 'deleteMany': {
175
+ const options = lodash_1.default.omitBy({
176
+ filter: JsonDataService.convertFilter(query.filter)
177
+ }, lodash_1.default.isNil);
178
+ return {
179
+ method: query.queryType,
180
+ options,
181
+ args: [options]
182
+ };
183
+ }
184
+ default:
185
+ throw new Error(`Unimplemented query type "${query.queryType}"`);
186
+ }
187
+ }
188
+ static convertFilter(str) {
189
+ const ast = typeof str === 'string'
190
+ ? (0, url_1.$parse)(str)
191
+ : str;
192
+ if (!ast || !(ast instanceof url_1.Expression))
193
+ return ast;
194
+ if (ast instanceof url_1.ComparisonExpression) {
195
+ const left = JsonDataService.convertFilter(ast.left);
196
+ const right = JsonDataService.convertFilter(ast.right);
197
+ switch (ast.op) {
198
+ case '=':
199
+ return { $eq: { [left]: right } };
200
+ case '!=':
201
+ return { $ne: { [left]: right } };
202
+ case '>':
203
+ return { $gt: { [left]: right } };
204
+ case '>=':
205
+ return { $gte: { [left]: right } };
206
+ case '<':
207
+ return { $lt: { [left]: right } };
208
+ case '<=':
209
+ return { $lte: { [left]: right } };
210
+ case 'in':
211
+ return { $in: { [left]: right } };
212
+ case '!in':
213
+ return { $nin: { [left]: right } };
214
+ default:
215
+ throw new Error(`ComparisonExpression operator (${ast.op}) not implemented yet`);
216
+ }
217
+ }
218
+ if (ast instanceof url_1.QualifiedIdentifier) {
219
+ return ast.value;
220
+ }
221
+ if (ast instanceof url_1.NumberLiteral ||
222
+ ast instanceof url_1.StringLiteral ||
223
+ ast instanceof url_1.BooleanLiteral ||
224
+ ast instanceof url_1.NullLiteral ||
225
+ ast instanceof url_1.DateLiteral ||
226
+ ast instanceof url_1.TimeLiteral) {
227
+ return ast.value;
228
+ }
229
+ if (ast instanceof url_1.ArrayExpression) {
230
+ return ast.items.map(JsonDataService.convertFilter);
231
+ }
232
+ if (ast instanceof url_1.LogicalExpression) {
233
+ if (ast.op === 'or')
234
+ return { $or: ast.items.map(JsonDataService.convertFilter) };
235
+ return { $and: ast.items.map(JsonDataService.convertFilter) };
236
+ }
237
+ if (ast instanceof url_1.ArrayExpression) {
238
+ return ast.items.map(JsonDataService.convertFilter);
239
+ }
240
+ if (ast instanceof url_1.ParenthesesExpression) {
241
+ return JsonDataService.convertFilter(ast.expression);
242
+ }
243
+ throw new Error(`${ast.type} is not implemented yet`);
13
244
  }
14
245
  }
15
246
  exports.JsonDataService = JsonDataService;
@@ -8,8 +8,8 @@ const get_caller_file_util_js_1 = require("./get-caller-file.util.js");
8
8
  async function createI18n(options) {
9
9
  const opts = {
10
10
  ...options,
11
- resourceDirs: undefined
12
11
  };
12
+ delete opts.resourceDirs;
13
13
  const instance = i18n_1.I18n.createInstance(opts);
14
14
  await instance.init();
15
15
  await instance.loadResourceDir(path_1.default.resolve((0, get_caller_file_util_js_1.getCallerFile)(), '../../../i18n'));
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getCallerFile = void 0;
4
+ const PATH_PATTERN = /^(?:file:\/\/)?(.+)$/;
4
5
  function getCallerFile(position = 1) {
5
6
  if (position >= Error.stackTraceLimit) {
6
7
  throw new TypeError('getCallerFile(position) requires position be less then Error.stackTraceLimit but position was: `' +
@@ -13,7 +14,11 @@ function getCallerFile(position = 1) {
13
14
  if (stack !== null && typeof stack === 'object') {
14
15
  // stack[0] holds this file
15
16
  // stack[1] holds where this function was called
16
- return stack[position] ? stack[position].getFileName() : undefined;
17
+ const s = stack[position] ?
18
+ stack[position].getFileName() : undefined;
19
+ const m = s ? PATH_PATTERN.exec(s) : undefined;
20
+ return m ? m[1] : '';
17
21
  }
22
+ return '';
18
23
  }
19
24
  exports.getCallerFile = getCallerFile;
@@ -1,2 +1,3 @@
1
1
  export declare const OpraVersion = "1.0";
2
2
  export declare const RESOURCE_METADATA = "opra:resource.metadata";
3
+ export declare const RESOLVER_METADATA = "opra:resolver.metadata";
package/esm/constants.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export const OpraVersion = '1.0';
2
2
  export const RESOURCE_METADATA = 'opra:resource.metadata';
3
+ export const RESOLVER_METADATA = 'opra:resolver.metadata';
@@ -1,5 +1,5 @@
1
1
  import "reflect-metadata";
2
- import { EntityResourceMetadata } from '../interfaces/opra-schema.metadata.js';
2
+ import { EntityResourceMetadata } from '../interfaces/metadata/opra-schema.metadata.js';
3
3
  import { TypeThunkAsync } from '../types.js';
4
4
  export declare type EntityResourceOptions = Pick<EntityResourceMetadata, 'name' | 'description'> & {};
5
5
  export declare function ApiEntityResource(entityFunc: TypeThunkAsync, options?: EntityResourceOptions): (target: Function) => void;
@@ -0,0 +1,2 @@
1
+ import { ApiResolverMetadata } from '../interfaces/metadata/api-resolver.metadata.js';
2
+ export declare function ApiResolver(options?: ApiResolverMetadata): PropertyDecorator;
@@ -0,0 +1,9 @@
1
+ import { RESOLVER_METADATA } from '../constants.js';
2
+ export function ApiResolver(options) {
3
+ return (target, propertyKey) => {
4
+ const metadata = {
5
+ ...options
6
+ };
7
+ Reflect.defineMetadata(RESOLVER_METADATA, metadata, target, propertyKey);
8
+ };
9
+ }
@@ -0,0 +1,10 @@
1
+ import { ApiException, ErrorResponse } from '../api-exception.js';
2
+ /**
3
+ * 406 Not Acceptable
4
+ * This response is sent when the web server, after performing server-driven content negotiation,
5
+ * doesn't find any content that conforms to the criteria given by the user agent.
6
+ */
7
+ export declare class NotAcceptableError extends ApiException {
8
+ constructor(response?: string | ErrorResponse | Error, cause?: Error);
9
+ protected _initResponse(response: Partial<ErrorResponse>): void;
10
+ }
@@ -0,0 +1,22 @@
1
+ import { translate } from '@opra/i18n';
2
+ import { HttpStatus } from '../../enums/index.js';
3
+ import { ApiException } from '../api-exception.js';
4
+ /**
5
+ * 406 Not Acceptable
6
+ * This response is sent when the web server, after performing server-driven content negotiation,
7
+ * doesn't find any content that conforms to the criteria given by the user agent.
8
+ */
9
+ export class NotAcceptableError extends ApiException {
10
+ constructor(response, cause) {
11
+ super(response, cause);
12
+ this.status = HttpStatus.NOT_ACCEPTABLE;
13
+ }
14
+ _initResponse(response) {
15
+ super._initResponse({
16
+ message: translate('error:NOT_ACCEPTABLE', 'Not Acceptable'),
17
+ severity: 'error',
18
+ code: 'NOT_ACCEPTABLE',
19
+ ...response
20
+ });
21
+ }
22
+ }
@@ -4,7 +4,9 @@ export * from './http-errors/failed-dependency.error.js';
4
4
  export * from './http-errors/forbidden.error.js';
5
5
  export * from './http-errors/internal-server.error.js';
6
6
  export * from './http-errors/method-not-allowed.error.js';
7
+ export * from './http-errors/not-acceptable.error.js';
7
8
  export * from './http-errors/not-found.error.js';
8
9
  export * from './http-errors/unauthorized.error.js';
9
10
  export * from './http-errors/unprocessable-entity.error.js';
10
11
  export * from './resource-errors/resource-conflict.error.js';
12
+ export * from './resource-errors/resource-not-found.error.js';
@@ -4,7 +4,9 @@ export * from './http-errors/failed-dependency.error.js';
4
4
  export * from './http-errors/forbidden.error.js';
5
5
  export * from './http-errors/internal-server.error.js';
6
6
  export * from './http-errors/method-not-allowed.error.js';
7
+ export * from './http-errors/not-acceptable.error.js';
7
8
  export * from './http-errors/not-found.error.js';
8
9
  export * from './http-errors/unauthorized.error.js';
9
10
  export * from './http-errors/unprocessable-entity.error.js';
10
11
  export * from './resource-errors/resource-conflict.error.js';
12
+ export * from './resource-errors/resource-not-found.error.js';
@@ -0,0 +1,4 @@
1
+ import { ApiException } from '../api-exception.js';
2
+ export declare class ResourceNotFoundError extends ApiException {
3
+ constructor(resource: string, keyValue: any, cause?: Error);
4
+ }
@@ -0,0 +1,15 @@
1
+ import { translate } from '@opra/i18n';
2
+ import { HttpStatus } from '../../enums/index.js';
3
+ import { ApiException } from '../api-exception.js';
4
+ export class ResourceNotFoundError extends ApiException {
5
+ constructor(resource, keyValue, cause) {
6
+ super({
7
+ message: translate(`error:RESOURCE_NOT_FOUND`, { resource: resource + '@' + keyValue }),
8
+ severity: 'error',
9
+ code: 'RESOURCE_NOT_FOUND',
10
+ subject: resource,
11
+ key: keyValue
12
+ }, cause);
13
+ this.status = HttpStatus.NOT_FOUND;
14
+ }
15
+ }
@@ -8,11 +8,6 @@ export declare namespace OpraAdapter {
8
8
  executionContext: IExecutionContext;
9
9
  isBatch: boolean;
10
10
  }) => object | Promise<object>;
11
- type RequestFinishEvent = (args: {
12
- executionContext: IExecutionContext;
13
- userContext?: any;
14
- failed: boolean;
15
- }) => Promise<void>;
16
11
  interface Options {
17
12
  i18n?: I18n | I18nOptions | (() => Promise<I18n>);
18
13
  userContext?: UserContextResolver;
@@ -57,5 +52,6 @@ export declare abstract class OpraAdapter<TExecutionContext extends IExecutionCo
57
52
  protected abstract sendError(executionContext: TExecutionContext, error: ApiException): Promise<void>;
58
53
  protected abstract isBatch(executionContext: TExecutionContext): boolean;
59
54
  protected handler(executionContext: TExecutionContext): Promise<void>;
55
+ protected _metadataExecute(ctx: QueryContext): Promise<void>;
60
56
  protected static initI18n(options?: OpraAdapter.Options): Promise<I18n>;
61
57
  }
@@ -1,6 +1,6 @@
1
1
  import { AsyncEventEmitter } from 'strict-typed-events';
2
2
  import { I18n } from '@opra/i18n';
3
- import { FailedDependencyError } from '../../exception/index.js';
3
+ import { BadRequestError, FailedDependencyError } from '../../exception/index.js';
4
4
  import { wrapError } from '../../exception/wrap-error.js';
5
5
  import { createI18n } from '../../utils/create-i18n.js';
6
6
  export class OpraAdapter {
@@ -38,8 +38,12 @@ export class OpraAdapter {
38
38
  continue;
39
39
  }
40
40
  try {
41
- const resource = context.query.resource;
42
41
  const promise = (async () => {
42
+ if (context.query.queryType === 'metadata') {
43
+ await this._metadataExecute(context);
44
+ return;
45
+ }
46
+ const resource = context.query.resource;
43
47
  await resource.prepare(context);
44
48
  if (this.userContextResolver && !userContext)
45
49
  userContext = this.userContextResolver({
@@ -88,6 +92,32 @@ export class OpraAdapter {
88
92
  }
89
93
  }
90
94
  }
95
+ async _metadataExecute(ctx) {
96
+ const query = ctx.query;
97
+ let out;
98
+ if (query.resourcePath.length > 2)
99
+ throw new BadRequestError();
100
+ if (query.resourcePath?.length) {
101
+ if (query.resourcePath[0] === 'resources') {
102
+ const resource = this.service.getResource(query.resourcePath[1]);
103
+ out = resource.getMetadata();
104
+ query.resourcePath[1] = resource.name;
105
+ }
106
+ else if (query.resourcePath[0] === 'types') {
107
+ const dataType = this.service.getDataType(query.resourcePath[1]);
108
+ out = dataType.getMetadata();
109
+ query.resourcePath[1] = dataType.name;
110
+ }
111
+ else
112
+ throw new BadRequestError();
113
+ ctx.response.value = {
114
+ '@opra:metadata': '/$metadata/' + query.resourcePath.join('/'),
115
+ ...out
116
+ };
117
+ return;
118
+ }
119
+ ctx.response.value = this.service.getMetadata();
120
+ }
91
121
  static async initI18n(options) {
92
122
  if (options?.i18n instanceof I18n)
93
123
  return options.i18n;
@@ -1,7 +1,7 @@
1
1
  import { OpraURL } from '@opra/url';
2
2
  import { ApiException } from '../../exception/index.js';
3
3
  import { IHttpExecutionContext } from '../../interfaces/execution-context.interface.js';
4
- import { OpraQuery } from '../../interfaces/query.interface.js';
4
+ import { OpraMetadataQuery, OpraQuery } from '../../interfaces/query.interface.js';
5
5
  import { HeadersObject } from '../../utils/headers.js';
6
6
  import { QueryContext } from '../query-context.js';
7
7
  import { OpraAdapter } from './adapter.js';
@@ -18,6 +18,7 @@ interface PreparedOutput {
18
18
  export declare class OpraHttpAdapter<TExecutionContext extends IHttpExecutionContext> extends OpraAdapter<IHttpExecutionContext> {
19
19
  protected prepareRequests(executionContext: TExecutionContext): QueryContext[];
20
20
  prepareRequest(executionContext: IHttpExecutionContext, url: OpraURL, method: string, headers: HeadersObject, body?: any): QueryContext;
21
+ buildMetadataQuery(url: OpraURL): OpraMetadataQuery;
21
22
  buildQuery(url: OpraURL, method: string, body?: any): OpraQuery | undefined;
22
23
  protected sendResponse(executionContext: TExecutionContext, queryContexts: QueryContext[]): Promise<void>;
23
24
  protected isBatch(executionContext: TExecutionContext): boolean;