@toa.io/extensions.exposition 0.22.1 → 0.24.0-alpha.0
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/source/authenticate.ts +3 -2
- package/components/identity.basic/source/transit.ts +4 -3
- package/components/octets.storage/manifest.toa.yaml +26 -0
- package/components/octets.storage/operations/delete.js +7 -0
- package/components/octets.storage/operations/fetch.js +46 -0
- package/components/octets.storage/operations/get.js +7 -0
- package/components/octets.storage/operations/list.js +7 -0
- package/components/octets.storage/operations/permute.js +7 -0
- package/components/octets.storage/operations/store.js +11 -0
- package/cucumber.js +0 -1
- package/documentation/access.md +2 -3
- package/documentation/cache.md +42 -0
- package/documentation/octets.md +196 -0
- package/documentation/protocol.md +49 -5
- package/documentation/tree.md +1 -5
- package/features/access.feature +1 -0
- package/features/cache.feature +160 -0
- package/features/errors.feature +18 -0
- package/features/identity.basic.feature +2 -0
- package/features/octets.feature +295 -0
- package/features/octets.workflows.feature +114 -0
- package/features/routes.feature +40 -0
- package/features/steps/HTTP.ts +54 -6
- package/features/steps/Parameters.ts +5 -2
- package/features/steps/Workspace.ts +8 -5
- package/features/steps/components/octets.tester/manifest.toa.yaml +15 -0
- package/features/steps/components/octets.tester/operations/bar.js +12 -0
- package/features/steps/components/octets.tester/operations/baz.js +11 -0
- package/features/steps/components/octets.tester/operations/diversify.js +14 -0
- package/features/steps/components/octets.tester/operations/err.js +16 -0
- package/features/steps/components/octets.tester/operations/foo.js +7 -0
- package/features/steps/components/octets.tester/operations/lenna.png +0 -0
- package/features/steps/components/pots/manifest.toa.yaml +1 -1
- package/features/streams.feature +5 -1
- package/package.json +16 -9
- package/readme.md +8 -5
- package/schemas/octets/context.cos.yaml +1 -0
- package/schemas/octets/delete.cos.yaml +1 -0
- package/schemas/octets/fetch.cos.yaml +3 -0
- package/schemas/octets/list.cos.yaml +1 -0
- package/schemas/octets/permute.cos.yaml +1 -0
- package/schemas/octets/store.cos.yaml +3 -0
- package/source/Gateway.ts +9 -4
- package/source/HTTP/Server.fixtures.ts +2 -6
- package/source/HTTP/Server.test.ts +9 -31
- package/source/HTTP/Server.ts +55 -28
- package/source/HTTP/exceptions.ts +2 -12
- package/source/HTTP/formats/index.ts +7 -4
- package/source/HTTP/formats/json.ts +3 -0
- package/source/HTTP/formats/msgpack.ts +3 -0
- package/source/HTTP/formats/text.ts +3 -0
- package/source/HTTP/formats/yaml.ts +3 -0
- package/source/HTTP/messages.test.ts +3 -49
- package/source/HTTP/messages.ts +60 -35
- package/source/RTD/Route.ts +1 -1
- package/source/RTD/segment.ts +2 -1
- package/source/RTD/syntax/parse.ts +2 -1
- package/source/Remotes.ts +8 -0
- package/source/Tenant.ts +5 -0
- package/source/directives/auth/Family.ts +26 -22
- package/source/directives/auth/Rule.ts +1 -1
- package/source/directives/cache/Control.ts +59 -0
- package/source/directives/cache/Exact.ts +7 -0
- package/source/directives/cache/Family.ts +36 -0
- package/source/directives/cache/index.ts +3 -0
- package/source/directives/cache/types.ts +9 -0
- package/source/directives/index.ts +3 -1
- package/source/directives/octets/Context.ts +18 -0
- package/source/directives/octets/Delete.ts +32 -0
- package/source/directives/octets/Family.ts +68 -0
- package/source/directives/octets/Fetch.ts +85 -0
- package/source/directives/octets/List.ts +32 -0
- package/source/directives/octets/Permute.ts +37 -0
- package/source/directives/octets/Store.ts +158 -0
- package/source/directives/octets/index.ts +3 -0
- package/source/directives/octets/schemas.ts +12 -0
- package/source/directives/octets/types.ts +13 -0
- package/transpiled/Annotation.d.ts +7 -0
- package/transpiled/Annotation.js +3 -0
- package/transpiled/Annotation.js.map +1 -0
- package/transpiled/Branch.d.ts +7 -0
- package/transpiled/Branch.js +3 -0
- package/transpiled/Branch.js.map +1 -0
- package/transpiled/Composition.d.ts +14 -0
- package/transpiled/Composition.js +43 -0
- package/transpiled/Composition.js.map +1 -0
- package/transpiled/Context.d.ts +5 -0
- package/transpiled/Context.js +3 -0
- package/transpiled/Context.js.map +1 -0
- package/transpiled/Directive.d.ts +32 -0
- package/transpiled/Directive.js +76 -0
- package/transpiled/Directive.js.map +1 -0
- package/transpiled/Endpoint.d.ts +20 -0
- package/transpiled/Endpoint.js +45 -0
- package/transpiled/Endpoint.js.map +1 -0
- package/transpiled/Factory.d.ts +10 -0
- package/transpiled/Factory.js +66 -0
- package/transpiled/Factory.js.map +1 -0
- package/transpiled/Gateway.d.ts +19 -0
- package/transpiled/Gateway.js +92 -0
- package/transpiled/Gateway.js.map +1 -0
- package/transpiled/HTTP/Server.d.ts +22 -0
- package/transpiled/HTTP/Server.fixtures.d.ts +11 -0
- package/transpiled/HTTP/Server.fixtures.js +32 -0
- package/transpiled/HTTP/Server.fixtures.js.map +1 -0
- package/transpiled/HTTP/Server.js +131 -0
- package/transpiled/HTTP/Server.js.map +1 -0
- package/transpiled/HTTP/exceptions.d.ts +34 -0
- package/transpiled/HTTP/exceptions.js +71 -0
- package/transpiled/HTTP/exceptions.js.map +1 -0
- package/transpiled/HTTP/formats/index.d.ts +10 -0
- package/transpiled/HTTP/formats/index.js +38 -0
- package/transpiled/HTTP/formats/index.js.map +1 -0
- package/transpiled/HTTP/formats/json.d.ts +6 -0
- package/transpiled/HTTP/formats/json.js +17 -0
- package/transpiled/HTTP/formats/json.js.map +1 -0
- package/transpiled/HTTP/formats/msgpack.d.ts +6 -0
- package/transpiled/HTTP/formats/msgpack.js +38 -0
- package/transpiled/HTTP/formats/msgpack.js.map +1 -0
- package/transpiled/HTTP/formats/text.d.ts +6 -0
- package/transpiled/HTTP/formats/text.js +15 -0
- package/transpiled/HTTP/formats/text.js.map +1 -0
- package/transpiled/HTTP/formats/yaml.d.ts +6 -0
- package/transpiled/HTTP/formats/yaml.js +41 -0
- package/transpiled/HTTP/formats/yaml.js.map +1 -0
- package/transpiled/HTTP/index.d.ts +3 -0
- package/transpiled/HTTP/index.js +20 -0
- package/transpiled/HTTP/index.js.map +1 -0
- package/transpiled/HTTP/messages.d.ts +28 -0
- package/transpiled/HTTP/messages.js +70 -0
- package/transpiled/HTTP/messages.js.map +1 -0
- package/transpiled/Mapping.d.ts +8 -0
- package/transpiled/Mapping.js +38 -0
- package/transpiled/Mapping.js.map +1 -0
- package/transpiled/Query.d.ts +13 -0
- package/transpiled/Query.js +107 -0
- package/transpiled/Query.js.map +1 -0
- package/transpiled/RTD/Context.d.ts +11 -0
- package/transpiled/RTD/Context.js +3 -0
- package/transpiled/RTD/Context.js.map +1 -0
- package/transpiled/RTD/Directives.d.ts +7 -0
- package/transpiled/RTD/Directives.js +3 -0
- package/transpiled/RTD/Directives.js.map +1 -0
- package/transpiled/RTD/Endpoint.d.ts +9 -0
- package/transpiled/RTD/Endpoint.js +3 -0
- package/transpiled/RTD/Endpoint.js.map +1 -0
- package/transpiled/RTD/Match.d.ts +11 -0
- package/transpiled/RTD/Match.js +3 -0
- package/transpiled/RTD/Match.js.map +1 -0
- package/transpiled/RTD/Method.d.ts +9 -0
- package/transpiled/RTD/Method.js +16 -0
- package/transpiled/RTD/Method.js.map +1 -0
- package/transpiled/RTD/Node.d.ts +21 -0
- package/transpiled/RTD/Node.js +61 -0
- package/transpiled/RTD/Node.js.map +1 -0
- package/transpiled/RTD/Route.d.ts +14 -0
- package/transpiled/RTD/Route.js +49 -0
- package/transpiled/RTD/Route.js.map +1 -0
- package/transpiled/RTD/Tree.d.ts +14 -0
- package/transpiled/RTD/Tree.js +40 -0
- package/transpiled/RTD/Tree.js.map +1 -0
- package/transpiled/RTD/factory.d.ts +6 -0
- package/transpiled/RTD/factory.js +36 -0
- package/transpiled/RTD/factory.js.map +1 -0
- package/transpiled/RTD/index.d.ts +8 -0
- package/transpiled/RTD/index.js +38 -0
- package/transpiled/RTD/index.js.map +1 -0
- package/transpiled/RTD/segment.d.ts +8 -0
- package/transpiled/RTD/segment.js +25 -0
- package/transpiled/RTD/segment.js.map +1 -0
- package/transpiled/RTD/syntax/index.d.ts +2 -0
- package/transpiled/RTD/syntax/index.js +19 -0
- package/transpiled/RTD/syntax/index.js.map +1 -0
- package/transpiled/RTD/syntax/parse.d.ts +4 -0
- package/transpiled/RTD/syntax/parse.js +128 -0
- package/transpiled/RTD/syntax/parse.js.map +1 -0
- package/transpiled/RTD/syntax/types.d.ts +41 -0
- package/transpiled/RTD/syntax/types.js +5 -0
- package/transpiled/RTD/syntax/types.js.map +1 -0
- package/transpiled/Remotes.d.ts +9 -0
- package/transpiled/Remotes.js +25 -0
- package/transpiled/Remotes.js.map +1 -0
- package/transpiled/Tenant.d.ts +13 -0
- package/transpiled/Tenant.js +34 -0
- package/transpiled/Tenant.js.map +1 -0
- package/transpiled/deployment.d.ts +3 -0
- package/transpiled/deployment.js +67 -0
- package/transpiled/deployment.js.map +1 -0
- package/transpiled/directives/auth/Anonymous.d.ts +6 -0
- package/transpiled/directives/auth/Anonymous.js +17 -0
- package/transpiled/directives/auth/Anonymous.js.map +1 -0
- package/transpiled/directives/auth/Echo.d.ts +6 -0
- package/transpiled/directives/auth/Echo.js +13 -0
- package/transpiled/directives/auth/Echo.js.map +1 -0
- package/transpiled/directives/auth/Family.d.ts +20 -0
- package/transpiled/directives/auth/Family.js +118 -0
- package/transpiled/directives/auth/Family.js.map +1 -0
- package/transpiled/directives/auth/Id.d.ts +7 -0
- package/transpiled/directives/auth/Id.js +17 -0
- package/transpiled/directives/auth/Id.js.map +1 -0
- package/transpiled/directives/auth/Incept.d.ts +10 -0
- package/transpiled/directives/auth/Incept.js +58 -0
- package/transpiled/directives/auth/Incept.js.map +1 -0
- package/transpiled/directives/auth/Role.d.ts +11 -0
- package/transpiled/directives/auth/Role.js +44 -0
- package/transpiled/directives/auth/Role.js.map +1 -0
- package/transpiled/directives/auth/Rule.d.ts +9 -0
- package/transpiled/directives/auth/Rule.js +22 -0
- package/transpiled/directives/auth/Rule.js.map +1 -0
- package/transpiled/directives/auth/Scheme.d.ts +7 -0
- package/transpiled/directives/auth/Scheme.js +47 -0
- package/transpiled/directives/auth/Scheme.js.map +1 -0
- package/transpiled/directives/auth/index.d.ts +2 -0
- package/transpiled/directives/auth/index.js +7 -0
- package/transpiled/directives/auth/index.js.map +1 -0
- package/transpiled/directives/auth/schemes.d.ts +3 -0
- package/transpiled/directives/auth/schemes.js +9 -0
- package/transpiled/directives/auth/schemes.js.map +1 -0
- package/transpiled/directives/auth/split.d.ts +2 -0
- package/transpiled/directives/auth/split.js +38 -0
- package/transpiled/directives/auth/split.js.map +1 -0
- package/transpiled/directives/auth/types.d.ts +31 -0
- package/transpiled/directives/auth/types.js +3 -0
- package/transpiled/directives/auth/types.js.map +1 -0
- package/transpiled/directives/cache/Control.d.ts +9 -0
- package/transpiled/directives/cache/Control.js +42 -0
- package/transpiled/directives/cache/Control.js.map +1 -0
- package/transpiled/directives/cache/Exact.d.ts +4 -0
- package/transpiled/directives/cache/Exact.js +11 -0
- package/transpiled/directives/cache/Exact.js.map +1 -0
- package/transpiled/directives/cache/Family.d.ts +12 -0
- package/transpiled/directives/cache/Family.js +26 -0
- package/transpiled/directives/cache/Family.js.map +1 -0
- package/transpiled/directives/cache/index.d.ts +2 -0
- package/transpiled/directives/cache/index.js +7 -0
- package/transpiled/directives/cache/index.js.map +1 -0
- package/transpiled/directives/cache/types.d.ts +7 -0
- package/transpiled/directives/cache/types.js +3 -0
- package/transpiled/directives/cache/types.js.map +1 -0
- package/transpiled/directives/dev/Family.d.ts +10 -0
- package/transpiled/directives/dev/Family.js +27 -0
- package/transpiled/directives/dev/Family.js.map +1 -0
- package/transpiled/directives/dev/Stub.d.ts +7 -0
- package/transpiled/directives/dev/Stub.js +14 -0
- package/transpiled/directives/dev/Stub.js.map +1 -0
- package/transpiled/directives/dev/Throw.d.ts +7 -0
- package/transpiled/directives/dev/Throw.js +14 -0
- package/transpiled/directives/dev/Throw.js.map +1 -0
- package/transpiled/directives/dev/index.d.ts +2 -0
- package/transpiled/directives/dev/index.js +7 -0
- package/transpiled/directives/dev/index.js.map +1 -0
- package/transpiled/directives/dev/types.d.ts +4 -0
- package/transpiled/directives/dev/types.js +3 -0
- package/transpiled/directives/dev/types.js.map +1 -0
- package/transpiled/directives/index.d.ts +2 -0
- package/transpiled/directives/index.js +12 -0
- package/transpiled/directives/index.js.map +1 -0
- package/transpiled/directives/octets/Context.d.ts +8 -0
- package/transpiled/directives/octets/Context.js +40 -0
- package/transpiled/directives/octets/Context.js.map +1 -0
- package/transpiled/directives/octets/Delete.d.ts +10 -0
- package/transpiled/directives/octets/Delete.js +47 -0
- package/transpiled/directives/octets/Delete.js.map +1 -0
- package/transpiled/directives/octets/Family.d.ts +12 -0
- package/transpiled/directives/octets/Family.js +49 -0
- package/transpiled/directives/octets/Family.js.map +1 -0
- package/transpiled/directives/octets/Fetch.d.ts +18 -0
- package/transpiled/directives/octets/Fetch.js +77 -0
- package/transpiled/directives/octets/Fetch.js.map +1 -0
- package/transpiled/directives/octets/List.d.ts +10 -0
- package/transpiled/directives/octets/List.js +47 -0
- package/transpiled/directives/octets/List.js.map +1 -0
- package/transpiled/directives/octets/Permute.d.ts +10 -0
- package/transpiled/directives/octets/Permute.js +51 -0
- package/transpiled/directives/octets/Permute.js.map +1 -0
- package/transpiled/directives/octets/Store.d.ts +33 -0
- package/transpiled/directives/octets/Store.js +124 -0
- package/transpiled/directives/octets/Store.js.map +1 -0
- package/transpiled/directives/octets/index.d.ts +2 -0
- package/transpiled/directives/octets/index.js +7 -0
- package/transpiled/directives/octets/index.js.map +1 -0
- package/transpiled/directives/octets/schemas.d.ts +6 -0
- package/transpiled/directives/octets/schemas.js +17 -0
- package/transpiled/directives/octets/schemas.js.map +1 -0
- package/transpiled/directives/octets/types.d.ts +9 -0
- package/transpiled/directives/octets/types.js +3 -0
- package/transpiled/directives/octets/types.js.map +1 -0
- package/transpiled/discovery.d.ts +1 -0
- package/transpiled/discovery.js +3 -0
- package/transpiled/discovery.js.map +1 -0
- package/transpiled/exceptions.d.ts +2 -0
- package/transpiled/exceptions.js +39 -0
- package/transpiled/exceptions.js.map +1 -0
- package/transpiled/index.d.ts +5 -0
- package/transpiled/index.js +12 -0
- package/transpiled/index.js.map +1 -0
- package/transpiled/manifest.d.ts +3 -0
- package/transpiled/manifest.js +61 -0
- package/transpiled/manifest.js.map +1 -0
- package/transpiled/root.d.ts +2 -0
- package/transpiled/root.js +39 -0
- package/transpiled/root.js.map +1 -0
- package/transpiled/schemas.d.ts +3 -0
- package/transpiled/schemas.js +14 -0
- package/transpiled/schemas.js.map +1 -0
- package/transpiled/tsconfig.tsbuildinfo +1 -0
|
Binary file
|
package/features/streams.feature
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/extensions.exposition",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0-alpha.0",
|
|
4
4
|
"description": "Toa Exposition",
|
|
5
5
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/toa-io/toa#readme",
|
|
@@ -17,15 +17,17 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@toa.io/core": "0.
|
|
21
|
-
"@toa.io/generic": "0.
|
|
22
|
-
"@toa.io/http": "0.
|
|
23
|
-
"@toa.io/schemas": "0.
|
|
24
|
-
"@toa.io/streams": "0.
|
|
20
|
+
"@toa.io/core": "0.24.0-alpha.0",
|
|
21
|
+
"@toa.io/generic": "0.24.0-alpha.0",
|
|
22
|
+
"@toa.io/http": "0.24.0-alpha.0",
|
|
23
|
+
"@toa.io/schemas": "0.24.0-alpha.0",
|
|
24
|
+
"@toa.io/streams": "0.24.0-alpha.0",
|
|
25
25
|
"bcryptjs": "2.4.3",
|
|
26
26
|
"cors": "2.8.5",
|
|
27
|
+
"error-value": "0.3.0",
|
|
27
28
|
"express": "4.18.2",
|
|
28
29
|
"js-yaml": "4.1.0",
|
|
30
|
+
"matchacho": "0.3.5",
|
|
29
31
|
"msgpackr": "1.9.5",
|
|
30
32
|
"negotiator": "0.6.3",
|
|
31
33
|
"paseto": "3.1.4"
|
|
@@ -35,16 +37,21 @@
|
|
|
35
37
|
"testEnvironment": "node"
|
|
36
38
|
},
|
|
37
39
|
"scripts": {
|
|
38
|
-
"
|
|
40
|
+
"test": "jest",
|
|
41
|
+
"transpile": "rm -rf transpiled && npx tsc && npm run transpile:basic && npm run transpile:tokens && npm run transpile:roles",
|
|
39
42
|
"transpile:basic": "rm -rf ./components/identity.basic/operations && npx tsc -p ./components/identity.basic",
|
|
40
43
|
"transpile:tokens": "rm -rf ./components/identity.tokens/operations && npx tsc -p ./components/identity.tokens",
|
|
44
|
+
"transpile:roles": "rm -rf ./components/identity.roles/operations && npx tsc -p ./components/identity.roles",
|
|
41
45
|
"features": "npx cucumber-js"
|
|
42
46
|
},
|
|
43
47
|
"devDependencies": {
|
|
48
|
+
"@toa.io/extensions.storages": "0.24.0-alpha.0",
|
|
44
49
|
"@types/bcryptjs": "2.4.3",
|
|
45
50
|
"@types/cors": "2.8.13",
|
|
46
51
|
"@types/express": "4.17.17",
|
|
47
|
-
"@types/
|
|
52
|
+
"@types/fs-extra": "11.0.4",
|
|
53
|
+
"@types/negotiator": "0.6.1",
|
|
54
|
+
"fs-extra": "11.1.1"
|
|
48
55
|
},
|
|
49
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "09a88bdf444bc5dba66dbf0005c382f3526f0296"
|
|
50
57
|
}
|
package/readme.md
CHANGED
|
@@ -39,11 +39,12 @@ See [features](features) for more examples.
|
|
|
39
39
|
</picture>
|
|
40
40
|
</a>
|
|
41
41
|
|
|
42
|
-
The Exposition extension includes a Service which is an HTTP server with ingress and a Tenant.
|
|
43
|
-
Service communicates
|
|
44
|
-
|
|
45
|
-
of the Tenant is
|
|
46
|
-
|
|
42
|
+
The Exposition extension includes a Service, which is an HTTP server with ingress and a Tenant.
|
|
43
|
+
The Service communicates with Tenants to discover their resource declarations and exposes them as
|
|
44
|
+
HTTP resources.
|
|
45
|
+
An instance of the Tenant is running within each Composition that has at least one Component with a
|
|
46
|
+
resource
|
|
47
|
+
declaration.
|
|
47
48
|
|
|
48
49
|
## Resource tree discovery
|
|
49
50
|
|
|
@@ -179,5 +180,7 @@ exposition:
|
|
|
179
180
|
- [Resource Tree Definition](documentation/tree.md)
|
|
180
181
|
- [Identity authentication](documentation/identity.md)
|
|
181
182
|
- [Access authorization](documentation/access.md)
|
|
183
|
+
- [BLOBs](documentation/octets.md)
|
|
182
184
|
- [Components and resources](documentation/components.md)
|
|
185
|
+
- [Caching](documentation/cache.md)
|
|
183
186
|
- [Features](features)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
string
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
~
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
~
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
~
|
package/source/Gateway.ts
CHANGED
|
@@ -35,9 +35,6 @@ export class Gateway extends Connector {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
private async process (request: http.IncomingMessage): Promise<http.OutgoingMessage> {
|
|
38
|
-
if (request.path[request.path.length - 1] !== '/')
|
|
39
|
-
throw new http.NotFound('Trailing slash is required.')
|
|
40
|
-
|
|
41
38
|
const match = this.tree.match(request.path)
|
|
42
39
|
|
|
43
40
|
if (match === null)
|
|
@@ -60,11 +57,19 @@ export class Gateway extends Connector {
|
|
|
60
57
|
private async call
|
|
61
58
|
(method: Method<Endpoint, Directives>, request: http.IncomingMessage, parameters: Parameter[]):
|
|
62
59
|
Promise<http.OutgoingMessage> {
|
|
60
|
+
if (request.path[request.path.length - 1] !== '/')
|
|
61
|
+
throw new http.NotFound('Trailing slash is required.')
|
|
62
|
+
|
|
63
|
+
if (request.encoder === null)
|
|
64
|
+
throw new http.NotAcceptable()
|
|
65
|
+
|
|
63
66
|
if (method.endpoint === null)
|
|
64
67
|
throw new http.MethodNotAllowed()
|
|
65
68
|
|
|
69
|
+
const body = await request.parse()
|
|
70
|
+
|
|
66
71
|
const reply = await method.endpoint
|
|
67
|
-
.call(
|
|
72
|
+
.call(body, request.query, parameters)
|
|
68
73
|
.catch(rethrow) as Maybe<unknown>
|
|
69
74
|
|
|
70
75
|
if (reply instanceof Error)
|
|
@@ -17,17 +17,13 @@ const app = {
|
|
|
17
17
|
} as unknown as jest.Mock<Express>
|
|
18
18
|
|
|
19
19
|
export function createRequest (req: Partial<Request> = {}, content: string | Buffer = ''):
|
|
20
|
-
jest.MockedObject<
|
|
20
|
+
jest.MockedObject<IncomingMessage> {
|
|
21
21
|
const buffer = Buffer.isBuffer(content) ? content : Buffer.from(content)
|
|
22
22
|
const stream = Readable.from(buffer)
|
|
23
23
|
|
|
24
24
|
Object.assign(stream, { headers: {} }, req)
|
|
25
25
|
|
|
26
|
-
return stream as unknown as jest.MockedObject<
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function createIncomingMessage (path: string, method: string = 'GET'): IncomingMessage {
|
|
30
|
-
return { method, path, headers: {}, body: undefined, query: {} }
|
|
26
|
+
return stream as unknown as jest.MockedObject<IncomingMessage>
|
|
31
27
|
}
|
|
32
28
|
|
|
33
29
|
export const res = {
|
|
@@ -93,43 +93,22 @@ it('should send 501 on unknown method', async () => {
|
|
|
93
93
|
expect(res.sendStatus).toHaveBeenCalledWith(501)
|
|
94
94
|
})
|
|
95
95
|
|
|
96
|
-
describe('request', () => {
|
|
97
|
-
const process = jest.fn(async () => ({})) as unknown as Processing
|
|
98
|
-
|
|
99
|
-
beforeEach(() => {
|
|
100
|
-
server.attach(process)
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
it('should pass decoded request', async () => {
|
|
104
|
-
const path = generate()
|
|
105
|
-
const method = generate()
|
|
106
|
-
const headers = { 'content-type': 'application/json' }
|
|
107
|
-
const body = { [generate()]: generate() }
|
|
108
|
-
const json = JSON.stringify(body)
|
|
109
|
-
const req = createRequest({ path, method, headers }, json)
|
|
110
|
-
|
|
111
|
-
await use(req)
|
|
112
|
-
|
|
113
|
-
expect(process).toHaveBeenCalledWith(expect.objectContaining({ path, method, headers, body }))
|
|
114
|
-
})
|
|
115
|
-
})
|
|
116
|
-
|
|
117
96
|
describe('result', () => {
|
|
118
97
|
it('should send status code 200 if the result has a value', async () => {
|
|
119
|
-
const process = async (): Promise<OutgoingMessage> => ({ headers: {}, body: generate() })
|
|
120
98
|
const req = createRequest()
|
|
121
99
|
|
|
122
|
-
server.attach(
|
|
100
|
+
server.attach(async (): Promise<OutgoingMessage> => ({
|
|
101
|
+
headers: new Headers(), body: generate()
|
|
102
|
+
}))
|
|
123
103
|
await use(req)
|
|
124
104
|
|
|
125
105
|
expect(res.status).toHaveBeenCalledWith(200)
|
|
126
106
|
})
|
|
127
107
|
|
|
128
108
|
it('should send status code 204 if the result has no value', async () => {
|
|
129
|
-
const process = async (): Promise<OutgoingMessage> => ({ headers: {} })
|
|
130
109
|
const req = createRequest()
|
|
131
110
|
|
|
132
|
-
server.attach(
|
|
111
|
+
server.attach(async (): Promise<OutgoingMessage> => ({ headers: new Headers() }))
|
|
133
112
|
await use(req)
|
|
134
113
|
|
|
135
114
|
expect(res.status).toHaveBeenCalledWith(204)
|
|
@@ -139,17 +118,16 @@ describe('result', () => {
|
|
|
139
118
|
const body = { [generate()]: generate() }
|
|
140
119
|
const json = JSON.stringify(body)
|
|
141
120
|
const buf = Buffer.from(json)
|
|
142
|
-
const process = async (): Promise<OutgoingMessage> => ({ headers: {}, body })
|
|
143
121
|
const req = createRequest({ headers: { accept: 'application/json' } })
|
|
144
122
|
|
|
145
|
-
server.attach(
|
|
123
|
+
server.attach(async (): Promise<OutgoingMessage> => ({ headers: new Headers(), body }))
|
|
146
124
|
await use(req)
|
|
147
125
|
|
|
148
|
-
expect(res.
|
|
126
|
+
expect(res.end).toHaveBeenCalledWith(buf)
|
|
149
127
|
})
|
|
150
128
|
|
|
151
129
|
it('should return 500 on exception', async () => {
|
|
152
|
-
|
|
130
|
+
async function process (): Promise<OutgoingMessage> {
|
|
153
131
|
throw new Error('Bad')
|
|
154
132
|
}
|
|
155
133
|
|
|
@@ -170,7 +148,7 @@ describe('result', () => {
|
|
|
170
148
|
const message = generate()
|
|
171
149
|
const req = createRequest()
|
|
172
150
|
|
|
173
|
-
|
|
151
|
+
async function process (): Promise<OutgoingMessage> {
|
|
174
152
|
throw new Error(message)
|
|
175
153
|
}
|
|
176
154
|
|
|
@@ -184,7 +162,7 @@ describe('result', () => {
|
|
|
184
162
|
const req = createRequest()
|
|
185
163
|
const message = generate()
|
|
186
164
|
|
|
187
|
-
|
|
165
|
+
async function process (): Promise<OutgoingMessage> {
|
|
188
166
|
throw new BadRequest(message)
|
|
189
167
|
}
|
|
190
168
|
|
package/source/HTTP/Server.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import os from 'node:os'
|
|
1
3
|
import express from 'express'
|
|
2
4
|
import cors from 'cors'
|
|
3
5
|
import { Connector } from '@toa.io/core'
|
|
4
6
|
import { promex } from '@toa.io/generic'
|
|
7
|
+
import Negotiator from 'negotiator'
|
|
5
8
|
import { read, write, type IncomingMessage, type OutgoingMessage } from './messages'
|
|
6
9
|
import { ClientError, Exception } from './exceptions'
|
|
10
|
+
import { formats, types } from './formats'
|
|
11
|
+
import type { Format } from './formats'
|
|
7
12
|
import type * as http from 'node:http'
|
|
8
13
|
import type { Express, Request, Response, NextFunction } from 'express'
|
|
9
14
|
|
|
@@ -19,8 +24,10 @@ export class Server extends Connector {
|
|
|
19
24
|
this.debug = debug
|
|
20
25
|
}
|
|
21
26
|
|
|
22
|
-
public static create (options
|
|
23
|
-
const properties: Properties =
|
|
27
|
+
public static create (options?: Partial<Properties>): Server {
|
|
28
|
+
const properties: Properties = options === undefined
|
|
29
|
+
? DEFAULTS
|
|
30
|
+
: { ...DEFAULTS, ...options }
|
|
24
31
|
|
|
25
32
|
const app = express()
|
|
26
33
|
|
|
@@ -32,8 +39,8 @@ export class Server extends Connector {
|
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
public attach (process: Processing): void {
|
|
35
|
-
this.app.use((request:
|
|
36
|
-
this.
|
|
42
|
+
this.app.use((request: any, response: Response) => {
|
|
43
|
+
this.extend(request)
|
|
37
44
|
.then(process)
|
|
38
45
|
.then(this.success(request, response))
|
|
39
46
|
.catch(this.fail(request, response))
|
|
@@ -62,14 +69,16 @@ export class Server extends Connector {
|
|
|
62
69
|
console.info('HTTP Server has been stopped.')
|
|
63
70
|
}
|
|
64
71
|
|
|
65
|
-
private async
|
|
66
|
-
const
|
|
67
|
-
const body = await read(request)
|
|
72
|
+
private async extend (request: IncomingMessage): Promise<IncomingMessage> {
|
|
73
|
+
const message = request as unknown as IncomingMessage
|
|
68
74
|
|
|
69
|
-
|
|
75
|
+
message.encoder = negotiate(request)
|
|
76
|
+
message.parse = async <T> (): Promise<T> => await read(request)
|
|
77
|
+
|
|
78
|
+
return message
|
|
70
79
|
}
|
|
71
80
|
|
|
72
|
-
private success (request:
|
|
81
|
+
private success (request: IncomingMessage, response: Response) {
|
|
73
82
|
return (message: OutgoingMessage) => {
|
|
74
83
|
let status = message.status
|
|
75
84
|
|
|
@@ -79,34 +88,35 @@ export class Server extends Connector {
|
|
|
79
88
|
else if (message.body === undefined) status = 204
|
|
80
89
|
else status = 200
|
|
81
90
|
|
|
82
|
-
response
|
|
83
|
-
|
|
84
|
-
.set(message.headers)
|
|
91
|
+
response.status(status)
|
|
92
|
+
message.headers?.forEach((value, key) => response.set(key, value))
|
|
85
93
|
|
|
86
94
|
if (message.body !== undefined && message.body !== null)
|
|
87
|
-
write(request, response, message
|
|
95
|
+
write(request, response, message)
|
|
88
96
|
else
|
|
89
97
|
response.end()
|
|
90
98
|
}
|
|
91
99
|
}
|
|
92
100
|
|
|
93
|
-
private fail (request:
|
|
94
|
-
return (exception: Error) => {
|
|
95
|
-
|
|
101
|
+
private fail (request: IncomingMessage, response: Response) {
|
|
102
|
+
return async (exception: Error) => {
|
|
103
|
+
if (!request.complete)
|
|
104
|
+
await adam(request)
|
|
96
105
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const outputAllowed = exception instanceof ClientError || this.debug
|
|
106
|
+
const status = exception instanceof Exception
|
|
107
|
+
? exception.status
|
|
108
|
+
: 500
|
|
101
109
|
|
|
102
110
|
response.status(status)
|
|
103
111
|
|
|
112
|
+
const outputAllowed = exception instanceof ClientError || this.debug
|
|
113
|
+
|
|
104
114
|
if (outputAllowed) {
|
|
105
115
|
const body = exception instanceof Exception
|
|
106
116
|
? exception.body
|
|
107
117
|
: (exception.stack ?? exception.message)
|
|
108
118
|
|
|
109
|
-
write(request, response, body)
|
|
119
|
+
write(request, response, { body })
|
|
110
120
|
} else
|
|
111
121
|
response.end()
|
|
112
122
|
}
|
|
@@ -120,16 +130,33 @@ function supportedMethods (methods: Set<string>) {
|
|
|
120
130
|
}
|
|
121
131
|
}
|
|
122
132
|
|
|
133
|
+
function negotiate (request: Request): Format | null {
|
|
134
|
+
const negotiator = new Negotiator(request)
|
|
135
|
+
const mediaType = negotiator.mediaType(types)
|
|
136
|
+
|
|
137
|
+
return mediaType === undefined ? null : formats[mediaType]
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// https://github.com/whatwg/fetch/issues/1254
|
|
141
|
+
async function adam (request: Request): Promise<void> {
|
|
142
|
+
const completed = promex()
|
|
143
|
+
const devnull = fs.createWriteStream(os.devNull)
|
|
144
|
+
|
|
145
|
+
request
|
|
146
|
+
.on('end', completed.callback)
|
|
147
|
+
.pipe(devnull)
|
|
148
|
+
|
|
149
|
+
return await completed
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const DEFAULTS = {
|
|
153
|
+
methods: new Set<string>(['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']),
|
|
154
|
+
debug: false
|
|
155
|
+
}
|
|
156
|
+
|
|
123
157
|
interface Properties {
|
|
124
158
|
methods: Set<string>
|
|
125
159
|
debug: boolean
|
|
126
160
|
}
|
|
127
161
|
|
|
128
|
-
function defaults (): Properties {
|
|
129
|
-
return {
|
|
130
|
-
methods: new Set<string>(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']),
|
|
131
|
-
debug: false
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
162
|
export type Processing = (input: IncomingMessage) => Promise<OutgoingMessage>
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { types } from './formats'
|
|
2
|
-
|
|
3
1
|
export class Exception extends Error {
|
|
4
2
|
public readonly status: number
|
|
5
3
|
public readonly body?: any
|
|
@@ -50,21 +48,13 @@ export class MethodNotAllowed extends ClientError {
|
|
|
50
48
|
}
|
|
51
49
|
}
|
|
52
50
|
|
|
53
|
-
class
|
|
54
|
-
private static readonly message = 'Supported media types:\n- ' + types.join('\n- ')
|
|
55
|
-
|
|
56
|
-
protected constructor (status: number) {
|
|
57
|
-
super(status, MediaTypeException.message)
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export class NotAcceptable extends MediaTypeException {
|
|
51
|
+
export class NotAcceptable extends ClientError {
|
|
62
52
|
public constructor () {
|
|
63
53
|
super(406)
|
|
64
54
|
}
|
|
65
55
|
}
|
|
66
56
|
|
|
67
|
-
export class UnsupportedMediaType extends
|
|
57
|
+
export class UnsupportedMediaType extends ClientError {
|
|
68
58
|
public constructor () {
|
|
69
59
|
super(415)
|
|
70
60
|
}
|
|
@@ -5,15 +5,18 @@ import * as msgpack from './msgpack'
|
|
|
5
5
|
import * as text from './text'
|
|
6
6
|
|
|
7
7
|
export const formats: Record<string, Format> = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
[msgpack.type]: msgpack,
|
|
9
|
+
[yaml.type]: yaml,
|
|
10
|
+
[json.type]: json,
|
|
11
|
+
[text.type]: text
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export const types = Object.keys(formats)
|
|
15
15
|
|
|
16
16
|
export interface Format {
|
|
17
|
+
readonly type: string
|
|
18
|
+
readonly multipart: string
|
|
19
|
+
|
|
17
20
|
encode: (value: any) => Buffer
|
|
18
21
|
decode: (buffer: Buffer) => any
|
|
19
22
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { Buffer } from 'node:buffer'
|
|
2
1
|
import { generate } from 'randomstring'
|
|
3
2
|
import * as msgpack from 'msgpackr'
|
|
4
|
-
import {
|
|
5
|
-
import { createRequest
|
|
6
|
-
import { BadRequest,
|
|
3
|
+
import { read } from './messages'
|
|
4
|
+
import { createRequest } from './Server.fixtures'
|
|
5
|
+
import { BadRequest, UnsupportedMediaType } from './exceptions'
|
|
7
6
|
|
|
8
7
|
beforeEach(() => {
|
|
9
8
|
jest.clearAllMocks()
|
|
@@ -69,48 +68,3 @@ describe('read', () => {
|
|
|
69
68
|
await expect(read(request)).rejects.toThrow(BadRequest)
|
|
70
69
|
})
|
|
71
70
|
})
|
|
72
|
-
|
|
73
|
-
describe('write', () => {
|
|
74
|
-
it('should write encoded response', async () => {
|
|
75
|
-
const value = { [generate()]: generate() }
|
|
76
|
-
const json = JSON.stringify(value)
|
|
77
|
-
const buf = Buffer.from(json)
|
|
78
|
-
const headers = { accept: 'application/json' }
|
|
79
|
-
const request = createRequest({ headers }, buf)
|
|
80
|
-
|
|
81
|
-
write(request, res, value)
|
|
82
|
-
|
|
83
|
-
expect(res.set).toHaveBeenCalledWith('content-type', 'application/json')
|
|
84
|
-
expect(res.send).toHaveBeenCalledWith(buf)
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
it('should throw on unsupported response media type', async () => {
|
|
88
|
-
const headers = { accept: 'wtf/' + generate() }
|
|
89
|
-
const request = createRequest({ headers })
|
|
90
|
-
const value = generate()
|
|
91
|
-
|
|
92
|
-
expect(() => {
|
|
93
|
-
write(request, res, value)
|
|
94
|
-
}).toThrow(NotAcceptable)
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
it('should use application/yaml as default', async () => {
|
|
98
|
-
const request = createRequest()
|
|
99
|
-
const message: OutgoingMessage = { headers: {}, body: 'hello' }
|
|
100
|
-
|
|
101
|
-
write(request, res, message)
|
|
102
|
-
|
|
103
|
-
expect(res.set).toHaveBeenCalledWith('content-type', 'application/yaml')
|
|
104
|
-
expect(res.send).toHaveBeenCalled()
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it('should negotiate', async () => {
|
|
108
|
-
const headers = { accept: 'text/html, application/*;q=0.2, image/jpeg;q=0.8' }
|
|
109
|
-
const request = createRequest({ headers })
|
|
110
|
-
const message: OutgoingMessage = { headers: {}, body: 'hello' }
|
|
111
|
-
|
|
112
|
-
write(request, res, message)
|
|
113
|
-
|
|
114
|
-
expect(res.set).toHaveBeenCalledWith('content-type', 'application/yaml')
|
|
115
|
-
})
|
|
116
|
-
})
|