@toa.io/userland 1.0.0-dev.1 → 1.1.0-dev.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 (29) hide show
  1. package/example/components/echo/manifest.toa.yaml +5 -0
  2. package/example/components/echo/operations/get.js +7 -0
  3. package/example/components/echo/operations/resolve.js +7 -0
  4. package/example/components/echo/samples/get.yaml +12 -0
  5. package/example/components/web/manifest.toa.yaml +8 -0
  6. package/example/components/web/operations/get.js +10 -0
  7. package/example/components/web/samples/get.yaml +6 -0
  8. package/package.json +9 -9
  9. package/samples/notes.md +2 -2
  10. package/samples/readme.md +37 -5
  11. package/samples/src/.replay/.suite/translate/.operation/.prepare/cast.js +20 -0
  12. package/samples/src/.replay/.suite/translate/.operation/.prepare/cast.test.js +95 -0
  13. package/samples/src/.replay/.suite/translate/.operation/.prepare/expand.js +14 -0
  14. package/samples/src/.replay/.suite/translate/.operation/.prepare/index.js +7 -0
  15. package/samples/src/.replay/.suite/translate/.operation/.prepare/shortcuts/.aspect.js +26 -0
  16. package/samples/src/.replay/.suite/translate/.operation/.prepare/shortcuts/configuration.js +5 -0
  17. package/samples/src/.replay/.suite/translate/.operation/.prepare/shortcuts/http.js +5 -0
  18. package/samples/src/.replay/.suite/translate/.operation/.prepare/shortcuts/index.js +9 -0
  19. package/samples/src/.replay/.suite/translate/.operation/.prepare/shortcuts/state.js +5 -0
  20. package/samples/src/.replay/.suite/translate/.operation/.prepare/types/async.js +3 -0
  21. package/samples/src/.replay/.suite/translate/.operation/.prepare/types/cast.js +26 -0
  22. package/samples/src/.replay/.suite/translate/.operation/.prepare/types/index.js +5 -0
  23. package/samples/src/.replay/.suite/translate/.operation/.prepare/types/map.js +3 -0
  24. package/samples/src/.replay/.suite/translate/.operation/.prepare/types/set.js +3 -0
  25. package/samples/src/.replay/.suite/translate/.operation/.prepare/types/sync.js +3 -0
  26. package/samples/src/.replay/.suite/translate/.operation/prepare.js +4 -4
  27. package/samples/types/operation.d.ts +2 -2
  28. package/samples/src/.replay/.suite/translate/.operation/specials/configuration.js +0 -26
  29. package/samples/src/.replay/.suite/translate/.operation/specials/index.js +0 -5
@@ -3,6 +3,11 @@ name: echo
3
3
  operations:
4
4
  signal:
5
5
  output: string
6
+ get:
7
+ output: string
8
+ resolve:
9
+ input: string
10
+ output: string
6
11
 
7
12
  configuration:
8
13
  signal: quack
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+
3
+ async function computation (_, context) {
4
+ return context.state.value
5
+ }
6
+
7
+ exports.computation = computation
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+
3
+ async function computation (key, context) {
4
+ return context.state.values.get(key)
5
+ }
6
+
7
+ exports.computation = computation
@@ -0,0 +1,12 @@
1
+ title: Should return value
2
+ extensions:
3
+ state:
4
+ - result:
5
+ value: bar
6
+ permanent: true
7
+ output: bar
8
+ ---
9
+ title: Should return value (declaration with a shortcut)
10
+ state:
11
+ value: bar
12
+ output: bar
@@ -0,0 +1,8 @@
1
+ name: web
2
+
3
+ operations:
4
+ get:
5
+ output: object
6
+
7
+ origins:
8
+ dev: https://null
@@ -0,0 +1,10 @@
1
+ 'use strict'
2
+
3
+ async function computation (_, context) {
4
+ const response = await context.http.dev.path.to.resource.get()
5
+ const output = await response.json()
6
+
7
+ return { output }
8
+ }
9
+
10
+ exports.computation = computation
@@ -0,0 +1,6 @@
1
+ title: Should return response
2
+ http:
3
+ json:async:
4
+ foo: bar
5
+ output:
6
+ foo: bar
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/userland",
3
- "version": "1.0.0-dev.1",
3
+ "version": "1.1.0-dev.0",
4
4
  "description": "Toa development kit",
5
5
  "homepage": "https://toa.io",
6
6
  "author": {
@@ -24,14 +24,14 @@
24
24
  "main": "src/index.js",
25
25
  "types": "types/index.d.ts",
26
26
  "dependencies": {
27
- "@toa.io/boot": "1.0.0-dev.1",
28
- "@toa.io/core": "1.0.0-dev.1",
29
- "@toa.io/filesystem": "1.0.0-dev.1",
30
- "@toa.io/generic": "0.9.1-dev.0",
31
- "@toa.io/norm": "1.0.0-dev.1",
32
- "@toa.io/schemas": "0.8.2-dev.1",
33
- "@toa.io/yaml": "0.7.4-dev.1",
27
+ "@toa.io/boot": "1.0.1-dev.0",
28
+ "@toa.io/core": "1.0.1-dev.0",
29
+ "@toa.io/filesystem": "1.0.1-dev.0",
30
+ "@toa.io/generic": "0.10.0-dev.0",
31
+ "@toa.io/norm": "1.0.1-dev.0",
32
+ "@toa.io/schemas": "0.8.3-dev.0",
33
+ "@toa.io/yaml": "0.7.5-dev.0",
34
34
  "tap": "16.3.4"
35
35
  },
36
- "gitHead": "1121d84f7051a0d54ecf899efeaf3b328d7894c0"
36
+ "gitHead": "5e7998b39ea6111a2cf7d38fad8aeae71d742621"
37
37
  }
package/samples/notes.md CHANGED
@@ -3,10 +3,10 @@
3
3
  ## Extending Replay
4
4
 
5
5
  1. Update [`example`](../example). `toa replay` should either fail or throw exception.
6
- 2. Add [feature](./features). It should fail.
6
+ 2. Add [feature](/features). It should fail.
7
7
  3. If the new decorator is being developed:
8
8
  1. Add the [decorator](/extensions/sampling/docs/replay.md).
9
9
  2. Add [boot extension](/runtime/boot/src/extensions).
10
10
  4. Update `suite` [translation](./src/.suite/.component/translate.js).
11
- 5. Ensure feature is passing.
11
+ 5. Ensure the feature is passing.
12
12
  6. Ensure example is passing with `toa replay`.
package/samples/readme.md CHANGED
@@ -26,14 +26,14 @@ to be verified. See its [schema](./src/.replay/.suite/translate/schemas/operatio
26
26
  > as [multi-document YAML files](https://yaml.org/spec/1.2.2/#22-structures).
27
27
 
28
28
  Operation samples must be located under the `samples` directory in the component or context root.
29
- Sample file names must follow the convention: `namespace.name.operation.yaml`, that is, be an
29
+ Sample file names must follow the convention: `namespace.component.operation.yaml`, that is, be an
30
30
  endpoint of the operation samples to be applied to. For component-level sample files `namespace`
31
31
  and `name` must match corresponding component, therefore are optional.
32
32
 
33
33
  ## Message Samples
34
34
 
35
35
  Message Sample is an object containing receiver's input (`payload`) to be substituted and
36
- outcomes (`input` and `query`) to be verified. Message sample may contain corresponding operation
36
+ outcomes (`input` and `query`) to be verified. Message sample may contain the corresponding operation
37
37
  sample. See its [schema](./src/.replay/.suite/translate/schemas/message.cos.yaml).
38
38
 
39
39
  > Message samples are always [autonomous](#autonomy).
@@ -42,15 +42,47 @@ sample. See its [schema](./src/.replay/.suite/translate/schemas/message.cos.yaml
42
42
 
43
43
  Operation samples must be located under the `samples/messages` directory in the component or context
44
44
  root. Samples file names must follow the convention `namesace.component.event.yaml`, that is, be a
45
- label
46
- of the event receiver is consuming.
45
+ label of the event receiver is consuming.
46
+
47
+ #### Aspect Shortcuts
48
+
49
+ - `configuration` for [Configuration](/extensions/configuration)
50
+ - `state` for [State](/extensions/state)
51
+ - `http` for HTTP Aspect from [Origins](/extensions/origins)
52
+
53
+ #### Aspect Result Type Hints
54
+
55
+ When using aspect calls, there might be situations where the returned values cannot be adequately described using YAML.
56
+ To address this issue, type hints can be used.
57
+
58
+ ```yaml
59
+ state:
60
+ values:Map:
61
+ foo: 1
62
+ bar: 2
63
+ ```
64
+
65
+ In the above code snippet, the `state` Aspect returns a `values` field of type Map.
66
+
67
+ ```yaml
68
+ state:
69
+ values:Set: [foo, bar]
70
+ ```
71
+
72
+ `sync` and `async` hints define functions:
73
+
74
+ ```yaml
75
+ state:
76
+ get:sync: foo
77
+ request:async: bar
78
+ ```
47
79
 
48
80
  ## Autonomy
49
81
 
50
82
  Component level samples are *autonomous*, namely, does not assume actual remote calls as
51
83
  replaying of component-level samples will boot only that component. Remote call attempt not declared
52
84
  within sample will cause an exception.
53
- See [examples](../example/components/math/calculations/samples).
85
+ See [examples](../example/components/math.calculations/samples).
54
86
 
55
87
  For context level samples (integration samples), remote calls with non-declared outputs will be
56
88
  actually performed. Replaying these samples will boot the composition with all components of the
@@ -0,0 +1,20 @@
1
+ 'use strict'
2
+
3
+ const { plain } = require('@toa.io/generic')
4
+ const types = require('./types')
5
+
6
+ /**
7
+ * @param {toa.sampling.request.Extensions} extensions
8
+ */
9
+ function cast (extensions) {
10
+ for (const calls of Object.values(extensions)) calls.map(resolve)
11
+ }
12
+
13
+ /**
14
+ * @param {toa.sampling.request.extensions.Call} call
15
+ */
16
+ function resolve (call) {
17
+ if (plain(call.result)) call.result = types.cast(call.result)
18
+ }
19
+
20
+ exports.cast = cast
@@ -0,0 +1,95 @@
1
+ 'use strict'
2
+
3
+ const { cast } = require('./')
4
+
5
+ it('should be', async () => {
6
+ expect(cast).toBeInstanceOf(Function)
7
+ })
8
+
9
+ it('should cast to a Map', async () => {
10
+ /** @type {toa.sampling.request.Extensions} */
11
+ const extensions = {
12
+ state: [{
13
+ result: {
14
+ 'value:Map': { foo: 'bar' }
15
+ }
16
+ }]
17
+ }
18
+
19
+ cast(extensions)
20
+
21
+ const value = /** @type {Map} */ extensions.state[0].result.value
22
+
23
+ expect(value).toBeDefined()
24
+ expect(value).toBeInstanceOf(Map)
25
+ expect(Array.from(value.keys())).toStrictEqual(['foo'])
26
+ expect(value.get('foo')).toStrictEqual('bar')
27
+ })
28
+
29
+ it('should not throw if result is undefined', async () => {
30
+ const extensions = { state: [{}] }
31
+
32
+ expect(() => cast(extensions)).not.toThrow()
33
+ })
34
+
35
+ it('should cast to a Set', async () => {
36
+ const names = ['Mary', 'Bob', 'Elizabeth']
37
+
38
+ /** @type {toa.sampling.request.Extensions} */
39
+ const extensions = {
40
+ state: [{
41
+ result: {
42
+ 'names:Set': names
43
+ }
44
+ }]
45
+ }
46
+
47
+ cast(extensions)
48
+
49
+ const set = /** @type {Map} */ extensions.state[0].result.names
50
+
51
+ expect(set).toBeDefined()
52
+ expect(set).toBeInstanceOf(Set)
53
+ expect(Array.from(set)).toStrictEqual(names)
54
+ })
55
+
56
+ it('should cast to a Function', async () => {
57
+ const extensions = {
58
+ foo: [{
59
+ result: {
60
+ 'resolve:sync': 'hello'
61
+ }
62
+ }]
63
+ }
64
+
65
+ cast(extensions)
66
+
67
+ const resolve = extensions.foo[0].result.resolve
68
+
69
+ expect(resolve).toBeDefined()
70
+ expect(resolve).toBeInstanceOf(Function)
71
+ expect(resolve()).toStrictEqual('hello')
72
+ })
73
+
74
+ it('should cast to async Function', async () => {
75
+ const extensions = {
76
+ foo: [{
77
+ result: {
78
+ 'resolve:async': 'hello'
79
+ }
80
+ }]
81
+ }
82
+
83
+ cast(extensions)
84
+
85
+ const resolve = extensions.foo[0].result.resolve
86
+
87
+ expect(resolve).toBeDefined()
88
+ expect(resolve).toBeInstanceOf(Function)
89
+
90
+ const promise = resolve()
91
+
92
+ expect(promise).toBeInstanceOf(Promise)
93
+ await expect(promise).resolves.toStrictEqual('hello')
94
+ })
95
+
@@ -0,0 +1,14 @@
1
+ 'use strict'
2
+
3
+ const shortcuts = require('./shortcuts')
4
+
5
+ /**
6
+ * @param {toa.samples.Operation & Object} declaration
7
+ */
8
+ function expand (declaration) {
9
+ for (const [shortcut, expand] of Object.entries(shortcuts)) {
10
+ if (shortcut in declaration) expand(declaration)
11
+ }
12
+ }
13
+
14
+ exports.expand = expand
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+
3
+ const { expand } = require('./expand')
4
+ const { cast } = require('./cast')
5
+
6
+ exports.expand = expand
7
+ exports.cast = cast
@@ -0,0 +1,26 @@
1
+ 'use strict'
2
+
3
+ const aspect = (name) =>
4
+ /**
5
+ * @param {toa.samples.Operation & Record<string, any>} declaration
6
+ */
7
+ (declaration) => {
8
+ const value = declaration[name]
9
+
10
+ delete declaration[name]
11
+
12
+ if (declaration.extensions === undefined) declaration.extensions = {}
13
+
14
+ if (name in declaration.extensions) throw new Error(`${name} aspect sample is ambiguous`)
15
+
16
+ /** @type {toa.sampling.request.extensions.Call} */
17
+ const call = {
18
+ result: value,
19
+ permanent: true
20
+ }
21
+
22
+ declaration.extensions[name] = [call]
23
+
24
+ }
25
+
26
+ exports.aspect = aspect
@@ -0,0 +1,5 @@
1
+ 'use strict'
2
+
3
+ const { aspect } = require('./.aspect')
4
+
5
+ exports.configuration = aspect('configuration')
@@ -0,0 +1,5 @@
1
+ 'use strict'
2
+
3
+ const { aspect } = require('./.aspect')
4
+
5
+ exports.http = aspect('http')
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+
3
+ const { configuration } = require('./configuration')
4
+ const { state } = require('./state')
5
+ const { http } = require('./http')
6
+
7
+ exports.configuration = configuration
8
+ exports.state = state
9
+ exports.http = http
@@ -0,0 +1,5 @@
1
+ 'use strict'
2
+
3
+ const { aspect } = require('./.aspect')
4
+
5
+ exports.state = aspect('state')
@@ -0,0 +1,3 @@
1
+ 'use strict'
2
+
3
+ module.exports = (value) => async () => value
@@ -0,0 +1,26 @@
1
+ 'use strict'
2
+
3
+ const { map } = require('@toa.io/generic')
4
+
5
+ const types = {
6
+ Map: require('./map'),
7
+ Set: require('./set'),
8
+ sync: require('./sync'),
9
+ async: require('./async')
10
+ }
11
+
12
+ function cast (value) {
13
+ return map(value, replace)
14
+ }
15
+
16
+ function replace (key, value) {
17
+ const [name, type] = key.split(':')
18
+
19
+ if (!(type in types)) return
20
+
21
+ const typed = types[type](value)
22
+
23
+ return [name, typed]
24
+ }
25
+
26
+ exports.cast = cast
@@ -0,0 +1,5 @@
1
+ 'use strict'
2
+
3
+ const { cast } = require('./cast')
4
+
5
+ exports.cast = cast
@@ -0,0 +1,3 @@
1
+ 'use strict'
2
+
3
+ module.exports = (value) => new Map(Object.entries(value))
@@ -0,0 +1,3 @@
1
+ 'use strict'
2
+
3
+ module.exports = (value) => new Set(value)
@@ -0,0 +1,3 @@
1
+ 'use strict'
2
+
3
+ module.exports = (value) => () => value
@@ -1,14 +1,14 @@
1
1
  'use strict'
2
2
 
3
- const specials = require('./specials')
3
+ const { cast, expand } = require('./.prepare')
4
4
 
5
5
  /**
6
6
  * @param {toa.samples.Operation & Object} declaration
7
7
  */
8
8
  const prepare = (declaration) => {
9
- for (const [keyword, prepare] of Object.entries(specials)) {
10
- if (keyword in declaration) prepare(declaration)
11
- }
9
+ expand(declaration)
10
+
11
+ if ('extensions' in declaration) cast(declaration.extensions)
12
12
  }
13
13
 
14
14
  exports.prepare = prepare
@@ -1,5 +1,5 @@
1
1
  import * as _core from '@toa.io/core/types'
2
- import * as _sampling from '@toa.io/extensions.sampling'
2
+ import * as _sampling from '@toa.io/extensions.sampling/types/request'
3
3
 
4
4
  declare namespace toa.samples {
5
5
 
@@ -27,7 +27,7 @@ declare namespace toa.samples {
27
27
  remote?: operations.Calls
28
28
  local?: operations.Calls
29
29
  events?: operations.Events
30
- extensions?: _sampling.request.Extensions
30
+ extensions?: _sampling.Extensions
31
31
  }
32
32
 
33
33
  }
@@ -1,26 +0,0 @@
1
- 'use strict'
2
-
3
- /**
4
- * @param {toa.samples.Operation & Object} declaration
5
- */
6
- const configuration = (declaration) => {
7
- const configuration = declaration.configuration
8
-
9
- delete declaration.configuration
10
-
11
- if (declaration.extensions === undefined) declaration.extensions = {}
12
-
13
- if ('configuration' in declaration.extensions) {
14
- throw new Error('Configuration extension sample is ambiguous')
15
- }
16
-
17
- /** @type {toa.sampling.request.extensions.Call} */
18
- const call = {
19
- result: configuration,
20
- permanent: true
21
- }
22
-
23
- declaration.extensions.configuration = [call]
24
- }
25
-
26
- exports.configuration = configuration
@@ -1,5 +0,0 @@
1
- 'use strict'
2
-
3
- const { configuration } = require('./configuration')
4
-
5
- exports.configuration = configuration