@toa.io/extensions.exposition 0.9.0-canary.2 → 0.20.0-alpha.1

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 (363) 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/protocol.md +15 -0
  38. package/documentation/query.md +226 -0
  39. package/documentation/tree.md +169 -0
  40. package/features/access.feature +442 -0
  41. package/features/annotation.feature +28 -0
  42. package/features/body.feature +21 -0
  43. package/features/directives.feature +56 -0
  44. package/features/dynamic.feature +89 -0
  45. package/features/errors.feature +208 -0
  46. package/features/identity.basic.feature +235 -0
  47. package/features/identity.feature +61 -0
  48. package/features/identity.roles.feature +51 -0
  49. package/features/identity.tokens.feature +116 -0
  50. package/features/queries.feature +214 -0
  51. package/features/routes.feature +49 -0
  52. package/features/steps/Common.ts +10 -0
  53. package/features/steps/Components.ts +43 -0
  54. package/features/steps/Database.ts +58 -0
  55. package/features/steps/Gateway.ts +105 -0
  56. package/features/steps/HTTP.ts +71 -0
  57. package/features/steps/Parameters.ts +12 -0
  58. package/features/steps/Workspace.ts +40 -0
  59. package/features/steps/components/greeter/manifest.toa.yaml +9 -0
  60. package/features/steps/components/greeter/operations/greet.js +7 -0
  61. package/features/steps/components/pots/manifest.toa.yaml +20 -0
  62. package/features/steps/components/users/manifest.toa.yaml +11 -0
  63. package/features/steps/tsconfig.json +9 -0
  64. package/package.json +31 -17
  65. package/readme.md +181 -0
  66. package/schemas/annotation.cos.yaml +4 -0
  67. package/schemas/directive.cos.yaml +3 -0
  68. package/schemas/method.cos.yaml +9 -0
  69. package/schemas/node.cos.yaml +5 -0
  70. package/schemas/query.cos.yaml +16 -0
  71. package/schemas/querystring.cos.yaml +5 -0
  72. package/schemas/range.cos.yaml +2 -0
  73. package/schemas/route.cos.yaml +2 -0
  74. package/source/Annotation.ts +6 -0
  75. package/source/Branch.ts +8 -0
  76. package/source/Composition.ts +58 -0
  77. package/source/Context.ts +6 -0
  78. package/source/Directive.test.ts +91 -0
  79. package/source/Directive.ts +120 -0
  80. package/source/Endpoint.ts +57 -0
  81. package/source/Factory.ts +53 -0
  82. package/source/Gateway.ts +93 -0
  83. package/source/HTTP/Server.fixtures.ts +45 -0
  84. package/source/HTTP/Server.test.ts +221 -0
  85. package/source/HTTP/Server.ts +135 -0
  86. package/source/HTTP/exceptions.ts +77 -0
  87. package/source/HTTP/formats/index.ts +19 -0
  88. package/source/HTTP/formats/json.ts +13 -0
  89. package/source/HTTP/formats/msgpack.ts +10 -0
  90. package/source/HTTP/formats/text.ts +9 -0
  91. package/source/HTTP/formats/yaml.ts +14 -0
  92. package/source/HTTP/index.ts +3 -0
  93. package/source/HTTP/messages.test.ts +116 -0
  94. package/source/HTTP/messages.ts +77 -0
  95. package/source/Mapping.ts +48 -0
  96. package/source/Query.test.ts +37 -0
  97. package/source/Query.ts +105 -0
  98. package/source/RTD/Context.ts +16 -0
  99. package/source/RTD/Directives.ts +9 -0
  100. package/source/RTD/Endpoint.ts +11 -0
  101. package/source/RTD/Match.ts +16 -0
  102. package/source/RTD/Method.ts +24 -0
  103. package/source/RTD/Node.ts +85 -0
  104. package/source/RTD/Route.ts +58 -0
  105. package/source/RTD/Tree.ts +57 -0
  106. package/source/RTD/factory.ts +47 -0
  107. package/source/RTD/index.ts +8 -0
  108. package/source/RTD/segment.test.ts +32 -0
  109. package/source/RTD/segment.ts +25 -0
  110. package/source/RTD/syntax/index.ts +2 -0
  111. package/source/RTD/syntax/parse.test.ts +188 -0
  112. package/source/RTD/syntax/parse.ts +153 -0
  113. package/source/RTD/syntax/types.ts +48 -0
  114. package/source/Remotes.test.ts +41 -0
  115. package/source/Remotes.ts +20 -0
  116. package/source/Tenant.ts +38 -0
  117. package/source/deployment.ts +42 -0
  118. package/source/directives/auth/Anonymous.ts +14 -0
  119. package/source/directives/auth/Echo.ts +12 -0
  120. package/source/directives/auth/Family.ts +145 -0
  121. package/source/directives/auth/Id.ts +19 -0
  122. package/source/directives/auth/Incept.ts +42 -0
  123. package/source/directives/auth/Role.test.ts +62 -0
  124. package/source/directives/auth/Role.ts +56 -0
  125. package/source/directives/auth/Rule.ts +28 -0
  126. package/source/directives/auth/Scheme.ts +26 -0
  127. package/source/directives/auth/index.ts +3 -0
  128. package/source/directives/auth/schemes.ts +8 -0
  129. package/source/directives/auth/split.ts +15 -0
  130. package/source/directives/auth/types.ts +37 -0
  131. package/source/directives/dev/Family.ts +34 -0
  132. package/source/directives/dev/Stub.ts +14 -0
  133. package/source/directives/dev/index.ts +3 -0
  134. package/source/directives/dev/types.ts +5 -0
  135. package/source/directives/index.ts +5 -0
  136. package/source/discovery.ts +1 -0
  137. package/source/exceptions.ts +17 -0
  138. package/source/index.test.ts +9 -0
  139. package/source/index.ts +6 -0
  140. package/source/manifest.test.ts +57 -0
  141. package/source/manifest.ts +35 -0
  142. package/source/root.ts +38 -0
  143. package/source/schemas.ts +9 -0
  144. package/transpiled/Annotation.d.ts +6 -0
  145. package/transpiled/Annotation.js +3 -0
  146. package/transpiled/Annotation.js.map +1 -0
  147. package/transpiled/Branch.d.ts +7 -0
  148. package/transpiled/Branch.js +3 -0
  149. package/transpiled/Branch.js.map +1 -0
  150. package/transpiled/Composition.d.ts +14 -0
  151. package/transpiled/Composition.js +43 -0
  152. package/transpiled/Composition.js.map +1 -0
  153. package/transpiled/Context.d.ts +5 -0
  154. package/transpiled/Context.js +3 -0
  155. package/transpiled/Context.js.map +1 -0
  156. package/transpiled/Directive.d.ts +32 -0
  157. package/transpiled/Directive.js +76 -0
  158. package/transpiled/Directive.js.map +1 -0
  159. package/transpiled/Endpoint.d.ts +20 -0
  160. package/transpiled/Endpoint.js +44 -0
  161. package/transpiled/Endpoint.js.map +1 -0
  162. package/transpiled/Factory.d.ts +11 -0
  163. package/transpiled/Factory.js +67 -0
  164. package/transpiled/Factory.js.map +1 -0
  165. package/transpiled/Gateway.d.ts +19 -0
  166. package/transpiled/Gateway.js +90 -0
  167. package/transpiled/Gateway.js.map +1 -0
  168. package/transpiled/HTTP/Server.d.ts +22 -0
  169. package/transpiled/HTTP/Server.fixtures.d.ts +12 -0
  170. package/transpiled/HTTP/Server.fixtures.js +36 -0
  171. package/transpiled/HTTP/Server.fixtures.js.map +1 -0
  172. package/transpiled/HTTP/Server.js +111 -0
  173. package/transpiled/HTTP/Server.js.map +1 -0
  174. package/transpiled/HTTP/exceptions.d.ts +39 -0
  175. package/transpiled/HTTP/exceptions.js +78 -0
  176. package/transpiled/HTTP/exceptions.js.map +1 -0
  177. package/transpiled/HTTP/formats/index.d.ts +8 -0
  178. package/transpiled/HTTP/formats/index.js +38 -0
  179. package/transpiled/HTTP/formats/index.js.map +1 -0
  180. package/transpiled/HTTP/formats/json.d.ts +4 -0
  181. package/transpiled/HTTP/formats/json.js +15 -0
  182. package/transpiled/HTTP/formats/json.js.map +1 -0
  183. package/transpiled/HTTP/formats/msgpack.d.ts +4 -0
  184. package/transpiled/HTTP/formats/msgpack.js +36 -0
  185. package/transpiled/HTTP/formats/msgpack.js.map +1 -0
  186. package/transpiled/HTTP/formats/text.d.ts +4 -0
  187. package/transpiled/HTTP/formats/text.js +13 -0
  188. package/transpiled/HTTP/formats/text.js.map +1 -0
  189. package/transpiled/HTTP/formats/yaml.d.ts +4 -0
  190. package/transpiled/HTTP/formats/yaml.js +39 -0
  191. package/transpiled/HTTP/formats/yaml.js.map +1 -0
  192. package/transpiled/HTTP/index.d.ts +3 -0
  193. package/transpiled/HTTP/index.js +20 -0
  194. package/transpiled/HTTP/index.js.map +1 -0
  195. package/transpiled/HTTP/messages.d.ts +27 -0
  196. package/transpiled/HTTP/messages.js +49 -0
  197. package/transpiled/HTTP/messages.js.map +1 -0
  198. package/transpiled/Mapping.d.ts +8 -0
  199. package/transpiled/Mapping.js +35 -0
  200. package/transpiled/Mapping.js.map +1 -0
  201. package/transpiled/Query.d.ts +13 -0
  202. package/transpiled/Query.js +107 -0
  203. package/transpiled/Query.js.map +1 -0
  204. package/transpiled/RTD/Context.d.ts +11 -0
  205. package/transpiled/RTD/Context.js +3 -0
  206. package/transpiled/RTD/Context.js.map +1 -0
  207. package/transpiled/RTD/Directives.d.ts +7 -0
  208. package/transpiled/RTD/Directives.js +3 -0
  209. package/transpiled/RTD/Directives.js.map +1 -0
  210. package/transpiled/RTD/Endpoint.d.ts +9 -0
  211. package/transpiled/RTD/Endpoint.js +3 -0
  212. package/transpiled/RTD/Endpoint.js.map +1 -0
  213. package/transpiled/RTD/Match.d.ts +11 -0
  214. package/transpiled/RTD/Match.js +3 -0
  215. package/transpiled/RTD/Match.js.map +1 -0
  216. package/transpiled/RTD/Method.d.ts +9 -0
  217. package/transpiled/RTD/Method.js +16 -0
  218. package/transpiled/RTD/Method.js.map +1 -0
  219. package/transpiled/RTD/Node.d.ts +21 -0
  220. package/transpiled/RTD/Node.js +61 -0
  221. package/transpiled/RTD/Node.js.map +1 -0
  222. package/transpiled/RTD/Route.d.ts +14 -0
  223. package/transpiled/RTD/Route.js +48 -0
  224. package/transpiled/RTD/Route.js.map +1 -0
  225. package/transpiled/RTD/Tree.d.ts +14 -0
  226. package/transpiled/RTD/Tree.js +45 -0
  227. package/transpiled/RTD/Tree.js.map +1 -0
  228. package/transpiled/RTD/factory.d.ts +6 -0
  229. package/transpiled/RTD/factory.js +36 -0
  230. package/transpiled/RTD/factory.js.map +1 -0
  231. package/transpiled/RTD/index.d.ts +8 -0
  232. package/transpiled/RTD/index.js +38 -0
  233. package/transpiled/RTD/index.js.map +1 -0
  234. package/transpiled/RTD/segment.d.ts +8 -0
  235. package/transpiled/RTD/segment.js +23 -0
  236. package/transpiled/RTD/segment.js.map +1 -0
  237. package/transpiled/RTD/syntax/index.d.ts +2 -0
  238. package/transpiled/RTD/syntax/index.js +19 -0
  239. package/transpiled/RTD/syntax/index.js.map +1 -0
  240. package/transpiled/RTD/syntax/parse.d.ts +4 -0
  241. package/transpiled/RTD/syntax/parse.js +128 -0
  242. package/transpiled/RTD/syntax/parse.js.map +1 -0
  243. package/transpiled/RTD/syntax/types.d.ts +41 -0
  244. package/transpiled/RTD/syntax/types.js +5 -0
  245. package/transpiled/RTD/syntax/types.js.map +1 -0
  246. package/transpiled/Remotes.d.ts +7 -0
  247. package/transpiled/Remotes.js +19 -0
  248. package/transpiled/Remotes.js.map +1 -0
  249. package/transpiled/Tenant.d.ts +12 -0
  250. package/transpiled/Tenant.js +30 -0
  251. package/transpiled/Tenant.js.map +1 -0
  252. package/transpiled/deployment.d.ts +3 -0
  253. package/transpiled/deployment.js +61 -0
  254. package/transpiled/deployment.js.map +1 -0
  255. package/transpiled/directives/auth/Anonymous.d.ts +6 -0
  256. package/transpiled/directives/auth/Anonymous.js +17 -0
  257. package/transpiled/directives/auth/Anonymous.js.map +1 -0
  258. package/transpiled/directives/auth/Echo.d.ts +6 -0
  259. package/transpiled/directives/auth/Echo.js +13 -0
  260. package/transpiled/directives/auth/Echo.js.map +1 -0
  261. package/transpiled/directives/auth/Family.d.ts +20 -0
  262. package/transpiled/directives/auth/Family.js +125 -0
  263. package/transpiled/directives/auth/Family.js.map +1 -0
  264. package/transpiled/directives/auth/Id.d.ts +7 -0
  265. package/transpiled/directives/auth/Id.js +17 -0
  266. package/transpiled/directives/auth/Id.js.map +1 -0
  267. package/transpiled/directives/auth/Incept.d.ts +10 -0
  268. package/transpiled/directives/auth/Incept.js +59 -0
  269. package/transpiled/directives/auth/Incept.js.map +1 -0
  270. package/transpiled/directives/auth/Role.d.ts +11 -0
  271. package/transpiled/directives/auth/Role.js +44 -0
  272. package/transpiled/directives/auth/Role.js.map +1 -0
  273. package/transpiled/directives/auth/Rule.d.ts +9 -0
  274. package/transpiled/directives/auth/Rule.js +22 -0
  275. package/transpiled/directives/auth/Rule.js.map +1 -0
  276. package/transpiled/directives/auth/Scheme.d.ts +7 -0
  277. package/transpiled/directives/auth/Scheme.js +47 -0
  278. package/transpiled/directives/auth/Scheme.js.map +1 -0
  279. package/transpiled/directives/auth/index.d.ts +2 -0
  280. package/transpiled/directives/auth/index.js +7 -0
  281. package/transpiled/directives/auth/index.js.map +1 -0
  282. package/transpiled/directives/auth/schemes.d.ts +3 -0
  283. package/transpiled/directives/auth/schemes.js +9 -0
  284. package/transpiled/directives/auth/schemes.js.map +1 -0
  285. package/transpiled/directives/auth/split.d.ts +2 -0
  286. package/transpiled/directives/auth/split.js +38 -0
  287. package/transpiled/directives/auth/split.js.map +1 -0
  288. package/transpiled/directives/auth/types.d.ts +31 -0
  289. package/transpiled/directives/auth/types.js +3 -0
  290. package/transpiled/directives/auth/types.js.map +1 -0
  291. package/transpiled/directives/dev/Family.d.ts +10 -0
  292. package/transpiled/directives/dev/Family.js +25 -0
  293. package/transpiled/directives/dev/Family.js.map +1 -0
  294. package/transpiled/directives/dev/Stub.d.ts +7 -0
  295. package/transpiled/directives/dev/Stub.js +14 -0
  296. package/transpiled/directives/dev/Stub.js.map +1 -0
  297. package/transpiled/directives/dev/index.d.ts +2 -0
  298. package/transpiled/directives/dev/index.js +7 -0
  299. package/transpiled/directives/dev/index.js.map +1 -0
  300. package/transpiled/directives/dev/types.d.ts +4 -0
  301. package/transpiled/directives/dev/types.js +3 -0
  302. package/transpiled/directives/dev/types.js.map +1 -0
  303. package/transpiled/directives/index.d.ts +2 -0
  304. package/transpiled/directives/index.js +10 -0
  305. package/transpiled/directives/index.js.map +1 -0
  306. package/transpiled/discovery.d.ts +1 -0
  307. package/transpiled/discovery.js +3 -0
  308. package/transpiled/discovery.js.map +1 -0
  309. package/transpiled/exceptions.d.ts +2 -0
  310. package/transpiled/exceptions.js +39 -0
  311. package/transpiled/exceptions.js.map +1 -0
  312. package/transpiled/index.d.ts +5 -0
  313. package/transpiled/index.js +12 -0
  314. package/transpiled/index.js.map +1 -0
  315. package/transpiled/manifest.d.ts +3 -0
  316. package/transpiled/manifest.js +30 -0
  317. package/transpiled/manifest.js.map +1 -0
  318. package/transpiled/root.d.ts +2 -0
  319. package/transpiled/root.js +39 -0
  320. package/transpiled/root.js.map +1 -0
  321. package/transpiled/schemas.d.ts +3 -0
  322. package/transpiled/schemas.js +14 -0
  323. package/transpiled/schemas.js.map +1 -0
  324. package/transpiled/tsconfig.tsbuildinfo +1 -0
  325. package/tsconfig.json +12 -0
  326. package/src/.manifest/index.js +0 -7
  327. package/src/.manifest/normalize.js +0 -58
  328. package/src/.manifest/schema.yaml +0 -71
  329. package/src/.manifest/validate.js +0 -17
  330. package/src/constants.js +0 -3
  331. package/src/deployment.js +0 -23
  332. package/src/exposition.js +0 -68
  333. package/src/factory.js +0 -76
  334. package/src/index.js +0 -9
  335. package/src/manifest.js +0 -12
  336. package/src/query/criteria.js +0 -55
  337. package/src/query/enum.js +0 -35
  338. package/src/query/index.js +0 -17
  339. package/src/query/query.js +0 -60
  340. package/src/query/range.js +0 -28
  341. package/src/query/sort.js +0 -19
  342. package/src/remote.js +0 -88
  343. package/src/server.js +0 -83
  344. package/src/tenant.js +0 -29
  345. package/src/translate/etag.js +0 -14
  346. package/src/translate/index.js +0 -7
  347. package/src/translate/request.js +0 -68
  348. package/src/translate/response.js +0 -62
  349. package/src/tree.js +0 -109
  350. package/test/manifest.normalize.fixtures.js +0 -37
  351. package/test/manifest.normalize.test.js +0 -37
  352. package/test/manifest.validate.test.js +0 -40
  353. package/test/query.range.test.js +0 -18
  354. package/test/tree.fixtures.js +0 -21
  355. package/test/tree.test.js +0 -44
  356. package/types/annotations.d.ts +0 -10
  357. package/types/declarations.d.ts +0 -31
  358. package/types/exposition.d.ts +0 -13
  359. package/types/http.d.ts +0 -13
  360. package/types/query.d.ts +0 -16
  361. package/types/remote.d.ts +0 -19
  362. package/types/server.d.ts +0 -13
  363. package/types/tree.d.ts +0 -33
@@ -0,0 +1,47 @@
1
+ import { Node, type Properties } from './Node'
2
+ import { Route } from './Route'
3
+ import { type Context } from './Context'
4
+ import { segment } from './segment'
5
+ import { Method, type Methods } from './Method'
6
+ import { type Endpoint } from './Endpoint'
7
+ import { type Directives } from './Directives'
8
+ import type * as syntax from './syntax'
9
+
10
+ export function createNode<TEndpoint extends Endpoint, TDirectives extends Directives>
11
+ (node: syntax.Node, context: Context): Node<TEndpoint, TDirectives> {
12
+ if (node.isolated === true)
13
+ context.directives.stack = node.directives
14
+ else
15
+ context.directives.stack = node.directives.concat(context.directives.stack)
16
+
17
+ const routes: Route[] = node.routes.map((route) => createRoute(route, context))
18
+ const methods: Methods = {}
19
+
20
+ for (const method of node.methods)
21
+ methods[method.verb] = createMethod(method, context)
22
+
23
+ const properties: Properties = { protected: node.protected ?? context.protected }
24
+
25
+ return new Node(routes, methods, properties)
26
+ }
27
+
28
+ function createRoute (route: syntax.Route, context: Context): Route {
29
+ const stack = context.directives.stack.slice()
30
+ const segments = segment(route.path)
31
+ const node = createNode(route.node, context)
32
+
33
+ context.directives.stack = stack // restore
34
+
35
+ return new Route(segments, node)
36
+ }
37
+
38
+ function createMethod (method: syntax.Method, context: Context): Method {
39
+ const stack = context.directives.stack.concat(method.directives.reverse())
40
+ const directives = context.directives.factory.create(stack)
41
+
42
+ const endpoint = method.mapping?.endpoint === undefined
43
+ ? null
44
+ : context.endpoints.create(method, context)
45
+
46
+ return new Method(endpoint, directives)
47
+ }
@@ -0,0 +1,8 @@
1
+ export * from './Tree'
2
+ export * from './Node'
3
+ export * from './Match'
4
+ export * from './Method'
5
+ export * from './Context'
6
+ export * from './Endpoint'
7
+ export * from './Directives'
8
+ export * as syntax from './syntax'
@@ -0,0 +1,32 @@
1
+ import { segment, fragment } from './segment'
2
+
3
+ it('should return segments', async () => {
4
+ const segments = segment('/foo/bar/')
5
+
6
+ expect(segments).toHaveLength(2)
7
+ expect(segments[0].fragment).toBe('foo')
8
+ expect(segments[1].fragment).toBe('bar')
9
+ })
10
+
11
+ it('should parse placeholders', async () => {
12
+ const segments = segment('/foo/:id/')
13
+
14
+ expect(segments).toHaveLength(2)
15
+ expect(segments[0].fragment).toBe('foo')
16
+ expect(segments[1].fragment).toBeNull()
17
+
18
+ // helping typescript
19
+ if (segments[1].fragment !== null) throw new Error('?')
20
+
21
+ expect(segments[1].placeholder).toBe('id')
22
+ })
23
+
24
+ it('should handle root path', async () => {
25
+ expect(segment('/')).toStrictEqual([])
26
+ })
27
+
28
+ it('should split', async () => {
29
+ const parts = fragment('/foo/bar/')
30
+
31
+ expect(parts).toStrictEqual(['foo', 'bar'])
32
+ })
@@ -0,0 +1,25 @@
1
+ export function segment (path: string): Segment[] {
2
+ return fragment(path).map(parse)
3
+ }
4
+
5
+ export function fragment (path: string): string[] {
6
+ const parts = path.split('/')
7
+
8
+ // trailing slash
9
+ if (parts[parts.length - 1] === '') parts.length--
10
+
11
+ // leading slash
12
+ return parts.splice(1)
13
+ }
14
+
15
+ function parse (segment: string): Segment {
16
+ if (segment[0] === ':') return { fragment: null, placeholder: segment.substring(1) }
17
+ else return { fragment: segment }
18
+ }
19
+
20
+ export type Segment = {
21
+ fragment: string
22
+ } | {
23
+ fragment: null
24
+ placeholder: string
25
+ }
@@ -0,0 +1,2 @@
1
+ export * from './parse'
2
+ export * from './types'
@@ -0,0 +1,188 @@
1
+ import { parse } from './parse'
2
+
3
+ describe('routes', () => {
4
+ it('should parse route', async () => {
5
+ const declaration = {
6
+ '/': {},
7
+ '/foo': {}
8
+ }
9
+
10
+ const node = parse(declaration)
11
+
12
+ expect(node.routes).toHaveLength(2)
13
+ expect(node.routes[0].path).toBe('/')
14
+ expect(node.routes[1].path).toBe('/foo')
15
+ })
16
+
17
+ it('should parse nested routes', async () => {
18
+ const declaration = {
19
+ '/': {
20
+ '/foo': {},
21
+ '/bar': {}
22
+ }
23
+ }
24
+
25
+ const node = parse(declaration)
26
+ const root = node.routes[0].node
27
+
28
+ expect(root.routes).toHaveLength(2)
29
+ expect(root.routes[0].path).toBe('/foo')
30
+ expect(root.routes[1].path).toBe('/bar')
31
+ })
32
+ })
33
+
34
+ describe('methods', () => {
35
+ it('should parse methods', () => {
36
+ const declaration = {
37
+ '/': {
38
+ GET: {
39
+ endpoint: 'observe'
40
+ }
41
+ }
42
+ }
43
+
44
+ const node = parse(declaration)
45
+ const root = node.routes[0].node
46
+
47
+ expect(root.methods).toHaveLength(1)
48
+ expect(root.methods[0].verb).toBe('GET')
49
+ expect(root.methods[0].mapping).toMatchObject({ endpoint: 'observe' })
50
+ })
51
+
52
+ it('should parse endpoint shortcut', async () => {
53
+ const declaration = {
54
+ '/': {
55
+ GET: 'observe'
56
+ }
57
+ }
58
+
59
+ const node = parse(declaration)
60
+ const root = node.routes[0].node
61
+
62
+ expect(root.methods).toHaveLength(1)
63
+ expect(root.methods[0].verb).toBe('GET')
64
+ expect(root.methods[0].mapping).toMatchObject({ endpoint: 'observe' })
65
+ })
66
+
67
+ it('should parse fq endpoint', async () => {
68
+ const declaration = {
69
+ '/': {
70
+ GET: 'dummies.dummy.observe'
71
+ }
72
+ }
73
+
74
+ const node = parse(declaration)
75
+ const root = node.routes[0].node
76
+
77
+ expect(root.methods).toHaveLength(1)
78
+ expect(root.methods[0].verb).toBe('GET')
79
+
80
+ expect(root.methods[0].mapping).toMatchObject({
81
+ namespace: 'dummies',
82
+ component: 'dummy',
83
+ endpoint: 'observe'
84
+ })
85
+ })
86
+
87
+ it('should parse fq endpoint within default namespace', async () => {
88
+ const declaration = {
89
+ '/': {
90
+ GET: 'dummy.observe'
91
+ }
92
+ }
93
+
94
+ const node = parse(declaration)
95
+ const root = node.routes[0].node
96
+
97
+ expect(root.methods).toHaveLength(1)
98
+ expect(root.methods[0].verb).toBe('GET')
99
+
100
+ expect(root.methods[0].mapping).toMatchObject({
101
+ namespace: 'default',
102
+ component: 'dummy',
103
+ endpoint: 'observe'
104
+ })
105
+ })
106
+
107
+ it('should parse directives', async () => {
108
+ const declaration = {
109
+ '/': {
110
+ GET: {
111
+ 'auth:incept': 'id',
112
+ endpoint: 'observe'
113
+ }
114
+ }
115
+ }
116
+
117
+ const node = parse(declaration)
118
+ const root = node.routes[0].node
119
+
120
+ expect(root.methods[0].directives)
121
+ .toStrictEqual([{ family: 'auth', name: 'incept', value: 'id' }])
122
+ })
123
+ })
124
+
125
+ describe('directives', () => {
126
+ it('should parse shortcuts', async () => {
127
+ const declaration = {
128
+ '/': {
129
+ foo: 'baz'
130
+ }
131
+ }
132
+
133
+ const shortcuts = new Map<string, string>([
134
+ ['foo', 'dev:foo']
135
+ ])
136
+
137
+ const node = parse(declaration, shortcuts)
138
+ const root = node.routes[0].node
139
+
140
+ expect(root.directives).toHaveLength(1)
141
+ expect(root.directives[0].family).toBe('dev')
142
+ expect(root.directives[0].name).toBe('foo')
143
+ expect(root.directives[0].value).toBe('baz')
144
+ })
145
+ })
146
+
147
+ describe('validation', () => {
148
+ it('should throw on unknown key', async () => {
149
+ const declaration = { hello: 'world' }
150
+
151
+ expect(() => parse(declaration)).toThrow('RTD parse error: unknown key \'hello\'.')
152
+ })
153
+
154
+ it('should throw on invalid mapping', async () => {
155
+ const declaration = {
156
+ '/': {
157
+ GET: {
158
+ endpoint: 'observe',
159
+ hello: 'world'
160
+ }
161
+ }
162
+ }
163
+
164
+ expect(() => parse(declaration)).toThrow('/methods/0/mapping')
165
+ })
166
+ })
167
+
168
+ it('should expand ranges', async () => {
169
+ const declaration = {
170
+ '/': {
171
+ GET: {
172
+ endpoint: 'enumerate',
173
+ query: {
174
+ omit: 3,
175
+ limit: 2
176
+ }
177
+ }
178
+ }
179
+ }
180
+
181
+ const node = parse(declaration)
182
+ const query = node.routes[0].node.methods[0].mapping?.query
183
+
184
+ expect(query).toMatchObject({
185
+ omit: { value: 3, range: [3, 3] },
186
+ limit: { value: 2, range: [2, 2] }
187
+ })
188
+ })
@@ -0,0 +1,153 @@
1
+ import * as schemas from '../../schemas'
2
+ import {
3
+ verbs,
4
+ type Node,
5
+ type Route,
6
+ type Method,
7
+ type Mapping,
8
+ type Directive, type Range
9
+ } from './types'
10
+
11
+ export function parse (input: object, shortcuts?: Shortcuts): Node {
12
+ const node = parseNode(input, shortcuts)
13
+
14
+ schemas.node.validate(node)
15
+
16
+ return node
17
+ }
18
+
19
+ function parseNode (input: object, shortcuts?: Shortcuts): Node {
20
+ const node = createNode()
21
+
22
+ for (const [key, value] of Object.entries(input)) {
23
+ if (PROPERTIES.includes(key as keyof Node)) {
24
+ node[key as keyof Node] = value
25
+
26
+ continue
27
+ }
28
+
29
+ if (key[0] === '/') {
30
+ const route = parseRoute(key, value, shortcuts)
31
+
32
+ node.routes.push(route)
33
+
34
+ continue
35
+ }
36
+
37
+ if (verbs.has(key)) {
38
+ const method = parseMethod(key, value, shortcuts)
39
+
40
+ node.methods.push(method)
41
+
42
+ continue
43
+ }
44
+
45
+ const directive = parseDirective(key, value, shortcuts)
46
+
47
+ if (directive !== null) {
48
+ node.directives.push(directive)
49
+
50
+ continue
51
+ }
52
+
53
+ throw new Error(`RTD parse error: unknown key '${key}'.`)
54
+ }
55
+
56
+ return node
57
+ }
58
+
59
+ export function createNode (): Node {
60
+ return {
61
+ routes: [],
62
+ methods: [],
63
+ directives: []
64
+ }
65
+ }
66
+
67
+ function parseRoute (path: string, value: object, shortcuts?: Shortcuts): Route {
68
+ const node = parse(value, shortcuts)
69
+
70
+ return createRoute(path, node)
71
+ }
72
+
73
+ function createRoute (path: string, node: Node): Route {
74
+ return { path, node }
75
+ }
76
+
77
+ function parseMethod (verb: string, value: Mapping | string, shortcuts?: Shortcuts): Method {
78
+ const mapping = typeof value === 'string' ? { endpoint: value } : value
79
+
80
+ parseEndpoint(mapping)
81
+ parseQuery(mapping)
82
+
83
+ const directives = parseDirectives(mapping, shortcuts)
84
+
85
+ return { verb, mapping, directives }
86
+ }
87
+
88
+ function parseEndpoint (mapping: Mapping): void {
89
+ if (mapping.endpoint === undefined)
90
+ return
91
+
92
+ const [endpoiont, component, namespace] = mapping.endpoint.split('.').reverse()
93
+
94
+ if (component !== undefined) {
95
+ mapping.component = component
96
+ mapping.namespace = namespace ?? mapping.namespace ?? 'default'
97
+ mapping.endpoint = endpoiont
98
+ }
99
+ }
100
+
101
+ function parseQuery (mapping: any): void {
102
+ const query = mapping.query
103
+
104
+ if (query === undefined)
105
+ return
106
+
107
+ if (typeof query.limit === 'number')
108
+ query.limit = expandRange(query.limit)
109
+
110
+ if (typeof query.omit === 'number')
111
+ query.omit = expandRange(query.omit)
112
+ }
113
+
114
+ function parseDirectives (mapping: Record<string, any>, shortcuts?: Shortcuts): Directive[] {
115
+ const directives: Directive[] = []
116
+
117
+ for (const [key, value] of Object.entries(mapping)) {
118
+ const directive = parseDirective(key, value, shortcuts)
119
+
120
+ if (directive === null)
121
+ continue
122
+
123
+ directives.push(directive)
124
+
125
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
126
+ delete mapping[key]
127
+ }
128
+
129
+ return directives
130
+ }
131
+
132
+ function parseDirective (key: string, value: any, shortcuts?: Shortcuts): Directive | null {
133
+ if (shortcuts?.has(key) === true)
134
+ key = shortcuts.get(key) as string
135
+
136
+ const match = key.match(DIRECTIVE_RX)
137
+
138
+ if (match === null)
139
+ return null
140
+
141
+ const { family, name } = match.groups as { family: string, name: string }
142
+
143
+ return { family, name, value }
144
+ }
145
+
146
+ function expandRange (range: number): Range {
147
+ return { value: range, range: [range, range] }
148
+ }
149
+
150
+ const DIRECTIVE_RX = /^(?<family>\w{1,32}):(?<name>\w{1,32})$/
151
+ const PROPERTIES: Array<keyof Node> = ['protected', 'isolated']
152
+
153
+ export type Shortcuts = Map<string, string>
@@ -0,0 +1,48 @@
1
+ export interface Node {
2
+ protected?: boolean
3
+ isolated?: boolean
4
+ routes: Route[]
5
+ methods: Method[]
6
+ directives: Directive[]
7
+ }
8
+
9
+ export interface Route {
10
+ path: string
11
+ node: Node
12
+ }
13
+
14
+ export interface Method {
15
+ verb: string
16
+ mapping?: Mapping
17
+ directives: Directive[]
18
+ }
19
+
20
+ export interface Directive {
21
+ family: string
22
+ name: string
23
+ value: any
24
+ }
25
+
26
+ export interface Mapping {
27
+ namespace?: string
28
+ component?: string
29
+ endpoint: string
30
+ query?: Query
31
+ }
32
+
33
+ export interface Query {
34
+ id?: string
35
+ criteria?: string
36
+ sort?: string
37
+ omit: Range
38
+ limit: Range
39
+ selectors?: string[]
40
+ projection?: string[]
41
+ }
42
+
43
+ export interface Range {
44
+ value?: number
45
+ range: [number, number]
46
+ }
47
+
48
+ export const verbs = new Set<string>(['GET', 'POST', 'PUT', 'PATCH', 'DELETE'])
@@ -0,0 +1,41 @@
1
+ import { generate } from 'randomstring'
2
+ import { Connector } from '@toa.io/core'
3
+ import { Remotes } from './Remotes'
4
+ import type { Bootloader } from './Factory'
5
+ import type { Locator } from '@toa.io/core'
6
+
7
+ jest.mock('@toa.io/boot', () => ({
8
+ remote: async (locator: Locator) => await boot.remote(locator)
9
+ }))
10
+
11
+ const boot = {
12
+ remote: jest.fn(async (..._) => ({
13
+ link: jest.fn((connector: Connector) => undefined)
14
+ }))
15
+ } as unknown as jest.MockedObjectDeep<Bootloader>
16
+
17
+ const namespace = generate()
18
+ const name = generate()
19
+
20
+ let remotes: Remotes
21
+
22
+ beforeEach(() => {
23
+ remotes = new Remotes(boot)
24
+ })
25
+
26
+ it('should create remote', async () => {
27
+ const remote = await remotes.discover(namespace, name)
28
+
29
+ expect(boot.remote).toHaveBeenCalledWith(expect.objectContaining({ namespace, name }))
30
+ expect(remote).toStrictEqual(await boot.remote.mock.results[0].value)
31
+ })
32
+
33
+ it('should be instance of Connector', async () => {
34
+ expect(remotes).toBeInstanceOf(Connector)
35
+ })
36
+
37
+ it('should depend on created remotes', async () => {
38
+ const remote = await remotes.discover(namespace, name)
39
+
40
+ expect(remote.link).toHaveBeenCalledWith(remotes)
41
+ })
@@ -0,0 +1,20 @@
1
+ import { Locator, Connector, type Component } from '@toa.io/core'
2
+ import { type Bootloader } from './Factory'
3
+
4
+ export class Remotes extends Connector {
5
+ private readonly boot: Bootloader
6
+
7
+ public constructor (boot: Bootloader) {
8
+ super()
9
+ this.boot = boot
10
+ }
11
+
12
+ public async discover (namespace: string, name: string): Promise<Component> {
13
+ const locator = new Locator(name, namespace)
14
+ const remote = await this.boot.remote(locator)
15
+
16
+ this.depends(remote)
17
+
18
+ return remote
19
+ }
20
+ }
@@ -0,0 +1,38 @@
1
+ import { Connector, type Locator, type bindings } from '@toa.io/core'
2
+ import { type Label } from './discovery'
3
+ import { type Branch } from './Branch'
4
+ import type * as RTD from './RTD/syntax'
5
+
6
+ export class Tenant extends Connector {
7
+ private readonly broadcast: Broadcast
8
+ private readonly branch: Branch
9
+
10
+ public constructor (broadcast: Broadcast, locator: Locator, node: RTD.Node) {
11
+ super()
12
+
13
+ this.broadcast = broadcast
14
+
15
+ this.branch = {
16
+ namespace: locator.namespace,
17
+ component: locator.name,
18
+ isolated: locator.namespace === 'identity',
19
+ node
20
+ }
21
+
22
+ this.depends(broadcast)
23
+ }
24
+
25
+ public override async open (): Promise<void> {
26
+ await this.expose()
27
+ await this.broadcast.receive('ping', this.expose.bind(this))
28
+
29
+ console.info('Exposition Tenant for ' +
30
+ `'${this.branch.namespace}.${this.branch.component}' has started.`)
31
+ }
32
+
33
+ private async expose (): Promise<void> {
34
+ await this.broadcast.transmit('expose', this.branch)
35
+ }
36
+ }
37
+
38
+ type Broadcast = bindings.Broadcast<Label>
@@ -0,0 +1,42 @@
1
+ import { type Dependency, type Service } from '@toa.io/operations'
2
+ import { encode } from '@toa.io/generic'
3
+ import { type Annotation } from './Annotation'
4
+ import * as schemas from './schemas'
5
+ import { shortcuts } from './Directive'
6
+ import { components } from './Composition'
7
+ import { parse } from './RTD/syntax'
8
+
9
+ export function deployment (_: unknown, annotation: Annotation | undefined): Dependency {
10
+ const labels = components().labels
11
+
12
+ const service: Service = {
13
+ group: 'exposition',
14
+ name: 'gateway',
15
+ port: 8000,
16
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
17
+ version: require('../package.json').version,
18
+ variables: [],
19
+ components: labels
20
+ }
21
+
22
+ if (annotation?.host !== undefined)
23
+ service.ingress = {
24
+ host: annotation.host,
25
+ class: annotation.class,
26
+ annotations: annotation.annotations
27
+ }
28
+
29
+ if (annotation?.['/'] !== undefined) {
30
+ annotation['/'] = parse(annotation['/'], shortcuts)
31
+
32
+ service.variables.push({
33
+ name: 'TOA_EXPOSITION',
34
+ value: encode(annotation['/'])
35
+ })
36
+ }
37
+
38
+ if (annotation !== undefined)
39
+ schemas.annotaion.validate(annotation)
40
+
41
+ return { services: [service] }
42
+ }
@@ -0,0 +1,14 @@
1
+ import { type Directive, type Input } from './types'
2
+
3
+ export class Anonymous implements Directive {
4
+ private readonly allow: boolean
5
+
6
+ public constructor (allow: boolean) {
7
+ this.allow = allow
8
+ }
9
+
10
+ public authorize (_: any, input: Input): boolean {
11
+ if ('authorization' in input.headers) return false
12
+ else return this.allow
13
+ }
14
+ }
@@ -0,0 +1,12 @@
1
+ import { type OutgoingMessage } from '../../HTTP'
2
+ import { type Directive, type Identity } from './types'
3
+
4
+ export class Echo implements Directive {
5
+ public authorize (identity: Identity | null): boolean {
6
+ return identity !== null
7
+ }
8
+
9
+ public reply (identity: Identity | null): OutgoingMessage {
10
+ return { body: identity }
11
+ }
12
+ }