@toa.io/extensions.exposition 1.0.0-alpha.2 → 1.0.0-alpha.4
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.bans/manifest.toa.yaml +1 -0
- package/components/identity.basic/manifest.toa.yaml +1 -0
- package/components/identity.basic/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.federation/manifest.toa.yaml +13 -0
- package/components/identity.federation/operations/authenticate.js +4 -4
- package/components/identity.federation/operations/authenticate.js.map +1 -1
- package/components/identity.federation/operations/create.js +4 -4
- package/components/identity.federation/operations/create.js.map +1 -1
- package/components/identity.federation/operations/{assertions-as-values.cjs → lib/assertions-as-values.js} +1 -1
- package/components/identity.federation/operations/lib/assertions-as-values.js.map +1 -0
- package/components/identity.federation/operations/{jwt.d.cts → lib/jwt.d.ts} +5 -4
- package/components/identity.federation/operations/{jwt.cjs → lib/jwt.js} +35 -11
- package/components/identity.federation/operations/lib/jwt.js.map +1 -0
- package/components/identity.federation/operations/schemas.d.ts +16 -0
- package/components/identity.federation/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.federation/operations/types.d.ts +1 -1
- package/components/identity.federation/source/authenticate.ts +2 -2
- package/components/identity.federation/source/create.ts +2 -2
- package/components/identity.federation/source/{assertions-as-values.cts → lib/assertions-as-values.ts} +1 -2
- package/components/identity.federation/source/lib/jwt.test.ts +56 -0
- package/components/identity.federation/source/{jwt.cts → lib/jwt.ts} +57 -29
- package/components/identity.federation/source/schemas.ts +16 -0
- package/components/identity.federation/source/types.ts +1 -1
- package/components/identity.roles/manifest.toa.yaml +1 -0
- package/components/identity.roles/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.tokens/manifest.toa.yaml +1 -0
- package/components/identity.tokens/operations/tsconfig.tsbuildinfo +1 -1
- package/components/octets.storage/manifest.toa.yaml +1 -0
- package/components/octets.storage/operations/store.js +1 -1
- package/documentation/components.md +12 -5
- package/documentation/identity.md +7 -0
- package/documentation/octets.md +12 -0
- package/documentation/query.md +45 -2
- package/features/body.feature +1 -1
- package/features/errors.feature +1 -1
- package/features/etag.feature +86 -0
- package/features/identity.federation.feature +31 -1
- package/features/octets.entries.feature +1 -1
- package/features/octets.workflows.feature +38 -0
- package/features/steps/Gateway.ts +3 -0
- package/features/steps/IdP.ts +29 -0
- package/features/steps/components/echo/manifest.toa.yaml +1 -0
- package/features/steps/components/greeter/manifest.toa.yaml +1 -0
- package/features/steps/components/octets.tester/manifest.toa.yaml +1 -0
- package/features/steps/components/pots/manifest.toa.yaml +10 -3
- package/features/steps/components/sequences/manifest.toa.yaml +1 -0
- package/features/timing.feature +43 -0
- package/package.json +7 -10
- package/readme.md +7 -6
- package/schemas/annotation.cos.yaml +1 -0
- package/schemas/octets/workflow.cos.yaml +12 -0
- package/schemas/querystring.cos.yaml +1 -0
- package/source/Annotation.ts +1 -0
- package/source/Directive.test.ts +3 -3
- package/source/Directive.ts +11 -11
- package/source/Endpoint.ts +18 -4
- package/source/Factory.ts +8 -4
- package/source/Gateway.ts +55 -42
- package/source/HTTP/Context.ts +67 -0
- package/source/HTTP/Server.test.ts +1 -1
- package/source/HTTP/Server.ts +60 -95
- package/source/HTTP/Timing.ts +40 -0
- package/source/HTTP/index.ts +1 -0
- package/source/HTTP/messages.test.ts +27 -8
- package/source/HTTP/messages.ts +32 -48
- package/source/Mapping.ts +7 -8
- package/source/deployment.ts +6 -0
- package/source/directives/auth/Anonymous.ts +3 -2
- package/source/directives/auth/Authorization.ts +5 -3
- package/source/directives/auth/Incept.ts +11 -6
- package/source/directives/auth/Role.ts +5 -3
- package/source/directives/auth/Scheme.ts +2 -2
- package/source/directives/cache/Cache.ts +2 -2
- package/source/directives/cache/Control.ts +5 -5
- package/source/directives/cache/types.ts +1 -1
- package/source/directives/cors/CORS.ts +5 -3
- package/source/directives/octets/Context.ts +1 -1
- package/source/directives/octets/Delete.ts +21 -11
- package/source/directives/octets/Fetch.ts +29 -14
- package/source/directives/octets/List.ts +14 -6
- package/source/directives/octets/Octets.ts +7 -3
- package/source/directives/octets/Permute.ts +12 -6
- package/source/directives/octets/Store.ts +32 -16
- package/source/directives/octets/Workflow.ts +41 -0
- package/source/directives/octets/schemas.test.ts +21 -0
- package/source/directives/octets/schemas.ts +2 -0
- package/source/directives/octets/{workflow → workflows}/Execution.ts +0 -2
- package/source/directives/octets/{workflow → workflows}/Workflow.ts +2 -2
- package/source/directives/vary/Vary.ts +1 -1
- package/source/directives/vary/embeddings/Header.ts +1 -1
- package/source/directives/vary/embeddings/Language.ts +1 -1
- package/source/io.ts +2 -2
- package/transpiled/Annotation.d.ts +1 -0
- package/transpiled/Directive.d.ts +6 -6
- package/transpiled/Directive.js +8 -8
- package/transpiled/Directive.js.map +1 -1
- package/transpiled/Endpoint.d.ts +3 -3
- package/transpiled/Endpoint.js +34 -1
- package/transpiled/Endpoint.js.map +1 -1
- package/transpiled/Factory.js +4 -2
- package/transpiled/Factory.js.map +1 -1
- package/transpiled/Gateway.d.ts +5 -6
- package/transpiled/Gateway.js +38 -32
- package/transpiled/Gateway.js.map +1 -1
- package/transpiled/HTTP/Context.d.ts +24 -0
- package/transpiled/HTTP/Context.js +47 -0
- package/transpiled/HTTP/Context.js.map +1 -0
- package/transpiled/HTTP/Server.d.ts +8 -7
- package/transpiled/HTTP/Server.js +68 -76
- package/transpiled/HTTP/Server.js.map +1 -1
- package/transpiled/HTTP/Timing.d.ts +10 -0
- package/transpiled/HTTP/Timing.js +29 -0
- package/transpiled/HTTP/Timing.js.map +1 -0
- package/transpiled/HTTP/index.d.ts +1 -0
- package/transpiled/HTTP/index.js +1 -0
- package/transpiled/HTTP/index.js.map +1 -1
- package/transpiled/HTTP/messages.d.ts +7 -21
- package/transpiled/HTTP/messages.js +24 -26
- package/transpiled/HTTP/messages.js.map +1 -1
- package/transpiled/Mapping.js +7 -7
- package/transpiled/Mapping.js.map +1 -1
- package/transpiled/deployment.js +5 -0
- package/transpiled/deployment.js.map +1 -1
- package/transpiled/directives/auth/Anonymous.js +3 -4
- package/transpiled/directives/auth/Anonymous.js.map +1 -1
- package/transpiled/directives/auth/Authorization.js +1 -1
- package/transpiled/directives/auth/Authorization.js.map +1 -1
- package/transpiled/directives/auth/Incept.d.ts +1 -1
- package/transpiled/directives/auth/Incept.js +11 -6
- package/transpiled/directives/auth/Incept.js.map +1 -1
- package/transpiled/directives/auth/Role.js +5 -3
- package/transpiled/directives/auth/Role.js.map +1 -1
- package/transpiled/directives/auth/Scheme.js +2 -2
- package/transpiled/directives/auth/Scheme.js.map +1 -1
- package/transpiled/directives/cache/Cache.d.ts +1 -1
- package/transpiled/directives/cache/Cache.js +2 -2
- package/transpiled/directives/cache/Cache.js.map +1 -1
- package/transpiled/directives/cache/Control.d.ts +3 -3
- package/transpiled/directives/cache/Control.js +3 -3
- package/transpiled/directives/cache/Control.js.map +1 -1
- package/transpiled/directives/cache/types.d.ts +1 -1
- package/transpiled/directives/cors/CORS.js +4 -3
- package/transpiled/directives/cors/CORS.js.map +1 -1
- package/transpiled/directives/octets/Context.d.ts +1 -1
- package/transpiled/directives/octets/Context.js.map +1 -1
- package/transpiled/directives/octets/Delete.d.ts +2 -2
- package/transpiled/directives/octets/Delete.js +21 -11
- package/transpiled/directives/octets/Delete.js.map +1 -1
- package/transpiled/directives/octets/Fetch.d.ts +1 -1
- package/transpiled/directives/octets/Fetch.js +28 -14
- package/transpiled/directives/octets/Fetch.js.map +1 -1
- package/transpiled/directives/octets/List.d.ts +1 -1
- package/transpiled/directives/octets/List.js +13 -6
- package/transpiled/directives/octets/List.js.map +1 -1
- package/transpiled/directives/octets/Octets.js +7 -3
- package/transpiled/directives/octets/Octets.js.map +1 -1
- package/transpiled/directives/octets/Permute.d.ts +1 -1
- package/transpiled/directives/octets/Permute.js +11 -6
- package/transpiled/directives/octets/Permute.js.map +1 -1
- package/transpiled/directives/octets/Store.d.ts +3 -2
- package/transpiled/directives/octets/Store.js +19 -11
- package/transpiled/directives/octets/Store.js.map +1 -1
- package/transpiled/directives/octets/Workflow.d.ts +14 -0
- package/transpiled/directives/octets/Workflow.js +52 -0
- package/transpiled/directives/octets/Workflow.js.map +1 -0
- package/transpiled/directives/octets/schemas.d.ts +2 -0
- package/transpiled/directives/octets/schemas.js +2 -1
- package/transpiled/directives/octets/schemas.js.map +1 -1
- package/transpiled/directives/octets/{workflow → workflows}/Execution.js +0 -1
- package/transpiled/directives/octets/workflows/Execution.js.map +1 -0
- package/transpiled/directives/octets/{workflow → workflows}/Workflow.d.ts +1 -1
- package/transpiled/directives/octets/{workflow → workflows}/Workflow.js +2 -2
- package/transpiled/directives/octets/workflows/Workflow.js.map +1 -0
- package/transpiled/directives/octets/workflows/index.js.map +1 -0
- package/transpiled/directives/vary/Vary.js +1 -1
- 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/io.d.ts +2 -2
- package/transpiled/tsconfig.tsbuildinfo +1 -1
- package/components/identity.federation/operations/assertions-as-values.cjs.map +0 -1
- package/components/identity.federation/operations/jwt.cjs.map +0 -1
- package/source/HTTP/Server.fixtures.ts +0 -40
- package/transpiled/HTTP/Server.fixtures.d.ts +0 -10
- package/transpiled/HTTP/Server.fixtures.js +0 -31
- package/transpiled/HTTP/Server.fixtures.js.map +0 -1
- package/transpiled/directives/octets/workflow/Execution.js.map +0 -1
- package/transpiled/directives/octets/workflow/Workflow.js.map +0 -1
- package/transpiled/directives/octets/workflow/index.js.map +0 -1
- /package/components/identity.federation/operations/{assertions-as-values.d.cts → lib/assertions-as-values.d.ts} +0 -0
- /package/source/directives/octets/{workflow → workflows}/index.ts +0 -0
- /package/transpiled/directives/octets/{workflow → workflows}/Execution.d.ts +0 -0
- /package/transpiled/directives/octets/{workflow → workflows}/index.d.ts +0 -0
- /package/transpiled/directives/octets/{workflow → workflows}/index.js +0 -0
|
@@ -246,3 +246,41 @@ Feature: Octets storage workflows
|
|
|
246
246
|
concat: hello world
|
|
247
247
|
--cut--
|
|
248
248
|
"""
|
|
249
|
+
|
|
250
|
+
Scenario: Executing a workflow with `octets:workflow`
|
|
251
|
+
Given the `octets.tester` is running
|
|
252
|
+
And the annotation:
|
|
253
|
+
"""yaml
|
|
254
|
+
/:
|
|
255
|
+
auth:anonymous: true
|
|
256
|
+
octets:context: octets
|
|
257
|
+
POST:
|
|
258
|
+
octets:store: ~
|
|
259
|
+
/*:
|
|
260
|
+
DELETE:
|
|
261
|
+
octets:workflow:
|
|
262
|
+
echo: octets.tester.echo
|
|
263
|
+
"""
|
|
264
|
+
When the stream of `lenna.ascii` is received with the following headers:
|
|
265
|
+
"""
|
|
266
|
+
POST / HTTP/1.1
|
|
267
|
+
content-type: application/octet-stream
|
|
268
|
+
"""
|
|
269
|
+
Then the following reply is sent:
|
|
270
|
+
"""
|
|
271
|
+
201 Created
|
|
272
|
+
"""
|
|
273
|
+
When the following request is received:
|
|
274
|
+
"""
|
|
275
|
+
DELETE /10cf16b458f759e0d617f2f3d83599ff HTTP/1.1
|
|
276
|
+
accept: application/yaml
|
|
277
|
+
"""
|
|
278
|
+
Then the following reply is sent:
|
|
279
|
+
"""
|
|
280
|
+
202 Accepted
|
|
281
|
+
content-type: multipart/yaml; boundary=cut
|
|
282
|
+
|
|
283
|
+
--cut
|
|
284
|
+
echo: 10cf16b458f759e0d617f2f3d83599ff
|
|
285
|
+
--cut--
|
|
286
|
+
"""
|
package/features/steps/IdP.ts
CHANGED
|
@@ -117,4 +117,33 @@ export class IdP {
|
|
|
117
117
|
|
|
118
118
|
this.captures.set(`${user}.id_token`, idToken)
|
|
119
119
|
}
|
|
120
|
+
|
|
121
|
+
@given('the IDP {word} token for {word} is issued with following secret:')
|
|
122
|
+
public async issueSymmetricToken (alg: string, user: string, secret: string): Promise<void> {
|
|
123
|
+
console.log('Sym token for %s with secret "%s"', user, secret)
|
|
124
|
+
|
|
125
|
+
const jwt = [
|
|
126
|
+
{
|
|
127
|
+
typ: 'JWT',
|
|
128
|
+
alg
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
iss: IdP.issuer,
|
|
132
|
+
sub: `${user}-mock-id`,
|
|
133
|
+
aud: 'test',
|
|
134
|
+
iat: Math.floor(Date.now() / 1000),
|
|
135
|
+
exp: Math.floor((Date.now() + 1000 * 60 * 5) / 1000)
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
.map((v) => Buffer.from(JSON.stringify(v)).toString('base64url'))
|
|
139
|
+
.join('.')
|
|
140
|
+
|
|
141
|
+
const signature = crypto.createHmac(alg.replace(/^HS(\d{3})$/, 'sha$1'), secret)
|
|
142
|
+
.update(jwt)
|
|
143
|
+
.digest('base64url')
|
|
144
|
+
|
|
145
|
+
const idToken = `${jwt}.${signature}`
|
|
146
|
+
|
|
147
|
+
this.captures.set(`${user}.id_token`, idToken)
|
|
148
|
+
}
|
|
120
149
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
name: pots
|
|
2
|
+
version: 0.0.0
|
|
2
3
|
|
|
3
4
|
entity:
|
|
4
5
|
schema:
|
|
@@ -7,11 +8,17 @@ entity:
|
|
|
7
8
|
temperature?: number
|
|
8
9
|
|
|
9
10
|
operations:
|
|
10
|
-
|
|
11
|
+
create:
|
|
11
12
|
query: false
|
|
13
|
+
forward: transit
|
|
14
|
+
input:
|
|
15
|
+
title*: .
|
|
16
|
+
volume*: .
|
|
17
|
+
transit:
|
|
18
|
+
concurrency: retry
|
|
12
19
|
input:
|
|
13
|
-
title
|
|
14
|
-
volume
|
|
20
|
+
title: .
|
|
21
|
+
volume: .
|
|
15
22
|
observe:
|
|
16
23
|
output:
|
|
17
24
|
id: string
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Feature: Server timing
|
|
2
|
+
|
|
3
|
+
Background:
|
|
4
|
+
Given the `pots` is running with the following manifest:
|
|
5
|
+
"""yaml
|
|
6
|
+
exposition:
|
|
7
|
+
/:
|
|
8
|
+
POST: create
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
Scenario: Server timing is not available by default
|
|
12
|
+
When the following request is received:
|
|
13
|
+
"""
|
|
14
|
+
POST /pots/ HTTP/1.1
|
|
15
|
+
content-type: application/yaml
|
|
16
|
+
|
|
17
|
+
title: Hello
|
|
18
|
+
volume: 1.5
|
|
19
|
+
"""
|
|
20
|
+
Then the reply does not contain:
|
|
21
|
+
"""
|
|
22
|
+
server-timing:
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
Scenario: Server timing is sent when debug is enabled
|
|
26
|
+
Given the annotation:
|
|
27
|
+
"""
|
|
28
|
+
trace: true
|
|
29
|
+
"""
|
|
30
|
+
When the following request is received:
|
|
31
|
+
"""
|
|
32
|
+
POST /pots/ HTTP/1.1
|
|
33
|
+
content-type: application/yaml
|
|
34
|
+
|
|
35
|
+
title: Hello
|
|
36
|
+
volume: 1.5
|
|
37
|
+
"""
|
|
38
|
+
# to debug, break it and look at the console
|
|
39
|
+
Then the following reply is sent:
|
|
40
|
+
"""
|
|
41
|
+
201 Created
|
|
42
|
+
server-timing:
|
|
43
|
+
"""
|
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.4",
|
|
4
4
|
"description": "Toa Exposition",
|
|
5
5
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/toa-io/toa#readme",
|
|
@@ -17,13 +17,11 @@
|
|
|
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.
|
|
23
|
-
"@toa.io/streams": "1.0.0-alpha.2",
|
|
20
|
+
"@toa.io/core": "1.0.0-alpha.4",
|
|
21
|
+
"@toa.io/generic": "1.0.0-alpha.4",
|
|
22
|
+
"@toa.io/schemas": "1.0.0-alpha.4",
|
|
24
23
|
"bcryptjs": "2.4.3",
|
|
25
24
|
"error-value": "0.3.0",
|
|
26
|
-
"express": "4.18.2",
|
|
27
25
|
"js-yaml": "4.1.0",
|
|
28
26
|
"matchacho": "0.3.5",
|
|
29
27
|
"msgpackr": "1.10.1",
|
|
@@ -45,12 +43,11 @@
|
|
|
45
43
|
"features": "cucumber-js"
|
|
46
44
|
},
|
|
47
45
|
"devDependencies": {
|
|
48
|
-
"@toa.io/agent": "1.0.0-alpha.
|
|
49
|
-
"@toa.io/extensions.storages": "1.0.0-alpha.
|
|
46
|
+
"@toa.io/agent": "1.0.0-alpha.4",
|
|
47
|
+
"@toa.io/extensions.storages": "1.0.0-alpha.4",
|
|
50
48
|
"@types/bcryptjs": "2.4.3",
|
|
51
49
|
"@types/cors": "2.8.13",
|
|
52
|
-
"@types/express": "4.17.17",
|
|
53
50
|
"@types/negotiator": "0.6.1"
|
|
54
51
|
},
|
|
55
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "80a91c0d9c167484247a91e69a0c0a3c344f90d0"
|
|
56
53
|
}
|
package/readme.md
CHANGED
|
@@ -127,12 +127,13 @@ exposition:
|
|
|
127
127
|
host: the.example.com
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
-
| Option | Type | Description
|
|
131
|
-
|
|
132
|
-
| `host` | `string` | Domain name to be used for the corresponding Kubernetes Ingress.
|
|
133
|
-
| `class` | `string` | Ingress class
|
|
134
|
-
| `annotations` | `object` | Ingress annotations
|
|
135
|
-
| `debug` | `boolean` | Output server errors. Default `false`.
|
|
130
|
+
| Option | Type | Description |
|
|
131
|
+
|---------------|-----------|-------------------------------------------------------------------------------------------------------------------|
|
|
132
|
+
| `host` | `string` | Domain name to be used for the corresponding Kubernetes Ingress. |
|
|
133
|
+
| `class` | `string` | Ingress class |
|
|
134
|
+
| `annotations` | `object` | Ingress annotations |
|
|
135
|
+
| `debug` | `boolean` | Output server errors. Default `false`. |
|
|
136
|
+
| `trace` | `boolean` | Output [server timing](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing). Default `false`. |
|
|
136
137
|
|
|
137
138
|
### Context resources
|
|
138
139
|
|
package/source/Annotation.ts
CHANGED
package/source/Directive.test.ts
CHANGED
|
@@ -2,8 +2,8 @@ import assert from 'node:assert'
|
|
|
2
2
|
import { generate } from 'randomstring'
|
|
3
3
|
import { DirectivesFactory, type Family } from './Directive'
|
|
4
4
|
import { type syntax } from './RTD'
|
|
5
|
-
import { type IncomingMessage } from './HTTP'
|
|
6
5
|
import { type Remotes } from './Remotes'
|
|
6
|
+
import type { Context } from './HTTP'
|
|
7
7
|
|
|
8
8
|
const families: Array<jest.MockedObjectDeep<Family>> = [
|
|
9
9
|
{
|
|
@@ -76,7 +76,7 @@ it('should apply directive', async () => {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
const directives = factory.create([declaration])
|
|
79
|
-
const request = generate() as unknown as
|
|
79
|
+
const request = generate() as unknown as Context
|
|
80
80
|
const directive = families[0].create.mock.results[0].value
|
|
81
81
|
|
|
82
82
|
await directives.preflight(request, [])
|
|
@@ -89,7 +89,7 @@ it('should apply directive', async () => {
|
|
|
89
89
|
|
|
90
90
|
it('should apply mandatory families', async () => {
|
|
91
91
|
const directives = factory.create([])
|
|
92
|
-
const request = generate() as unknown as
|
|
92
|
+
const request = generate() as unknown as Context
|
|
93
93
|
|
|
94
94
|
await directives.preflight(request, [])
|
|
95
95
|
|
package/source/Directive.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Context, OutgoingMessage } from './HTTP'
|
|
2
2
|
import type { Remotes } from './Remotes'
|
|
3
3
|
import type { Output } from './io'
|
|
4
4
|
import type * as RTD from './RTD'
|
|
@@ -10,15 +10,15 @@ export class Directives implements RTD.Directives<Directives> {
|
|
|
10
10
|
this.sets = sets
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
public async preflight (
|
|
13
|
+
public async preflight (context: Context, parameters: RTD.Parameter[]): Promise<Output> {
|
|
14
14
|
for (const set of this.sets) {
|
|
15
15
|
if (set.family.preflight === undefined)
|
|
16
16
|
continue
|
|
17
17
|
|
|
18
|
-
const output = await set.family.preflight(set.directives,
|
|
18
|
+
const output = await set.family.preflight(set.directives, context, parameters)
|
|
19
19
|
|
|
20
20
|
if (output !== null) {
|
|
21
|
-
await this.settle(
|
|
21
|
+
await this.settle(context, output)
|
|
22
22
|
|
|
23
23
|
return output
|
|
24
24
|
}
|
|
@@ -27,10 +27,10 @@ export class Directives implements RTD.Directives<Directives> {
|
|
|
27
27
|
return null
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
public async settle (
|
|
30
|
+
public async settle (context: Context, response: OutgoingMessage): Promise<void> {
|
|
31
31
|
for (const set of this.sets)
|
|
32
32
|
if (set.family.settle !== undefined)
|
|
33
|
-
await set.family.settle(set.directives,
|
|
33
|
+
await set.family.settle(set.directives, context, response)
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
public merge (directives: Directives): void {
|
|
@@ -39,7 +39,7 @@ export class Directives implements RTD.Directives<Directives> {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export class DirectivesFactory implements RTD.DirectivesFactory<Directives> {
|
|
42
|
-
private readonly
|
|
42
|
+
private readonly remotes: Remotes
|
|
43
43
|
private readonly families: Record<string, Family> = {}
|
|
44
44
|
private readonly mandatory: string[] = []
|
|
45
45
|
|
|
@@ -51,7 +51,7 @@ export class DirectivesFactory implements RTD.DirectivesFactory<Directives> {
|
|
|
51
51
|
this.mandatory.push(family.name)
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
this.
|
|
54
|
+
this.remotes = remotes
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
public create (declarations: RTD.syntax.Directive[]): Directives {
|
|
@@ -67,7 +67,7 @@ export class DirectivesFactory implements RTD.DirectivesFactory<Directives> {
|
|
|
67
67
|
if (family === undefined)
|
|
68
68
|
throw new Error(`Directive family '${declaration.family}' is not found.`)
|
|
69
69
|
|
|
70
|
-
const directive = family.create(declaration.name, declaration.value, this.
|
|
70
|
+
const directive = family.create(declaration.name, declaration.value, this.remotes)
|
|
71
71
|
|
|
72
72
|
groups[family.name] ??= []
|
|
73
73
|
groups[family.name].push(directive)
|
|
@@ -109,11 +109,11 @@ export interface Family<TDirective = any, TExtension = any> {
|
|
|
109
109
|
create: (name: string, value: any, remotes: Remotes) => TDirective
|
|
110
110
|
|
|
111
111
|
preflight?: (directives: TDirective[],
|
|
112
|
-
request:
|
|
112
|
+
request: Context & TExtension,
|
|
113
113
|
parameters: RTD.Parameter[]) => Output | Promise<Output>
|
|
114
114
|
|
|
115
115
|
settle?: (directives: TDirective[],
|
|
116
|
-
request:
|
|
116
|
+
request: Context & TExtension,
|
|
117
117
|
response: OutgoingMessage) => void | Promise<void>
|
|
118
118
|
}
|
|
119
119
|
|
package/source/Endpoint.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { type Component
|
|
1
|
+
import { type Component } from '@toa.io/core'
|
|
2
2
|
import { type Remotes } from './Remotes'
|
|
3
3
|
import { Mapping } from './Mapping'
|
|
4
4
|
import { type Context } from './Context'
|
|
5
|
+
import * as http from './HTTP'
|
|
5
6
|
import type * as RTD from './RTD'
|
|
6
|
-
import type * as http from './HTTP'
|
|
7
7
|
|
|
8
8
|
export class Endpoint implements RTD.Endpoint<Endpoint> {
|
|
9
9
|
private readonly endpoint: string
|
|
@@ -17,12 +17,26 @@ export class Endpoint implements RTD.Endpoint<Endpoint> {
|
|
|
17
17
|
this.discovery = discovery
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
public async call
|
|
20
|
+
public async call
|
|
21
|
+
(body: any, query: http.Query, parameters: RTD.Parameter[]): Promise<http.OutgoingMessage> {
|
|
21
22
|
const request = this.mapping.fit(body, query, parameters)
|
|
22
23
|
|
|
23
24
|
this.remote ??= await this.discovery
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
const reply = await this.remote.invoke(this.endpoint, request)
|
|
27
|
+
|
|
28
|
+
if (reply instanceof Error)
|
|
29
|
+
throw new http.Conflict(reply)
|
|
30
|
+
|
|
31
|
+
const message: http.OutgoingMessage = { body: reply }
|
|
32
|
+
|
|
33
|
+
if (typeof reply === 'object' && reply !== null && '_version' in reply) {
|
|
34
|
+
message.headers ??= new Headers()
|
|
35
|
+
message.headers.set('etag', `"${reply._version.toString()}"`)
|
|
36
|
+
delete reply._version
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return message
|
|
26
40
|
}
|
|
27
41
|
|
|
28
42
|
public async close (): Promise<void> {
|
package/source/Factory.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { type Directives, DirectivesFactory } from './Directive'
|
|
|
9
9
|
import { Composition } from './Composition'
|
|
10
10
|
import * as root from './root'
|
|
11
11
|
import { Interception } from './Interception'
|
|
12
|
+
import type { Broadcast } from './Gateway'
|
|
12
13
|
import type { Connector, Locator, extensions } from '@toa.io/core'
|
|
13
14
|
|
|
14
15
|
export class Factory implements extensions.Factory {
|
|
@@ -19,15 +20,16 @@ export class Factory implements extensions.Factory {
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
public tenant (locator: Locator, node: syntax.Node): Connector {
|
|
22
|
-
const broadcast = this.boot.bindings.broadcast(CHANNEL, locator.id)
|
|
23
|
+
const broadcast: Broadcast = this.boot.bindings.broadcast(CHANNEL, locator.id)
|
|
23
24
|
|
|
24
25
|
return new Tenant(broadcast, locator, node)
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
public service (): Connector | null {
|
|
28
29
|
const debug = process.env.TOA_EXPOSITION_DEBUG === '1'
|
|
29
|
-
const
|
|
30
|
-
const
|
|
30
|
+
const trace = process.env.TOA_EXPOSITION_TRACE === '1'
|
|
31
|
+
const broadcast: Broadcast = this.boot.bindings.broadcast(CHANNEL)
|
|
32
|
+
const server = Server.create({ methods: syntax.verbs, debug, trace })
|
|
31
33
|
const remotes = new Remotes(this.boot)
|
|
32
34
|
const node = root.resolve()
|
|
33
35
|
const methods = new EndpointsFactory(remotes)
|
|
@@ -36,10 +38,12 @@ export class Factory implements extensions.Factory {
|
|
|
36
38
|
const tree = new Tree<Endpoint, Directives>(node, methods, directives)
|
|
37
39
|
|
|
38
40
|
const composition = new Composition(this.boot)
|
|
39
|
-
const gateway = new Gateway(broadcast,
|
|
41
|
+
const gateway = new Gateway(broadcast, tree, interception)
|
|
40
42
|
|
|
41
43
|
gateway.depends(remotes)
|
|
42
44
|
gateway.depends(composition)
|
|
45
|
+
|
|
46
|
+
server.attach(gateway.process.bind(gateway))
|
|
43
47
|
server.depends(gateway)
|
|
44
48
|
|
|
45
49
|
return server
|
package/source/Gateway.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { type bindings, Connector } from '@toa.io/core'
|
|
2
2
|
import * as http from './HTTP'
|
|
3
3
|
import { rethrow } from './exceptions'
|
|
4
|
-
import type {
|
|
5
|
-
import type { Maybe } from '@toa.io/types'
|
|
4
|
+
import type { Interception } from './Interception'
|
|
6
5
|
import type { Method, Parameter, Tree } from './RTD'
|
|
7
6
|
import type { Label } from './discovery'
|
|
8
7
|
import type { Branch } from './Branch'
|
|
@@ -12,84 +11,96 @@ import type { Directives } from './Directive'
|
|
|
12
11
|
export class Gateway extends Connector {
|
|
13
12
|
private readonly broadcast: Broadcast
|
|
14
13
|
private readonly tree: Tree<Endpoint, Directives>
|
|
15
|
-
private readonly interceptor:
|
|
16
|
-
private readonly server: Connector
|
|
14
|
+
private readonly interceptor: Interception
|
|
17
15
|
|
|
18
|
-
// eslint-disable-next-line max-
|
|
19
|
-
public constructor (broadcast: Broadcast,
|
|
16
|
+
// eslint-disable-next-line max-len
|
|
17
|
+
public constructor (broadcast: Broadcast, tree: Tree<Endpoint, Directives>, interception: Interception) {
|
|
20
18
|
super()
|
|
21
19
|
|
|
22
20
|
this.broadcast = broadcast
|
|
23
21
|
this.tree = tree
|
|
24
22
|
this.interceptor = interception
|
|
25
|
-
this.server = server
|
|
26
23
|
|
|
27
24
|
this.depends(broadcast)
|
|
28
|
-
// this.depends(server)
|
|
29
|
-
|
|
30
|
-
server.attach(this.process.bind(this))
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
protected override async open (): Promise<void> {
|
|
34
|
-
await this.discover()
|
|
35
|
-
|
|
36
|
-
console.info('Gateway has started and is awaiting resource branches.')
|
|
37
25
|
}
|
|
38
26
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
private async process (request: http.IncomingMessage): Promise<http.OutgoingMessage> {
|
|
44
|
-
const interception = await this.interceptor.intercept(request)
|
|
27
|
+
public async process (context: http.Context): Promise<http.OutgoingMessage> {
|
|
28
|
+
const interception = await context.timing.capture('gate:intercept',
|
|
29
|
+
this.interceptor.intercept(context))
|
|
45
30
|
|
|
46
31
|
if (interception !== null)
|
|
47
32
|
return interception
|
|
48
33
|
|
|
49
|
-
const match = this.tree.match(
|
|
34
|
+
const match = this.tree.match(context.url.pathname)
|
|
50
35
|
|
|
51
36
|
if (match === null)
|
|
52
37
|
throw new http.NotFound()
|
|
53
38
|
|
|
54
|
-
const {
|
|
39
|
+
const {
|
|
40
|
+
node,
|
|
41
|
+
parameters
|
|
42
|
+
} = match
|
|
55
43
|
|
|
56
|
-
if (!(request.method in node.methods))
|
|
44
|
+
if (!(context.request.method in node.methods))
|
|
57
45
|
throw new http.MethodNotAllowed()
|
|
58
46
|
|
|
59
|
-
const method = node.methods[request.method]
|
|
60
|
-
const interruption = await method.directives.preflight(request, parameters)
|
|
61
|
-
const response = interruption ?? await this.call(method, request, parameters)
|
|
47
|
+
const method = node.methods[context.request.method]
|
|
62
48
|
|
|
63
|
-
await
|
|
49
|
+
const interruption = await context.timing.capture('gate:preflight',
|
|
50
|
+
method.directives.preflight(context, parameters))
|
|
51
|
+
|
|
52
|
+
const response = interruption ??
|
|
53
|
+
await context.timing.capture('gate:call', this.call(method, context, parameters))
|
|
54
|
+
|
|
55
|
+
await context.timing.capture('gate:settle', method.directives.settle(context, response))
|
|
64
56
|
|
|
65
57
|
return response
|
|
66
58
|
}
|
|
67
59
|
|
|
60
|
+
protected override async open (): Promise<void> {
|
|
61
|
+
await this.discover()
|
|
62
|
+
|
|
63
|
+
console.info('Gateway has started and is awaiting resource branches.')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
protected override dispose (): void {
|
|
67
|
+
console.info('Gateway is closed.')
|
|
68
|
+
}
|
|
69
|
+
|
|
68
70
|
private async call
|
|
69
|
-
(method: Method<Endpoint, Directives>,
|
|
71
|
+
(method: Method<Endpoint, Directives>, context: http.Context, parameters: Parameter[]):
|
|
70
72
|
Promise<http.OutgoingMessage> {
|
|
71
|
-
if (
|
|
73
|
+
if (context.url.pathname[context.url.pathname.length - 1] !== '/')
|
|
72
74
|
throw new http.NotFound('Trailing slash is required.')
|
|
73
75
|
|
|
74
|
-
if (
|
|
76
|
+
if (context.encoder === null)
|
|
75
77
|
throw new http.NotAcceptable()
|
|
76
78
|
|
|
77
79
|
if (method.endpoint === null)
|
|
78
80
|
throw new http.MethodNotAllowed()
|
|
79
81
|
|
|
80
|
-
const body = await
|
|
82
|
+
const body = await context.body()
|
|
83
|
+
const query = this.query(context)
|
|
84
|
+
|
|
85
|
+
return await method.endpoint
|
|
86
|
+
.call(body, query, parameters)
|
|
87
|
+
.catch(rethrow) as http.OutgoingMessage
|
|
88
|
+
}
|
|
81
89
|
|
|
82
|
-
|
|
83
|
-
|
|
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']
|
|
84
93
|
|
|
85
|
-
|
|
86
|
-
.
|
|
87
|
-
.catch(rethrow) as Maybe<unknown>
|
|
94
|
+
if (etag !== undefined) {
|
|
95
|
+
const match = etag.match(ETAG)
|
|
88
96
|
|
|
89
|
-
|
|
90
|
-
|
|
97
|
+
if (match === null)
|
|
98
|
+
throw new http.BadRequest('Invalid ETag.')
|
|
99
|
+
else
|
|
100
|
+
query.version = parseInt(match.groups!.version)
|
|
101
|
+
}
|
|
91
102
|
|
|
92
|
-
return
|
|
103
|
+
return query
|
|
93
104
|
}
|
|
94
105
|
|
|
95
106
|
private async discover (): Promise<void> {
|
|
@@ -109,4 +120,6 @@ export class Gateway extends Connector {
|
|
|
109
120
|
}
|
|
110
121
|
}
|
|
111
122
|
|
|
112
|
-
|
|
123
|
+
const ETAG = /^"(?<version>\d{1,32})"$/
|
|
124
|
+
|
|
125
|
+
export type Broadcast = bindings.Broadcast<Label>
|