@toa.io/extensions.exposition 0.24.0-alpha.9 → 1.0.0-alpha.2
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/context.toa.yaml +12 -0
- package/components/identity.bans/manifest.toa.yaml +1 -1
- package/components/identity.basic/manifest.toa.yaml +1 -1
- package/components/identity.basic/operations/authenticate.js +1 -2
- package/components/identity.basic/operations/authenticate.js.map +1 -1
- package/components/identity.basic/operations/transit.js.map +1 -1
- package/components/identity.basic/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.basic/source/authenticate.ts +0 -1
- package/components/identity.federation/events/principal.js +22 -0
- package/components/identity.federation/manifest.toa.yaml +88 -0
- package/components/identity.federation/operations/assertions-as-values.cjs +45 -0
- package/components/identity.federation/operations/assertions-as-values.cjs.map +1 -0
- package/components/identity.federation/operations/assertions-as-values.d.cts +4 -0
- package/components/identity.federation/operations/authenticate.d.ts +3 -0
- package/components/identity.federation/operations/authenticate.js +20 -0
- package/components/identity.federation/operations/authenticate.js.map +1 -0
- package/components/identity.federation/operations/create.d.ts +10 -0
- package/components/identity.federation/operations/create.js +15 -0
- package/components/identity.federation/operations/create.js.map +1 -0
- package/components/identity.federation/operations/jwt.cjs +112 -0
- package/components/identity.federation/operations/jwt.cjs.map +1 -0
- package/components/identity.federation/operations/jwt.d.cts +19 -0
- package/components/identity.federation/operations/schemas.d.ts +43 -0
- package/components/identity.federation/operations/schemas.js +9 -0
- package/components/identity.federation/operations/schemas.js.map +1 -0
- package/components/identity.federation/operations/tsconfig.tsbuildinfo +1 -0
- package/components/identity.federation/operations/types.d.ts +51 -0
- package/components/identity.federation/operations/types.js +3 -0
- package/components/identity.federation/operations/types.js.map +1 -0
- package/components/identity.federation/source/assertions-as-values.cts +20 -0
- package/components/identity.federation/source/authenticate.ts +28 -0
- package/components/identity.federation/source/create.ts +26 -0
- package/components/identity.federation/source/jwt.cts +143 -0
- package/components/identity.federation/source/schemas.ts +45 -0
- package/components/identity.federation/source/types.ts +56 -0
- package/components/identity.federation/tsconfig.json +9 -0
- package/components/identity.roles/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.tokens/manifest.toa.yaml +1 -1
- package/components/identity.tokens/operations/authenticate.js.map +1 -1
- package/components/identity.tokens/operations/decrypt.js.map +1 -1
- 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 +2 -2
- package/cucumber.js +0 -1
- package/documentation/components.md +24 -1
- package/documentation/identity.md +7 -7
- package/documentation/octets.md +90 -37
- package/documentation/protocol.md +21 -1
- package/documentation/query.md +1 -1
- package/documentation/vary.md +69 -0
- package/features/cors.feature +72 -0
- package/features/identity.feature +19 -3
- package/features/identity.federation.feature +125 -0
- package/features/octets.entries.feature +121 -0
- package/features/octets.feature +2 -28
- package/features/octets.meta.feature +65 -0
- package/features/octets.workflows.feature +138 -4
- package/features/response.feature +65 -0
- package/features/routes.feature +37 -0
- package/features/steps/Captures.ts +6 -0
- package/features/steps/Components.ts +18 -6
- package/features/steps/Gateway.ts +1 -2
- package/features/steps/HTTP.ts +34 -6
- package/features/steps/IdP.ts +120 -0
- package/features/steps/Parameters.ts +8 -2
- package/features/steps/Workspace.ts +5 -7
- package/features/steps/components/echo/operations/error.js +11 -0
- package/features/steps/components/octets.tester/manifest.toa.yaml +3 -0
- package/features/steps/components/octets.tester/operations/concat.js +7 -0
- package/features/steps/components/octets.tester/operations/echo.js +7 -0
- package/features/steps/components/users/manifest.toa.yaml +3 -0
- package/features/steps/components/users.properties/manifest.toa.yaml +13 -0
- package/features/steps/tsconfig.json +1 -1
- package/features/vary.feature +150 -0
- package/package.json +17 -18
- package/schemas/octets/delete.cos.yaml +2 -1
- package/schemas/octets/list.cos.yaml +2 -1
- package/source/Directive.test.ts +8 -2
- package/source/Directive.ts +19 -16
- package/source/Factory.ts +8 -7
- package/source/Gateway.ts +22 -8
- package/source/HTTP/Server.fixtures.ts +0 -1
- package/source/HTTP/Server.test.ts +61 -134
- package/source/HTTP/Server.ts +68 -37
- package/source/HTTP/formats/msgpack.ts +9 -6
- package/source/HTTP/formats/text.ts +1 -1
- package/source/HTTP/formats/yaml.ts +1 -1
- package/source/HTTP/messages.ts +15 -3
- package/source/Interception.ts +24 -0
- package/source/RTD/Directives.ts +2 -2
- package/source/RTD/Tree.ts +3 -0
- package/source/RTD/syntax/parse.ts +6 -6
- package/source/RTD/syntax/types.ts +1 -1
- package/source/deployment.ts +1 -2
- package/source/directives/auth/{Family.ts → Authorization.ts} +29 -33
- package/source/directives/auth/Incept.ts +1 -1
- package/source/directives/auth/Rule.ts +2 -2
- package/source/directives/auth/index.ts +2 -2
- package/source/directives/auth/schemes.ts +2 -1
- package/source/directives/auth/types.ts +9 -6
- package/source/directives/cache/{Family.ts → Cache.ts} +4 -5
- package/source/directives/cache/index.ts +2 -2
- package/source/directives/cache/types.ts +1 -1
- package/source/directives/cors/CORS.ts +54 -0
- package/source/directives/cors/index.ts +3 -0
- package/source/directives/dev/{Family.ts → Development.ts} +3 -4
- package/source/directives/dev/Stub.ts +4 -4
- package/source/directives/dev/Throw.ts +4 -4
- package/source/directives/dev/index.ts +2 -2
- package/source/directives/dev/types.ts +1 -1
- package/source/directives/index.ts +10 -6
- package/source/directives/octets/Context.ts +1 -1
- package/source/directives/octets/Delete.ts +50 -9
- package/source/directives/octets/Fetch.ts +18 -19
- package/source/directives/octets/List.ts +37 -7
- package/source/directives/octets/{Family.ts → Octets.ts} +7 -6
- package/source/directives/octets/Permute.ts +3 -3
- package/source/directives/octets/Store.ts +43 -99
- package/source/directives/octets/index.ts +2 -2
- package/source/directives/octets/schemas.ts +11 -6
- package/source/directives/octets/types.ts +4 -3
- package/source/directives/octets/workflow/Execution.ts +78 -0
- package/source/directives/octets/workflow/Workflow.ts +37 -0
- package/source/directives/octets/workflow/index.ts +1 -0
- package/source/directives/vary/Directive.ts +6 -0
- package/source/directives/vary/Embed.ts +62 -0
- package/source/directives/vary/Properties.ts +17 -0
- package/source/directives/vary/Vary.ts +48 -0
- package/source/directives/vary/embeddings/Embedding.ts +6 -0
- package/source/directives/vary/embeddings/Header.ts +30 -0
- package/source/directives/vary/embeddings/Language.ts +31 -0
- package/source/directives/vary/embeddings/index.ts +11 -0
- package/source/directives/vary/index.ts +3 -0
- package/source/io.ts +4 -0
- package/source/manifest.test.ts +6 -14
- package/source/manifest.ts +9 -6
- package/source/schemas.ts +7 -3
- package/transpiled/Composition.js.map +1 -1
- package/transpiled/Directive.d.ts +6 -7
- package/transpiled/Directive.js +12 -10
- package/transpiled/Directive.js.map +1 -1
- package/transpiled/Factory.d.ts +0 -1
- package/transpiled/Factory.js +7 -6
- package/transpiled/Factory.js.map +1 -1
- package/transpiled/Gateway.d.ts +8 -5
- package/transpiled/Gateway.js +12 -2
- package/transpiled/Gateway.js.map +1 -1
- package/transpiled/HTTP/Server.d.ts +5 -3
- package/transpiled/HTTP/Server.fixtures.d.ts +0 -1
- package/transpiled/HTTP/Server.fixtures.js +1 -2
- package/transpiled/HTTP/Server.fixtures.js.map +1 -1
- package/transpiled/HTTP/Server.js +50 -32
- package/transpiled/HTTP/Server.js.map +1 -1
- package/transpiled/HTTP/formats/msgpack.d.ts +2 -2
- package/transpiled/HTTP/formats/msgpack.js +8 -29
- package/transpiled/HTTP/formats/msgpack.js.map +1 -1
- package/transpiled/HTTP/formats/text.d.ts +3 -1
- package/transpiled/HTTP/formats/text.js.map +1 -1
- package/transpiled/HTTP/formats/yaml.js +1 -1
- package/transpiled/HTTP/formats/yaml.js.map +1 -1
- package/transpiled/HTTP/messages.d.ts +5 -0
- package/transpiled/HTTP/messages.js +8 -3
- package/transpiled/HTTP/messages.js.map +1 -1
- package/transpiled/Interception.d.ts +9 -0
- package/transpiled/Interception.js +19 -0
- package/transpiled/Interception.js.map +1 -0
- package/transpiled/Query.js.map +1 -1
- package/transpiled/RTD/Directives.d.ts +2 -2
- package/transpiled/RTD/Node.js.map +1 -1
- package/transpiled/RTD/Route.js.map +1 -1
- package/transpiled/RTD/Tree.js +2 -0
- package/transpiled/RTD/Tree.js.map +1 -1
- package/transpiled/RTD/syntax/parse.js +1 -1
- package/transpiled/RTD/syntax/parse.js.map +1 -1
- package/transpiled/RTD/syntax/types.js +1 -1
- package/transpiled/RTD/syntax/types.js.map +1 -1
- package/transpiled/deployment.js +1 -2
- package/transpiled/deployment.js.map +1 -1
- package/transpiled/directives/auth/{Family.d.ts → Authorization.d.ts} +4 -4
- package/transpiled/directives/auth/{Family.js → Authorization.js} +15 -8
- package/transpiled/directives/auth/Authorization.js.map +1 -0
- package/transpiled/directives/auth/Incept.js.map +1 -1
- package/transpiled/directives/auth/Role.js.map +1 -1
- package/transpiled/directives/auth/Rule.d.ts +2 -2
- package/transpiled/directives/auth/Rule.js.map +1 -1
- package/transpiled/directives/auth/index.d.ts +2 -2
- package/transpiled/directives/auth/index.js +4 -5
- package/transpiled/directives/auth/index.js.map +1 -1
- package/transpiled/directives/auth/schemes.js +2 -1
- package/transpiled/directives/auth/schemes.js.map +1 -1
- package/transpiled/directives/auth/types.d.ts +4 -4
- package/transpiled/directives/cache/{Family.d.ts → Cache.d.ts} +4 -5
- package/transpiled/directives/cache/{Family.js → Cache.js} +4 -2
- package/transpiled/directives/cache/{Family.js.map → Cache.js.map} +1 -1
- package/transpiled/directives/cache/index.d.ts +2 -2
- package/transpiled/directives/cache/index.js +4 -5
- package/transpiled/directives/cache/index.js.map +1 -1
- package/transpiled/directives/cache/types.d.ts +1 -1
- package/transpiled/directives/cors/CORS.d.ts +11 -0
- package/transpiled/directives/cors/CORS.js +44 -0
- package/transpiled/directives/cors/CORS.js.map +1 -0
- package/transpiled/directives/cors/index.d.ts +2 -0
- package/transpiled/directives/cors/index.js +6 -0
- package/transpiled/directives/cors/index.js.map +1 -0
- package/transpiled/directives/dev/{Family.d.ts → Development.d.ts} +3 -4
- package/transpiled/directives/dev/{Family.js → Development.js} +4 -2
- package/transpiled/directives/dev/Development.js.map +1 -0
- package/transpiled/directives/dev/Stub.d.ts +3 -3
- package/transpiled/directives/dev/Stub.js.map +1 -1
- package/transpiled/directives/dev/Throw.d.ts +3 -3
- package/transpiled/directives/dev/Throw.js.map +1 -1
- package/transpiled/directives/dev/index.d.ts +2 -2
- package/transpiled/directives/dev/index.js +4 -5
- package/transpiled/directives/dev/index.js.map +1 -1
- package/transpiled/directives/dev/types.d.ts +1 -1
- package/transpiled/directives/index.d.ts +3 -1
- package/transpiled/directives/index.js +9 -9
- package/transpiled/directives/index.js.map +1 -1
- package/transpiled/directives/octets/Context.d.ts +1 -1
- package/transpiled/directives/octets/Delete.d.ts +12 -3
- package/transpiled/directives/octets/Delete.js +32 -7
- package/transpiled/directives/octets/Delete.js.map +1 -1
- package/transpiled/directives/octets/Fetch.d.ts +5 -6
- package/transpiled/directives/octets/Fetch.js +11 -12
- package/transpiled/directives/octets/Fetch.js.map +1 -1
- package/transpiled/directives/octets/List.d.ts +7 -2
- package/transpiled/directives/octets/List.js +22 -4
- package/transpiled/directives/octets/List.js.map +1 -1
- package/transpiled/directives/octets/Octets.d.ts +12 -0
- package/transpiled/directives/octets/{Family.js → Octets.js} +6 -4
- package/transpiled/directives/octets/Octets.js.map +1 -0
- package/transpiled/directives/octets/Permute.d.ts +1 -1
- package/transpiled/directives/octets/Permute.js +2 -2
- package/transpiled/directives/octets/Permute.js.map +1 -1
- package/transpiled/directives/octets/Store.d.ts +10 -21
- package/transpiled/directives/octets/Store.js +25 -69
- package/transpiled/directives/octets/Store.js.map +1 -1
- package/transpiled/directives/octets/index.d.ts +2 -2
- package/transpiled/directives/octets/index.js +4 -5
- package/transpiled/directives/octets/index.js.map +1 -1
- package/transpiled/directives/octets/schemas.d.ts +11 -6
- package/transpiled/directives/octets/schemas.js.map +1 -1
- package/transpiled/directives/octets/types.d.ts +4 -3
- package/transpiled/directives/octets/workflow/Execution.d.ts +25 -0
- package/transpiled/directives/octets/workflow/Execution.js +55 -0
- package/transpiled/directives/octets/workflow/Execution.js.map +1 -0
- package/transpiled/directives/octets/workflow/Workflow.d.ts +12 -0
- package/transpiled/directives/octets/workflow/Workflow.js +25 -0
- package/transpiled/directives/octets/workflow/Workflow.js.map +1 -0
- package/transpiled/directives/octets/workflow/index.d.ts +1 -0
- package/transpiled/directives/octets/workflow/index.js +6 -0
- package/transpiled/directives/octets/workflow/index.js.map +1 -0
- package/transpiled/directives/vary/Directive.d.ts +5 -0
- package/transpiled/directives/vary/Directive.js +3 -0
- package/transpiled/directives/vary/Directive.js.map +1 -0
- package/transpiled/directives/vary/Embed.d.ts +10 -0
- package/transpiled/directives/vary/Embed.js +49 -0
- package/transpiled/directives/vary/Embed.js.map +1 -0
- package/transpiled/directives/vary/Properties.d.ts +9 -0
- package/transpiled/directives/vary/Properties.js +16 -0
- package/transpiled/directives/vary/Properties.js.map +1 -0
- package/transpiled/directives/vary/Vary.d.ts +10 -0
- package/transpiled/directives/vary/Vary.js +36 -0
- package/transpiled/directives/vary/Vary.js.map +1 -0
- package/transpiled/directives/vary/embeddings/Embedding.d.ts +5 -0
- package/transpiled/directives/vary/embeddings/Embedding.js +3 -0
- package/transpiled/directives/vary/embeddings/Embedding.js.map +1 -0
- package/transpiled/directives/vary/embeddings/Header.d.ts +7 -0
- package/transpiled/directives/vary/embeddings/Header.js +26 -0
- package/transpiled/directives/vary/embeddings/Header.js.map +1 -0
- package/transpiled/directives/vary/embeddings/Language.d.ts +7 -0
- package/transpiled/directives/vary/embeddings/Language.js +28 -0
- package/transpiled/directives/vary/embeddings/Language.js.map +1 -0
- package/transpiled/directives/vary/embeddings/index.d.ts +5 -0
- package/transpiled/directives/vary/embeddings/index.js +10 -0
- package/transpiled/directives/vary/embeddings/index.js.map +1 -0
- package/transpiled/directives/vary/index.d.ts +2 -0
- package/transpiled/directives/vary/index.js +6 -0
- package/transpiled/directives/vary/index.js.map +1 -0
- package/transpiled/io.d.ts +3 -0
- package/transpiled/io.js +3 -0
- package/transpiled/io.js.map +1 -0
- package/transpiled/manifest.js +10 -5
- package/transpiled/manifest.js.map +1 -1
- package/transpiled/schemas.d.ts +7 -3
- package/transpiled/schemas.js.map +1 -1
- package/transpiled/tsconfig.tsbuildinfo +1 -1
- package/transpiled/directives/auth/Family.js.map +0 -1
- package/transpiled/directives/dev/Family.js.map +0 -1
- package/transpiled/directives/octets/Family.d.ts +0 -12
- package/transpiled/directives/octets/Family.js.map +0 -1
|
@@ -1,199 +1,126 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
1
2
|
import { Connector } from '@toa.io/core'
|
|
2
|
-
import { immediate } from '@toa.io/generic'
|
|
3
|
-
import { generate } from 'randomstring'
|
|
4
3
|
import { type Processing, Server } from './Server'
|
|
5
4
|
import { type OutgoingMessage } from './messages'
|
|
6
|
-
import { express, cors, createRequest, res, next } from './Server.fixtures'
|
|
7
5
|
import { BadRequest } from './exceptions'
|
|
8
|
-
import type { Express, Request, RequestHandler } from 'express'
|
|
9
|
-
import type { CorsOptions } from 'cors'
|
|
10
|
-
import type http from 'node:http'
|
|
11
|
-
|
|
12
|
-
jest.mock('express', () => () => express())
|
|
13
|
-
jest.mock('cors', () => (options: CorsOptions) => cors(options))
|
|
14
6
|
|
|
15
7
|
let server: Server
|
|
16
|
-
let app: jest.MockedObject<Express>
|
|
17
|
-
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
jest.clearAllMocks()
|
|
20
8
|
|
|
21
|
-
|
|
22
|
-
|
|
9
|
+
beforeAll(() => {
|
|
10
|
+
server = Server.create({ port: 0 })
|
|
23
11
|
})
|
|
24
12
|
|
|
25
13
|
it('should instance of connector', async () => {
|
|
26
14
|
expect(server).toBeInstanceOf(Connector)
|
|
27
15
|
})
|
|
28
16
|
|
|
29
|
-
it('should create express app', async () => {
|
|
30
|
-
expect(express).toHaveBeenCalled()
|
|
31
|
-
expect(app.disable).toHaveBeenCalledWith('x-powered-by')
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it('should support cors', async () => {
|
|
35
|
-
expect(cors).toHaveBeenCalledWith({ allowedHeaders: ['content-type'] } satisfies CorsOptions)
|
|
36
|
-
|
|
37
|
-
const middleware = cors.mock.results[0].value
|
|
38
|
-
|
|
39
|
-
expect(app.use).toHaveBeenCalledWith(middleware)
|
|
40
|
-
})
|
|
41
|
-
|
|
42
17
|
it('should start HTTP server', async () => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
await immediate()
|
|
46
|
-
|
|
47
|
-
expect(app.listen).toHaveBeenCalledWith(8000, expect.anything())
|
|
48
|
-
|
|
49
|
-
const done = app.listen.mock.calls[0][1]
|
|
50
|
-
|
|
51
|
-
if (done !== undefined) done()
|
|
18
|
+
await server.connect()
|
|
52
19
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
it('should stop HTTP server', async () => {
|
|
57
|
-
const started = server.connect()
|
|
58
|
-
|
|
59
|
-
await immediate()
|
|
60
|
-
|
|
61
|
-
app.listen.mock.calls[0][1]?.() // `listen` callback
|
|
62
|
-
|
|
63
|
-
await started
|
|
64
|
-
|
|
65
|
-
const stopped = server.disconnect()
|
|
66
|
-
const httpServer: jest.MockedObject<http.Server> = app.listen.mock.results[0].value
|
|
67
|
-
|
|
68
|
-
await immediate()
|
|
69
|
-
|
|
70
|
-
expect(httpServer.close).toHaveBeenCalled()
|
|
71
|
-
|
|
72
|
-
httpServer.close.mock.calls[0][0]?.() // `close` callback
|
|
73
|
-
|
|
74
|
-
await stopped
|
|
20
|
+
expect(server.connected).toBeTruthy()
|
|
21
|
+
expect(server.port).toBeGreaterThan(0)
|
|
75
22
|
})
|
|
76
23
|
|
|
77
24
|
it('should register request handler', async () => {
|
|
78
|
-
const process = jest.fn(
|
|
79
|
-
const req = createRequest()
|
|
25
|
+
const process: Processing = jest.fn().mockResolvedValue(undefined)
|
|
80
26
|
|
|
81
27
|
server.attach(process)
|
|
82
28
|
|
|
83
|
-
await
|
|
29
|
+
const res = await fetch(`http://localhost:${server.port}`)
|
|
30
|
+
|
|
31
|
+
await res.text()
|
|
84
32
|
|
|
85
|
-
expect(process).
|
|
33
|
+
expect(process).toHaveBeenCalledTimes(1)
|
|
86
34
|
})
|
|
87
35
|
|
|
88
36
|
it('should send 501 on unknown method', async () => {
|
|
89
|
-
const
|
|
37
|
+
const head = await fetch(`http://localhost:${server.port}/`, { method: 'COPY' })
|
|
90
38
|
|
|
91
|
-
await
|
|
39
|
+
await head.text()
|
|
40
|
+
expect(head.status).toBe(501)
|
|
41
|
+
})
|
|
92
42
|
|
|
93
|
-
|
|
43
|
+
it('should stop HTTP server', async () => {
|
|
44
|
+
await server.disconnect()
|
|
45
|
+
expect(server.port).toBe(0)
|
|
46
|
+
expect(server.connected).toBeFalsy()
|
|
94
47
|
})
|
|
95
48
|
|
|
96
49
|
describe('result', () => {
|
|
97
|
-
|
|
98
|
-
|
|
50
|
+
beforeEach(async () => {
|
|
51
|
+
server = Server.create({ port: 0 })
|
|
52
|
+
await server.connect()
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
afterEach(async () => {
|
|
56
|
+
await server.disconnect()
|
|
57
|
+
})
|
|
99
58
|
|
|
59
|
+
it('should send status code 200 if the result has a value', async () => {
|
|
100
60
|
server.attach(async (): Promise<OutgoingMessage> => ({
|
|
101
|
-
headers: new Headers(), body:
|
|
61
|
+
headers: new Headers(), body: randomUUID()
|
|
102
62
|
}))
|
|
103
|
-
await use(req)
|
|
104
63
|
|
|
105
|
-
|
|
64
|
+
const res = await fetch(`http://localhost:${server.port}/`)
|
|
65
|
+
|
|
66
|
+
await res.text()
|
|
67
|
+
expect(res.status).toBe(200)
|
|
106
68
|
})
|
|
107
69
|
|
|
108
70
|
it('should send status code 204 if the result has no value', async () => {
|
|
109
|
-
const req = createRequest()
|
|
110
|
-
|
|
111
71
|
server.attach(async (): Promise<OutgoingMessage> => ({ headers: new Headers() }))
|
|
112
|
-
await use(req)
|
|
113
72
|
|
|
114
|
-
|
|
73
|
+
const res = await fetch(`http://localhost:${server.port}/`)
|
|
74
|
+
|
|
75
|
+
await res.text()
|
|
76
|
+
expect(res.status).toBe(204)
|
|
115
77
|
})
|
|
116
78
|
|
|
117
79
|
it('should send result', async () => {
|
|
118
|
-
const body = { [
|
|
119
|
-
const json = JSON.stringify(body)
|
|
120
|
-
const buf = Buffer.from(json)
|
|
121
|
-
const req = createRequest({ headers: { accept: 'application/json' } })
|
|
80
|
+
const body = { [randomUUID()]: randomUUID() }
|
|
122
81
|
|
|
123
|
-
server.attach(async (): Promise<OutgoingMessage> =>
|
|
124
|
-
|
|
82
|
+
server.attach(async (): Promise<OutgoingMessage> =>
|
|
83
|
+
({ headers: new Headers(), body }))
|
|
125
84
|
|
|
126
|
-
|
|
127
|
-
|
|
85
|
+
const res = await fetch(`http://localhost:${server.port}/`,
|
|
86
|
+
{ headers: { accept: 'application/json' } })
|
|
128
87
|
|
|
129
|
-
|
|
130
|
-
async function process (): Promise<OutgoingMessage> {
|
|
131
|
-
throw new Error('Bad')
|
|
132
|
-
}
|
|
88
|
+
const result = await res.json()
|
|
133
89
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
server.attach(process)
|
|
137
|
-
await use(req)
|
|
138
|
-
|
|
139
|
-
expect(res.status).toHaveBeenCalledWith(500)
|
|
90
|
+
expect(result).toEqual(body)
|
|
140
91
|
})
|
|
141
92
|
|
|
142
|
-
it('should
|
|
143
|
-
jest.
|
|
144
|
-
|
|
145
|
-
server = Server.create({ debug: true })
|
|
146
|
-
app = express.mock.results[0]?.value
|
|
147
|
-
|
|
148
|
-
const message = generate()
|
|
149
|
-
const req = createRequest()
|
|
150
|
-
|
|
151
|
-
async function process (): Promise<OutgoingMessage> {
|
|
152
|
-
throw new Error(message)
|
|
153
|
-
}
|
|
93
|
+
it('should return 500 on exception', async () => {
|
|
94
|
+
server.attach(jest.fn().mockRejectedValue(new Error('Bad')))
|
|
154
95
|
|
|
155
|
-
server.
|
|
156
|
-
await use(req)
|
|
96
|
+
const res = await fetch(`http://localhost:${server.port}/`)
|
|
157
97
|
|
|
158
|
-
|
|
98
|
+
await res.text()
|
|
99
|
+
expect(res.status).toBe(500)
|
|
159
100
|
})
|
|
160
101
|
|
|
161
102
|
it('should send client error', async () => {
|
|
162
|
-
const
|
|
163
|
-
const message = generate()
|
|
103
|
+
const message = randomUUID()
|
|
164
104
|
|
|
165
|
-
|
|
166
|
-
throw new BadRequest(message)
|
|
167
|
-
}
|
|
105
|
+
server.attach(jest.fn().mockRejectedValueOnce(new BadRequest(message)))
|
|
168
106
|
|
|
169
|
-
server.
|
|
170
|
-
await
|
|
107
|
+
const res = await fetch(`http://localhost:${server.port}/`)
|
|
108
|
+
const text = await res.text()
|
|
171
109
|
|
|
172
|
-
expect(res.status).
|
|
110
|
+
expect(res.status).toBe(400)
|
|
111
|
+
expect(text).toContain(message)
|
|
173
112
|
})
|
|
174
113
|
})
|
|
175
114
|
|
|
176
115
|
describe('options', () => {
|
|
177
116
|
it('should send 501 on unspecified method', async () => {
|
|
178
|
-
|
|
117
|
+
server = Server.create({ methods: new Set(['COPY']), port: 0 })
|
|
118
|
+
await server.connect()
|
|
179
119
|
|
|
180
|
-
|
|
181
|
-
app = express.mock.results[0]?.value
|
|
120
|
+
const res = await fetch(`http://localhost:${server.port}/`)
|
|
182
121
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
expect(res.sendStatus).toHaveBeenCalledWith(501)
|
|
122
|
+
await res.text()
|
|
123
|
+
await server.disconnect()
|
|
124
|
+
expect(res.status).toBe(501)
|
|
188
125
|
})
|
|
189
126
|
})
|
|
190
|
-
|
|
191
|
-
async function use (req: Request): Promise<void> {
|
|
192
|
-
for (const call of app.use.mock.calls) {
|
|
193
|
-
const usage = call[0] as unknown as RequestHandler
|
|
194
|
-
|
|
195
|
-
usage(req, res, next)
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
await immediate()
|
|
199
|
-
}
|
package/source/HTTP/Server.ts
CHANGED
|
@@ -1,56 +1,67 @@
|
|
|
1
1
|
import fs from 'node:fs'
|
|
2
2
|
import os from 'node:os'
|
|
3
3
|
import express from 'express'
|
|
4
|
-
import cors from 'cors'
|
|
5
4
|
import { Connector } from '@toa.io/core'
|
|
6
5
|
import { promex } from '@toa.io/generic'
|
|
7
6
|
import Negotiator from 'negotiator'
|
|
8
7
|
import { read, write, type IncomingMessage, type OutgoingMessage } from './messages'
|
|
9
8
|
import { ClientError, Exception } from './exceptions'
|
|
10
9
|
import { formats, types } from './formats'
|
|
11
|
-
import type { Format } from './formats'
|
|
12
10
|
import type * as http from 'node:http'
|
|
13
11
|
import type { Express, Request, Response, NextFunction } from 'express'
|
|
14
12
|
|
|
15
13
|
export class Server extends Connector {
|
|
16
|
-
private readonly debug: boolean
|
|
17
|
-
private readonly app: Express
|
|
18
14
|
private server?: http.Server
|
|
15
|
+
private readonly app: Express
|
|
16
|
+
private readonly debug: boolean
|
|
17
|
+
private readonly requestedPort: number
|
|
19
18
|
|
|
20
|
-
private constructor (app: Express, debug: boolean) {
|
|
19
|
+
private constructor (app: Express, debug: boolean, port: number) {
|
|
21
20
|
super()
|
|
22
21
|
|
|
23
22
|
this.app = app
|
|
24
23
|
this.debug = debug
|
|
24
|
+
this.requestedPort = port
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public get port (): number {
|
|
28
|
+
if (this.server === undefined) return this.requestedPort
|
|
29
|
+
|
|
30
|
+
const address = this.server.address()
|
|
31
|
+
|
|
32
|
+
if (address === null || typeof address === 'string')
|
|
33
|
+
throw new Error('Server is not listening on a port.')
|
|
34
|
+
|
|
35
|
+
return address.port
|
|
25
36
|
}
|
|
26
37
|
|
|
27
38
|
public static create (options?: Partial<Properties>): Server {
|
|
28
|
-
const properties
|
|
39
|
+
const properties = options === undefined
|
|
29
40
|
? DEFAULTS
|
|
30
41
|
: { ...DEFAULTS, ...options }
|
|
31
42
|
|
|
32
43
|
const app = express()
|
|
33
44
|
|
|
34
45
|
app.disable('x-powered-by')
|
|
35
|
-
app.use(cors({ allowedHeaders: ['content-type'] }))
|
|
36
46
|
app.use(supportedMethods(properties.methods))
|
|
37
47
|
|
|
38
|
-
return new Server(app, properties.debug)
|
|
48
|
+
return new Server(app, properties.debug, properties.port)
|
|
39
49
|
}
|
|
40
50
|
|
|
41
51
|
public attach (process: Processing): void {
|
|
42
|
-
this.app.use((request:
|
|
43
|
-
this.extend(request)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
.
|
|
52
|
+
this.app.use((request: Request, response: Response) => {
|
|
53
|
+
const message = this.extend(request)
|
|
54
|
+
|
|
55
|
+
process(message)
|
|
56
|
+
.then(this.success(message, response))
|
|
57
|
+
.catch(this.fail(message, response))
|
|
47
58
|
})
|
|
48
59
|
}
|
|
49
60
|
|
|
50
61
|
protected override async open (): Promise<void> {
|
|
51
62
|
const listening = promex()
|
|
52
63
|
|
|
53
|
-
this.server = this.app.listen(
|
|
64
|
+
this.server = this.app.listen(this.requestedPort, listening.callback)
|
|
54
65
|
|
|
55
66
|
await listening
|
|
56
67
|
|
|
@@ -63,17 +74,27 @@ export class Server extends Connector {
|
|
|
63
74
|
this.server?.close(stopped.callback)
|
|
64
75
|
|
|
65
76
|
await stopped
|
|
66
|
-
}
|
|
67
77
|
|
|
68
|
-
|
|
78
|
+
this.server = undefined
|
|
79
|
+
|
|
69
80
|
console.info('HTTP Server has been stopped.')
|
|
70
81
|
}
|
|
71
82
|
|
|
72
|
-
private
|
|
73
|
-
const message = request as
|
|
83
|
+
private extend (request: Request): IncomingMessage {
|
|
84
|
+
const message = request as IncomingMessage
|
|
74
85
|
|
|
75
|
-
|
|
76
|
-
|
|
86
|
+
negotiate(request, message)
|
|
87
|
+
|
|
88
|
+
message.pipelines = { body: [], response: [] }
|
|
89
|
+
|
|
90
|
+
message.parse = async <T> (): Promise<T> => {
|
|
91
|
+
const value = await read(request)
|
|
92
|
+
|
|
93
|
+
if (message.pipelines.body.length === 0)
|
|
94
|
+
return value
|
|
95
|
+
|
|
96
|
+
return message.pipelines.body.reduce((value, transform) => transform(value), value)
|
|
97
|
+
}
|
|
77
98
|
|
|
78
99
|
return message
|
|
79
100
|
}
|
|
@@ -89,12 +110,7 @@ export class Server extends Connector {
|
|
|
89
110
|
else status = 200
|
|
90
111
|
|
|
91
112
|
response.status(status)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (message.body !== undefined && message.body !== null)
|
|
95
|
-
write(request, response, message)
|
|
96
|
-
else
|
|
97
|
-
response.end()
|
|
113
|
+
write(request, response, message)
|
|
98
114
|
}
|
|
99
115
|
}
|
|
100
116
|
|
|
@@ -109,16 +125,15 @@ export class Server extends Connector {
|
|
|
109
125
|
|
|
110
126
|
response.status(status)
|
|
111
127
|
|
|
112
|
-
const
|
|
128
|
+
const message: OutgoingMessage = {}
|
|
129
|
+
const verbose = exception instanceof ClientError || this.debug
|
|
113
130
|
|
|
114
|
-
if (
|
|
115
|
-
|
|
131
|
+
if (verbose)
|
|
132
|
+
message.body = exception instanceof Exception
|
|
116
133
|
? exception.body
|
|
117
134
|
: (exception.stack ?? exception.message)
|
|
118
135
|
|
|
119
|
-
|
|
120
|
-
} else
|
|
121
|
-
response.end()
|
|
136
|
+
write(request, response, message)
|
|
122
137
|
}
|
|
123
138
|
}
|
|
124
139
|
}
|
|
@@ -130,11 +145,23 @@ function supportedMethods (methods: Set<string>) {
|
|
|
130
145
|
}
|
|
131
146
|
}
|
|
132
147
|
|
|
133
|
-
function negotiate (request: Request):
|
|
148
|
+
function negotiate (request: Request, message: IncomingMessage): void {
|
|
149
|
+
if (request.headers.accept !== undefined) {
|
|
150
|
+
const match = SUBTYPE.exec(request.headers.accept)
|
|
151
|
+
|
|
152
|
+
if (match !== null) {
|
|
153
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
154
|
+
const { type, subtype, suffix } = match.groups!
|
|
155
|
+
|
|
156
|
+
request.headers.accept = `${type}/${suffix}`
|
|
157
|
+
message.subtype = subtype
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
134
161
|
const negotiator = new Negotiator(request)
|
|
135
162
|
const mediaType = negotiator.mediaType(types)
|
|
136
163
|
|
|
137
|
-
|
|
164
|
+
message.encoder = mediaType === undefined ? null : formats[mediaType]
|
|
138
165
|
}
|
|
139
166
|
|
|
140
167
|
// https://github.com/whatwg/fetch/issues/1254
|
|
@@ -149,14 +176,18 @@ async function adam (request: Request): Promise<void> {
|
|
|
149
176
|
return await completed
|
|
150
177
|
}
|
|
151
178
|
|
|
152
|
-
const DEFAULTS = {
|
|
153
|
-
methods: new Set<string>(['GET', '
|
|
154
|
-
debug: false
|
|
179
|
+
const DEFAULTS: Properties = {
|
|
180
|
+
methods: new Set<string>(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']),
|
|
181
|
+
debug: false,
|
|
182
|
+
port: 8000
|
|
155
183
|
}
|
|
156
184
|
|
|
157
185
|
interface Properties {
|
|
158
186
|
methods: Set<string>
|
|
159
187
|
debug: boolean
|
|
188
|
+
port: number
|
|
160
189
|
}
|
|
161
190
|
|
|
162
191
|
export type Processing = (input: IncomingMessage) => Promise<OutgoingMessage>
|
|
192
|
+
|
|
193
|
+
const SUBTYPE = /^(?<type>\w{1,32})\/(vnd\.toa\.(?<subtype>\S{1,32})\+)(?<suffix>\S{1,32})$/
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { type Buffer } from 'node:buffer'
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
export const type = 'application/msgpack'
|
|
5
|
-
export const multipart = 'multipart/msgpack'
|
|
2
|
+
import { pack, unpack } from 'msgpackr'
|
|
6
3
|
|
|
7
4
|
export function decode (buffer: Buffer): any {
|
|
8
|
-
return
|
|
5
|
+
return unpack(buffer)
|
|
9
6
|
}
|
|
10
7
|
|
|
11
8
|
export function encode (value: any): Buffer {
|
|
12
|
-
|
|
9
|
+
if (typeof value === 'object' && value !== null)
|
|
10
|
+
Object.setPrototypeOf(value, null)
|
|
11
|
+
|
|
12
|
+
return pack(value)
|
|
13
13
|
}
|
|
14
|
+
|
|
15
|
+
export const type = 'application/msgpack'
|
|
16
|
+
export const multipart = 'multipart/msgpack'
|
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
|
|
@@ -47,8 +52,10 @@ function send (message: OutgoingMessage, request: IncomingMessage, response: Res
|
|
|
47
52
|
|
|
48
53
|
const buf = request.encoder.encode(message.body)
|
|
49
54
|
|
|
50
|
-
response
|
|
51
|
-
|
|
55
|
+
response
|
|
56
|
+
.set('content-type', request.encoder.type)
|
|
57
|
+
.append('vary', 'accept')
|
|
58
|
+
.end(buf)
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
function stream
|
|
@@ -97,6 +104,11 @@ export interface IncomingMessage extends Request {
|
|
|
97
104
|
query: Query
|
|
98
105
|
parse: <T> () => Promise<T>
|
|
99
106
|
encoder: Format | null
|
|
107
|
+
subtype: string | null
|
|
108
|
+
pipelines: {
|
|
109
|
+
body: Array<(input: unknown) => unknown>
|
|
110
|
+
response: Array<(output: OutgoingMessage) => void>
|
|
111
|
+
}
|
|
100
112
|
}
|
|
101
113
|
|
|
102
114
|
export interface OutgoingMessage {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Input, Output } from './io'
|
|
2
|
+
|
|
3
|
+
export class Interception implements Interceptor {
|
|
4
|
+
private readonly interceptors: Interceptor[]
|
|
5
|
+
|
|
6
|
+
public constructor (interceptors: Interceptor[]) {
|
|
7
|
+
this.interceptors = interceptors
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public async intercept (input: Input): Promise<Output> {
|
|
11
|
+
for (const interceptor of this.interceptors) {
|
|
12
|
+
const output = await interceptor.intercept(input)
|
|
13
|
+
|
|
14
|
+
if (output !== null)
|
|
15
|
+
return output
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface Interceptor {
|
|
23
|
+
intercept: (input: Input) => Output | Promise<Output>
|
|
24
|
+
}
|
package/source/RTD/Directives.ts
CHANGED
package/source/RTD/Tree.ts
CHANGED
|
@@ -28,7 +28,7 @@ function parseNode (input: object, shortcuts?: Shortcuts): Node {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
if (key[0] === '/') {
|
|
31
|
-
const route = parseRoute(key, value, shortcuts)
|
|
31
|
+
const route = parseRoute(key, value as Node, shortcuts)
|
|
32
32
|
|
|
33
33
|
node.routes.push(route)
|
|
34
34
|
|
|
@@ -36,7 +36,7 @@ function parseNode (input: object, shortcuts?: Shortcuts): Node {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
if (verbs.has(key)) {
|
|
39
|
-
const method = parseMethod(key, value, shortcuts)
|
|
39
|
+
const method = parseMethod(key, value as Mapping, shortcuts)
|
|
40
40
|
|
|
41
41
|
node.methods.push(method)
|
|
42
42
|
|
|
@@ -65,7 +65,7 @@ export function createNode (): Node {
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
function parseRoute (path: string, value:
|
|
68
|
+
function parseRoute (path: string, value: Node, shortcuts?: Shortcuts): Route {
|
|
69
69
|
const node = parse(value, shortcuts)
|
|
70
70
|
|
|
71
71
|
return createRoute(path, node)
|
|
@@ -106,10 +106,10 @@ function parseQuery (mapping: any): void {
|
|
|
106
106
|
return
|
|
107
107
|
|
|
108
108
|
if (typeof query.limit === 'number')
|
|
109
|
-
query.limit = expandRange(query.limit)
|
|
109
|
+
query.limit = expandRange(query.limit as number)
|
|
110
110
|
|
|
111
111
|
if (typeof query.omit === 'number')
|
|
112
|
-
query.omit = expandRange(query.omit)
|
|
112
|
+
query.omit = expandRange(query.omit as number)
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
function parseDirectives (mapping: Record<string, any>, shortcuts?: Shortcuts): Directive[] {
|
|
@@ -132,7 +132,7 @@ function parseDirectives (mapping: Record<string, any>, shortcuts?: Shortcuts):
|
|
|
132
132
|
|
|
133
133
|
function parseDirective (key: string, value: any, shortcuts?: Shortcuts): Directive | null {
|
|
134
134
|
if (shortcuts?.has(key) === true)
|
|
135
|
-
key = shortcuts.get(key)
|
|
135
|
+
key = shortcuts.get(key)! // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
136
136
|
|
|
137
137
|
const match = key.match(DIRECTIVE_RX)
|
|
138
138
|
|
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',
|