@opra/core 0.23.1 → 0.24.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/{operation-context.js → endpoint-context.js} +5 -5
- package/cjs/adapter/http/http-adapter-base.js +2 -4
- package/cjs/adapter/http/request-handlers/entity-request-handler.js +82 -68
- package/cjs/adapter/http/request-handlers/request-handler-base.js +5 -5
- package/cjs/adapter/http/request-handlers/storage-request-handler.js +16 -17
- package/cjs/adapter/platform-adapter.host.js +1 -1
- package/cjs/augmentation/resource.augmentation.js +6 -6
- package/cjs/index.js +1 -1
- package/esm/adapter/{operation-context.js → endpoint-context.js} +4 -4
- package/esm/adapter/http/http-adapter-base.js +2 -4
- package/esm/adapter/http/request-handlers/entity-request-handler.js +82 -68
- package/esm/adapter/http/request-handlers/request-handler-base.js +5 -5
- package/esm/adapter/http/request-handlers/storage-request-handler.js +16 -17
- package/esm/adapter/platform-adapter.host.js +1 -1
- package/esm/augmentation/resource.augmentation.js +7 -7
- package/esm/index.js +1 -1
- package/i18n/en/error.json +3 -2
- package/package.json +2 -2
- package/types/adapter/{operation-context.d.ts → endpoint-context.d.ts} +3 -3
- package/types/adapter/http/request-handlers/entity-request-handler.d.ts +4 -4
- package/types/adapter/http/request-handlers/request-handler-base.d.ts +1 -1
- package/types/adapter/http/request-handlers/storage-request-handler.d.ts +3 -3
- package/types/adapter/platform-adapter.d.ts +2 -2
- package/types/adapter/request.d.ts +1 -1
- package/types/adapter/response.d.ts +1 -1
- package/types/augmentation/collection.augmentation.d.ts +21 -16
- package/types/augmentation/resource.augmentation.d.ts +6 -1
- package/types/augmentation/singleton.augmentation.d.ts +14 -10
- package/types/augmentation/storage.augmentation.d.ts +9 -5
- package/types/index.d.ts +1 -1
|
@@ -2,7 +2,7 @@ import bodyParser from 'body-parser';
|
|
|
2
2
|
import { toBoolean, toInt } from 'putil-varhelpers';
|
|
3
3
|
import * as valgen from 'valgen';
|
|
4
4
|
import { BadRequestError, Collection, HttpHeaderCodes, HttpStatusCodes, InternalServerError, MethodNotAllowedError, OpraException, ResourceNotFoundError, Singleton, translate, } from '@opra/common';
|
|
5
|
-
import {
|
|
5
|
+
import { EndpointContext } from '../../endpoint-context.js';
|
|
6
6
|
import { RequestHost } from '../../request.host.js';
|
|
7
7
|
import { ResponseHost } from '../../response.host.js';
|
|
8
8
|
import { parseArrayParam } from '../helpers/query-parsers.js';
|
|
@@ -23,9 +23,8 @@ export class EntityRequestHandler extends RequestHandlerBase {
|
|
|
23
23
|
if (!request)
|
|
24
24
|
return;
|
|
25
25
|
const response = new ResponseHost({ http: outgoing });
|
|
26
|
-
const context =
|
|
27
|
-
|
|
28
|
-
await this.executeOperation(context);
|
|
26
|
+
const context = EndpointContext.from(executionContext, request, response);
|
|
27
|
+
await this.callEndpoint(context);
|
|
29
28
|
if (response.errors.length) {
|
|
30
29
|
context.errors.push(...response.errors);
|
|
31
30
|
return;
|
|
@@ -48,7 +47,15 @@ export class EntityRequestHandler extends RequestHandlerBase {
|
|
|
48
47
|
}
|
|
49
48
|
async parseRequest(incoming) {
|
|
50
49
|
const p = incoming.parsedUrl.path[0];
|
|
51
|
-
const resource = this.adapter.api.getResource(p.resource);
|
|
50
|
+
const resource = this.adapter.api.getResource(p.resource, true);
|
|
51
|
+
if (!resource)
|
|
52
|
+
throw new BadRequestError({
|
|
53
|
+
message: translate('error:SOURCE_NOT_FOUND,', 'Resource not found'),
|
|
54
|
+
code: 'SOURCE_NOT_FOUND',
|
|
55
|
+
details: {
|
|
56
|
+
path: incoming.parsedUrl.address
|
|
57
|
+
}
|
|
58
|
+
});
|
|
52
59
|
try {
|
|
53
60
|
if (resource instanceof Collection)
|
|
54
61
|
return await this.parseCollectionRequest(resource, incoming);
|
|
@@ -68,18 +75,18 @@ export class EntityRequestHandler extends RequestHandlerBase {
|
|
|
68
75
|
throw new BadRequestError(e);
|
|
69
76
|
}
|
|
70
77
|
}
|
|
71
|
-
async
|
|
78
|
+
async callEndpoint(context) {
|
|
72
79
|
const request = context.request;
|
|
73
80
|
const { response } = context;
|
|
74
81
|
const resource = request.resource;
|
|
75
|
-
// Call
|
|
82
|
+
// Call endpoint handler method
|
|
76
83
|
let value;
|
|
77
84
|
try {
|
|
78
|
-
value = await request.controller[request.
|
|
85
|
+
value = await request.controller[request.endpoint].call(request.controller, context);
|
|
79
86
|
if (value == null)
|
|
80
87
|
value = response.value;
|
|
81
|
-
const {
|
|
82
|
-
if (
|
|
88
|
+
const { endpoint } = request;
|
|
89
|
+
if (endpoint === 'delete' || endpoint === 'deleteMany' || endpoint === 'updateMany') {
|
|
83
90
|
let affected = 0;
|
|
84
91
|
if (typeof value === 'number')
|
|
85
92
|
affected = value;
|
|
@@ -87,15 +94,15 @@ export class EntityRequestHandler extends RequestHandlerBase {
|
|
|
87
94
|
affected = value ? 1 : 0;
|
|
88
95
|
if (typeof value === 'object')
|
|
89
96
|
affected = value.affected || value.affectedRows ||
|
|
90
|
-
(
|
|
97
|
+
(endpoint === 'updateMany' ? value.updated : value.deleted);
|
|
91
98
|
response.value = affected;
|
|
92
99
|
}
|
|
93
100
|
else {
|
|
94
|
-
// "get" and "update"
|
|
95
|
-
if (value == null && (request.
|
|
101
|
+
// "get" and "update" endpoints must return the entity instance, otherwise it means resource not found
|
|
102
|
+
if (value == null && (request.endpoint === 'get' || request.endpoint === 'update'))
|
|
96
103
|
throw new ResourceNotFoundError(resource.name, request.key);
|
|
97
|
-
// "findMany"
|
|
98
|
-
if (request.
|
|
104
|
+
// "findMany" endpoint should return array of entity instances
|
|
105
|
+
if (request.endpoint === 'findMany')
|
|
99
106
|
value = value == null ? [] : Array.isArray(value) ? value : [value];
|
|
100
107
|
else
|
|
101
108
|
value = value == null ? {} : Array.isArray(value) ? value[0] : value;
|
|
@@ -111,33 +118,40 @@ export class EntityRequestHandler extends RequestHandlerBase {
|
|
|
111
118
|
const resource = request.resource;
|
|
112
119
|
const outgoing = response.switchToHttp();
|
|
113
120
|
let responseObject;
|
|
114
|
-
if (request.
|
|
121
|
+
if (request.endpoint === 'delete' || request.endpoint === 'deleteMany') {
|
|
122
|
+
responseObject = {
|
|
123
|
+
resource: resource.name,
|
|
124
|
+
operation: 'delete',
|
|
125
|
+
affected: response.value
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
else if (request.endpoint === 'updateMany') {
|
|
115
129
|
responseObject = {
|
|
116
130
|
resource: resource.name,
|
|
117
|
-
operation:
|
|
131
|
+
operation: 'update',
|
|
118
132
|
affected: response.value
|
|
119
133
|
};
|
|
120
134
|
}
|
|
121
135
|
else {
|
|
122
136
|
if (!response.value)
|
|
123
|
-
throw new InternalServerError(`"${request.
|
|
124
|
-
const encode = resource.getEncoder(request.
|
|
137
|
+
throw new InternalServerError(`"${request.endpoint}" endpoint should return value`);
|
|
138
|
+
const encode = resource.getEncoder(request.endpoint);
|
|
125
139
|
const data = encode(response.value, { coerce: true });
|
|
126
|
-
if (request.
|
|
140
|
+
if (request.endpoint === 'create')
|
|
127
141
|
outgoing.statusCode = 201;
|
|
128
142
|
responseObject = {
|
|
129
143
|
resource: resource.name,
|
|
130
|
-
|
|
144
|
+
endpoint: request.endpoint,
|
|
131
145
|
data
|
|
132
146
|
};
|
|
133
|
-
if (request.
|
|
147
|
+
if (request.endpoint === 'create' || request.endpoint === 'update')
|
|
134
148
|
responseObject.affected = 1;
|
|
135
|
-
if (request.
|
|
149
|
+
if (request.endpoint === 'findMany' && response.count != null && response.count >= 0)
|
|
136
150
|
responseObject.totalCount = response.count;
|
|
137
151
|
}
|
|
138
152
|
outgoing.statusCode = outgoing.statusCode || HttpStatusCodes.OK;
|
|
139
153
|
const body = this.adapter._i18n.deep(responseObject);
|
|
140
|
-
outgoing.setHeader(HttpHeaderCodes.Content_Type, 'application/json; charset=utf-8');
|
|
154
|
+
outgoing.setHeader(HttpHeaderCodes.Content_Type, 'application/opra+json; charset=utf-8');
|
|
141
155
|
outgoing.send(JSON.stringify(body));
|
|
142
156
|
outgoing.end();
|
|
143
157
|
}
|
|
@@ -151,8 +165,8 @@ export class EntityRequestHandler extends RequestHandlerBase {
|
|
|
151
165
|
switch (incoming.method) {
|
|
152
166
|
case 'POST': {
|
|
153
167
|
if (p.key == null) {
|
|
154
|
-
const
|
|
155
|
-
const jsonReader = this.getBodyLoader(
|
|
168
|
+
const endpointMeta = await this.assertEndpoint(resource, 'create');
|
|
169
|
+
const jsonReader = this.getBodyLoader(endpointMeta);
|
|
156
170
|
const decode = resource.getDecoder('create');
|
|
157
171
|
let data = await jsonReader(incoming);
|
|
158
172
|
data = decode(data, { coerce: true });
|
|
@@ -160,11 +174,11 @@ export class EntityRequestHandler extends RequestHandlerBase {
|
|
|
160
174
|
const omit = parseArrayParam(params.get('$omit'));
|
|
161
175
|
const include = parseArrayParam(params.get('$include'));
|
|
162
176
|
return new RequestHost({
|
|
163
|
-
controller:
|
|
177
|
+
controller: endpointMeta.controller,
|
|
164
178
|
http: incoming,
|
|
165
179
|
contentId,
|
|
166
180
|
resource,
|
|
167
|
-
|
|
181
|
+
endpoint: 'create',
|
|
168
182
|
data,
|
|
169
183
|
params: {
|
|
170
184
|
pick: pick && resource.normalizeFieldPath(pick),
|
|
@@ -177,24 +191,24 @@ export class EntityRequestHandler extends RequestHandlerBase {
|
|
|
177
191
|
}
|
|
178
192
|
case 'DELETE': {
|
|
179
193
|
if (p.key != null) {
|
|
180
|
-
const
|
|
194
|
+
const endpointMeta = await this.assertEndpoint(resource, 'delete');
|
|
181
195
|
return new RequestHost({
|
|
182
|
-
controller:
|
|
196
|
+
controller: endpointMeta.controller,
|
|
183
197
|
http: incoming,
|
|
184
198
|
contentId,
|
|
185
199
|
resource,
|
|
186
|
-
|
|
200
|
+
endpoint: 'delete',
|
|
187
201
|
key: resource.parseKeyValue(p.key)
|
|
188
202
|
});
|
|
189
203
|
}
|
|
190
|
-
const
|
|
204
|
+
const endpointMeta = await this.assertEndpoint(resource, 'deleteMany');
|
|
191
205
|
const filter = resource.normalizeFilter(params.get('$filter'));
|
|
192
206
|
return new RequestHost({
|
|
193
|
-
controller:
|
|
207
|
+
controller: endpointMeta.controller,
|
|
194
208
|
http: incoming,
|
|
195
209
|
contentId,
|
|
196
210
|
resource,
|
|
197
|
-
|
|
211
|
+
endpoint: 'deleteMany',
|
|
198
212
|
params: {
|
|
199
213
|
filter
|
|
200
214
|
}
|
|
@@ -205,13 +219,13 @@ export class EntityRequestHandler extends RequestHandlerBase {
|
|
|
205
219
|
const omit = parseArrayParam(params.get('$omit'));
|
|
206
220
|
const include = parseArrayParam(params.get('$include'));
|
|
207
221
|
if (p.key != null) {
|
|
208
|
-
const
|
|
222
|
+
const endpointMeta = await this.assertEndpoint(resource, 'get');
|
|
209
223
|
return new RequestHost({
|
|
210
|
-
controller:
|
|
224
|
+
controller: endpointMeta.controller,
|
|
211
225
|
http: incoming,
|
|
212
226
|
contentId,
|
|
213
227
|
resource,
|
|
214
|
-
|
|
228
|
+
endpoint: 'get',
|
|
215
229
|
key: resource.parseKeyValue(p.key),
|
|
216
230
|
params: {
|
|
217
231
|
pick: pick && resource.normalizeFieldPath(pick),
|
|
@@ -220,15 +234,15 @@ export class EntityRequestHandler extends RequestHandlerBase {
|
|
|
220
234
|
}
|
|
221
235
|
});
|
|
222
236
|
}
|
|
223
|
-
const
|
|
237
|
+
const endpointMeta = await this.assertEndpoint(resource, 'findMany');
|
|
224
238
|
const filter = resource.normalizeFilter(params.get('$filter'));
|
|
225
239
|
const sort = parseArrayParam(params.get('$sort'));
|
|
226
240
|
return new RequestHost({
|
|
227
|
-
controller:
|
|
241
|
+
controller: endpointMeta.controller,
|
|
228
242
|
http: incoming,
|
|
229
243
|
contentId,
|
|
230
244
|
resource,
|
|
231
|
-
|
|
245
|
+
endpoint: 'findMany',
|
|
232
246
|
params: {
|
|
233
247
|
pick: pick && resource.normalizeFieldPath(pick),
|
|
234
248
|
omit: omit && resource.normalizeFieldPath(omit),
|
|
@@ -244,8 +258,8 @@ export class EntityRequestHandler extends RequestHandlerBase {
|
|
|
244
258
|
}
|
|
245
259
|
case 'PATCH': {
|
|
246
260
|
if (p.key != null) {
|
|
247
|
-
const
|
|
248
|
-
const jsonReader = this.getBodyLoader(
|
|
261
|
+
const endpointMeta = await this.assertEndpoint(resource, 'update');
|
|
262
|
+
const jsonReader = this.getBodyLoader(endpointMeta);
|
|
249
263
|
const decode = resource.getDecoder('update');
|
|
250
264
|
let data = await jsonReader(incoming);
|
|
251
265
|
data = decode(data, { coerce: true });
|
|
@@ -253,11 +267,11 @@ export class EntityRequestHandler extends RequestHandlerBase {
|
|
|
253
267
|
const omit = parseArrayParam(params.get('$omit'));
|
|
254
268
|
const include = parseArrayParam(params.get('$include'));
|
|
255
269
|
return new RequestHost({
|
|
256
|
-
controller:
|
|
270
|
+
controller: endpointMeta.controller,
|
|
257
271
|
http: incoming,
|
|
258
272
|
contentId,
|
|
259
273
|
resource,
|
|
260
|
-
|
|
274
|
+
endpoint: 'update',
|
|
261
275
|
key: resource.parseKeyValue(p.key),
|
|
262
276
|
data,
|
|
263
277
|
params: {
|
|
@@ -267,18 +281,18 @@ export class EntityRequestHandler extends RequestHandlerBase {
|
|
|
267
281
|
}
|
|
268
282
|
});
|
|
269
283
|
}
|
|
270
|
-
const
|
|
271
|
-
const jsonReader = this.getBodyLoader(
|
|
284
|
+
const endpointMeta = await this.assertEndpoint(resource, 'updateMany');
|
|
285
|
+
const jsonReader = this.getBodyLoader(endpointMeta);
|
|
272
286
|
const decode = resource.getDecoder('updateMany');
|
|
273
287
|
let data = await jsonReader(incoming);
|
|
274
288
|
data = decode(data, { coerce: true });
|
|
275
289
|
const filter = resource.normalizeFilter(params.get('$filter'));
|
|
276
290
|
return new RequestHost({
|
|
277
|
-
controller:
|
|
291
|
+
controller: endpointMeta.controller,
|
|
278
292
|
http: incoming,
|
|
279
293
|
contentId,
|
|
280
294
|
resource,
|
|
281
|
-
|
|
295
|
+
endpoint: 'updateMany',
|
|
282
296
|
data,
|
|
283
297
|
params: {
|
|
284
298
|
filter,
|
|
@@ -287,7 +301,7 @@ export class EntityRequestHandler extends RequestHandlerBase {
|
|
|
287
301
|
}
|
|
288
302
|
}
|
|
289
303
|
throw new MethodNotAllowedError({
|
|
290
|
-
message: `Collection
|
|
304
|
+
message: `Collection resource do not accept http "${incoming.method}" method`
|
|
291
305
|
});
|
|
292
306
|
}
|
|
293
307
|
async parseSingletonRequest(resource, incoming) {
|
|
@@ -298,8 +312,8 @@ export class EntityRequestHandler extends RequestHandlerBase {
|
|
|
298
312
|
const params = incoming.parsedUrl.searchParams;
|
|
299
313
|
switch (incoming.method) {
|
|
300
314
|
case 'POST': {
|
|
301
|
-
const
|
|
302
|
-
const jsonReader = this.getBodyLoader(
|
|
315
|
+
const endpointMeta = await this.assertEndpoint(resource, 'create');
|
|
316
|
+
const jsonReader = this.getBodyLoader(endpointMeta);
|
|
303
317
|
const decode = resource.getDecoder('create');
|
|
304
318
|
let data = await jsonReader(incoming);
|
|
305
319
|
data = decode(data, { coerce: true });
|
|
@@ -307,11 +321,11 @@ export class EntityRequestHandler extends RequestHandlerBase {
|
|
|
307
321
|
const omit = parseArrayParam(params.get('$omit'));
|
|
308
322
|
const include = parseArrayParam(params.get('$include'));
|
|
309
323
|
return new RequestHost({
|
|
310
|
-
controller:
|
|
324
|
+
controller: endpointMeta.controller,
|
|
311
325
|
http: incoming,
|
|
312
326
|
contentId,
|
|
313
327
|
resource,
|
|
314
|
-
|
|
328
|
+
endpoint: 'create',
|
|
315
329
|
data,
|
|
316
330
|
params: {
|
|
317
331
|
pick: pick && resource.normalizeFieldPath(pick),
|
|
@@ -321,26 +335,26 @@ export class EntityRequestHandler extends RequestHandlerBase {
|
|
|
321
335
|
});
|
|
322
336
|
}
|
|
323
337
|
case 'DELETE': {
|
|
324
|
-
const
|
|
338
|
+
const endpointMeta = await this.assertEndpoint(resource, 'delete');
|
|
325
339
|
return new RequestHost({
|
|
326
|
-
controller:
|
|
340
|
+
controller: endpointMeta.controller,
|
|
327
341
|
http: incoming,
|
|
328
342
|
contentId,
|
|
329
343
|
resource,
|
|
330
|
-
|
|
344
|
+
endpoint: 'delete',
|
|
331
345
|
});
|
|
332
346
|
}
|
|
333
347
|
case 'GET': {
|
|
334
|
-
const
|
|
348
|
+
const endpointMeta = await this.assertEndpoint(resource, 'get');
|
|
335
349
|
const pick = parseArrayParam(params.get('$pick'));
|
|
336
350
|
const omit = parseArrayParam(params.get('$omit'));
|
|
337
351
|
const include = parseArrayParam(params.get('$include'));
|
|
338
352
|
return new RequestHost({
|
|
339
|
-
controller:
|
|
353
|
+
controller: endpointMeta.controller,
|
|
340
354
|
http: incoming,
|
|
341
355
|
contentId,
|
|
342
356
|
resource,
|
|
343
|
-
|
|
357
|
+
endpoint: 'get',
|
|
344
358
|
params: {
|
|
345
359
|
pick: pick && resource.normalizeFieldPath(pick),
|
|
346
360
|
omit: omit && resource.normalizeFieldPath(omit),
|
|
@@ -349,8 +363,8 @@ export class EntityRequestHandler extends RequestHandlerBase {
|
|
|
349
363
|
});
|
|
350
364
|
}
|
|
351
365
|
case 'PATCH': {
|
|
352
|
-
const
|
|
353
|
-
const jsonReader = this.getBodyLoader(
|
|
366
|
+
const endpointMeta = await this.assertEndpoint(resource, 'update');
|
|
367
|
+
const jsonReader = this.getBodyLoader(endpointMeta);
|
|
354
368
|
const decode = resource.getDecoder('update');
|
|
355
369
|
let data = await jsonReader(incoming);
|
|
356
370
|
data = decode(data, { coerce: true });
|
|
@@ -358,11 +372,11 @@ export class EntityRequestHandler extends RequestHandlerBase {
|
|
|
358
372
|
const omit = parseArrayParam(params.get('$omit'));
|
|
359
373
|
const include = parseArrayParam(params.get('$include'));
|
|
360
374
|
return new RequestHost({
|
|
361
|
-
controller:
|
|
375
|
+
controller: endpointMeta.controller,
|
|
362
376
|
http: incoming,
|
|
363
377
|
contentId,
|
|
364
378
|
resource,
|
|
365
|
-
|
|
379
|
+
endpoint: 'update',
|
|
366
380
|
data,
|
|
367
381
|
params: {
|
|
368
382
|
pick: pick && resource.normalizeFieldPath(pick),
|
|
@@ -373,14 +387,14 @@ export class EntityRequestHandler extends RequestHandlerBase {
|
|
|
373
387
|
}
|
|
374
388
|
}
|
|
375
389
|
throw new MethodNotAllowedError({
|
|
376
|
-
message: `Singleton
|
|
390
|
+
message: `Singleton resource do not accept http "${incoming.method}" method`
|
|
377
391
|
});
|
|
378
392
|
}
|
|
379
|
-
getBodyLoader(
|
|
380
|
-
let bodyLoader = this.bodyLoaders.get(
|
|
393
|
+
getBodyLoader(endpoint) {
|
|
394
|
+
let bodyLoader = this.bodyLoaders.get(endpoint);
|
|
381
395
|
if (!bodyLoader) {
|
|
382
396
|
const parser = bodyParser.json({
|
|
383
|
-
limit:
|
|
397
|
+
limit: endpoint.input?.maxContentSize,
|
|
384
398
|
type: 'json'
|
|
385
399
|
});
|
|
386
400
|
bodyLoader = (incoming) => {
|
|
@@ -393,7 +407,7 @@ export class EntityRequestHandler extends RequestHandlerBase {
|
|
|
393
407
|
parser(incoming, {}, next);
|
|
394
408
|
});
|
|
395
409
|
};
|
|
396
|
-
this.bodyLoaders.set(
|
|
410
|
+
this.bodyLoaders.set(endpoint, bodyLoader);
|
|
397
411
|
}
|
|
398
412
|
return bodyLoader;
|
|
399
413
|
}
|
|
@@ -6,16 +6,16 @@ export class RequestHandlerBase {
|
|
|
6
6
|
constructor(adapter) {
|
|
7
7
|
this.adapter = adapter;
|
|
8
8
|
}
|
|
9
|
-
async
|
|
9
|
+
async assertEndpoint(resource, endpoint) {
|
|
10
10
|
const controller = await this.adapter.getController(resource);
|
|
11
|
-
const
|
|
12
|
-
if (
|
|
11
|
+
const endpointMeta = (typeof controller?.[endpoint] === 'function') && resource.operations[endpoint];
|
|
12
|
+
if (endpointMeta)
|
|
13
13
|
return {
|
|
14
|
-
...
|
|
14
|
+
...endpointMeta,
|
|
15
15
|
controller
|
|
16
16
|
};
|
|
17
17
|
throw new ForbiddenError({
|
|
18
|
-
message: translate('RESOLVER_FORBIDDEN', { resource: resource.name,
|
|
18
|
+
message: translate('RESOLVER_FORBIDDEN', { resource: resource.name, endpoint }, `'{{resource}}' endpoint does not accept '{{endpoint}}' operations`),
|
|
19
19
|
severity: 'error',
|
|
20
20
|
code: 'RESOLVER_FORBIDDEN'
|
|
21
21
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import { BadRequestError, HttpStatusCodes, isReadable, OpraException, Storage, uid } from '@opra/common';
|
|
4
|
-
import {
|
|
4
|
+
import { EndpointContext } from '../../endpoint-context.js';
|
|
5
5
|
import { RequestHost } from '../../request.host.js';
|
|
6
6
|
import { ResponseHost } from '../../response.host.js';
|
|
7
7
|
import { MultipartIterator } from '../helpers/multipart-helper.js';
|
|
@@ -22,9 +22,8 @@ export class StorageRequestHandler extends RequestHandlerBase {
|
|
|
22
22
|
if (!request)
|
|
23
23
|
return;
|
|
24
24
|
const response = new ResponseHost({ http: outgoing });
|
|
25
|
-
const context =
|
|
26
|
-
|
|
27
|
-
await this.executeOperation(context);
|
|
25
|
+
const context = EndpointContext.from(executionContext, request, response);
|
|
26
|
+
await this.callEndpoint(context);
|
|
28
27
|
if (response.errors.length) {
|
|
29
28
|
context.errors.push(...response.errors);
|
|
30
29
|
return;
|
|
@@ -40,30 +39,30 @@ export class StorageRequestHandler extends RequestHandlerBase {
|
|
|
40
39
|
return;
|
|
41
40
|
switch (incoming.method) {
|
|
42
41
|
case 'GET': {
|
|
43
|
-
const
|
|
42
|
+
const endpointMeta = await this.assertEndpoint(resource, 'get');
|
|
44
43
|
return new RequestHost({
|
|
45
|
-
controller:
|
|
44
|
+
controller: endpointMeta.controller,
|
|
46
45
|
http: incoming,
|
|
47
46
|
resource,
|
|
48
|
-
|
|
47
|
+
endpoint: 'get',
|
|
49
48
|
contentId
|
|
50
49
|
});
|
|
51
50
|
}
|
|
52
51
|
case 'DELETE': {
|
|
53
|
-
const
|
|
52
|
+
const endpointMeta = await this.assertEndpoint(resource, 'delete');
|
|
54
53
|
return new RequestHost({
|
|
55
|
-
controller:
|
|
54
|
+
controller: endpointMeta.controller,
|
|
56
55
|
http: incoming,
|
|
57
56
|
resource,
|
|
58
|
-
|
|
57
|
+
endpoint: 'delete',
|
|
59
58
|
contentId
|
|
60
59
|
});
|
|
61
60
|
}
|
|
62
61
|
case 'POST': {
|
|
63
|
-
const
|
|
62
|
+
const endpointMeta = await this.assertEndpoint(resource, 'post');
|
|
64
63
|
await fs.mkdir(this._uploadDir, { recursive: true });
|
|
65
64
|
const multipartIterator = new MultipartIterator(incoming, {
|
|
66
|
-
...
|
|
65
|
+
...endpointMeta,
|
|
67
66
|
filename: () => this.adapter.serviceName + '_p' + process.pid +
|
|
68
67
|
't' + String(Date.now()).substring(8) + 'r' + uid(12)
|
|
69
68
|
});
|
|
@@ -74,10 +73,10 @@ export class StorageRequestHandler extends RequestHandlerBase {
|
|
|
74
73
|
await multipartIterator.deleteFiles().catch(() => void 0);
|
|
75
74
|
});
|
|
76
75
|
return new RequestHost({
|
|
77
|
-
controller:
|
|
76
|
+
controller: endpointMeta.controller,
|
|
78
77
|
http: incoming,
|
|
79
78
|
resource,
|
|
80
|
-
|
|
79
|
+
endpoint: 'post',
|
|
81
80
|
contentId,
|
|
82
81
|
parts: multipartIterator
|
|
83
82
|
});
|
|
@@ -90,13 +89,13 @@ export class StorageRequestHandler extends RequestHandlerBase {
|
|
|
90
89
|
throw new BadRequestError(e);
|
|
91
90
|
}
|
|
92
91
|
}
|
|
93
|
-
async
|
|
92
|
+
async callEndpoint(context) {
|
|
94
93
|
const request = context.request;
|
|
95
94
|
const { response } = context;
|
|
96
|
-
// Call
|
|
95
|
+
// Call endpoint handler method
|
|
97
96
|
let value;
|
|
98
97
|
try {
|
|
99
|
-
value = await request.controller[request.
|
|
98
|
+
value = await request.controller[request.endpoint].call(request.controller, context);
|
|
100
99
|
if (response.value == null)
|
|
101
100
|
response.value = value;
|
|
102
101
|
}
|
|
@@ -63,7 +63,7 @@ export class PlatformAdapterHost extends AsyncEventEmitter {
|
|
|
63
63
|
this._i18n = this._i18n || I18n.defaultInstance;
|
|
64
64
|
if (!this._i18n.isInitialized)
|
|
65
65
|
await this._i18n.init();
|
|
66
|
-
// Initialize all
|
|
66
|
+
// Initialize all resources
|
|
67
67
|
for (const resource of this.api.resources.values()) {
|
|
68
68
|
await this.getController(resource);
|
|
69
69
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Collection,
|
|
1
|
+
import { Collection, Resource, Singleton, SOURCE_METADATA } from "@opra/common";
|
|
2
2
|
// @ts-ignore
|
|
3
3
|
const oldConstruct = Resource.prototype._construct;
|
|
4
4
|
// @ts-ignore
|
|
@@ -10,15 +10,15 @@ Resource.prototype._construct = function (init) {
|
|
|
10
10
|
};
|
|
11
11
|
Collection.OnInit = Singleton.OnInit = function () {
|
|
12
12
|
return (target, propertyKey) => {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
Reflect.defineMetadata(
|
|
13
|
+
const sourceMetadata = (Reflect.getOwnMetadata(SOURCE_METADATA, target.constructor) || {});
|
|
14
|
+
sourceMetadata.onInit = target[propertyKey];
|
|
15
|
+
Reflect.defineMetadata(SOURCE_METADATA, target.constructor, sourceMetadata);
|
|
16
16
|
};
|
|
17
17
|
};
|
|
18
18
|
Collection.OnShutdown = Singleton.OnShutdown = function () {
|
|
19
19
|
return (target, propertyKey) => {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
Reflect.defineMetadata(
|
|
20
|
+
const sourceMetadata = (Reflect.getOwnMetadata(SOURCE_METADATA, target.constructor) || {});
|
|
21
|
+
sourceMetadata.onShutdown = target[propertyKey];
|
|
22
|
+
Reflect.defineMetadata(SOURCE_METADATA, target.constructor, sourceMetadata);
|
|
23
23
|
};
|
|
24
24
|
};
|
package/esm/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import './augmentation/singleton.augmentation.js';
|
|
|
5
5
|
import './augmentation/storage.augmentation.js';
|
|
6
6
|
export * from './types.js';
|
|
7
7
|
export * from './adapter/execution-context.js';
|
|
8
|
-
export * from './adapter/
|
|
8
|
+
export * from './adapter/endpoint-context.js';
|
|
9
9
|
export * from './adapter/platform-adapter.js';
|
|
10
10
|
export * from './adapter/request.js';
|
|
11
11
|
export * from './adapter/response.js';
|
package/i18n/en/error.json
CHANGED
|
@@ -8,11 +8,12 @@
|
|
|
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
|
+
"SOURCE_NOT_FOUND": "Source not found",
|
|
11
12
|
"REQUEST_VALIDATION": "Request validation failed",
|
|
12
13
|
"RESPONSE_VALIDATION": "Response validation failed",
|
|
13
|
-
"RESOURCE_NOT_FOUND": "Resource not found
|
|
14
|
+
"RESOURCE_NOT_FOUND": "Resource not found",
|
|
14
15
|
"RESOURCE_CONFLICT": "There is already an other {{resource}} resource with same field values ({{fields}})",
|
|
15
|
-
"RESOLVER_FORBIDDEN": "The {{resource}} endpoint does not accept '{{
|
|
16
|
+
"RESOLVER_FORBIDDEN": "The {{resource}} endpoint does not accept '{{endpoint}}' operations",
|
|
16
17
|
"UNKNOWN_FIELD": "Unknown field '{{field}}'",
|
|
17
18
|
"UNACCEPTED_SORT_FIELD": "Field '{{field}}' is not available for sort operation",
|
|
18
19
|
"UNACCEPTED_FILTER_FIELD": "Field '{{field}}' is not available for filter operation",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opra/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "Opra schema package",
|
|
5
5
|
"author": "Panates",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"clean:cover": "rimraf ../../coverage/core"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@opra/common": "^0.
|
|
30
|
+
"@opra/common": "^0.24.0",
|
|
31
31
|
"accepts": "^1.3.8",
|
|
32
32
|
"content-disposition": "^0.5.4",
|
|
33
33
|
"content-type": "^1.0.5",
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { ExecutionContext } from './execution-context';
|
|
2
2
|
import type { Request } from './request';
|
|
3
3
|
import type { Response } from './response';
|
|
4
|
-
export interface
|
|
4
|
+
export interface EndpointContext<TRequest extends Request = Request, TResponse extends Response = Response> extends ExecutionContext {
|
|
5
5
|
request: TRequest;
|
|
6
6
|
response: TResponse;
|
|
7
7
|
requestScope: Record<string | number | symbol, any>;
|
|
8
8
|
}
|
|
9
|
-
export declare namespace
|
|
10
|
-
function from<TRequest extends Request = Request, TResponse extends Response = Response>(executionContext: ExecutionContext, request: TRequest, response: TResponse):
|
|
9
|
+
export declare namespace EndpointContext {
|
|
10
|
+
function from<TRequest extends Request = Request, TResponse extends Response = Response>(executionContext: ExecutionContext, request: TRequest, response: TResponse): EndpointContext;
|
|
11
11
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Collection, OpraSchema, Singleton } from '@opra/common';
|
|
2
|
+
import { EndpointContext } from '../../endpoint-context.js';
|
|
2
3
|
import { ExecutionContext } from '../../execution-context.js';
|
|
3
|
-
import { OperationContext } from '../../operation-context.js';
|
|
4
4
|
import { Request } from '../../request.js';
|
|
5
5
|
import type { HttpAdapterBase } from '../http-adapter-base.js';
|
|
6
6
|
import { HttpServerRequest } from '../http-server-request.js';
|
|
@@ -15,10 +15,10 @@ export declare class EntityRequestHandler extends RequestHandlerBase {
|
|
|
15
15
|
constructor(adapter: HttpAdapterBase);
|
|
16
16
|
processRequest(executionContext: ExecutionContext): Promise<void>;
|
|
17
17
|
parseRequest(incoming: HttpServerRequest): Promise<Request | undefined>;
|
|
18
|
-
|
|
19
|
-
sendResponse(context:
|
|
18
|
+
callEndpoint(context: EndpointContext): Promise<void>;
|
|
19
|
+
sendResponse(context: EndpointContext): Promise<void>;
|
|
20
20
|
parseCollectionRequest(resource: Collection, incoming: HttpServerRequest): Promise<Request>;
|
|
21
21
|
parseSingletonRequest(resource: Singleton, incoming: HttpServerRequest): Promise<Request>;
|
|
22
|
-
getBodyLoader(
|
|
22
|
+
getBodyLoader(endpoint: OpraSchema.Collection.CreateEndpoint | OpraSchema.Collection.UpdateEndpoint | OpraSchema.Collection.UpdateManyEndpoint): BodyLoaderFunction;
|
|
23
23
|
}
|
|
24
24
|
export {};
|
|
@@ -9,7 +9,7 @@ export declare abstract class RequestHandlerBase implements RequestHandler {
|
|
|
9
9
|
readonly adapter: HttpAdapterBase;
|
|
10
10
|
protected constructor(adapter: HttpAdapterBase);
|
|
11
11
|
abstract processRequest(executionContext: ExecutionContext): Promise<void>;
|
|
12
|
-
|
|
12
|
+
assertEndpoint<T extends OpraSchema.Endpoint = OpraSchema.Endpoint>(resource: Resource, endpoint: string): Promise<T & {
|
|
13
13
|
controller: any;
|
|
14
14
|
}>;
|
|
15
15
|
}
|