@opra/core 0.0.12 → 0.0.13

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 (69) hide show
  1. package/cjs/constants.js +2 -1
  2. package/cjs/decorators/{entity-resource.decorator.js → api-entity-resource.decorator.js} +0 -0
  3. package/cjs/decorators/api-resolver.decorator.js +13 -0
  4. package/cjs/exception/http-errors/not-acceptable.error.js +26 -0
  5. package/cjs/exception/index.js +2 -0
  6. package/cjs/exception/resource-errors/resource-not-found.error.js +19 -0
  7. package/cjs/implementation/adapter/adapter.js +31 -1
  8. package/cjs/implementation/adapter/http-adapter.js +117 -90
  9. package/cjs/implementation/data-type/complex-type.js +14 -0
  10. package/cjs/implementation/data-type/data-type.js +12 -0
  11. package/cjs/implementation/data-type/entity-type.js +4 -3
  12. package/cjs/implementation/opra-document.js +2 -6
  13. package/cjs/implementation/opra-service.js +22 -0
  14. package/cjs/implementation/resource/entity-resource-handler.js +35 -12
  15. package/cjs/implementation/resource/resource-handler.js +14 -0
  16. package/cjs/implementation/schema-generator.js +32 -11
  17. package/cjs/index.js +1 -1
  18. package/cjs/interfaces/{opra-schema.metadata.js → metadata/api-resolver.metadata.js} +0 -0
  19. package/cjs/interfaces/metadata/opra-schema.metadata.js +11 -0
  20. package/cjs/interfaces/query.interface.js +19 -2
  21. package/cjs/services/json-data-service.js +235 -4
  22. package/cjs/utils/create-i18n.js +1 -1
  23. package/cjs/utils/get-caller-file.util.js +6 -1
  24. package/esm/constants.d.ts +1 -0
  25. package/esm/constants.js +1 -0
  26. package/esm/decorators/{entity-resource.decorator.d.ts → api-entity-resource.decorator.d.ts} +1 -1
  27. package/esm/decorators/{entity-resource.decorator.js → api-entity-resource.decorator.js} +0 -0
  28. package/esm/decorators/api-resolver.decorator.d.ts +2 -0
  29. package/esm/decorators/api-resolver.decorator.js +9 -0
  30. package/esm/exception/http-errors/not-acceptable.error.d.ts +10 -0
  31. package/esm/exception/http-errors/not-acceptable.error.js +22 -0
  32. package/esm/exception/index.d.ts +2 -0
  33. package/esm/exception/index.js +2 -0
  34. package/esm/exception/resource-errors/resource-not-found.error.d.ts +4 -0
  35. package/esm/exception/resource-errors/resource-not-found.error.js +15 -0
  36. package/esm/implementation/adapter/adapter.d.ts +1 -5
  37. package/esm/implementation/adapter/adapter.js +32 -2
  38. package/esm/implementation/adapter/http-adapter.d.ts +2 -1
  39. package/esm/implementation/adapter/http-adapter.js +118 -91
  40. package/esm/implementation/data-type/complex-type.d.ts +1 -0
  41. package/esm/implementation/data-type/complex-type.js +13 -0
  42. package/esm/implementation/data-type/data-type.d.ts +2 -0
  43. package/esm/implementation/data-type/data-type.js +12 -0
  44. package/esm/implementation/data-type/entity-type.js +3 -3
  45. package/esm/implementation/opra-document.js +2 -6
  46. package/esm/implementation/opra-service.d.ts +1 -0
  47. package/esm/implementation/opra-service.js +21 -0
  48. package/esm/implementation/resource/entity-resource-handler.d.ts +1 -1
  49. package/esm/implementation/resource/entity-resource-handler.js +35 -12
  50. package/esm/implementation/resource/resource-handler.d.ts +3 -3
  51. package/esm/implementation/resource/resource-handler.js +13 -0
  52. package/esm/implementation/schema-generator.js +33 -12
  53. package/esm/index.d.ts +1 -1
  54. package/esm/index.js +1 -1
  55. package/esm/interfaces/metadata/api-resolver.metadata.d.ts +3 -0
  56. package/esm/interfaces/{opra-schema.metadata.js → metadata/api-resolver.metadata.js} +0 -0
  57. package/esm/interfaces/metadata/opra-schema.metadata.d.ts +7 -0
  58. package/esm/interfaces/metadata/opra-schema.metadata.js +10 -0
  59. package/esm/interfaces/query.interface.d.ts +12 -5
  60. package/esm/interfaces/query.interface.js +19 -2
  61. package/esm/services/json-data-service.d.ts +66 -7
  62. package/esm/services/json-data-service.js +235 -4
  63. package/esm/types.d.ts +10 -1
  64. package/esm/utils/create-i18n.js +1 -1
  65. package/esm/utils/get-caller-file.util.d.ts +1 -1
  66. package/esm/utils/get-caller-file.util.js +6 -1
  67. package/i18n/en/error.json +3 -0
  68. package/package.json +6 -7
  69. package/esm/interfaces/opra-schema.metadata.d.ts +0 -14
@@ -1,7 +1,7 @@
1
1
  import { OpraURL } from '@opra/url';
2
2
  import { OpraVersion } from '../../constants.js';
3
3
  import { HttpHeaders, HttpStatus } from '../../enums/index.js';
4
- import { ApiException, BadRequestError, InternalServerError, MethodNotAllowedError, NotFoundError, } from '../../exception/index.js';
4
+ import { BadRequestError, InternalServerError, MethodNotAllowedError, NotFoundError, } from '../../exception/index.js';
5
5
  import { wrapError } from '../../exception/wrap-error.js';
6
6
  import { OpraQuery } from '../../interfaces/query.interface.js';
7
7
  import { Headers } from '../../utils/headers.js';
@@ -30,7 +30,7 @@ export class OpraHttpAdapter extends OpraAdapter {
30
30
  const query = this.buildQuery(url, method, body);
31
31
  if (!query)
32
32
  throw new MethodNotAllowedError({
33
- message: `Method "${method}" is not allowed by target resource`
33
+ message: `Method "${method}" is not allowed by target endpoint`
34
34
  });
35
35
  return new QueryContext({
36
36
  service: this.service,
@@ -41,119 +41,146 @@ export class OpraHttpAdapter extends OpraAdapter {
41
41
  continueOnError: query.operation === 'read'
42
42
  });
43
43
  }
44
+ buildMetadataQuery(url) {
45
+ const pathLen = url.path.size;
46
+ const resourcePath = [];
47
+ let pathIndex = 0;
48
+ while (pathIndex < pathLen) {
49
+ const p = url.path.get(pathIndex++);
50
+ if (p.key)
51
+ throw new BadRequestError();
52
+ if (p.resource !== '$metadata') {
53
+ if (pathIndex === 1)
54
+ resourcePath.push('resources');
55
+ resourcePath.push(p.resource);
56
+ }
57
+ }
58
+ const opts = {
59
+ pick: url.searchParams.get('$pick'),
60
+ omit: url.searchParams.get('$omit'),
61
+ include: url.searchParams.get('$include'),
62
+ };
63
+ return OpraQuery.forGetMetadata(resourcePath, opts);
64
+ }
44
65
  buildQuery(url, method, body) {
45
66
  let container = this.service;
46
67
  try {
47
- let pathIndex = 0;
48
68
  const pathLen = url.path.size;
69
+ // Check if requesting metadata
70
+ for (let i = 0; i < pathLen; i++) {
71
+ const p = url.path.get(i);
72
+ if (p.resource === '$metadata') {
73
+ if (method !== 'GET')
74
+ return;
75
+ return this.buildMetadataQuery(url);
76
+ }
77
+ }
78
+ let pathIndex = 0;
49
79
  while (pathIndex < pathLen) {
50
80
  let p = url.path.get(pathIndex++);
51
81
  const resource = container.getResource(p.resource);
52
82
  // Move through path directories (containers)
53
83
  if (resource instanceof ContainerResourceHandler) {
54
84
  container = resource;
85
+ continue;
55
86
  }
56
- else {
57
- method = method.toUpperCase();
58
- if (resource instanceof EntityResourceHandler) {
59
- const scope = p.key ? 'instance' : 'collection';
60
- if (pathIndex < pathLen && !(method === 'GET' && scope === 'instance'))
61
- return;
62
- let query;
63
- switch (method) {
64
- case 'GET': {
65
- if (scope === 'collection') {
66
- query = OpraQuery.forSearch(resource, {
67
- filter: url.searchParams.get('$filter'),
68
- limit: url.searchParams.get('$limit'),
69
- skip: url.searchParams.get('$skip'),
70
- distinct: url.searchParams.get('$distinct'),
71
- count: url.searchParams.get('$count'),
72
- sort: url.searchParams.get('$sort'),
73
- pick: url.searchParams.get('$pick'),
74
- omit: url.searchParams.get('$omit'),
75
- include: url.searchParams.get('$include'),
76
- });
77
- }
78
- else {
79
- query = OpraQuery.forGet(resource, p.key, {
80
- pick: url.searchParams.get('$pick'),
81
- omit: url.searchParams.get('$omit'),
82
- include: url.searchParams.get('$include')
83
- });
84
- // Move through properties
85
- let nested;
86
- let path = resource.name;
87
- while (pathIndex < pathLen) {
88
- const dataType = nested
89
- ? this.service.getDataType(nested.property.type || 'string')
90
- : query.resource.dataType;
91
- if (!(dataType instanceof ComplexType))
92
- throw new Error(`"${path}" is not a ComplexType and has no properties.`);
93
- p = url.path.get(pathIndex++);
94
- path += '.' + p.resource;
95
- const prop = dataType.properties?.[p.resource];
96
- if (!prop)
97
- throw new NotFoundError({ message: `Invalid or unknown resource path (${path})` });
98
- const q = OpraQuery.forGetProperty(prop);
99
- if (nested) {
100
- nested.nested = q;
101
- }
102
- else {
103
- query.nested = q;
104
- }
105
- nested = q;
87
+ method = method.toUpperCase();
88
+ if (resource instanceof EntityResourceHandler) {
89
+ const scope = p.key ? 'instance' : 'collection';
90
+ if (pathIndex < pathLen && !(method === 'GET' && scope === 'instance'))
91
+ return;
92
+ let query;
93
+ switch (method) {
94
+ case 'GET': {
95
+ if (scope === 'collection') {
96
+ query = OpraQuery.forSearch(resource, {
97
+ filter: url.searchParams.get('$filter'),
98
+ limit: url.searchParams.get('$limit'),
99
+ skip: url.searchParams.get('$skip'),
100
+ distinct: url.searchParams.get('$distinct'),
101
+ count: url.searchParams.get('$count'),
102
+ sort: url.searchParams.get('$sort'),
103
+ pick: url.searchParams.get('$pick'),
104
+ omit: url.searchParams.get('$omit'),
105
+ include: url.searchParams.get('$include'),
106
+ });
107
+ }
108
+ else {
109
+ query = OpraQuery.forGetEntity(resource, p.key, {
110
+ pick: url.searchParams.get('$pick'),
111
+ omit: url.searchParams.get('$omit'),
112
+ include: url.searchParams.get('$include')
113
+ });
114
+ // Move through properties
115
+ let nested;
116
+ let path = resource.name;
117
+ while (pathIndex < pathLen) {
118
+ const dataType = nested
119
+ ? this.service.getDataType(nested.property.type || 'string')
120
+ : query.resource.dataType;
121
+ if (!(dataType instanceof ComplexType))
122
+ throw new Error(`"${path}" is not a ComplexType and has no properties.`);
123
+ p = url.path.get(pathIndex++);
124
+ path += '.' + p.resource;
125
+ const prop = dataType.properties?.[p.resource];
126
+ if (!prop)
127
+ throw new NotFoundError({ message: `Invalid or unknown resource path (${path})` });
128
+ const q = OpraQuery.forGetProperty(prop);
129
+ if (nested) {
130
+ nested.nested = q;
131
+ }
132
+ else {
133
+ query.nested = q;
106
134
  }
135
+ nested = q;
107
136
  }
108
- break;
109
137
  }
110
- case 'DELETE': {
111
- if (scope === 'collection') {
112
- query = OpraQuery.forDeleteMany(resource, {
113
- filter: url.searchParams.get('$filter'),
114
- });
115
- }
116
- else {
117
- query = OpraQuery.forDelete(resource, p.key);
118
- }
119
- break;
138
+ break;
139
+ }
140
+ case 'DELETE': {
141
+ if (scope === 'collection') {
142
+ query = OpraQuery.forDeleteMany(resource, {
143
+ filter: url.searchParams.get('$filter'),
144
+ });
120
145
  }
121
- case 'POST': {
122
- if (scope === 'collection') {
123
- query = OpraQuery.forCreate(resource, body, {
124
- pick: url.searchParams.get('$pick'),
125
- omit: url.searchParams.get('$omit'),
126
- include: url.searchParams.get('$include')
127
- });
128
- }
129
- break;
146
+ else {
147
+ query = OpraQuery.forDelete(resource, p.key);
130
148
  }
131
- case 'PATCH': {
132
- if (scope === 'collection') {
133
- query = OpraQuery.forUpdateMany(resource, body, {
134
- filter: url.searchParams.get('$filter')
135
- });
136
- }
137
- else {
138
- query = OpraQuery.forUpdate(resource, p.key, body, {
139
- pick: url.searchParams.get('$pick'),
140
- omit: url.searchParams.get('$omit'),
141
- include: url.searchParams.get('$include')
142
- });
143
- }
144
- break;
149
+ break;
150
+ }
151
+ case 'POST': {
152
+ if (scope === 'collection') {
153
+ query = OpraQuery.forCreate(resource, body, {
154
+ pick: url.searchParams.get('$pick'),
155
+ omit: url.searchParams.get('$omit'),
156
+ include: url.searchParams.get('$include')
157
+ });
158
+ }
159
+ break;
160
+ }
161
+ case 'PATCH': {
162
+ if (scope === 'collection') {
163
+ query = OpraQuery.forUpdateMany(resource, body, {
164
+ filter: url.searchParams.get('$filter')
165
+ });
166
+ }
167
+ else {
168
+ query = OpraQuery.forUpdate(resource, p.key, body, {
169
+ pick: url.searchParams.get('$pick'),
170
+ omit: url.searchParams.get('$omit'),
171
+ include: url.searchParams.get('$include')
172
+ });
145
173
  }
174
+ break;
146
175
  }
147
- return query;
148
176
  }
177
+ return query;
149
178
  }
150
179
  }
151
180
  throw new InternalServerError();
152
181
  }
153
182
  catch (e) {
154
- if (e instanceof ApiException)
155
- throw e;
156
- throw new BadRequestError({ message: e.message });
183
+ throw BadRequestError.wrap(e);
157
184
  }
158
185
  }
159
186
  async sendResponse(executionContext, queryContexts) {
@@ -13,6 +13,7 @@ export declare class ComplexType extends DataType {
13
13
  get abstract(): boolean;
14
14
  get additionalProperties(): boolean | string | Pick<OpraSchema.Property, 'type' | 'format' | 'isArray' | 'enum'> | undefined;
15
15
  getProperty(name: string): OpraSchema.Property;
16
+ getMetadata(): OpraSchema.ComplexTypeMetadata;
16
17
  toString(): string;
17
18
  [nodeInspectCustom](): string;
18
19
  }
@@ -1,3 +1,4 @@
1
+ import merge from 'putil-merge';
1
2
  import { Responsive } from '../../utils/responsive-object.js';
2
3
  import { colorFgMagenta, colorFgYellow, colorReset, nodeInspectCustom } from '../../utils/terminal-utils.js';
3
4
  import { DataType } from './data-type.js';
@@ -25,6 +26,18 @@ export class ComplexType extends DataType {
25
26
  throw new Error(`"${this.name}" type has no property named "${name}"`);
26
27
  return t;
27
28
  }
29
+ getMetadata() {
30
+ const out = super.getMetadata();
31
+ if (this.additionalProperties)
32
+ out.additionalProperties = this.additionalProperties;
33
+ if (this.ownProperties) {
34
+ out.properties = {};
35
+ for (const [k, prop] of Object.entries(this.ownProperties)) {
36
+ out.properties[k] = merge({}, prop, { deep: true });
37
+ }
38
+ }
39
+ return out;
40
+ }
28
41
  toString() {
29
42
  return `[${Object.getPrototypeOf(this).constructor.name} ${this.name}]`;
30
43
  }
@@ -10,6 +10,8 @@ export declare abstract class DataType {
10
10
  get kind(): OpraSchema.DataTypeKind;
11
11
  get name(): string;
12
12
  get description(): string | undefined;
13
+ get abstract(): boolean;
13
14
  get ctor(): Type;
14
15
  is(typeName: string): boolean;
16
+ getMetadata(): any;
15
17
  }
@@ -22,10 +22,22 @@ export class DataType {
22
22
  get description() {
23
23
  return this._args.description;
24
24
  }
25
+ get abstract() {
26
+ return !!this._args.abstract;
27
+ }
25
28
  get ctor() {
26
29
  return this._args.ctor;
27
30
  }
28
31
  is(typeName) {
29
32
  return this.name === typeName || !!(this.base && this.base.is(typeName));
30
33
  }
34
+ getMetadata() {
35
+ return {
36
+ kind: this.kind,
37
+ base: this.base?.name,
38
+ abstract: this.abstract ? true : undefined,
39
+ name: this.name,
40
+ description: this.description,
41
+ };
42
+ }
31
43
  }
@@ -1,4 +1,4 @@
1
- import { SqbConnect } from '@opra/optionals';
1
+ import * as Optionals from '@opra/optionals';
2
2
  import { ComplexType } from './complex-type.js';
3
3
  export class EntityType extends ComplexType {
4
4
  constructor(owner, args, base) {
@@ -7,8 +7,8 @@ export class EntityType extends ComplexType {
7
7
  }, base);
8
8
  this._args.kind = 'EntityType';
9
9
  // Try to determine primary key info from SQB
10
- if (args.ctor) {
11
- const sqbEntity = SqbConnect.EntityMetadata.get(args.ctor);
10
+ if (Optionals.SqbConnect && args.ctor) {
11
+ const sqbEntity = Optionals.SqbConnect.EntityMetadata.get(args.ctor);
12
12
  if (sqbEntity?.indexes) {
13
13
  const primaryIndex = sqbEntity.indexes.find(x => x.primary);
14
14
  if (primaryIndex) {
@@ -70,7 +70,8 @@ export class OpraDocument {
70
70
  recursiveSet.add(schema.name);
71
71
  let baseType;
72
72
  if (schema.base) {
73
- if (!this.types[schema.base]) {
73
+ baseType = this.types[schema.base];
74
+ if (!baseType) {
74
75
  const baseSchema = dataTypes.find(dt => dt.name.toLowerCase() === schema.base?.toLowerCase()) ||
75
76
  internalDataTypes.get(schema.base.toLowerCase());
76
77
  if (!baseSchema)
@@ -102,10 +103,5 @@ export class OpraDocument {
102
103
  return dataType;
103
104
  };
104
105
  dataTypes.forEach(dataType => processDataType(dataType));
105
- // Sort data types by name
106
- const newTypes = Responsive();
107
- Object.keys(this.types).sort()
108
- .forEach(name => newTypes[name] = this.types[name]);
109
- this._types = newTypes;
110
106
  }
111
107
  }
@@ -14,6 +14,7 @@ export declare class OpraService extends OpraDocument implements IResourceContai
14
14
  get servers(): OpraSchema.ServerInfo[] | undefined;
15
15
  getResource<T extends ResourceHandler>(name: string): T;
16
16
  getEntityResource(name: string): EntityResourceHandler;
17
+ getMetadata(): OpraSchema.ServiceMetadata;
17
18
  protected _addResources(resources: OpraSchema.Resource[]): void;
18
19
  static create(args: SchemaGenerator.GenerateServiceArgs): Promise<OpraService>;
19
20
  }
@@ -1,4 +1,7 @@
1
+ import merge from 'putil-merge';
1
2
  import { OpraSchema } from '@opra/schema';
3
+ import { OpraVersion } from '../constants.js';
4
+ import { internalDataTypes } from '../utils/internal-data-types.js';
2
5
  import { Responsive } from '../utils/responsive-object.js';
3
6
  import { EntityType } from './data-type/entity-type.js';
4
7
  import { OpraDocument } from './opra-document.js';
@@ -29,6 +32,24 @@ export class OpraService extends OpraDocument {
29
32
  throw new Error(`"${name}" is not an EntityResource`);
30
33
  return t;
31
34
  }
35
+ getMetadata() {
36
+ const out = {
37
+ '@opra:metadata': '/$metadata',
38
+ version: OpraVersion,
39
+ servers: this.servers?.map(x => merge({}, x, { deep: true })),
40
+ info: merge({}, this.info, { deep: true }),
41
+ types: [],
42
+ resources: []
43
+ };
44
+ for (const [k, dataType] of Object.entries(this.types)) {
45
+ if (!internalDataTypes.has(k))
46
+ out.types.push(dataType.getMetadata());
47
+ }
48
+ for (const resource of Object.values(this.resources)) {
49
+ out.resources.push(resource.getMetadata());
50
+ }
51
+ return out;
52
+ }
32
53
  _addResources(resources) {
33
54
  for (const r of resources) {
34
55
  if (OpraSchema.isEntityResource(r)) {
@@ -14,5 +14,5 @@ export declare class EntityResourceHandler extends ResourceHandler {
14
14
  readonly dataType: EntityType;
15
15
  constructor(args: EntityResourceControllerArgs);
16
16
  execute(ctx: QueryContext): Promise<void>;
17
- _executeFn(ctx: QueryContext, fnName: string): Promise<any>;
17
+ _executeFn(ctx: QueryContext, queryType: string): Promise<any>;
18
18
  }
@@ -1,9 +1,10 @@
1
1
  import _ from 'lodash';
2
+ import { translate } from '@opra/i18n';
3
+ import { ForbiddenError, ResourceNotFoundError } from '../../exception/index.js';
2
4
  import { OpraQuery } from '../../interfaces/query.interface.js';
5
+ import { ComplexType } from '../data-type/complex-type.js';
3
6
  import { EntityType } from '../data-type/entity-type.js';
4
7
  import { ResourceHandler } from './resource-handler.js';
5
- var isSearchQuery = OpraQuery.isSearchQuery;
6
- import { UnprocessableEntityError } from '../../exception/index.js';
7
8
  export class EntityResourceHandler extends ResourceHandler {
8
9
  service;
9
10
  dataType;
@@ -20,7 +21,7 @@ export class EntityResourceHandler extends ResourceHandler {
20
21
  }
21
22
  async execute(ctx) {
22
23
  const { query } = ctx;
23
- if (isSearchQuery(query)) {
24
+ if (OpraQuery.isSearchQuery(query)) {
24
25
  const promises = [];
25
26
  let search;
26
27
  let count;
@@ -39,12 +40,28 @@ export class EntityResourceHandler extends ResourceHandler {
39
40
  }
40
41
  ctx.response.value = await this._executeFn(ctx, query.queryType);
41
42
  }
42
- async _executeFn(ctx, fnName) {
43
- const fn = this._args[fnName];
44
- let result = typeof fn === 'function' ? (await fn(ctx)) : undefined;
45
- switch (fnName) {
43
+ async _executeFn(ctx, queryType) {
44
+ const resolverInfo = this._args.resolvers?.[queryType];
45
+ if (resolverInfo.forbidden || !resolverInfo.handler)
46
+ throw new ForbiddenError({
47
+ message: translate('RESOLVER_FORBIDDEN', { queryType }),
48
+ severity: 'error',
49
+ code: 'RESOLVER_FORBIDDEN'
50
+ });
51
+ let result = await resolverInfo.handler(ctx);
52
+ switch (queryType) {
46
53
  case 'search':
47
- return { items: Array.isArray(result) ? result : (ctx.response.value ? [result] : []) };
54
+ return {
55
+ '@opra:metadata': '/$metadata/types/' + this.dataType.name,
56
+ items: Array.isArray(result) ? result : (ctx.response.value ? [result] : [])
57
+ };
58
+ case 'get':
59
+ case 'update':
60
+ if (!result) {
61
+ const query = ctx.query;
62
+ throw new ResourceNotFoundError(this.name, query.keyValue);
63
+ }
64
+ break;
48
65
  case 'count':
49
66
  return { count: result || 0 };
50
67
  case 'delete':
@@ -59,17 +76,23 @@ export class EntityResourceHandler extends ResourceHandler {
59
76
  affectedRecords = result.affectedRows || result.affectedRecords;
60
77
  return { affectedRecords };
61
78
  }
62
- result = Array.isArray(result) ? result[0] : result;
63
79
  if (!result)
64
- throw new UnprocessableEntityError();
80
+ return;
81
+ result = Array.isArray(result) ? result[0] : result;
82
+ let dataType = this.dataType;
65
83
  if (ctx.resultPath) {
66
84
  const pathArray = ctx.resultPath.split('.');
67
85
  for (const property of pathArray) {
86
+ const prop = dataType instanceof ComplexType ? dataType.properties?.[property] : undefined;
87
+ dataType = prop && prop.type ? this.service.types[prop.type || 'string'] : undefined;
68
88
  result = result && typeof result === 'object' && result[property];
69
89
  }
70
90
  }
71
- if (fnName === 'create')
91
+ if (queryType === 'create')
72
92
  ctx.response.status = 201;
73
- return result;
93
+ return {
94
+ '@opra:metadata': dataType ? '/$metadata/types/' + dataType.name : '__unknown__',
95
+ ...result
96
+ };
74
97
  }
75
98
  }
@@ -2,14 +2,14 @@ import { OpraSchema } from '@opra/schema';
2
2
  import { nodeInspectCustom } from '../../utils/terminal-utils.js';
3
3
  import { QueryContext } from '../query-context.js';
4
4
  export declare abstract class ResourceHandler {
5
- protected readonly _args: OpraSchema.Resource & {
6
- prepare?: Function;
7
- };
5
+ protected readonly _args: OpraSchema.Resource;
6
+ protected path: string;
8
7
  protected constructor(args: OpraSchema.Resource);
9
8
  get name(): string;
10
9
  get description(): string | undefined;
11
10
  toString(): string;
12
11
  prepare(ctx: QueryContext): Promise<void>;
12
+ getMetadata(): any;
13
13
  abstract execute(ctx: QueryContext): Promise<void>;
14
14
  [nodeInspectCustom](): string;
15
15
  }
@@ -1,6 +1,8 @@
1
+ import merge from 'putil-merge';
1
2
  import { colorFgMagenta, colorFgYellow, colorReset, nodeInspectCustom } from '../../utils/terminal-utils.js';
2
3
  export class ResourceHandler {
3
4
  _args;
5
+ path;
4
6
  constructor(args) {
5
7
  this._args = args;
6
8
  }
@@ -20,6 +22,17 @@ export class ResourceHandler {
20
22
  await fn(ctx);
21
23
  }
22
24
  }
25
+ getMetadata() {
26
+ return merge({}, {
27
+ ...this._args,
28
+ instance: undefined,
29
+ }, {
30
+ deep: true,
31
+ filter: (source, key) => {
32
+ return (key !== 'instance' && typeof source[key] !== 'function' && source[key] != null);
33
+ }
34
+ });
35
+ }
23
36
  [nodeInspectCustom]() {
24
37
  return `[${colorFgYellow + Object.getPrototypeOf(this).constructor.name + colorReset}` +
25
38
  ` ${colorFgMagenta + this.name + colorReset}]`;
@@ -1,9 +1,8 @@
1
1
  import { isPromise } from 'util/types';
2
2
  import { DATATYPE_METADATA, DATATYPE_PROPERTIES, OpraSchema } from '@opra/schema';
3
- import { RESOURCE_METADATA } from '../constants.js';
3
+ import { RESOLVER_METADATA, RESOURCE_METADATA } from '../constants.js';
4
4
  import { isConstructor, resolveClassAsync } from '../utils/class-utils.js';
5
5
  import { builtinClassMap, internalDataTypes, primitiveDataTypeNames } from '../utils/internal-data-types.js';
6
- var isDataType = OpraSchema.isDataType;
7
6
  const entityMethods = ['search', 'count', 'create', 'get', 'update', 'updateMany', 'delete', 'deleteMany'];
8
7
  export class SchemaGenerator {
9
8
  _dataTypes = {};
@@ -55,7 +54,7 @@ export class SchemaGenerator {
55
54
  }
56
55
  return this.addDataType(schema);
57
56
  }
58
- if (!isDataType(thunk))
57
+ if (!OpraSchema.isDataType(thunk))
59
58
  throw new TypeError(`Invalid data type schema`);
60
59
  // Check if datatype previously added
61
60
  const currentSchema = this._dataTypes[thunk.name];
@@ -89,16 +88,25 @@ export class SchemaGenerator {
89
88
  ...metadata,
90
89
  type,
91
90
  name,
92
- instance
91
+ instance,
92
+ resolvers: {}
93
93
  };
94
94
  if (OpraSchema.isEntityResource(resourceSchema)) {
95
95
  for (const methodName of entityMethods) {
96
- let fn = instance['pre_' + methodName];
97
- if (typeof fn === 'function')
98
- resourceSchema['pre_' + methodName] = fn.bind(instance);
99
- fn = instance[methodName];
100
- if (typeof fn === 'function')
101
- resourceSchema[methodName] = fn.bind(instance);
96
+ let fn = instance[methodName];
97
+ if (typeof fn === 'function') {
98
+ const info = resourceSchema.resolvers[methodName] = {
99
+ ...Reflect.getMetadata(RESOLVER_METADATA, proto, methodName)
100
+ };
101
+ if (!info.forbidden) {
102
+ info.handler = fn.bind(instance);
103
+ fn = instance['pre_' + methodName];
104
+ if (typeof fn === 'function')
105
+ resourceSchema['pre_' + methodName] = fn.bind(instance);
106
+ }
107
+ }
108
+ else
109
+ resourceSchema.resolvers[methodName] = { forbidden: true };
102
110
  }
103
111
  }
104
112
  }
@@ -145,8 +153,21 @@ export class SchemaGenerator {
145
153
  await generator.addResource(resource);
146
154
  }
147
155
  }
148
- const types = Object.keys(generator._dataTypes).sort()
149
- .map(name => generator._dataTypes[name]);
156
+ const types = Object.keys(generator._dataTypes)
157
+ .map(name => generator._dataTypes[name])
158
+ .sort((a, b) => {
159
+ if (a.kind !== b.kind) {
160
+ if (a.kind === 'SimpleType')
161
+ return -1;
162
+ if (b.kind === 'SimpleType')
163
+ return 1;
164
+ if (a.kind === 'ComplexType')
165
+ return -1;
166
+ if (b.kind === 'ComplexType')
167
+ return 1;
168
+ }
169
+ return (a.name < b.name ? -1 : 1);
170
+ });
150
171
  const resources = Object.keys(generator._resources).sort()
151
172
  .map(name => generator._resources[name]);
152
173
  return {
package/esm/index.d.ts CHANGED
@@ -3,7 +3,7 @@ export * from './constants.js';
3
3
  export * from './types.js';
4
4
  export * from './enums/index.js';
5
5
  export * from './exception/index.js';
6
- export * from './decorators/entity-resource.decorator.js';
6
+ export * from './decorators/api-entity-resource.decorator.js';
7
7
  export * from './interfaces/execution-context.interface.js';
8
8
  export * from './interfaces/query.interface.js';
9
9
  export * from './interfaces/resource-container.interface.js';
package/esm/index.js CHANGED
@@ -3,7 +3,7 @@ export * from './constants.js';
3
3
  export * from './types.js';
4
4
  export * from './enums/index.js';
5
5
  export * from './exception/index.js';
6
- export * from './decorators/entity-resource.decorator.js';
6
+ export * from './decorators/api-entity-resource.decorator.js';
7
7
  export * from './interfaces/execution-context.interface.js';
8
8
  export * from './interfaces/query.interface.js';
9
9
  export * from './interfaces/resource-container.interface.js';
@@ -0,0 +1,3 @@
1
+ import { StrictOmit } from 'ts-gems';
2
+ import { OpraSchema } from '@opra/schema';
3
+ export declare type ApiResolverMetadata = StrictOmit<OpraSchema.ResolverInfo, 'handler'>;