@opra/core 0.13.0 → 0.15.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 (98) hide show
  1. package/cjs/adapter/adapter.js +88 -294
  2. package/cjs/adapter/http/express-adapter.js +27 -0
  3. package/cjs/adapter/http/http-adapter.js +466 -0
  4. package/cjs/adapter/http/http-request-context.host.js +28 -0
  5. package/cjs/adapter/http/http-request.host.js +14 -0
  6. package/cjs/adapter/http/http-response.host.js +14 -0
  7. package/cjs/adapter/internal/metadata.resource.js +27 -0
  8. package/cjs/adapter/request-context.host.js +30 -0
  9. package/cjs/adapter/request.host.js +19 -0
  10. package/cjs/adapter/response.host.js +20 -0
  11. package/cjs/augmentation/resource.augmentation.js +26 -0
  12. package/cjs/index.js +7 -8
  13. package/esm/adapter/adapter.d.ts +52 -28
  14. package/esm/adapter/adapter.js +88 -295
  15. package/esm/adapter/http/express-adapter.d.ts +11 -0
  16. package/esm/adapter/http/express-adapter.js +22 -0
  17. package/esm/adapter/http/http-adapter.d.ts +37 -0
  18. package/esm/adapter/http/http-adapter.js +462 -0
  19. package/esm/adapter/http/http-request-context.host.d.ts +16 -0
  20. package/esm/adapter/http/http-request-context.host.js +24 -0
  21. package/esm/adapter/http/http-request.host.d.ts +7 -0
  22. package/esm/adapter/http/http-request.host.js +10 -0
  23. package/esm/adapter/http/http-response.host.d.ts +7 -0
  24. package/esm/adapter/http/http-response.host.js +10 -0
  25. package/esm/{interfaces → adapter/interfaces}/logger.interface.d.ts +1 -0
  26. package/esm/adapter/interfaces/request-context.interface.d.ts +25 -0
  27. package/esm/adapter/interfaces/request.interface.d.ts +13 -0
  28. package/esm/adapter/interfaces/response.interface.d.ts +22 -0
  29. package/esm/adapter/internal/metadata.resource.d.ts +7 -0
  30. package/esm/adapter/internal/metadata.resource.js +24 -0
  31. package/esm/adapter/request-context.host.d.ts +19 -0
  32. package/esm/adapter/request-context.host.js +26 -0
  33. package/esm/adapter/request.host.d.ts +26 -0
  34. package/esm/adapter/request.host.js +15 -0
  35. package/esm/adapter/response.host.d.ts +20 -0
  36. package/esm/adapter/response.host.js +16 -0
  37. package/esm/augmentation/resource.augmentation.d.ts +33 -0
  38. package/esm/augmentation/resource.augmentation.js +24 -0
  39. package/esm/index.d.ts +7 -8
  40. package/esm/index.js +7 -8
  41. package/i18n/en/error.json +1 -1
  42. package/package.json +9 -7
  43. package/cjs/adapter/classes/execution-context.host.js +0 -16
  44. package/cjs/adapter/classes/express-request-wrapper.host.js +0 -36
  45. package/cjs/adapter/classes/express-response-wrapper.host.js +0 -55
  46. package/cjs/adapter/classes/http-execution-context.host.js +0 -28
  47. package/cjs/adapter/classes/metadata.resource.js +0 -22
  48. package/cjs/adapter/express-adapter.js +0 -26
  49. package/cjs/adapter/http-adapter.js +0 -443
  50. package/cjs/adapter/request-contexts/batch-request-context.js +0 -11
  51. package/cjs/adapter/request-contexts/request-context.js +0 -25
  52. package/cjs/adapter/request-contexts/single-request-context.js +0 -14
  53. package/cjs/interfaces/resource.interface.js +0 -2
  54. package/cjs/services/data-service.js +0 -9
  55. package/cjs/services/json-singleton-service.js +0 -96
  56. package/cjs/utils/create-i18n.js +0 -21
  57. package/cjs/utils/get-caller-file.util.js +0 -24
  58. package/esm/adapter/classes/execution-context.host.d.ts +0 -10
  59. package/esm/adapter/classes/execution-context.host.js +0 -12
  60. package/esm/adapter/classes/express-request-wrapper.host.d.ts +0 -19
  61. package/esm/adapter/classes/express-request-wrapper.host.js +0 -32
  62. package/esm/adapter/classes/express-response-wrapper.host.d.ts +0 -22
  63. package/esm/adapter/classes/express-response-wrapper.host.js +0 -51
  64. package/esm/adapter/classes/http-execution-context.host.d.ts +0 -13
  65. package/esm/adapter/classes/http-execution-context.host.js +0 -24
  66. package/esm/adapter/classes/metadata.resource.d.ts +0 -8
  67. package/esm/adapter/classes/metadata.resource.js +0 -19
  68. package/esm/adapter/express-adapter.d.ts +0 -11
  69. package/esm/adapter/express-adapter.js +0 -21
  70. package/esm/adapter/http-adapter.d.ts +0 -37
  71. package/esm/adapter/http-adapter.js +0 -439
  72. package/esm/adapter/request-contexts/batch-request-context.d.ts +0 -7
  73. package/esm/adapter/request-contexts/batch-request-context.js +0 -7
  74. package/esm/adapter/request-contexts/request-context.d.ts +0 -22
  75. package/esm/adapter/request-contexts/request-context.js +0 -21
  76. package/esm/adapter/request-contexts/single-request-context.d.ts +0 -10
  77. package/esm/adapter/request-contexts/single-request-context.js +0 -10
  78. package/esm/enums/issue-severity.enum.d.ts +0 -1
  79. package/esm/interfaces/execution-context.interface.d.ts +0 -47
  80. package/esm/interfaces/i18n-options.interface.d.ts +0 -28
  81. package/esm/interfaces/resource.interface.d.ts +0 -23
  82. package/esm/interfaces/resource.interface.js +0 -1
  83. package/esm/services/data-service.d.ts +0 -2
  84. package/esm/services/data-service.js +0 -5
  85. package/esm/services/json-singleton-service.d.ts +0 -39
  86. package/esm/services/json-singleton-service.js +0 -91
  87. package/esm/utils/create-i18n.d.ts +0 -3
  88. package/esm/utils/create-i18n.js +0 -16
  89. package/esm/utils/get-caller-file.util.d.ts +0 -1
  90. package/esm/utils/get-caller-file.util.js +0 -20
  91. /package/cjs/{interfaces → adapter/interfaces}/logger.interface.js +0 -0
  92. /package/cjs/{enums/issue-severity.enum.js → adapter/interfaces/request-context.interface.js} +0 -0
  93. /package/cjs/{interfaces/execution-context.interface.js → adapter/interfaces/request.interface.js} +0 -0
  94. /package/cjs/{interfaces/i18n-options.interface.js → adapter/interfaces/response.interface.js} +0 -0
  95. /package/esm/{interfaces → adapter/interfaces}/logger.interface.js +0 -0
  96. /package/esm/{enums/issue-severity.enum.js → adapter/interfaces/request-context.interface.js} +0 -0
  97. /package/esm/{interfaces/execution-context.interface.js → adapter/interfaces/request.interface.js} +0 -0
  98. /package/esm/{interfaces/i18n-options.interface.js → adapter/interfaces/response.interface.js} +0 -0
@@ -1,322 +1,116 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OpraAdapter = void 0;
4
- const power_tasks_1 = require("power-tasks");
5
- const strict_typed_events_1 = require("strict-typed-events");
4
+ const tslib_1 = require("tslib");
5
+ const path_1 = tslib_1.__importDefault(require("path"));
6
6
  const common_1 = require("@opra/common");
7
- const create_i18n_js_1 = require("../utils/create-i18n.js");
8
- const metadata_resource_js_1 = require("./classes/metadata.resource.js");
9
- const batch_request_context_js_1 = require("./request-contexts/batch-request-context.js");
10
- const single_request_context_js_1 = require("./request-contexts/single-request-context.js");
11
- const noOp = () => void 0;
7
+ const metadata_resource_js_1 = require("./internal/metadata.resource.js");
8
+ /**
9
+ * @class OpraAdapter
10
+ */
12
11
  class OpraAdapter {
13
- constructor(document) {
14
- this.document = document;
15
- this._internalResources = new common_1.ResponsiveMap();
12
+ constructor(api) {
13
+ this.api = api;
16
14
  }
17
- async close() {
18
- const promises = [];
19
- for (const r of this.document.resources.values()) {
20
- if (r.instance) {
21
- const shutDown = r.instance.shutDown;
22
- if (shutDown)
23
- promises.push((async () => shutDown.call(r.instance))());
24
- }
25
- }
26
- await Promise.allSettled(promises);
27
- }
28
- async handler(executionContext) {
29
- let requestContext;
30
- let failed = false;
31
- try {
32
- if (this.userContextResolver)
33
- executionContext.userContext = this.userContextResolver(executionContext);
34
- requestContext = await this.parse(executionContext);
35
- const task = this._requestContextToTask(executionContext, requestContext);
36
- await task.toPromise().catch(noOp);
37
- await this.sendResponse(executionContext, requestContext);
38
- }
39
- catch (e) {
40
- failed = true;
41
- const error = (0, common_1.wrapException)(e);
42
- await this.sendError(executionContext, error);
43
- if (this.logger)
44
- try {
45
- this.logger.error(e);
46
- }
47
- catch {
48
- // noop
49
- }
50
- }
51
- finally {
52
- if (executionContext instanceof strict_typed_events_1.AsyncEventEmitter) {
53
- await executionContext
54
- .emitAsyncSerial('finish', {
55
- context: executionContext,
56
- failed
57
- }).catch();
58
- }
59
- }
60
- }
61
- _requestContextToTask(executionContext, requestContext) {
62
- if (requestContext instanceof batch_request_context_js_1.BatchRequestContext) {
63
- const children = requestContext.queries.map(q => this._requestContextToTask(executionContext, q));
64
- return new power_tasks_1.Task(children, { bail: true });
65
- }
66
- else if (requestContext instanceof single_request_context_js_1.SingleRequestContext) {
67
- const { query } = requestContext;
68
- const task = new power_tasks_1.Task(async () => {
69
- if (query.resource) {
70
- const { resource } = query;
71
- // call pre_xxx method
72
- const fn = resource.metadata['pre_' + query.method];
73
- if (fn && typeof fn === 'function') {
74
- await fn(requestContext);
75
- }
76
- await this._executeResourceQuery(this.document, resource, requestContext);
77
- // todo execute sub property queries
78
- return;
79
- }
80
- throw new common_1.InternalServerError('Not implemented yet');
81
- }, {
82
- id: requestContext.contentId,
83
- exclusive: query.operation !== 'read'
84
- });
85
- task.on('finish', () => {
86
- if (task.error) {
87
- if (task.failedDependencies)
88
- requestContext.errors.push(new common_1.FailedDependencyError());
89
- else
90
- requestContext.errors.push((0, common_1.wrapException)(task.error));
91
- if (this.logger)
92
- try {
93
- this.logger.error(task.error);
94
- }
95
- catch {
96
- // noop
97
- }
98
- return;
99
- }
100
- });
101
- return task;
102
- }
103
- /* istanbul ignore next */
104
- throw new TypeError('Invalid request context instance');
105
- }
106
- async _init(options) {
15
+ /**
16
+ * Initializes the adapter
17
+ * @param options
18
+ * @protected
19
+ */
20
+ async init(options) {
107
21
  this.logger = options?.logger;
108
22
  if (options?.i18n instanceof common_1.I18n)
109
23
  this.i18n = options.i18n;
110
24
  else if (typeof options?.i18n === 'function')
111
25
  this.i18n = await options.i18n();
112
26
  else
113
- this.i18n = await (0, create_i18n_js_1.createI18n)(options?.i18n);
27
+ this.i18n = await this._createI18n(options?.i18n);
114
28
  this.i18n = this.i18n || common_1.I18n.defaultInstance;
115
29
  if (!this.i18n.isInitialized)
116
30
  await this.i18n.init();
117
- this.userContextResolver = options?.userContext;
118
- const metadataResource = new metadata_resource_js_1.MetadataResource();
119
- const metadataResourceInfo = new common_1.SingletonResourceInfo(this.document, '$metadata', this.document.getComplexDataType('object'), {
120
- kind: 'SingletonResource',
121
- type: 'object',
122
- instance: metadataResource,
123
- get: {
124
- handler: metadataResource.get.bind(metadataResource)
125
- }
31
+ this._internalDoc = await common_1.DocumentFactory.createDocument({
32
+ version: common_1.OpraSchema.SpecVersion,
33
+ info: {
34
+ version: common_1.OpraSchema.SpecVersion,
35
+ title: 'Internal resources',
36
+ },
37
+ references: { 'api': this.api },
38
+ resources: [new metadata_resource_js_1.MetadataResource(this.api)]
126
39
  });
127
- this._internalResources.set(metadataResourceInfo.name, metadataResourceInfo);
128
- metadataResource.init(metadataResourceInfo);
129
- for (const r of this.document.resources.values()) {
130
- if (r.instance) {
131
- const init = r.instance.init;
132
- if (init)
133
- await init.call(r.instance, r);
134
- }
40
+ const promises = [];
41
+ for (const r of this.api.resources.values()) {
42
+ const onInit = r.onInit;
43
+ if (onInit)
44
+ promises.push((async () => onInit.call(r.controller, r))());
135
45
  }
46
+ await Promise.all(promises);
136
47
  }
137
- async _executeResourceQuery(document, resource, context) {
138
- if (resource instanceof common_1.CollectionResourceInfo) {
139
- const { query } = context;
140
- if (query instanceof common_1.CollectionSearchQuery) {
141
- const promises = [];
142
- let search;
143
- promises.push(this._executeCollectionResource(document, resource, context)
144
- .then(v => search = v));
145
- if (query.count && resource.metadata.count) {
146
- const ctx = {
147
- query: new common_1.CollectionCountQuery(query.resource, { filter: query.filter }),
148
- resultPath: ''
149
- };
150
- Object.setPrototypeOf(ctx, context);
151
- promises.push(this._executeCollectionResource(document, resource, ctx)
152
- .then(r => {
153
- context.responseHeaders[common_1.HttpHeaderCodes.X_Opra_Count] = r;
154
- }));
155
- }
156
- await Promise.all(promises);
157
- context.response = search;
158
- return;
159
- }
160
- context.response = await this._executeCollectionResource(document, resource, context);
161
- return;
162
- }
163
- else if (resource instanceof common_1.SingletonResourceInfo) {
164
- context.response = await this._executeSingletonResource(document, resource, context);
165
- return;
48
+ /**
49
+ * Calls shutDown hook for all resources
50
+ */
51
+ async close() {
52
+ const promises = [];
53
+ for (const r of this.api.resources.values()) {
54
+ const onShutdown = r.onShutdown;
55
+ if (onShutdown)
56
+ promises.push((async () => onShutdown.call(r.controller, r))());
166
57
  }
167
- throw new Error(`Executing "${resource.kind}" has not been implemented yet`);
58
+ await Promise.allSettled(promises);
168
59
  }
169
- async _executeCollectionResource(document, resource, context) {
170
- const method = context.query.method;
171
- const handler = resource.getHandler(method);
172
- if (!handler)
173
- throw new common_1.ForbiddenError({
174
- message: (0, common_1.translate)('RESOLVER_FORBIDDEN', { method }, `The resource endpoint does not accept '{{method}}' operations`),
175
- severity: 'error',
176
- code: 'RESOLVER_FORBIDDEN'
177
- });
178
- let result;
179
- switch (method) {
180
- case 'create': {
181
- const query = context.query;
182
- result = await handler(context, query.data, query);
183
- result = Array.isArray(result) ? result[0] : result;
184
- if (result)
185
- context.status = 201;
186
- context.responseHeaders[common_1.HttpHeaderCodes.X_Opra_DataType] = resource.dataType.name;
187
- return result;
188
- }
189
- case 'count': {
190
- const query = context.query;
191
- result = await handler(context, query);
192
- return result;
193
- }
194
- case 'get': {
195
- const query = context.query;
196
- result = await handler(context, query.keyValue, query);
197
- result = Array.isArray(result) ? result[0] : result;
198
- if (!result)
199
- throw new common_1.ResourceNotFoundError(resource.name, query.keyValue);
200
- const v = await this._pathWalkThrough(query, query.dataType, result, resource.name);
201
- if (v.value === undefined)
202
- throw new common_1.ResourceNotFoundError(v.path);
203
- if (v.dataType)
204
- context.responseHeaders[common_1.HttpHeaderCodes.X_Opra_DataType] = v.dataType.name;
205
- return v.value;
206
- }
207
- case 'search': {
208
- const query = context.query;
209
- result = await handler(context, query);
210
- const items = Array.isArray(result) ? result : (context.response ? [result] : []);
211
- context.responseHeaders[common_1.HttpHeaderCodes.X_Opra_DataType] = resource.dataType.name;
212
- return items;
213
- }
214
- case 'update': {
215
- const query = context.query;
216
- result = await handler(context, query.keyValue, query.data, query);
217
- result = Array.isArray(result) ? result[0] : result;
218
- if (!result)
219
- throw new common_1.ResourceNotFoundError(resource.name, query.keyValue);
220
- context.responseHeaders[common_1.HttpHeaderCodes.X_Opra_DataType] = resource.dataType.name;
221
- return result;
222
- }
223
- case 'delete':
224
- case 'deleteMany':
225
- case 'updateMany': {
226
- switch (method) {
227
- case 'delete': {
228
- const query = context.query;
229
- result = await handler(context, query.keyValue, query);
230
- break;
231
- }
232
- case 'deleteMany': {
233
- const query = context.query;
234
- result = await handler(context, query);
235
- break;
236
- }
237
- case 'updateMany': {
238
- const query = context.query;
239
- result = await handler(context, query.data, query);
240
- break;
241
- }
242
- }
243
- let affected;
244
- if (typeof result === 'number')
245
- affected = result;
246
- if (typeof result === 'boolean')
247
- affected = result ? 1 : 0;
248
- if (typeof result === 'object')
249
- affected = result.affectedRows || result.affected;
250
- return {
251
- operation: context.query.method,
252
- affected
253
- };
254
- }
60
+ async executeRequest(context) {
61
+ const { request, response } = context;
62
+ const { resource, operation } = request;
63
+ if (resource instanceof common_1.Collection || resource instanceof common_1.Singleton) {
64
+ const endpoint = resource.operations[operation];
65
+ if (!endpoint?.handler)
66
+ throw new common_1.ForbiddenError({
67
+ message: (0, common_1.translate)('RESOLVER_FORBIDDEN', { operation }, `The resource endpoint does not accept '{{operation}}' operations`),
68
+ severity: 'error',
69
+ code: 'RESOLVER_FORBIDDEN'
70
+ });
71
+ const value = await endpoint.handler(context);
72
+ if (value != null)
73
+ response.value = value;
74
+ await this.afterExecuteRequest(context);
255
75
  }
256
76
  }
257
- async _executeSingletonResource(document, resource, context) {
258
- const method = context.query.method;
259
- const resolverInfo = resource.metadata[method];
260
- if (!(resolverInfo && resolverInfo.handler))
261
- throw new common_1.ForbiddenError({
262
- message: (0, common_1.translate)('RESOLVER_FORBIDDEN', { method }, `The resource endpoint does not accept '{{method}}' operations`),
263
- severity: 'error',
264
- code: 'RESOLVER_FORBIDDEN'
265
- });
266
- let result = await resolverInfo.handler(context);
267
- switch (method) {
268
- case 'get': {
269
- const query = context.query;
270
- result = await resolverInfo.handler(context, query);
271
- result = Array.isArray(result) ? result[0] : result;
272
- if (!result)
273
- throw new common_1.ResourceNotFoundError(resource.name);
274
- const v = await this._pathWalkThrough(query, query.dataType, result, resource.name);
275
- if (v.value === undefined)
276
- throw new common_1.ResourceNotFoundError(v.path);
277
- if (v.dataType)
278
- context.responseHeaders[common_1.HttpHeaderCodes.X_Opra_DataType] = v.dataType.name;
279
- return v.value;
280
- }
77
+ async afterExecuteRequest(context) {
78
+ const { request, response } = context;
79
+ const { resource, crud, many } = request;
80
+ if (crud === 'delete' || (crud === 'update' && many)) {
81
+ let affected = 0;
82
+ if (typeof response.value === 'number')
83
+ affected = response.value;
84
+ if (typeof response.value === 'boolean')
85
+ affected = response.value ? 1 : 0;
86
+ if (typeof response.value === 'object')
87
+ affected = response.value.affectedRows || response.value.affected;
88
+ response.value = {
89
+ operation: request.operation,
90
+ affected
91
+ };
281
92
  }
282
- if (!result)
283
- return;
284
- result = Array.isArray(result) ? result[0] : result;
285
- let dataType = resource.dataType;
286
- if (context.resultPath) {
287
- const pathArray = context.resultPath.split('.');
288
- for (const field of pathArray) {
289
- const prop = dataType instanceof common_1.ComplexType ? dataType.fields.get(field) : undefined;
290
- dataType = prop && prop.type ? this.document.types.get(prop.type) : undefined;
291
- result = result && typeof result === 'object' && result[field];
292
- }
93
+ else if (response.value != null) {
94
+ if (!request.many)
95
+ response.value = Array.isArray(response.value) ? response.value[0] : response.value;
96
+ else
97
+ response.value = Array.isArray(response.value) ? response.value : [response.value];
293
98
  }
294
- if (method === 'create')
295
- context.status = 201;
296
- context.responseHeaders[common_1.HttpHeaderCodes.X_Opra_DataType] = resource.dataType.name;
297
- return result;
99
+ if ((request.operation === 'get' || request.operation === 'update') && response.value == null)
100
+ throw new common_1.ResourceNotFoundError(resource.name, request.args.key);
298
101
  }
299
- async _pathWalkThrough(query, dataType, value, parentPath) {
300
- const { child } = query;
301
- if (!child)
302
- return { value, dataType, path: parentPath };
303
- // Make a case in sensitive lookup
304
- const fieldNameLower = child.fieldName.toLowerCase();
305
- const path = parentPath + (parentPath ? '.' : '') + child.fieldName;
306
- for (const key of Object.keys(value)) {
307
- if (key.toLowerCase() === fieldNameLower) {
308
- let v = value[key];
309
- if (v == null)
310
- return { path };
311
- if (child.child && child.dataType instanceof common_1.ComplexType) {
312
- if (Array.isArray(v))
313
- v = v[0];
314
- return this._pathWalkThrough(child, child.dataType, v, path);
315
- }
316
- return { value: v, dataType: child.dataType, path };
317
- }
318
- }
319
- return { path };
102
+ async _createI18n(options) {
103
+ const opts = {
104
+ ...options,
105
+ };
106
+ delete opts.resourceDirs;
107
+ const instance = common_1.I18n.createInstance(opts);
108
+ await instance.init();
109
+ await instance.loadResourceDir(path_1.default.resolve((0, common_1.getStackFileName)(), '../../../i18n'));
110
+ if (options?.resourceDirs)
111
+ for (const dir of options.resourceDirs)
112
+ await instance.loadResourceDir(dir);
113
+ return instance;
320
114
  }
321
115
  }
322
116
  exports.OpraAdapter = OpraAdapter;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OpraExpressAdapter = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const body_parser_1 = tslib_1.__importDefault(require("body-parser"));
6
+ const common_1 = require("@opra/common");
7
+ const http_adapter_js_1 = require("./http-adapter.js");
8
+ const noOp = () => void 0;
9
+ class OpraExpressAdapter extends http_adapter_js_1.OpraHttpAdapter {
10
+ constructor() {
11
+ super(...arguments);
12
+ this.platform = 'express';
13
+ }
14
+ static async create(app, document, options) {
15
+ const adapter = new OpraExpressAdapter(document);
16
+ await adapter.init(options);
17
+ const prefix = '/' + (0, common_1.normalizePath)(options?.prefix, true);
18
+ app.use(prefix, body_parser_1.default.json());
19
+ app.use(prefix, (req, res, next) => {
20
+ req.end = noOp;
21
+ req.send = noOp;
22
+ adapter.handler(req, res).catch(e => next(e));
23
+ });
24
+ return adapter;
25
+ }
26
+ }
27
+ exports.OpraExpressAdapter = OpraExpressAdapter;