@opra/core 0.0.5

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 (139) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/cjs/constants.js +5 -0
  4. package/cjs/decorators/entity-resource.decorator.js +24 -0
  5. package/cjs/enums/http-headers.enum.js +393 -0
  6. package/cjs/enums/http-status.enum.js +300 -0
  7. package/cjs/enums/index.js +5 -0
  8. package/cjs/enums/issue-severity.enum.js +2 -0
  9. package/cjs/exception/api-exception.js +63 -0
  10. package/cjs/exception/errors/bad-request.error.js +22 -0
  11. package/cjs/exception/errors/failed-dependency.error.js +21 -0
  12. package/cjs/exception/errors/forbidden.error.js +23 -0
  13. package/cjs/exception/errors/internal-server.error.js +21 -0
  14. package/cjs/exception/errors/method-not-allowed.error.js +22 -0
  15. package/cjs/exception/errors/not-found.error.js +25 -0
  16. package/cjs/exception/errors/unauthorized.error.js +22 -0
  17. package/cjs/exception/errors/unprocessable-entity.error.js +21 -0
  18. package/cjs/exception/index.js +11 -0
  19. package/cjs/implementation/adapter/adapter.js +72 -0
  20. package/cjs/implementation/adapter/express-adapter.js +93 -0
  21. package/cjs/implementation/adapter/http-adapter.js +262 -0
  22. package/cjs/implementation/data-type/complex-type.js +39 -0
  23. package/cjs/implementation/data-type/data-type.js +35 -0
  24. package/cjs/implementation/data-type/entity-type.js +33 -0
  25. package/cjs/implementation/data-type/simple-type.js +30 -0
  26. package/cjs/implementation/execution-context.js +49 -0
  27. package/cjs/implementation/opra-document.js +116 -0
  28. package/cjs/implementation/opra-service.js +59 -0
  29. package/cjs/implementation/resource/container-resource-controller.js +26 -0
  30. package/cjs/implementation/resource/entity-resource-info.js +68 -0
  31. package/cjs/implementation/resource/resource-info.js +24 -0
  32. package/cjs/implementation/schema-generator.js +173 -0
  33. package/cjs/index.js +25 -0
  34. package/cjs/interfaces/entity-resource.interface.js +2 -0
  35. package/cjs/interfaces/execution-query.interface.js +190 -0
  36. package/cjs/interfaces/http-context.interface.js +2 -0
  37. package/cjs/interfaces/opra-schema.metadata.js +2 -0
  38. package/cjs/interfaces/resource-container.interface.js +2 -0
  39. package/cjs/package.json +3 -0
  40. package/cjs/services/data-service.js +9 -0
  41. package/cjs/services/json-data-service.js +15 -0
  42. package/cjs/types.js +2 -0
  43. package/cjs/utils/class-utils.js +37 -0
  44. package/cjs/utils/headers.js +58 -0
  45. package/cjs/utils/internal-data-types.js +44 -0
  46. package/cjs/utils/responsive-object.js +49 -0
  47. package/cjs/utils/string-path-to-object-tree.js +26 -0
  48. package/cjs/utils/terminal-utils.js +7 -0
  49. package/esm/constants.d.ts +2 -0
  50. package/esm/constants.js +2 -0
  51. package/esm/decorators/entity-resource.decorator.d.ts +5 -0
  52. package/esm/decorators/entity-resource.decorator.js +19 -0
  53. package/esm/enums/http-headers.enum.d.ts +368 -0
  54. package/esm/enums/http-headers.enum.js +390 -0
  55. package/esm/enums/http-status.enum.d.ts +290 -0
  56. package/esm/enums/http-status.enum.js +297 -0
  57. package/esm/enums/index.d.ts +2 -0
  58. package/esm/enums/index.js +2 -0
  59. package/esm/enums/issue-severity.enum.d.ts +1 -0
  60. package/esm/enums/issue-severity.enum.js +1 -0
  61. package/esm/exception/api-exception.d.ts +38 -0
  62. package/esm/exception/api-exception.js +59 -0
  63. package/esm/exception/errors/bad-request.error.d.ts +9 -0
  64. package/esm/exception/errors/bad-request.error.js +18 -0
  65. package/esm/exception/errors/failed-dependency.error.d.ts +8 -0
  66. package/esm/exception/errors/failed-dependency.error.js +17 -0
  67. package/esm/exception/errors/forbidden.error.d.ts +10 -0
  68. package/esm/exception/errors/forbidden.error.js +19 -0
  69. package/esm/exception/errors/internal-server.error.d.ts +8 -0
  70. package/esm/exception/errors/internal-server.error.js +17 -0
  71. package/esm/exception/errors/method-not-allowed.error.d.ts +9 -0
  72. package/esm/exception/errors/method-not-allowed.error.js +18 -0
  73. package/esm/exception/errors/not-found.error.d.ts +12 -0
  74. package/esm/exception/errors/not-found.error.js +21 -0
  75. package/esm/exception/errors/unauthorized.error.d.ts +9 -0
  76. package/esm/exception/errors/unauthorized.error.js +18 -0
  77. package/esm/exception/errors/unprocessable-entity.error.d.ts +8 -0
  78. package/esm/exception/errors/unprocessable-entity.error.js +17 -0
  79. package/esm/exception/index.d.ts +8 -0
  80. package/esm/exception/index.js +8 -0
  81. package/esm/implementation/adapter/adapter.d.ts +18 -0
  82. package/esm/implementation/adapter/adapter.js +68 -0
  83. package/esm/implementation/adapter/express-adapter.d.ts +12 -0
  84. package/esm/implementation/adapter/express-adapter.js +89 -0
  85. package/esm/implementation/adapter/http-adapter.d.ts +27 -0
  86. package/esm/implementation/adapter/http-adapter.js +258 -0
  87. package/esm/implementation/data-type/complex-type.d.ts +18 -0
  88. package/esm/implementation/data-type/complex-type.js +35 -0
  89. package/esm/implementation/data-type/data-type.d.ts +15 -0
  90. package/esm/implementation/data-type/data-type.js +31 -0
  91. package/esm/implementation/data-type/entity-type.d.ts +10 -0
  92. package/esm/implementation/data-type/entity-type.js +29 -0
  93. package/esm/implementation/data-type/simple-type.d.ts +15 -0
  94. package/esm/implementation/data-type/simple-type.js +26 -0
  95. package/esm/implementation/execution-context.d.ts +42 -0
  96. package/esm/implementation/execution-context.js +43 -0
  97. package/esm/implementation/opra-document.d.ts +26 -0
  98. package/esm/implementation/opra-document.js +111 -0
  99. package/esm/implementation/opra-service.d.ts +19 -0
  100. package/esm/implementation/opra-service.js +55 -0
  101. package/esm/implementation/resource/container-resource-controller.d.ts +12 -0
  102. package/esm/implementation/resource/container-resource-controller.js +22 -0
  103. package/esm/implementation/resource/entity-resource-info.d.ts +24 -0
  104. package/esm/implementation/resource/entity-resource-info.js +63 -0
  105. package/esm/implementation/resource/resource-info.d.ts +10 -0
  106. package/esm/implementation/resource/resource-info.js +20 -0
  107. package/esm/implementation/schema-generator.d.ts +21 -0
  108. package/esm/implementation/schema-generator.js +169 -0
  109. package/esm/index.d.ts +22 -0
  110. package/esm/index.js +22 -0
  111. package/esm/interfaces/entity-resource.interface.d.ts +9 -0
  112. package/esm/interfaces/entity-resource.interface.js +1 -0
  113. package/esm/interfaces/execution-query.interface.d.ts +102 -0
  114. package/esm/interfaces/execution-query.interface.js +186 -0
  115. package/esm/interfaces/http-context.interface.d.ts +23 -0
  116. package/esm/interfaces/http-context.interface.js +1 -0
  117. package/esm/interfaces/opra-schema.metadata.d.ts +14 -0
  118. package/esm/interfaces/opra-schema.metadata.js +1 -0
  119. package/esm/interfaces/resource-container.interface.d.ts +6 -0
  120. package/esm/interfaces/resource-container.interface.js +1 -0
  121. package/esm/services/data-service.d.ts +2 -0
  122. package/esm/services/data-service.js +5 -0
  123. package/esm/services/json-data-service.d.ts +9 -0
  124. package/esm/services/json-data-service.js +10 -0
  125. package/esm/types.d.ts +11 -0
  126. package/esm/types.js +1 -0
  127. package/esm/utils/class-utils.d.ts +6 -0
  128. package/esm/utils/class-utils.js +30 -0
  129. package/esm/utils/headers.d.ts +9 -0
  130. package/esm/utils/headers.js +55 -0
  131. package/esm/utils/internal-data-types.d.ts +4 -0
  132. package/esm/utils/internal-data-types.js +41 -0
  133. package/esm/utils/responsive-object.d.ts +3 -0
  134. package/esm/utils/responsive-object.js +45 -0
  135. package/esm/utils/string-path-to-object-tree.d.ts +4 -0
  136. package/esm/utils/string-path-to-object-tree.js +22 -0
  137. package/esm/utils/terminal-utils.d.ts +4 -0
  138. package/esm/utils/terminal-utils.js +4 -0
  139. package/package.json +77 -0
@@ -0,0 +1,8 @@
1
+ export * from './api-exception.js';
2
+ export * from './errors/bad-request.error.js';
3
+ export * from './errors/failed-dependency.error.js';
4
+ export * from './errors/internal-server.error.js';
5
+ export * from './errors/method-not-allowed.error.js';
6
+ export * from './errors/not-found.error.js';
7
+ export * from './errors/unauthorized.error.js';
8
+ export * from './errors/unprocessable-entity.error.js';
@@ -0,0 +1,8 @@
1
+ export * from './api-exception.js';
2
+ export * from './errors/bad-request.error.js';
3
+ export * from './errors/failed-dependency.error.js';
4
+ export * from './errors/internal-server.error.js';
5
+ export * from './errors/method-not-allowed.error.js';
6
+ export * from './errors/not-found.error.js';
7
+ export * from './errors/unauthorized.error.js';
8
+ export * from './errors/unprocessable-entity.error.js';
@@ -0,0 +1,18 @@
1
+ import { I18n } from '@opra/i18n';
2
+ import { ApiException } from '../../exception/index.js';
3
+ import { ExecutionContext } from '../execution-context.js';
4
+ import { OpraService } from '../opra-service.js';
5
+ export declare namespace OpraAdapter {
6
+ interface Options {
7
+ i18n?: I18n;
8
+ }
9
+ }
10
+ export declare abstract class OpraAdapter<TAdapterContext = any, TOptions extends OpraAdapter.Options = OpraAdapter.Options> {
11
+ readonly service: OpraService;
12
+ i18n: I18n;
13
+ constructor(service: OpraService, options?: TOptions);
14
+ protected abstract prepareExecutionContexts(adapterContext: TAdapterContext, userContext: any): ExecutionContext[];
15
+ protected abstract sendResponse(adapterContext: TAdapterContext, executionContexts: ExecutionContext[]): Promise<void>;
16
+ protected abstract sendError(adapterContext: TAdapterContext, error: ApiException): Promise<void>;
17
+ protected handler(adapterContext: TAdapterContext, userContext: any): Promise<void>;
18
+ }
@@ -0,0 +1,68 @@
1
+ import { isPromise } from 'util/types';
2
+ import { I18n } from '@opra/i18n';
3
+ import { ApiException, FailedDependencyError, InternalServerError } from '../../exception/index.js';
4
+ export class OpraAdapter {
5
+ service;
6
+ i18n;
7
+ constructor(service, options) {
8
+ this.service = service;
9
+ this.i18n = options?.i18n || I18n.defaultInstance;
10
+ }
11
+ async handler(adapterContext, userContext) {
12
+ let executionContexts;
13
+ try {
14
+ executionContexts = this.prepareExecutionContexts(adapterContext, userContext);
15
+ }
16
+ catch (e) {
17
+ const error = InternalServerError.wrap(e);
18
+ await this.sendError(adapterContext, error);
19
+ return;
20
+ }
21
+ let stop = false;
22
+ // Read requests can be executed simultaneously, write request should be executed one by one
23
+ let promises;
24
+ let exclusive = false;
25
+ for (const ctx of executionContexts) {
26
+ const request = ctx.request;
27
+ const response = ctx.response;
28
+ exclusive = exclusive || request.query.operationType !== 'read';
29
+ try {
30
+ // Wait previous read requests before executing update request
31
+ if (exclusive && promises) {
32
+ await Promise.all(promises);
33
+ promises = undefined;
34
+ }
35
+ // If previous request in bucket had an error and executed an update
36
+ // we do not execute next requests
37
+ if (stop) {
38
+ response.errors.push(new FailedDependencyError());
39
+ }
40
+ else {
41
+ const resource = ctx.request.query.resource;
42
+ const v = resource.execute(ctx);
43
+ if (isPromise(v)) {
44
+ if (exclusive)
45
+ await v;
46
+ else {
47
+ promises = promises || [];
48
+ promises.push(v);
49
+ }
50
+ }
51
+ // todo execute sub property queries
52
+ }
53
+ }
54
+ catch (e) {
55
+ response.errors.unshift(ApiException.wrap(e));
56
+ }
57
+ if (response.errors && response.errors.length) {
58
+ // noinspection SuspiciousTypeOfGuard
59
+ response.errors = response.errors.map(e => ApiException.wrap(e));
60
+ if (exclusive)
61
+ stop = stop || !!response.errors.find(e => !(e.response.severity === 'warning' || e.response.severity === 'info'));
62
+ }
63
+ }
64
+ if (promises)
65
+ await Promise.all(promises);
66
+ await this.sendResponse(adapterContext, executionContexts);
67
+ }
68
+ }
@@ -0,0 +1,12 @@
1
+ import type { Application, Request } from 'express';
2
+ import type { IHttpAdapterContext } from '../../interfaces/http-context.interface.js';
3
+ import type { OpraService } from '../opra-service.js';
4
+ import { OpraHttpAdapter } from './http-adapter.js';
5
+ export declare namespace OpraExpressAdapter {
6
+ interface Options extends OpraHttpAdapter.Options {
7
+ userContext?: (request: Request) => object | Promise<object>;
8
+ }
9
+ }
10
+ export declare class OpraExpressAdapter extends OpraHttpAdapter<IHttpAdapterContext, OpraExpressAdapter.Options> {
11
+ static init(app: Application, service: OpraService, options?: OpraExpressAdapter.Options): OpraExpressAdapter;
12
+ }
@@ -0,0 +1,89 @@
1
+ import { normalizePath } from '@opra/url';
2
+ import { OpraHttpAdapter } from './http-adapter.js';
3
+ export class OpraExpressAdapter extends OpraHttpAdapter {
4
+ static init(app, service, options) {
5
+ const adapter = new OpraExpressAdapter(service, options);
6
+ const prefix = '/' + normalizePath(options?.prefix, true);
7
+ const userContextResolver = options?.userContext;
8
+ app.use(prefix, (request, response, next) => {
9
+ (async () => {
10
+ const userContext = userContextResolver && await userContextResolver(request);
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, userContext);
18
+ })().catch(e => next(e));
19
+ });
20
+ return adapter;
21
+ }
22
+ }
23
+ class ExpressRequestWrapper {
24
+ instance;
25
+ constructor(instance) {
26
+ this.instance = instance;
27
+ }
28
+ getInstance() {
29
+ return this.instance;
30
+ }
31
+ getMethod() {
32
+ return this.instance.method;
33
+ }
34
+ getUrl() {
35
+ return this.instance.url;
36
+ }
37
+ getHeaderNames() {
38
+ return Object.keys(this.instance.headers);
39
+ }
40
+ getHeader(name) {
41
+ return this.instance.get(name);
42
+ }
43
+ getHeaders() {
44
+ return this.instance.headers;
45
+ }
46
+ getBody() {
47
+ return this.instance.body;
48
+ }
49
+ }
50
+ class ExpressResponseWrapper {
51
+ instance;
52
+ constructor(instance) {
53
+ this.instance = instance;
54
+ }
55
+ getInstance() {
56
+ return this.instance;
57
+ }
58
+ getHeaderNames() {
59
+ return this.instance.getHeaderNames();
60
+ }
61
+ getHeader(name) {
62
+ return this.instance.get(name);
63
+ }
64
+ setHeader(name, value) {
65
+ this.instance.setHeader(name, value);
66
+ return this;
67
+ }
68
+ getStatus() {
69
+ return this.instance.statusCode;
70
+ }
71
+ setStatus(value) {
72
+ // noinspection SuspiciousTypeOfGuard
73
+ this.instance.status(typeof value === 'number'
74
+ ? value
75
+ : parseInt(value, 10) || 500);
76
+ return this;
77
+ }
78
+ send(body) {
79
+ if (typeof body === 'string' || Buffer.isBuffer(body))
80
+ this.instance.send(body);
81
+ else
82
+ this.instance.json(body);
83
+ return this;
84
+ }
85
+ end() {
86
+ this.instance.end();
87
+ return this;
88
+ }
89
+ }
@@ -0,0 +1,27 @@
1
+ import { OpraURL } from '@opra/url';
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';
5
+ import { HeadersObject } from '../../utils/headers.js';
6
+ import { ExecutionContext } from '../execution-context.js';
7
+ import { OpraAdapter } from './adapter.js';
8
+ export declare namespace OpraHttpAdapter {
9
+ type Options = OpraAdapter.Options & {
10
+ prefix?: string;
11
+ };
12
+ }
13
+ interface PreparedOutput {
14
+ status: number;
15
+ headers?: Record<string, string>;
16
+ body?: any;
17
+ }
18
+ export declare class OpraHttpAdapter<TAdapterContext extends IHttpAdapterContext, TOptions extends OpraHttpAdapter.Options = OpraHttpAdapter.Options> extends OpraAdapter<IHttpAdapterContext, TOptions> {
19
+ protected prepareExecutionContexts(adapterContext: TAdapterContext, userContext: any): ExecutionContext[];
20
+ prepareExecutionContext(adapterContext: any, url: OpraURL, method: string, headers: HeadersObject, body?: any, userContext?: any): ExecutionContext;
21
+ buildQuery(url: OpraURL, method: string, body?: any): ExecutionQuery | undefined;
22
+ protected sendResponse(adapterContext: TAdapterContext, executionContexts: ExecutionContext[]): Promise<void>;
23
+ protected isBatch(adapterContext: any): boolean;
24
+ protected createOutput(ctx: ExecutionContext): PreparedOutput;
25
+ protected sendError(adapterContext: TAdapterContext, error: ApiException): Promise<void>;
26
+ }
27
+ export {};
@@ -0,0 +1,258 @@
1
+ import { OpraURL } from '@opra/url';
2
+ import { OpraVersion } from '../../constants.js';
3
+ import { HttpHeaders, HttpStatus } from '../../enums/index.js';
4
+ import { ApiException, BadRequestError, InternalServerError, MethodNotAllowedError, NotFoundError, } from '../../exception/index.js';
5
+ import { ExecutionQuery } from '../../interfaces/execution-query.interface.js';
6
+ import { Headers } from '../../utils/headers.js';
7
+ import { ComplexType } from '../data-type/complex-type.js';
8
+ import { ExecutionContext, ExecutionRequest, ExecutionResponse } from '../execution-context.js';
9
+ import { ContainerResourceController } from '../resource/container-resource-controller.js';
10
+ import { EntityResourceInfo } from '../resource/entity-resource-info.js';
11
+ import { OpraAdapter } from './adapter.js';
12
+ export class OpraHttpAdapter extends OpraAdapter {
13
+ prepareExecutionContexts(adapterContext, userContext) {
14
+ const req = adapterContext.getRequest();
15
+ // todo implement batch requests
16
+ if (this.isBatch(adapterContext)) {
17
+ throw new Error('not implemented yet');
18
+ }
19
+ const url = new OpraURL(req.getUrl());
20
+ return [this.prepareExecutionContext(adapterContext, url, req.getMethod(), Headers.from(req.getHeaders()), req.getBody(), userContext)];
21
+ }
22
+ prepareExecutionContext(adapterContext, url, method, headers, body, userContext) {
23
+ if (!url.path.size)
24
+ throw new BadRequestError();
25
+ if (method !== 'GET' && url.path.size > 1)
26
+ throw new BadRequestError();
27
+ const query = this.buildQuery(url, method, body);
28
+ if (!query)
29
+ throw new MethodNotAllowedError({
30
+ message: `Method "${method}" is not allowed by target resource`
31
+ });
32
+ const request = new ExecutionRequest({
33
+ query,
34
+ headers,
35
+ params: url.searchParams,
36
+ });
37
+ const response = new ExecutionResponse();
38
+ // noinspection UnnecessaryLocalVariableJS
39
+ const executionContext = new ExecutionContext({
40
+ type: 'http',
41
+ service: this.service,
42
+ request,
43
+ response,
44
+ adapterContext,
45
+ userContext,
46
+ continueOnError: request.query.operationType === 'read'
47
+ });
48
+ return executionContext;
49
+ }
50
+ buildQuery(url, method, body) {
51
+ let container = this.service;
52
+ try {
53
+ let pathIndex = 0;
54
+ const pathLen = url.path.size;
55
+ while (pathIndex < pathLen) {
56
+ let p = url.path.get(pathIndex++);
57
+ const resource = container.getResource(p.resource);
58
+ // Move through path directories (containers)
59
+ if (resource instanceof ContainerResourceController) {
60
+ container = resource;
61
+ }
62
+ else {
63
+ method = method.toUpperCase();
64
+ if (resource instanceof EntityResourceInfo) {
65
+ const scope = p.key ? 'instance' : 'collection';
66
+ if (pathIndex < pathLen && !(method === 'GET' && scope === 'instance'))
67
+ return;
68
+ let query;
69
+ switch (method) {
70
+ case 'GET': {
71
+ if (scope === 'collection') {
72
+ query = ExecutionQuery.forSearch(resource, {
73
+ filter: url.searchParams.get('$filter'),
74
+ limit: url.searchParams.get('$limit'),
75
+ skip: url.searchParams.get('$skip'),
76
+ distinct: url.searchParams.get('$distinct'),
77
+ total: url.searchParams.get('$total'),
78
+ sort: url.searchParams.get('$sort'),
79
+ pick: url.searchParams.get('$pick'),
80
+ omit: url.searchParams.get('$omit'),
81
+ include: url.searchParams.get('$include'),
82
+ });
83
+ }
84
+ else {
85
+ query = ExecutionQuery.forRead(resource, p.key, {
86
+ pick: url.searchParams.get('$pick'),
87
+ omit: url.searchParams.get('$omit'),
88
+ include: url.searchParams.get('$include')
89
+ });
90
+ // Move through properties
91
+ let nested;
92
+ let path = resource.name;
93
+ while (pathIndex < pathLen) {
94
+ const dataType = nested
95
+ ? this.service.getDataType(nested.property.type || 'string')
96
+ : query.resource.dataType;
97
+ if (!(dataType instanceof ComplexType))
98
+ throw new Error(`"${path}" is not a ComplexType and has no properties.`);
99
+ p = url.path.get(pathIndex++);
100
+ path += '.' + p.resource;
101
+ const prop = dataType.properties?.[p.resource];
102
+ if (!prop)
103
+ throw new NotFoundError({ message: `Invalid or unknown resource path (${path})` });
104
+ const q = ExecutionQuery.forProperty(prop);
105
+ if (nested) {
106
+ nested.nested = q;
107
+ }
108
+ else {
109
+ query.nested = q;
110
+ }
111
+ nested = q;
112
+ }
113
+ }
114
+ break;
115
+ }
116
+ case 'DELETE': {
117
+ if (scope === 'collection') {
118
+ query = ExecutionQuery.forDeleteMany(resource, {
119
+ filter: url.searchParams.get('$filter'),
120
+ });
121
+ }
122
+ else {
123
+ query = ExecutionQuery.forDelete(resource, p.key);
124
+ }
125
+ break;
126
+ }
127
+ case 'POST': {
128
+ if (scope === 'collection') {
129
+ query = ExecutionQuery.forCreate(resource, body, {
130
+ pick: url.searchParams.get('$pick'),
131
+ omit: url.searchParams.get('$omit'),
132
+ include: url.searchParams.get('$include')
133
+ });
134
+ }
135
+ break;
136
+ }
137
+ case 'PATCH': {
138
+ if (scope === 'collection') {
139
+ query = ExecutionQuery.forUpdateMany(resource, body, {
140
+ filter: url.searchParams.get('$filter')
141
+ });
142
+ }
143
+ else {
144
+ query = ExecutionQuery.forUpdate(resource, p.key, body, {
145
+ pick: url.searchParams.get('$pick'),
146
+ omit: url.searchParams.get('$omit'),
147
+ include: url.searchParams.get('$include')
148
+ });
149
+ }
150
+ break;
151
+ }
152
+ }
153
+ return query;
154
+ }
155
+ }
156
+ }
157
+ throw new InternalServerError();
158
+ }
159
+ catch (e) {
160
+ if (e instanceof ApiException)
161
+ throw e;
162
+ throw new BadRequestError({ message: e.message });
163
+ }
164
+ }
165
+ async sendResponse(adapterContext, executionContexts) {
166
+ const outputPackets = [];
167
+ for (const ctx of executionContexts) {
168
+ const v = this.createOutput(ctx);
169
+ outputPackets.push(v);
170
+ }
171
+ if (this.isBatch(adapterContext)) {
172
+ // this.writeError([], new InternalServerError({message: 'Not implemented yet'}));
173
+ return;
174
+ }
175
+ if (!outputPackets.length) {
176
+ const err = new NotFoundError();
177
+ outputPackets.push({
178
+ status: err.status,
179
+ body: {
180
+ errors: [err.response]
181
+ }
182
+ });
183
+ }
184
+ const out = outputPackets[0];
185
+ const resp = adapterContext.getResponse();
186
+ resp.setStatus(out.status);
187
+ resp.setHeader(HttpHeaders.Content_Type, 'application/json');
188
+ resp.setHeader(HttpHeaders.Cache_Control, 'no-cache');
189
+ resp.setHeader(HttpHeaders.Pragma, 'no-cache');
190
+ resp.setHeader(HttpHeaders.Expires, '-1');
191
+ resp.setHeader(HttpHeaders.X_Opra_Version, OpraVersion);
192
+ if (out.headers) {
193
+ for (const [k, v] of Object.entries(out.headers)) {
194
+ resp.setHeader(k, v);
195
+ }
196
+ }
197
+ resp.send(JSON.stringify(out.body));
198
+ }
199
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
200
+ isBatch(adapterContext) {
201
+ return false;
202
+ }
203
+ createOutput(ctx) {
204
+ const { query } = ctx.request;
205
+ // Determine response status
206
+ let status = ctx.response.status;
207
+ if (ctx.response.errors.length) {
208
+ if (!status || status < 400) {
209
+ status = 0;
210
+ for (const e of ctx.response.errors) {
211
+ status = Math.max(status, e.status || status);
212
+ }
213
+ if (status < HttpStatus.BAD_REQUEST)
214
+ status = HttpStatus.INTERNAL_SERVER_ERROR;
215
+ }
216
+ }
217
+ else
218
+ status = status || (query.operationType === 'create' ? HttpStatus.CREATED : HttpStatus.OK);
219
+ let body;
220
+ let value = ctx.response.value;
221
+ if (query.queryType === 'search') {
222
+ body = {
223
+ // '@origin': ctx.resource.name + (ctx.request.resultPath ? '.' + ctx.request.resultPath : ''),
224
+ items: ctx.response.value,
225
+ total: ctx.response.total
226
+ };
227
+ }
228
+ else {
229
+ // Move to sub property if result path defined
230
+ if (value && ctx.request.resultPath) {
231
+ const pathArray = ctx.request.resultPath.split('.');
232
+ for (const property of pathArray) {
233
+ value = value && typeof value === 'object' && value[property];
234
+ }
235
+ }
236
+ body = value;
237
+ }
238
+ if (ctx.response.errors?.length) {
239
+ body = body || {};
240
+ body.errors = ctx.response.errors.map(e => e.response);
241
+ }
242
+ return {
243
+ status,
244
+ headers: ctx.response.headers,
245
+ body
246
+ };
247
+ }
248
+ async sendError(adapterContext, error) {
249
+ const resp = adapterContext.getResponse();
250
+ resp.setStatus(error.status || 500);
251
+ resp.setHeader(HttpHeaders.Content_Type, 'application/json');
252
+ resp.setHeader(HttpHeaders.Cache_Control, 'no-cache');
253
+ resp.setHeader(HttpHeaders.Pragma, 'no-cache');
254
+ resp.setHeader(HttpHeaders.Expires, '-1');
255
+ resp.setHeader(HttpHeaders.X_Opra_Version, OpraVersion);
256
+ resp.send(JSON.stringify(error.response));
257
+ }
258
+ }
@@ -0,0 +1,18 @@
1
+ import { StrictOmit } from 'ts-gems';
2
+ import { OpraSchema } from '@opra/schema';
3
+ import { ResponsiveObject } from '../../utils/responsive-object.js';
4
+ import { nodeInspectCustom } from '../../utils/terminal-utils.js';
5
+ import type { OpraDocument } from '../opra-document.js';
6
+ import { DataType } from './data-type.js';
7
+ export declare type ComplexTypeArgs = StrictOmit<OpraSchema.ComplexType, 'kind'>;
8
+ export declare class ComplexType extends DataType {
9
+ protected readonly _args: StrictOmit<ComplexTypeArgs, 'properties'>;
10
+ readonly ownProperties?: ResponsiveObject<OpraSchema.Property>;
11
+ readonly properties?: ResponsiveObject<OpraSchema.Property>;
12
+ constructor(owner: OpraDocument, args: ComplexTypeArgs, base?: ComplexType);
13
+ get abstract(): boolean;
14
+ get additionalProperties(): boolean | string | Pick<OpraSchema.Property, 'type' | 'format' | 'isArray' | 'enum'> | undefined;
15
+ getProperty(name: string): OpraSchema.Property;
16
+ toString(): string;
17
+ [nodeInspectCustom](): string;
18
+ }
@@ -0,0 +1,35 @@
1
+ import { Responsive } from '../../utils/responsive-object.js';
2
+ import { colorFgMagenta, colorFgYellow, colorReset, nodeInspectCustom } from '../../utils/terminal-utils.js';
3
+ import { DataType } from './data-type.js';
4
+ export class ComplexType extends DataType {
5
+ ownProperties;
6
+ properties;
7
+ constructor(owner, args, base) {
8
+ super(owner, {
9
+ kind: 'ComplexType',
10
+ ...args
11
+ }, base);
12
+ this.ownProperties = args?.properties && Responsive(args.properties);
13
+ this.properties = (base?.properties || this.ownProperties) &&
14
+ Responsive({ ...base?.properties, ...this.ownProperties });
15
+ }
16
+ get abstract() {
17
+ return !!this._args.abstract;
18
+ }
19
+ get additionalProperties() {
20
+ return this._args.additionalProperties;
21
+ }
22
+ getProperty(name) {
23
+ const t = this.properties?.[name];
24
+ if (!t)
25
+ throw new Error(`"${this.name}" type has no property named "${name}"`);
26
+ return t;
27
+ }
28
+ toString() {
29
+ return `[${Object.getPrototypeOf(this).constructor.name} ${this.name}]`;
30
+ }
31
+ [nodeInspectCustom]() {
32
+ return `[${colorFgYellow + Object.getPrototypeOf(this).constructor.name + colorReset}` +
33
+ ` ${colorFgMagenta + this.name + colorReset}]`;
34
+ }
35
+ }
@@ -0,0 +1,15 @@
1
+ import { Type } from 'ts-gems';
2
+ import { OpraSchema } from '@opra/schema';
3
+ import type { OpraDocument } from '../opra-document.js';
4
+ export declare abstract class DataType {
5
+ protected _owner: OpraDocument;
6
+ protected readonly _args: any;
7
+ readonly base?: DataType;
8
+ protected constructor(owner: OpraDocument, args: OpraSchema.DataType, base?: DataType);
9
+ get owner(): OpraDocument;
10
+ get kind(): OpraSchema.DataTypeKind;
11
+ get name(): string;
12
+ get description(): string | undefined;
13
+ get ctor(): Type;
14
+ is(typeName: string): boolean;
15
+ }
@@ -0,0 +1,31 @@
1
+ export class DataType {
2
+ _owner;
3
+ _args;
4
+ base;
5
+ constructor(owner, args, base) {
6
+ this._args = { ...args };
7
+ this._owner = owner;
8
+ if (base) {
9
+ this.base = base;
10
+ Object.setPrototypeOf(this._args, base._args);
11
+ }
12
+ }
13
+ get owner() {
14
+ return this._owner;
15
+ }
16
+ get kind() {
17
+ return this._args.kind;
18
+ }
19
+ get name() {
20
+ return this._args.name;
21
+ }
22
+ get description() {
23
+ return this._args.description;
24
+ }
25
+ get ctor() {
26
+ return this._args.ctor;
27
+ }
28
+ is(typeName) {
29
+ return this.name === typeName || !!(this.base && this.base.is(typeName));
30
+ }
31
+ }
@@ -0,0 +1,10 @@
1
+ import { StrictOmit } from 'ts-gems';
2
+ import { OpraSchema } from '@opra/schema';
3
+ import { OpraDocument } from '../opra-document.js';
4
+ import { ComplexType } from './complex-type.js';
5
+ export declare type EntityTypeArgs = StrictOmit<OpraSchema.EntityType, 'kind'>;
6
+ export declare class EntityType extends ComplexType {
7
+ protected readonly _args: StrictOmit<EntityTypeArgs, 'properties'>;
8
+ constructor(owner: OpraDocument, args: EntityTypeArgs, base?: ComplexType | EntityType);
9
+ get primaryKey(): string;
10
+ }
@@ -0,0 +1,29 @@
1
+ import { SqbConnect } from '@opra/optionals';
2
+ import { ComplexType } from './complex-type.js';
3
+ export class EntityType extends ComplexType {
4
+ constructor(owner, args, base) {
5
+ super(owner, {
6
+ ...args
7
+ }, base);
8
+ this._args.kind = 'EntityType';
9
+ // Try to determine primary key info from SQB
10
+ if (args.ctor) {
11
+ const sqbEntity = SqbConnect.EntityMetadata.get(args.ctor);
12
+ if (sqbEntity?.indexes) {
13
+ const primaryIndex = sqbEntity.indexes.find(x => x.primary);
14
+ if (primaryIndex) {
15
+ if (primaryIndex.columns.length > 1)
16
+ throw new TypeError(`Multi-key indexes is not implemented yet`);
17
+ this._args.primaryKey = primaryIndex.columns[0];
18
+ }
19
+ }
20
+ }
21
+ if (!this.primaryKey)
22
+ throw new TypeError(`You must provide primaryKey fo "${this.name}" entity`);
23
+ if (!this.getProperty(this.primaryKey))
24
+ throw new TypeError(`"${this.name}" entity has no such property named "${this.primaryKey}" which defined as primary key`);
25
+ }
26
+ get primaryKey() {
27
+ return this._args.primaryKey;
28
+ }
29
+ }
@@ -0,0 +1,15 @@
1
+ import { StrictOmit } from 'ts-gems';
2
+ import { OpraSchema } from '@opra/schema';
3
+ import { nodeInspectCustom } from '../../utils/terminal-utils.js';
4
+ import type { OpraDocument } from '../opra-document.js';
5
+ import { DataType } from './data-type.js';
6
+ export declare class SimpleType extends DataType {
7
+ protected readonly _args: OpraSchema.SimpleType;
8
+ readonly base?: SimpleType;
9
+ constructor(owner: OpraDocument, args: StrictOmit<OpraSchema.SimpleType, 'kind'>, base?: SimpleType);
10
+ get type(): 'boolean' | 'number' | 'integer' | 'string';
11
+ get format(): string | undefined;
12
+ get default(): boolean | number | string | undefined;
13
+ toString(): string;
14
+ [nodeInspectCustom](): string;
15
+ }