@toa.io/extensions.exposition 1.0.0-alpha.4 → 1.0.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/features/cors.feature +2 -2
- package/features/etag.feature +11 -1
- package/package.json +7 -7
- 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/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 +2 -3
- 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 +2 -2
- 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 +1 -1
- package/source/directives/vary/embeddings/Language.ts +1 -1
- 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/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.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.map +1 -1
- 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 +1 -1
- 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/tsconfig.tsbuildinfo +1 -1
package/features/cors.feature
CHANGED
|
@@ -19,10 +19,10 @@ Feature: CORS Support
|
|
|
19
19
|
204 No Content
|
|
20
20
|
access-control-allow-origin: https://hello.world
|
|
21
21
|
access-control-allow-methods: GET, POST, PUT, PATCH, DELETE
|
|
22
|
-
access-control-allow-headers: accept, authorization, content-type
|
|
22
|
+
access-control-allow-headers: accept, authorization, content-type, etag, if-match, if-none-match
|
|
23
23
|
access-control-allow-credentials: true
|
|
24
24
|
access-control-max-age: 3600
|
|
25
|
-
cache-control:
|
|
25
|
+
cache-control: max-age=3600
|
|
26
26
|
vary: origin
|
|
27
27
|
"""
|
|
28
28
|
When the following request is received:
|
package/features/etag.feature
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Feature: Optimistic concurrency control
|
|
2
2
|
|
|
3
|
-
Scenario:
|
|
3
|
+
Scenario: Using `etag`
|
|
4
4
|
Given the `pots` is running with the following manifest:
|
|
5
5
|
"""yaml
|
|
6
6
|
exposition:
|
|
@@ -35,6 +35,16 @@ Feature: Optimistic concurrency control
|
|
|
35
35
|
200 OK
|
|
36
36
|
etag: "1"
|
|
37
37
|
"""
|
|
38
|
+
When the following request is received:
|
|
39
|
+
"""
|
|
40
|
+
GET /pots/${{ id }}/ HTTP/1.1
|
|
41
|
+
if-none-match: "1"
|
|
42
|
+
"""
|
|
43
|
+
Then the following reply is sent:
|
|
44
|
+
"""
|
|
45
|
+
304 Not Modified
|
|
46
|
+
etag: "1"
|
|
47
|
+
"""
|
|
38
48
|
When the following request is received:
|
|
39
49
|
"""
|
|
40
50
|
PUT /pots/${{ id }}/ HTTP/1.1
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/extensions.exposition",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.5",
|
|
4
4
|
"description": "Toa Exposition",
|
|
5
5
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/toa-io/toa#readme",
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@toa.io/core": "1.0.0-alpha.
|
|
21
|
-
"@toa.io/generic": "1.0.0-alpha.
|
|
22
|
-
"@toa.io/schemas": "1.0.0-alpha.
|
|
20
|
+
"@toa.io/core": "1.0.0-alpha.5",
|
|
21
|
+
"@toa.io/generic": "1.0.0-alpha.5",
|
|
22
|
+
"@toa.io/schemas": "1.0.0-alpha.5",
|
|
23
23
|
"bcryptjs": "2.4.3",
|
|
24
24
|
"error-value": "0.3.0",
|
|
25
25
|
"js-yaml": "4.1.0",
|
|
@@ -43,11 +43,11 @@
|
|
|
43
43
|
"features": "cucumber-js"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@toa.io/agent": "1.0.0-alpha.
|
|
47
|
-
"@toa.io/extensions.storages": "1.0.0-alpha.
|
|
46
|
+
"@toa.io/agent": "1.0.0-alpha.5",
|
|
47
|
+
"@toa.io/extensions.storages": "1.0.0-alpha.5",
|
|
48
48
|
"@types/bcryptjs": "2.4.3",
|
|
49
49
|
"@types/cors": "2.8.13",
|
|
50
50
|
"@types/negotiator": "0.6.1"
|
|
51
51
|
},
|
|
52
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "1e4bb4ac28a6dddff1f4b8c5be7224fcdc47b847"
|
|
53
53
|
}
|
package/source/Context.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { type Endpoint } from './Endpoint'
|
|
2
|
-
import { type Directives } from './Directive'
|
|
3
|
-
import { type Branch } from './Branch'
|
|
4
1
|
import type * as RTD from './RTD'
|
|
5
2
|
|
|
6
|
-
export type Context = RTD.Context<
|
|
3
|
+
export type Context = RTD.Context<Extension>
|
|
4
|
+
|
|
5
|
+
interface Extension {
|
|
6
|
+
namespace: string
|
|
7
|
+
component: string
|
|
8
|
+
}
|
package/source/Directive.test.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import assert from 'node:assert'
|
|
2
2
|
import { generate } from 'randomstring'
|
|
3
|
-
import { DirectivesFactory
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { DirectivesFactory } from './Directive'
|
|
4
|
+
import type { syntax, DirectiveFamily } from './RTD'
|
|
5
|
+
import type { Remotes } from './Remotes'
|
|
6
6
|
import type { Context } from './HTTP'
|
|
7
7
|
|
|
8
|
-
const families: Array<jest.MockedObjectDeep<
|
|
8
|
+
const families: Array<jest.MockedObjectDeep<DirectiveFamily>> = [
|
|
9
9
|
{
|
|
10
10
|
name: 'foo',
|
|
11
11
|
mandatory: true,
|
package/source/Directive.ts
CHANGED
|
@@ -3,10 +3,10 @@ import type { Remotes } from './Remotes'
|
|
|
3
3
|
import type { Output } from './io'
|
|
4
4
|
import type * as RTD from './RTD'
|
|
5
5
|
|
|
6
|
-
export class Directives implements RTD.Directives
|
|
7
|
-
private readonly sets: DirectiveSet[]
|
|
6
|
+
export class Directives implements RTD.Directives {
|
|
7
|
+
private readonly sets: RTD.DirectiveSet[]
|
|
8
8
|
|
|
9
|
-
public constructor (sets: DirectiveSet[]) {
|
|
9
|
+
public constructor (sets: RTD.DirectiveSet[]) {
|
|
10
10
|
this.sets = sets
|
|
11
11
|
}
|
|
12
12
|
|
|
@@ -32,18 +32,14 @@ export class Directives implements RTD.Directives<Directives> {
|
|
|
32
32
|
if (set.family.settle !== undefined)
|
|
33
33
|
await set.family.settle(set.directives, context, response)
|
|
34
34
|
}
|
|
35
|
-
|
|
36
|
-
public merge (directives: Directives): void {
|
|
37
|
-
this.sets.push(...directives.sets)
|
|
38
|
-
}
|
|
39
35
|
}
|
|
40
36
|
|
|
41
|
-
export class DirectivesFactory implements RTD.
|
|
37
|
+
export class DirectivesFactory implements RTD.DirectiveFactory {
|
|
42
38
|
private readonly remotes: Remotes
|
|
43
|
-
private readonly families: Record<string,
|
|
39
|
+
private readonly families: Record<string, RTD.DirectiveFamily> = {}
|
|
44
40
|
private readonly mandatory: string[] = []
|
|
45
41
|
|
|
46
|
-
public constructor (families:
|
|
42
|
+
public constructor (families: RTD.DirectiveFamily[], remotes: Remotes) {
|
|
47
43
|
for (const family of families) {
|
|
48
44
|
this.families[family.name] = family
|
|
49
45
|
|
|
@@ -74,7 +70,7 @@ export class DirectivesFactory implements RTD.DirectivesFactory<Directives> {
|
|
|
74
70
|
mandatory.delete(family.name)
|
|
75
71
|
}
|
|
76
72
|
|
|
77
|
-
const sets: DirectiveSet[] = []
|
|
73
|
+
const sets: RTD.DirectiveSet[] = []
|
|
78
74
|
|
|
79
75
|
for (const family of mandatory)
|
|
80
76
|
sets.push({
|
|
@@ -99,25 +95,3 @@ export const shortcuts: RTD.syntax.Shortcuts = new Map([
|
|
|
99
95
|
['rule', 'auth:rule'],
|
|
100
96
|
['incept', 'auth:incept']
|
|
101
97
|
])
|
|
102
|
-
|
|
103
|
-
export interface Family<TDirective = any, TExtension = any> {
|
|
104
|
-
readonly name: string
|
|
105
|
-
readonly mandatory: boolean
|
|
106
|
-
|
|
107
|
-
// produce: (declarations: RTD.syntax.Directive[], remotes: Remotes) => TDirective[]
|
|
108
|
-
|
|
109
|
-
create: (name: string, value: any, remotes: Remotes) => TDirective
|
|
110
|
-
|
|
111
|
-
preflight?: (directives: TDirective[],
|
|
112
|
-
request: Context & TExtension,
|
|
113
|
-
parameters: RTD.Parameter[]) => Output | Promise<Output>
|
|
114
|
-
|
|
115
|
-
settle?: (directives: TDirective[],
|
|
116
|
-
request: Context & TExtension,
|
|
117
|
-
response: OutgoingMessage) => void | Promise<void>
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
interface DirectiveSet {
|
|
121
|
-
family: Family
|
|
122
|
-
directives: any[]
|
|
123
|
-
}
|
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/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
|
|
@@ -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
|
|