@toa.io/extensions.exposition 0.20.0-dev.8 → 0.20.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.
Files changed (191) hide show
  1. package/components/context.toa.yaml +15 -0
  2. package/components/identity.bans/manifest.toa.yaml +18 -0
  3. package/components/identity.basic/events/principal.js +9 -0
  4. package/components/identity.basic/manifest.toa.yaml +50 -0
  5. package/components/identity.basic/source/authenticate.ts +29 -0
  6. package/components/identity.basic/source/create.ts +19 -0
  7. package/components/identity.basic/source/transit.ts +64 -0
  8. package/components/identity.basic/source/types.ts +42 -0
  9. package/components/identity.basic/tsconfig.json +9 -0
  10. package/components/identity.roles/manifest.toa.yaml +31 -0
  11. package/components/identity.roles/source/list.ts +7 -0
  12. package/components/identity.roles/source/principal.ts +20 -0
  13. package/components/identity.roles/tsconfig.json +9 -0
  14. package/components/identity.tokens/manifest.toa.yaml +39 -0
  15. package/components/identity.tokens/receivers/identity.bans.updated.js +3 -0
  16. package/components/identity.tokens/source/authenticate.test.ts +56 -0
  17. package/components/identity.tokens/source/authenticate.ts +38 -0
  18. package/components/identity.tokens/source/decrypt.test.ts +59 -0
  19. package/components/identity.tokens/source/decrypt.ts +25 -0
  20. package/components/identity.tokens/source/encrypt.test.ts +35 -0
  21. package/components/identity.tokens/source/encrypt.ts +25 -0
  22. package/components/identity.tokens/source/revoke.ts +5 -0
  23. package/components/identity.tokens/source/types.ts +48 -0
  24. package/components/identity.tokens/tsconfig.json +9 -0
  25. package/cucumber.js +9 -0
  26. package/documentation/.assets/ia3-dark.jpg +0 -0
  27. package/documentation/.assets/ia3-light.jpg +0 -0
  28. package/documentation/.assets/overview-dark.jpg +0 -0
  29. package/documentation/.assets/overview-light.jpg +0 -0
  30. package/documentation/.assets/role-scopes-dark.jpg +0 -0
  31. package/documentation/.assets/role-scopes-light.jpg +0 -0
  32. package/documentation/.assets/rtd-dark.jpg +0 -0
  33. package/documentation/.assets/rtd-light.jpg +0 -0
  34. package/documentation/access.md +256 -0
  35. package/documentation/components.md +276 -0
  36. package/documentation/identity.md +156 -0
  37. package/documentation/notes/sse.md +71 -0
  38. package/documentation/protocol.md +18 -0
  39. package/documentation/query.md +226 -0
  40. package/documentation/tree.md +169 -0
  41. package/features/access.feature +448 -0
  42. package/features/annotation.feature +30 -0
  43. package/features/body.feature +45 -0
  44. package/features/directives.feature +56 -0
  45. package/features/dynamic.feature +99 -0
  46. package/features/errors.feature +193 -0
  47. package/features/identity.basic.feature +276 -0
  48. package/features/identity.feature +61 -0
  49. package/features/identity.roles.feature +51 -0
  50. package/features/identity.tokens.feature +119 -0
  51. package/features/queries.feature +214 -0
  52. package/features/routes.feature +49 -0
  53. package/features/steps/Common.ts +10 -0
  54. package/features/steps/Components.ts +43 -0
  55. package/features/steps/Database.ts +58 -0
  56. package/features/steps/Gateway.ts +113 -0
  57. package/features/steps/HTTP.ts +71 -0
  58. package/features/steps/Parameters.ts +12 -0
  59. package/features/steps/Workspace.ts +40 -0
  60. package/features/steps/components/echo/manifest.toa.yaml +9 -0
  61. package/features/steps/components/echo/operations/affect.js +7 -0
  62. package/features/steps/components/echo/operations/compute.js +7 -0
  63. package/features/steps/components/greeter/manifest.toa.yaml +5 -0
  64. package/features/steps/components/greeter/operations/greet.js +7 -0
  65. package/features/steps/components/pots/manifest.toa.yaml +20 -0
  66. package/features/steps/components/sequences/manifest.toa.yaml +10 -0
  67. package/features/steps/components/sequences/operations/numbers.js +7 -0
  68. package/features/steps/components/sequences/operations/tokens.js +16 -0
  69. package/features/steps/components/users/manifest.toa.yaml +11 -0
  70. package/features/steps/tsconfig.json +9 -0
  71. package/features/streams.feature +26 -0
  72. package/package.json +32 -17
  73. package/readme.md +183 -0
  74. package/schemas/annotation.cos.yaml +5 -0
  75. package/schemas/directive.cos.yaml +3 -0
  76. package/schemas/method.cos.yaml +8 -0
  77. package/schemas/node.cos.yaml +5 -0
  78. package/schemas/query.cos.yaml +17 -0
  79. package/schemas/querystring.cos.yaml +5 -0
  80. package/schemas/range.cos.yaml +2 -0
  81. package/schemas/route.cos.yaml +2 -0
  82. package/source/Annotation.ts +7 -0
  83. package/source/Branch.ts +8 -0
  84. package/source/Composition.ts +57 -0
  85. package/source/Context.ts +6 -0
  86. package/source/Directive.test.ts +91 -0
  87. package/source/Directive.ts +120 -0
  88. package/source/Endpoint.ts +59 -0
  89. package/source/Factory.ts +51 -0
  90. package/source/Gateway.ts +93 -0
  91. package/source/HTTP/Server.fixtures.ts +45 -0
  92. package/source/HTTP/Server.test.ts +221 -0
  93. package/source/HTTP/Server.ts +135 -0
  94. package/source/HTTP/exceptions.ts +77 -0
  95. package/source/HTTP/formats/index.ts +19 -0
  96. package/source/HTTP/formats/json.ts +13 -0
  97. package/source/HTTP/formats/msgpack.ts +10 -0
  98. package/source/HTTP/formats/text.ts +9 -0
  99. package/source/HTTP/formats/yaml.ts +14 -0
  100. package/source/HTTP/index.ts +3 -0
  101. package/source/HTTP/messages.test.ts +116 -0
  102. package/source/HTTP/messages.ts +89 -0
  103. package/source/Mapping.ts +51 -0
  104. package/source/Query.test.ts +37 -0
  105. package/source/Query.ts +105 -0
  106. package/source/RTD/Context.ts +16 -0
  107. package/source/RTD/Directives.ts +9 -0
  108. package/source/RTD/Endpoint.ts +11 -0
  109. package/source/RTD/Match.ts +16 -0
  110. package/source/RTD/Method.ts +24 -0
  111. package/source/RTD/Node.ts +85 -0
  112. package/source/RTD/Route.ts +59 -0
  113. package/source/RTD/Tree.ts +54 -0
  114. package/source/RTD/factory.ts +47 -0
  115. package/source/RTD/index.ts +8 -0
  116. package/source/RTD/segment.test.ts +32 -0
  117. package/source/RTD/segment.ts +25 -0
  118. package/source/RTD/syntax/index.ts +2 -0
  119. package/source/RTD/syntax/parse.test.ts +188 -0
  120. package/source/RTD/syntax/parse.ts +153 -0
  121. package/source/RTD/syntax/types.ts +48 -0
  122. package/source/Remotes.test.ts +42 -0
  123. package/source/Remotes.ts +22 -0
  124. package/source/Tenant.ts +38 -0
  125. package/source/deployment.ts +49 -0
  126. package/source/directives/auth/Anonymous.ts +14 -0
  127. package/source/directives/auth/Echo.ts +12 -0
  128. package/source/directives/auth/Family.ts +145 -0
  129. package/source/directives/auth/Id.ts +19 -0
  130. package/source/directives/auth/Incept.ts +42 -0
  131. package/source/directives/auth/Role.test.ts +62 -0
  132. package/source/directives/auth/Role.ts +56 -0
  133. package/source/directives/auth/Rule.ts +28 -0
  134. package/source/directives/auth/Scheme.ts +26 -0
  135. package/source/directives/auth/index.ts +3 -0
  136. package/source/directives/auth/schemes.ts +8 -0
  137. package/source/directives/auth/split.ts +15 -0
  138. package/source/directives/auth/types.ts +37 -0
  139. package/source/directives/dev/Family.ts +36 -0
  140. package/source/directives/dev/Stub.ts +14 -0
  141. package/source/directives/dev/Throw.ts +14 -0
  142. package/source/directives/dev/index.ts +3 -0
  143. package/source/directives/dev/types.ts +5 -0
  144. package/source/directives/index.ts +5 -0
  145. package/source/discovery.ts +1 -0
  146. package/source/exceptions.ts +17 -0
  147. package/source/index.test.ts +9 -0
  148. package/source/index.ts +6 -0
  149. package/source/manifest.test.ts +59 -0
  150. package/source/manifest.ts +48 -0
  151. package/source/root.ts +38 -0
  152. package/source/schemas.ts +9 -0
  153. package/tsconfig.json +12 -0
  154. package/src/.manifest/index.js +0 -7
  155. package/src/.manifest/normalize.js +0 -58
  156. package/src/.manifest/schema.yaml +0 -71
  157. package/src/.manifest/validate.js +0 -17
  158. package/src/constants.js +0 -3
  159. package/src/deployment.js +0 -23
  160. package/src/exposition.js +0 -68
  161. package/src/factory.js +0 -76
  162. package/src/index.js +0 -9
  163. package/src/manifest.js +0 -12
  164. package/src/query/criteria.js +0 -55
  165. package/src/query/enum.js +0 -35
  166. package/src/query/index.js +0 -17
  167. package/src/query/query.js +0 -60
  168. package/src/query/range.js +0 -28
  169. package/src/query/sort.js +0 -19
  170. package/src/remote.js +0 -88
  171. package/src/server.js +0 -83
  172. package/src/tenant.js +0 -29
  173. package/src/translate/etag.js +0 -14
  174. package/src/translate/index.js +0 -7
  175. package/src/translate/request.js +0 -68
  176. package/src/translate/response.js +0 -62
  177. package/src/tree.js +0 -109
  178. package/test/manifest.normalize.fixtures.js +0 -37
  179. package/test/manifest.normalize.test.js +0 -37
  180. package/test/manifest.validate.test.js +0 -40
  181. package/test/query.range.test.js +0 -18
  182. package/test/tree.fixtures.js +0 -21
  183. package/test/tree.test.js +0 -44
  184. package/types/annotations.d.ts +0 -10
  185. package/types/declarations.d.ts +0 -31
  186. package/types/exposition.d.ts +0 -13
  187. package/types/http.d.ts +0 -13
  188. package/types/query.d.ts +0 -16
  189. package/types/remote.d.ts +0 -19
  190. package/types/server.d.ts +0 -13
  191. package/types/tree.d.ts +0 -33
@@ -1,28 +0,0 @@
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
package/src/query/sort.js DELETED
@@ -1,19 +0,0 @@
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 DELETED
@@ -1,88 +0,0 @@
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.Component} */
13
- #remote
14
-
15
- /** @type {toa.extensions.exposition.Tree} */
16
- #tree
17
-
18
- /**
19
- * @param {toa.extensions.exposition.Server} server
20
- * @param {toa.core.Component} 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 === 'default' ? '' : 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
package/src/server.js DELETED
@@ -1,83 +0,0 @@
1
- 'use strict'
2
-
3
- const express = require('express')
4
- const cors = require('cors')
5
-
6
- const { Connector } = require('@toa.io/core')
7
- const { console } = require('@toa.io/console')
8
-
9
- const { PORT } = require('./constants')
10
-
11
- // noinspection JSClosureCompilerSyntax
12
- /**
13
- * @implements {toa.extensions.exposition.Server}
14
- */
15
- class Server extends Connector {
16
- /** @type {import('express').Application} */
17
- #app
18
- /** @type {import('http').Server} */
19
- #server
20
-
21
- constructor () {
22
- super()
23
-
24
- // TODO: remove express
25
- this.#app = express()
26
- this.#app.disable('x-powered-by')
27
- this.#app.enable('case sensitive routing')
28
- this.#app.enable('strict routing')
29
- this.#app.disable('etag')
30
- this.#app.use(express.json())
31
- this.#app.use(cors({ allowedHeaders: ['content-type'] }))
32
-
33
- this.#app.use((req, res, next) => {
34
- req.safe = req.method in SAFE
35
-
36
- if (req.method in METHODS) next()
37
- else res.status(501).end()
38
- })
39
- }
40
-
41
- route (route, callback) {
42
- this.#app.use(route, callback)
43
- }
44
-
45
- async open () {
46
- console.info(`Starting HTTP server at :${PORT} ...`)
47
-
48
- return new Promise((resolve, reject) => {
49
- const error = () => reject(new Error(`Error starting HTTP server at :${PORT}`))
50
- // noinspection JSCheckFunctionSignatures
51
- this.#server = this.#app.listen(PORT, () => {
52
- console.info(`HTTP server at :${PORT} started`)
53
-
54
- this.#server.off('error', error)
55
- resolve()
56
- })
57
-
58
- this.#server.on('error', error)
59
- })
60
- }
61
-
62
- async close () {
63
- console.info(`Stopping HTTP server at :${PORT} ...`)
64
-
65
- return new Promise((resolve, reject) => {
66
- const error = () => reject(new Error(`Error stopping HTTP server at :${PORT}`))
67
-
68
- this.#server.close(() => {
69
- console.info(`HTTP server at :${PORT} stopped`)
70
-
71
- this.#server.off('error', error)
72
- resolve()
73
- })
74
-
75
- this.#server.on('error', error)
76
- })
77
- }
78
- }
79
-
80
- const METHODS = { HEAD: 1, GET: 1, POST: 1, PUT: 1, PATCH: 1 }
81
- const SAFE = { HEAD: 1, GET: 1 }
82
-
83
- exports.Server = Server
package/src/tenant.js DELETED
@@ -1,29 +0,0 @@
1
- 'use strict'
2
-
3
- const { Connector } = require('@toa.io/core')
4
-
5
- class Tenant extends Connector {
6
- /** @type {toa.core.bindings.Broadcast} */
7
- #binding
8
- #declaration
9
-
10
- constructor (binding, { namespace, name }, resources) {
11
- super()
12
-
13
- this.#binding = binding
14
- this.#declaration = { namespace, name, resources }
15
-
16
- this.depends(binding)
17
- }
18
-
19
- async open () {
20
- await this.#binding.receive('ping', () => this.#expose())
21
- await this.#expose()
22
- }
23
-
24
- async #expose () {
25
- await this.#binding.transmit('expose', this.#declaration)
26
- }
27
- }
28
-
29
- exports.Tenant = Tenant
@@ -1,14 +0,0 @@
1
- 'use strict'
2
-
3
- const get = (header) => {
4
- const match = header.match(rx)
5
- return match === null ? null : match[1]
6
- }
7
-
8
- const set = (value) => '"' + value + '"'
9
-
10
- const rx = /^"([^"]+)"$/
11
-
12
- exports.get = get
13
- exports.set = set
14
- exports.rx = rx
@@ -1,7 +0,0 @@
1
- 'use strict'
2
-
3
- const { request } = require('./request')
4
- const response = require('./response')
5
-
6
- exports.request = request
7
- exports.response = response
@@ -1,68 +0,0 @@
1
- 'use strict'
2
-
3
- const { exceptions: { RequestSyntaxException, RequestConflictException } } = require('@toa.io/core')
4
- const { empty } = require('@toa.io/generic')
5
-
6
- const etag = require('./etag')
7
-
8
- /**
9
- * @hot
10
- *
11
- * @param {import('express').Request} req
12
- * @param {{[key: string]: string}} params
13
- * @returns {toa.core.Request}
14
- */
15
- const request = (req, params) => {
16
- const request = {}
17
-
18
- if (!empty(req.body)) request.input = req.body
19
-
20
- if (!empty(req.query)) {
21
- request.query = req.query
22
-
23
- if (request.query.projection !== undefined) request.query.projection = request.query.projection.split(',')
24
- if (request.query.sort !== undefined) request.query.sort = request.query.sort.split(',')
25
- }
26
-
27
- if (!empty(params)) {
28
- if (req.method === 'POST') {
29
- if (request.input === undefined) request.input = {}
30
-
31
- for (const [key, value] of Object.entries(params)) {
32
- if (request.input[key] === undefined) request.input[key] = value
33
- else throw new RequestConflictException(`Input property '${key}' conflicts with path parameter`)
34
- }
35
- } else {
36
- const criteria = []
37
-
38
- if (request.query === undefined) request.query = {}
39
-
40
- for (const [key, value] of Object.entries(params)) {
41
- if (key === 'id') request.query.id = value
42
- else criteria.push(key + '==' + value)
43
- }
44
-
45
- if (criteria.length > 0) {
46
- const value = criteria.join(';')
47
-
48
- if (request.query.criteria === undefined) request.query.criteria = value
49
- else request.query.criteria = value + ';' + request.query.criteria
50
- }
51
- }
52
- }
53
-
54
- const condition = req.get('if-match')
55
-
56
- if (condition !== undefined && condition !== '*') {
57
- const value = etag.get(condition)
58
-
59
- if (value === null) throw new RequestSyntaxException('ETag value must match ' + etag.rx)
60
- if (request.query === undefined) request.query = {}
61
-
62
- request.query.version = +value
63
- }
64
-
65
- return request
66
- }
67
-
68
- exports.request = request
@@ -1,62 +0,0 @@
1
- 'use strict'
2
-
3
- const { exceptions: { codes } } = require('@toa.io/core')
4
-
5
- const etag = require('./etag')
6
-
7
- /**
8
- * @param {toa.core.Reply} reply
9
- * @param res
10
- * @param req
11
- * @hot
12
- */
13
- const ok = (reply, res, req) => {
14
- if (reply.output?._version !== undefined) {
15
- const { _version, ...output } = reply.output
16
- const condition = req.get('if-none-match')
17
-
18
- if (condition !== undefined && req.safe) {
19
- const value = etag.get(condition)
20
-
21
- if (value === _version) {
22
- res.status(304).end()
23
- return
24
- }
25
- }
26
-
27
- res.set('etag', etag.set(_version))
28
- reply.output = output
29
- }
30
-
31
- let status
32
-
33
- if (req.method === 'POST') status = 201
34
- else if (reply.output !== undefined || reply.error !== undefined) status = 200
35
- else status = 204
36
-
37
- res.status(status)
38
- if (status !== 204) res.send(reply)
39
- }
40
-
41
- const missed = (response) => response.status(404)
42
-
43
- const exception = (exception, response) => {
44
- const status = STATUSES[exception.code] || 500
45
-
46
- response.status(status)
47
- response.send(exception)
48
- }
49
-
50
- const STATUSES = {
51
- [codes.RequestContract]: 400,
52
- [codes.RequestSyntax]: 400,
53
- [codes.QuerySyntax]: 400,
54
- [codes.RequestConflict]: 403,
55
- [codes.StateNotFound]: 404,
56
- [codes.NotImplemented]: 405,
57
- [codes.StatePrecondition]: 412
58
- }
59
-
60
- exports.ok = ok
61
- exports.missed = missed
62
- exports.exception = exception
package/src/tree.js DELETED
@@ -1,109 +0,0 @@
1
- 'use strict'
2
-
3
- const path = require('path')
4
- const { match } = require('path-to-regexp')
5
-
6
- const { console } = require('@toa.io/console')
7
-
8
- /**
9
- * @implements {toa.extensions.exposition.Tree}
10
- */
11
- class Tree {
12
- /** @type {toa.extensions.exposition.tree.Node[]} */
13
- #nodes
14
-
15
- /** @type {toa.extensions.exposition.query.Factory} */
16
- #query
17
-
18
- /**
19
- * @param {toa.extensions.exposition.query.Factory} query
20
- */
21
- constructor (query) {
22
- this.#query = query
23
- }
24
-
25
- /** @hot */
26
- match (path) {
27
- // dev only check
28
- if (process.env.TOA_ENV === 'local') {
29
- const nodes = this.#nodes.filter((node) => node.match(path) !== false)
30
-
31
- if (nodes.length > 1) {
32
- const routes = nodes.map((node) => node.route)
33
-
34
- throw new Error('Ambiguous routes ' + routes.join(', '))
35
- }
36
- }
37
-
38
- let match
39
-
40
- const node = this.#nodes.find((node) => {
41
- match = node.match(path)
42
- return match !== false
43
- })
44
-
45
- return node === undefined ? undefined : { node, params: match.params }
46
- }
47
-
48
- update (tree) {
49
- this.#nodes = []
50
- this.#traverse(tree)
51
- }
52
-
53
- /**
54
- * @param {toa.extensions.exposition.declarations.Node | any} node
55
- * @param {string} route
56
- * @param {toa.extensions.exposition.declarations.Node} parent
57
- */
58
- #traverse (node, route = undefined, parent = undefined) {
59
- const current = {}
60
-
61
- if (route === undefined) route = '/'
62
- else route = trail(route)
63
-
64
- if (parent !== undefined) node.parent = parent
65
-
66
- if (node.operations) {
67
- current.route = route
68
- current.match = match(route)
69
- current.query = this.#query(node)
70
- current.operations = {}
71
-
72
- for (const operation of node.operations) current.operations[method(operation)] = operation
73
-
74
- this.#nodes.push(current)
75
- }
76
-
77
- let branches = 0
78
-
79
- for (const [key, value] of Object.entries(node)) {
80
- if (key[0] === '/') {
81
- branches++
82
-
83
- const branch = path.posix.resolve(route, '.' + key)
84
-
85
- this.#traverse(value, branch, node)
86
- }
87
- }
88
-
89
- if (branches === 0 && node.operations === undefined) {
90
- console.warn(`Resource tree leaf '${route}' has no operations`)
91
- }
92
- }
93
- }
94
-
95
- const trail = (path) => path[path.length - 1] === '/' ? path : path + '/'
96
-
97
- const method = (operation) => {
98
- if (operation.type === 'transition') {
99
- if (operation.query === false) return 'POST'
100
- else return 'PUT'
101
- }
102
-
103
- if (operation.type === 'observation') return 'GET'
104
- if (operation.type === 'assignment') return 'PATCH'
105
- if (operation.type === 'computation') return 'GET'
106
- if (operation.type === 'effect') return 'POST'
107
- }
108
-
109
- exports.Tree = Tree
@@ -1,37 +0,0 @@
1
- 'use strict'
2
-
3
- const manifest = {
4
- operations: {
5
- one: {
6
- type: 'transition',
7
- scope: 'object',
8
- query: false
9
- },
10
- two: {
11
- type: 'observation',
12
- scope: 'objects'
13
- },
14
- three: {
15
- type: 'observation',
16
- scope: 'objects'
17
- }
18
- }
19
- }
20
-
21
- const resources = {
22
- '/': ['one', 'two'],
23
- '/top': {
24
- operations: ['one'],
25
- '/nested': {
26
- operations: ['two'],
27
- '/deeper': {
28
- operations: [{
29
- operation: 'three'
30
- }]
31
- }
32
- }
33
- }
34
- }
35
-
36
- exports.manifest = manifest
37
- exports.resources = resources
@@ -1,37 +0,0 @@
1
- 'use strict'
2
-
3
- const clone = require('clone-deep')
4
-
5
- const { normalize } = require('../src/.manifest')
6
- const fixtures = require('./manifest.normalize.fixtures')
7
-
8
- describe('normalize', () => {
9
- let manifest, resources
10
-
11
- const map = (operation) => ({
12
- ...fixtures.manifest.operations[operation],
13
- operation
14
- })
15
-
16
- beforeEach(() => {
17
- manifest = clone(fixtures.manifest)
18
- resources = clone(fixtures.resources)
19
- })
20
-
21
- it('should expand array', () => {
22
- normalize(resources, manifest)
23
-
24
- expect(resources['/'].operations)
25
- .toStrictEqual(fixtures.resources['/'].map(map))
26
- })
27
-
28
- it('should expand operations', () => {
29
- normalize(resources, manifest)
30
-
31
- expect(resources['/top'].operations)
32
- .toStrictEqual(fixtures.resources['/top'].operations.map(map))
33
-
34
- expect(resources['/top']['/nested'].operations)
35
- .toStrictEqual(fixtures.resources['/top']['/nested'].operations.map(map))
36
- })
37
- })
@@ -1,40 +0,0 @@
1
- 'use strict'
2
-
3
- const { validate } = require('../src/.manifest')
4
-
5
- it('should validate', () => {
6
- const resources = {
7
- '/path/to': {
8
- operations: [
9
- {
10
- operation: 'foo',
11
- type: 'observation'
12
- }
13
- ],
14
- '/deeper/': {
15
- operations: [{
16
- operation: 'bar',
17
- type: 'transition',
18
- query: false
19
- }]
20
- }
21
- }
22
- }
23
-
24
- expect(() => validate(resources)).not.toThrow()
25
- })
26
-
27
- it.each(['computation', 'effect'])('should allow %s operation', async (type) => {
28
- const resources = {
29
- '/path/to': {
30
- operations: [
31
- {
32
- operation: 'foo',
33
- type
34
- }
35
- ]
36
- }
37
- }
38
-
39
- expect(() => validate(resources)).not.toThrow()
40
- })
@@ -1,18 +0,0 @@
1
- 'use strict'
2
-
3
- const { Range } = require('../src/query/range')
4
-
5
- let range
6
-
7
- const operation = { type: 'observation', scope: 'objects' }
8
-
9
- describe('exact', () => {
10
- beforeAll(() => {
11
- range = new Range({ value: 10, range: [] })
12
- })
13
-
14
- it('should throw on value mismatch', () => {
15
- expect(() => range.parse(10, operation)).not.toThrow()
16
- expect(() => range.parse(11, operation)).toThrow(/out of range/)
17
- })
18
- })
@@ -1,21 +0,0 @@
1
- 'use strict'
2
-
3
- const declaration = {
4
- '/': {
5
- operations: ['find'],
6
- '/:id': {
7
- operations: ['observe'],
8
- '/segment': {
9
- operations: ['delete']
10
- }
11
- },
12
- '/segment/:param': {
13
- operations: ['transit']
14
- }
15
- },
16
- '/sibling': {
17
- operations: ['update']
18
- }
19
- }
20
-
21
- exports.declaration = declaration