@toa.io/extensions.exposition 0.2.1-dev.3
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/package.json +36 -0
- package/src/.manifest/index.js +7 -0
- package/src/.manifest/normalize.js +58 -0
- package/src/.manifest/schema.yaml +69 -0
- package/src/.manifest/validate.js +17 -0
- package/src/constants.js +3 -0
- package/src/deployment.js +23 -0
- package/src/exposition.js +68 -0
- package/src/factory.js +76 -0
- package/src/index.js +9 -0
- package/src/manifest.js +12 -0
- package/src/query/criteria.js +55 -0
- package/src/query/enum.js +35 -0
- package/src/query/index.js +17 -0
- package/src/query/query.js +60 -0
- package/src/query/range.js +28 -0
- package/src/query/sort.js +19 -0
- package/src/remote.js +88 -0
- package/src/server.js +83 -0
- package/src/tenant.js +28 -0
- package/src/translate/etag.js +14 -0
- package/src/translate/index.js +7 -0
- package/src/translate/request.js +68 -0
- package/src/translate/response.js +62 -0
- package/src/tree.js +107 -0
- package/test/manifest.normalize.fixtures.js +37 -0
- package/test/manifest.normalize.test.js +37 -0
- package/test/manifest.validate.test.js +25 -0
- package/test/query.range.test.js +18 -0
- package/test/tree.fixtures.js +21 -0
- package/test/tree.test.js +44 -0
- package/types/annotations.d.ts +10 -0
- package/types/declarations.d.ts +31 -0
- package/types/exposition.d.ts +13 -0
- package/types/http.d.ts +13 -0
- package/types/query.d.ts +16 -0
- package/types/remote.d.ts +19 -0
- package/types/server.d.ts +13 -0
- package/types/tree.d.ts +33 -0
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@toa.io/extensions.exposition",
|
|
3
|
+
"version": "0.2.1-dev.3",
|
|
4
|
+
"description": "Toa Resources Exposition",
|
|
5
|
+
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
6
|
+
"homepage": "https://github.com/toa-io/toa#readme",
|
|
7
|
+
"main": "src/index.js",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/toa-io/toa.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/toa-io/toa/issues"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"test": "echo \"Error: run tests from root\" && exit 1"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/express": "4.17.13"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@toa.io/bindings.amqp": "*",
|
|
26
|
+
"@toa.io/console": "*",
|
|
27
|
+
"@toa.io/core": "*",
|
|
28
|
+
"@toa.io/generic": "*",
|
|
29
|
+
"@toa.io/schema": "*",
|
|
30
|
+
"@toa.io/yaml": "*",
|
|
31
|
+
"cors": "2.8.5",
|
|
32
|
+
"express": "4.18.1",
|
|
33
|
+
"path-to-regexp": "6.2.0"
|
|
34
|
+
},
|
|
35
|
+
"gitHead": "2be07592325b2e4dc823e81d882a4e50bf50de24"
|
|
36
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @returns {toa.extensions.exposition.declarations.Node}
|
|
5
|
+
*/
|
|
6
|
+
const normalize = (node, manifest) => {
|
|
7
|
+
if (node instanceof Array) node = { operations: node }
|
|
8
|
+
|
|
9
|
+
node.operations = operations(node.operations, manifest)
|
|
10
|
+
node.query = query(node.query)
|
|
11
|
+
|
|
12
|
+
for (const [key, value] of Object.entries(node)) {
|
|
13
|
+
if (key.substring(0, 1) === '/') node[key] = normalize(value, manifest)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return node
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @returns {toa.extensions.exposition.declarations.Operation[] | undefined}
|
|
21
|
+
*/
|
|
22
|
+
const operations = (operations, manifest) => {
|
|
23
|
+
if (operations === undefined) return
|
|
24
|
+
|
|
25
|
+
return operations.map((operation) => {
|
|
26
|
+
if (typeof operation === 'object') operation = operation.operation
|
|
27
|
+
|
|
28
|
+
if (manifest.operations[operation] === undefined) {
|
|
29
|
+
throw new Error(`Resource references undefined operation '${operation}'`)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const { type, scope, query } = manifest.operations[operation]
|
|
33
|
+
const normal = { operation, type, scope }
|
|
34
|
+
|
|
35
|
+
if (query !== undefined) normal.query = query
|
|
36
|
+
|
|
37
|
+
return normal
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @returns {toa.extensions.exposition.declarations.Query | undefined}
|
|
43
|
+
*/
|
|
44
|
+
const query = (query) => {
|
|
45
|
+
if (query === undefined) return
|
|
46
|
+
|
|
47
|
+
if (query.omit !== undefined && typeof query.omit !== 'object') {
|
|
48
|
+
query.omit = { value: query.omit, range: [] }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (query.limit !== undefined && typeof query.limit !== 'object') {
|
|
52
|
+
query.limit = { value: query.limit, range: [] }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return query
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
exports.normalize = normalize
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
$schema: https://json-schema.org/draft/2019-09/schema
|
|
2
|
+
$id: https://schemas.toa.io/0.0.0/rtd
|
|
3
|
+
|
|
4
|
+
definitions:
|
|
5
|
+
resource:
|
|
6
|
+
patternProperties:
|
|
7
|
+
^(\/([^\/#\?]+)?)+\/?$:
|
|
8
|
+
$ref: "#/definitions/resource"
|
|
9
|
+
properties:
|
|
10
|
+
query:
|
|
11
|
+
type: object
|
|
12
|
+
properties:
|
|
13
|
+
criteria:
|
|
14
|
+
nullable: true
|
|
15
|
+
type: string
|
|
16
|
+
minLength: 1
|
|
17
|
+
sort:
|
|
18
|
+
type: array
|
|
19
|
+
uniqueItems: true
|
|
20
|
+
minItems: 1
|
|
21
|
+
items:
|
|
22
|
+
type: string
|
|
23
|
+
projection:
|
|
24
|
+
type: array
|
|
25
|
+
uniqueItems: true
|
|
26
|
+
minItems: 1
|
|
27
|
+
items:
|
|
28
|
+
type: string
|
|
29
|
+
omit:
|
|
30
|
+
$ref: "#/definitions/range"
|
|
31
|
+
limit:
|
|
32
|
+
$ref: "#/definitions/range"
|
|
33
|
+
additionalProperties: false
|
|
34
|
+
operations:
|
|
35
|
+
type: array
|
|
36
|
+
uniqueItems: true
|
|
37
|
+
minItems: 1
|
|
38
|
+
items:
|
|
39
|
+
type: object
|
|
40
|
+
properties:
|
|
41
|
+
operation:
|
|
42
|
+
$ref: definitions#/definitions/token
|
|
43
|
+
type:
|
|
44
|
+
enum: [transition, observation, assignment]
|
|
45
|
+
scope:
|
|
46
|
+
type: string
|
|
47
|
+
enum: [object, objects, changeset, none]
|
|
48
|
+
query:
|
|
49
|
+
type: boolean
|
|
50
|
+
additionalProperties: false
|
|
51
|
+
additionalProperties: false
|
|
52
|
+
range:
|
|
53
|
+
type: object
|
|
54
|
+
properties:
|
|
55
|
+
value:
|
|
56
|
+
type: integer
|
|
57
|
+
minimum: 0
|
|
58
|
+
range:
|
|
59
|
+
type: array
|
|
60
|
+
uniqueItems: true
|
|
61
|
+
minItems: 0
|
|
62
|
+
items:
|
|
63
|
+
$ref: "#/definitions/range/properties/value"
|
|
64
|
+
additionalProperties: false
|
|
65
|
+
required: [range]
|
|
66
|
+
|
|
67
|
+
$ref: "#/definitions/resource"
|
|
68
|
+
not:
|
|
69
|
+
required: [operations] # root doesn't have path
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const path = require('path')
|
|
4
|
+
|
|
5
|
+
const { Schema } = require('@toa.io/schema')
|
|
6
|
+
const { load } = require('@toa.io/yaml')
|
|
7
|
+
|
|
8
|
+
const schema = load.sync(path.resolve(__dirname, 'schema.yaml'))
|
|
9
|
+
const validator = new Schema(schema)
|
|
10
|
+
|
|
11
|
+
const validate = (declaration) => {
|
|
12
|
+
const error = validator.fit(declaration)
|
|
13
|
+
|
|
14
|
+
if (error) throw new Error(error.message)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
exports.validate = validate
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { PORT } = require('./constants')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {toa.norm.context.dependencies.Instance[]} components
|
|
7
|
+
* @param {toa.extensions.exposition.Annotations} annotations
|
|
8
|
+
* @type {toa.deployment.dependency.Constructor}
|
|
9
|
+
*/
|
|
10
|
+
const deployment = (components, annotations) => {
|
|
11
|
+
const group = 'exposition'
|
|
12
|
+
const name = 'resources'
|
|
13
|
+
const version = require('../package.json').version
|
|
14
|
+
const port = PORT
|
|
15
|
+
const ingress = annotations
|
|
16
|
+
|
|
17
|
+
/** @type {toa.deployment.dependency.Service} */
|
|
18
|
+
const exposition = { group, name, version, port, ingress }
|
|
19
|
+
|
|
20
|
+
return { services: [exposition] }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
exports.deployment = deployment
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { Connector } = require('@toa.io/core')
|
|
4
|
+
const { console } = require('@toa.io/console')
|
|
5
|
+
|
|
6
|
+
class Exposition extends Connector {
|
|
7
|
+
/** @type {toa.core.bindings.Broadcaster} */
|
|
8
|
+
#broadcast
|
|
9
|
+
|
|
10
|
+
/** @type {toa.extensions.exposition.remotes.Factory} */
|
|
11
|
+
#remote
|
|
12
|
+
|
|
13
|
+
/** @type {toa.extensions.exposition.exposition.Remotes} */
|
|
14
|
+
#remotes = {}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {toa.core.bindings.Broadcaster} broadcast
|
|
18
|
+
* @param {toa.extensions.exposition.remotes.Factory} connect
|
|
19
|
+
*/
|
|
20
|
+
constructor (broadcast, connect) {
|
|
21
|
+
super()
|
|
22
|
+
|
|
23
|
+
this.#broadcast = broadcast
|
|
24
|
+
this.#remote = connect
|
|
25
|
+
|
|
26
|
+
this.depends(broadcast)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** @override */
|
|
30
|
+
async connection () {
|
|
31
|
+
await this.#broadcast.receive('expose', this.#expose.bind(this))
|
|
32
|
+
this.#broadcast.send('ping', {}).then()
|
|
33
|
+
|
|
34
|
+
console.info(this.constructor.name + ' started')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {toa.extensions.exposition.declarations.Exposition} declaration
|
|
39
|
+
* @returns {Promise<void>}
|
|
40
|
+
*/
|
|
41
|
+
async #expose (declaration) {
|
|
42
|
+
const { namespace, name, resources } = declaration
|
|
43
|
+
const key = namespace + '/' + name
|
|
44
|
+
|
|
45
|
+
if (this.#remotes[key] === undefined) this.#remotes[key] = this.#connect(namespace, name)
|
|
46
|
+
|
|
47
|
+
const remote = await this.#remotes[key]
|
|
48
|
+
|
|
49
|
+
remote.update(resources)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {string} namespace
|
|
54
|
+
* @param {string} name
|
|
55
|
+
* @returns {Promise<Remote>}
|
|
56
|
+
*/
|
|
57
|
+
async #connect (namespace, name) {
|
|
58
|
+
const remote = await this.#remote(namespace, name)
|
|
59
|
+
|
|
60
|
+
await remote.connect()
|
|
61
|
+
|
|
62
|
+
this.depends(remote)
|
|
63
|
+
|
|
64
|
+
return remote
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
exports.Exposition = Exposition
|
package/src/factory.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { Locator } = require('@toa.io/core')
|
|
4
|
+
const { remap } = require('@toa.io/generic')
|
|
5
|
+
|
|
6
|
+
const { Tenant } = require('./tenant')
|
|
7
|
+
const { Exposition } = require('./exposition')
|
|
8
|
+
const { Server } = require('./server')
|
|
9
|
+
const { Remote } = require('./remote')
|
|
10
|
+
const { Tree } = require('./tree')
|
|
11
|
+
const { Query, constraints } = require('./query')
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @implements {toa.core.extensions.Factory}
|
|
15
|
+
*/
|
|
16
|
+
class Factory {
|
|
17
|
+
#boot
|
|
18
|
+
|
|
19
|
+
/** @type {toa.extensions.exposition.Server} */
|
|
20
|
+
#server
|
|
21
|
+
|
|
22
|
+
constructor (boot) {
|
|
23
|
+
this.#boot = boot
|
|
24
|
+
this.#server = new Server()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
tenant (locator, declaration) {
|
|
28
|
+
const broadcast = this.#boot.bindings.broadcast(BINDING, GROUP, locator.id)
|
|
29
|
+
|
|
30
|
+
return new Tenant(broadcast, locator, declaration)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
service (name) {
|
|
34
|
+
if (name === undefined || name === 'default' || name === 'resources') return this.#expose()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
#expose () {
|
|
38
|
+
const broadcast = this.#boot.bindings.broadcast(BINDING, GROUP)
|
|
39
|
+
const connect = this.#connect.bind(this)
|
|
40
|
+
const exposition = new Exposition(broadcast, connect)
|
|
41
|
+
|
|
42
|
+
exposition.depends(this.#server)
|
|
43
|
+
|
|
44
|
+
return exposition
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {string} namespace
|
|
49
|
+
* @param {string} name
|
|
50
|
+
* @returns {Promise<toa.extensions.exposition.Remote>}
|
|
51
|
+
*/
|
|
52
|
+
async #connect (namespace, name) {
|
|
53
|
+
const locator = new Locator(name, namespace)
|
|
54
|
+
const remote = await this.#boot.remote(locator)
|
|
55
|
+
const query = this.#query.bind(this)
|
|
56
|
+
const tree = new Tree(query)
|
|
57
|
+
|
|
58
|
+
return new Remote(this.#server, remote, tree)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {toa.extensions.exposition.declarations.Node | any} node
|
|
63
|
+
* @returns {toa.extensions.exposition.Query}
|
|
64
|
+
*/
|
|
65
|
+
#query (node) {
|
|
66
|
+
const query = Query.merge(node)
|
|
67
|
+
const properties = remap(query, (value, key) => new constraints[key](value))
|
|
68
|
+
|
|
69
|
+
return new Query(properties)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const BINDING = '@toa.io/bindings.amqp'
|
|
74
|
+
const GROUP = 'exposition'
|
|
75
|
+
|
|
76
|
+
exports.Factory = Factory
|
package/src/index.js
ADDED
package/src/manifest.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { exceptions: { RequestConflictException } } = require('@toa.io/core')
|
|
4
|
+
|
|
5
|
+
class Criteria {
|
|
6
|
+
#value
|
|
7
|
+
#open
|
|
8
|
+
#logic
|
|
9
|
+
#right
|
|
10
|
+
|
|
11
|
+
constructor (value) {
|
|
12
|
+
if (value === null) return
|
|
13
|
+
|
|
14
|
+
const last = value.slice(-1)
|
|
15
|
+
|
|
16
|
+
if (last === ',' || last === ';') {
|
|
17
|
+
this.#open = true
|
|
18
|
+
this.#right = true
|
|
19
|
+
this.#logic = last
|
|
20
|
+
|
|
21
|
+
value = value.substring(0, value.length - 1)
|
|
22
|
+
} else {
|
|
23
|
+
const first = value.substring(0, 1)
|
|
24
|
+
|
|
25
|
+
this.#open = first === ',' || first === ';'
|
|
26
|
+
|
|
27
|
+
if (this.#open === true) {
|
|
28
|
+
this.#right = false
|
|
29
|
+
this.#logic = first
|
|
30
|
+
|
|
31
|
+
value = value.substring(1)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this.#value = value
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** @hot */
|
|
39
|
+
parse (value, operation) {
|
|
40
|
+
if (operation.query === false) return value
|
|
41
|
+
|
|
42
|
+
if (value !== undefined) {
|
|
43
|
+
if (this.#open === true) {
|
|
44
|
+
if (this.#right) value = this.#value + this.#logic + value
|
|
45
|
+
else value = value + this.#logic + this.#value
|
|
46
|
+
} else {
|
|
47
|
+
throw new RequestConflictException('Query criteria is defined as closed')
|
|
48
|
+
}
|
|
49
|
+
} else value = this.#value
|
|
50
|
+
|
|
51
|
+
return value
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
exports.Criteria = Criteria
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { exceptions: { RequestConflictException } } = require('@toa.io/core')
|
|
4
|
+
|
|
5
|
+
class Enum {
|
|
6
|
+
#value
|
|
7
|
+
#keys
|
|
8
|
+
|
|
9
|
+
constructor (value) {
|
|
10
|
+
this.#value = value
|
|
11
|
+
|
|
12
|
+
this.#keys = value.reduce((acc, key) => {
|
|
13
|
+
acc[key] = true
|
|
14
|
+
return acc
|
|
15
|
+
}, {})
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** @hot */
|
|
19
|
+
parse (value, operation) {
|
|
20
|
+
if (operation.type !== 'observation') return value
|
|
21
|
+
|
|
22
|
+
if (value === undefined) return this.#value
|
|
23
|
+
else if (value instanceof Array) {
|
|
24
|
+
const key = value.find((key) => !(key in this.#keys))
|
|
25
|
+
|
|
26
|
+
if (key !== undefined) {
|
|
27
|
+
throw new RequestConflictException(`Query projection must not contain '${key}'`)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return value
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
exports.Enum = Enum
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { Query } = require('./query')
|
|
4
|
+
const { Range } = require('./range')
|
|
5
|
+
const { Criteria } = require('./criteria')
|
|
6
|
+
const { Enum } = require('./enum')
|
|
7
|
+
const { Sort } = require('./sort')
|
|
8
|
+
|
|
9
|
+
exports.Query = Query
|
|
10
|
+
|
|
11
|
+
exports.constraints = {
|
|
12
|
+
criteria: Criteria,
|
|
13
|
+
sort: Sort,
|
|
14
|
+
omit: Range,
|
|
15
|
+
limit: Range,
|
|
16
|
+
projection: Enum
|
|
17
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { merge } = require('@toa.io/generic')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @implements {toa.extensions.exposition.Query}
|
|
7
|
+
*/
|
|
8
|
+
class Query {
|
|
9
|
+
#constraints
|
|
10
|
+
|
|
11
|
+
constructor (constraints) {
|
|
12
|
+
this.#constraints = Object.entries(constraints)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** @hot */
|
|
16
|
+
parse (query, operation) {
|
|
17
|
+
for (const [key, constraint] of this.#constraints) {
|
|
18
|
+
const value = constraint.parse(query?.[key], operation)
|
|
19
|
+
|
|
20
|
+
if (value !== undefined) {
|
|
21
|
+
if (query === undefined) query = {}
|
|
22
|
+
|
|
23
|
+
query[key] = value
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return query
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {toa.extensions.exposition.declarations.Node} node
|
|
32
|
+
* @returns {toa.extensions.exposition.declarations.Query}
|
|
33
|
+
*/
|
|
34
|
+
static merge (node) {
|
|
35
|
+
const query = {}
|
|
36
|
+
let current = node
|
|
37
|
+
|
|
38
|
+
do {
|
|
39
|
+
if (current.query !== undefined) merge(query, current.query, { ignore: true })
|
|
40
|
+
|
|
41
|
+
current = current.parent
|
|
42
|
+
} while (current !== undefined)
|
|
43
|
+
|
|
44
|
+
merge(query, DEFAULTS, { ignore: true })
|
|
45
|
+
|
|
46
|
+
return query
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const DEFAULTS = {
|
|
51
|
+
omit: {
|
|
52
|
+
range: [0, 1000]
|
|
53
|
+
},
|
|
54
|
+
limit: {
|
|
55
|
+
value: 100,
|
|
56
|
+
range: [1, 100]
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
exports.Query = Query
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { exceptions: { RequestConflictException } } = require('@toa.io/core')
|
|
4
|
+
|
|
5
|
+
class Range {
|
|
6
|
+
#value
|
|
7
|
+
#min
|
|
8
|
+
#max
|
|
9
|
+
|
|
10
|
+
constructor (constraint) {
|
|
11
|
+
this.#value = constraint.value
|
|
12
|
+
|
|
13
|
+
this.#min = constraint.range[0] === undefined ? this.#value : constraint.range[0]
|
|
14
|
+
this.#max = constraint.range[1] === undefined ? this.#value : constraint.range[1]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** @hot */
|
|
18
|
+
parse (value, operation) {
|
|
19
|
+
if (operation.type !== 'observation' || operation.scope !== 'objects') return value
|
|
20
|
+
if (value === undefined) return this.#value
|
|
21
|
+
|
|
22
|
+
if (value > this.#max || value < this.#min) {
|
|
23
|
+
throw new RequestConflictException(`Query omit/limit value is out of range [${this.#min}, ${this.#max}]`)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
exports.Range = Range
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
class Sort {
|
|
4
|
+
#value
|
|
5
|
+
|
|
6
|
+
constructor (value) {
|
|
7
|
+
this.#value = value
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** @hot */
|
|
11
|
+
parse (value, operation) {
|
|
12
|
+
if (operation.query === false) return value
|
|
13
|
+
|
|
14
|
+
if (value === undefined) return this.#value
|
|
15
|
+
else return this.#value.concat(value)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
exports.Sort = Sort
|
package/src/remote.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { Connector, exceptions: { NotImplementedException } } = require('@toa.io/core')
|
|
4
|
+
const { console } = require('@toa.io/console')
|
|
5
|
+
|
|
6
|
+
const translate = require('./translate')
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @implements {toa.extensions.exposition.Remote}
|
|
10
|
+
*/
|
|
11
|
+
class Remote extends Connector {
|
|
12
|
+
/** @type {toa.core.Runtime} */
|
|
13
|
+
#remote
|
|
14
|
+
|
|
15
|
+
/** @type {toa.extensions.exposition.Tree} */
|
|
16
|
+
#tree
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {toa.extensions.exposition.Server} server
|
|
20
|
+
* @param {toa.core.Runtime} remote
|
|
21
|
+
* @param {toa.extensions.exposition.Tree} tree
|
|
22
|
+
*/
|
|
23
|
+
constructor (server, remote, tree) {
|
|
24
|
+
super()
|
|
25
|
+
|
|
26
|
+
const { namespace, name } = remote.locator
|
|
27
|
+
const route = '/' + (namespace === name ? namespace : namespace + '/' + name) + '*'
|
|
28
|
+
|
|
29
|
+
server.route(route, (req, res) => this.#reply(req, res))
|
|
30
|
+
|
|
31
|
+
this.#remote = remote
|
|
32
|
+
this.#tree = tree
|
|
33
|
+
|
|
34
|
+
this.depends(server)
|
|
35
|
+
this.depends(remote)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
update (declaration) {
|
|
39
|
+
console.info(`Updating tree '${this.#remote.locator.id}'`)
|
|
40
|
+
|
|
41
|
+
this.#tree.update(declaration)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @hot
|
|
46
|
+
* @param {toa.extensions.exposition.http.Request} req
|
|
47
|
+
* @param {toa.extensions.exposition.http.Response} res
|
|
48
|
+
* @return {Promise<void>}
|
|
49
|
+
*/
|
|
50
|
+
async #reply (req, res) {
|
|
51
|
+
const match = this.#tree.match(req.params[0])
|
|
52
|
+
|
|
53
|
+
if (match !== undefined) {
|
|
54
|
+
try {
|
|
55
|
+
const reply = await this.#call(req, match)
|
|
56
|
+
|
|
57
|
+
translate.response.ok(reply, res, req)
|
|
58
|
+
} catch (e) {
|
|
59
|
+
translate.response.exception(e, res)
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
translate.response.missed(res)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
res.end()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param {toa.extensions.exposition.http.Request} req
|
|
70
|
+
* @param {toa.extensions.exposition.tree.Match} match
|
|
71
|
+
* @return {Promise<toa.core.Reply>}
|
|
72
|
+
*/
|
|
73
|
+
async #call (req, match) {
|
|
74
|
+
const method = req.method === 'HEAD' ? 'GET' : req.method
|
|
75
|
+
const operation = match.node.operations[method]
|
|
76
|
+
|
|
77
|
+
if (operation === undefined) throw new NotImplementedException()
|
|
78
|
+
|
|
79
|
+
const request = translate.request(req, match.params)
|
|
80
|
+
const query = match.node.query.parse(request.query, operation)
|
|
81
|
+
|
|
82
|
+
if (query !== undefined) request.query = query
|
|
83
|
+
|
|
84
|
+
return this.#remote.invoke(operation.operation, request)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
exports.Remote = Remote
|