@opra/core 0.2.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 +7 -6
  9. package/cjs/interfaces/resource.interface.js +2 -0
  10. package/cjs/services/{json-data-service.js → json-collection-service.js} +62 -45
  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 +7 -6
  27. package/esm/index.js +7 -6
  28. package/esm/interfaces/resource.interface.d.ts +22 -0
  29. package/esm/interfaces/resource.interface.js +1 -0
  30. package/esm/services/{json-data-service.d.ts → json-collection-service.d.ts} +18 -19
  31. package/esm/services/{json-data-service.js → json-collection-service.js} +61 -44
  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 +16 -14
  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
@@ -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 { OpraService } 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: OpraService, 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;
@@ -0,0 +1,238 @@
1
+ import { BadRequestError, InternalServerError, IssueSeverity, MethodNotAllowedError, NotFoundError, OpraException, wrapException } from '@opra/exception';
2
+ import { CollectionCreateQuery, CollectionDeleteManyQuery, CollectionDeleteQuery, CollectionGetQuery, CollectionResourceInfo, CollectionSearchQuery, CollectionUpdateManyQuery, CollectionUpdateQuery, ComplexType, ContainerResourceInfo, FieldGetQuery, OpraSchema, SingletonGetQuery, SingletonResourceInfo, UnionType, } from '@opra/schema';
3
+ import { OpraURL } from '@opra/url';
4
+ import { HttpHeaders, HttpStatus } from '../enums/index.js';
5
+ import { HeadersMap } from '../helpers/headers-map.js';
6
+ import { OpraAdapter } from './adapter.js';
7
+ import { QueryContext } from './query-context.js';
8
+ export class OpraHttpAdapter extends OpraAdapter {
9
+ prepareRequests(executionContext) {
10
+ const req = executionContext.getRequestWrapper();
11
+ // todo implement batch requests
12
+ if (this.isBatch(executionContext)) {
13
+ throw new Error('not implemented yet');
14
+ }
15
+ const url = new OpraURL(req.getUrl());
16
+ return [
17
+ this.prepareRequest(executionContext, url, req.getMethod(), new HeadersMap(req.getHeaders()), req.getBody())
18
+ ];
19
+ }
20
+ prepareRequest(executionContext, url, method, headers, body) {
21
+ if (!url.path.size)
22
+ throw new BadRequestError();
23
+ if (method !== 'GET' && url.path.size > 1)
24
+ throw new BadRequestError();
25
+ const query = this.buildQuery(url, method, body);
26
+ if (!query)
27
+ throw new MethodNotAllowedError({
28
+ message: `Method "${method}" is not allowed by target endpoint`
29
+ });
30
+ return new QueryContext({
31
+ service: this.document,
32
+ executionContext,
33
+ query,
34
+ headers: new HeadersMap(),
35
+ params: url.searchParams,
36
+ continueOnError: query.operation === 'read'
37
+ });
38
+ }
39
+ buildQuery(url, method, body) {
40
+ const pathLen = url.path.size;
41
+ let p = url.path.get(0);
42
+ let resource = this._internalResources.get(p.resource) || this.document.getResource(p.resource);
43
+ let container;
44
+ let pathIndex = 0;
45
+ while (resource && resource instanceof ContainerResourceInfo) {
46
+ container = resource;
47
+ p = url.path.get(++pathIndex);
48
+ resource = container.getResource(p.resource);
49
+ }
50
+ try {
51
+ method = method.toUpperCase();
52
+ let query;
53
+ if (resource instanceof SingletonResourceInfo && !p.key) {
54
+ switch (method) {
55
+ case 'GET': {
56
+ query = new SingletonGetQuery(resource);
57
+ }
58
+ }
59
+ }
60
+ else if (resource instanceof CollectionResourceInfo) {
61
+ switch (method) {
62
+ case 'GET': {
63
+ if (p.key) {
64
+ const searchParams = url.searchParams;
65
+ query = new CollectionGetQuery(resource, p.key, {
66
+ pick: searchParams.get('$pick'),
67
+ omit: searchParams.get('$omit'),
68
+ include: searchParams.get('$include')
69
+ });
70
+ }
71
+ else {
72
+ const searchParams = url.searchParams;
73
+ query = new CollectionSearchQuery(resource, {
74
+ filter: searchParams.get('$filter'),
75
+ limit: searchParams.get('$limit'),
76
+ skip: searchParams.get('$skip'),
77
+ distinct: searchParams.get('$distinct'),
78
+ count: searchParams.get('$count'),
79
+ sort: searchParams.get('$sort'),
80
+ pick: searchParams.get('$pick'),
81
+ omit: searchParams.get('$omit'),
82
+ include: searchParams.get('$include')
83
+ });
84
+ }
85
+ break;
86
+ }
87
+ case 'DELETE': {
88
+ const searchParams = url.searchParams;
89
+ query = p.key
90
+ ? new CollectionDeleteQuery(resource, p.key)
91
+ : new CollectionDeleteManyQuery(resource, {
92
+ filter: searchParams.get('$filter'),
93
+ });
94
+ break;
95
+ }
96
+ case 'POST': {
97
+ if (!p.key) {
98
+ const searchParams = url.searchParams;
99
+ query = new CollectionCreateQuery(resource, body, {
100
+ pick: searchParams.get('$pick'),
101
+ omit: searchParams.get('$omit'),
102
+ include: searchParams.get('$include')
103
+ });
104
+ }
105
+ break;
106
+ }
107
+ case 'PATCH': {
108
+ if (p.key) {
109
+ const searchParams = url.searchParams;
110
+ query = new CollectionUpdateQuery(resource, p.key, body, {
111
+ pick: searchParams.get('$pick'),
112
+ omit: searchParams.get('$omit'),
113
+ include: searchParams.get('$include')
114
+ });
115
+ }
116
+ else {
117
+ const searchParams = url.searchParams;
118
+ query = new CollectionUpdateManyQuery(resource, body, {
119
+ filter: searchParams.get('$filter')
120
+ });
121
+ }
122
+ break;
123
+ }
124
+ }
125
+ }
126
+ else
127
+ throw new InternalServerError();
128
+ if (query instanceof SingletonGetQuery || query instanceof CollectionGetQuery || query instanceof FieldGetQuery) {
129
+ // Move through properties
130
+ let parentType;
131
+ const curPath = [];
132
+ let parent = query;
133
+ while (++pathIndex < pathLen) {
134
+ p = url.path.get(pathIndex);
135
+ parentType = parent.dataType;
136
+ if (parent.dataType instanceof UnionType) {
137
+ if (parent.dataType.name === 'any')
138
+ parentType = this.document.getComplexDataType('object');
139
+ else
140
+ throw new TypeError(`"${resource.name}.${curPath.join()}" is a UnionType and needs type casting.`);
141
+ }
142
+ if (!(parentType instanceof ComplexType))
143
+ throw new TypeError(`"${resource.name}.${curPath.join()}" is not a ComplexType and has no fields.`);
144
+ curPath.push(p.resource);
145
+ parent.child = new FieldGetQuery(parent, p.resource, { castingType: parentType });
146
+ parent = parent.child;
147
+ }
148
+ }
149
+ return query;
150
+ }
151
+ catch (e) {
152
+ if (e instanceof OpraException)
153
+ throw e;
154
+ throw new BadRequestError(e);
155
+ }
156
+ }
157
+ async sendResponse(executionContext, queryContexts) {
158
+ const outputPackets = [];
159
+ for (const ctx of queryContexts) {
160
+ const v = this.createOutput(ctx);
161
+ outputPackets.push(v);
162
+ }
163
+ if (this.isBatch(executionContext)) {
164
+ // this.writeError([], new InternalServerError({message: 'Not implemented yet'}));
165
+ return;
166
+ }
167
+ if (!outputPackets.length)
168
+ return this.sendError(executionContext, new NotFoundError());
169
+ const out = outputPackets[0];
170
+ const resp = executionContext.getResponseWrapper();
171
+ resp.setStatus(out.status);
172
+ resp.setHeader(HttpHeaders.Content_Type, 'application/json');
173
+ resp.setHeader(HttpHeaders.Cache_Control, 'no-cache');
174
+ resp.setHeader(HttpHeaders.Pragma, 'no-cache');
175
+ resp.setHeader(HttpHeaders.Expires, '-1');
176
+ resp.setHeader(HttpHeaders.X_Opra_Version, OpraSchema.Version);
177
+ if (out.headers) {
178
+ for (const [k, v] of Object.entries(out.headers)) {
179
+ resp.setHeader(k, v);
180
+ }
181
+ }
182
+ resp.send(JSON.stringify(out.body));
183
+ resp.end();
184
+ }
185
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
186
+ isBatch(executionContext) {
187
+ return false;
188
+ }
189
+ createOutput(ctx) {
190
+ const { query } = ctx;
191
+ let body;
192
+ let status = ctx.status || 0;
193
+ const errors = ctx.errors.map(e => wrapException(e));
194
+ if (errors && errors.length) {
195
+ // Sort errors from fatal to info
196
+ errors.sort((a, b) => {
197
+ const i = IssueSeverity.Keys.indexOf(a.issue.severity) - IssueSeverity.Keys.indexOf(b.issue.severity);
198
+ if (i === 0)
199
+ return b.status - a.status;
200
+ return i;
201
+ });
202
+ if (!status || status < HttpStatus.BAD_REQUEST) {
203
+ status = errors[0].status;
204
+ if (status < HttpStatus.BAD_REQUEST)
205
+ status = HttpStatus.INTERNAL_SERVER_ERROR;
206
+ }
207
+ body = {
208
+ operation: ctx.query.method,
209
+ errors: errors.map(e => e.issue)
210
+ };
211
+ }
212
+ else {
213
+ body = ctx.response;
214
+ status = status || (query.operation === 'create' ? HttpStatus.CREATED : HttpStatus.OK);
215
+ }
216
+ body = this.i18n.deep(body);
217
+ return {
218
+ status,
219
+ headers: ctx.responseHeaders.toObject(),
220
+ body
221
+ };
222
+ }
223
+ async sendError(executionContext, error) {
224
+ const resp = executionContext.getResponseWrapper();
225
+ resp.setStatus(error.status || 500);
226
+ resp.setHeader(HttpHeaders.Content_Type, 'application/json');
227
+ resp.setHeader(HttpHeaders.Cache_Control, 'no-cache');
228
+ resp.setHeader(HttpHeaders.Pragma, 'no-cache');
229
+ resp.setHeader(HttpHeaders.Expires, '-1');
230
+ resp.setHeader(HttpHeaders.X_Opra_Version, OpraSchema.Version);
231
+ const issue = this.i18n.deep(error.issue);
232
+ const body = {
233
+ operation: 'unknown',
234
+ errors: [issue]
235
+ };
236
+ resp.send(JSON.stringify(body));
237
+ }
238
+ }
@@ -0,0 +1,8 @@
1
+ import { SingletonResourceInfo } from '@opra/schema';
2
+ import { ISingletonResource } from '../interfaces/resource.interface.js';
3
+ import { JsonSingletonService } from '../services/json-singleton-service.js';
4
+ export declare class MetadataResource implements ISingletonResource<any> {
5
+ service: JsonSingletonService<any>;
6
+ init(resource: SingletonResourceInfo): void;
7
+ get(): any;
8
+ }
@@ -0,0 +1,20 @@
1
+ import { __decorate } from "tslib";
2
+ import { OprSingletonResource } from '@opra/schema';
3
+ import { JsonSingletonService } from '../services/json-singleton-service.js';
4
+ let MetadataResource = class MetadataResource {
5
+ service;
6
+ init(resource) {
7
+ this.service = new JsonSingletonService(resource.dataType, {
8
+ data: resource.document.getMetadata(true)
9
+ });
10
+ }
11
+ get() {
12
+ return this.service.get();
13
+ }
14
+ };
15
+ MetadataResource = __decorate([
16
+ OprSingletonResource(Object, {
17
+ name: '$metadata'
18
+ })
19
+ ], MetadataResource);
20
+ export { MetadataResource };
@@ -1,15 +1,15 @@
1
1
  import { OpraException } from '@opra/exception';
2
- import { OpraAnyQuery, OpraService } from '@opra/schema';
3
- import { SearchParams } from '@opra/url';
2
+ import { OpraDocument, OpraQuery } from '@opra/schema';
3
+ import { OpraURLSearchParams } from '@opra/url';
4
4
  import { HttpStatus } from '../enums/index.js';
5
+ import { HeadersMap } from '../helpers/headers-map.js';
5
6
  import { ContextType, IExecutionContext, IHttpExecutionContext } from '../interfaces/execution-context.interface.js';
6
- import { HeadersMap } from './headers-map.js';
7
7
  export declare type QueryContextArgs = Pick<QueryContext, 'service' | 'executionContext' | 'query' | 'params' | 'headers' | 'userContext' | 'parentValue' | 'continueOnError'>;
8
8
  export declare class QueryContext {
9
- readonly service: OpraService;
9
+ readonly service: OpraDocument;
10
10
  readonly executionContext: IExecutionContext;
11
- readonly query: OpraAnyQuery;
12
- readonly params: SearchParams;
11
+ readonly query: OpraQuery;
12
+ readonly params: OpraURLSearchParams;
13
13
  readonly headers: HeadersMap;
14
14
  readonly parentValue?: any;
15
15
  readonly resultPath: string;
@@ -1,5 +1,5 @@
1
1
  import { OpraURLSearchParams } from '@opra/url';
2
- import { HeadersMap } from './headers-map.js';
2
+ import { HeadersMap } from '../helpers/headers-map.js';
3
3
  export class QueryContext {
4
4
  service;
5
5
  executionContext;
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export declare enum HttpHeaders {
5
5
  X_Opra_Version = "X-Opra-Version",
6
- X_Opra_Schema = "X-Opra-Schema",
6
+ X_Opra_DataType = "X-Opra-DataType",
7
7
  X_Opra_Count = "X-Opra-Count",
8
8
  /**
9
9
  * Defines the authentication method that should be used to access a resource.