@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.
- package/components/identity.basic/manifest.toa.yaml +2 -0
- package/components/identity.federation/manifest.toa.yaml +0 -6
- package/components/identity.roles/manifest.toa.yaml +1 -0
- package/documentation/io.md +56 -0
- package/documentation/query.md +9 -7
- package/documentation/tree.md +22 -4
- package/features/access.feature +11 -1
- package/features/annotation.feature +1 -0
- package/features/body.feature +1 -0
- package/features/cache.feature +3 -0
- package/features/cors.feature +2 -2
- package/features/directives.feature +2 -0
- package/features/dynamic.feature +14 -7
- package/features/errors.feature +3 -2
- package/features/etag.feature +12 -1
- package/features/identity.basic.feature +23 -0
- package/features/identity.federation.feature +3 -5
- package/features/identity.roles.feature +1 -0
- package/features/identity.tokens.feature +3 -0
- package/features/io.feature +167 -0
- package/features/octets.entries.feature +2 -0
- package/features/octets.feature +2 -0
- package/features/octets.meta.feature +4 -3
- package/features/octets.workflows.feature +1 -0
- package/features/queries.feature +9 -1
- package/features/response.feature +3 -0
- package/features/routes.feature +17 -10
- package/features/steps/components/pots/manifest.toa.yaml +2 -0
- package/features/steps/components/users/manifest.toa.yaml +1 -0
- package/features/steps/components/users.properties/manifest.toa.yaml +1 -0
- package/features/vary.feature +33 -3
- package/package.json +7 -7
- package/schemas/io/input.cos.yaml +3 -0
- package/schemas/io/message.cos.yaml +5 -0
- package/schemas/io/output.cos.yaml +5 -0
- package/source/Context.ts +6 -4
- package/source/Directive.test.ts +4 -4
- package/source/Directive.ts +7 -33
- package/source/Endpoint.ts +41 -6
- package/source/Factory.ts +10 -4
- package/source/Gateway.ts +4 -29
- package/source/HTTP/Server.ts +2 -2
- package/source/RTD/Context.ts +7 -10
- package/source/RTD/Directives.ts +28 -4
- package/source/RTD/Endpoint.ts +6 -4
- package/source/RTD/Match.ts +2 -7
- package/source/RTD/Method.ts +7 -13
- package/source/RTD/Node.ts +13 -14
- package/source/RTD/Tree.ts +17 -16
- package/source/RTD/factory.ts +2 -5
- package/source/directives/auth/Authorization.ts +5 -6
- package/source/directives/cache/Cache.ts +2 -2
- package/source/directives/cors/CORS.ts +13 -7
- package/source/directives/dev/Development.ts +3 -3
- package/source/directives/index.ts +5 -4
- package/source/directives/io/Directive.ts +11 -0
- package/source/directives/io/IO.ts +43 -0
- package/source/directives/io/Input.ts +50 -0
- package/source/directives/io/Message.ts +1 -0
- package/source/directives/io/Output.ts +69 -0
- package/source/directives/io/index.ts +3 -0
- package/source/directives/io/schemas.ts +12 -0
- package/source/directives/octets/Octets.ts +2 -3
- package/source/directives/octets/Store.ts +8 -2
- package/source/directives/vary/Vary.ts +2 -2
- package/source/directives/vary/embeddings/Header.ts +8 -6
- package/source/directives/vary/embeddings/Language.ts +1 -1
- package/source/root.ts +5 -0
- package/transpiled/Context.d.ts +6 -4
- package/transpiled/Directive.d.ts +4 -17
- package/transpiled/Directive.js +0 -3
- package/transpiled/Directive.js.map +1 -1
- package/transpiled/Endpoint.d.ts +5 -3
- package/transpiled/Endpoint.js +29 -4
- package/transpiled/Endpoint.js.map +1 -1
- package/transpiled/Factory.js +5 -1
- package/transpiled/Factory.js.map +1 -1
- package/transpiled/Gateway.d.ts +1 -4
- package/transpiled/Gateway.js +1 -17
- package/transpiled/Gateway.js.map +1 -1
- package/transpiled/HTTP/Server.js +2 -2
- package/transpiled/HTTP/Server.js.map +1 -1
- package/transpiled/RTD/Context.d.ts +7 -6
- package/transpiled/RTD/Directives.d.ts +19 -4
- package/transpiled/RTD/Endpoint.d.ts +6 -4
- package/transpiled/RTD/Match.d.ts +2 -4
- package/transpiled/RTD/Method.d.ts +7 -7
- package/transpiled/RTD/Method.js.map +1 -1
- package/transpiled/RTD/Node.d.ts +4 -6
- package/transpiled/RTD/Node.js +2 -1
- package/transpiled/RTD/Node.js.map +1 -1
- package/transpiled/RTD/Tree.d.ts +6 -6
- package/transpiled/RTD/Tree.js +4 -1
- package/transpiled/RTD/Tree.js.map +1 -1
- package/transpiled/RTD/factory.d.ts +2 -4
- package/transpiled/RTD/factory.js.map +1 -1
- package/transpiled/directives/auth/Authorization.d.ts +2 -3
- package/transpiled/directives/auth/Authorization.js +3 -3
- package/transpiled/directives/auth/Authorization.js.map +1 -1
- package/transpiled/directives/cache/Cache.d.ts +2 -2
- package/transpiled/directives/cors/CORS.d.ts +2 -3
- package/transpiled/directives/cors/CORS.js +13 -7
- package/transpiled/directives/cors/CORS.js.map +1 -1
- package/transpiled/directives/dev/Development.d.ts +3 -3
- package/transpiled/directives/dev/Development.js.map +1 -1
- package/transpiled/directives/index.d.ts +2 -2
- package/transpiled/directives/index.js +4 -3
- package/transpiled/directives/index.js.map +1 -1
- package/transpiled/directives/io/Directive.d.ts +8 -0
- package/transpiled/directives/io/Directive.js +3 -0
- package/transpiled/directives/io/Directive.js.map +1 -0
- package/transpiled/directives/io/IO.d.ts +9 -0
- package/transpiled/directives/io/IO.js +33 -0
- package/transpiled/directives/io/IO.js.map +1 -0
- package/transpiled/directives/io/Input.d.ts +11 -0
- package/transpiled/directives/io/Input.js +63 -0
- package/transpiled/directives/io/Input.js.map +1 -0
- package/transpiled/directives/io/Message.d.ts +1 -0
- package/transpiled/directives/io/Message.js +3 -0
- package/transpiled/directives/io/Message.js.map +1 -0
- package/transpiled/directives/io/Output.d.ts +13 -0
- package/transpiled/directives/io/Output.js +76 -0
- package/transpiled/directives/io/Output.js.map +1 -0
- package/transpiled/directives/io/index.d.ts +2 -0
- package/transpiled/directives/io/index.js +6 -0
- package/transpiled/directives/io/index.js.map +1 -0
- package/transpiled/directives/io/schemas.d.ts +7 -0
- package/transpiled/directives/io/schemas.js +14 -0
- package/transpiled/directives/io/schemas.js.map +1 -0
- package/transpiled/directives/octets/Octets.d.ts +2 -3
- package/transpiled/directives/octets/Octets.js.map +1 -1
- package/transpiled/directives/octets/Store.js +7 -2
- package/transpiled/directives/octets/Store.js.map +1 -1
- package/transpiled/directives/vary/Vary.d.ts +2 -2
- package/transpiled/directives/vary/embeddings/Header.js +8 -6
- package/transpiled/directives/vary/embeddings/Header.js.map +1 -1
- package/transpiled/directives/vary/embeddings/Language.js +1 -1
- package/transpiled/directives/vary/embeddings/Language.js.map +1 -1
- package/transpiled/root.js +5 -0
- package/transpiled/root.js.map +1 -1
- package/transpiled/tsconfig.tsbuildinfo +1 -1
package/source/Endpoint.ts
CHANGED
|
@@ -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
|
|
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
|
-
(
|
|
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 = {
|
|
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
|
-
|
|
36
|
-
|
|
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
|
|
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 {
|
|
6
|
+
import { EndpointsFactory } from './Endpoint'
|
|
7
7
|
import { families, interceptors } from './directives'
|
|
8
|
-
import {
|
|
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
|
-
|
|
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
|
|
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
|
|
11
|
+
private readonly tree: Tree
|
|
14
12
|
private readonly interceptor: Interception
|
|
15
13
|
|
|
16
|
-
|
|
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(
|
|
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>
|
package/source/HTTP/Server.ts
CHANGED
|
@@ -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)
|
package/source/RTD/Context.ts
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import { type
|
|
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
|
|
7
|
+
readonly endpoints: EndpointsFactory
|
|
11
8
|
readonly directives: {
|
|
12
|
-
readonly factory:
|
|
13
|
-
stack:
|
|
9
|
+
readonly factory: DirectiveFactory
|
|
10
|
+
stack: Directive[]
|
|
14
11
|
}
|
|
15
12
|
readonly extension?: TExtension
|
|
16
13
|
}
|
package/source/RTD/Directives.ts
CHANGED
|
@@ -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
|
|
4
|
-
|
|
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
|
|
8
|
-
create: (directives: syntax.Directive[]) =>
|
|
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
|
}
|
package/source/RTD/Endpoint.ts
CHANGED
|
@@ -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
|
|
5
|
-
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
|
|
10
|
-
create: (method: syntax.Method, context: Context) =>
|
|
11
|
+
export interface EndpointsFactory {
|
|
12
|
+
create: (method: syntax.Method, context: Context) => Endpoint
|
|
11
13
|
}
|
package/source/RTD/Match.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
package/source/RTD/Method.ts
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import type { Endpoint } from './Endpoint'
|
|
2
|
+
import type { Directives } from './Directives'
|
|
3
3
|
|
|
4
|
-
export class Method
|
|
5
|
-
|
|
6
|
-
|
|
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:
|
|
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>
|
package/source/RTD/Node.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
32
|
+
public merge (node: Node): void {
|
|
38
33
|
this.intermediate = node.intermediate
|
|
39
34
|
|
|
40
|
-
if (!this.protected)
|
|
41
|
-
|
|
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
|
|
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()
|
|
50
|
+
void method.close()
|
|
51
|
+
|
|
52
|
+
// race condition is really unlikely
|
|
54
53
|
}
|
|
55
54
|
|
|
56
|
-
private append (node: Node
|
|
55
|
+
private append (node: Node): void {
|
|
57
56
|
for (const route of node.routes)
|
|
58
57
|
this.mergeRoute(route)
|
|
59
58
|
|
package/source/RTD/Tree.ts
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
import { type Node } from './Node'
|
|
2
1
|
import { createNode } from './factory'
|
|
3
2
|
import { fragment } from './segment'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import
|
|
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
|
|
16
|
-
private readonly endpoints: EndpointsFactory
|
|
17
|
-
private readonly directives:
|
|
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:
|
|
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
|
|
24
|
+
public match (path: string): Match | null {
|
|
28
25
|
if (path === '/')
|
|
29
|
-
return {
|
|
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
|
|
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,
|
package/source/RTD/factory.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
40
|
+
assert.ok(name in constructors,
|
|
42
41
|
`Directive '${name}' is not provided by the '${this.name}' family.`)
|
|
43
42
|
|
|
44
|
-
const Class =
|
|
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
|
|
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 {
|
|
5
|
+
import type { DirectiveFamily } from '../../RTD'
|
|
6
6
|
import type * as http from '../../HTTP'
|
|
7
7
|
|
|
8
|
-
export class Cache implements
|
|
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
|
|
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.
|
|
19
|
+
'access-control-allow-headers': Array.from(this.requestHeaders).join(', '),
|
|
14
20
|
'access-control-max-age': '3600',
|
|
15
|
-
'cache-control': '
|
|
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
|
|
44
|
-
this.
|
|
45
|
-
this.headers.set('access-control-allow-headers', Array.from(this.
|
|
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 {
|
|
5
|
+
import type { DirectiveFamily } from '../../RTD'
|
|
6
6
|
|
|
7
|
-
export class Development implements
|
|
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:
|
|
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 {
|
|
8
|
+
import type { DirectiveFamily } from '../RTD'
|
|
8
9
|
import type { Interceptor } from '../Interception'
|
|
9
10
|
|
|
10
|
-
export const families:
|
|
11
|
+
export const families: DirectiveFamily[] = [authorization, io, cache, vary, octets, dev]
|
|
11
12
|
export const interceptors: Interceptor[] = [cors]
|
|
@@ -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([])
|