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