@opra/core 0.0.8 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/cjs/exception/api-exception.js +32 -35
  2. package/cjs/exception/{errors → http-errors}/bad-request.error.js +7 -4
  3. package/cjs/exception/{errors → http-errors}/failed-dependency.error.js +7 -4
  4. package/cjs/exception/{errors → http-errors}/forbidden.error.js +7 -4
  5. package/cjs/exception/{errors → http-errors}/internal-server.error.js +10 -5
  6. package/cjs/exception/{errors → http-errors}/method-not-allowed.error.js +7 -4
  7. package/cjs/exception/{errors → http-errors}/not-found.error.js +6 -3
  8. package/cjs/exception/{errors → http-errors}/unauthorized.error.js +6 -3
  9. package/cjs/exception/{errors → http-errors}/unprocessable-entity.error.js +7 -4
  10. package/cjs/exception/index.js +9 -7
  11. package/cjs/exception/resource-errors/resource-conflict.error.js +19 -0
  12. package/cjs/exception/wrap-error.js +2 -2
  13. package/cjs/implementation/adapter/adapter.js +31 -29
  14. package/cjs/implementation/adapter/express-adapter.js +40 -9
  15. package/cjs/implementation/adapter/http-adapter.js +28 -35
  16. package/cjs/implementation/{execution-context.js → query-context.js} +18 -22
  17. package/cjs/implementation/resource/entity-resource-handler.js +16 -12
  18. package/cjs/implementation/resource/resource-handler.js +1 -1
  19. package/cjs/implementation/schema-generator.js +38 -51
  20. package/cjs/index.js +3 -4
  21. package/cjs/interfaces/{http-context.interface.js → execution-context.interface.js} +0 -0
  22. package/cjs/interfaces/{execution-query.interface.js → query.interface.js} +24 -24
  23. package/cjs/utils/create-i18n.js +21 -0
  24. package/cjs/utils/get-caller-file.util.js +19 -0
  25. package/cjs/utils/internal-data-types.js +54 -17
  26. package/esm/exception/api-exception.d.ts +3 -2
  27. package/esm/exception/api-exception.js +32 -35
  28. package/esm/exception/{errors → http-errors}/bad-request.error.d.ts +2 -1
  29. package/esm/exception/{errors → http-errors}/bad-request.error.js +7 -4
  30. package/esm/exception/{errors → http-errors}/failed-dependency.error.d.ts +2 -1
  31. package/esm/exception/{errors → http-errors}/failed-dependency.error.js +7 -4
  32. package/esm/exception/{errors → http-errors}/forbidden.error.d.ts +2 -1
  33. package/esm/exception/{errors → http-errors}/forbidden.error.js +7 -4
  34. package/esm/exception/{errors → http-errors}/internal-server.error.d.ts +2 -1
  35. package/esm/exception/{errors → http-errors}/internal-server.error.js +9 -5
  36. package/esm/exception/{errors → http-errors}/method-not-allowed.error.d.ts +2 -1
  37. package/esm/exception/{errors → http-errors}/method-not-allowed.error.js +7 -4
  38. package/esm/exception/{errors → http-errors}/not-found.error.d.ts +2 -1
  39. package/esm/exception/{errors → http-errors}/not-found.error.js +6 -3
  40. package/esm/exception/{errors → http-errors}/unauthorized.error.d.ts +2 -1
  41. package/esm/exception/{errors → http-errors}/unauthorized.error.js +6 -3
  42. package/esm/exception/{errors → http-errors}/unprocessable-entity.error.d.ts +2 -1
  43. package/esm/exception/{errors → http-errors}/unprocessable-entity.error.js +7 -4
  44. package/esm/exception/index.d.ts +9 -7
  45. package/esm/exception/index.js +9 -7
  46. package/esm/exception/resource-errors/resource-conflict.error.d.ts +4 -0
  47. package/esm/exception/resource-errors/resource-conflict.error.js +15 -0
  48. package/esm/exception/wrap-error.js +2 -2
  49. package/esm/implementation/adapter/adapter.d.ts +22 -14
  50. package/esm/implementation/adapter/adapter.js +31 -29
  51. package/esm/implementation/adapter/express-adapter.d.ts +2 -2
  52. package/esm/implementation/adapter/express-adapter.js +40 -9
  53. package/esm/implementation/adapter/http-adapter.d.ts +11 -12
  54. package/esm/implementation/adapter/http-adapter.js +28 -35
  55. package/esm/implementation/query-context.d.ts +32 -0
  56. package/esm/implementation/{execution-context.js → query-context.js} +15 -18
  57. package/esm/implementation/resource/container-resource-handler.d.ts +2 -2
  58. package/esm/implementation/resource/entity-resource-handler.d.ts +3 -3
  59. package/esm/implementation/resource/entity-resource-handler.js +16 -12
  60. package/esm/implementation/resource/resource-handler.d.ts +3 -3
  61. package/esm/implementation/resource/resource-handler.js +1 -1
  62. package/esm/implementation/schema-generator.js +38 -51
  63. package/esm/index.d.ts +3 -4
  64. package/esm/index.js +3 -4
  65. package/esm/interfaces/execution-context.interface.d.ts +39 -0
  66. package/esm/interfaces/{http-context.interface.js → execution-context.interface.js} +0 -0
  67. package/esm/interfaces/query.interface.d.ts +108 -0
  68. package/esm/interfaces/{execution-query.interface.js → query.interface.js} +23 -23
  69. package/esm/services/entity-resource-controller.d.ts +11 -11
  70. package/esm/types.d.ts +1 -3
  71. package/esm/utils/create-i18n.d.ts +3 -0
  72. package/esm/utils/create-i18n.js +16 -0
  73. package/esm/utils/get-caller-file.util.d.ts +1 -0
  74. package/esm/utils/get-caller-file.util.js +15 -0
  75. package/esm/utils/internal-data-types.d.ts +2 -1
  76. package/esm/utils/internal-data-types.js +53 -16
  77. package/i18n/en/error.json +12 -0
  78. package/package.json +10 -6
  79. package/cjs/interfaces/user-context.interface.js +0 -2
  80. package/esm/implementation/execution-context.d.ts +0 -42
  81. package/esm/interfaces/execution-query.interface.d.ts +0 -102
  82. package/esm/interfaces/http-context.interface.d.ts +0 -23
  83. package/esm/interfaces/user-context.interface.d.ts +0 -3
  84. package/esm/interfaces/user-context.interface.js +0 -1
@@ -1,35 +1,36 @@
1
+ import { AsyncEventEmitter } from 'strict-typed-events';
1
2
  import { I18n } from '@opra/i18n';
2
3
  import { FailedDependencyError } from '../../exception/index.js';
3
4
  import { wrapError } from '../../exception/wrap-error.js';
5
+ import { createI18n } from '../../utils/create-i18n.js';
4
6
  export class OpraAdapter {
5
7
  service;
6
8
  i18n;
7
- constructor(service, i18n) {
9
+ userContextResolver;
10
+ constructor(service, options) {
8
11
  this.service = service;
9
- this.i18n = i18n || I18n.defaultInstance;
12
+ this.i18n = options?.i18n || I18n.defaultInstance;
13
+ this.userContextResolver = options?.userContext;
10
14
  }
11
- async handler(adapterContext, userContextResolver) {
15
+ async handler(executionContext) {
12
16
  if (!this.i18n.isInitialized)
13
17
  await this.i18n.init();
14
- const executionContexts = [];
15
- let requests;
18
+ let queryContexts;
16
19
  let userContext;
20
+ let failed = false;
17
21
  try {
18
- requests = this.prepareRequests(adapterContext);
22
+ queryContexts = this.prepareRequests(executionContext);
19
23
  let stop = false;
20
24
  // Read requests can be executed simultaneously, write request should be executed one by one
21
25
  let promises;
22
26
  let exclusive = false;
23
- for (const request of requests) {
24
- exclusive = exclusive || request.query.operationType !== 'read';
27
+ for (const context of queryContexts) {
28
+ exclusive = exclusive || context.query.operation !== 'read';
25
29
  // Wait previous read requests before executing update request
26
30
  if (exclusive && promises) {
27
31
  await Promise.allSettled(promises);
28
32
  promises = undefined;
29
33
  }
30
- const resource = request.query.resource;
31
- const context = this.createExecutionContext(adapterContext, request);
32
- executionContexts.push(context);
33
34
  // If previous request in bucket had an error and executed an update
34
35
  // we do not execute next requests
35
36
  if (stop) {
@@ -37,10 +38,14 @@ export class OpraAdapter {
37
38
  continue;
38
39
  }
39
40
  try {
41
+ const resource = context.query.resource;
40
42
  const promise = (async () => {
41
43
  await resource.prepare(context);
42
- if (userContextResolver && !userContext)
43
- userContext = userContextResolver(this.isBatch(adapterContext));
44
+ if (this.userContextResolver && !userContext)
45
+ userContext = this.userContextResolver({
46
+ executionContext,
47
+ isBatch: this.isBatch(executionContext)
48
+ });
44
49
  context.userContext = userContext;
45
50
  await resource.execute(context);
46
51
  })().catch(e => {
@@ -66,22 +71,21 @@ export class OpraAdapter {
66
71
  }
67
72
  if (promises)
68
73
  await Promise.allSettled(promises);
69
- if (userContext && typeof userContext.onRequestFinish === 'function') {
70
- try {
71
- const hasError = !!executionContexts.find(ctx => ctx.response.errors?.length);
72
- await userContext.onRequestFinish(hasError);
73
- }
74
- catch (e) {
75
- await this.sendError(adapterContext, wrapError(e));
76
- return;
77
- }
78
- }
79
- await this.sendResponse(adapterContext, executionContexts);
74
+ await this.sendResponse(executionContext, queryContexts);
80
75
  }
81
76
  catch (e) {
77
+ failed = true;
82
78
  const error = wrapError(e);
83
- await this.sendError(adapterContext, error);
84
- return;
79
+ await this.sendError(executionContext, error);
80
+ }
81
+ finally {
82
+ if (executionContext instanceof AsyncEventEmitter) {
83
+ await executionContext
84
+ .emitAsyncSerial('finish', {
85
+ userContext,
86
+ failed
87
+ }).catch();
88
+ }
85
89
  }
86
90
  }
87
91
  static async initI18n(options) {
@@ -89,8 +93,6 @@ export class OpraAdapter {
89
93
  return options.i18n;
90
94
  if (typeof options?.i18n === 'function')
91
95
  return options.i18n();
92
- const instance = I18n.createInstance(options?.i18n);
93
- await instance.init();
94
- return instance;
96
+ return createI18n(options?.i18n);
95
97
  }
96
98
  }
@@ -1,11 +1,11 @@
1
1
  import type { Application } from 'express';
2
- import type { IHttpAdapterContext } from '../../interfaces/http-context.interface.js';
2
+ import type { IHttpExecutionContext } from '../../interfaces/execution-context.interface';
3
3
  import type { OpraService } from '../opra-service.js';
4
4
  import { OpraHttpAdapter } from './http-adapter.js';
5
5
  export declare namespace OpraExpressAdapter {
6
6
  interface Options extends OpraHttpAdapter.Options {
7
7
  }
8
8
  }
9
- export declare class OpraExpressAdapter extends OpraHttpAdapter<IHttpAdapterContext> {
9
+ export declare class OpraExpressAdapter extends OpraHttpAdapter<IHttpExecutionContext> {
10
10
  static init(app: Application, service: OpraService, options?: OpraExpressAdapter.Options): Promise<OpraExpressAdapter>;
11
11
  }
@@ -1,25 +1,56 @@
1
+ import { AsyncEventEmitter } from 'strict-typed-events';
1
2
  import { normalizePath } from '@opra/url';
2
3
  import { OpraHttpAdapter } from './http-adapter.js';
3
4
  export class OpraExpressAdapter extends OpraHttpAdapter {
4
5
  static async init(app, service, options) {
5
6
  const i18n = await this.initI18n(options);
6
- const adapter = new OpraExpressAdapter(service, i18n);
7
+ const adapter = new OpraExpressAdapter(service, {
8
+ ...options,
9
+ i18n
10
+ });
7
11
  const prefix = '/' + normalizePath(options?.prefix, true);
8
- const userContextResolver = options?.userContext;
9
12
  app.use(prefix, (request, response, next) => {
10
13
  (async () => {
11
- const req = new ExpressRequestWrapper(request);
12
- const res = new ExpressResponseWrapper(response);
13
- const adapterContext = {
14
- getRequest: () => req,
15
- getResponse: () => res
16
- };
17
- await adapter.handler(adapterContext, (isBatch) => userContextResolver && userContextResolver(request, { platform: 'express', isBatch }));
14
+ const executionContext = new ExpressExecutionContext(request, response);
15
+ await adapter.handler(executionContext);
18
16
  })().catch(e => next(e));
19
17
  });
20
18
  return adapter;
21
19
  }
22
20
  }
21
+ class ExpressExecutionContext extends AsyncEventEmitter {
22
+ _request;
23
+ _response;
24
+ constructor(request, response) {
25
+ super();
26
+ this._request = new ExpressRequestWrapper(request);
27
+ this._response = new ExpressResponseWrapper(response);
28
+ }
29
+ getType() {
30
+ return 'http';
31
+ }
32
+ getPlatform() {
33
+ return 'express';
34
+ }
35
+ switchToHttp() {
36
+ return this;
37
+ }
38
+ getRequest() {
39
+ return this._request.getInstance();
40
+ }
41
+ getResponse() {
42
+ return this._response.getInstance();
43
+ }
44
+ getRequestWrapper() {
45
+ return this._request;
46
+ }
47
+ getResponseWrapper() {
48
+ return this._response;
49
+ }
50
+ onFinish(fn) {
51
+ this.on('finish', fn);
52
+ }
53
+ }
23
54
  class ExpressRequestWrapper {
24
55
  instance;
25
56
  constructor(instance) {
@@ -1,9 +1,9 @@
1
1
  import { OpraURL } from '@opra/url';
2
2
  import { ApiException } from '../../exception/index.js';
3
- import { ExecutionQuery } from '../../interfaces/execution-query.interface.js';
4
- import { IHttpAdapterContext } from '../../interfaces/http-context.interface.js';
3
+ import { IHttpExecutionContext } from '../../interfaces/execution-context.interface.js';
4
+ import { OpraQuery } from '../../interfaces/query.interface.js';
5
5
  import { HeadersObject } from '../../utils/headers.js';
6
- import { ExecutionContext, ExecutionRequest } from '../execution-context.js';
6
+ import { QueryContext } from '../query-context.js';
7
7
  import { OpraAdapter } from './adapter.js';
8
8
  export declare namespace OpraHttpAdapter {
9
9
  type Options = OpraAdapter.Options & {
@@ -15,14 +15,13 @@ interface PreparedOutput {
15
15
  headers?: Record<string, string>;
16
16
  body?: any;
17
17
  }
18
- export declare class OpraHttpAdapter<TAdapterContext extends IHttpAdapterContext> extends OpraAdapter<IHttpAdapterContext> {
19
- protected prepareRequests(adapterContext: TAdapterContext): ExecutionRequest[];
20
- prepareRequest(adapterContext: any, url: OpraURL, method: string, headers: HeadersObject, body?: any): ExecutionRequest;
21
- createExecutionContext(adapterContext: any, request: ExecutionRequest): ExecutionContext;
22
- buildQuery(url: OpraURL, method: string, body?: any): ExecutionQuery | undefined;
23
- protected sendResponse(adapterContext: TAdapterContext, executionContexts: ExecutionContext[]): Promise<void>;
24
- protected isBatch(adapterContext: TAdapterContext): boolean;
25
- protected createOutput(ctx: ExecutionContext): PreparedOutput;
26
- protected sendError(adapterContext: TAdapterContext, error: ApiException): Promise<void>;
18
+ export declare class OpraHttpAdapter<TExecutionContext extends IHttpExecutionContext> extends OpraAdapter<IHttpExecutionContext> {
19
+ protected prepareRequests(executionContext: TExecutionContext): QueryContext[];
20
+ prepareRequest(executionContext: IHttpExecutionContext, url: OpraURL, method: string, headers: HeadersObject, body?: any): QueryContext;
21
+ buildQuery(url: OpraURL, method: string, body?: any): OpraQuery | undefined;
22
+ protected sendResponse(executionContext: TExecutionContext, queryContexts: QueryContext[]): Promise<void>;
23
+ protected isBatch(executionContext: TExecutionContext): boolean;
24
+ protected createOutput(ctx: QueryContext): PreparedOutput;
25
+ protected sendError(executionContext: TExecutionContext, error: ApiException): Promise<void>;
27
26
  }
28
27
  export {};
@@ -3,26 +3,26 @@ import { OpraVersion } from '../../constants.js';
3
3
  import { HttpHeaders, HttpStatus } from '../../enums/index.js';
4
4
  import { ApiException, BadRequestError, InternalServerError, MethodNotAllowedError, NotFoundError, } from '../../exception/index.js';
5
5
  import { wrapError } from '../../exception/wrap-error.js';
6
- import { ExecutionQuery } from '../../interfaces/execution-query.interface.js';
6
+ import { OpraQuery } from '../../interfaces/query.interface.js';
7
7
  import { Headers } from '../../utils/headers.js';
8
8
  import { ComplexType } from '../data-type/complex-type.js';
9
- import { ExecutionContext, ExecutionRequest, ExecutionResponse } from '../execution-context.js';
9
+ import { QueryContext } from '../query-context.js';
10
10
  import { ContainerResourceHandler } from '../resource/container-resource-handler.js';
11
11
  import { EntityResourceHandler } from '../resource/entity-resource-handler.js';
12
12
  import { OpraAdapter } from './adapter.js';
13
13
  export class OpraHttpAdapter extends OpraAdapter {
14
- prepareRequests(adapterContext) {
15
- const req = adapterContext.getRequest();
14
+ prepareRequests(executionContext) {
15
+ const req = executionContext.getRequestWrapper();
16
16
  // todo implement batch requests
17
- if (this.isBatch(adapterContext)) {
17
+ if (this.isBatch(executionContext)) {
18
18
  throw new Error('not implemented yet');
19
19
  }
20
20
  const url = new OpraURL(req.getUrl());
21
21
  return [
22
- this.prepareRequest(adapterContext, url, req.getMethod(), Headers.from(req.getHeaders()), req.getBody())
22
+ this.prepareRequest(executionContext, url, req.getMethod(), Headers.from(req.getHeaders()), req.getBody())
23
23
  ];
24
24
  }
25
- prepareRequest(adapterContext, url, method, headers, body) {
25
+ prepareRequest(executionContext, url, method, headers, body) {
26
26
  if (!url.path.size)
27
27
  throw new BadRequestError();
28
28
  if (method !== 'GET' && url.path.size > 1)
@@ -32,20 +32,13 @@ export class OpraHttpAdapter extends OpraAdapter {
32
32
  throw new MethodNotAllowedError({
33
33
  message: `Method "${method}" is not allowed by target resource`
34
34
  });
35
- return new ExecutionRequest({
35
+ return new QueryContext({
36
+ service: this.service,
37
+ executionContext,
36
38
  query,
37
39
  headers,
38
40
  params: url.searchParams,
39
- });
40
- }
41
- createExecutionContext(adapterContext, request) {
42
- return new ExecutionContext({
43
- type: 'http',
44
- service: this.service,
45
- request,
46
- response: new ExecutionResponse(),
47
- adapterContext,
48
- continueOnError: request.query.operationType === 'read'
41
+ continueOnError: query.operation === 'read'
49
42
  });
50
43
  }
51
44
  buildQuery(url, method, body) {
@@ -70,7 +63,7 @@ export class OpraHttpAdapter extends OpraAdapter {
70
63
  switch (method) {
71
64
  case 'GET': {
72
65
  if (scope === 'collection') {
73
- query = ExecutionQuery.forSearch(resource, {
66
+ query = OpraQuery.forSearch(resource, {
74
67
  filter: url.searchParams.get('$filter'),
75
68
  limit: url.searchParams.get('$limit'),
76
69
  skip: url.searchParams.get('$skip'),
@@ -83,7 +76,7 @@ export class OpraHttpAdapter extends OpraAdapter {
83
76
  });
84
77
  }
85
78
  else {
86
- query = ExecutionQuery.forGet(resource, p.key, {
79
+ query = OpraQuery.forGet(resource, p.key, {
87
80
  pick: url.searchParams.get('$pick'),
88
81
  omit: url.searchParams.get('$omit'),
89
82
  include: url.searchParams.get('$include')
@@ -102,7 +95,7 @@ export class OpraHttpAdapter extends OpraAdapter {
102
95
  const prop = dataType.properties?.[p.resource];
103
96
  if (!prop)
104
97
  throw new NotFoundError({ message: `Invalid or unknown resource path (${path})` });
105
- const q = ExecutionQuery.forGetProperty(prop);
98
+ const q = OpraQuery.forGetProperty(prop);
106
99
  if (nested) {
107
100
  nested.nested = q;
108
101
  }
@@ -116,18 +109,18 @@ export class OpraHttpAdapter extends OpraAdapter {
116
109
  }
117
110
  case 'DELETE': {
118
111
  if (scope === 'collection') {
119
- query = ExecutionQuery.forDeleteMany(resource, {
112
+ query = OpraQuery.forDeleteMany(resource, {
120
113
  filter: url.searchParams.get('$filter'),
121
114
  });
122
115
  }
123
116
  else {
124
- query = ExecutionQuery.forDelete(resource, p.key);
117
+ query = OpraQuery.forDelete(resource, p.key);
125
118
  }
126
119
  break;
127
120
  }
128
121
  case 'POST': {
129
122
  if (scope === 'collection') {
130
- query = ExecutionQuery.forCreate(resource, body, {
123
+ query = OpraQuery.forCreate(resource, body, {
131
124
  pick: url.searchParams.get('$pick'),
132
125
  omit: url.searchParams.get('$omit'),
133
126
  include: url.searchParams.get('$include')
@@ -137,12 +130,12 @@ export class OpraHttpAdapter extends OpraAdapter {
137
130
  }
138
131
  case 'PATCH': {
139
132
  if (scope === 'collection') {
140
- query = ExecutionQuery.forUpdateMany(resource, body, {
133
+ query = OpraQuery.forUpdateMany(resource, body, {
141
134
  filter: url.searchParams.get('$filter')
142
135
  });
143
136
  }
144
137
  else {
145
- query = ExecutionQuery.forUpdate(resource, p.key, body, {
138
+ query = OpraQuery.forUpdate(resource, p.key, body, {
146
139
  pick: url.searchParams.get('$pick'),
147
140
  omit: url.searchParams.get('$omit'),
148
141
  include: url.searchParams.get('$include')
@@ -163,13 +156,13 @@ export class OpraHttpAdapter extends OpraAdapter {
163
156
  throw new BadRequestError({ message: e.message });
164
157
  }
165
158
  }
166
- async sendResponse(adapterContext, executionContexts) {
159
+ async sendResponse(executionContext, queryContexts) {
167
160
  const outputPackets = [];
168
- for (const ctx of executionContexts) {
161
+ for (const ctx of queryContexts) {
169
162
  const v = this.createOutput(ctx);
170
163
  outputPackets.push(v);
171
164
  }
172
- if (this.isBatch(adapterContext)) {
165
+ if (this.isBatch(executionContext)) {
173
166
  // this.writeError([], new InternalServerError({message: 'Not implemented yet'}));
174
167
  return;
175
168
  }
@@ -183,7 +176,7 @@ export class OpraHttpAdapter extends OpraAdapter {
183
176
  });
184
177
  }
185
178
  const out = outputPackets[0];
186
- const resp = adapterContext.getResponse();
179
+ const resp = executionContext.getResponseWrapper();
187
180
  resp.setStatus(out.status);
188
181
  resp.setHeader(HttpHeaders.Content_Type, 'application/json');
189
182
  resp.setHeader(HttpHeaders.Cache_Control, 'no-cache');
@@ -198,11 +191,11 @@ export class OpraHttpAdapter extends OpraAdapter {
198
191
  resp.send(JSON.stringify(out.body));
199
192
  }
200
193
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
201
- isBatch(adapterContext) {
194
+ isBatch(executionContext) {
202
195
  return false;
203
196
  }
204
197
  createOutput(ctx) {
205
- const { query } = ctx.request;
198
+ const { query } = ctx;
206
199
  let status = ctx.response.status;
207
200
  let body = ctx.response.value || {};
208
201
  const errors = ctx.response.errors?.map(e => wrapError(e));
@@ -219,7 +212,7 @@ export class OpraHttpAdapter extends OpraAdapter {
219
212
  }
220
213
  else {
221
214
  delete body.errors;
222
- status = status || (query.operationType === 'create' ? HttpStatus.CREATED : HttpStatus.OK);
215
+ status = status || (query.operation === 'create' ? HttpStatus.CREATED : HttpStatus.OK);
223
216
  }
224
217
  body = this.i18n.deep(body);
225
218
  return {
@@ -228,8 +221,8 @@ export class OpraHttpAdapter extends OpraAdapter {
228
221
  body
229
222
  };
230
223
  }
231
- async sendError(adapterContext, error) {
232
- const resp = adapterContext.getResponse();
224
+ async sendError(executionContext, error) {
225
+ const resp = executionContext.getResponseWrapper();
233
226
  resp.setStatus(error.status || 500);
234
227
  resp.setHeader(HttpHeaders.Content_Type, 'application/json');
235
228
  resp.setHeader(HttpHeaders.Cache_Control, 'no-cache');
@@ -0,0 +1,32 @@
1
+ import { SearchParams } from '@opra/url';
2
+ import { HttpStatus } from '../enums/index.js';
3
+ import { ApiException } from '../exception/index.js';
4
+ import { ContextType, IExecutionContext, IHttpExecutionContext } from '../interfaces/execution-context.interface.js';
5
+ import { OpraQuery } from '../interfaces/query.interface.js';
6
+ import { HeadersObject } from '../utils/headers.js';
7
+ import { OpraService } from './opra-service.js';
8
+ export declare type QueryContextArgs = Pick<QueryContext, 'service' | 'executionContext' | 'query' | 'params' | 'headers' | 'userContext' | 'parentValue' | 'continueOnError'>;
9
+ export declare class QueryContext {
10
+ readonly service: OpraService;
11
+ readonly executionContext: IExecutionContext;
12
+ readonly query: OpraQuery;
13
+ readonly params: SearchParams;
14
+ readonly headers: HeadersObject;
15
+ readonly parentValue?: any;
16
+ readonly resultPath: string;
17
+ readonly response: QueryResponse;
18
+ userContext?: any;
19
+ continueOnError?: boolean;
20
+ constructor(args: QueryContextArgs);
21
+ get type(): ContextType;
22
+ switchToHttp(): IHttpExecutionContext;
23
+ }
24
+ export declare type QueryResponseArgs = Pick<QueryResponse, 'status' | 'value' | 'total'>;
25
+ export declare class QueryResponse {
26
+ headers: HeadersObject;
27
+ errors: ApiException[];
28
+ status?: HttpStatus;
29
+ value?: any;
30
+ total?: number;
31
+ constructor(args?: QueryResponseArgs);
32
+ }
@@ -1,36 +1,33 @@
1
1
  import { OpraURLSearchParams } from '@opra/url';
2
2
  import { Headers } from '../utils/headers.js';
3
- export class ExecutionContext {
4
- type;
3
+ export class QueryContext {
5
4
  service;
6
- request;
7
- response;
8
- adapterContext;
9
- userContext;
10
- continueOnError;
11
- constructor(args) {
12
- Object.assign(this, args);
13
- }
14
- switchToHttp() {
15
- if (this.type !== 'http')
16
- throw new Error(`You can't access http context within an ${this.type} context`);
17
- return this.adapterContext;
18
- }
19
- }
20
- export class ExecutionRequest {
5
+ executionContext;
21
6
  query;
22
7
  params;
23
8
  headers;
24
9
  parentValue;
25
10
  resultPath;
11
+ response;
12
+ userContext;
13
+ continueOnError;
26
14
  constructor(args) {
27
15
  Object.assign(this, args);
16
+ this.response = new QueryResponse();
28
17
  this.params = this.params || new OpraURLSearchParams();
29
18
  this.headers = this.headers || Headers.create();
30
19
  this.resultPath = this.resultPath || '';
31
20
  }
21
+ get type() {
22
+ return this.executionContext.getType();
23
+ }
24
+ switchToHttp() {
25
+ if (this.type !== 'http')
26
+ throw new Error(`You can't access http context within an ${this.type} context`);
27
+ return this.executionContext;
28
+ }
32
29
  }
33
- export class ExecutionResponse {
30
+ export class QueryResponse {
34
31
  headers = Headers.create();
35
32
  errors = [];
36
33
  status;
@@ -1,7 +1,7 @@
1
1
  import { StrictOmit } from 'ts-gems';
2
2
  import { OpraSchema } from '@opra/schema';
3
3
  import { IResourceContainer } from '../../interfaces/resource-container.interface.js';
4
- import { ExecutionContext } from '../execution-context.js';
4
+ import { QueryContext } from '../query-context.js';
5
5
  import { EntityResourceHandler } from './entity-resource-handler.js';
6
6
  import { ResourceHandler } from './resource-handler.js';
7
7
  export declare type ContainerResourceControllerArgs = StrictOmit<OpraSchema.ContainerResource, 'kind'> & {};
@@ -10,5 +10,5 @@ export declare class ContainerResourceHandler extends ResourceHandler implements
10
10
  constructor(args: ContainerResourceControllerArgs);
11
11
  getResource<T extends ResourceHandler>(name: string): T;
12
12
  getEntityResource(name: string): EntityResourceHandler;
13
- execute(ctx: ExecutionContext): Promise<void>;
13
+ execute(ctx: QueryContext): Promise<void>;
14
14
  }
@@ -1,8 +1,8 @@
1
1
  import { StrictOmit } from 'ts-gems';
2
2
  import { OpraSchema } from '@opra/schema';
3
3
  import { EntityType } from '../data-type/entity-type.js';
4
- import { ExecutionContext } from '../execution-context.js';
5
4
  import { OpraService } from '../opra-service.js';
5
+ import { QueryContext } from '../query-context.js';
6
6
  import { ResourceHandler } from './resource-handler.js';
7
7
  export declare type EntityResourceControllerArgs = StrictOmit<OpraSchema.EntityResource, 'kind'> & {
8
8
  service: OpraService;
@@ -13,6 +13,6 @@ export declare class EntityResourceHandler extends ResourceHandler {
13
13
  readonly service: OpraService;
14
14
  readonly dataType: EntityType;
15
15
  constructor(args: EntityResourceControllerArgs);
16
- execute(ctx: ExecutionContext): Promise<void>;
17
- _executeFn(ctx: ExecutionContext, fnName: string): Promise<any>;
16
+ execute(ctx: QueryContext): Promise<void>;
17
+ _executeFn(ctx: QueryContext, fnName: string): Promise<any>;
18
18
  }
@@ -1,8 +1,9 @@
1
1
  import _ from 'lodash';
2
- import { ExecutionQuery } from '../../interfaces/execution-query.interface.js';
2
+ import { OpraQuery } from '../../interfaces/query.interface.js';
3
3
  import { EntityType } from '../data-type/entity-type.js';
4
4
  import { ResourceHandler } from './resource-handler.js';
5
- var isSearchQuery = ExecutionQuery.isSearchQuery;
5
+ var isSearchQuery = OpraQuery.isSearchQuery;
6
+ import { UnprocessableEntityError } from '../../exception/index.js';
6
7
  export class EntityResourceHandler extends ResourceHandler {
7
8
  service;
8
9
  dataType;
@@ -18,7 +19,7 @@ export class EntityResourceHandler extends ResourceHandler {
18
19
  throw new TypeError(`You should provide an EntityType for EntityResourceController`);
19
20
  }
20
21
  async execute(ctx) {
21
- const { query } = ctx.request;
22
+ const { query } = ctx;
22
23
  if (isSearchQuery(query)) {
23
24
  const promises = [];
24
25
  let search;
@@ -57,15 +58,18 @@ export class EntityResourceHandler extends ResourceHandler {
57
58
  if (typeof result === 'object')
58
59
  affectedRecords = result.affectedRows || result.affectedRecords;
59
60
  return { affectedRecords };
60
- default:
61
- result = Array.isArray(result) ? result[0] : result;
62
- if (result && ctx.request.resultPath) {
63
- const pathArray = ctx.request.resultPath.split('.');
64
- for (const property of pathArray) {
65
- result = result && typeof result === 'object' && result[property];
66
- }
67
- }
68
- return result;
69
61
  }
62
+ result = Array.isArray(result) ? result[0] : result;
63
+ if (!result)
64
+ throw new UnprocessableEntityError();
65
+ if (ctx.resultPath) {
66
+ const pathArray = ctx.resultPath.split('.');
67
+ for (const property of pathArray) {
68
+ result = result && typeof result === 'object' && result[property];
69
+ }
70
+ }
71
+ if (fnName === 'create')
72
+ ctx.response.status = 201;
73
+ return result;
70
74
  }
71
75
  }
@@ -1,6 +1,6 @@
1
1
  import { OpraSchema } from '@opra/schema';
2
2
  import { nodeInspectCustom } from '../../utils/terminal-utils.js';
3
- import { ExecutionContext } from '../execution-context.js';
3
+ import { QueryContext } from '../query-context.js';
4
4
  export declare abstract class ResourceHandler {
5
5
  protected readonly _args: OpraSchema.Resource & {
6
6
  prepare?: Function;
@@ -9,7 +9,7 @@ export declare abstract class ResourceHandler {
9
9
  get name(): string;
10
10
  get description(): string | undefined;
11
11
  toString(): string;
12
- prepare(ctx: ExecutionContext): Promise<void>;
13
- abstract execute(ctx: ExecutionContext): Promise<void>;
12
+ prepare(ctx: QueryContext): Promise<void>;
13
+ abstract execute(ctx: QueryContext): Promise<void>;
14
14
  [nodeInspectCustom](): string;
15
15
  }
@@ -14,7 +14,7 @@ export class ResourceHandler {
14
14
  return `[${Object.getPrototypeOf(this).constructor.name} ${this.name}]`;
15
15
  }
16
16
  async prepare(ctx) {
17
- const { query } = ctx.request;
17
+ const { query } = ctx;
18
18
  const fn = this._args['pre_' + query.queryType];
19
19
  if (fn && typeof fn === 'function') {
20
20
  await fn(ctx);