@toa.io/extensions.exposition 0.9.0-canary.2 → 0.20.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/context.toa.yaml +15 -0
- package/components/identity.bans/manifest.toa.yaml +18 -0
- package/components/identity.basic/events/principal.js +9 -0
- package/components/identity.basic/manifest.toa.yaml +50 -0
- package/components/identity.basic/source/authenticate.ts +29 -0
- package/components/identity.basic/source/create.ts +19 -0
- package/components/identity.basic/source/transit.ts +64 -0
- package/components/identity.basic/source/types.ts +42 -0
- package/components/identity.basic/tsconfig.json +9 -0
- package/components/identity.roles/manifest.toa.yaml +31 -0
- package/components/identity.roles/source/list.ts +7 -0
- package/components/identity.roles/source/principal.ts +20 -0
- package/components/identity.roles/tsconfig.json +9 -0
- package/components/identity.tokens/manifest.toa.yaml +39 -0
- package/components/identity.tokens/receivers/identity.bans.updated.js +3 -0
- package/components/identity.tokens/source/authenticate.test.ts +56 -0
- package/components/identity.tokens/source/authenticate.ts +38 -0
- package/components/identity.tokens/source/decrypt.test.ts +59 -0
- package/components/identity.tokens/source/decrypt.ts +25 -0
- package/components/identity.tokens/source/encrypt.test.ts +35 -0
- package/components/identity.tokens/source/encrypt.ts +25 -0
- package/components/identity.tokens/source/revoke.ts +5 -0
- package/components/identity.tokens/source/types.ts +48 -0
- package/components/identity.tokens/tsconfig.json +9 -0
- package/cucumber.js +9 -0
- package/documentation/.assets/ia3-dark.jpg +0 -0
- package/documentation/.assets/ia3-light.jpg +0 -0
- package/documentation/.assets/overview-dark.jpg +0 -0
- package/documentation/.assets/overview-light.jpg +0 -0
- package/documentation/.assets/role-scopes-dark.jpg +0 -0
- package/documentation/.assets/role-scopes-light.jpg +0 -0
- package/documentation/.assets/rtd-dark.jpg +0 -0
- package/documentation/.assets/rtd-light.jpg +0 -0
- package/documentation/access.md +256 -0
- package/documentation/components.md +276 -0
- package/documentation/identity.md +156 -0
- package/documentation/protocol.md +15 -0
- package/documentation/query.md +226 -0
- package/documentation/tree.md +169 -0
- package/features/access.feature +442 -0
- package/features/annotation.feature +28 -0
- package/features/body.feature +21 -0
- package/features/directives.feature +56 -0
- package/features/dynamic.feature +89 -0
- package/features/errors.feature +208 -0
- package/features/identity.basic.feature +235 -0
- package/features/identity.feature +61 -0
- package/features/identity.roles.feature +51 -0
- package/features/identity.tokens.feature +116 -0
- package/features/queries.feature +214 -0
- package/features/routes.feature +49 -0
- package/features/steps/Common.ts +10 -0
- package/features/steps/Components.ts +43 -0
- package/features/steps/Database.ts +58 -0
- package/features/steps/Gateway.ts +105 -0
- package/features/steps/HTTP.ts +71 -0
- package/features/steps/Parameters.ts +12 -0
- package/features/steps/Workspace.ts +40 -0
- package/features/steps/components/greeter/manifest.toa.yaml +9 -0
- package/features/steps/components/greeter/operations/greet.js +7 -0
- package/features/steps/components/pots/manifest.toa.yaml +20 -0
- package/features/steps/components/users/manifest.toa.yaml +11 -0
- package/features/steps/tsconfig.json +9 -0
- package/package.json +31 -17
- package/readme.md +181 -0
- package/schemas/annotation.cos.yaml +4 -0
- package/schemas/directive.cos.yaml +3 -0
- package/schemas/method.cos.yaml +9 -0
- package/schemas/node.cos.yaml +5 -0
- package/schemas/query.cos.yaml +16 -0
- package/schemas/querystring.cos.yaml +5 -0
- package/schemas/range.cos.yaml +2 -0
- package/schemas/route.cos.yaml +2 -0
- package/source/Annotation.ts +6 -0
- package/source/Branch.ts +8 -0
- package/source/Composition.ts +58 -0
- package/source/Context.ts +6 -0
- package/source/Directive.test.ts +91 -0
- package/source/Directive.ts +120 -0
- package/source/Endpoint.ts +57 -0
- package/source/Factory.ts +53 -0
- package/source/Gateway.ts +93 -0
- package/source/HTTP/Server.fixtures.ts +45 -0
- package/source/HTTP/Server.test.ts +221 -0
- package/source/HTTP/Server.ts +135 -0
- package/source/HTTP/exceptions.ts +77 -0
- package/source/HTTP/formats/index.ts +19 -0
- package/source/HTTP/formats/json.ts +13 -0
- package/source/HTTP/formats/msgpack.ts +10 -0
- package/source/HTTP/formats/text.ts +9 -0
- package/source/HTTP/formats/yaml.ts +14 -0
- package/source/HTTP/index.ts +3 -0
- package/source/HTTP/messages.test.ts +116 -0
- package/source/HTTP/messages.ts +77 -0
- package/source/Mapping.ts +48 -0
- package/source/Query.test.ts +37 -0
- package/source/Query.ts +105 -0
- package/source/RTD/Context.ts +16 -0
- package/source/RTD/Directives.ts +9 -0
- package/source/RTD/Endpoint.ts +11 -0
- package/source/RTD/Match.ts +16 -0
- package/source/RTD/Method.ts +24 -0
- package/source/RTD/Node.ts +85 -0
- package/source/RTD/Route.ts +58 -0
- package/source/RTD/Tree.ts +57 -0
- package/source/RTD/factory.ts +47 -0
- package/source/RTD/index.ts +8 -0
- package/source/RTD/segment.test.ts +32 -0
- package/source/RTD/segment.ts +25 -0
- package/source/RTD/syntax/index.ts +2 -0
- package/source/RTD/syntax/parse.test.ts +188 -0
- package/source/RTD/syntax/parse.ts +153 -0
- package/source/RTD/syntax/types.ts +48 -0
- package/source/Remotes.test.ts +41 -0
- package/source/Remotes.ts +20 -0
- package/source/Tenant.ts +38 -0
- package/source/deployment.ts +42 -0
- package/source/directives/auth/Anonymous.ts +14 -0
- package/source/directives/auth/Echo.ts +12 -0
- package/source/directives/auth/Family.ts +145 -0
- package/source/directives/auth/Id.ts +19 -0
- package/source/directives/auth/Incept.ts +42 -0
- package/source/directives/auth/Role.test.ts +62 -0
- package/source/directives/auth/Role.ts +56 -0
- package/source/directives/auth/Rule.ts +28 -0
- package/source/directives/auth/Scheme.ts +26 -0
- package/source/directives/auth/index.ts +3 -0
- package/source/directives/auth/schemes.ts +8 -0
- package/source/directives/auth/split.ts +15 -0
- package/source/directives/auth/types.ts +37 -0
- package/source/directives/dev/Family.ts +34 -0
- package/source/directives/dev/Stub.ts +14 -0
- package/source/directives/dev/index.ts +3 -0
- package/source/directives/dev/types.ts +5 -0
- package/source/directives/index.ts +5 -0
- package/source/discovery.ts +1 -0
- package/source/exceptions.ts +17 -0
- package/source/index.test.ts +9 -0
- package/source/index.ts +6 -0
- package/source/manifest.test.ts +57 -0
- package/source/manifest.ts +35 -0
- package/source/root.ts +38 -0
- package/source/schemas.ts +9 -0
- package/transpiled/Annotation.d.ts +6 -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 +44 -0
- package/transpiled/Endpoint.js.map +1 -0
- package/transpiled/Factory.d.ts +11 -0
- package/transpiled/Factory.js +67 -0
- package/transpiled/Factory.js.map +1 -0
- package/transpiled/Gateway.d.ts +19 -0
- package/transpiled/Gateway.js +90 -0
- package/transpiled/Gateway.js.map +1 -0
- package/transpiled/HTTP/Server.d.ts +22 -0
- package/transpiled/HTTP/Server.fixtures.d.ts +12 -0
- package/transpiled/HTTP/Server.fixtures.js +36 -0
- package/transpiled/HTTP/Server.fixtures.js.map +1 -0
- package/transpiled/HTTP/Server.js +111 -0
- package/transpiled/HTTP/Server.js.map +1 -0
- package/transpiled/HTTP/exceptions.d.ts +39 -0
- package/transpiled/HTTP/exceptions.js +78 -0
- package/transpiled/HTTP/exceptions.js.map +1 -0
- package/transpiled/HTTP/formats/index.d.ts +8 -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 +4 -0
- package/transpiled/HTTP/formats/json.js +15 -0
- package/transpiled/HTTP/formats/json.js.map +1 -0
- package/transpiled/HTTP/formats/msgpack.d.ts +4 -0
- package/transpiled/HTTP/formats/msgpack.js +36 -0
- package/transpiled/HTTP/formats/msgpack.js.map +1 -0
- package/transpiled/HTTP/formats/text.d.ts +4 -0
- package/transpiled/HTTP/formats/text.js +13 -0
- package/transpiled/HTTP/formats/text.js.map +1 -0
- package/transpiled/HTTP/formats/yaml.d.ts +4 -0
- package/transpiled/HTTP/formats/yaml.js +39 -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 +27 -0
- package/transpiled/HTTP/messages.js +49 -0
- package/transpiled/HTTP/messages.js.map +1 -0
- package/transpiled/Mapping.d.ts +8 -0
- package/transpiled/Mapping.js +35 -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 +48 -0
- package/transpiled/RTD/Route.js.map +1 -0
- package/transpiled/RTD/Tree.d.ts +14 -0
- package/transpiled/RTD/Tree.js +45 -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 +23 -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 +7 -0
- package/transpiled/Remotes.js +19 -0
- package/transpiled/Remotes.js.map +1 -0
- package/transpiled/Tenant.d.ts +12 -0
- package/transpiled/Tenant.js +30 -0
- package/transpiled/Tenant.js.map +1 -0
- package/transpiled/deployment.d.ts +3 -0
- package/transpiled/deployment.js +61 -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 +125 -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 +59 -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/dev/Family.d.ts +10 -0
- package/transpiled/directives/dev/Family.js +25 -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/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 +10 -0
- package/transpiled/directives/index.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 +30 -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
- package/tsconfig.json +12 -0
- package/src/.manifest/index.js +0 -7
- package/src/.manifest/normalize.js +0 -58
- package/src/.manifest/schema.yaml +0 -71
- package/src/.manifest/validate.js +0 -17
- package/src/constants.js +0 -3
- package/src/deployment.js +0 -23
- package/src/exposition.js +0 -68
- package/src/factory.js +0 -76
- package/src/index.js +0 -9
- package/src/manifest.js +0 -12
- package/src/query/criteria.js +0 -55
- package/src/query/enum.js +0 -35
- package/src/query/index.js +0 -17
- package/src/query/query.js +0 -60
- package/src/query/range.js +0 -28
- package/src/query/sort.js +0 -19
- package/src/remote.js +0 -88
- package/src/server.js +0 -83
- package/src/tenant.js +0 -29
- package/src/translate/etag.js +0 -14
- package/src/translate/index.js +0 -7
- package/src/translate/request.js +0 -68
- package/src/translate/response.js +0 -62
- package/src/tree.js +0 -109
- package/test/manifest.normalize.fixtures.js +0 -37
- package/test/manifest.normalize.test.js +0 -37
- package/test/manifest.validate.test.js +0 -40
- package/test/query.range.test.js +0 -18
- package/test/tree.fixtures.js +0 -21
- package/test/tree.test.js +0 -44
- package/types/annotations.d.ts +0 -10
- package/types/declarations.d.ts +0 -31
- package/types/exposition.d.ts +0 -13
- package/types/http.d.ts +0 -13
- package/types/query.d.ts +0 -16
- package/types/remote.d.ts +0 -19
- package/types/server.d.ts +0 -13
- package/types/tree.d.ts +0 -33
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Tenant } from './Tenant'
|
|
2
|
+
import { Gateway } from './Gateway'
|
|
3
|
+
import { Remotes } from './Remotes'
|
|
4
|
+
import { Tree, syntax } from './RTD'
|
|
5
|
+
import { Server } from './HTTP'
|
|
6
|
+
import { type Endpoint, EndpointFactory } from './Endpoint'
|
|
7
|
+
import * as directives from './directives'
|
|
8
|
+
import { type Directives, DirectivesFactory, type Family } from './Directive'
|
|
9
|
+
import { Composition } from './Composition'
|
|
10
|
+
import * as root from './root'
|
|
11
|
+
import type { Connector, Locator, extensions } from '@toa.io/core'
|
|
12
|
+
|
|
13
|
+
export class Factory implements extensions.Factory {
|
|
14
|
+
private readonly boot: Bootloader
|
|
15
|
+
private readonly families: Family[]
|
|
16
|
+
|
|
17
|
+
public constructor (boot: Bootloader) {
|
|
18
|
+
this.boot = boot
|
|
19
|
+
this.families = directives.families
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public tenant (locator: Locator, manifest: syntax.Node): Connector {
|
|
23
|
+
const broadcast = this.boot.bindings.broadcast(CHANNEL, locator.id)
|
|
24
|
+
|
|
25
|
+
return new Tenant(broadcast, locator, manifest)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public service (): Connector | null {
|
|
29
|
+
return this.gateway()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private gateway (): Gateway {
|
|
33
|
+
const broadcast = this.boot.bindings.broadcast(CHANNEL)
|
|
34
|
+
const server = Server.create({ methods: syntax.verbs })
|
|
35
|
+
const remotes = new Remotes(this.boot)
|
|
36
|
+
const methods = new EndpointFactory(remotes)
|
|
37
|
+
const directives = new DirectivesFactory(this.families, remotes)
|
|
38
|
+
const node = root.resolve()
|
|
39
|
+
const tree = new Tree<Endpoint, Directives>(node, methods, directives)
|
|
40
|
+
|
|
41
|
+
const composition = new Composition(this.boot)
|
|
42
|
+
const gateway = new Gateway(broadcast, server, tree)
|
|
43
|
+
|
|
44
|
+
gateway.depends(composition)
|
|
45
|
+
|
|
46
|
+
return gateway
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const CHANNEL = 'exposition'
|
|
51
|
+
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
|
53
|
+
export type Bootloader = typeof import('@toa.io/boot')
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { type bindings, Connector } from '@toa.io/core'
|
|
2
|
+
import { Nope, type Nopeable } from 'nopeable'
|
|
3
|
+
import * as http from './HTTP'
|
|
4
|
+
import { rethrow } from './exceptions'
|
|
5
|
+
import { type Method, type Parameter, type Tree } from './RTD'
|
|
6
|
+
import { type Label } from './discovery'
|
|
7
|
+
import { type Branch } from './Branch'
|
|
8
|
+
import { type Endpoint } from './Endpoint'
|
|
9
|
+
import { type Directives } from './Directive'
|
|
10
|
+
|
|
11
|
+
export class Gateway extends Connector {
|
|
12
|
+
private readonly broadcast: Broadcast
|
|
13
|
+
private readonly tree: Tree<Endpoint, Directives>
|
|
14
|
+
|
|
15
|
+
public constructor (broadcast: Broadcast, server: http.Server, tree: Tree<Endpoint, Directives>) {
|
|
16
|
+
super()
|
|
17
|
+
|
|
18
|
+
this.broadcast = broadcast
|
|
19
|
+
this.tree = tree
|
|
20
|
+
|
|
21
|
+
this.depends(broadcast)
|
|
22
|
+
this.depends(server)
|
|
23
|
+
|
|
24
|
+
server.attach(this.process.bind(this))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
protected override async open (): Promise<void> {
|
|
28
|
+
await this.discover()
|
|
29
|
+
|
|
30
|
+
console.info('Gateway has started and is awaiting resource branches.')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
protected override dispose (): void {
|
|
34
|
+
console.info('Gateway is closed.')
|
|
35
|
+
}
|
|
36
|
+
|
|
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
|
+
const match = this.tree.match(request.path)
|
|
42
|
+
|
|
43
|
+
if (match === null)
|
|
44
|
+
throw new http.NotFound()
|
|
45
|
+
|
|
46
|
+
const { node, parameters } = match
|
|
47
|
+
|
|
48
|
+
if (!(request.method in node.methods))
|
|
49
|
+
throw new http.MethodNotAllowed()
|
|
50
|
+
|
|
51
|
+
const method = node.methods[request.method]
|
|
52
|
+
const interruption = await method.directives.preflight(request, parameters)
|
|
53
|
+
const response = interruption ?? await this.call(method, request, parameters)
|
|
54
|
+
|
|
55
|
+
await method.directives.settle(request, response)
|
|
56
|
+
|
|
57
|
+
return response
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private async call
|
|
61
|
+
(method: Method<Endpoint, Directives>, request: http.IncomingMessage, parameters: Parameter[]):
|
|
62
|
+
Promise<http.OutgoingMessage> {
|
|
63
|
+
if (method.endpoint === null)
|
|
64
|
+
throw new http.MethodNotAllowed()
|
|
65
|
+
|
|
66
|
+
const reply = await method.endpoint
|
|
67
|
+
.call(request.body, request.query, parameters)
|
|
68
|
+
.catch(rethrow) as Nopeable<unknown>
|
|
69
|
+
|
|
70
|
+
if (reply instanceof Nope)
|
|
71
|
+
throw new http.Conflict(reply)
|
|
72
|
+
|
|
73
|
+
return { body: reply }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private async discover (): Promise<void> {
|
|
77
|
+
await this.broadcast.receive<Branch>('expose', this.merge.bind(this))
|
|
78
|
+
await this.broadcast.transmit<null>('ping', null)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private merge (branch: Branch): void {
|
|
82
|
+
try {
|
|
83
|
+
this.tree.merge(branch.node, branch)
|
|
84
|
+
|
|
85
|
+
console.info('Resource branch of ' +
|
|
86
|
+
`'${branch.namespace}.${branch.component}' has been merged.`)
|
|
87
|
+
} catch (exception) {
|
|
88
|
+
console.error(exception)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
type Broadcast = bindings.Broadcast<Label>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Buffer } from 'buffer'
|
|
2
|
+
import { Readable } from 'stream'
|
|
3
|
+
import type { IncomingMessage } from './messages'
|
|
4
|
+
import type * as http from 'node:http'
|
|
5
|
+
import type { NextFunction, Response, Express, Request } from 'express'
|
|
6
|
+
import type { CorsOptions } from 'cors'
|
|
7
|
+
|
|
8
|
+
const server = {
|
|
9
|
+
close: jest.fn()
|
|
10
|
+
} as unknown as jest.Mock<http.Server>
|
|
11
|
+
|
|
12
|
+
const app = {
|
|
13
|
+
enable: jest.fn(),
|
|
14
|
+
disable: jest.fn(),
|
|
15
|
+
use: jest.fn(),
|
|
16
|
+
listen: jest.fn(() => server)
|
|
17
|
+
} as unknown as jest.Mock<Express>
|
|
18
|
+
|
|
19
|
+
export function createRequest (req: Partial<Request> = {}, content: string | Buffer = ''):
|
|
20
|
+
jest.MockedObject<Request> {
|
|
21
|
+
const buffer = Buffer.isBuffer(content) ? content : Buffer.from(content)
|
|
22
|
+
const stream = Readable.from(buffer)
|
|
23
|
+
|
|
24
|
+
Object.assign(stream, { headers: {} }, req)
|
|
25
|
+
|
|
26
|
+
return stream as unknown as jest.MockedObject<Request>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function createIncomingMessage (path: string, method: string = 'GET'): IncomingMessage {
|
|
30
|
+
return { method, path, headers: {}, body: undefined, query: {} }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const res = {
|
|
34
|
+
status: jest.fn(() => res),
|
|
35
|
+
sendStatus: jest.fn(() => res),
|
|
36
|
+
set: jest.fn(() => res),
|
|
37
|
+
send: jest.fn(() => res),
|
|
38
|
+
end: jest.fn(() => res)
|
|
39
|
+
} as unknown as jest.MockedObject<Response>
|
|
40
|
+
|
|
41
|
+
export const next = jest.fn() as unknown as NextFunction
|
|
42
|
+
|
|
43
|
+
export const express = jest.fn(() => app)
|
|
44
|
+
|
|
45
|
+
export const cors = jest.fn((_: CorsOptions) => () => undefined)
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { Connector } from '@toa.io/core'
|
|
2
|
+
import { immediate } from '@toa.io/generic'
|
|
3
|
+
import { generate } from 'randomstring'
|
|
4
|
+
import { type Processing, Server } from './Server'
|
|
5
|
+
import { type OutgoingMessage } from './messages'
|
|
6
|
+
import { express, cors, createRequest, res, next } from './Server.fixtures'
|
|
7
|
+
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
|
+
|
|
15
|
+
let server: Server
|
|
16
|
+
let app: jest.MockedObject<Express>
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
jest.clearAllMocks()
|
|
20
|
+
|
|
21
|
+
server = Server.create()
|
|
22
|
+
app = express.mock.results[0]?.value
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('should instance of connector', async () => {
|
|
26
|
+
expect(server).toBeInstanceOf(Connector)
|
|
27
|
+
})
|
|
28
|
+
|
|
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
|
+
it('should start HTTP server', async () => {
|
|
43
|
+
const stared = server.connect()
|
|
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()
|
|
52
|
+
|
|
53
|
+
await stared
|
|
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
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('should register request handler', async () => {
|
|
78
|
+
const process = jest.fn(async () => ({})) as unknown as Processing
|
|
79
|
+
const req = createRequest()
|
|
80
|
+
|
|
81
|
+
server.attach(process)
|
|
82
|
+
|
|
83
|
+
await use(req)
|
|
84
|
+
|
|
85
|
+
expect(process).toHaveBeenCalled()
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('should send 501 on unknown method', async () => {
|
|
89
|
+
const req = createRequest({ method: generate() })
|
|
90
|
+
|
|
91
|
+
await use(req)
|
|
92
|
+
|
|
93
|
+
expect(res.sendStatus).toHaveBeenCalledWith(501)
|
|
94
|
+
})
|
|
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
|
+
describe('result', () => {
|
|
118
|
+
it('should send status code 200 if the result has a value', async () => {
|
|
119
|
+
const process = async (): Promise<OutgoingMessage> => ({ headers: {}, body: generate() })
|
|
120
|
+
const req = createRequest()
|
|
121
|
+
|
|
122
|
+
server.attach(process)
|
|
123
|
+
await use(req)
|
|
124
|
+
|
|
125
|
+
expect(res.status).toHaveBeenCalledWith(200)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('should send status code 204 if the result has no value', async () => {
|
|
129
|
+
const process = async (): Promise<OutgoingMessage> => ({ headers: {} })
|
|
130
|
+
const req = createRequest()
|
|
131
|
+
|
|
132
|
+
server.attach(process)
|
|
133
|
+
await use(req)
|
|
134
|
+
|
|
135
|
+
expect(res.status).toHaveBeenCalledWith(204)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('should send result', async () => {
|
|
139
|
+
const body = { [generate()]: generate() }
|
|
140
|
+
const json = JSON.stringify(body)
|
|
141
|
+
const buf = Buffer.from(json)
|
|
142
|
+
const process = async (): Promise<OutgoingMessage> => ({ headers: {}, body })
|
|
143
|
+
const req = createRequest({ headers: { accept: 'application/json' } })
|
|
144
|
+
|
|
145
|
+
server.attach(process)
|
|
146
|
+
await use(req)
|
|
147
|
+
|
|
148
|
+
expect(res.send).toHaveBeenCalledWith(buf)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('should return 500 on exception', async () => {
|
|
152
|
+
const process = async (): Promise<OutgoingMessage> => {
|
|
153
|
+
throw new Error('Bad')
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const req = createRequest()
|
|
157
|
+
|
|
158
|
+
server.attach(process)
|
|
159
|
+
await use(req)
|
|
160
|
+
|
|
161
|
+
expect(res.status).toHaveBeenCalledWith(500)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('should output exception message if debug is enabled', async () => {
|
|
165
|
+
jest.clearAllMocks()
|
|
166
|
+
|
|
167
|
+
server = Server.create({ debug: true })
|
|
168
|
+
app = express.mock.results[0]?.value
|
|
169
|
+
|
|
170
|
+
const message = generate()
|
|
171
|
+
const req = createRequest()
|
|
172
|
+
|
|
173
|
+
const process = async (): Promise<OutgoingMessage> => {
|
|
174
|
+
throw new Error(message)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
server.attach(process)
|
|
178
|
+
await use(req)
|
|
179
|
+
|
|
180
|
+
expect(res.status).toHaveBeenCalledWith(500)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('should send client error', async () => {
|
|
184
|
+
const req = createRequest()
|
|
185
|
+
const message = generate()
|
|
186
|
+
|
|
187
|
+
const process = async (): Promise<OutgoingMessage> => {
|
|
188
|
+
throw new BadRequest(message)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
server.attach(process)
|
|
192
|
+
await use(req)
|
|
193
|
+
|
|
194
|
+
expect(res.status).toHaveBeenCalledWith(400)
|
|
195
|
+
})
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
describe('options', () => {
|
|
199
|
+
it('should send 501 on unspecified method', async () => {
|
|
200
|
+
jest.clearAllMocks()
|
|
201
|
+
|
|
202
|
+
server = Server.create({ methods: new Set(['COPY']) })
|
|
203
|
+
app = express.mock.results[0]?.value
|
|
204
|
+
|
|
205
|
+
const req = createRequest({ method: 'GET' })
|
|
206
|
+
|
|
207
|
+
await use(req)
|
|
208
|
+
|
|
209
|
+
expect(res.sendStatus).toHaveBeenCalledWith(501)
|
|
210
|
+
})
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
async function use (req: Request): Promise<void> {
|
|
214
|
+
for (const call of app.use.mock.calls) {
|
|
215
|
+
const usage = call[0] as unknown as RequestHandler
|
|
216
|
+
|
|
217
|
+
usage(req, res, next)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
await immediate()
|
|
221
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import express from 'express'
|
|
2
|
+
import cors from 'cors'
|
|
3
|
+
import { Connector } from '@toa.io/core'
|
|
4
|
+
import { promex } from '@toa.io/generic'
|
|
5
|
+
import { read, write, type IncomingMessage, type OutgoingMessage } from './messages'
|
|
6
|
+
import { ClientError, Exception } from './exceptions'
|
|
7
|
+
import type * as http from 'node:http'
|
|
8
|
+
import type { Express, Request, Response, NextFunction } from 'express'
|
|
9
|
+
|
|
10
|
+
export class Server extends Connector {
|
|
11
|
+
private readonly debug: boolean
|
|
12
|
+
private readonly app: Express
|
|
13
|
+
private server?: http.Server
|
|
14
|
+
|
|
15
|
+
private constructor (app: Express, debug: boolean) {
|
|
16
|
+
super()
|
|
17
|
+
|
|
18
|
+
this.app = app
|
|
19
|
+
this.debug = debug
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public static create (options: Partial<Properties> = {}): Server {
|
|
23
|
+
const properties: Properties = Object.assign({}, defaults(), options)
|
|
24
|
+
|
|
25
|
+
const app = express()
|
|
26
|
+
|
|
27
|
+
app.disable('x-powered-by')
|
|
28
|
+
app.use(cors({ allowedHeaders: ['content-type'] }))
|
|
29
|
+
app.use(supportedMethods(properties.methods))
|
|
30
|
+
|
|
31
|
+
return new Server(app, properties.debug)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public attach (process: Processing): void {
|
|
35
|
+
this.app.use((request: Request, response: Response): void => {
|
|
36
|
+
this.read(request)
|
|
37
|
+
.then(process)
|
|
38
|
+
.then(this.success(request, response))
|
|
39
|
+
.catch(this.fail(request, response))
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
protected override async open (): Promise<void> {
|
|
44
|
+
const listening = promex()
|
|
45
|
+
|
|
46
|
+
this.server = this.app.listen(8000, listening.callback)
|
|
47
|
+
|
|
48
|
+
await listening
|
|
49
|
+
|
|
50
|
+
console.info('HTTP Server is listening.')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
protected override async close (): Promise<void> {
|
|
54
|
+
const stopped = promex()
|
|
55
|
+
|
|
56
|
+
this.server?.close(stopped.callback)
|
|
57
|
+
|
|
58
|
+
await stopped
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
protected override dispose (): void {
|
|
62
|
+
console.info('HTTP Server has been stopped.')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private async read (request: Request): Promise<IncomingMessage> {
|
|
66
|
+
const { method, path, headers, query } = request
|
|
67
|
+
const body = await read(request)
|
|
68
|
+
|
|
69
|
+
return { method, path, headers, query, body }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private success (request: Request, response: Response) {
|
|
73
|
+
return (message: OutgoingMessage) => {
|
|
74
|
+
let status = message.status
|
|
75
|
+
|
|
76
|
+
if (status === undefined)
|
|
77
|
+
if (message.body === null) status = 404
|
|
78
|
+
else if (request.method === 'POST') status = 201
|
|
79
|
+
else if (message.body === undefined) status = 204
|
|
80
|
+
else status = 200
|
|
81
|
+
|
|
82
|
+
response
|
|
83
|
+
.status(status)
|
|
84
|
+
.set(message.headers)
|
|
85
|
+
|
|
86
|
+
if (message.body !== undefined && message.body !== null)
|
|
87
|
+
write(request, response, message.body)
|
|
88
|
+
else
|
|
89
|
+
response.end()
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private fail (request: Request, response: Response) {
|
|
94
|
+
return (exception: Error) => {
|
|
95
|
+
let status = 500
|
|
96
|
+
|
|
97
|
+
if (exception instanceof Exception)
|
|
98
|
+
status = exception.status
|
|
99
|
+
|
|
100
|
+
const outputAllowed = exception instanceof ClientError || this.debug
|
|
101
|
+
|
|
102
|
+
response.status(status)
|
|
103
|
+
|
|
104
|
+
if (outputAllowed) {
|
|
105
|
+
const body = exception instanceof Exception
|
|
106
|
+
? exception.body
|
|
107
|
+
: (exception.stack ?? exception.message)
|
|
108
|
+
|
|
109
|
+
write(request, response, body)
|
|
110
|
+
} else
|
|
111
|
+
response.end()
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function supportedMethods (methods: Set<string>) {
|
|
117
|
+
return (req: Request, res: Response, next: NextFunction): void => {
|
|
118
|
+
if (methods.has(req.method)) next()
|
|
119
|
+
else res.sendStatus(501)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
interface Properties {
|
|
124
|
+
methods: Set<string>
|
|
125
|
+
debug: boolean
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function defaults (): Properties {
|
|
129
|
+
return {
|
|
130
|
+
methods: new Set<string>(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']),
|
|
131
|
+
debug: process.env.TOA_DEV === '1'
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export type Processing = (input: IncomingMessage) => Promise<OutgoingMessage>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { types } from './formats'
|
|
2
|
+
|
|
3
|
+
export class Exception extends Error {
|
|
4
|
+
public readonly status: number
|
|
5
|
+
public readonly body?: any
|
|
6
|
+
|
|
7
|
+
protected constructor (status: number, body?: any) {
|
|
8
|
+
super()
|
|
9
|
+
this.status = status
|
|
10
|
+
this.body = body
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class ClientError extends Exception {
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class BadRequest extends ClientError {
|
|
18
|
+
public constructor (body?: any) {
|
|
19
|
+
super(400, body)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class Unauthorized extends ClientError {
|
|
24
|
+
public constructor (body?: any) {
|
|
25
|
+
super(401, body)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class Forbidden extends ClientError {
|
|
30
|
+
public constructor (body?: any) {
|
|
31
|
+
super(403, body)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class NotFound extends ClientError {
|
|
36
|
+
public constructor (body?: any) {
|
|
37
|
+
super(404, body)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class Conflict extends ClientError {
|
|
42
|
+
public constructor (body: any) {
|
|
43
|
+
super(409, body)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class MethodNotAllowed extends ClientError {
|
|
48
|
+
public constructor () {
|
|
49
|
+
super(405)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
class MediaTypeException extends ClientError {
|
|
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 {
|
|
62
|
+
public constructor () {
|
|
63
|
+
super(406)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export class UnsupportedMediaType extends MediaTypeException {
|
|
68
|
+
public constructor () {
|
|
69
|
+
super(415)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export class PreconditionFailed extends ClientError {
|
|
74
|
+
public constructor () {
|
|
75
|
+
super(412)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Buffer } from 'node:buffer'
|
|
2
|
+
import * as json from './json'
|
|
3
|
+
import * as yaml from './yaml'
|
|
4
|
+
import * as msgpack from './msgpack'
|
|
5
|
+
import * as text from './text'
|
|
6
|
+
|
|
7
|
+
export const formats: Record<string, Format> = {
|
|
8
|
+
'application/yaml': yaml,
|
|
9
|
+
'application/msgpack': msgpack,
|
|
10
|
+
'application/json': json,
|
|
11
|
+
'text/plain': text
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const types = Object.keys(formats)
|
|
15
|
+
|
|
16
|
+
export interface Format {
|
|
17
|
+
encode: (value: any) => Buffer
|
|
18
|
+
decode: (buffer: Buffer) => any
|
|
19
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer'
|
|
2
|
+
|
|
3
|
+
export function decode (buffer: Buffer): any {
|
|
4
|
+
const text = buffer.toString()
|
|
5
|
+
|
|
6
|
+
return JSON.parse(text)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function encode (value: any): Buffer {
|
|
10
|
+
const text = JSON.stringify(value)
|
|
11
|
+
|
|
12
|
+
return Buffer.from(text)
|
|
13
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer'
|
|
2
|
+
import * as yaml from 'js-yaml'
|
|
3
|
+
|
|
4
|
+
export function decode (buffer: Buffer): any {
|
|
5
|
+
const text = buffer.toString()
|
|
6
|
+
|
|
7
|
+
return yaml.load(text)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function encode (value: any): Buffer {
|
|
11
|
+
const text = yaml.dump(value)
|
|
12
|
+
|
|
13
|
+
return Buffer.from(text)
|
|
14
|
+
}
|