@toa.io/extensions.exposition 1.0.0-alpha.4 → 1.0.0-alpha.6

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 (141) hide show
  1. package/components/identity.basic/manifest.toa.yaml +2 -0
  2. package/components/identity.federation/manifest.toa.yaml +0 -6
  3. package/components/identity.roles/manifest.toa.yaml +1 -0
  4. package/documentation/io.md +56 -0
  5. package/documentation/query.md +9 -7
  6. package/documentation/tree.md +22 -4
  7. package/features/access.feature +11 -1
  8. package/features/annotation.feature +1 -0
  9. package/features/body.feature +1 -0
  10. package/features/cache.feature +3 -0
  11. package/features/cors.feature +2 -2
  12. package/features/directives.feature +2 -0
  13. package/features/dynamic.feature +14 -7
  14. package/features/errors.feature +3 -2
  15. package/features/etag.feature +12 -1
  16. package/features/identity.basic.feature +23 -0
  17. package/features/identity.federation.feature +3 -5
  18. package/features/identity.roles.feature +1 -0
  19. package/features/identity.tokens.feature +3 -0
  20. package/features/io.feature +167 -0
  21. package/features/octets.entries.feature +2 -0
  22. package/features/octets.feature +2 -0
  23. package/features/octets.meta.feature +4 -3
  24. package/features/octets.workflows.feature +1 -0
  25. package/features/queries.feature +9 -1
  26. package/features/response.feature +3 -0
  27. package/features/routes.feature +17 -10
  28. package/features/steps/components/pots/manifest.toa.yaml +2 -0
  29. package/features/steps/components/users/manifest.toa.yaml +1 -0
  30. package/features/steps/components/users.properties/manifest.toa.yaml +1 -0
  31. package/features/vary.feature +33 -3
  32. package/package.json +7 -7
  33. package/schemas/io/input.cos.yaml +3 -0
  34. package/schemas/io/message.cos.yaml +5 -0
  35. package/schemas/io/output.cos.yaml +5 -0
  36. package/source/Context.ts +6 -4
  37. package/source/Directive.test.ts +4 -4
  38. package/source/Directive.ts +7 -33
  39. package/source/Endpoint.ts +41 -6
  40. package/source/Factory.ts +10 -4
  41. package/source/Gateway.ts +4 -29
  42. package/source/HTTP/Server.ts +2 -2
  43. package/source/RTD/Context.ts +7 -10
  44. package/source/RTD/Directives.ts +28 -4
  45. package/source/RTD/Endpoint.ts +6 -4
  46. package/source/RTD/Match.ts +2 -7
  47. package/source/RTD/Method.ts +7 -13
  48. package/source/RTD/Node.ts +13 -14
  49. package/source/RTD/Tree.ts +17 -16
  50. package/source/RTD/factory.ts +2 -5
  51. package/source/directives/auth/Authorization.ts +5 -6
  52. package/source/directives/cache/Cache.ts +2 -2
  53. package/source/directives/cors/CORS.ts +13 -7
  54. package/source/directives/dev/Development.ts +3 -3
  55. package/source/directives/index.ts +5 -4
  56. package/source/directives/io/Directive.ts +11 -0
  57. package/source/directives/io/IO.ts +43 -0
  58. package/source/directives/io/Input.ts +50 -0
  59. package/source/directives/io/Message.ts +1 -0
  60. package/source/directives/io/Output.ts +69 -0
  61. package/source/directives/io/index.ts +3 -0
  62. package/source/directives/io/schemas.ts +12 -0
  63. package/source/directives/octets/Octets.ts +2 -3
  64. package/source/directives/octets/Store.ts +8 -2
  65. package/source/directives/vary/Vary.ts +2 -2
  66. package/source/directives/vary/embeddings/Header.ts +8 -6
  67. package/source/directives/vary/embeddings/Language.ts +1 -1
  68. package/source/root.ts +5 -0
  69. package/transpiled/Context.d.ts +6 -4
  70. package/transpiled/Directive.d.ts +4 -17
  71. package/transpiled/Directive.js +0 -3
  72. package/transpiled/Directive.js.map +1 -1
  73. package/transpiled/Endpoint.d.ts +5 -3
  74. package/transpiled/Endpoint.js +29 -4
  75. package/transpiled/Endpoint.js.map +1 -1
  76. package/transpiled/Factory.js +5 -1
  77. package/transpiled/Factory.js.map +1 -1
  78. package/transpiled/Gateway.d.ts +1 -4
  79. package/transpiled/Gateway.js +1 -17
  80. package/transpiled/Gateway.js.map +1 -1
  81. package/transpiled/HTTP/Server.js +2 -2
  82. package/transpiled/HTTP/Server.js.map +1 -1
  83. package/transpiled/RTD/Context.d.ts +7 -6
  84. package/transpiled/RTD/Directives.d.ts +19 -4
  85. package/transpiled/RTD/Endpoint.d.ts +6 -4
  86. package/transpiled/RTD/Match.d.ts +2 -4
  87. package/transpiled/RTD/Method.d.ts +7 -7
  88. package/transpiled/RTD/Method.js.map +1 -1
  89. package/transpiled/RTD/Node.d.ts +4 -6
  90. package/transpiled/RTD/Node.js +2 -1
  91. package/transpiled/RTD/Node.js.map +1 -1
  92. package/transpiled/RTD/Tree.d.ts +6 -6
  93. package/transpiled/RTD/Tree.js +4 -1
  94. package/transpiled/RTD/Tree.js.map +1 -1
  95. package/transpiled/RTD/factory.d.ts +2 -4
  96. package/transpiled/RTD/factory.js.map +1 -1
  97. package/transpiled/directives/auth/Authorization.d.ts +2 -3
  98. package/transpiled/directives/auth/Authorization.js +3 -3
  99. package/transpiled/directives/auth/Authorization.js.map +1 -1
  100. package/transpiled/directives/cache/Cache.d.ts +2 -2
  101. package/transpiled/directives/cors/CORS.d.ts +2 -3
  102. package/transpiled/directives/cors/CORS.js +13 -7
  103. package/transpiled/directives/cors/CORS.js.map +1 -1
  104. package/transpiled/directives/dev/Development.d.ts +3 -3
  105. package/transpiled/directives/dev/Development.js.map +1 -1
  106. package/transpiled/directives/index.d.ts +2 -2
  107. package/transpiled/directives/index.js +4 -3
  108. package/transpiled/directives/index.js.map +1 -1
  109. package/transpiled/directives/io/Directive.d.ts +8 -0
  110. package/transpiled/directives/io/Directive.js +3 -0
  111. package/transpiled/directives/io/Directive.js.map +1 -0
  112. package/transpiled/directives/io/IO.d.ts +9 -0
  113. package/transpiled/directives/io/IO.js +33 -0
  114. package/transpiled/directives/io/IO.js.map +1 -0
  115. package/transpiled/directives/io/Input.d.ts +11 -0
  116. package/transpiled/directives/io/Input.js +63 -0
  117. package/transpiled/directives/io/Input.js.map +1 -0
  118. package/transpiled/directives/io/Message.d.ts +1 -0
  119. package/transpiled/directives/io/Message.js +3 -0
  120. package/transpiled/directives/io/Message.js.map +1 -0
  121. package/transpiled/directives/io/Output.d.ts +13 -0
  122. package/transpiled/directives/io/Output.js +76 -0
  123. package/transpiled/directives/io/Output.js.map +1 -0
  124. package/transpiled/directives/io/index.d.ts +2 -0
  125. package/transpiled/directives/io/index.js +6 -0
  126. package/transpiled/directives/io/index.js.map +1 -0
  127. package/transpiled/directives/io/schemas.d.ts +7 -0
  128. package/transpiled/directives/io/schemas.js +14 -0
  129. package/transpiled/directives/io/schemas.js.map +1 -0
  130. package/transpiled/directives/octets/Octets.d.ts +2 -3
  131. package/transpiled/directives/octets/Octets.js.map +1 -1
  132. package/transpiled/directives/octets/Store.js +7 -2
  133. package/transpiled/directives/octets/Store.js.map +1 -1
  134. package/transpiled/directives/vary/Vary.d.ts +2 -2
  135. package/transpiled/directives/vary/embeddings/Header.js +8 -6
  136. package/transpiled/directives/vary/embeddings/Header.js.map +1 -1
  137. package/transpiled/directives/vary/embeddings/Language.js +1 -1
  138. package/transpiled/directives/vary/embeddings/Language.js.map +1 -1
  139. package/transpiled/root.js +5 -0
  140. package/transpiled/root.js.map +1 -1
  141. package/transpiled/tsconfig.tsbuildinfo +1 -1
@@ -5,7 +5,7 @@ import { type Context } from './Context'
5
5
  import * as http from './HTTP'
6
6
  import type * as RTD from './RTD'
7
7
 
8
- export class Endpoint implements RTD.Endpoint<Endpoint> {
8
+ export class Endpoint implements RTD.Endpoint {
9
9
  private readonly endpoint: string
10
10
  private readonly mapping: Mapping
11
11
  private readonly discovery: Promise<Component>
@@ -18,7 +18,9 @@ export class Endpoint implements RTD.Endpoint<Endpoint> {
18
18
  }
19
19
 
20
20
  public async call
21
- (body: any, query: http.Query, parameters: RTD.Parameter[]): Promise<http.OutgoingMessage> {
21
+ (context: http.Context, parameters: RTD.Parameter[]): Promise<http.OutgoingMessage> {
22
+ const body = await context.body()
23
+ const query = this.query(context)
22
24
  const request = this.mapping.fit(body, query, parameters)
23
25
 
24
26
  this.remote ??= await this.discovery
@@ -28,14 +30,26 @@ export class Endpoint implements RTD.Endpoint<Endpoint> {
28
30
  if (reply instanceof Error)
29
31
  throw new http.Conflict(reply)
30
32
 
31
- const message: http.OutgoingMessage = { body: reply }
33
+ const message: http.OutgoingMessage = {}
32
34
 
33
35
  if (typeof reply === 'object' && reply !== null && '_version' in reply) {
36
+ const etag = context.request.headers['if-none-match']
37
+
34
38
  message.headers ??= new Headers()
35
- message.headers.set('etag', `"${reply._version.toString()}"`)
36
- delete reply._version
39
+
40
+ if (etag !== undefined && reply._version === this.version(etag)) {
41
+ message.status = 304
42
+ message.headers.set('etag', etag)
43
+
44
+ return message
45
+ } else {
46
+ message.headers.set('etag', `"${reply._version.toString()}"`)
47
+ delete reply._version
48
+ }
37
49
  }
38
50
 
51
+ message.body = reply
52
+
39
53
  return message
40
54
  }
41
55
 
@@ -44,9 +58,28 @@ export class Endpoint implements RTD.Endpoint<Endpoint> {
44
58
 
45
59
  await this.remote.disconnect(INTERRUPT)
46
60
  }
61
+
62
+ private query (context: http.Context): http.Query {
63
+ const query: http.Query = Object.fromEntries(context.url.searchParams)
64
+ const etag = context.request.headers['if-match']
65
+
66
+ if (etag !== undefined)
67
+ query.version = this.version(etag)
68
+
69
+ return query
70
+ }
71
+
72
+ private version (etag: string): number {
73
+ const match = etag.match(ETAG)
74
+
75
+ if (match === null)
76
+ throw new http.BadRequest('Invalid ETag.')
77
+
78
+ return Number.parseInt(match.groups!.version)
79
+ }
47
80
  }
48
81
 
49
- export class EndpointsFactory implements RTD.EndpointsFactory<Endpoint> {
82
+ export class EndpointsFactory implements RTD.EndpointsFactory {
50
83
  private readonly remotes: Remotes
51
84
 
52
85
  public constructor (remotes: Remotes) {
@@ -70,4 +103,6 @@ export class EndpointsFactory implements RTD.EndpointsFactory<Endpoint> {
70
103
  }
71
104
  }
72
105
 
106
+ const ETAG = /^"(?<version>\d{1,32})"$/
107
+
73
108
  const INTERRUPT = true
package/source/Factory.ts CHANGED
@@ -3,9 +3,9 @@ import { Gateway } from './Gateway'
3
3
  import { Remotes } from './Remotes'
4
4
  import { Tree, syntax } from './RTD'
5
5
  import { Server } from './HTTP'
6
- import { type Endpoint, EndpointsFactory } from './Endpoint'
6
+ import { EndpointsFactory } from './Endpoint'
7
7
  import { families, interceptors } from './directives'
8
- import { type Directives, DirectivesFactory } from './Directive'
8
+ import { DirectivesFactory } from './Directive'
9
9
  import { Composition } from './Composition'
10
10
  import * as root from './root'
11
11
  import { Interception } from './Interception'
@@ -29,13 +29,19 @@ export class Factory implements extensions.Factory {
29
29
  const debug = process.env.TOA_EXPOSITION_DEBUG === '1'
30
30
  const trace = process.env.TOA_EXPOSITION_TRACE === '1'
31
31
  const broadcast: Broadcast = this.boot.bindings.broadcast(CHANNEL)
32
- const server = Server.create({ methods: syntax.verbs, debug, trace })
32
+
33
+ const server = Server.create({
34
+ methods: syntax.verbs,
35
+ debug,
36
+ trace
37
+ })
38
+
33
39
  const remotes = new Remotes(this.boot)
34
40
  const node = root.resolve()
35
41
  const methods = new EndpointsFactory(remotes)
36
42
  const directives = new DirectivesFactory(families, remotes)
37
43
  const interception = new Interception(interceptors)
38
- const tree = new Tree<Endpoint, Directives>(node, methods, directives)
44
+ const tree = new Tree(node, methods, directives)
39
45
 
40
46
  const composition = new Composition(this.boot)
41
47
  const gateway = new Gateway(broadcast, tree, interception)
package/source/Gateway.ts CHANGED
@@ -5,16 +5,13 @@ import type { Interception } from './Interception'
5
5
  import type { Method, Parameter, Tree } from './RTD'
6
6
  import type { Label } from './discovery'
7
7
  import type { Branch } from './Branch'
8
- import type { Endpoint } from './Endpoint'
9
- import type { Directives } from './Directive'
10
8
 
11
9
  export class Gateway extends Connector {
12
10
  private readonly broadcast: Broadcast
13
- private readonly tree: Tree<Endpoint, Directives>
11
+ private readonly tree: Tree
14
12
  private readonly interceptor: Interception
15
13
 
16
- // eslint-disable-next-line max-len
17
- public constructor (broadcast: Broadcast, tree: Tree<Endpoint, Directives>, interception: Interception) {
14
+ public constructor (broadcast: Broadcast, tree: Tree, interception: Interception) {
18
15
  super()
19
16
 
20
17
  this.broadcast = broadcast
@@ -67,8 +64,7 @@ export class Gateway extends Connector {
67
64
  console.info('Gateway is closed.')
68
65
  }
69
66
 
70
- private async call
71
- (method: Method<Endpoint, Directives>, context: http.Context, parameters: Parameter[]):
67
+ private async call (method: Method, context: http.Context, parameters: Parameter[]):
72
68
  Promise<http.OutgoingMessage> {
73
69
  if (context.url.pathname[context.url.pathname.length - 1] !== '/')
74
70
  throw new http.NotFound('Trailing slash is required.')
@@ -79,30 +75,11 @@ export class Gateway extends Connector {
79
75
  if (method.endpoint === null)
80
76
  throw new http.MethodNotAllowed()
81
77
 
82
- const body = await context.body()
83
- const query = this.query(context)
84
-
85
78
  return await method.endpoint
86
- .call(body, query, parameters)
79
+ .call(context, parameters)
87
80
  .catch(rethrow) as http.OutgoingMessage
88
81
  }
89
82
 
90
- private query (context: http.Context): http.Query {
91
- const query: http.Query = Object.fromEntries(context.url.searchParams)
92
- const etag = context.request.headers['if-match']
93
-
94
- if (etag !== undefined) {
95
- const match = etag.match(ETAG)
96
-
97
- if (match === null)
98
- throw new http.BadRequest('Invalid ETag.')
99
- else
100
- query.version = parseInt(match.groups!.version)
101
- }
102
-
103
- return query
104
- }
105
-
106
83
  private async discover (): Promise<void> {
107
84
  await this.broadcast.receive<Branch>('expose', this.merge.bind(this))
108
85
  await this.broadcast.transmit<null>('ping', null)
@@ -120,6 +97,4 @@ export class Gateway extends Connector {
120
97
  }
121
98
  }
122
99
 
123
- const ETAG = /^"(?<version>\d{1,32})"$/
124
-
125
100
  export type Broadcast = bindings.Broadcast<Label>
@@ -102,7 +102,7 @@ export class Server extends Connector {
102
102
  else
103
103
  status = 200
104
104
 
105
- response.statusCode = status
105
+ response.statusCode = message.status = status
106
106
  write(context, response, message)
107
107
  }
108
108
  }
@@ -116,7 +116,7 @@ export class Server extends Connector {
116
116
  ? exception.status
117
117
  : 500
118
118
 
119
- const message: OutgoingMessage = {}
119
+ const message: OutgoingMessage = { status: response.statusCode }
120
120
  const verbose = exception instanceof ClientError || this.properties.debug
121
121
 
122
122
  if (verbose)
@@ -1,16 +1,13 @@
1
- import { type Directives, type DirectivesFactory } from './Directives'
2
- import { type Endpoint, type EndpointsFactory } from './Endpoint'
1
+ import { type DirectiveFactory } from './Directives'
2
+ import { type EndpointsFactory } from './Endpoint'
3
+ import type { Directive } from './syntax'
3
4
 
4
- export interface Context<
5
- TEndpoint extends Endpoint = any,
6
- TDirectives extends Directives<TDirectives> = any,
7
- TExtension = any
8
- > {
5
+ export interface Context<TExtension = any> {
9
6
  readonly protected: boolean
10
- readonly endpoints: EndpointsFactory<TEndpoint>
7
+ readonly endpoints: EndpointsFactory
11
8
  readonly directives: {
12
- readonly factory: DirectivesFactory
13
- stack: TDirectives[]
9
+ readonly factory: DirectiveFactory
10
+ stack: Directive[]
14
11
  }
15
12
  readonly extension?: TExtension
16
13
  }
@@ -1,9 +1,33 @@
1
+ import type { Parameter } from './Match'
1
2
  import type * as syntax from './syntax'
3
+ import type { Context, OutgoingMessage } from '../HTTP'
4
+ import type { Output } from '../io'
2
5
 
3
- export interface Directives<TDirective = any> {
4
- merge: (directive: TDirective) => void
6
+ export interface Directives {
7
+ preflight: (context: Context, parameters: Parameter[]) => Promise<Output>
8
+ settle: (context: Context, response: OutgoingMessage) => Promise<void>
5
9
  }
6
10
 
7
- export interface DirectivesFactory<T = any> {
8
- create: (directives: syntax.Directive[]) => T
11
+ export interface DirectiveFactory {
12
+ create: (directives: syntax.Directive[]) => Directives
13
+ }
14
+
15
+ export interface DirectiveSet {
16
+ family: DirectiveFamily
17
+ directives: any[]
18
+ }
19
+
20
+ export interface DirectiveFamily<TDirective = any, TExtension = any> {
21
+ readonly name: string
22
+ readonly mandatory: boolean
23
+
24
+ create: (name: string, ...rest: any[]) => TDirective
25
+
26
+ preflight?: (directives: TDirective[],
27
+ request: Context & TExtension,
28
+ parameters: Parameter[]) => Output | Promise<Output>
29
+
30
+ settle?: (directives: TDirective[],
31
+ request: Context & TExtension,
32
+ response: OutgoingMessage) => void | Promise<void>
9
33
  }
@@ -1,11 +1,13 @@
1
1
  import { type Context } from './Context'
2
+ import type * as http from '../HTTP'
2
3
  import type * as syntax from './syntax'
4
+ import type * as RTD from './index'
3
5
 
4
- export interface Endpoint<T extends Endpoint = any> {
5
- call: T['call']
6
+ export interface Endpoint {
7
+ call: (context: http.Context, parameters: RTD.Parameter[]) => Promise<http.OutgoingMessage>
6
8
  close: () => Promise<void>
7
9
  }
8
10
 
9
- export interface EndpointsFactory<T extends Endpoint<T> = any> {
10
- create: (method: syntax.Method, context: Context) => T
11
+ export interface EndpointsFactory {
12
+ create: (method: syntax.Method, context: Context) => Endpoint
11
13
  }
@@ -1,12 +1,7 @@
1
1
  import { type Node } from './Node'
2
- import { type Directives } from './Directives'
3
- import { type Endpoint } from './Endpoint'
4
2
 
5
- export interface Match<
6
- TEndpoint extends Endpoint<TEndpoint> = any,
7
- TDirectives extends Directives<TDirectives> = any
8
- > {
9
- node: Node<TEndpoint, TDirectives>
3
+ export interface Match {
4
+ node: Node
10
5
  parameters: Parameter[]
11
6
  }
12
7
 
@@ -1,14 +1,11 @@
1
- import { type Directives } from './Directives'
2
- import { type Endpoint } from './Endpoint'
1
+ import type { Endpoint } from './Endpoint'
2
+ import type { Directives } from './Directives'
3
3
 
4
- export class Method<
5
- TEndpoint extends Endpoint<TEndpoint> = any,
6
- TDirectives extends Directives<TDirectives> = any
7
- > {
8
- public readonly endpoint: TEndpoint | null
9
- public readonly directives: TDirectives
4
+ export class Method {
5
+ public readonly endpoint: Endpoint | null
6
+ public readonly directives: Directives
10
7
 
11
- public constructor (endpoint: TEndpoint | null, directives: TDirectives) {
8
+ public constructor (endpoint: Endpoint | null, directives: Directives) {
12
9
  this.endpoint = endpoint
13
10
  this.directives = directives
14
11
  }
@@ -18,7 +15,4 @@ export class Method<
18
15
  }
19
16
  }
20
17
 
21
- export type Methods<
22
- TEndpoint extends Endpoint<TEndpoint> = any,
23
- TDirectives extends Directives<TDirectives> = any
24
- > = Record<string, Method<TEndpoint, TDirectives>>
18
+ export type Methods = Record<string, Method>
@@ -1,20 +1,15 @@
1
1
  import { type Route } from './Route'
2
2
  import { type Methods } from './Method'
3
3
  import { type Match, type Parameter } from './Match'
4
- import { type Directives } from './Directives'
5
- import { type Endpoint } from './Endpoint'
6
4
 
7
- export class Node<
8
- TEndpoint extends Endpoint<TEndpoint> = any,
9
- TDirectives extends Directives<TDirectives> = any
10
- > {
5
+ export class Node {
11
6
  public intermediate: boolean
12
- public methods: Methods<TEndpoint, TDirectives>
7
+ public methods: Methods
13
8
  private readonly protected: boolean
14
9
  private routes: Route[]
15
10
 
16
11
  public constructor
17
- (routes: Route[], methods: Methods<TEndpoint, TDirectives>, properties: Properties) {
12
+ (routes: Route[], methods: Methods, properties: Properties) {
18
13
  this.routes = routes
19
14
  this.methods = methods
20
15
  this.protected = properties.protected
@@ -34,26 +29,30 @@ export class Node<
34
29
  return null
35
30
  }
36
31
 
37
- public merge (node: Node<TEndpoint, TDirectives>): void {
32
+ public merge (node: Node): void {
38
33
  this.intermediate = node.intermediate
39
34
 
40
- if (!this.protected) this.replace(node)
41
- else this.append(node)
35
+ if (!this.protected)
36
+ this.replace(node)
37
+ else
38
+ this.append(node)
42
39
 
43
40
  this.sort()
44
41
  }
45
42
 
46
- private replace (node: Node<TEndpoint, TDirectives>): void {
43
+ private replace (node: Node): void {
47
44
  const methods = Object.values(this.methods)
48
45
 
49
46
  this.routes = node.routes
50
47
  this.methods = node.methods
51
48
 
52
49
  for (const method of methods)
53
- void method.close() // race condition is really unlikely
50
+ void method.close()
51
+
52
+ // race condition is really unlikely
54
53
  }
55
54
 
56
- private append (node: Node<TEndpoint, TDirectives>): void {
55
+ private append (node: Node): void {
57
56
  for (const route of node.routes)
58
57
  this.mergeRoute(route)
59
58
 
@@ -1,32 +1,32 @@
1
- import { type Node } from './Node'
2
1
  import { createNode } from './factory'
3
2
  import { fragment } from './segment'
4
- import { type Match } from './Match'
5
- import { type Context } from './Context'
6
- import { type Directives, type DirectivesFactory } from './Directives'
7
- import { type Endpoint, type EndpointsFactory } from './Endpoint'
3
+ import type { Node } from './Node'
4
+ import type { Match } from './Match'
5
+ import type { Context } from './Context'
6
+ import type { DirectiveFactory } from './Directives'
7
+ import type { EndpointsFactory } from './Endpoint'
8
8
  import type * as syntax from './syntax'
9
9
 
10
- export class Tree<
11
- TEndpoint extends Endpoint<TEndpoint> = any,
12
- TDirectives extends Directives<TDirectives> = any
13
- > {
10
+ export class Tree {
14
11
  private readonly root: syntax.Node
15
- private readonly trunk: Node<TEndpoint, TDirectives>
16
- private readonly endpoints: EndpointsFactory<TEndpoint>
17
- private readonly directives: DirectivesFactory
12
+ private readonly trunk: Node
13
+ private readonly endpoints: EndpointsFactory
14
+ private readonly directives: DirectiveFactory
18
15
 
19
16
  public constructor
20
- (node: syntax.Node, endpoints: EndpointsFactory, directives: DirectivesFactory) {
17
+ (node: syntax.Node, endpoints: EndpointsFactory, directives: DirectiveFactory) {
21
18
  this.endpoints = endpoints
22
19
  this.directives = directives
23
20
  this.trunk = this.createNode(node, PROTECTED)
24
21
  this.root = node
25
22
  }
26
23
 
27
- public match (path: string): Match<TEndpoint, TDirectives> | null {
24
+ public match (path: string): Match | null {
28
25
  if (path === '/')
29
- return { node: this.trunk, parameters: [] }
26
+ return {
27
+ node: this.trunk,
28
+ parameters: []
29
+ }
30
30
 
31
31
  const fragments = fragment(path)
32
32
 
@@ -39,7 +39,8 @@ export class Tree<
39
39
  this.trunk.merge(branch)
40
40
  }
41
41
 
42
- private createNode (node: syntax.Node, protect: boolean, extension?: any): Node {
42
+ private createNode
43
+ (node: syntax.Node, protect: boolean, extension?: any): Node {
43
44
  const context: Context = {
44
45
  protected: protect,
45
46
  endpoints: this.endpoints,
@@ -1,14 +1,11 @@
1
1
  import { Node, type Properties } from './Node'
2
2
  import { Route } from './Route'
3
- import { type Context } from './Context'
4
3
  import { segment } from './segment'
5
4
  import { Method, type Methods } from './Method'
6
- import { type Endpoint } from './Endpoint'
7
- import { type Directives } from './Directives'
5
+ import type { Context } from './Context'
8
6
  import type * as syntax from './syntax'
9
7
 
10
- export function createNode<TEndpoint extends Endpoint, TDirectives extends Directives>
11
- (node: syntax.Node, context: Context): Node<TEndpoint, TDirectives> {
8
+ export function createNode (node: syntax.Node, context: Context): Node {
12
9
  if (node.isolated === true)
13
10
  context.directives.stack = node.directives
14
11
  else
@@ -13,8 +13,7 @@ import { PRIMARY, PROVIDERS } from './schemes'
13
13
  import type { Output } from '../../io'
14
14
  import type { Component } from '@toa.io/core'
15
15
  import type { Remotes } from '../../Remotes'
16
- import type { Family } from '../../Directive'
17
- import type { Parameter } from '../../RTD'
16
+ import type { Parameter, DirectiveFamily } from '../../RTD'
18
17
  import type {
19
18
  AuthenticationResult,
20
19
  Ban,
@@ -27,7 +26,7 @@ import type {
27
26
  Schemes
28
27
  } from './types'
29
28
 
30
- export class Authorization implements Family<Directive, Extension> {
29
+ export class Authorization implements DirectiveFamily<Directive, Extension> {
31
30
  public readonly depends: string[] = ['Vary']
32
31
  public readonly name: string = 'auth'
33
32
  public readonly mandatory: boolean = true
@@ -38,10 +37,10 @@ export class Authorization implements Family<Directive, Extension> {
38
37
  private bans: Component | null = null
39
38
 
40
39
  public create (name: string, value: any, remotes: Remotes): Directive {
41
- assert.ok(name in CLASSES,
40
+ assert.ok(name in constructors,
42
41
  `Directive '${name}' is not provided by the '${this.name}' family.`)
43
42
 
44
- const Class = CLASSES[name]
43
+ const Class = constructors[name]
45
44
 
46
45
  for (const name of REMOTES)
47
46
  this.discovery[name] ??= remotes.discover('identity', name)
@@ -133,7 +132,7 @@ export class Authorization implements Family<Directive, Extension> {
133
132
  }
134
133
  }
135
134
 
136
- const CLASSES: Record<string, new (value: any, argument?: any) => Directive> = {
135
+ const constructors: Record<string, new (value: any, argument?: any) => Directive> = {
137
136
  anonymous: Anonymous,
138
137
  id: Id,
139
138
  role: Role,
@@ -2,10 +2,10 @@ import { Control } from './Control'
2
2
  import { Exact } from './Exact'
3
3
  import type { Input, Output } from '../../io'
4
4
  import type { Directive } from './types'
5
- import type { Family } from '../../Directive'
5
+ import type { DirectiveFamily } from '../../RTD'
6
6
  import type * as http from '../../HTTP'
7
7
 
8
- export class Cache implements Family<Directive> {
8
+ export class Cache implements DirectiveFamily<Directive> {
9
9
  public readonly name: string = 'cache'
10
10
  public readonly mandatory: boolean = false
11
11
 
@@ -3,16 +3,22 @@ import type { Interceptor } from '../../Interception'
3
3
 
4
4
  export class CORS implements Interceptor {
5
5
  public readonly name = 'cors'
6
- public readonly mandatory = true
7
6
 
8
- private readonly allowedHeaders = new Set<string>(['accept', 'authorization', 'content-type'])
7
+ private readonly requestHeaders = new Set<string>([
8
+ 'accept',
9
+ 'authorization',
10
+ 'content-type',
11
+ 'etag',
12
+ 'if-match',
13
+ 'if-none-match'
14
+ ])
9
15
 
10
16
  private readonly headers = new Headers({
11
17
  'access-control-allow-methods': 'GET, POST, PUT, PATCH, DELETE',
12
18
  'access-control-allow-credentials': 'true',
13
- 'access-control-allow-headers': Array.from(this.allowedHeaders).join(', '),
19
+ 'access-control-allow-headers': Array.from(this.requestHeaders).join(', '),
14
20
  'access-control-max-age': '3600',
15
- 'cache-control': 'public, max-age=3600',
21
+ 'cache-control': 'max-age=3600',
16
22
  vary: 'origin'
17
23
  })
18
24
 
@@ -40,9 +46,9 @@ export class CORS implements Interceptor {
40
46
  return null
41
47
  }
42
48
 
43
- public allowHeader (header: string): void {
44
- this.allowedHeaders.add(header.toLowerCase())
45
- this.headers.set('access-control-allow-headers', Array.from(this.allowedHeaders).join(', '))
49
+ public allow (header: string): void {
50
+ this.requestHeaders.add(header.toLowerCase())
51
+ this.headers.set('access-control-allow-headers', Array.from(this.requestHeaders).join(', '))
46
52
  }
47
53
 
48
54
  private preflightResponse (origin: string): Output {
@@ -2,13 +2,13 @@ import { Stub } from './Stub'
2
2
  import { Throw } from './Throw'
3
3
  import { type Directive } from './types'
4
4
  import type { Input, Output } from '../../io'
5
- import type { Family } from '../../Directive'
5
+ import type { DirectiveFamily } from '../../RTD'
6
6
 
7
- export class Development implements Family<Directive> {
7
+ export class Development implements DirectiveFamily<Directive> {
8
8
  public readonly name: string = 'dev'
9
9
  public readonly mandatory: boolean = false
10
10
 
11
- public create (name: string, value: any): Directive {
11
+ public create (name: string, value: unknown): Directive {
12
12
  const Class = constructors[name]
13
13
 
14
14
  if (Class === undefined)
@@ -1,11 +1,12 @@
1
- import { dev } from './dev'
2
1
  import { authorization } from './auth'
3
2
  import { cache } from './cache'
4
- import { octets } from './octets'
5
3
  import { cors } from './cors'
4
+ import { dev } from './dev'
5
+ import { octets } from './octets'
6
+ import { io } from './io'
6
7
  import { vary } from './vary'
7
- import type { Family } from '../Directive'
8
+ import type { DirectiveFamily } from '../RTD'
8
9
  import type { Interceptor } from '../Interception'
9
10
 
10
- export const families: Family[] = [authorization, cache, octets, vary, dev]
11
+ export const families: DirectiveFamily[] = [authorization, io, cache, vary, octets, dev]
11
12
  export const interceptors: Interceptor[] = [cors]
@@ -0,0 +1,11 @@
1
+ import type { Input } from '../../io'
2
+
3
+ export interface Directive {
4
+ attach: (context: Input) => void
5
+ }
6
+
7
+ export interface Constructor {
8
+ validate: (value: unknown) => void
9
+
10
+ new (value: any): Directive
11
+ }
@@ -0,0 +1,43 @@
1
+ import { Output } from './Output'
2
+ import { Input } from './Input'
3
+ import type { Constructor, Directive } from './Directive'
4
+ import type { Input as Context } from '../../io'
5
+ import type { DirectiveFamily } from '../../RTD'
6
+
7
+ export class IO implements DirectiveFamily<Directive> {
8
+ public readonly name = 'io'
9
+ public readonly mandatory = true
10
+
11
+ public create (name: string, value: unknown): Directive {
12
+ if (!(name in constructors))
13
+ throw new Error(`Directive 'io:${name}' is not implemented.`)
14
+
15
+ const Directive = constructors[name]
16
+
17
+ Directive.validate(value)
18
+
19
+ return new Directive(value)
20
+ }
21
+
22
+ public preflight (directives: Directive[], context: Context): null {
23
+ let restricted = false
24
+
25
+ for (const directive of directives) {
26
+ restricted ||= directive instanceof Output
27
+
28
+ directive.attach(context)
29
+ }
30
+
31
+ if (!restricted)
32
+ DENIAL.attach(context)
33
+
34
+ return null
35
+ }
36
+ }
37
+
38
+ const constructors: Record<string, Constructor> = {
39
+ output: Output,
40
+ input: Input
41
+ }
42
+
43
+ const DENIAL: Output = new Output([])