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