@opra/core 0.3.0 → 0.4.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 (52) hide show
  1. package/cjs/adapter/adapter.js +319 -0
  2. package/cjs/{implementation → adapter}/express-adapter.js +3 -6
  3. package/cjs/adapter/http-adapter.js +242 -0
  4. package/cjs/adapter/metadata-resource.js +23 -0
  5. package/cjs/{implementation → adapter}/query-context.js +1 -1
  6. package/cjs/enums/http-headers.enum.js +1 -1
  7. package/cjs/{implementation → helpers}/headers-map.js +2 -2
  8. package/cjs/index.js +6 -5
  9. package/cjs/interfaces/resource.interface.js +2 -0
  10. package/cjs/services/json-collection-service.js +14 -14
  11. package/cjs/services/json-singleton-service.js +97 -0
  12. package/esm/{implementation → adapter}/adapter.d.ts +17 -9
  13. package/esm/adapter/adapter.js +315 -0
  14. package/esm/{implementation → adapter}/express-adapter.d.ts +2 -2
  15. package/esm/{implementation → adapter}/express-adapter.js +3 -6
  16. package/esm/{implementation → adapter}/http-adapter.d.ts +2 -3
  17. package/esm/adapter/http-adapter.js +238 -0
  18. package/esm/adapter/metadata-resource.d.ts +8 -0
  19. package/esm/adapter/metadata-resource.js +20 -0
  20. package/esm/{implementation → adapter}/query-context.d.ts +6 -6
  21. package/esm/{implementation → adapter}/query-context.js +1 -1
  22. package/esm/enums/http-headers.enum.d.ts +1 -1
  23. package/esm/enums/http-headers.enum.js +1 -1
  24. package/esm/{implementation → helpers}/headers-map.d.ts +1 -1
  25. package/esm/{implementation → helpers}/headers-map.js +1 -1
  26. package/esm/index.d.ts +6 -5
  27. package/esm/index.js +6 -5
  28. package/esm/interfaces/resource.interface.d.ts +22 -0
  29. package/esm/interfaces/resource.interface.js +1 -0
  30. package/esm/services/json-collection-service.d.ts +11 -12
  31. package/esm/services/json-collection-service.js +15 -15
  32. package/esm/services/json-singleton-service.d.ts +39 -0
  33. package/esm/services/json-singleton-service.js +92 -0
  34. package/esm/types.d.ts +2 -8
  35. package/esm/utils/create-i18n.d.ts +1 -1
  36. package/package.json +15 -13
  37. package/cjs/implementation/adapter-utils/entity-resource-execute.util.js +0 -86
  38. package/cjs/implementation/adapter-utils/resource-execute.util.js +0 -11
  39. package/cjs/implementation/adapter-utils/resource-prepare.util.js +0 -11
  40. package/cjs/implementation/adapter.js +0 -130
  41. package/cjs/implementation/http-adapter.js +0 -253
  42. package/cjs/interfaces/entity-service.interface.js +0 -30
  43. package/esm/implementation/adapter-utils/entity-resource-execute.util.d.ts +0 -3
  44. package/esm/implementation/adapter-utils/entity-resource-execute.util.js +0 -82
  45. package/esm/implementation/adapter-utils/resource-execute.util.d.ts +0 -3
  46. package/esm/implementation/adapter-utils/resource-execute.util.js +0 -7
  47. package/esm/implementation/adapter-utils/resource-prepare.util.d.ts +0 -3
  48. package/esm/implementation/adapter-utils/resource-prepare.util.js +0 -7
  49. package/esm/implementation/adapter.js +0 -126
  50. package/esm/implementation/http-adapter.js +0 -249
  51. package/esm/interfaces/entity-service.interface.d.ts +0 -19
  52. package/esm/interfaces/entity-service.interface.js +0 -26
@@ -195,7 +195,7 @@ class JsonCollectionService {
195
195
  indexes: {}
196
196
  };
197
197
  for (const [k, f] of this.resource.dataType.fields.entries()) {
198
- const fieldType = this.resource.owner.getDataType(f.type || 'string');
198
+ const fieldType = this.resource.document.getDataType(f.type || 'string');
199
199
  const o = table.model[k + ':' + dataTypeToSQLType(fieldType, !!f.isArray)] = {};
200
200
  if (k === this.primaryKey)
201
201
  o.pk = true;
@@ -203,21 +203,21 @@ class JsonCollectionService {
203
203
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
204
204
  const indexes = table.indexes;
205
205
  // Add indexes for sort fields
206
- const searchMethod = this.resource.metadata.methods.search;
207
- if (searchMethod) {
208
- if (searchMethod.sortFields) {
209
- searchMethod.sortFields.forEach(fieldName => {
206
+ const searchResolver = this.resource.metadata.search;
207
+ if (searchResolver) {
208
+ if (searchResolver.sortFields) {
209
+ searchResolver.sortFields.forEach(fieldName => {
210
210
  const f = this.dataType.getField(fieldName);
211
- const fieldType = this.resource.owner.getDataType(f.type || 'string');
211
+ const fieldType = this.resource.document.getDataType(f.type || 'string');
212
212
  const t = dataTypeToSQLType(fieldType, !!f.isArray);
213
213
  if (indexingTypes.includes(t))
214
214
  indexes[fieldName + ':' + t] = {};
215
215
  });
216
216
  }
217
- if (searchMethod.filters) {
218
- searchMethod.filters.forEach(filter => {
217
+ if (searchResolver.filters) {
218
+ searchResolver.filters.forEach(filter => {
219
219
  const f = this.dataType.getField(filter.field);
220
- const fieldType = this.resource.owner.getDataType(f.type || 'string');
220
+ const fieldType = this.resource.document.getDataType(f.type || 'string');
221
221
  const t = dataTypeToSQLType(fieldType, !!f.isArray);
222
222
  if (indexingTypes.includes(t))
223
223
  indexes[filter.field + ':' + t] = {};
@@ -245,10 +245,10 @@ class JsonCollectionService {
245
245
  }
246
246
  }
247
247
  _prepare(query) {
248
- if (query.resource instanceof schema_1.EntityResource) {
248
+ if (query.resource instanceof schema_1.CollectionResourceInfo) {
249
249
  if (query.dataType !== this.dataType)
250
250
  throw new TypeError(`Query data type (${query.dataType.name}) ` +
251
- `differs from JsonDataService data type (${this.dataType.name})`);
251
+ `differs from JsonCollectionService data type (${this.dataType.name})`);
252
252
  }
253
253
  switch (query.method) {
254
254
  case 'count': {
@@ -276,7 +276,7 @@ class JsonCollectionService {
276
276
  };
277
277
  }
278
278
  case 'get': {
279
- if (query.kind === 'GetInstanceQuery') {
279
+ if (query.kind === 'CollectionGetQuery') {
280
280
  const options = lodash_1.default.omitBy({
281
281
  pick: query.pick,
282
282
  omit: query.omit,
@@ -290,7 +290,7 @@ class JsonCollectionService {
290
290
  args: [keyValue, options]
291
291
  };
292
292
  }
293
- if (query.kind === 'GetFieldQuery') {
293
+ if (query.kind === 'FieldGetQuery') {
294
294
  // todo
295
295
  }
296
296
  break;
@@ -369,7 +369,7 @@ class JsonCollectionService {
369
369
  }
370
370
  _convertSelect(args) {
371
371
  const result = [];
372
- const document = this.dataType.owner;
372
+ const document = this.dataType.document;
373
373
  const processDataType = (dt, path, pick, omit, include) => {
374
374
  let kl;
375
375
  for (const [k, f] of dt.fields) {
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JsonSingletonService = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const lodash_1 = tslib_1.__importDefault(require("lodash"));
6
+ const schema_1 = require("@opra/schema");
7
+ class JsonSingletonService {
8
+ dataType;
9
+ _data;
10
+ constructor(dataType, options) {
11
+ this.dataType = dataType;
12
+ this._data = options?.data;
13
+ }
14
+ async processRequest(ctx) {
15
+ const prepared = this._prepare(ctx.query);
16
+ const fn = this[prepared.method];
17
+ if (!fn)
18
+ throw new TypeError(`Unimplemented method (${prepared.method})`);
19
+ // @ts-ignore
20
+ return fn.apply(this, prepared.args);
21
+ }
22
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
23
+ get(options) {
24
+ return this._data;
25
+ }
26
+ _prepare(query) {
27
+ if (query.resource instanceof schema_1.SingletonResourceInfo) {
28
+ if (query.dataType !== this.dataType)
29
+ throw new TypeError(`Query data type (${query.dataType.name}) ` +
30
+ `differs from JsonCollectionService data type (${this.dataType.name})`);
31
+ }
32
+ switch (query.method) {
33
+ case 'create': {
34
+ const options = lodash_1.default.omitBy({
35
+ pick: query.pick,
36
+ omit: query.omit,
37
+ include: query.include
38
+ }, lodash_1.default.isNil);
39
+ const { data } = query;
40
+ return {
41
+ method: query.method,
42
+ values: data,
43
+ options,
44
+ args: [data, options]
45
+ };
46
+ }
47
+ case 'get': {
48
+ if (query.kind === 'CollectionGetQuery') {
49
+ const options = lodash_1.default.omitBy({
50
+ pick: query.pick,
51
+ omit: query.omit,
52
+ include: query.include
53
+ }, lodash_1.default.isNil);
54
+ const keyValue = query.keyValue;
55
+ return {
56
+ method: query.method,
57
+ keyValue,
58
+ options,
59
+ args: [keyValue, options]
60
+ };
61
+ }
62
+ if (query.kind === 'FieldGetQuery') {
63
+ // todo
64
+ }
65
+ break;
66
+ }
67
+ case 'update': {
68
+ const options = lodash_1.default.omitBy({
69
+ pick: query.pick,
70
+ omit: query.omit,
71
+ include: query.include
72
+ }, lodash_1.default.isNil);
73
+ const { data } = query;
74
+ const keyValue = query.keyValue;
75
+ return {
76
+ method: query.method,
77
+ keyValue: query.keyValue,
78
+ values: data,
79
+ options,
80
+ args: [keyValue, data, options]
81
+ };
82
+ }
83
+ case 'delete': {
84
+ const options = {};
85
+ const keyValue = query.keyValue;
86
+ return {
87
+ method: query.method,
88
+ keyValue,
89
+ options,
90
+ args: [keyValue, options]
91
+ };
92
+ }
93
+ }
94
+ throw new Error(`Unimplemented query type "${query.method}"`);
95
+ }
96
+ }
97
+ exports.JsonSingletonService = JsonSingletonService;
@@ -1,6 +1,7 @@
1
+ import { ResponsiveMap } from '@opra/common';
1
2
  import { OpraException } from '@opra/exception';
2
3
  import { FallbackLng, I18n, LanguageResource } from '@opra/i18n';
3
- import { OpraApi } from '@opra/schema';
4
+ import { CollectionGetQuery, CollectionResourceInfo, ComplexType, DataType, FieldGetQuery, OpraDocument, ResourceInfo, SingletonGetQuery, SingletonResourceInfo } from '@opra/schema';
4
5
  import { IExecutionContext } from '../interfaces/execution-context.interface.js';
5
6
  import { QueryContext } from './query-context.js';
6
7
  export declare namespace OpraAdapter {
@@ -41,17 +42,24 @@ export declare namespace OpraAdapter {
41
42
  }
42
43
  }
43
44
  export declare abstract class OpraAdapter<TExecutionContext extends IExecutionContext> {
44
- readonly service: OpraApi;
45
- readonly i18n: I18n;
46
- readonly userContextResolver?: OpraAdapter.UserContextResolver;
47
- constructor(service: OpraApi, options?: Omit<OpraAdapter.Options, 'i18n'> & {
48
- i18n: I18n;
49
- });
45
+ readonly document: OpraDocument;
46
+ protected i18n: I18n;
47
+ protected userContextResolver?: OpraAdapter.UserContextResolver;
48
+ protected _internalResources: ResponsiveMap<string, ResourceInfo>;
49
+ constructor(document: OpraDocument);
50
50
  protected abstract prepareRequests(executionContext: TExecutionContext): QueryContext[];
51
51
  protected abstract sendResponse(executionContext: TExecutionContext, queryContexts: QueryContext[]): Promise<void>;
52
52
  protected abstract sendError(executionContext: TExecutionContext, error: OpraException): Promise<void>;
53
53
  protected abstract isBatch(executionContext: TExecutionContext): boolean;
54
54
  protected handler(executionContext: TExecutionContext): Promise<void>;
55
- protected _getSchemaExecute(ctx: QueryContext): Promise<void>;
56
- protected static initI18n(options?: OpraAdapter.Options): Promise<I18n>;
55
+ protected _resourcePrepare(resource: ResourceInfo, context: QueryContext): Promise<void>;
56
+ protected _resourceExecute(document: OpraDocument, resource: ResourceInfo, context: QueryContext): Promise<void>;
57
+ protected _init(options?: OpraAdapter.Options): Promise<void>;
58
+ protected _collectionResourceExecute(document: OpraDocument, resource: CollectionResourceInfo, context: QueryContext): Promise<any>;
59
+ protected _singletonResourceExecute(document: OpraDocument, resource: SingletonResourceInfo, context: QueryContext): Promise<any>;
60
+ protected _pathWalkThrough(query: CollectionGetQuery | SingletonGetQuery | FieldGetQuery, dataType: ComplexType, value: any, parentPath: string): Promise<{
61
+ value?: any;
62
+ dataType?: DataType;
63
+ path: string;
64
+ }>;
57
65
  }
@@ -0,0 +1,315 @@
1
+ import { AsyncEventEmitter } from 'strict-typed-events';
2
+ import { ResponsiveMap } from '@opra/common';
3
+ import { FailedDependencyError, ForbiddenError, ResourceNotFoundError, wrapException } from '@opra/exception';
4
+ import { I18n, translate } from '@opra/i18n';
5
+ import { CollectionCountQuery, CollectionResourceInfo, ComplexType, SingletonResourceInfo } from '@opra/schema';
6
+ import { HttpHeaders } from '../enums/index.js';
7
+ import { createI18n } from '../utils/create-i18n.js';
8
+ import { MetadataResource } from './metadata-resource.js';
9
+ export class OpraAdapter {
10
+ document;
11
+ i18n;
12
+ userContextResolver;
13
+ // protected _metadataResource: SingletonResourceInfo;
14
+ _internalResources = new ResponsiveMap();
15
+ constructor(document) {
16
+ this.document = document;
17
+ }
18
+ async handler(executionContext) {
19
+ let queryContexts;
20
+ let userContext;
21
+ let failed = false;
22
+ try {
23
+ queryContexts = this.prepareRequests(executionContext);
24
+ let stop = false;
25
+ // Read requests can be executed simultaneously, write request should be executed one by one
26
+ let promises;
27
+ let exclusive = false;
28
+ for (const context of queryContexts) {
29
+ exclusive = exclusive || context.query.operation !== 'read';
30
+ // Wait previous read requests before executing update request
31
+ if (exclusive && promises) {
32
+ await Promise.allSettled(promises);
33
+ promises = undefined;
34
+ }
35
+ // If previous request in bucket had an error and executed an update
36
+ // we do not execute next requests
37
+ if (stop) {
38
+ context.errors.push(new FailedDependencyError());
39
+ continue;
40
+ }
41
+ try {
42
+ const promise = (async () => {
43
+ // if (context.query.method === 'metadata') {
44
+ // await this._getSchemaExecute(context); //todo
45
+ // return;
46
+ // }
47
+ const resource = context.query.resource;
48
+ await this._resourcePrepare(resource, context);
49
+ if (this.userContextResolver && !userContext)
50
+ userContext = this.userContextResolver({
51
+ executionContext,
52
+ isBatch: this.isBatch(executionContext)
53
+ });
54
+ context.userContext = userContext;
55
+ await this._resourceExecute(this.document, resource, context);
56
+ })().catch(e => {
57
+ context.errors.push(e);
58
+ });
59
+ if (exclusive)
60
+ await promise;
61
+ else {
62
+ promises = promises || [];
63
+ promises.push(promise);
64
+ }
65
+ // todo execute sub property queries
66
+ }
67
+ catch (e) {
68
+ context.errors.unshift(e);
69
+ }
70
+ if (context.errors.length) {
71
+ // noinspection SuspiciousTypeOfGuard
72
+ context.errors = context.errors.map(e => wrapException(e));
73
+ if (exclusive)
74
+ stop = stop || !!context.errors.find(e => !(e.issue.severity === 'warning' || e.issue.severity === 'info'));
75
+ }
76
+ }
77
+ if (promises)
78
+ await Promise.allSettled(promises);
79
+ await this.sendResponse(executionContext, queryContexts);
80
+ }
81
+ catch (e) {
82
+ failed = true;
83
+ const error = wrapException(e);
84
+ await this.sendError(executionContext, error);
85
+ }
86
+ finally {
87
+ if (executionContext instanceof AsyncEventEmitter) {
88
+ await executionContext
89
+ .emitAsyncSerial('finish', {
90
+ userContext,
91
+ failed
92
+ }).catch();
93
+ }
94
+ }
95
+ }
96
+ async _resourcePrepare(resource, context) {
97
+ const { query } = context;
98
+ const fn = resource.metadata['pre_' + query.method];
99
+ if (fn && typeof fn === 'function') {
100
+ await fn(context);
101
+ }
102
+ }
103
+ async _resourceExecute(document, resource, context) {
104
+ if (resource instanceof CollectionResourceInfo) {
105
+ const { query } = context;
106
+ if (query.kind === 'SearchCollectionQuery') {
107
+ const promises = [];
108
+ let search;
109
+ promises.push(this._collectionResourceExecute(document, resource, context)
110
+ .then(v => search = v));
111
+ if (query.count && resource.metadata.count) {
112
+ const ctx = {
113
+ query: new CollectionCountQuery(query.resource, { filter: query.filter }),
114
+ resultPath: ''
115
+ };
116
+ Object.setPrototypeOf(ctx, context);
117
+ promises.push(this._collectionResourceExecute(document, resource, ctx));
118
+ }
119
+ await Promise.all(promises);
120
+ context.response = search;
121
+ return;
122
+ }
123
+ context.response = await this._collectionResourceExecute(document, resource, context);
124
+ return;
125
+ }
126
+ else if (resource instanceof SingletonResourceInfo) {
127
+ context.response = await this._singletonResourceExecute(document, resource, context);
128
+ return;
129
+ }
130
+ throw new Error(`Executing "${resource.kind}" has not been implemented yet`);
131
+ }
132
+ async _init(options) {
133
+ if (options?.i18n instanceof I18n)
134
+ this.i18n = options.i18n;
135
+ else if (typeof options?.i18n === 'function')
136
+ this.i18n = await options.i18n();
137
+ else
138
+ this.i18n = await createI18n(options?.i18n);
139
+ this.i18n = this.i18n || I18n.defaultInstance;
140
+ if (!this.i18n.isInitialized)
141
+ await this.i18n.init();
142
+ this.userContextResolver = options?.userContext;
143
+ const metadataResource = new MetadataResource();
144
+ const metadataResourceInfo = new SingletonResourceInfo(this.document, '$metadata', this.document.getComplexDataType('object'), {
145
+ kind: 'SingletonResource',
146
+ type: 'object',
147
+ instance: metadataResource,
148
+ get: {
149
+ handler: metadataResource.get.bind(metadataResource)
150
+ }
151
+ });
152
+ this._internalResources.set(metadataResourceInfo.name, metadataResourceInfo);
153
+ metadataResource.init(metadataResourceInfo);
154
+ for (const r of this.document.resources.values()) {
155
+ if (r.instance) {
156
+ const init = r.instance.init;
157
+ if (init)
158
+ await init.call(r.instance, r);
159
+ }
160
+ }
161
+ }
162
+ async _collectionResourceExecute(document, resource, context) {
163
+ const method = context.query.method;
164
+ const resolverInfo = resource.metadata[method];
165
+ if (!(resolverInfo && resolverInfo.handler))
166
+ throw new ForbiddenError({
167
+ message: translate('RESOLVER_FORBIDDEN', { method }, `The resource endpoint does not accept '{{method}}' operations`),
168
+ severity: 'error',
169
+ code: 'RESOLVER_FORBIDDEN'
170
+ });
171
+ let result;
172
+ switch (method) {
173
+ case 'create': {
174
+ const query = context.query;
175
+ result = await resolverInfo.handler(context, query.data, query);
176
+ result = Array.isArray(result) ? result[0] : result;
177
+ if (result)
178
+ context.status = 201;
179
+ context.responseHeaders.set(HttpHeaders.X_Opra_DataType, resource.dataType.name);
180
+ return result;
181
+ }
182
+ case 'count': {
183
+ const query = context.query;
184
+ result = await resolverInfo.handler(context, query);
185
+ context.responseHeaders.set(HttpHeaders.X_Opra_Count, result);
186
+ return result;
187
+ }
188
+ case 'get': {
189
+ const query = context.query;
190
+ result = await resolverInfo.handler(context, query.keyValue, query);
191
+ result = Array.isArray(result) ? result[0] : result;
192
+ if (!result)
193
+ throw new ResourceNotFoundError(resource.name, query.keyValue);
194
+ const v = await this._pathWalkThrough(query, query.dataType, result, resource.name);
195
+ if (v.value === undefined)
196
+ throw new ResourceNotFoundError(v.path);
197
+ if (v.dataType)
198
+ context.responseHeaders.set(HttpHeaders.X_Opra_DataType, v.dataType.name);
199
+ return v.value;
200
+ }
201
+ case 'search': {
202
+ const query = context.query;
203
+ result = await resolverInfo.handler(context, query);
204
+ const items = Array.isArray(result) ? result : (context.response ? [result] : []);
205
+ context.responseHeaders.set(HttpHeaders.X_Opra_DataType, resource.dataType.name);
206
+ return items;
207
+ }
208
+ case 'update': {
209
+ const query = context.query;
210
+ result = await resolverInfo.handler(context, query.keyValue, query.data, query);
211
+ result = Array.isArray(result) ? result[0] : result;
212
+ if (!result)
213
+ throw new ResourceNotFoundError(resource.name, query.keyValue);
214
+ context.responseHeaders.set(HttpHeaders.X_Opra_DataType, resource.dataType.name);
215
+ return result;
216
+ }
217
+ case 'delete':
218
+ case 'deleteMany':
219
+ case 'updateMany': {
220
+ switch (method) {
221
+ case 'delete': {
222
+ const query = context.query;
223
+ result = await resolverInfo.handler(context, query.keyValue, query);
224
+ break;
225
+ }
226
+ case 'deleteMany': {
227
+ const query = context.query;
228
+ result = await resolverInfo.handler(context, query);
229
+ break;
230
+ }
231
+ case 'updateMany': {
232
+ const query = context.query;
233
+ result = await resolverInfo.handler(context, query.data, query);
234
+ break;
235
+ }
236
+ }
237
+ let affected;
238
+ if (typeof result === 'number')
239
+ affected = result;
240
+ if (typeof result === 'boolean')
241
+ affected = result ? 1 : 0;
242
+ if (typeof result === 'object')
243
+ affected = result.affectedRows || result.affected;
244
+ return {
245
+ operation: context.query.method,
246
+ affected
247
+ };
248
+ }
249
+ }
250
+ }
251
+ async _singletonResourceExecute(document, resource, context) {
252
+ const method = context.query.method;
253
+ const resolverInfo = resource.metadata[method];
254
+ if (!(resolverInfo && resolverInfo.handler))
255
+ throw new ForbiddenError({
256
+ message: translate('RESOLVER_FORBIDDEN', { method }, `The resource endpoint does not accept '{{method}}' operations`),
257
+ severity: 'error',
258
+ code: 'RESOLVER_FORBIDDEN'
259
+ });
260
+ let result = await resolverInfo.handler(context);
261
+ switch (method) {
262
+ case 'get': {
263
+ const query = context.query;
264
+ result = await resolverInfo.handler(context, query);
265
+ result = Array.isArray(result) ? result[0] : result;
266
+ if (!result)
267
+ throw new ResourceNotFoundError(resource.name);
268
+ const v = await this._pathWalkThrough(query, query.dataType, result, resource.name);
269
+ if (v.value === undefined)
270
+ throw new ResourceNotFoundError(v.path);
271
+ if (v.dataType)
272
+ context.responseHeaders.set(HttpHeaders.X_Opra_DataType, v.dataType.name);
273
+ return v.value;
274
+ }
275
+ }
276
+ if (!result)
277
+ return;
278
+ result = Array.isArray(result) ? result[0] : result;
279
+ let dataType = resource.dataType;
280
+ if (context.resultPath) {
281
+ const pathArray = context.resultPath.split('.');
282
+ for (const field of pathArray) {
283
+ const prop = dataType instanceof ComplexType ? dataType.fields.get(field) : undefined;
284
+ dataType = prop && prop.type ? this.document.types.get(prop.type) : undefined;
285
+ result = result && typeof result === 'object' && result[field];
286
+ }
287
+ }
288
+ if (method === 'create')
289
+ context.status = 201;
290
+ context.responseHeaders.set(HttpHeaders.X_Opra_DataType, resource.dataType.name);
291
+ return result;
292
+ }
293
+ async _pathWalkThrough(query, dataType, value, parentPath) {
294
+ const { child } = query;
295
+ if (!child)
296
+ return { value, dataType, path: parentPath };
297
+ // Make a case in sensitive lookup
298
+ const fieldNameLower = child.fieldName.toLowerCase();
299
+ const path = parentPath + (parentPath ? '.' : '') + child.fieldName;
300
+ for (const key of Object.keys(value)) {
301
+ if (key.toLowerCase() === fieldNameLower) {
302
+ let v = value[key];
303
+ if (v == null)
304
+ return { path };
305
+ if (child.child && child.dataType instanceof ComplexType) {
306
+ if (Array.isArray(v))
307
+ v = v[0];
308
+ return this._pathWalkThrough(child, child.dataType, v, path);
309
+ }
310
+ return { value: v, dataType: child.dataType, path };
311
+ }
312
+ }
313
+ return { path };
314
+ }
315
+ }
@@ -1,5 +1,5 @@
1
1
  import type { Application } from 'express';
2
- import { OpraApi } from '@opra/schema';
2
+ import { OpraDocument } from '@opra/schema';
3
3
  import type { IHttpExecutionContext } from '../interfaces/execution-context.interface';
4
4
  import { OpraHttpAdapter } from './http-adapter.js';
5
5
  export declare namespace OpraExpressAdapter {
@@ -7,5 +7,5 @@ export declare namespace OpraExpressAdapter {
7
7
  }
8
8
  }
9
9
  export declare class OpraExpressAdapter extends OpraHttpAdapter<IHttpExecutionContext> {
10
- static init(app: Application, service: OpraApi, options?: OpraExpressAdapter.Options): Promise<OpraExpressAdapter>;
10
+ static init(app: Application, document: OpraDocument, options?: OpraExpressAdapter.Options): Promise<OpraExpressAdapter>;
11
11
  }
@@ -3,12 +3,9 @@ import { AsyncEventEmitter } from 'strict-typed-events';
3
3
  import { normalizePath } from '@opra/url';
4
4
  import { OpraHttpAdapter } from './http-adapter.js';
5
5
  export class OpraExpressAdapter extends OpraHttpAdapter {
6
- static async init(app, service, options) {
7
- const i18n = await this.initI18n(options);
8
- const adapter = new OpraExpressAdapter(service, {
9
- ...options,
10
- i18n
11
- });
6
+ static async init(app, document, options) {
7
+ const adapter = new OpraExpressAdapter(document);
8
+ await adapter._init(options);
12
9
  const prefix = '/' + normalizePath(options?.prefix, true);
13
10
  app.use(prefix, bodyParser.json());
14
11
  app.use(prefix, (request, response, next) => {
@@ -1,5 +1,5 @@
1
1
  import { OpraException } from '@opra/exception';
2
- import { OpraAnyQuery, OpraGetMetadataQuery } from '@opra/schema';
2
+ import { OpraQuery } from '@opra/schema';
3
3
  import { OpraURL } from '@opra/url';
4
4
  import { IHttpExecutionContext } from '../interfaces/execution-context.interface.js';
5
5
  import { OpraAdapter } from './adapter.js';
@@ -17,8 +17,7 @@ interface PreparedOutput {
17
17
  export declare class OpraHttpAdapter<TExecutionContext extends IHttpExecutionContext> extends OpraAdapter<IHttpExecutionContext> {
18
18
  protected prepareRequests(executionContext: TExecutionContext): QueryContext[];
19
19
  prepareRequest(executionContext: IHttpExecutionContext, url: OpraURL, method: string, headers: Map<string, string>, body?: any): QueryContext;
20
- buildGGetMetadataQuery(url: OpraURL): OpraGetMetadataQuery;
21
- buildQuery(url: OpraURL, method: string, body?: any): OpraAnyQuery | undefined;
20
+ buildQuery(url: OpraURL, method: string, body?: any): OpraQuery | undefined;
22
21
  protected sendResponse(executionContext: TExecutionContext, queryContexts: QueryContext[]): Promise<void>;
23
22
  protected isBatch(executionContext: TExecutionContext): boolean;
24
23
  protected createOutput(ctx: QueryContext): PreparedOutput;