@toa.io/userland 0.10.1-dev.0 → 0.20.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/example/.env +9 -0
  2. package/example/components/echo/manifest.toa.yaml +7 -0
  3. package/example/components/echo/operations/get.js +7 -0
  4. package/example/components/echo/operations/resolve.js +7 -0
  5. package/example/components/echo/operations/signal.js +11 -3
  6. package/example/components/echo/samples/get.yaml +12 -0
  7. package/example/components/echo/samples/signal.yaml +0 -3
  8. package/example/components/math.calculations/operations/increment.js +1 -3
  9. package/example/components/math.calculations/samples/add.yaml +1 -2
  10. package/example/components/math.calculations/samples/assets/ab.yaml +2 -0
  11. package/example/components/math.calculations/samples/increment.yaml +3 -6
  12. package/example/components/tea.pots/manifest.toa.yaml +2 -2
  13. package/example/components/web/manifest.toa.yaml +8 -0
  14. package/example/components/web/operations/get.js +14 -0
  15. package/example/components/web/samples/get.yaml +8 -0
  16. package/example/context.toa.yaml +7 -0
  17. package/example/stage/call.test.js +5 -4
  18. package/example/stage/events.test.js +6 -3
  19. package/example/stage/invoke.test.js +4 -0
  20. package/package.json +10 -9
  21. package/samples/notes.md +2 -2
  22. package/samples/readme.md +74 -8
  23. package/samples/src/.replay/.suite/component.js +3 -8
  24. package/samples/src/.replay/.suite/operation.js +1 -1
  25. package/samples/src/.replay/.suite/operations.js +5 -3
  26. package/samples/src/.replay/.suite/translate/.operation/.prepare/cast.js +20 -0
  27. package/samples/src/.replay/.suite/translate/.operation/.prepare/cast.test.js +94 -0
  28. package/samples/src/.replay/.suite/translate/.operation/.prepare/expand.js +14 -0
  29. package/samples/src/.replay/.suite/translate/.operation/.prepare/index.js +7 -0
  30. package/samples/src/.replay/.suite/translate/.operation/.prepare/shortcuts/.aspect.js +26 -0
  31. package/samples/src/.replay/.suite/translate/.operation/.prepare/shortcuts/configuration.js +5 -0
  32. package/samples/src/.replay/.suite/translate/.operation/.prepare/shortcuts/http.js +5 -0
  33. package/samples/src/.replay/.suite/translate/.operation/.prepare/shortcuts/index.js +9 -0
  34. package/samples/src/.replay/.suite/translate/.operation/.prepare/shortcuts/state.js +5 -0
  35. package/samples/src/.replay/.suite/translate/.operation/.prepare/types/async.js +3 -0
  36. package/samples/src/.replay/.suite/translate/.operation/.prepare/types/cast.js +26 -0
  37. package/samples/src/.replay/.suite/translate/.operation/.prepare/types/index.js +5 -0
  38. package/samples/src/.replay/.suite/translate/.operation/.prepare/types/map.js +3 -0
  39. package/samples/src/.replay/.suite/translate/.operation/.prepare/types/set.js +3 -0
  40. package/samples/src/.replay/.suite/translate/.operation/.prepare/types/sync.js +3 -0
  41. package/samples/src/.replay/.suite/translate/.operation/prepare.js +4 -4
  42. package/samples/src/.replay/index.js +2 -0
  43. package/samples/src/.replay/stage.js +60 -0
  44. package/samples/src/.replay/suite.js +3 -2
  45. package/samples/src/.replay/test.js +5 -3
  46. package/samples/src/components.js +1 -1
  47. package/samples/src/context.js +2 -2
  48. package/samples/src/replay.js +4 -5
  49. package/samples/src/suite/components.js +3 -3
  50. package/samples/test/components.test.js +2 -2
  51. package/samples/test/context.test.js +2 -2
  52. package/samples/test/replay.test.js +108 -72
  53. package/samples/test/replay.translate.message.test.js +2 -6
  54. package/samples/test/replay.translate.operation.test.js +1 -1
  55. package/samples/test/stage.mock.js +10 -14
  56. package/samples/test/suite.components.test.js +5 -31
  57. package/samples/types/operation.d.ts +2 -2
  58. package/samples/types/replay.d.ts +3 -3
  59. package/samples/types/suite.d.ts +5 -2
  60. package/stage/src/binding/binding.js +14 -13
  61. package/stage/src/component.js +7 -2
  62. package/stage/src/index.js +0 -3
  63. package/stage/test/component.test.js +1 -1
  64. package/stage/types/index.d.ts +2 -1
  65. package/example/components/tea.pots/samples/messages/store.orders.created.yaml +0 -37
  66. package/samples/src/.replay/.suite/translate/.operation/specials/configuration.js +0 -26
  67. package/samples/src/.replay/.suite/translate/.operation/specials/index.js +0 -5
  68. package/samples/test/replay.fixtures.js +0 -72
package/example/.env ADDED
@@ -0,0 +1,9 @@
1
+ TOA_ENV=local
2
+ TOA_AMQP_CONTEXT=eyIuIjpbImFtcXA6Ly9sb2NhbGhvc3QiXX0=
3
+ TOA_AMQP_CONTEXT__USERNAME=developer
4
+ TOA_AMQP_CONTEXT__PASSWORD=secret
5
+ TOA_MONGODB_TEA_POTS=mongodb://localhost
6
+ TOA_MONGODB_TEA_POTS_USERNAME=developer
7
+ TOA_MONGODB_TEA_POTS_PASSWORD=secret
8
+ TOA_ORIGINS_DEFAULT_WEB_DEV=http://localhost
9
+ TOA_ORIGINS_DEFAULT_WEB__PROPERTIES=3gAA
@@ -3,6 +3,13 @@ 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
14
+
15
+ state: ~
@@ -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
@@ -1,7 +1,15 @@
1
1
  'use strict'
2
2
 
3
- async function computation (input, context) {
4
- return context.configuration.signal
3
+ class Computation {
4
+ #context
5
+
6
+ async mount (context) {
7
+ this.#context = context
8
+ }
9
+
10
+ async execute () {
11
+ return this.#context.configuration.signal
12
+ }
5
13
  }
6
14
 
7
- exports.computation = computation
15
+ 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
@@ -1,6 +1,3 @@
1
- title: Should echo
2
- output: quack
3
- ---
4
1
  title: Should substitute configuration
5
2
  output: woof
6
3
  configuration:
@@ -4,9 +4,7 @@ async function computation (input, context) {
4
4
  let value = input.value
5
5
 
6
6
  for (let i = 0; i < input.times; i++) {
7
- const reply = await context.local.add({ input: { a: value, b: 1 } })
8
-
9
- value = reply.output
7
+ value = await context.local.add({ input: { a: value, b: 1 } })
10
8
  }
11
9
 
12
10
  return value
@@ -1,7 +1,6 @@
1
1
  title: Should add numbers
2
2
  input:
3
- a: 1
4
- b: 2
3
+ <assign: assets/ab.yaml
5
4
  output: 3
6
5
  ---
7
6
  title: Should add negative numbers
@@ -1,6 +1,7 @@
1
1
  title: Should increment number
2
2
  input:
3
3
  value: 1
4
+ times: 1
4
5
  output: 2
5
6
  local:
6
7
  add:
@@ -9,12 +10,7 @@ local:
9
10
  b: 1
10
11
  output: 2
11
12
  ---
12
- title: Should increment with actual call
13
- input:
14
- value: 2
15
- output: 3
16
- ---
17
- title: Should increment twice (with second actual call)
13
+ title: Should increment twice
18
14
  input:
19
15
  value: 0
20
16
  times: 2
@@ -28,3 +24,4 @@ local:
28
24
  - input:
29
25
  a: 1
30
26
  b: 1
27
+ output: 2
@@ -4,7 +4,7 @@ namespace: tea
4
4
  entity:
5
5
  storage: mongodb
6
6
  schema:
7
- material*: [glass, ceramic, steel]
7
+ material*: [ glass, ceramic, steel ]
8
8
  volume: number
9
9
  booked: false
10
10
 
@@ -21,7 +21,7 @@ operations:
21
21
 
22
22
  receivers:
23
23
  store.orders.created:
24
- transition: transit
24
+ operation: transit
25
25
  binding: amqp # avoid discovery
26
26
 
27
27
  configuration:
@@ -0,0 +1,8 @@
1
+ name: web
2
+
3
+ operations:
4
+ get:
5
+ output: object
6
+
7
+ origins:
8
+ dev: http://api.example.com
@@ -0,0 +1,14 @@
1
+ 'use strict'
2
+
3
+ const { Nope } = require('nopeable')
4
+
5
+ async function computation (_, context) {
6
+ const response = await context.http.dev.path.to.resource.get()
7
+ const type = response.headers.get('content-type')
8
+
9
+ if (type !== 'application/json') return new Nope(0, 'Unsupported media type ' + type)
10
+
11
+ return await response.json()
12
+ }
13
+
14
+ exports.computation = computation
@@ -0,0 +1,8 @@
1
+ title: Should return response
2
+ http:
3
+ headers:
4
+ get:sync: 'application/json'
5
+ json:async:
6
+ foo: bar
7
+ output:
8
+ foo: bar
@@ -1,3 +1,10 @@
1
1
  name: examples
2
2
  packages: components/*
3
3
  registry: example
4
+
5
+ amqp: amqp://localhost
6
+ mongodb: mongodb://localhost
7
+
8
+ origins:
9
+ default.web:
10
+ dev: http://localhost
@@ -6,13 +6,12 @@ const stage = require('@toa.io/userland/stage')
6
6
 
7
7
  const root = resolve(__dirname, '../components')
8
8
 
9
- /** @type {toa.core.Component} */
10
9
  let echo
11
-
12
- /** @type {toa.core.Component} */
13
10
  let math
14
11
 
15
12
  beforeAll(async () => {
13
+ process.env.TOA_DEV = '1'
14
+
16
15
  const paths = ['echo', 'math.calculations'].map((rel) => resolve(root, rel))
17
16
 
18
17
  await stage.composition(paths)
@@ -23,12 +22,14 @@ beforeAll(async () => {
23
22
 
24
23
  afterAll(async () => {
25
24
  await stage.shutdown()
25
+
26
+ delete process.env.TOA_DEV
26
27
  })
27
28
 
28
29
  it('should call endpoint', async () => {
29
30
  const reply = await echo.invoke('signal', {})
30
31
 
31
- expect(reply.output).toStrictEqual('quack')
32
+ expect(reply).toStrictEqual('quack')
32
33
  })
33
34
 
34
35
  it('should throw on invalid input', async () => {
@@ -6,10 +6,11 @@ const stage = require('@toa.io/userland/stage')
6
6
  const binding = stage.binding.binding
7
7
  const root = resolve(__dirname, '../components')
8
8
 
9
- /** @type {toa.core.Component} */
10
9
  let remote
11
10
 
12
11
  beforeAll(async () => {
12
+ process.env.TOA_DEV = '1'
13
+
13
14
  const path = resolve(root, 'tea.pots')
14
15
 
15
16
  await stage.composition([path])
@@ -19,11 +20,13 @@ beforeAll(async () => {
19
20
 
20
21
  afterAll(async () => {
21
22
  await stage.shutdown()
23
+
24
+ delete process.env.TOA_DEV
22
25
  })
23
26
 
24
27
  it('should receive event', async () => {
25
28
  const created = await remote.invoke('transit', { input: { material: 'glass' } })
26
- const id = created.output.id
29
+ const id = created.id
27
30
  const payload = { pot: id }
28
31
  const message = { payload }
29
32
  const request = { query: { id } }
@@ -32,7 +35,7 @@ it('should receive event', async () => {
32
35
 
33
36
  const reply = await remote.invoke('observe', request)
34
37
 
35
- expect(reply.output.booked).toStrictEqual(true)
38
+ expect(reply.booked).toStrictEqual(true)
36
39
  })
37
40
 
38
41
  it('should emit event', async () => {
@@ -9,6 +9,8 @@ const root = resolve(__dirname, '../components')
9
9
  let component
10
10
 
11
11
  beforeAll(async () => {
12
+ process.env.TOA_DEV = '1'
13
+
12
14
  const path = resolve(root, 'math.calculations')
13
15
 
14
16
  component = await stage.component(path)
@@ -16,6 +18,8 @@ beforeAll(async () => {
16
18
 
17
19
  afterAll(async () => {
18
20
  await stage.shutdown()
21
+
22
+ delete process.env.TOA_DEV
19
23
  })
20
24
 
21
25
  it('should invoke', async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/userland",
3
- "version": "0.10.1-dev.0",
3
+ "version": "0.20.0-alpha.0",
4
4
  "description": "Toa development kit",
5
5
  "homepage": "https://toa.io",
6
6
  "author": {
@@ -24,14 +24,15 @@
24
24
  "main": "src/index.js",
25
25
  "types": "types/index.d.ts",
26
26
  "dependencies": {
27
- "@toa.io/boot": "0.8.2-dev.0",
28
- "@toa.io/core": "0.8.1",
29
- "@toa.io/filesystem": "0.7.2",
30
- "@toa.io/generic": "0.9.0",
31
- "@toa.io/norm": "0.10.1-dev.0",
32
- "@toa.io/schemas": "0.8.1",
33
- "@toa.io/yaml": "0.7.3",
27
+ "@toa.io/boot": "0.20.0-alpha.0",
28
+ "@toa.io/core": "0.20.0-alpha.0",
29
+ "@toa.io/filesystem": "0.20.0-alpha.0",
30
+ "@toa.io/generic": "0.20.0-alpha.0",
31
+ "@toa.io/norm": "0.20.0-alpha.0",
32
+ "@toa.io/schemas": "0.20.0-alpha.0",
33
+ "@toa.io/storages.null": "0.20.0-alpha.0",
34
+ "@toa.io/yaml": "0.20.0-alpha.0",
34
35
  "tap": "16.3.4"
35
36
  },
36
- "gitHead": "7f1fdbfe3aa33eb4a06cb1a33d15be18854bcd88"
37
+ "gitHead": "d047190899218b5249901a01a2a4caec5b34cf09"
37
38
  }
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,31 +26,63 @@ 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
- > Message samples are always [autonomous](#autonomy).
39
+ > Message samples are only supported at [context level](#autonomy).
40
40
 
41
41
  ### Declaration
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
@@ -60,6 +92,40 @@ context (so as required extensions). See [examples](../example/samples).
60
92
 
61
93
  ## Replay
62
94
 
63
- Samples may be *replayed* using [`toa replay`](/runtime/cli/readme.md#replay) command.
64
-
65
95
  > Replaying samples requires local deployment environment.
96
+
97
+ ### CLI
98
+
99
+ Samples may be replayed using [`toa replay`](/runtime/cli/readme.md#replay) command.
100
+
101
+ See [features](/features/cli/replay.feature).
102
+
103
+ ### Framework
104
+
105
+ `async components(paths: string[], options?): boolean`
106
+
107
+ Replay component samples.
108
+
109
+ `async components(paths: string[], options?): boolean`
110
+
111
+ Replay context and its components' samples.
112
+
113
+ #### Options
114
+
115
+ <dl>
116
+ <dt><code><strong>id</strong>: string</code></dt>
117
+ <dd>Replay samples for a specified component</dd>
118
+
119
+ <dt><code><strong>integration</strong>: string</code></dt>
120
+ <dd>Replay samples for a specified component only</dd>
121
+ <dd></dd>
122
+
123
+ <dt><code><strong>operation</strong>: string</code></dt>
124
+ <dd>Replay samples for specified operation</dd>
125
+
126
+ <dt><code><strong>title</strong>: string</code></dt>
127
+ <dd>Replay samples with titles matching given regexp</dd>
128
+
129
+ </dl>
130
+
131
+ See [types](types/suite.d.ts).
@@ -5,21 +5,16 @@ const stage = require('@toa.io/userland/stage')
5
5
  const { operation } = require('./operation')
6
6
 
7
7
  /**
8
- *
9
- * @param {string} id
8
+ * @param {toa.core.Component} component
10
9
  * @param {toa.samples.operations.Set} set
11
10
  * @param {boolean} autonomous
12
11
  * @returns {Function}
13
12
  */
14
- const component = (id, set, autonomous) =>
13
+ const component = (component, set, autonomous) =>
15
14
  async (test) => {
16
- const remote = await stage.remote(id)
17
-
18
15
  for (const [endpoint, samples] of Object.entries(set)) {
19
- await test.test(endpoint, operation(remote, endpoint, samples, autonomous))
16
+ await test.test(endpoint, operation(component, endpoint, samples, autonomous))
20
17
  }
21
-
22
- await remote.disconnect()
23
18
  }
24
19
 
25
20
  exports.component = component
@@ -19,7 +19,7 @@ const operation = (remote, endpoint, samples, autonomous) =>
19
19
  const request = translate.operation(operation, autonomous)
20
20
  const name = operation.title ?? 'Sample #' + n
21
21
 
22
- await test.test(name, async () => remote.invoke(endpoint, request))
22
+ await test.test(name, async () => await remote.invoke(endpoint, request))
23
23
  }
24
24
 
25
25
  test.end()
@@ -4,15 +4,17 @@ const { component } = require('./component')
4
4
 
5
5
  /**
6
6
  * @param {toa.samples.suite.Operations} operations
7
+ * @param {Record<string, toa.core.Component>} components
7
8
  * @param {boolean} autonomous
8
9
  * @return {Function}
9
10
  */
10
- const operations = (operations, autonomous) =>
11
+ const operations = (operations, components, autonomous) =>
11
12
  async (test) => {
12
13
  for (const [id, set] of Object.entries(operations)) {
13
- await test.test(id, component(id, set, autonomous))
14
- }
14
+ const remote = components[id]
15
15
 
16
+ await test.test(id, component(remote, set, autonomous))
17
+ }
16
18
  }
17
19
 
18
20
  exports.operations = operations
@@ -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,94 @@
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
+ })
@@ -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