@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
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { HeadersMap, HttpHeaders, HttpStatus } from '@opra/common';
|
|
2
|
+
import { BadRequestError, InternalServerError, IssueSeverity, MethodNotAllowedError, NotFoundError, OpraException, wrapException } from '@opra/exception';
|
|
3
|
+
import { CollectionCreateQuery, CollectionDeleteManyQuery, CollectionDeleteQuery, CollectionGetQuery, CollectionResourceInfo, CollectionSearchQuery, CollectionUpdateManyQuery, CollectionUpdateQuery, ComplexType, ContainerResourceInfo, FieldGetQuery, OpraSchema, SingletonGetQuery, SingletonResourceInfo, UnionType, } from '@opra/schema';
|
|
4
|
+
import { OpraURL } from '@opra/url';
|
|
5
|
+
import { OpraAdapter } from './adapter.js';
|
|
6
|
+
import { QueryContext } from './query-context.js';
|
|
7
|
+
export class OpraHttpAdapter extends OpraAdapter {
|
|
8
|
+
prepareRequests(executionContext) {
|
|
9
|
+
const req = executionContext.getRequestWrapper();
|
|
10
|
+
// todo implement batch requests
|
|
11
|
+
if (this.isBatch(executionContext)) {
|
|
12
|
+
throw new Error('not implemented yet');
|
|
13
|
+
}
|
|
14
|
+
const url = new OpraURL(req.getUrl());
|
|
15
|
+
return [
|
|
16
|
+
this.prepareRequest(executionContext, url, req.getMethod(), new HeadersMap(req.getHeaders()), req.getBody())
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
prepareRequest(executionContext, url, method, headers, body) {
|
|
20
|
+
if (!url.path.size)
|
|
21
|
+
throw new BadRequestError();
|
|
22
|
+
if (method !== 'GET' && url.path.size > 1)
|
|
23
|
+
throw new BadRequestError();
|
|
24
|
+
const query = this.buildQuery(url, method, body);
|
|
25
|
+
if (!query)
|
|
26
|
+
throw new MethodNotAllowedError({
|
|
27
|
+
message: `Method "${method}" is not allowed by target endpoint`
|
|
28
|
+
});
|
|
29
|
+
return new QueryContext({
|
|
30
|
+
service: this.document,
|
|
31
|
+
executionContext,
|
|
32
|
+
query,
|
|
33
|
+
headers: new HeadersMap(),
|
|
34
|
+
params: url.searchParams,
|
|
35
|
+
continueOnError: query.operation === 'read'
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
buildQuery(url, method, body) {
|
|
39
|
+
const pathLen = url.path.size;
|
|
40
|
+
let p = url.path.get(0);
|
|
41
|
+
let resource = this._internalResources.get(p.resource) || this.document.getResource(p.resource);
|
|
42
|
+
let container;
|
|
43
|
+
let pathIndex = 0;
|
|
44
|
+
while (resource && resource instanceof ContainerResourceInfo) {
|
|
45
|
+
container = resource;
|
|
46
|
+
p = url.path.get(++pathIndex);
|
|
47
|
+
resource = container.getResource(p.resource);
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
method = method.toUpperCase();
|
|
51
|
+
let query;
|
|
52
|
+
if (resource instanceof SingletonResourceInfo && !p.key) {
|
|
53
|
+
switch (method) {
|
|
54
|
+
case 'GET': {
|
|
55
|
+
query = new SingletonGetQuery(resource);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else if (resource instanceof CollectionResourceInfo) {
|
|
60
|
+
switch (method) {
|
|
61
|
+
case 'GET': {
|
|
62
|
+
if (p.key) {
|
|
63
|
+
const searchParams = url.searchParams;
|
|
64
|
+
query = new CollectionGetQuery(resource, p.key, {
|
|
65
|
+
pick: searchParams.get('$pick'),
|
|
66
|
+
omit: searchParams.get('$omit'),
|
|
67
|
+
include: searchParams.get('$include')
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
const searchParams = url.searchParams;
|
|
72
|
+
query = new CollectionSearchQuery(resource, {
|
|
73
|
+
filter: searchParams.get('$filter'),
|
|
74
|
+
limit: searchParams.get('$limit'),
|
|
75
|
+
skip: searchParams.get('$skip'),
|
|
76
|
+
distinct: searchParams.get('$distinct'),
|
|
77
|
+
count: searchParams.get('$count'),
|
|
78
|
+
sort: searchParams.get('$sort'),
|
|
79
|
+
pick: searchParams.get('$pick'),
|
|
80
|
+
omit: searchParams.get('$omit'),
|
|
81
|
+
include: searchParams.get('$include')
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case 'DELETE': {
|
|
87
|
+
const searchParams = url.searchParams;
|
|
88
|
+
query = p.key
|
|
89
|
+
? new CollectionDeleteQuery(resource, p.key)
|
|
90
|
+
: new CollectionDeleteManyQuery(resource, {
|
|
91
|
+
filter: searchParams.get('$filter'),
|
|
92
|
+
});
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case 'POST': {
|
|
96
|
+
if (!p.key) {
|
|
97
|
+
const searchParams = url.searchParams;
|
|
98
|
+
query = new CollectionCreateQuery(resource, body, {
|
|
99
|
+
pick: searchParams.get('$pick'),
|
|
100
|
+
omit: searchParams.get('$omit'),
|
|
101
|
+
include: searchParams.get('$include')
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
case 'PATCH': {
|
|
107
|
+
if (p.key) {
|
|
108
|
+
const searchParams = url.searchParams;
|
|
109
|
+
query = new CollectionUpdateQuery(resource, p.key, body, {
|
|
110
|
+
pick: searchParams.get('$pick'),
|
|
111
|
+
omit: searchParams.get('$omit'),
|
|
112
|
+
include: searchParams.get('$include')
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
const searchParams = url.searchParams;
|
|
117
|
+
query = new CollectionUpdateManyQuery(resource, body, {
|
|
118
|
+
filter: searchParams.get('$filter')
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else
|
|
126
|
+
throw new InternalServerError();
|
|
127
|
+
if (query instanceof SingletonGetQuery || query instanceof CollectionGetQuery || query instanceof FieldGetQuery) {
|
|
128
|
+
// Move through properties
|
|
129
|
+
let parentType;
|
|
130
|
+
const curPath = [];
|
|
131
|
+
let parent = query;
|
|
132
|
+
while (++pathIndex < pathLen) {
|
|
133
|
+
p = url.path.get(pathIndex);
|
|
134
|
+
parentType = parent.dataType;
|
|
135
|
+
if (parent.dataType instanceof UnionType) {
|
|
136
|
+
if (parent.dataType.name === 'any')
|
|
137
|
+
parentType = this.document.getComplexDataType('object');
|
|
138
|
+
else
|
|
139
|
+
throw new TypeError(`"${resource.name}.${curPath.join()}" is a UnionType and needs type casting.`);
|
|
140
|
+
}
|
|
141
|
+
if (!(parentType instanceof ComplexType))
|
|
142
|
+
throw new TypeError(`"${resource.name}.${curPath.join()}" is not a ComplexType and has no fields.`);
|
|
143
|
+
curPath.push(p.resource);
|
|
144
|
+
parent.child = new FieldGetQuery(parent, p.resource, { castingType: parentType });
|
|
145
|
+
parent = parent.child;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return query;
|
|
149
|
+
}
|
|
150
|
+
catch (e) {
|
|
151
|
+
if (e instanceof OpraException)
|
|
152
|
+
throw e;
|
|
153
|
+
throw new BadRequestError(e);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async sendResponse(executionContext, queryContexts) {
|
|
157
|
+
const outputPackets = [];
|
|
158
|
+
for (const ctx of queryContexts) {
|
|
159
|
+
const v = this.createOutput(ctx);
|
|
160
|
+
outputPackets.push(v);
|
|
161
|
+
}
|
|
162
|
+
if (this.isBatch(executionContext)) {
|
|
163
|
+
// this.writeError([], new InternalServerError({message: 'Not implemented yet'}));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (!outputPackets.length)
|
|
167
|
+
return this.sendError(executionContext, new NotFoundError());
|
|
168
|
+
const out = outputPackets[0];
|
|
169
|
+
const resp = executionContext.getResponseWrapper();
|
|
170
|
+
resp.setStatus(out.status);
|
|
171
|
+
resp.setHeader(HttpHeaders.Content_Type, 'application/json');
|
|
172
|
+
resp.setHeader(HttpHeaders.Cache_Control, 'no-cache');
|
|
173
|
+
resp.setHeader(HttpHeaders.Pragma, 'no-cache');
|
|
174
|
+
resp.setHeader(HttpHeaders.Expires, '-1');
|
|
175
|
+
resp.setHeader(HttpHeaders.X_Opra_Version, OpraSchema.Version);
|
|
176
|
+
if (out.headers) {
|
|
177
|
+
for (const [k, v] of Object.entries(out.headers)) {
|
|
178
|
+
resp.setHeader(k, v);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
resp.send(JSON.stringify(out.body));
|
|
182
|
+
resp.end();
|
|
183
|
+
}
|
|
184
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
185
|
+
isBatch(executionContext) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
createOutput(ctx) {
|
|
189
|
+
const { query } = ctx;
|
|
190
|
+
let body;
|
|
191
|
+
let status = ctx.status || 0;
|
|
192
|
+
const errors = ctx.errors.map(e => wrapException(e));
|
|
193
|
+
if (errors && errors.length) {
|
|
194
|
+
// Sort errors from fatal to info
|
|
195
|
+
errors.sort((a, b) => {
|
|
196
|
+
const i = IssueSeverity.Keys.indexOf(a.issue.severity) - IssueSeverity.Keys.indexOf(b.issue.severity);
|
|
197
|
+
if (i === 0)
|
|
198
|
+
return b.status - a.status;
|
|
199
|
+
return i;
|
|
200
|
+
});
|
|
201
|
+
if (!status || status < HttpStatus.BAD_REQUEST) {
|
|
202
|
+
status = errors[0].status;
|
|
203
|
+
if (status < HttpStatus.BAD_REQUEST)
|
|
204
|
+
status = HttpStatus.INTERNAL_SERVER_ERROR;
|
|
205
|
+
}
|
|
206
|
+
body = {
|
|
207
|
+
operation: ctx.query.method,
|
|
208
|
+
errors: errors.map(e => e.issue)
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
body = ctx.response;
|
|
213
|
+
status = status || (query.operation === 'create' ? HttpStatus.CREATED : HttpStatus.OK);
|
|
214
|
+
}
|
|
215
|
+
body = this.i18n.deep(body);
|
|
216
|
+
return {
|
|
217
|
+
status,
|
|
218
|
+
headers: ctx.responseHeaders.toObject(),
|
|
219
|
+
body
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
async sendError(executionContext, error) {
|
|
223
|
+
const resp = executionContext.getResponseWrapper();
|
|
224
|
+
resp.setStatus(error.status || 500);
|
|
225
|
+
resp.setHeader(HttpHeaders.Content_Type, 'application/json');
|
|
226
|
+
resp.setHeader(HttpHeaders.Cache_Control, 'no-cache');
|
|
227
|
+
resp.setHeader(HttpHeaders.Pragma, 'no-cache');
|
|
228
|
+
resp.setHeader(HttpHeaders.Expires, '-1');
|
|
229
|
+
resp.setHeader(HttpHeaders.X_Opra_Version, OpraSchema.Version);
|
|
230
|
+
const issue = this.i18n.deep(error.issue);
|
|
231
|
+
const body = {
|
|
232
|
+
operation: 'unknown',
|
|
233
|
+
errors: [issue]
|
|
234
|
+
};
|
|
235
|
+
resp.send(JSON.stringify(body));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
@@ -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,14 @@
|
|
|
1
|
+
import { HeadersMap, HttpStatus } from '@opra/common';
|
|
1
2
|
import { OpraException } from '@opra/exception';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { HttpStatus } from '../enums/index.js';
|
|
3
|
+
import { OpraDocument, OpraQuery } from '@opra/schema';
|
|
4
|
+
import { OpraURLSearchParams } from '@opra/url';
|
|
5
5
|
import { ContextType, IExecutionContext, IHttpExecutionContext } from '../interfaces/execution-context.interface.js';
|
|
6
|
-
import { HeadersMap } from './headers-map.js';
|
|
7
6
|
export declare type QueryContextArgs = Pick<QueryContext, 'service' | 'executionContext' | 'query' | 'params' | 'headers' | 'userContext' | 'parentValue' | 'continueOnError'>;
|
|
8
7
|
export declare class QueryContext {
|
|
9
|
-
readonly service:
|
|
8
|
+
readonly service: OpraDocument;
|
|
10
9
|
readonly executionContext: IExecutionContext;
|
|
11
|
-
readonly query:
|
|
12
|
-
readonly params:
|
|
10
|
+
readonly query: OpraQuery;
|
|
11
|
+
readonly params: OpraURLSearchParams;
|
|
13
12
|
readonly headers: HeadersMap;
|
|
14
13
|
readonly parentValue?: any;
|
|
15
14
|
readonly resultPath: string;
|
package/esm/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
2
|
export * from './types.js';
|
|
3
|
-
export * from './enums/index.js';
|
|
4
3
|
export * from './interfaces/execution-context.interface.js';
|
|
5
|
-
export * from './interfaces/
|
|
6
|
-
export * from './
|
|
7
|
-
export * from './
|
|
8
|
-
export * from './
|
|
9
|
-
export * from './
|
|
4
|
+
export * from './interfaces/resource.interface.js';
|
|
5
|
+
export * from './adapter/query-context.js';
|
|
6
|
+
export * from './adapter/adapter.js';
|
|
7
|
+
export * from './adapter/http-adapter.js';
|
|
8
|
+
export * from './adapter/express-adapter.js';
|
|
10
9
|
export * from './services/data-service.js';
|
|
11
10
|
export * from './services/json-collection-service.js';
|
|
11
|
+
export * from './services/json-singleton-service.js';
|
package/esm/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
2
|
export * from './types.js';
|
|
3
|
-
export * from './enums/index.js';
|
|
4
3
|
export * from './interfaces/execution-context.interface.js';
|
|
5
|
-
export * from './interfaces/
|
|
6
|
-
export * from './
|
|
7
|
-
export * from './
|
|
8
|
-
export * from './
|
|
9
|
-
export * from './
|
|
4
|
+
export * from './interfaces/resource.interface.js';
|
|
5
|
+
export * from './adapter/query-context.js';
|
|
6
|
+
export * from './adapter/adapter.js';
|
|
7
|
+
export * from './adapter/http-adapter.js';
|
|
8
|
+
export * from './adapter/express-adapter.js';
|
|
10
9
|
export * from './services/data-service.js';
|
|
11
10
|
export * from './services/json-collection-service.js';
|
|
11
|
+
export * from './services/json-singleton-service.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Maybe } from 'ts-gems';
|
|
2
|
+
import { ResourceInfo } from '@opra/schema';
|
|
3
|
+
import { PartialOutput } from '../types.js';
|
|
4
|
+
export interface IResource {
|
|
5
|
+
init?(resource: ResourceInfo): void | Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
export interface ICollectionResource<T, TOutput = PartialOutput<T>> extends IResource {
|
|
8
|
+
create?(...args: any[]): TOutput | Promise<TOutput>;
|
|
9
|
+
count?(...args: any[]): number | Promise<number>;
|
|
10
|
+
delete?(...args: any[]): boolean | Promise<boolean>;
|
|
11
|
+
deleteMany?(...args: any[]): number | Promise<number>;
|
|
12
|
+
get?(...args: any[]): Maybe<TOutput> | Promise<Maybe<TOutput>>;
|
|
13
|
+
search?(...args: any[]): TOutput[] | Promise<TOutput[]>;
|
|
14
|
+
update?(...args: any[]): Maybe<TOutput> | Promise<Maybe<TOutput>>;
|
|
15
|
+
updateMany?(...args: any[]): number | Promise<number>;
|
|
16
|
+
}
|
|
17
|
+
export interface ISingletonResource<T, TOutput = PartialOutput<T>> extends IResource {
|
|
18
|
+
create?(...args: any[]): TOutput | Promise<TOutput>;
|
|
19
|
+
delete?(...args: any[]): boolean | Promise<boolean>;
|
|
20
|
+
get?(...args: any[]): Maybe<TOutput> | Promise<Maybe<TOutput>>;
|
|
21
|
+
update?(...args: any[]): Maybe<TOutput> | Promise<Maybe<TOutput>>;
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
import { Maybe } from 'ts-gems';
|
|
2
|
-
import {
|
|
2
|
+
import { CollectionResourceInfo, ComplexType, OpraQuery, OpraSchema } from '@opra/schema';
|
|
3
3
|
import { Expression } from '@opra/url';
|
|
4
|
-
import { QueryContext } from '../
|
|
5
|
-
import {
|
|
6
|
-
import { EntityInput, EntityOutput } from '../types.js';
|
|
4
|
+
import { QueryContext } from '../adapter/query-context.js';
|
|
5
|
+
import { PartialInput, PartialOutput } from '../types.js';
|
|
7
6
|
export interface JsonCollectionServiceOptions {
|
|
8
7
|
resourceName?: string;
|
|
9
8
|
defaultLimit?: number;
|
|
10
9
|
data?: any[];
|
|
11
10
|
}
|
|
12
|
-
export declare class JsonCollectionService<T, TOutput =
|
|
13
|
-
readonly resource:
|
|
11
|
+
export declare class JsonCollectionService<T, TOutput = PartialOutput<T>> {
|
|
12
|
+
readonly resource: CollectionResourceInfo;
|
|
14
13
|
private _status;
|
|
15
14
|
private _initError;
|
|
16
15
|
private _dbName;
|
|
17
16
|
private _initData?;
|
|
18
17
|
defaultLimit: number;
|
|
19
|
-
constructor(resource:
|
|
18
|
+
constructor(resource: CollectionResourceInfo, options?: JsonCollectionServiceOptions);
|
|
20
19
|
get dataType(): ComplexType;
|
|
21
20
|
get primaryKey(): string;
|
|
22
21
|
get resourceName(): string;
|
|
@@ -25,15 +24,15 @@ export declare class JsonCollectionService<T, TOutput = EntityOutput<T>> impleme
|
|
|
25
24
|
get(keyValue: any, options?: JsonCollectionService.GetOptions): Promise<Maybe<TOutput>>;
|
|
26
25
|
count(options?: JsonCollectionService.SearchOptions): Promise<number>;
|
|
27
26
|
search(options?: JsonCollectionService.SearchOptions): Promise<TOutput[]>;
|
|
28
|
-
create(data:
|
|
29
|
-
update(keyValue: any, data:
|
|
30
|
-
updateMany(data:
|
|
27
|
+
create(data: PartialInput<T>, options?: JsonCollectionService.CreateOptions): Promise<TOutput>;
|
|
28
|
+
update(keyValue: any, data: PartialInput<T>, options?: JsonCollectionService.UpdateOptions): Promise<Maybe<TOutput>>;
|
|
29
|
+
updateMany(data: PartialInput<T>, options?: JsonCollectionService.UpdateManyOptions): Promise<number>;
|
|
31
30
|
delete(keyValue: any): Promise<boolean>;
|
|
32
31
|
deleteMany(options?: JsonCollectionService.DeleteManyOptions): Promise<number>;
|
|
33
32
|
private _waitInitializing;
|
|
34
33
|
protected _init(): Promise<void>;
|
|
35
|
-
protected _prepare(query:
|
|
36
|
-
method: OpraSchema.
|
|
34
|
+
protected _prepare(query: OpraQuery): {
|
|
35
|
+
method: OpraSchema.CollectionMethod;
|
|
37
36
|
options: any;
|
|
38
37
|
keyValue?: any;
|
|
39
38
|
values?: any;
|
|
@@ -2,7 +2,7 @@ import _ from 'lodash';
|
|
|
2
2
|
import merge from 'putil-merge';
|
|
3
3
|
import { nSQL } from "@nano-sql/core";
|
|
4
4
|
import { BadRequestError, MethodNotAllowedError, ResourceConflictError } from '@opra/exception';
|
|
5
|
-
import {
|
|
5
|
+
import { CollectionResourceInfo, ComplexType } from '@opra/schema';
|
|
6
6
|
import { $parse, ArrayExpression, BooleanLiteral, ComparisonExpression, DateLiteral, Expression, LogicalExpression, NullLiteral, NumberLiteral, ParenthesesExpression, QualifiedIdentifier, StringLiteral, TimeLiteral } from '@opra/url';
|
|
7
7
|
import { pathToTree } from '../utils/path-to-tree.js';
|
|
8
8
|
let dbId = 1;
|
|
@@ -191,7 +191,7 @@ export class JsonCollectionService {
|
|
|
191
191
|
indexes: {}
|
|
192
192
|
};
|
|
193
193
|
for (const [k, f] of this.resource.dataType.fields.entries()) {
|
|
194
|
-
const fieldType = this.resource.
|
|
194
|
+
const fieldType = this.resource.document.getDataType(f.type || 'string');
|
|
195
195
|
const o = table.model[k + ':' + dataTypeToSQLType(fieldType, !!f.isArray)] = {};
|
|
196
196
|
if (k === this.primaryKey)
|
|
197
197
|
o.pk = true;
|
|
@@ -199,21 +199,21 @@ export class JsonCollectionService {
|
|
|
199
199
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
200
200
|
const indexes = table.indexes;
|
|
201
201
|
// Add indexes for sort fields
|
|
202
|
-
const
|
|
203
|
-
if (
|
|
204
|
-
if (
|
|
205
|
-
|
|
202
|
+
const searchResolver = this.resource.metadata.search;
|
|
203
|
+
if (searchResolver) {
|
|
204
|
+
if (searchResolver.sortFields) {
|
|
205
|
+
searchResolver.sortFields.forEach(fieldName => {
|
|
206
206
|
const f = this.dataType.getField(fieldName);
|
|
207
|
-
const fieldType = this.resource.
|
|
207
|
+
const fieldType = this.resource.document.getDataType(f.type || 'string');
|
|
208
208
|
const t = dataTypeToSQLType(fieldType, !!f.isArray);
|
|
209
209
|
if (indexingTypes.includes(t))
|
|
210
210
|
indexes[fieldName + ':' + t] = {};
|
|
211
211
|
});
|
|
212
212
|
}
|
|
213
|
-
if (
|
|
214
|
-
|
|
213
|
+
if (searchResolver.filters) {
|
|
214
|
+
searchResolver.filters.forEach(filter => {
|
|
215
215
|
const f = this.dataType.getField(filter.field);
|
|
216
|
-
const fieldType = this.resource.
|
|
216
|
+
const fieldType = this.resource.document.getDataType(f.type || 'string');
|
|
217
217
|
const t = dataTypeToSQLType(fieldType, !!f.isArray);
|
|
218
218
|
if (indexingTypes.includes(t))
|
|
219
219
|
indexes[filter.field + ':' + t] = {};
|
|
@@ -241,10 +241,10 @@ export class JsonCollectionService {
|
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
243
|
_prepare(query) {
|
|
244
|
-
if (query.resource instanceof
|
|
244
|
+
if (query.resource instanceof CollectionResourceInfo) {
|
|
245
245
|
if (query.dataType !== this.dataType)
|
|
246
246
|
throw new TypeError(`Query data type (${query.dataType.name}) ` +
|
|
247
|
-
`differs from
|
|
247
|
+
`differs from JsonCollectionService data type (${this.dataType.name})`);
|
|
248
248
|
}
|
|
249
249
|
switch (query.method) {
|
|
250
250
|
case 'count': {
|
|
@@ -272,7 +272,7 @@ export class JsonCollectionService {
|
|
|
272
272
|
};
|
|
273
273
|
}
|
|
274
274
|
case 'get': {
|
|
275
|
-
if (query.kind === '
|
|
275
|
+
if (query.kind === 'CollectionGetQuery') {
|
|
276
276
|
const options = _.omitBy({
|
|
277
277
|
pick: query.pick,
|
|
278
278
|
omit: query.omit,
|
|
@@ -286,7 +286,7 @@ export class JsonCollectionService {
|
|
|
286
286
|
args: [keyValue, options]
|
|
287
287
|
};
|
|
288
288
|
}
|
|
289
|
-
if (query.kind === '
|
|
289
|
+
if (query.kind === 'FieldGetQuery') {
|
|
290
290
|
// todo
|
|
291
291
|
}
|
|
292
292
|
break;
|
|
@@ -365,7 +365,7 @@ export class JsonCollectionService {
|
|
|
365
365
|
}
|
|
366
366
|
_convertSelect(args) {
|
|
367
367
|
const result = [];
|
|
368
|
-
const document = this.dataType.
|
|
368
|
+
const document = this.dataType.document;
|
|
369
369
|
const processDataType = (dt, path, pick, omit, include) => {
|
|
370
370
|
let kl;
|
|
371
371
|
for (const [k, f] of dt.fields) {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { DataType, OpraQuery, OpraSchema } from '@opra/schema';
|
|
2
|
+
import { QueryContext } from '../adapter/query-context.js';
|
|
3
|
+
import { PartialOutput } from '../types.js';
|
|
4
|
+
export interface JsonSingletonServiceOptions<T> {
|
|
5
|
+
data: T;
|
|
6
|
+
resourceName?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class JsonSingletonService<T, TOutput = PartialOutput<T>> {
|
|
9
|
+
readonly dataType: DataType;
|
|
10
|
+
protected _data?: T;
|
|
11
|
+
constructor(dataType: DataType, options?: JsonSingletonServiceOptions<T>);
|
|
12
|
+
processRequest(ctx: QueryContext): Promise<any>;
|
|
13
|
+
get(options?: JsonSingletonService.GetOptions): TOutput | undefined;
|
|
14
|
+
protected _prepare(query: OpraQuery): {
|
|
15
|
+
method: OpraSchema.SingletonMethod;
|
|
16
|
+
options: any;
|
|
17
|
+
keyValue?: any;
|
|
18
|
+
values?: any;
|
|
19
|
+
args: any[];
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export declare namespace JsonSingletonService {
|
|
23
|
+
type CreateOptions = {
|
|
24
|
+
query?: string;
|
|
25
|
+
pick?: string[];
|
|
26
|
+
omit?: string[];
|
|
27
|
+
include?: string[];
|
|
28
|
+
};
|
|
29
|
+
type GetOptions = {
|
|
30
|
+
pick?: string[];
|
|
31
|
+
omit?: string[];
|
|
32
|
+
include?: string[];
|
|
33
|
+
};
|
|
34
|
+
type UpdateOptions = {
|
|
35
|
+
pick?: string[];
|
|
36
|
+
omit?: string[];
|
|
37
|
+
include?: string[];
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import { SingletonResourceInfo } from '@opra/schema';
|
|
3
|
+
export class JsonSingletonService {
|
|
4
|
+
dataType;
|
|
5
|
+
_data;
|
|
6
|
+
constructor(dataType, options) {
|
|
7
|
+
this.dataType = dataType;
|
|
8
|
+
this._data = options?.data;
|
|
9
|
+
}
|
|
10
|
+
async processRequest(ctx) {
|
|
11
|
+
const prepared = this._prepare(ctx.query);
|
|
12
|
+
const fn = this[prepared.method];
|
|
13
|
+
if (!fn)
|
|
14
|
+
throw new TypeError(`Unimplemented method (${prepared.method})`);
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
return fn.apply(this, prepared.args);
|
|
17
|
+
}
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
19
|
+
get(options) {
|
|
20
|
+
return this._data;
|
|
21
|
+
}
|
|
22
|
+
_prepare(query) {
|
|
23
|
+
if (query.resource instanceof SingletonResourceInfo) {
|
|
24
|
+
if (query.dataType !== this.dataType)
|
|
25
|
+
throw new TypeError(`Query data type (${query.dataType.name}) ` +
|
|
26
|
+
`differs from JsonCollectionService data type (${this.dataType.name})`);
|
|
27
|
+
}
|
|
28
|
+
switch (query.method) {
|
|
29
|
+
case 'create': {
|
|
30
|
+
const options = _.omitBy({
|
|
31
|
+
pick: query.pick,
|
|
32
|
+
omit: query.omit,
|
|
33
|
+
include: query.include
|
|
34
|
+
}, _.isNil);
|
|
35
|
+
const { data } = query;
|
|
36
|
+
return {
|
|
37
|
+
method: query.method,
|
|
38
|
+
values: data,
|
|
39
|
+
options,
|
|
40
|
+
args: [data, options]
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
case 'get': {
|
|
44
|
+
if (query.kind === 'CollectionGetQuery') {
|
|
45
|
+
const options = _.omitBy({
|
|
46
|
+
pick: query.pick,
|
|
47
|
+
omit: query.omit,
|
|
48
|
+
include: query.include
|
|
49
|
+
}, _.isNil);
|
|
50
|
+
const keyValue = query.keyValue;
|
|
51
|
+
return {
|
|
52
|
+
method: query.method,
|
|
53
|
+
keyValue,
|
|
54
|
+
options,
|
|
55
|
+
args: [keyValue, options]
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (query.kind === 'FieldGetQuery') {
|
|
59
|
+
// todo
|
|
60
|
+
}
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
case 'update': {
|
|
64
|
+
const options = _.omitBy({
|
|
65
|
+
pick: query.pick,
|
|
66
|
+
omit: query.omit,
|
|
67
|
+
include: query.include
|
|
68
|
+
}, _.isNil);
|
|
69
|
+
const { data } = query;
|
|
70
|
+
const keyValue = query.keyValue;
|
|
71
|
+
return {
|
|
72
|
+
method: query.method,
|
|
73
|
+
keyValue: query.keyValue,
|
|
74
|
+
values: data,
|
|
75
|
+
options,
|
|
76
|
+
args: [keyValue, data, options]
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
case 'delete': {
|
|
80
|
+
const options = {};
|
|
81
|
+
const keyValue = query.keyValue;
|
|
82
|
+
return {
|
|
83
|
+
method: query.method,
|
|
84
|
+
keyValue,
|
|
85
|
+
options,
|
|
86
|
+
args: [keyValue, options]
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
throw new Error(`Unimplemented query type "${query.method}"`);
|
|
91
|
+
}
|
|
92
|
+
}
|
package/esm/types.d.ts
CHANGED
|
@@ -1,8 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export
|
|
3
|
-
export declare type EntityOutput<T> = DeepNullableIfPartial<T>;
|
|
4
|
-
export declare type DeepNullableIfPartial<T> = _DeepNullableIfPartial<T>;
|
|
5
|
-
declare type _DeepNullableIfPartial<T> = T extends Builtin ? T : T extends Promise<infer U> ? Promise<DeepNullableIfPartial<U>> : T extends (infer U)[] ? DeepNullableIfPartial<U>[] : {
|
|
6
|
-
[P in keyof T]?: DeepNullableIfPartial<Exclude<T[P], undefined>> | null;
|
|
7
|
-
};
|
|
8
|
-
export {};
|
|
1
|
+
import { PartialInput, PartialOutput } from '@opra/common';
|
|
2
|
+
export { PartialInput, PartialOutput };
|