@opra/core 0.1.1 → 0.3.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/enums/http-headers.enum.js +3 -2
- package/cjs/implementation/adapter-utils/entity-resource-execute.util.js +36 -34
- package/cjs/implementation/adapter-utils/resource-prepare.util.js +1 -1
- package/cjs/implementation/adapter.js +14 -19
- package/cjs/implementation/express-adapter.js +3 -0
- package/cjs/implementation/headers-map.js +18 -0
- package/cjs/implementation/http-adapter.js +65 -79
- package/cjs/implementation/query-context.js +12 -19
- package/cjs/index.js +1 -3
- package/cjs/services/json-collection-service.js +505 -0
- package/cjs/utils/path-to-tree.js +7 -5
- package/esm/enums/http-headers.enum.d.ts +3 -2
- package/esm/enums/http-headers.enum.js +3 -2
- package/esm/implementation/adapter-utils/entity-resource-execute.util.d.ts +2 -2
- package/esm/implementation/adapter-utils/entity-resource-execute.util.js +35 -33
- package/esm/implementation/adapter-utils/resource-execute.util.d.ts +2 -2
- package/esm/implementation/adapter-utils/resource-prepare.util.d.ts +2 -2
- package/esm/implementation/adapter-utils/resource-prepare.util.js +1 -1
- package/esm/implementation/adapter.d.ts +5 -5
- package/esm/implementation/adapter.js +12 -17
- package/esm/implementation/express-adapter.d.ts +2 -2
- package/esm/implementation/express-adapter.js +2 -0
- package/esm/implementation/headers-map.d.ts +5 -0
- package/esm/implementation/headers-map.js +14 -0
- package/esm/implementation/http-adapter.d.ts +6 -6
- package/esm/implementation/http-adapter.js +61 -75
- package/esm/implementation/query-context.d.ts +10 -16
- package/esm/implementation/query-context.js +11 -17
- package/esm/index.d.ts +1 -3
- package/esm/index.js +1 -3
- package/esm/interfaces/entity-service.interface.d.ts +8 -6
- package/esm/services/json-collection-service.d.ts +85 -0
- package/esm/services/json-collection-service.js +500 -0
- package/esm/types.d.ts +0 -3
- package/esm/utils/path-to-tree.d.ts +1 -1
- package/esm/utils/path-to-tree.js +7 -5
- package/i18n/en/error.json +5 -5
- package/package.json +10 -7
- package/cjs/exception/api-exception.js +0 -68
- package/cjs/exception/http-errors/bad-request.error.js +0 -26
- package/cjs/exception/http-errors/failed-dependency.error.js +0 -25
- package/cjs/exception/http-errors/forbidden.error.js +0 -27
- package/cjs/exception/http-errors/internal-server.error.js +0 -27
- package/cjs/exception/http-errors/method-not-allowed.error.js +0 -26
- package/cjs/exception/http-errors/not-acceptable.error.js +0 -26
- package/cjs/exception/http-errors/not-found.error.js +0 -29
- package/cjs/exception/http-errors/unauthorized.error.js +0 -26
- package/cjs/exception/http-errors/unprocessable-entity.error.js +0 -25
- package/cjs/exception/index.js +0 -15
- package/cjs/exception/resource-errors/resource-conflict.error.js +0 -19
- package/cjs/exception/resource-errors/resource-not-found.error.js +0 -19
- package/cjs/exception/wrap-error.js +0 -17
- package/cjs/interfaces/query.interface.js +0 -207
- package/cjs/services/json-data-service.js +0 -252
- package/esm/exception/api-exception.d.ts +0 -40
- package/esm/exception/api-exception.js +0 -64
- package/esm/exception/http-errors/bad-request.error.d.ts +0 -10
- package/esm/exception/http-errors/bad-request.error.js +0 -22
- package/esm/exception/http-errors/failed-dependency.error.d.ts +0 -9
- package/esm/exception/http-errors/failed-dependency.error.js +0 -21
- package/esm/exception/http-errors/forbidden.error.d.ts +0 -11
- package/esm/exception/http-errors/forbidden.error.js +0 -23
- package/esm/exception/http-errors/internal-server.error.d.ts +0 -9
- package/esm/exception/http-errors/internal-server.error.js +0 -22
- package/esm/exception/http-errors/method-not-allowed.error.d.ts +0 -10
- package/esm/exception/http-errors/method-not-allowed.error.js +0 -22
- package/esm/exception/http-errors/not-acceptable.error.d.ts +0 -10
- package/esm/exception/http-errors/not-acceptable.error.js +0 -22
- package/esm/exception/http-errors/not-found.error.d.ts +0 -13
- package/esm/exception/http-errors/not-found.error.js +0 -25
- package/esm/exception/http-errors/unauthorized.error.d.ts +0 -10
- package/esm/exception/http-errors/unauthorized.error.js +0 -22
- package/esm/exception/http-errors/unprocessable-entity.error.d.ts +0 -9
- package/esm/exception/http-errors/unprocessable-entity.error.js +0 -21
- package/esm/exception/index.d.ts +0 -12
- package/esm/exception/index.js +0 -12
- package/esm/exception/resource-errors/resource-conflict.error.d.ts +0 -4
- package/esm/exception/resource-errors/resource-conflict.error.js +0 -15
- package/esm/exception/resource-errors/resource-not-found.error.d.ts +0 -4
- package/esm/exception/resource-errors/resource-not-found.error.js +0 -15
- package/esm/exception/wrap-error.d.ts +0 -2
- package/esm/exception/wrap-error.js +0 -13
- package/esm/interfaces/query.interface.d.ts +0 -115
- package/esm/interfaces/query.interface.js +0 -203
- package/esm/services/json-data-service.d.ts +0 -68
- package/esm/services/json-data-service.js +0 -247
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Maybe } from 'ts-gems';
|
|
2
|
+
import { ComplexType, EntityResource, OpraAnyQuery, OpraSchema } from '@opra/schema';
|
|
3
|
+
import { Expression } from '@opra/url';
|
|
4
|
+
import { QueryContext } from '../implementation/query-context.js';
|
|
5
|
+
import { IEntityService } from '../interfaces/entity-service.interface.js';
|
|
6
|
+
import { EntityInput, EntityOutput } from '../types.js';
|
|
7
|
+
export interface JsonCollectionServiceOptions {
|
|
8
|
+
resourceName?: string;
|
|
9
|
+
defaultLimit?: number;
|
|
10
|
+
data?: any[];
|
|
11
|
+
}
|
|
12
|
+
export declare class JsonCollectionService<T, TOutput = EntityOutput<T>> implements IEntityService {
|
|
13
|
+
readonly resource: EntityResource;
|
|
14
|
+
private _status;
|
|
15
|
+
private _initError;
|
|
16
|
+
private _dbName;
|
|
17
|
+
private _initData?;
|
|
18
|
+
defaultLimit: number;
|
|
19
|
+
constructor(resource: EntityResource, options?: JsonCollectionServiceOptions);
|
|
20
|
+
get dataType(): ComplexType;
|
|
21
|
+
get primaryKey(): string;
|
|
22
|
+
get resourceName(): string;
|
|
23
|
+
close(): Promise<void>;
|
|
24
|
+
processRequest(ctx: QueryContext): Promise<any>;
|
|
25
|
+
get(keyValue: any, options?: JsonCollectionService.GetOptions): Promise<Maybe<TOutput>>;
|
|
26
|
+
count(options?: JsonCollectionService.SearchOptions): Promise<number>;
|
|
27
|
+
search(options?: JsonCollectionService.SearchOptions): Promise<TOutput[]>;
|
|
28
|
+
create(data: EntityInput<T>, options?: JsonCollectionService.CreateOptions): Promise<TOutput>;
|
|
29
|
+
update(keyValue: any, data: EntityInput<T>, options?: JsonCollectionService.UpdateOptions): Promise<Maybe<TOutput>>;
|
|
30
|
+
updateMany(data: EntityInput<T>, options?: JsonCollectionService.UpdateManyOptions): Promise<number>;
|
|
31
|
+
delete(keyValue: any): Promise<boolean>;
|
|
32
|
+
deleteMany(options?: JsonCollectionService.DeleteManyOptions): Promise<number>;
|
|
33
|
+
private _waitInitializing;
|
|
34
|
+
protected _init(): Promise<void>;
|
|
35
|
+
protected _prepare(query: OpraAnyQuery): {
|
|
36
|
+
method: OpraSchema.EntityMethod;
|
|
37
|
+
options: any;
|
|
38
|
+
keyValue?: any;
|
|
39
|
+
values?: any;
|
|
40
|
+
args: any[];
|
|
41
|
+
};
|
|
42
|
+
protected _convertSelect(args: {
|
|
43
|
+
pick?: string[];
|
|
44
|
+
omit?: string[];
|
|
45
|
+
include?: string[];
|
|
46
|
+
}): string[];
|
|
47
|
+
protected _convertFilter(str: string | Expression | undefined | {}): any;
|
|
48
|
+
}
|
|
49
|
+
export declare namespace JsonCollectionService {
|
|
50
|
+
type CountOptions = {
|
|
51
|
+
filter?: string | Expression | {};
|
|
52
|
+
};
|
|
53
|
+
type CreateOptions = {
|
|
54
|
+
pick?: string[];
|
|
55
|
+
omit?: string[];
|
|
56
|
+
include?: string[];
|
|
57
|
+
};
|
|
58
|
+
type GetOptions = {
|
|
59
|
+
pick?: string[];
|
|
60
|
+
omit?: string[];
|
|
61
|
+
include?: string[];
|
|
62
|
+
};
|
|
63
|
+
type SearchOptions = {
|
|
64
|
+
pick?: string[];
|
|
65
|
+
omit?: string[];
|
|
66
|
+
include?: string[];
|
|
67
|
+
filter?: any[];
|
|
68
|
+
sort?: string[];
|
|
69
|
+
limit?: number;
|
|
70
|
+
skip?: number;
|
|
71
|
+
distinct?: boolean;
|
|
72
|
+
count?: boolean;
|
|
73
|
+
};
|
|
74
|
+
type UpdateOptions = {
|
|
75
|
+
pick?: string[];
|
|
76
|
+
omit?: string[];
|
|
77
|
+
include?: string[];
|
|
78
|
+
};
|
|
79
|
+
type UpdateManyOptions = {
|
|
80
|
+
filter?: any[];
|
|
81
|
+
};
|
|
82
|
+
type DeleteManyOptions = {
|
|
83
|
+
filter?: any[];
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import merge from 'putil-merge';
|
|
3
|
+
import { nSQL } from "@nano-sql/core";
|
|
4
|
+
import { BadRequestError, MethodNotAllowedError, ResourceConflictError } from '@opra/exception';
|
|
5
|
+
import { ComplexType, EntityResource } from '@opra/schema';
|
|
6
|
+
import { $parse, ArrayExpression, BooleanLiteral, ComparisonExpression, DateLiteral, Expression, LogicalExpression, NullLiteral, NumberLiteral, ParenthesesExpression, QualifiedIdentifier, StringLiteral, TimeLiteral } from '@opra/url';
|
|
7
|
+
import { pathToTree } from '../utils/path-to-tree.js';
|
|
8
|
+
let dbId = 1;
|
|
9
|
+
const indexingTypes = ['int', 'float', 'number', 'date', 'string'];
|
|
10
|
+
export class JsonCollectionService {
|
|
11
|
+
resource;
|
|
12
|
+
_status = '';
|
|
13
|
+
_initError;
|
|
14
|
+
_dbName;
|
|
15
|
+
_initData;
|
|
16
|
+
defaultLimit;
|
|
17
|
+
constructor(resource, options) {
|
|
18
|
+
this.resource = resource;
|
|
19
|
+
if (this.resource.keyFields.length > 1)
|
|
20
|
+
throw new TypeError('JsonDataService currently doesn\'t support multiple primary keys');
|
|
21
|
+
this.defaultLimit = options?.defaultLimit ?? 10;
|
|
22
|
+
this._initData = options?.data;
|
|
23
|
+
}
|
|
24
|
+
get dataType() {
|
|
25
|
+
return this.resource.dataType;
|
|
26
|
+
}
|
|
27
|
+
get primaryKey() {
|
|
28
|
+
return this.resource.keyFields[0];
|
|
29
|
+
}
|
|
30
|
+
get resourceName() {
|
|
31
|
+
return this.resource.name;
|
|
32
|
+
}
|
|
33
|
+
async close() {
|
|
34
|
+
await this._waitInitializing();
|
|
35
|
+
if (this._status === 'initialized') {
|
|
36
|
+
this._status = 'initializing';
|
|
37
|
+
try {
|
|
38
|
+
await nSQL().disconnect(this._dbName);
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
this._status = '';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async processRequest(ctx) {
|
|
46
|
+
const prepared = this._prepare(ctx.query);
|
|
47
|
+
const fn = this[prepared.method];
|
|
48
|
+
if (!fn)
|
|
49
|
+
throw new TypeError(`Unimplemented method (${prepared.method})`);
|
|
50
|
+
// @ts-ignore
|
|
51
|
+
return fn.apply(this, prepared.args);
|
|
52
|
+
}
|
|
53
|
+
async get(keyValue, options) {
|
|
54
|
+
await this._init();
|
|
55
|
+
const select = this._convertSelect({
|
|
56
|
+
pick: options?.pick,
|
|
57
|
+
omit: options?.omit,
|
|
58
|
+
include: options?.include,
|
|
59
|
+
});
|
|
60
|
+
nSQL().useDatabase(this._dbName);
|
|
61
|
+
try {
|
|
62
|
+
const rows = await nSQL(this.resourceName)
|
|
63
|
+
.query('select', select)
|
|
64
|
+
.where([this.primaryKey, '=', keyValue])
|
|
65
|
+
.exec();
|
|
66
|
+
return unFlatten(rows[0]);
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
throw e;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async count(options) {
|
|
73
|
+
await this._init();
|
|
74
|
+
nSQL().useDatabase(this._dbName);
|
|
75
|
+
const rows = await nSQL(this.resourceName)
|
|
76
|
+
.query('select', ['COUNT(*) as count'])
|
|
77
|
+
.where(options?.filter || [])
|
|
78
|
+
.exec();
|
|
79
|
+
return (rows[0]?.count) || 0;
|
|
80
|
+
}
|
|
81
|
+
async search(options) {
|
|
82
|
+
await this._init();
|
|
83
|
+
const select = this._convertSelect({
|
|
84
|
+
pick: options?.pick,
|
|
85
|
+
omit: options?.omit,
|
|
86
|
+
include: options?.include,
|
|
87
|
+
});
|
|
88
|
+
const filter = this._convertFilter(options?.filter);
|
|
89
|
+
nSQL().useDatabase(this._dbName);
|
|
90
|
+
const query = nSQL(this.resourceName)
|
|
91
|
+
.query('select', select)
|
|
92
|
+
.limit(options?.limit || 10)
|
|
93
|
+
.offset(options?.skip || 0)
|
|
94
|
+
.orderBy(options?.sort || [])
|
|
95
|
+
.where(filter || []);
|
|
96
|
+
return (await query.exec()).map(x => unFlatten(x));
|
|
97
|
+
}
|
|
98
|
+
async create(data, options) {
|
|
99
|
+
if (!data[this.primaryKey])
|
|
100
|
+
throw new BadRequestError({
|
|
101
|
+
message: 'You must provide primary key value'
|
|
102
|
+
});
|
|
103
|
+
await this._init();
|
|
104
|
+
const keyValue = data[this.primaryKey];
|
|
105
|
+
nSQL().useDatabase(this._dbName);
|
|
106
|
+
const rows = await nSQL(this.resourceName).query('select', [this.primaryKey])
|
|
107
|
+
.where([this.primaryKey, '=', keyValue])
|
|
108
|
+
.exec();
|
|
109
|
+
if (rows.length)
|
|
110
|
+
throw new ResourceConflictError(this.resourceName, this.primaryKey);
|
|
111
|
+
await nSQL(this.resourceName).query('upsert', data)
|
|
112
|
+
.exec();
|
|
113
|
+
return await this.get(keyValue, options);
|
|
114
|
+
}
|
|
115
|
+
async update(keyValue, data, options) {
|
|
116
|
+
await this._init();
|
|
117
|
+
nSQL().useDatabase(this._dbName);
|
|
118
|
+
await nSQL(this.resourceName)
|
|
119
|
+
.query('conform rows', (row) => {
|
|
120
|
+
const out = merge({}, row, { deep: true, clone: true });
|
|
121
|
+
merge(out, data, { deep: true });
|
|
122
|
+
return out;
|
|
123
|
+
})
|
|
124
|
+
.where([this.primaryKey, '=', keyValue])
|
|
125
|
+
.exec();
|
|
126
|
+
// await nSQL(this.resourceName).query("rebuild indexes").exec();
|
|
127
|
+
return this.get(keyValue, options);
|
|
128
|
+
}
|
|
129
|
+
async updateMany(data, options) {
|
|
130
|
+
await this._init();
|
|
131
|
+
const filter = this._convertFilter(options?.filter);
|
|
132
|
+
nSQL().useDatabase(this._dbName);
|
|
133
|
+
let affected = 0;
|
|
134
|
+
await nSQL(this.resourceName)
|
|
135
|
+
.query('conform rows', (row) => {
|
|
136
|
+
const out = merge({}, row, { deep: true, clone: true });
|
|
137
|
+
merge(out, data, { deep: true });
|
|
138
|
+
affected++;
|
|
139
|
+
return out;
|
|
140
|
+
})
|
|
141
|
+
.where(filter)
|
|
142
|
+
.exec();
|
|
143
|
+
// await nSQL(this.resourceName).query("rebuild indexes").exec();
|
|
144
|
+
return affected;
|
|
145
|
+
}
|
|
146
|
+
async delete(keyValue) {
|
|
147
|
+
await this._init();
|
|
148
|
+
nSQL().useDatabase(this._dbName);
|
|
149
|
+
const result = await nSQL(this.resourceName)
|
|
150
|
+
.query('delete')
|
|
151
|
+
.where([this.primaryKey, '=', keyValue])
|
|
152
|
+
.exec();
|
|
153
|
+
return !!result.length;
|
|
154
|
+
}
|
|
155
|
+
async deleteMany(options) {
|
|
156
|
+
await this._init();
|
|
157
|
+
const filter = this._convertFilter(options?.filter);
|
|
158
|
+
nSQL().useDatabase(this._dbName);
|
|
159
|
+
const result = await nSQL(this.resourceName)
|
|
160
|
+
.query('delete')
|
|
161
|
+
.where(filter)
|
|
162
|
+
.exec();
|
|
163
|
+
return result.length;
|
|
164
|
+
}
|
|
165
|
+
async _waitInitializing() {
|
|
166
|
+
if (this._status === 'initializing') {
|
|
167
|
+
return new Promise((resolve, reject) => {
|
|
168
|
+
const reTry = () => setTimeout(() => {
|
|
169
|
+
if (this._status === '')
|
|
170
|
+
return resolve(this._init());
|
|
171
|
+
if (this._status === 'error')
|
|
172
|
+
return reject(this._initError);
|
|
173
|
+
if (this._status === 'initialized')
|
|
174
|
+
return resolve();
|
|
175
|
+
reTry();
|
|
176
|
+
}, 50).unref();
|
|
177
|
+
reTry();
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async _init() {
|
|
182
|
+
await this._waitInitializing();
|
|
183
|
+
if (this._status === 'initialized')
|
|
184
|
+
return;
|
|
185
|
+
this._status = 'initializing';
|
|
186
|
+
this._dbName = 'JsonDataService_DB_' + (dbId++);
|
|
187
|
+
try {
|
|
188
|
+
const table = {
|
|
189
|
+
name: this.resourceName,
|
|
190
|
+
model: {},
|
|
191
|
+
indexes: {}
|
|
192
|
+
};
|
|
193
|
+
for (const [k, f] of this.resource.dataType.fields.entries()) {
|
|
194
|
+
const fieldType = this.resource.owner.getDataType(f.type || 'string');
|
|
195
|
+
const o = table.model[k + ':' + dataTypeToSQLType(fieldType, !!f.isArray)] = {};
|
|
196
|
+
if (k === this.primaryKey)
|
|
197
|
+
o.pk = true;
|
|
198
|
+
}
|
|
199
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
200
|
+
const indexes = table.indexes;
|
|
201
|
+
// Add indexes for sort fields
|
|
202
|
+
const searchMethod = this.resource.metadata.methods.search;
|
|
203
|
+
if (searchMethod) {
|
|
204
|
+
if (searchMethod.sortFields) {
|
|
205
|
+
searchMethod.sortFields.forEach(fieldName => {
|
|
206
|
+
const f = this.dataType.getField(fieldName);
|
|
207
|
+
const fieldType = this.resource.owner.getDataType(f.type || 'string');
|
|
208
|
+
const t = dataTypeToSQLType(fieldType, !!f.isArray);
|
|
209
|
+
if (indexingTypes.includes(t))
|
|
210
|
+
indexes[fieldName + ':' + t] = {};
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
if (searchMethod.filters) {
|
|
214
|
+
searchMethod.filters.forEach(filter => {
|
|
215
|
+
const f = this.dataType.getField(filter.field);
|
|
216
|
+
const fieldType = this.resource.owner.getDataType(f.type || 'string');
|
|
217
|
+
const t = dataTypeToSQLType(fieldType, !!f.isArray);
|
|
218
|
+
if (indexingTypes.includes(t))
|
|
219
|
+
indexes[filter.field + ':' + t] = {};
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
await nSQL().createDatabase({
|
|
224
|
+
id: this._dbName,
|
|
225
|
+
version: 3,
|
|
226
|
+
tables: [table]
|
|
227
|
+
});
|
|
228
|
+
this._status = 'initialized';
|
|
229
|
+
if (this._initData) {
|
|
230
|
+
nSQL().useDatabase(this._dbName);
|
|
231
|
+
await nSQL(this.resourceName)
|
|
232
|
+
.query('upsert', this._initData)
|
|
233
|
+
.exec();
|
|
234
|
+
delete this._initData;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
catch (e) {
|
|
238
|
+
this._initError = e;
|
|
239
|
+
this._status = 'error';
|
|
240
|
+
throw e;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
_prepare(query) {
|
|
244
|
+
if (query.resource instanceof EntityResource) {
|
|
245
|
+
if (query.dataType !== this.dataType)
|
|
246
|
+
throw new TypeError(`Query data type (${query.dataType.name}) ` +
|
|
247
|
+
`differs from JsonDataService data type (${this.dataType.name})`);
|
|
248
|
+
}
|
|
249
|
+
switch (query.method) {
|
|
250
|
+
case 'count': {
|
|
251
|
+
const options = _.omitBy({
|
|
252
|
+
filter: this._convertFilter(query.filter)
|
|
253
|
+
}, _.isNil);
|
|
254
|
+
return {
|
|
255
|
+
method: query.method,
|
|
256
|
+
options,
|
|
257
|
+
args: [options]
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
case 'create': {
|
|
261
|
+
const options = _.omitBy({
|
|
262
|
+
pick: query.pick,
|
|
263
|
+
omit: query.omit,
|
|
264
|
+
include: query.include
|
|
265
|
+
}, _.isNil);
|
|
266
|
+
const { data } = query;
|
|
267
|
+
return {
|
|
268
|
+
method: query.method,
|
|
269
|
+
values: data,
|
|
270
|
+
options,
|
|
271
|
+
args: [data, options]
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
case 'get': {
|
|
275
|
+
if (query.kind === 'GetInstanceQuery') {
|
|
276
|
+
const options = _.omitBy({
|
|
277
|
+
pick: query.pick,
|
|
278
|
+
omit: query.omit,
|
|
279
|
+
include: query.include
|
|
280
|
+
}, _.isNil);
|
|
281
|
+
const keyValue = query.keyValue;
|
|
282
|
+
return {
|
|
283
|
+
method: query.method,
|
|
284
|
+
keyValue,
|
|
285
|
+
options,
|
|
286
|
+
args: [keyValue, options]
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
if (query.kind === 'GetFieldQuery') {
|
|
290
|
+
// todo
|
|
291
|
+
}
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
case 'search': {
|
|
295
|
+
if (query.distinct)
|
|
296
|
+
throw new MethodNotAllowedError({
|
|
297
|
+
message: '$distinct parameter is not supported by JsonDataService'
|
|
298
|
+
});
|
|
299
|
+
const options = _.omitBy({
|
|
300
|
+
pick: query.pick,
|
|
301
|
+
omit: query.omit,
|
|
302
|
+
include: query.include,
|
|
303
|
+
filter: this._convertFilter(query.filter),
|
|
304
|
+
sort: query.sort?.length ? query.sort : undefined,
|
|
305
|
+
skip: query.skip,
|
|
306
|
+
limit: query.limit,
|
|
307
|
+
offset: query.skip,
|
|
308
|
+
count: query.count,
|
|
309
|
+
}, _.isNil);
|
|
310
|
+
return {
|
|
311
|
+
method: query.method,
|
|
312
|
+
options,
|
|
313
|
+
args: [options]
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
case 'update': {
|
|
317
|
+
const options = _.omitBy({
|
|
318
|
+
pick: query.pick,
|
|
319
|
+
omit: query.omit,
|
|
320
|
+
include: query.include
|
|
321
|
+
}, _.isNil);
|
|
322
|
+
const { data } = query;
|
|
323
|
+
const keyValue = query.keyValue;
|
|
324
|
+
return {
|
|
325
|
+
method: query.method,
|
|
326
|
+
keyValue: query.keyValue,
|
|
327
|
+
values: data,
|
|
328
|
+
options,
|
|
329
|
+
args: [keyValue, data, options]
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
case 'updateMany': {
|
|
333
|
+
const options = _.omitBy({
|
|
334
|
+
filter: this._convertFilter(query.filter)
|
|
335
|
+
}, _.isNil);
|
|
336
|
+
const { data } = query;
|
|
337
|
+
return {
|
|
338
|
+
method: query.method,
|
|
339
|
+
options,
|
|
340
|
+
args: [data, options]
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
case 'delete': {
|
|
344
|
+
const options = {};
|
|
345
|
+
const keyValue = query.keyValue;
|
|
346
|
+
return {
|
|
347
|
+
method: query.method,
|
|
348
|
+
keyValue,
|
|
349
|
+
options,
|
|
350
|
+
args: [keyValue, options]
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
case 'deleteMany': {
|
|
354
|
+
const options = _.omitBy({
|
|
355
|
+
filter: this._convertFilter(query.filter)
|
|
356
|
+
}, _.isNil);
|
|
357
|
+
return {
|
|
358
|
+
method: query.method,
|
|
359
|
+
options,
|
|
360
|
+
args: [options]
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
throw new Error(`Unimplemented query type "${query.method}"`);
|
|
365
|
+
}
|
|
366
|
+
_convertSelect(args) {
|
|
367
|
+
const result = [];
|
|
368
|
+
const document = this.dataType.owner;
|
|
369
|
+
const processDataType = (dt, path, pick, omit, include) => {
|
|
370
|
+
let kl;
|
|
371
|
+
for (const [k, f] of dt.fields) {
|
|
372
|
+
kl = k.toLowerCase();
|
|
373
|
+
if (omit?.[kl] === true)
|
|
374
|
+
continue;
|
|
375
|
+
if ((((!pick && !f.exclusive) || pick?.[kl])) || include?.[kl]) {
|
|
376
|
+
const fieldType = document.getDataType(f.type);
|
|
377
|
+
const subPath = (path ? path + '.' : '') + f.name;
|
|
378
|
+
if (fieldType instanceof ComplexType) {
|
|
379
|
+
processDataType(fieldType, subPath, typeof pick?.[kl] === 'object' ? pick?.[kl] : undefined, typeof omit?.[kl] === 'object' ? omit?.[kl] : undefined, typeof include?.[kl] === 'object' ? include?.[kl] : undefined);
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
result.push(subPath);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
processDataType(this.dataType, '', (args.pick ? pathToTree(args.pick, true) : undefined), (args.omit ? pathToTree(args.omit, true) : undefined), (args.include ? pathToTree(args.include, true) : undefined));
|
|
387
|
+
return result;
|
|
388
|
+
}
|
|
389
|
+
_convertFilter(str) {
|
|
390
|
+
const ast = typeof str === 'string'
|
|
391
|
+
? $parse(str)
|
|
392
|
+
: str;
|
|
393
|
+
if (!ast || !(ast instanceof Expression))
|
|
394
|
+
return ast;
|
|
395
|
+
if (ast instanceof ComparisonExpression) {
|
|
396
|
+
const left = this._convertFilter(ast.left);
|
|
397
|
+
const right = this._convertFilter(ast.right);
|
|
398
|
+
switch (ast.op) {
|
|
399
|
+
case '=':
|
|
400
|
+
return [left, '=', right];
|
|
401
|
+
case '!=':
|
|
402
|
+
return [left, '!=', right];
|
|
403
|
+
case '>':
|
|
404
|
+
return [left, '>', right];
|
|
405
|
+
case '>=':
|
|
406
|
+
return [left, '>=', right];
|
|
407
|
+
case '<':
|
|
408
|
+
return [left, '<', right];
|
|
409
|
+
case '<=':
|
|
410
|
+
return [left, '<=', right];
|
|
411
|
+
case 'like':
|
|
412
|
+
return [left, 'LIKE', right];
|
|
413
|
+
case '!like':
|
|
414
|
+
return [left, 'NOT LIKE', right];
|
|
415
|
+
case 'in':
|
|
416
|
+
return [left, 'IN', Array.isArray(right) ? right : [right]];
|
|
417
|
+
case '!in':
|
|
418
|
+
return [left, 'NOT IN', Array.isArray(right) ? right : [right]];
|
|
419
|
+
default:
|
|
420
|
+
throw new Error(`ComparisonExpression operator (${ast.op}) not implemented yet`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
if (ast instanceof QualifiedIdentifier) {
|
|
424
|
+
return ast.value;
|
|
425
|
+
}
|
|
426
|
+
if (ast instanceof NumberLiteral ||
|
|
427
|
+
ast instanceof StringLiteral ||
|
|
428
|
+
ast instanceof BooleanLiteral ||
|
|
429
|
+
ast instanceof NullLiteral ||
|
|
430
|
+
ast instanceof DateLiteral ||
|
|
431
|
+
ast instanceof TimeLiteral) {
|
|
432
|
+
return ast.value;
|
|
433
|
+
}
|
|
434
|
+
if (ast instanceof ArrayExpression) {
|
|
435
|
+
return ast.items.map(item => this._convertFilter(item));
|
|
436
|
+
}
|
|
437
|
+
if (ast instanceof LogicalExpression) {
|
|
438
|
+
return ast.items.map(item => this._convertFilter(item))
|
|
439
|
+
.reduce((a, v) => {
|
|
440
|
+
if (a.length)
|
|
441
|
+
a.push(ast.op.toUpperCase());
|
|
442
|
+
a.push(v);
|
|
443
|
+
return a;
|
|
444
|
+
}, []);
|
|
445
|
+
}
|
|
446
|
+
if (ast instanceof ArrayExpression) {
|
|
447
|
+
return ast.items.map(item => this._convertFilter(item));
|
|
448
|
+
}
|
|
449
|
+
if (ast instanceof ParenthesesExpression) {
|
|
450
|
+
return this._convertFilter(ast.expression);
|
|
451
|
+
}
|
|
452
|
+
throw new Error(`${ast.kind} is not implemented yet`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
function unFlatten(input) {
|
|
456
|
+
if (!input)
|
|
457
|
+
return;
|
|
458
|
+
const target = {};
|
|
459
|
+
for (const k of Object.keys(input)) {
|
|
460
|
+
if (k.includes('.')) {
|
|
461
|
+
const keys = k.split('.');
|
|
462
|
+
let o = target;
|
|
463
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
464
|
+
o = o[keys[i]] = o[keys[i]] || {};
|
|
465
|
+
}
|
|
466
|
+
o[keys[keys.length - 1]] = input[k];
|
|
467
|
+
}
|
|
468
|
+
else
|
|
469
|
+
target[k] = input[k];
|
|
470
|
+
}
|
|
471
|
+
return target;
|
|
472
|
+
}
|
|
473
|
+
function dataTypeToSQLType(dataType, isArray) {
|
|
474
|
+
let out = 'any';
|
|
475
|
+
if (dataType.kind !== 'SimpleType')
|
|
476
|
+
out = 'object';
|
|
477
|
+
else {
|
|
478
|
+
switch (dataType.name) {
|
|
479
|
+
case 'boolean':
|
|
480
|
+
case 'number':
|
|
481
|
+
case 'string':
|
|
482
|
+
out = dataType.name;
|
|
483
|
+
break;
|
|
484
|
+
case 'integer':
|
|
485
|
+
out = 'int';
|
|
486
|
+
break;
|
|
487
|
+
// case 'date': //there is bug in nano-sql.
|
|
488
|
+
// case 'date-time':
|
|
489
|
+
// out = 'date';
|
|
490
|
+
// break;
|
|
491
|
+
case 'time':
|
|
492
|
+
out = 'string';
|
|
493
|
+
break;
|
|
494
|
+
case 'uuid':
|
|
495
|
+
out = 'uuid';
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return out + (isArray ? '[]' : '');
|
|
500
|
+
}
|
package/esm/types.d.ts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import { Builtin, DeepPickWritable } from 'ts-gems';
|
|
2
|
-
import { OpraSchema } from '@opra/schema';
|
|
3
|
-
export declare type QueryType = OpraSchema.EntityMethodType | 'schema' | 'execute';
|
|
4
|
-
export declare type KeyValue = string | number | boolean | object;
|
|
5
2
|
export declare type EntityInput<T> = DeepNullableIfPartial<DeepPickWritable<T>>;
|
|
6
3
|
export declare type EntityOutput<T> = DeepNullableIfPartial<T>;
|
|
7
4
|
export declare type DeepNullableIfPartial<T> = _DeepNullableIfPartial<T>;
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
const dotPattern = /^([^.]+)\.(.*)$/;
|
|
2
|
-
export function pathToTree(arr) {
|
|
2
|
+
export function pathToTree(arr, lowerCaseKeys) {
|
|
3
3
|
if (!arr.length)
|
|
4
4
|
return;
|
|
5
|
-
return
|
|
5
|
+
return _pathToTree(arr, {}, lowerCaseKeys);
|
|
6
6
|
}
|
|
7
|
-
function
|
|
8
|
-
for (
|
|
7
|
+
function _pathToTree(arr, target, lowerCaseKeys) {
|
|
8
|
+
for (let k of arr) {
|
|
9
|
+
if (lowerCaseKeys)
|
|
10
|
+
k = k.toLowerCase();
|
|
9
11
|
const m = dotPattern.exec(k);
|
|
10
12
|
if (m) {
|
|
11
13
|
const key = m[1];
|
|
12
14
|
if (target[key] === true)
|
|
13
15
|
continue;
|
|
14
16
|
const sub = target[key] = typeof target[key] === 'object' ? target[key] : {};
|
|
15
|
-
|
|
17
|
+
_pathToTree([m[2]], sub);
|
|
16
18
|
}
|
|
17
19
|
else {
|
|
18
20
|
target[k] = true;
|
package/i18n/en/error.json
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
"FAILED_DEPENDENCY": "The request failed due to failure of a previous request",
|
|
4
4
|
"FORBIDDEN": "You are not authorized to perform this action",
|
|
5
5
|
"INTERNAL_SERVER_ERROR": "Internal server error",
|
|
6
|
-
"METHOD_NOT_ALLOWED": "Method
|
|
7
|
-
"NOT_ACCEPTABLE": "Not
|
|
6
|
+
"METHOD_NOT_ALLOWED": "Method not allowed",
|
|
7
|
+
"NOT_ACCEPTABLE": "Not acceptable",
|
|
8
8
|
"NOT_FOUND": "Not found",
|
|
9
9
|
"UNAUTHORIZED": "You have not been authenticated to perform this action",
|
|
10
10
|
"UNPROCESSABLE_ENTITY": "Unprocessable entity",
|
|
11
|
-
|
|
12
|
-
"RESOLVER_FORBIDDEN": "The resource endpoint does not accept '{{queryType}}' operations",
|
|
13
11
|
"RESOURCE_NOT_FOUND": "The resource '{{resource}}' could not be found",
|
|
14
|
-
"RESOURCE_CONFLICT": "There is already an other {{resource}} resource with same field values ({{fields}})"
|
|
12
|
+
"RESOURCE_CONFLICT": "There is already an other {{resource}} resource with same field values ({{fields}})",
|
|
13
|
+
"RESOLVER_FORBIDDEN": "The resource endpoint does not accept '{{method}}' operations",
|
|
14
|
+
"UNACCEPTED_SORT_FIELD": "Field '{{field}}' is not available for sort operation"
|
|
15
15
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opra/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Opra schema package",
|
|
5
5
|
"author": "Panates",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,21 +27,24 @@
|
|
|
27
27
|
"clean:cover": "rimraf ../../coverage/core"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@opra/
|
|
31
|
-
"@opra/
|
|
32
|
-
"@opra/
|
|
33
|
-
"@opra/
|
|
30
|
+
"@opra/exception": "^0.3.0",
|
|
31
|
+
"@opra/i18n": "^0.3.0",
|
|
32
|
+
"@opra/optionals": "^0.3.0",
|
|
33
|
+
"@opra/schema": "^0.3.0",
|
|
34
|
+
"@opra/url": "^0.3.0",
|
|
35
|
+
"@nano-sql/core": "^2.3.7",
|
|
36
|
+
"express": "^4.18.1",
|
|
37
|
+
"body-parser": "^1.20.1",
|
|
34
38
|
"lodash": "^4.17.21",
|
|
35
39
|
"putil-isplainobject": "^1.1.4",
|
|
36
40
|
"putil-varhelpers": "^1.6.4",
|
|
37
41
|
"putil-merge": "^3.9.0",
|
|
38
|
-
"rule-judgment": "^1.1.5",
|
|
39
42
|
"strict-typed-events": "^2.2.0",
|
|
40
43
|
"ts-gems": "^2.2.0"
|
|
41
44
|
},
|
|
42
45
|
"devDependencies": {
|
|
43
46
|
"@types/express": "^4.17.14",
|
|
44
|
-
"
|
|
47
|
+
"@faker-js/faker": "^7.5.0"
|
|
45
48
|
},
|
|
46
49
|
"type": "module",
|
|
47
50
|
"types": "esm/index.d.ts",
|