@toa.io/extensions.exposition 1.0.0-alpha.54 → 1.0.0-alpha.56

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 (57) hide show
  1. package/components/identity.federation/operations/tsconfig.tsbuildinfo +1 -1
  2. package/documentation/introspection.md +37 -23
  3. package/documentation/query.md +21 -0
  4. package/features/identity.basic.feature +2 -0
  5. package/features/introspection.feature +78 -1
  6. package/features/io.feature +0 -1
  7. package/features/query.feature +24 -0
  8. package/features/steps/components/echo/manifest.toa.yaml +4 -2
  9. package/package.json +9 -8
  10. package/schemas/query.cos.yaml +4 -10
  11. package/source/Endpoint.ts +32 -2
  12. package/source/Gateway.ts +5 -11
  13. package/source/HTTP/Server.ts +1 -2
  14. package/source/Introspection.ts +11 -0
  15. package/source/Mapping.ts +56 -19
  16. package/source/Query.test.ts +3 -3
  17. package/source/Query.ts +90 -17
  18. package/source/RTD/Endpoint.ts +1 -1
  19. package/source/RTD/Method.ts +15 -2
  20. package/source/RTD/Node.ts +19 -11
  21. package/source/RTD/syntax/types.ts +3 -2
  22. package/source/Remotes.ts +0 -7
  23. package/source/directives/io/Input.ts +2 -2
  24. package/source/manifest.ts +1 -1
  25. package/transpiled/Endpoint.d.ts +2 -1
  26. package/transpiled/Endpoint.js +22 -2
  27. package/transpiled/Endpoint.js.map +1 -1
  28. package/transpiled/Gateway.js +5 -8
  29. package/transpiled/Gateway.js.map +1 -1
  30. package/transpiled/HTTP/Server.js +1 -2
  31. package/transpiled/HTTP/Server.js.map +1 -1
  32. package/transpiled/Introspection.d.ts +9 -0
  33. package/transpiled/Introspection.js +3 -0
  34. package/transpiled/Introspection.js.map +1 -0
  35. package/transpiled/Mapping.d.ts +9 -1
  36. package/transpiled/Mapping.js +44 -18
  37. package/transpiled/Mapping.js.map +1 -1
  38. package/transpiled/Query.d.ts +10 -2
  39. package/transpiled/Query.js +67 -15
  40. package/transpiled/Query.js.map +1 -1
  41. package/transpiled/RTD/Endpoint.d.ts +1 -1
  42. package/transpiled/RTD/Method.d.ts +4 -1
  43. package/transpiled/RTD/Method.js +11 -2
  44. package/transpiled/RTD/Method.js.map +1 -1
  45. package/transpiled/RTD/Node.d.ts +2 -1
  46. package/transpiled/RTD/Node.js +14 -10
  47. package/transpiled/RTD/Node.js.map +1 -1
  48. package/transpiled/RTD/syntax/types.d.ts +3 -2
  49. package/transpiled/RTD/syntax/types.js.map +1 -1
  50. package/transpiled/Remotes.d.ts +0 -2
  51. package/transpiled/Remotes.js +0 -5
  52. package/transpiled/Remotes.js.map +1 -1
  53. package/transpiled/directives/io/Input.js +2 -2
  54. package/transpiled/directives/io/Input.js.map +1 -1
  55. package/transpiled/manifest.js +1 -1
  56. package/transpiled/manifest.js.map +1 -1
  57. package/transpiled/tsconfig.tsbuildinfo +1 -1
@@ -1,17 +1,11 @@
1
- _: true
2
1
  id?: string
3
2
  criteria?: string
4
3
  sort?: string
5
- omit:
4
+ omit?:
6
5
  $ref: range
7
- default:
8
- value: 0
9
- range: [0, 1000]
10
- limit:
6
+ limit?:
11
7
  $ref: range
12
- default:
13
- value: 10
14
- range: [1, 1000]
15
- required: [value]
16
8
  selectors?: [string]
17
9
  projection?: [string]
10
+ parameters?: [string]
11
+ _: true
@@ -1,5 +1,6 @@
1
1
  import { Mapping } from './Mapping'
2
2
  import * as http from './HTTP'
3
+ import type { Introspection, Schema } from './Introspection'
3
4
  import type { Remote } from '@toa.io/core'
4
5
  import type { Remotes } from './Remotes'
5
6
  import type { Context } from './Context'
@@ -53,10 +54,39 @@ export class Endpoint implements RTD.Endpoint {
53
54
  return message
54
55
  }
55
56
 
56
- public async explain (): Promise<unknown> {
57
+ public async explain (parameters: RTD.Parameter[]): Promise<Introspection> {
57
58
  this.remote ??= await this.discovery
58
59
 
59
- return this.remote.explain(this.endpoint) ?? null
60
+ // eslint-disable-next-line @typescript-eslint/await-thenable
61
+ const operation = await this.remote.explain(this.endpoint)
62
+
63
+ let route: Record<string, Schema> | null = null
64
+
65
+ if (operation.input?.type === 'object')
66
+ for (const parameter of parameters) {
67
+ const schema = operation.input.properties[parameter.name]
68
+
69
+ // eslint-disable-next-line max-depth
70
+ if (schema !== undefined) {
71
+ route ??= {}
72
+ route[parameter.name] = schema
73
+
74
+ delete operation.input.properties[parameter.name]
75
+ }
76
+ }
77
+
78
+ const query = this.mapping.explain(operation)
79
+ const introspection: Introspection = {}
80
+
81
+ if (route !== null)
82
+ introspection.route = route
83
+
84
+ if (query !== null)
85
+ introspection.query = query
86
+
87
+ Object.assign(introspection, operation)
88
+
89
+ return introspection
60
90
  }
61
91
 
62
92
  public async close (): Promise<void> {
package/source/Gateway.ts CHANGED
@@ -32,7 +32,7 @@ export class Gateway extends Connector {
32
32
  const { node, parameters } = this.match(context)
33
33
 
34
34
  if (context.request.method === 'OPTIONS')
35
- return await this.explain(node)
35
+ return await this.explain(node, parameters)
36
36
 
37
37
  if (!(context.request.method in node.methods))
38
38
  throw new http.MethodNotAllowed()
@@ -101,18 +101,12 @@ export class Gateway extends Connector {
101
101
  .catch(rethrow) as http.OutgoingMessage
102
102
  }
103
103
 
104
- private async explain (node: Node): Promise<http.OutgoingMessage> {
105
- const methods: Record<string, unknown> = {}
106
-
107
- const explaining = Object.entries(node.methods)
108
- .map(async ([verb, method]) => (methods[verb] = await method.explain()))
109
-
110
- await Promise.all(explaining)
111
-
112
- const allow = [...Object.keys(node.methods), 'OPTIONS'].join(', ')
104
+ private async explain (node: Node, parameters: Parameter[]): Promise<http.OutgoingMessage> {
105
+ const body = await node.explain(parameters)
106
+ const allow = [...Object.keys(node.methods)].join(', ')
113
107
  const headers = new Headers({ allow })
114
108
 
115
- return { body: methods, headers }
109
+ return { body, headers }
116
110
  }
117
111
 
118
112
  private async discover (): Promise<void> {
@@ -117,9 +117,8 @@ export class Server extends Connector {
117
117
  response.statusCode = exception instanceof Exception ? exception.status : 500
118
118
 
119
119
  const message: OutgoingMessage = { status: response.statusCode }
120
- const verbose = exception instanceof ClientError || this.properties.debug
121
120
 
122
- if (verbose)
121
+ if (exception instanceof ClientError || this.properties.debug)
123
122
  message.body =
124
123
  exception instanceof Exception
125
124
  ? exception.body
@@ -0,0 +1,11 @@
1
+ import type { Remote } from '@toa.io/core'
2
+
3
+ export interface Introspection {
4
+ route?: Record<string, Schema>
5
+ query?: Record<string, Schema>
6
+ input?: Schema
7
+ output?: Schema
8
+ errors?: string[]
9
+ }
10
+
11
+ export type Schema = Awaited<ReturnType<Remote['explain']>>['input']
package/source/Mapping.ts CHANGED
@@ -1,51 +1,79 @@
1
1
  import { type Parameter } from './RTD'
2
2
  import { Query } from './Query'
3
+ import type { Introspection, Schema } from './Introspection'
4
+ import type { QueryString } from './Query'
3
5
  import type * as http from './HTTP'
4
6
  import type * as syntax from './RTD/syntax'
5
7
  import type * as core from '@toa.io/core'
6
8
 
7
9
  export abstract class Mapping {
8
- public static create (query?: syntax.Query): Mapping {
9
- if (query === undefined || query === null)
10
- return new InputMapping()
10
+ protected readonly query: Query
11
+
12
+ public constructor (query: Query) {
13
+ this.query = query
14
+ }
11
15
 
16
+ public static create (query?: syntax.Query): Mapping {
12
17
  const q = new Query(query)
13
18
 
14
- return new QueryableMapping(q)
19
+ return queryable(query)
20
+ ? new QueryableMapping(q)
21
+ : new InputMapping(q)
22
+ }
23
+
24
+ public explain (introspection: Introspection): Record<string, Schema> | null {
25
+ return this.query.explain(introspection)
15
26
  }
16
27
 
17
- public abstract fit (input: any, qs: http.Query, parameters: Parameter[]): core.Request
28
+ protected assign (input: any, qs: QueryString): void {
29
+ if (qs.parameters !== null) {
30
+ if (typeof input !== 'object' || input === null)
31
+ throw new Error('Input must be an object to embed query parameters')
32
+
33
+ Object.assign(input, qs.parameters)
34
+ }
35
+ }
36
+
37
+ public abstract fit (input: any, query: http.Query, parameters: Parameter[]): core.Request
18
38
  }
19
39
 
20
40
  class QueryableMapping extends Mapping {
21
- private readonly query: Query
41
+ public fit (input: any, query: http.Query, parameters: Parameter[]): core.Request {
42
+ const request: core.Request = {}
43
+ const qs = this.query.fit(query, parameters)
22
44
 
23
- public constructor (query: Query) {
24
- super()
45
+ if (input === undefined && qs.parameters !== null)
46
+ input = {}
25
47
 
26
- this.query = query
27
- }
48
+ this.assign(input, qs)
28
49
 
29
- public fit (input: any, qs: http.Query, parameters: Parameter[]): core.Request {
30
- const query = this.query.fit(qs, parameters)
50
+ if (input !== undefined)
51
+ request.input = input
31
52
 
32
- return {
33
- input,
34
- query
35
- }
53
+ if (qs.query !== null)
54
+ request.query = qs.query
55
+
56
+ return request
36
57
  }
37
58
  }
38
59
 
39
60
  class InputMapping extends Mapping {
40
- public fit (input: any, _: unknown, parameters: Parameter[]): core.Request {
61
+ public fit (input: any, query: http.Query, parameters: Parameter[]): core.Request {
41
62
  const request: core.Request = {}
63
+ const qs = this.query.fit(query, parameters)
42
64
 
43
- if (input === undefined && parameters.length > 0)
65
+ if (input === undefined && (parameters.length > 0 || qs.parameters !== null))
44
66
  input = {}
45
67
 
46
- if (typeof input === 'object' && input !== null)
68
+ if (parameters.length > 0) {
69
+ if (typeof input !== 'object' || input === null)
70
+ throw new Error('Input must be an object to embed route parameters')
71
+
47
72
  for (const parameter of parameters)
48
73
  input[parameter.name] = parameter.value
74
+ }
75
+
76
+ this.assign(input, qs)
49
77
 
50
78
  if (input !== undefined)
51
79
  request.input = input
@@ -53,3 +81,12 @@ class InputMapping extends Mapping {
53
81
  return request
54
82
  }
55
83
  }
84
+
85
+ export function queryable (query?: syntax.Query): boolean {
86
+ if (query === undefined || query === null)
87
+ return false
88
+
89
+ const keys = Object.keys(query)
90
+
91
+ return !(keys.length === 1 && keys[0] === 'parameters')
92
+ }
@@ -16,7 +16,7 @@ it('should combine request criteria', async () => {
16
16
  const instance = new Query(query)
17
17
  const result = instance.fit({ criteria: 'qux==4' }, parameters)
18
18
 
19
- expect(result.criteria).toStrictEqual('(bar==2;baz==3);(foo==1);(qux==4)')
19
+ expect(result.query!.criteria).toStrictEqual('(bar==2;baz==3);(foo==1);(qux==4)')
20
20
  })
21
21
 
22
22
  it('should set id parameter as query.id', async () => {
@@ -32,6 +32,6 @@ it('should set id parameter as query.id', async () => {
32
32
  const instance = new Query(query)
33
33
  const result = instance.fit({}, parameters)
34
34
 
35
- expect(result.criteria).toBeUndefined()
36
- expect(result.id).toStrictEqual(id)
35
+ expect(result.query!.criteria).toBeUndefined()
36
+ expect(result.query!.id).toStrictEqual(id)
37
37
  })
package/source/Query.ts CHANGED
@@ -1,6 +1,9 @@
1
+ import assert from 'node:assert'
1
2
  import * as http from './HTTP'
2
3
  import { type Parameter } from './RTD'
3
4
  import * as schemas from './schemas'
5
+ import { queryable } from './Mapping'
6
+ import type { Introspection, Schema } from './Introspection'
4
7
  import type * as syntax from './RTD/syntax'
5
8
  import type * as core from '@toa.io/core'
6
9
 
@@ -8,35 +11,97 @@ export class Query {
8
11
  private readonly query: syntax.Query
9
12
  private readonly closed: boolean = false
10
13
  private readonly prepend: ',' | ';' = ';'
14
+ private readonly queryable: boolean
11
15
 
12
- public constructor (query: syntax.Query) {
13
- if (query.criteria !== undefined) {
14
- if (query.criteria.endsWith(';'))
15
- query.criteria = query.criteria.slice(0, -1)
16
- else
17
- this.closed = true
16
+ public constructor (query: syntax.Query = {}) {
17
+ this.queryable = queryable(query)
18
+
19
+ if (this.queryable) {
20
+ query.omit ??= { value: 0, range: [0, 1000] }
21
+ query.limit ??= { value: 10, range: [1, 100] }
18
22
 
19
- if (query.criteria.startsWith(',') || query.criteria.startsWith(';')) {
20
- this.prepend = query.criteria[0] as ',' | ';'
23
+ if (query.criteria !== undefined) {
24
+ // eslint-disable-next-line max-depth
25
+ if (query.criteria.endsWith(';'))
26
+ query.criteria = query.criteria.slice(0, -1)
27
+ else
28
+ this.closed = true
21
29
 
22
- query.criteria = query.criteria.slice(1)
30
+ // eslint-disable-next-line max-depth
31
+ if (query.criteria.startsWith(',') || query.criteria.startsWith(';')) {
32
+ this.prepend = query.criteria[0] as ',' | ';'
33
+
34
+ query.criteria = query.criteria.slice(1)
35
+ }
23
36
  }
24
37
  }
25
38
 
26
39
  this.query = query
27
40
  }
28
41
 
29
- public fit (query: http.Query, parameters: Parameter[]): core.Query {
30
- const error = schemas.querystring.fit(query)
42
+ public fit (query: http.Query, parameters: Parameter[]): QueryString {
43
+ const qs = this.split(query)
31
44
 
32
- if (error !== null)
33
- throw new http.BadRequest('Query ' + error.message)
45
+ if (qs.query !== null) {
46
+ const error = schemas.querystring.fit(qs.query)
34
47
 
35
- this.fitCriteria(query, parameters)
36
- this.fitRanges(query)
37
- this.fitSort(query)
48
+ if (error !== null)
49
+ throw new http.BadRequest('Query ' + error.message)
38
50
 
39
- return query as core.Query
51
+ this.fitCriteria(qs.query, parameters)
52
+ this.fitRanges(qs.query)
53
+ this.fitSort(qs.query)
54
+ }
55
+
56
+ return {
57
+ query: qs.query as core.Query,
58
+ parameters: qs.parameters
59
+ }
60
+ }
61
+
62
+ public explain (introspection: Introspection): Record<string, Schema> | null {
63
+ if (this.query?.parameters === undefined || introspection.input?.type !== 'object')
64
+ return null
65
+
66
+ let query: Record<string, Schema> | null = null
67
+
68
+ for (const parameter of this.query.parameters) {
69
+ const schema = introspection.input.properties[parameter]
70
+
71
+ if (schema !== undefined) {
72
+ query ??= {}
73
+ query[parameter] = schema
74
+ }
75
+
76
+ delete introspection.input.properties[parameter]
77
+ }
78
+
79
+ return query
80
+ }
81
+
82
+ private split (query: http.Query): {
83
+ query: http.Query | null
84
+ parameters: Record<string, string> | null
85
+ } {
86
+ let parameters: Record<string, string> | null = null
87
+
88
+ if (this.query?.parameters !== undefined)
89
+ for (const key in query)
90
+ // eslint-disable-next-line max-depth
91
+ if (this.query.parameters.includes(key)) {
92
+ parameters ??= {}
93
+ parameters[key] = query[key] as string
94
+
95
+ delete query[key]
96
+ }
97
+
98
+ if (!this.queryable && Object.keys(query).length === 0)
99
+ query = null!
100
+
101
+ return {
102
+ query,
103
+ parameters
104
+ }
40
105
  }
41
106
 
42
107
  private fitCriteria (query: http.Query, parameters: Parameter[]): void {
@@ -77,6 +142,9 @@ export class Query {
77
142
  private fitRanges (qs: http.Query): void {
78
143
  const query = qs as core.Query
79
144
 
145
+ assert.ok(this.query.limit !== undefined, 'Query limit must be defined')
146
+ assert.ok(this.query.omit !== undefined, 'Query limit range must be defined')
147
+
80
148
  if (qs.limit !== undefined)
81
149
  query.limit = fit(qs.limit, this.query.limit.range, 'limit')
82
150
  else
@@ -114,3 +182,8 @@ interface CriteriaGroup {
114
182
  criteria: string
115
183
  operator: ',' | ';'
116
184
  }
185
+
186
+ export interface QueryString {
187
+ query: core.Query | null
188
+ parameters: Record<string, string> | null
189
+ }
@@ -6,7 +6,7 @@ import type * as RTD from './index'
6
6
  export interface Endpoint {
7
7
  call: (context: http.Context, parameters: RTD.Parameter[]) => Promise<http.OutgoingMessage>
8
8
 
9
- explain: () => unknown
9
+ explain: (parameters: RTD.Parameter[]) => Promise<unknown>
10
10
 
11
11
  close: () => Promise<void>
12
12
  }
@@ -1,20 +1,33 @@
1
+ import type { Parameter } from './Match'
1
2
  import type { Endpoint } from './Endpoint'
2
3
  import type { Directives } from './Directives'
3
4
 
4
5
  export class Method {
5
6
  public readonly endpoint: Endpoint | null
6
7
  public readonly directives: Directives
8
+ private introspection: unknown | null = null
9
+ private introspecting: Promise<unknown> | null = null
7
10
 
8
11
  public constructor (endpoint: Endpoint | null, directives: Directives) {
9
12
  this.endpoint = endpoint
10
13
  this.directives = directives
11
14
  }
12
15
 
13
- public async explain (): Promise<unknown> {
14
- return (await this.endpoint?.explain()) ?? null
16
+ public async explain (parameters: Parameter[]): Promise<unknown> {
17
+ if (this.introspection !== null)
18
+ return this.introspection
19
+
20
+ if (this.introspecting === null)
21
+ // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
22
+ this.introspecting = this.endpoint?.explain(parameters)!
23
+
24
+ this.introspection = await this.introspecting
25
+
26
+ return this.introspection
15
27
  }
16
28
 
17
29
  public async close (): Promise<void> {
30
+ console.log('!!! Closing method')
18
31
  await this.endpoint?.close()
19
32
  }
20
33
  }
@@ -35,38 +35,46 @@ export class Node {
35
35
  public merge (node: Node): void {
36
36
  this.intermediate = node.intermediate
37
37
 
38
- if (!this.protected)
39
- this.replace(node)
40
- else
38
+ if (this.protected)
41
39
  this.append(node)
40
+ else
41
+ this.replace(node)
42
42
 
43
43
  this.sort()
44
44
  }
45
45
 
46
+ public async explain (parameters: Parameter[]): Promise<Record<string, unknown>> {
47
+ const methods: Record<string, unknown> = {}
48
+
49
+ const explained = Object.entries(this.methods)
50
+ .map(async ([verb, method]) =>
51
+ (methods[verb] = await method.explain(parameters)))
52
+
53
+ await Promise.all(explained)
54
+
55
+ return methods
56
+ }
57
+
46
58
  private replace (node: Node): void {
47
59
  const methods = Object.values(this.methods)
48
60
 
49
61
  this.routes = node.routes
50
62
  this.methods = node.methods
51
63
 
64
+ // race condition is really unlikely
52
65
  for (const method of methods)
53
66
  void method.close()
54
-
55
- // race condition is really unlikely
56
67
  }
57
68
 
58
69
  private append (node: Node): void {
59
70
  for (const route of node.routes)
60
- this.mergeRoute(route)
71
+ this.route(route)
61
72
 
62
73
  for (const [verb, method] of Object.entries(node.methods))
63
- if (verb in this.methods)
64
- console.warn(`Overriding of the protected method ${verb} is not permitted.`)
65
- else
66
- this.methods[verb] = method
74
+ this.methods[verb] = method
67
75
  }
68
76
 
69
- private mergeRoute (candidate: Route): void {
77
+ private route (candidate: Route): void {
70
78
  for (const route of this.routes)
71
79
  if (candidate.equals(route)) {
72
80
  route.merge(candidate)
@@ -35,10 +35,11 @@ export interface Query {
35
35
  id?: string
36
36
  criteria?: string
37
37
  sort?: string
38
- omit: Range
39
- limit: Range
38
+ omit?: Range
39
+ limit?: Range
40
40
  selectors?: string[]
41
41
  projection?: string[]
42
+ parameters?: string[]
42
43
  }
43
44
 
44
45
  export interface Range {
package/source/Remotes.ts CHANGED
@@ -3,7 +3,6 @@ import { type Bootloader } from './Factory'
3
3
 
4
4
  export class Remotes extends Connector {
5
5
  private readonly boot: Bootloader
6
- private readonly remotes: Record<string, Promise<Remote>> = {}
7
6
 
8
7
  public constructor (boot: Bootloader) {
9
8
  super()
@@ -13,12 +12,6 @@ export class Remotes extends Connector {
13
12
  public async discover (namespace: string, name: string): Promise<Remote> {
14
13
  const locator = new Locator(name, namespace)
15
14
 
16
- this.remotes[locator.id] ??= this.create(locator)
17
-
18
- return await this.remotes[locator.id]
19
- }
20
-
21
- private async create (locator: Locator): Promise<Remote> {
22
15
  const remote = await this.boot.remote(locator)
23
16
 
24
17
  this.depends(remote)
@@ -23,13 +23,13 @@ export class Input implements Directive {
23
23
  try {
24
24
  schemas.message.validate(body)
25
25
  } catch {
26
- throw new BadRequest('Invalid request body.')
26
+ throw new BadRequest('Invalid request body')
27
27
  }
28
28
 
29
29
  const property = this.violation(body)
30
30
 
31
31
  if (property !== undefined)
32
- throw new BadRequest(`Unexpected input: ${property}.`)
32
+ throw new BadRequest(`Unexpected input: ${property}`)
33
33
 
34
34
  return body
35
35
  }
@@ -22,7 +22,7 @@ function wrap (declaration: object, namespace: string, name: string): object {
22
22
  const path = (namespace === undefined || namespace === 'default' ? '' : '/' + namespace) +
23
23
  '/' + name
24
24
 
25
- return { [path]: { protected: true, ...declaration } }
25
+ return { [path]: declaration }
26
26
  }
27
27
 
28
28
  function specify (node: Node, manifest: Manifest): void {
@@ -1,5 +1,6 @@
1
1
  import { Mapping } from './Mapping';
2
2
  import * as http from './HTTP';
3
+ import type { Introspection } from './Introspection';
3
4
  import type { Remote } from '@toa.io/core';
4
5
  import type { Remotes } from './Remotes';
5
6
  import type { Context } from './Context';
@@ -11,7 +12,7 @@ export declare class Endpoint implements RTD.Endpoint {
11
12
  private remote;
12
13
  constructor(endpoint: string, mapping: Mapping, discovery: Promise<Remote>);
13
14
  call(context: http.Context, parameters: RTD.Parameter[]): Promise<http.OutgoingMessage>;
14
- explain(): Promise<unknown>;
15
+ explain(parameters: RTD.Parameter[]): Promise<Introspection>;
15
16
  close(): Promise<void>;
16
17
  private query;
17
18
  private version;
@@ -61,9 +61,29 @@ class Endpoint {
61
61
  message.body = reply;
62
62
  return message;
63
63
  }
64
- async explain() {
64
+ async explain(parameters) {
65
65
  this.remote ??= await this.discovery;
66
- return this.remote.explain(this.endpoint) ?? null;
66
+ // eslint-disable-next-line @typescript-eslint/await-thenable
67
+ const operation = await this.remote.explain(this.endpoint);
68
+ let route = null;
69
+ if (operation.input?.type === 'object')
70
+ for (const parameter of parameters) {
71
+ const schema = operation.input.properties[parameter.name];
72
+ // eslint-disable-next-line max-depth
73
+ if (schema !== undefined) {
74
+ route ??= {};
75
+ route[parameter.name] = schema;
76
+ delete operation.input.properties[parameter.name];
77
+ }
78
+ }
79
+ const query = this.mapping.explain(operation);
80
+ const introspection = {};
81
+ if (route !== null)
82
+ introspection.route = route;
83
+ if (query !== null)
84
+ introspection.query = query;
85
+ Object.assign(introspection, operation);
86
+ return introspection;
67
87
  }
68
88
  async close() {
69
89
  this.remote ??= await this.discovery;
@@ -1 +1 @@
1
- {"version":3,"file":"Endpoint.js","sourceRoot":"","sources":["../source/Endpoint.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Endpoint.js","sourceRoot":"","sources":["../source/Endpoint.ts"],"names":[],"mappings":"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"}
@@ -48,7 +48,7 @@ class Gateway extends core_1.Connector {
48
48
  return interception;
49
49
  const { node, parameters } = this.match(context);
50
50
  if (context.request.method === 'OPTIONS')
51
- return await this.explain(node);
51
+ return await this.explain(node, parameters);
52
52
  if (!(context.request.method in node.methods))
53
53
  throw new http.MethodNotAllowed();
54
54
  const method = node.methods[context.request.method];
@@ -91,14 +91,11 @@ class Gateway extends core_1.Connector {
91
91
  .call(context, parameters)
92
92
  .catch(exceptions_1.rethrow);
93
93
  }
94
- async explain(node) {
95
- const methods = {};
96
- const explaining = Object.entries(node.methods)
97
- .map(async ([verb, method]) => (methods[verb] = await method.explain()));
98
- await Promise.all(explaining);
99
- const allow = [...Object.keys(node.methods), 'OPTIONS'].join(', ');
94
+ async explain(node, parameters) {
95
+ const body = await node.explain(parameters);
96
+ const allow = [...Object.keys(node.methods)].join(', ');
100
97
  const headers = new Headers({ allow });
101
- return { body: methods, headers };
98
+ return { body, headers };
102
99
  }
103
100
  async discover() {
104
101
  await this.broadcast.receive('expose', this.merge.bind(this));