@toa.io/extensions.exposition 0.24.0-alpha.20 → 0.24.0-alpha.22
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/documentation/protocol.md +1 -0
- package/features/cors.feature +35 -2
- package/features/routes.feature +37 -0
- package/features/steps/Components.ts +15 -8
- package/features/steps/Gateway.ts +1 -2
- package/features/steps/components/users/manifest.toa.yaml +3 -0
- package/features/steps/components/users.properties/manifest.toa.yaml +13 -0
- package/features/vary.feature +32 -2
- package/package.json +8 -8
- package/source/HTTP/Server.ts +14 -19
- package/source/HTTP/messages.ts +6 -1
- package/source/RTD/Tree.ts +3 -0
- package/source/deployment.ts +1 -2
- package/source/directives/cors/CORS.ts +27 -25
- package/source/directives/index.ts +1 -1
- package/source/manifest.test.ts +6 -14
- package/source/manifest.ts +9 -6
- package/transpiled/HTTP/Server.d.ts +1 -1
- package/transpiled/HTTP/Server.js +9 -18
- package/transpiled/HTTP/Server.js.map +1 -1
- package/transpiled/HTTP/messages.js +4 -1
- package/transpiled/HTTP/messages.js.map +1 -1
- package/transpiled/RTD/Tree.js +2 -0
- package/transpiled/RTD/Tree.js.map +1 -1
- package/transpiled/deployment.js +1 -2
- package/transpiled/deployment.js.map +1 -1
- package/transpiled/directives/cors/CORS.d.ts +2 -5
- package/transpiled/directives/cors/CORS.js +20 -18
- package/transpiled/directives/cors/CORS.js.map +1 -1
- package/transpiled/directives/index.js +1 -1
- package/transpiled/directives/index.js.map +1 -1
- package/transpiled/manifest.js +10 -5
- package/transpiled/manifest.js.map +1 -1
- package/transpiled/tsconfig.tsbuildinfo +1 -1
package/features/cors.feature
CHANGED
|
@@ -21,8 +21,8 @@ Feature: CORS Support
|
|
|
21
21
|
access-control-allow-methods: GET, POST, PUT, PATCH, DELETE
|
|
22
22
|
access-control-allow-headers: accept, authorization, content-type
|
|
23
23
|
access-control-allow-credentials: true
|
|
24
|
-
access-control-max-age:
|
|
25
|
-
cache-control: public, max-age=
|
|
24
|
+
access-control-max-age: 3600
|
|
25
|
+
cache-control: public, max-age=3600
|
|
26
26
|
vary: origin
|
|
27
27
|
"""
|
|
28
28
|
When the following request is received:
|
|
@@ -37,3 +37,36 @@ Feature: CORS Support
|
|
|
37
37
|
access-control-expose-headers: authorization, content-type, content-length, etag
|
|
38
38
|
vary: origin
|
|
39
39
|
"""
|
|
40
|
+
|
|
41
|
+
Scenario: Errors contain CORS headers
|
|
42
|
+
Given the annotation:
|
|
43
|
+
"""yaml
|
|
44
|
+
/:
|
|
45
|
+
/foo:
|
|
46
|
+
GET:
|
|
47
|
+
dev:stub: Hello
|
|
48
|
+
"""
|
|
49
|
+
When the following request is received:
|
|
50
|
+
"""
|
|
51
|
+
GET /bar/ HTTP/1.1
|
|
52
|
+
origin: https://hello.world
|
|
53
|
+
"""
|
|
54
|
+
Then the following reply is sent:
|
|
55
|
+
"""
|
|
56
|
+
404 Not Found
|
|
57
|
+
access-control-allow-origin: https://hello.world
|
|
58
|
+
access-control-expose-headers: authorization, content-type, content-length, etag
|
|
59
|
+
vary: origin
|
|
60
|
+
"""
|
|
61
|
+
When the following request is received:
|
|
62
|
+
"""
|
|
63
|
+
GET /foo/ HTTP/1.1
|
|
64
|
+
origin: https://hello.world
|
|
65
|
+
"""
|
|
66
|
+
Then the following reply is sent:
|
|
67
|
+
"""
|
|
68
|
+
401 Unauthorized
|
|
69
|
+
access-control-allow-origin: https://hello.world
|
|
70
|
+
access-control-expose-headers: authorization, content-type, content-length, etag
|
|
71
|
+
vary: origin
|
|
72
|
+
"""
|
package/features/routes.feature
CHANGED
|
@@ -87,3 +87,40 @@ Feature: Routes
|
|
|
87
87
|
|
|
88
88
|
Hello
|
|
89
89
|
"""
|
|
90
|
+
|
|
91
|
+
Scenario: Routes with naming conflicts
|
|
92
|
+
Given the Gateway is running
|
|
93
|
+
And the `users` is running with the following manifest:
|
|
94
|
+
"""yaml
|
|
95
|
+
exposition:
|
|
96
|
+
/:
|
|
97
|
+
POST: create
|
|
98
|
+
"""
|
|
99
|
+
And the `users.properties` is running with the following manifest:
|
|
100
|
+
"""yaml
|
|
101
|
+
exposition:
|
|
102
|
+
/:id:
|
|
103
|
+
GET: observe
|
|
104
|
+
"""
|
|
105
|
+
When the following request is received:
|
|
106
|
+
"""
|
|
107
|
+
GET /users/properties/b5534021e30042259badffbd1831e472/ HTTP/1.1
|
|
108
|
+
accept: application/yaml
|
|
109
|
+
"""
|
|
110
|
+
Then the following reply is sent:
|
|
111
|
+
"""
|
|
112
|
+
200 OK
|
|
113
|
+
|
|
114
|
+
newbie: false
|
|
115
|
+
"""
|
|
116
|
+
When the following request is received:
|
|
117
|
+
"""
|
|
118
|
+
POST /users/ HTTP/1.1
|
|
119
|
+
content-type: application/yaml
|
|
120
|
+
|
|
121
|
+
name: Alice
|
|
122
|
+
"""
|
|
123
|
+
Then the following reply is sent:
|
|
124
|
+
"""
|
|
125
|
+
201 Created
|
|
126
|
+
"""
|
|
@@ -9,7 +9,7 @@ import { Workspace } from './Workspace'
|
|
|
9
9
|
@binding([Workspace])
|
|
10
10
|
export class Components {
|
|
11
11
|
private readonly workspace: Workspace
|
|
12
|
-
private
|
|
12
|
+
private compositions: Record<string, Connector> = {}
|
|
13
13
|
|
|
14
14
|
public constructor (workspace: Workspace) {
|
|
15
15
|
this.workspace = workspace
|
|
@@ -27,22 +27,29 @@ export class Components {
|
|
|
27
27
|
await this.runComponent(name, manifest)
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
@after()
|
|
31
30
|
@given('the `{word}` is stopped')
|
|
32
|
-
public async stop (
|
|
33
|
-
|
|
31
|
+
public async stop (name: string): Promise<void> {
|
|
32
|
+
assert.ok(name in this.compositions, `Composition '${name}' is not running`)
|
|
33
|
+
|
|
34
|
+
await this.compositions[name].disconnect()
|
|
35
|
+
delete this.compositions[name]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@after()
|
|
39
|
+
public async shutdown (): Promise<void> {
|
|
40
|
+
const promises = Object.values(this.compositions).map((composition) => composition.disconnect())
|
|
34
41
|
|
|
35
|
-
|
|
42
|
+
await Promise.all(promises)
|
|
36
43
|
}
|
|
37
44
|
|
|
38
45
|
private async runComponent (name: string, manifest?: object): Promise<void> {
|
|
39
|
-
assert.ok(this.
|
|
46
|
+
assert.ok(!(name in this.compositions), `Composition '${name}' is already running`)
|
|
40
47
|
|
|
41
48
|
const path = await this.workspace.addComponent(name, manifest)
|
|
42
49
|
|
|
43
|
-
this.
|
|
50
|
+
this.compositions[name] = await boot.composition([path])
|
|
44
51
|
|
|
45
|
-
await this.
|
|
52
|
+
await this.compositions[name].connect()
|
|
46
53
|
await timeout(50) // discovery
|
|
47
54
|
}
|
|
48
55
|
}
|
|
@@ -18,8 +18,7 @@ export class Gateway {
|
|
|
18
18
|
const annotation = parse(yaml)
|
|
19
19
|
|
|
20
20
|
if ('/' in annotation) {
|
|
21
|
-
const
|
|
22
|
-
const tree = syntax.parse(node, shortcuts)
|
|
21
|
+
const tree = syntax.parse(annotation['/'], shortcuts)
|
|
23
22
|
|
|
24
23
|
process.env.TOA_EXPOSITION = encode(tree)
|
|
25
24
|
}
|
package/features/vary.feature
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Feature: The Vary directive family
|
|
2
2
|
|
|
3
|
-
Scenario Outline: Embedding a language code
|
|
3
|
+
Scenario Outline: Embedding a `<result>` language code
|
|
4
4
|
Given the `echo` is running with the following manifest:
|
|
5
5
|
"""yaml
|
|
6
6
|
exposition:
|
|
@@ -33,6 +33,36 @@ Feature: The Vary directive family
|
|
|
33
33
|
| fr | fr |
|
|
34
34
|
| sw | en |
|
|
35
35
|
|
|
36
|
+
Scenario: Listing languages on the root
|
|
37
|
+
Given the annotation:
|
|
38
|
+
"""
|
|
39
|
+
/:
|
|
40
|
+
vary:languages: [en, fr]
|
|
41
|
+
"""
|
|
42
|
+
And the `echo` is running with the following manifest:
|
|
43
|
+
"""yaml
|
|
44
|
+
exposition:
|
|
45
|
+
/:
|
|
46
|
+
GET:
|
|
47
|
+
anonymous: true
|
|
48
|
+
vary:embed:
|
|
49
|
+
name: language
|
|
50
|
+
endpoint: compute
|
|
51
|
+
"""
|
|
52
|
+
When the following request is received:
|
|
53
|
+
"""
|
|
54
|
+
GET /echo/ HTTP/1.1
|
|
55
|
+
accept: application/yaml
|
|
56
|
+
accept-language: fr
|
|
57
|
+
"""
|
|
58
|
+
Then the following reply is sent:
|
|
59
|
+
"""
|
|
60
|
+
200 OK
|
|
61
|
+
content-type: application/yaml
|
|
62
|
+
content-language: fr
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
|
|
36
66
|
Scenario: Embedding a value of an arbitrary header
|
|
37
67
|
Given the `echo` is running with the following manifest:
|
|
38
68
|
"""yaml
|
|
@@ -116,5 +146,5 @@ Feature: The Vary directive family
|
|
|
116
146
|
Then the following reply is sent:
|
|
117
147
|
"""
|
|
118
148
|
204 No Content
|
|
119
|
-
access-control-allow-headers: accept, content-type, accept-language, foo, bar
|
|
149
|
+
access-control-allow-headers: accept, authorization, content-type, accept-language, foo, bar
|
|
120
150
|
"""
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/extensions.exposition",
|
|
3
|
-
"version": "0.24.0-alpha.
|
|
3
|
+
"version": "0.24.0-alpha.22",
|
|
4
4
|
"description": "Toa Exposition",
|
|
5
5
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/toa-io/toa#readme",
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@toa.io/core": "0.24.0-alpha.
|
|
21
|
-
"@toa.io/generic": "0.24.0-alpha.
|
|
22
|
-
"@toa.io/schemas": "0.24.0-alpha.
|
|
23
|
-
"@toa.io/streams": "0.24.0-alpha.
|
|
20
|
+
"@toa.io/core": "0.24.0-alpha.22",
|
|
21
|
+
"@toa.io/generic": "0.24.0-alpha.22",
|
|
22
|
+
"@toa.io/schemas": "0.24.0-alpha.22",
|
|
23
|
+
"@toa.io/streams": "0.24.0-alpha.22",
|
|
24
24
|
"bcryptjs": "2.4.3",
|
|
25
25
|
"error-value": "0.3.0",
|
|
26
26
|
"express": "4.18.2",
|
|
@@ -45,12 +45,12 @@
|
|
|
45
45
|
"features": "cucumber-js"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@toa.io/extensions.storages": "0.24.0-alpha.
|
|
49
|
-
"@toa.io/http": "0.24.0-alpha.
|
|
48
|
+
"@toa.io/extensions.storages": "0.24.0-alpha.22",
|
|
49
|
+
"@toa.io/http": "0.24.0-alpha.22",
|
|
50
50
|
"@types/bcryptjs": "2.4.3",
|
|
51
51
|
"@types/cors": "2.8.13",
|
|
52
52
|
"@types/express": "4.17.17",
|
|
53
53
|
"@types/negotiator": "0.6.1"
|
|
54
54
|
},
|
|
55
|
-
"gitHead": "
|
|
55
|
+
"gitHead": "9680eca8da28019924a4c7cac5d803954d6c6936"
|
|
56
56
|
}
|
package/source/HTTP/Server.ts
CHANGED
|
@@ -13,11 +13,16 @@ import type { Express, Request, Response, NextFunction } from 'express'
|
|
|
13
13
|
|
|
14
14
|
export class Server extends Connector {
|
|
15
15
|
private server?: http.Server
|
|
16
|
+
private readonly app: Express
|
|
17
|
+
private readonly debug: boolean
|
|
18
|
+
private readonly requestedPort: number
|
|
16
19
|
|
|
17
|
-
private constructor (
|
|
18
|
-
private readonly debug: boolean,
|
|
19
|
-
private readonly requestedPort: number) {
|
|
20
|
+
private constructor (app: Express, debug: boolean, port: number) {
|
|
20
21
|
super()
|
|
22
|
+
|
|
23
|
+
this.app = app
|
|
24
|
+
this.debug = debug
|
|
25
|
+
this.requestedPort = port
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
public get port (): number {
|
|
@@ -39,7 +44,6 @@ export class Server extends Connector {
|
|
|
39
44
|
const app = express()
|
|
40
45
|
|
|
41
46
|
app.disable('x-powered-by')
|
|
42
|
-
// app.use(cors(CORS))
|
|
43
47
|
app.use(supportedMethods(properties.methods))
|
|
44
48
|
|
|
45
49
|
return new Server(app, properties.debug, properties.port)
|
|
@@ -97,9 +101,6 @@ export class Server extends Connector {
|
|
|
97
101
|
|
|
98
102
|
private success (request: IncomingMessage, response: Response) {
|
|
99
103
|
return (message: OutgoingMessage) => {
|
|
100
|
-
for (const transform of request.pipelines.response)
|
|
101
|
-
transform(message)
|
|
102
|
-
|
|
103
104
|
let status = message.status
|
|
104
105
|
|
|
105
106
|
if (status === undefined)
|
|
@@ -109,12 +110,7 @@ export class Server extends Connector {
|
|
|
109
110
|
else status = 200
|
|
110
111
|
|
|
111
112
|
response.status(status)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (message.body !== undefined && message.body !== null)
|
|
115
|
-
write(request, response, message)
|
|
116
|
-
else
|
|
117
|
-
response.end()
|
|
113
|
+
write(request, response, message)
|
|
118
114
|
}
|
|
119
115
|
}
|
|
120
116
|
|
|
@@ -129,16 +125,15 @@ export class Server extends Connector {
|
|
|
129
125
|
|
|
130
126
|
response.status(status)
|
|
131
127
|
|
|
132
|
-
const
|
|
128
|
+
const message: OutgoingMessage = {}
|
|
129
|
+
const verbose = exception instanceof ClientError || this.debug
|
|
133
130
|
|
|
134
|
-
if (
|
|
135
|
-
|
|
131
|
+
if (verbose)
|
|
132
|
+
message.body = exception instanceof Exception
|
|
136
133
|
? exception.body
|
|
137
134
|
: (exception.stack ?? exception.message)
|
|
138
135
|
|
|
139
|
-
|
|
140
|
-
} else
|
|
141
|
-
response.end()
|
|
136
|
+
write(request, response, message)
|
|
142
137
|
}
|
|
143
138
|
}
|
|
144
139
|
}
|
package/source/HTTP/messages.ts
CHANGED
|
@@ -9,6 +9,11 @@ import type { Format } from './formats'
|
|
|
9
9
|
|
|
10
10
|
export function write
|
|
11
11
|
(request: IncomingMessage, response: Response, message: OutgoingMessage): void {
|
|
12
|
+
for (const transform of request.pipelines.response)
|
|
13
|
+
transform(message)
|
|
14
|
+
|
|
15
|
+
message.headers?.forEach((value, key) => response.set(key, value))
|
|
16
|
+
|
|
12
17
|
if (message.body instanceof Readable)
|
|
13
18
|
stream(message, request, response)
|
|
14
19
|
else
|
|
@@ -36,7 +41,7 @@ export async function read (request: Request): Promise<any> {
|
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
function send (message: OutgoingMessage, request: IncomingMessage, response: Response): void {
|
|
39
|
-
if (message.body === undefined) {
|
|
44
|
+
if (message.body === undefined || message.body === null) {
|
|
40
45
|
response.end()
|
|
41
46
|
|
|
42
47
|
return
|
package/source/RTD/Tree.ts
CHANGED
package/source/deployment.ts
CHANGED
|
@@ -27,8 +27,7 @@ export function deployment (_: unknown, annotation: Annotation | undefined): Dep
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
if (annotation?.['/'] !== undefined) {
|
|
30
|
-
const
|
|
31
|
-
const tree = parse(node, shortcuts)
|
|
30
|
+
const tree = parse(annotation['/'], shortcuts)
|
|
32
31
|
|
|
33
32
|
service.variables.push({
|
|
34
33
|
name: 'TOA_EXPOSITION',
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import type { OutgoingMessage } from '../../HTTP'
|
|
2
1
|
import type { Input, Output } from '../../io'
|
|
3
|
-
import type { Family } from '../../Directive'
|
|
4
2
|
import type { Interceptor } from '../../Interception'
|
|
5
3
|
|
|
6
|
-
export class CORS implements
|
|
4
|
+
export class CORS implements Interceptor {
|
|
7
5
|
public readonly name = 'cors'
|
|
8
6
|
public readonly mandatory = true
|
|
9
7
|
|
|
@@ -13,40 +11,44 @@ export class CORS implements Family, Interceptor {
|
|
|
13
11
|
'access-control-allow-methods': 'GET, POST, PUT, PATCH, DELETE',
|
|
14
12
|
'access-control-allow-credentials': 'true',
|
|
15
13
|
'access-control-allow-headers': Array.from(this.allowedHeaders).join(', '),
|
|
16
|
-
'access-control-max-age': '
|
|
17
|
-
'cache-control': 'public, max-age=
|
|
14
|
+
'access-control-max-age': '3600',
|
|
15
|
+
'cache-control': 'public, max-age=3600',
|
|
18
16
|
vary: 'origin'
|
|
19
17
|
})
|
|
20
18
|
|
|
21
|
-
public
|
|
22
|
-
|
|
23
|
-
}
|
|
19
|
+
public intercept (input: Input): Output {
|
|
20
|
+
const origin = input.headers.origin
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return
|
|
22
|
+
if (origin === undefined)
|
|
23
|
+
return null
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
output.headers.set('access-control-expose-headers',
|
|
32
|
-
'authorization, content-type, content-length, etag')
|
|
33
|
-
output.headers.append('vary', 'origin')
|
|
34
|
-
}
|
|
25
|
+
if (input.method === 'OPTIONS')
|
|
26
|
+
return this.preflightResponse(origin)
|
|
35
27
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
input.pipelines.response.push((output) => {
|
|
29
|
+
output.headers ??= new Headers()
|
|
30
|
+
output.headers.set('access-control-allow-origin', origin)
|
|
31
|
+
output.headers.set('access-control-expose-headers',
|
|
32
|
+
'authorization, content-type, content-length, etag')
|
|
39
33
|
|
|
40
|
-
|
|
34
|
+
if (input.method === 'GET' || input.method === 'HEAD' || input.method === 'OPTIONS')
|
|
35
|
+
output.headers.append('vary', 'origin')
|
|
36
|
+
})
|
|
41
37
|
|
|
42
|
-
return
|
|
43
|
-
status: 204,
|
|
44
|
-
headers: this.headers
|
|
45
|
-
}
|
|
38
|
+
return null
|
|
46
39
|
}
|
|
47
40
|
|
|
48
41
|
public allowHeader (header: string): void {
|
|
49
42
|
this.allowedHeaders.add(header.toLowerCase())
|
|
50
43
|
this.headers.set('access-control-allow-headers', Array.from(this.allowedHeaders).join(', '))
|
|
51
44
|
}
|
|
45
|
+
|
|
46
|
+
private preflightResponse (origin: string): Output {
|
|
47
|
+
this.headers.set('access-control-allow-origin', origin)
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
status: 204,
|
|
51
|
+
headers: this.headers
|
|
52
|
+
}
|
|
53
|
+
}
|
|
52
54
|
}
|
|
@@ -7,5 +7,5 @@ import { vary } from './vary'
|
|
|
7
7
|
import type { Family } from '../Directive'
|
|
8
8
|
import type { Interceptor } from '../Interception'
|
|
9
9
|
|
|
10
|
-
export const families: Family[] = [authorization, cache, octets,
|
|
10
|
+
export const families: Family[] = [authorization, cache, octets, vary, dev]
|
|
11
11
|
export const interceptors: Interceptor[] = [cors]
|
package/source/manifest.test.ts
CHANGED
|
@@ -21,16 +21,8 @@ it('should create branch', async () => {
|
|
|
21
21
|
const node = manifest(declaration, mf)
|
|
22
22
|
|
|
23
23
|
expect(node).toBeDefined()
|
|
24
|
-
|
|
25
|
-
// namespace route
|
|
26
24
|
expect(node.routes).toHaveLength(1)
|
|
27
|
-
expect(node.routes[0].path).toBe('/' + namespace)
|
|
28
|
-
|
|
29
|
-
const ns = node.routes[0].node
|
|
30
|
-
|
|
31
|
-
// component route
|
|
32
|
-
expect(ns.routes).toHaveLength(1)
|
|
33
|
-
expect(ns.routes[0].path).toBe('/' + name)
|
|
25
|
+
expect(node.routes[0].path).toBe('/' + namespace + '/' + name)
|
|
34
26
|
})
|
|
35
27
|
|
|
36
28
|
it('should not create node for default namespace', async () => {
|
|
@@ -43,16 +35,16 @@ it('should not create node for default namespace', async () => {
|
|
|
43
35
|
})
|
|
44
36
|
|
|
45
37
|
it('should throw on invalid declaration type', async () => {
|
|
46
|
-
expect(() => manifest('hello' as unknown as object, mf))
|
|
38
|
+
expect(() => manifest('hello' as unknown as object, mf))
|
|
39
|
+
.toThrow('Exposition declaration must be an object')
|
|
47
40
|
})
|
|
48
41
|
|
|
49
42
|
it('should set namespace and component', async () => {
|
|
50
43
|
const node = manifest(declaration, mf)
|
|
51
44
|
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
const GET = root.methods[0]
|
|
45
|
+
const root = node.routes[0].node
|
|
46
|
+
const intemediate = root.routes[0].node
|
|
47
|
+
const GET = intemediate.methods[0]
|
|
56
48
|
|
|
57
49
|
expect(GET.mapping?.namespace).toBe(namespace)
|
|
58
50
|
expect(GET.mapping?.component).toBe(name)
|
package/source/manifest.ts
CHANGED
|
@@ -1,25 +1,28 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
1
2
|
import { parse, type Node, type Method, type Query } from './RTD/syntax'
|
|
2
3
|
import { shortcuts } from './Directive'
|
|
3
4
|
import * as schemas from './schemas'
|
|
4
5
|
import type { Manifest } from '@toa.io/norm'
|
|
5
6
|
|
|
6
7
|
export function manifest (declaration: object, manifest: Manifest): Node {
|
|
7
|
-
declaration
|
|
8
|
+
assert.ok(typeof declaration === 'object' && declaration !== null,
|
|
9
|
+
'Exposition declaration must be an object')
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
declaration = wrap(manifest.namespace, declaration)
|
|
11
|
+
declaration = wrap(declaration, manifest.namespace, manifest.name)
|
|
11
12
|
|
|
12
13
|
const node = parse(declaration, shortcuts)
|
|
13
14
|
|
|
14
15
|
concretize(node, manifest)
|
|
15
|
-
|
|
16
16
|
schemas.node.validate(node)
|
|
17
17
|
|
|
18
18
|
return node
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
function wrap (
|
|
22
|
-
|
|
21
|
+
function wrap (declaration: object, namespace: string, name: string): object {
|
|
22
|
+
const path = (namespace === undefined || namespace === 'default' ? '' : '/' + namespace) +
|
|
23
|
+
'/' + name
|
|
24
|
+
|
|
25
|
+
return { [path]: { protected: true, ...declaration } }
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
function concretize (node: Node, manifest: Manifest): void {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Connector } from '@toa.io/core';
|
|
2
2
|
import { type IncomingMessage, type OutgoingMessage } from './messages';
|
|
3
3
|
export declare class Server extends Connector {
|
|
4
|
+
private server?;
|
|
4
5
|
private readonly app;
|
|
5
6
|
private readonly debug;
|
|
6
7
|
private readonly requestedPort;
|
|
7
|
-
private server?;
|
|
8
8
|
private constructor();
|
|
9
9
|
get port(): number;
|
|
10
10
|
static create(options?: Partial<Properties>): Server;
|
|
@@ -14,15 +14,15 @@ const messages_1 = require("./messages");
|
|
|
14
14
|
const exceptions_1 = require("./exceptions");
|
|
15
15
|
const formats_1 = require("./formats");
|
|
16
16
|
class Server extends core_1.Connector {
|
|
17
|
+
server;
|
|
17
18
|
app;
|
|
18
19
|
debug;
|
|
19
20
|
requestedPort;
|
|
20
|
-
|
|
21
|
-
constructor(app, debug, requestedPort) {
|
|
21
|
+
constructor(app, debug, port) {
|
|
22
22
|
super();
|
|
23
23
|
this.app = app;
|
|
24
24
|
this.debug = debug;
|
|
25
|
-
this.requestedPort =
|
|
25
|
+
this.requestedPort = port;
|
|
26
26
|
}
|
|
27
27
|
get port() {
|
|
28
28
|
if (this.server === undefined)
|
|
@@ -38,7 +38,6 @@ class Server extends core_1.Connector {
|
|
|
38
38
|
: { ...DEFAULTS, ...options };
|
|
39
39
|
const app = (0, express_1.default)();
|
|
40
40
|
app.disable('x-powered-by');
|
|
41
|
-
// app.use(cors(CORS))
|
|
42
41
|
app.use(supportedMethods(properties.methods));
|
|
43
42
|
return new Server(app, properties.debug, properties.port);
|
|
44
43
|
}
|
|
@@ -77,8 +76,6 @@ class Server extends core_1.Connector {
|
|
|
77
76
|
}
|
|
78
77
|
success(request, response) {
|
|
79
78
|
return (message) => {
|
|
80
|
-
for (const transform of request.pipelines.response)
|
|
81
|
-
transform(message);
|
|
82
79
|
let status = message.status;
|
|
83
80
|
if (status === undefined)
|
|
84
81
|
if (message.body === null)
|
|
@@ -90,11 +87,7 @@ class Server extends core_1.Connector {
|
|
|
90
87
|
else
|
|
91
88
|
status = 200;
|
|
92
89
|
response.status(status);
|
|
93
|
-
|
|
94
|
-
if (message.body !== undefined && message.body !== null)
|
|
95
|
-
(0, messages_1.write)(request, response, message);
|
|
96
|
-
else
|
|
97
|
-
response.end();
|
|
90
|
+
(0, messages_1.write)(request, response, message);
|
|
98
91
|
};
|
|
99
92
|
}
|
|
100
93
|
fail(request, response) {
|
|
@@ -105,15 +98,13 @@ class Server extends core_1.Connector {
|
|
|
105
98
|
? exception.status
|
|
106
99
|
: 500;
|
|
107
100
|
response.status(status);
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
101
|
+
const message = {};
|
|
102
|
+
const verbose = exception instanceof exceptions_1.ClientError || this.debug;
|
|
103
|
+
if (verbose)
|
|
104
|
+
message.body = exception instanceof exceptions_1.Exception
|
|
111
105
|
? exception.body
|
|
112
106
|
: (exception.stack ?? exception.message);
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
else
|
|
116
|
-
response.end();
|
|
107
|
+
(0, messages_1.write)(request, response, message);
|
|
117
108
|
};
|
|
118
109
|
}
|
|
119
110
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Server.js","sourceRoot":"","sources":["../../source/HTTP/Server.ts"],"names":[],"mappings":";;;;;;AAAA,sDAAwB;AACxB,sDAAwB;AACxB,sDAA6B;AAC7B,uCAAwC;AACxC,6CAAwC;AACxC,4DAAmC;AACnC,yCAAoF;AACpF,6CAAqD;AACrD,uCAA0C;AAK1C,MAAa,MAAO,SAAQ,gBAAS;
|
|
1
|
+
{"version":3,"file":"Server.js","sourceRoot":"","sources":["../../source/HTTP/Server.ts"],"names":[],"mappings":"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"}
|