@toa.io/userland 1.0.0-alpha.29 → 1.0.0-alpha.291

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 (56) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/example/components/echo/manifest.toa.yaml +15 -5
  3. package/example/components/echo/operations/get.js +1 -5
  4. package/example/components/echo/operations/resolve.js +1 -5
  5. package/example/components/echo/operations/signal.js +3 -7
  6. package/example/components/math.calculations/manifest.toa.yaml +19 -6
  7. package/example/components/math.calculations/operations/add.js +1 -5
  8. package/example/components/math.calculations/operations/increment.js +1 -5
  9. package/example/components/math.proxy/manifest.toa.yaml +8 -3
  10. package/example/components/math.proxy/operations/add.js +1 -5
  11. package/example/components/tea.pots/manifest.toa.yaml +37 -9
  12. package/example/components/tea.pots/operations/same.js +1 -5
  13. package/example/components/tea.pots/receivers/store.orders.created.js +1 -5
  14. package/example/components/web/manifest.toa.yaml +2 -4
  15. package/example/components/web/operations/get.js +3 -8
  16. package/example/context.toa.yaml +2 -7
  17. package/example/stage/call.test.js +17 -10
  18. package/example/stage/events.test.js +63 -16
  19. package/example/stage/invoke.test.js +12 -9
  20. package/package.json +18 -12
  21. package/stage/package.json +1 -0
  22. package/stage/src/component.js +3 -12
  23. package/stage/src/composition.js +3 -11
  24. package/stage/src/index.js +15 -17
  25. package/stage/src/manifest.js +2 -6
  26. package/stage/src/remote.js +11 -8
  27. package/stage/src/service.js +11 -10
  28. package/stage/src/shutdown.js +2 -8
  29. package/stage/src/state.js +1 -4
  30. package/stage/test/boot.mock.js +9 -11
  31. package/stage/test/component.test.js +20 -10
  32. package/stage/test/composition.test.js +11 -20
  33. package/stage/test/manifest.test.js +15 -8
  34. package/stage/test/remote.test.js +14 -12
  35. package/stage/test/shutdown.test.js +26 -22
  36. package/stage/test/state.mock.js +5 -6
  37. package/stage/test/state.test.js +13 -12
  38. package/stage/types/index.d.ts +11 -10
  39. package/stage/types/state.d.ts +2 -4
  40. package/stage/src/binding/binding.js +0 -50
  41. package/stage/src/binding/consumer.js +0 -32
  42. package/stage/src/binding/emitter.js +0 -27
  43. package/stage/src/binding/factory.js +0 -49
  44. package/stage/src/binding/index.js +0 -10
  45. package/stage/src/binding/label.js +0 -12
  46. package/stage/src/binding/producer.js +0 -43
  47. package/stage/src/binding/receiver.js +0 -34
  48. package/stage/test/binding/binding.test.js +0 -15
  49. package/stage/test/binding/consumer.test.js +0 -52
  50. package/stage/test/binding/emitter.test.js +0 -78
  51. package/stage/test/binding/producer.fixtures.js +0 -10
  52. package/stage/test/binding/producer.test.js +0 -49
  53. package/stage/test/binding/receiver.fixtures.js +0 -7
  54. package/stage/test/binding/receiver.test.js +0 -54
  55. package/stage/test/binding.mock.js +0 -8
  56. package/stage/types/binding.d.ts +0 -20
package/CHANGELOG.md ADDED
@@ -0,0 +1,32 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
+
6
+ # [1.0.0-alpha.291](https://github.com/toa-io/toa/compare/v1.0.0-alpha.290...v1.0.0-alpha.291) (2026-09-07)
7
+
8
+ **Note:** Version bump only for package @toa.io/userland
9
+
10
+
11
+
12
+
13
+
14
+ # [1.0.0-alpha.289](https://github.com/toa-io/toa/compare/v1.0.0-alpha.288...v1.0.0-alpha.289) (2026-09-07)
15
+
16
+ **Note:** Version bump only for package @toa.io/userland
17
+
18
+
19
+
20
+
21
+
22
+ # [1.0.0-alpha.288](https://github.com/toa-io/toa/compare/v1.0.0-alpha.287...v1.0.0-alpha.288) (2026-09-06)
23
+
24
+ **Note:** Version bump only for package @toa.io/userland
25
+
26
+
27
+
28
+
29
+
30
+ # [1.0.0-alpha.287](https://github.com/toa-io/toa/compare/v1.0.0-alpha.286...v1.0.0-alpha.287) (2026-09-06)
31
+
32
+ **Note:** Version bump only for package @toa.io/userland
@@ -2,14 +2,24 @@ name: echo
2
2
 
3
3
  operations:
4
4
  signal:
5
- output: string
5
+ output:
6
+ type: string
6
7
  get:
7
- output: string
8
+ output:
9
+ type: string
8
10
  resolve:
9
- input: string
10
- output: string
11
+ input:
12
+ type: string
13
+ output:
14
+ type: string
11
15
 
12
16
  configuration:
13
- signal: quack
17
+ schema:
18
+ type: object
19
+ properties:
20
+ signal:
21
+ type: string
22
+ defaults:
23
+ signal: quack
14
24
 
15
25
  state: ~
@@ -1,7 +1,3 @@
1
- 'use strict'
2
-
3
- async function computation (_, context) {
1
+ export async function computation(_, context) {
4
2
  return context.state.value
5
3
  }
6
-
7
- exports.computation = computation
@@ -1,7 +1,3 @@
1
- 'use strict'
2
-
3
- async function computation (key, context) {
1
+ export async function computation(key, context) {
4
2
  return context.state.values.get(key)
5
3
  }
6
-
7
- exports.computation = computation
@@ -1,15 +1,11 @@
1
- 'use strict'
2
-
3
- class Computation {
1
+ export class Computation {
4
2
  #context
5
3
 
6
- async mount (context) {
4
+ async mount(context) {
7
5
  this.#context = context
8
6
  }
9
7
 
10
- async execute () {
8
+ async execute() {
11
9
  return this.#context.configuration.signal
12
10
  }
13
11
  }
14
-
15
- exports.Computation = Computation
@@ -4,11 +4,24 @@ namespace: math
4
4
  operations:
5
5
  add:
6
6
  input:
7
- a: number
8
- b: number
9
- output: number
7
+ properties:
8
+ a:
9
+ type: number
10
+ b:
11
+ type: number
12
+ type: object
13
+ output:
14
+ type: number
10
15
  increment:
11
16
  input:
12
- value: number
13
- times: 1
14
- output: number
17
+ properties:
18
+ value:
19
+ type: number
20
+ times:
21
+ type: number
22
+ type: object
23
+ required:
24
+ - value
25
+ - times
26
+ output:
27
+ type: number
@@ -1,7 +1,3 @@
1
- 'use strict'
2
-
3
- async function computation (input) {
1
+ export async function computation(input) {
4
2
  return input.a + input.b
5
3
  }
6
-
7
- exports.computation = computation
@@ -1,6 +1,4 @@
1
- 'use strict'
2
-
3
- async function computation (input, context) {
1
+ export async function computation(input, context) {
4
2
  let value = input.value
5
3
 
6
4
  for (let i = 0; i < input.times; i++) {
@@ -9,5 +7,3 @@ async function computation (input, context) {
9
7
 
10
8
  return value
11
9
  }
12
-
13
- exports.computation = computation
@@ -4,6 +4,11 @@ namespace: math
4
4
  operations:
5
5
  add:
6
6
  input:
7
- a: number
8
- b: number
9
- output: number
7
+ properties:
8
+ a:
9
+ type: number
10
+ b:
11
+ type: number
12
+ type: object
13
+ output:
14
+ type: number
@@ -1,10 +1,6 @@
1
- 'use strict'
2
-
3
- async function computation (input, context) {
1
+ export async function computation(input, context) {
4
2
  const request = { input }
5
3
 
6
4
  // noinspection JSUnresolvedVariable
7
5
  return context.remote.math.calculations.add(request)
8
6
  }
9
-
10
- exports.computation = computation
@@ -3,21 +3,43 @@ namespace: tea
3
3
 
4
4
  entity:
5
5
  storage: mongodb
6
- schema:
7
- material*: [ glass, ceramic, steel ]
8
- volume: number
6
+ properties:
7
+ volume:
8
+ type: number
9
+ booked:
10
+ type: boolean
11
+ material:
12
+ type: string
13
+ enum:
14
+ - glass
15
+ - ceramic
16
+ - steel
17
+ required:
18
+ - material
19
+ blank:
9
20
  booked: false
10
21
 
11
22
  operations:
12
23
  transit:
13
24
  concurrency: none
14
25
  input:
15
- material: .
16
- volume: .
17
- booked: .
26
+ properties:
27
+ material:
28
+ type: string
29
+ default: .
30
+ volume:
31
+ type: string
32
+ default: .
33
+ booked:
34
+ type: string
35
+ default: .
36
+ type: object
18
37
  same:
19
- input: .material
20
- output: array
38
+ input:
39
+ type: string
40
+ default: .material
41
+ output:
42
+ type: array
21
43
 
22
44
  receivers:
23
45
  store.orders.created:
@@ -25,4 +47,10 @@ receivers:
25
47
  binding: amqp # avoid discovery
26
48
 
27
49
  configuration:
28
- step: 0.5
50
+ schema:
51
+ type: object
52
+ properties:
53
+ step:
54
+ type: number
55
+ defaults:
56
+ step: 0.5
@@ -1,6 +1,4 @@
1
- 'use strict'
2
-
3
- async function computation (input, context) {
1
+ export async function computation(input, context) {
4
2
  const criteria = `material==${input}`
5
3
  const query = { criteria, limit }
6
4
 
@@ -9,5 +7,3 @@ async function computation (input, context) {
9
7
  }
10
8
 
11
9
  const limit = 10
12
-
13
- exports.computation = computation
@@ -1,8 +1,4 @@
1
- 'use strict'
2
-
3
- const request = async (payload) => ({
1
+ export const request = async (payload) => ({
4
2
  input: { booked: true },
5
3
  query: { id: payload.pot }
6
4
  })
7
-
8
- exports.request = request
@@ -2,7 +2,5 @@ name: web
2
2
 
3
3
  operations:
4
4
  get:
5
- output: object
6
-
7
- origins:
8
- dev: http://api.example.com
5
+ output:
6
+ type: object
@@ -1,15 +1,10 @@
1
- 'use strict'
2
-
3
- async function computation (_, context) {
4
- const response = await context.http.dev.path.to.resource.get()
1
+ export async function computation(_, context) {
2
+ const response = await context.fetch('http://api.example.com/path/to/resource')
5
3
  const type = response.headers.get('content-type')
6
4
 
7
- if (type !== 'application/json')
8
- return ERR_UNSUPPORTED_MEDIA_TYPE
5
+ if (type !== 'application/json') return ERR_UNSUPPORTED_MEDIA_TYPE
9
6
 
10
7
  return await response.json()
11
8
  }
12
9
 
13
10
  const ERR_UNSUPPORTED_MEDIA_TYPE = new Error('UNSUPPORTED_MEDIA_TYPE')
14
-
15
- exports.computation = computation
@@ -1,10 +1,5 @@
1
1
  name: examples
2
- packages: components/*
3
2
  registry: example
4
3
 
5
- amqp: amqp://localhost
6
- mongodb: mongodb://localhost
7
-
8
- origins:
9
- default.web:
10
- dev: http://localhost
4
+ amqp: amqp://localhost:31010
5
+ mongodb: mongodb://localhost:31020
@@ -1,16 +1,21 @@
1
- 'use strict'
1
+ import { it, before, after } from 'node:test'
2
+ import assert from 'node:assert/strict'
2
3
 
3
- const { resolve } = require('node:path')
4
- const { exceptions: { RequestContractException } } = require('@toa.io/core')
5
- const stage = require('@toa.io/userland/stage')
4
+ import { resolve } from 'node:path'
5
+ import { exceptions } from '@toa.io/core'
6
+ import * as stage from '@toa.io/userland/stage'
6
7
 
7
- const root = resolve(__dirname, '../components')
8
+ const { RequestContractException } = exceptions
9
+
10
+ const root = resolve(import.meta.dirname, '../components')
8
11
 
9
12
  let echo
10
13
  let math
11
14
 
12
- beforeAll(async () => {
15
+ before(async () => {
13
16
  process.env.TOA_DEV = '1'
17
+ process.env.TOA_CONFIGURATION_DEFAULT_ECHO = '{}'
18
+ process.env.TOA_CONFIGURATION_TEA_POTS = '{}'
14
19
 
15
20
  const paths = ['echo', 'math.calculations'].map((rel) => resolve(root, rel))
16
21
 
@@ -20,7 +25,7 @@ beforeAll(async () => {
20
25
  math = await stage.remote('math.calculations')
21
26
  })
22
27
 
23
- afterAll(async () => {
28
+ after(async () => {
24
29
  await stage.shutdown()
25
30
 
26
31
  delete process.env.TOA_DEV
@@ -29,13 +34,15 @@ afterAll(async () => {
29
34
  it('should call endpoint', async () => {
30
35
  const reply = await echo.invoke('signal', {})
31
36
 
32
- expect(reply).toStrictEqual('quack')
37
+ assert.deepStrictEqual(reply, 'quack')
33
38
  })
34
39
 
35
40
  it('should throw on invalid input', async () => {
36
41
  const a = 'not a number'
37
42
  const b = 'neither'
38
43
 
39
- await expect(math.invoke('add', { input: { a, b } }))
40
- .rejects.toBeInstanceOf(RequestContractException)
44
+ await assert.rejects(math.invoke('add', { input: { a, b } }), (error) => {
45
+ assert.ok(error instanceof RequestContractException)
46
+ return true
47
+ })
41
48
  })
@@ -1,51 +1,98 @@
1
- 'use strict'
1
+ import { it, before, after } from 'node:test'
2
+ import assert from 'node:assert/strict'
2
3
 
3
- const { resolve } = require('node:path')
4
- const stage = require('@toa.io/userland/stage')
4
+ import { resolve } from 'node:path'
5
+ import * as boot from '@toa.io/boot'
6
+ import { Connector, Locator } from '@toa.io/core'
7
+ import { timeout } from '@toa.io/generic'
8
+ import * as stage from '@toa.io/userland/stage'
5
9
 
6
- const binding = stage.binding.binding
7
- const root = resolve(__dirname, '../components')
10
+ const root = resolve(import.meta.dirname, '../components')
8
11
 
9
12
  let remote
13
+ let emitter
14
+ let consumer
10
15
 
11
- beforeAll(async () => {
16
+ before(async () => {
12
17
  process.env.TOA_DEV = '1'
18
+ process.env.TOA_CONFIGURATION_TEA_POTS = '{}'
19
+
20
+ // the outbox pumps on a tick; at the default five seconds a test would end before it ran
21
+ process.env.TOA_OUTBOX_INTERVAL = '100'
13
22
 
14
23
  const path = resolve(root, 'tea.pots')
15
24
 
16
25
  await stage.composition([path])
17
26
 
18
27
  remote = await stage.remote('tea.pots')
28
+
29
+ // what `store.orders` would emit, sent the way it would send it
30
+ emitter = await boot.bindings.emit(AMQP, new Locator('orders', 'store'), 'created')
31
+
32
+ await emitter.connect()
19
33
  })
20
34
 
21
- afterAll(async () => {
35
+ after(async () => {
36
+ await consumer?.disconnect()
37
+ await emitter.disconnect()
22
38
  await stage.shutdown()
23
39
 
24
40
  delete process.env.TOA_DEV
41
+ delete process.env.TOA_CONFIGURATION_TEA_POTS
42
+ delete process.env.TOA_OUTBOX_INTERVAL
25
43
  })
26
44
 
27
45
  it('should receive event', async () => {
28
46
  const created = await remote.invoke('transit', { input: { material: 'glass' } })
29
47
  const id = created.id
30
- const payload = { pot: id }
31
- const message = { payload }
32
48
  const request = { query: { id } }
33
49
 
34
- await binding.emit('store.orders.created', message)
50
+ await emitter.emit({ payload: { pot: id } })
51
+
52
+ // the receiver books the pot on delivery, which is asynchronous
53
+ const deadline = Date.now() + 5000
35
54
 
36
- const reply = await remote.invoke('observe', request)
55
+ let reply
37
56
 
38
- expect(reply.booked).toStrictEqual(true)
57
+ do {
58
+ await timeout(50)
59
+
60
+ reply = await remote.invoke('observe', request)
61
+ } while (reply.booked !== true && Date.now() < deadline)
62
+
63
+ assert.deepStrictEqual(reply.booked, true)
39
64
  })
40
65
 
41
66
  it('should emit event', async () => {
42
- expect.assertions(1)
43
-
44
67
  const material = 'steel'
45
68
 
46
- await binding.subscribe('tea.pots.created', (pot) => {
47
- expect(pot.payload.material).toStrictEqual(material)
69
+ const received = new Promise((resolve) => {
70
+ consumer = boot.receive('tea.pots.created', new Subscriber(resolve))
48
71
  })
49
72
 
73
+ consumer = await consumer
74
+
75
+ await consumer.connect()
50
76
  await remote.invoke('transit', { input: { material } })
77
+
78
+ const payload = await received
79
+
80
+ assert.deepStrictEqual(payload.material, material)
51
81
  })
82
+
83
+ /** What the event consumer hands deliveries to. */
84
+ class Subscriber extends Connector {
85
+ #handler
86
+
87
+ constructor(handler) {
88
+ super()
89
+
90
+ this.#handler = handler
91
+ }
92
+
93
+ async receive(message) {
94
+ this.#handler(message.payload)
95
+ }
96
+ }
97
+
98
+ const AMQP = '@toa.io/bindings.amqp'
@@ -1,22 +1,25 @@
1
- 'use strict'
1
+ import { it, before, after } from 'node:test'
2
+ import assert from 'node:assert/strict'
2
3
 
3
- const { resolve } = require('node:path')
4
- const stage = require('@toa.io/userland/stage')
4
+ import { resolve } from 'node:path'
5
+ import * as stage from '@toa.io/userland/stage'
5
6
 
6
- const root = resolve(__dirname, '../components')
7
+ const root = resolve(import.meta.dirname, '../components')
7
8
 
8
- /** @type {toa.core.Component} */
9
+ /** @type {import('@toa.io/core').Component} */
9
10
  let component
10
11
 
11
- beforeAll(async () => {
12
+ before(async () => {
12
13
  process.env.TOA_DEV = '1'
14
+ process.env.TOA_CONFIGURATION_DEFAULT_ECHO = '{}'
15
+ process.env.TOA_CONFIGURATION_TEA_POTS = '{}'
13
16
 
14
17
  const path = resolve(root, 'math.calculations')
15
18
 
16
19
  component = await stage.component(path)
17
20
  })
18
21
 
19
- afterAll(async () => {
22
+ after(async () => {
20
23
  await stage.shutdown()
21
24
 
22
25
  delete process.env.TOA_DEV
@@ -28,6 +31,6 @@ it('should invoke', async () => {
28
31
 
29
32
  const reply = await component.invoke('add', { input: { a, b } })
30
33
 
31
- expect(reply.exception).toBeUndefined()
32
- expect(reply.output).toStrictEqual(a + b)
34
+ assert.strictEqual(reply.exception, undefined)
35
+ assert.deepStrictEqual(reply.output, a + b)
33
36
  })
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@toa.io/userland",
3
- "version": "1.0.0-alpha.29",
3
+ "version": "1.0.0-alpha.291",
4
+ "type": "module",
4
5
  "description": "Toa development kit",
5
6
  "homepage": "https://toa.io",
6
7
  "author": {
@@ -16,23 +17,28 @@
16
17
  "url": "https://github.com/toa-io/toa/issues"
17
18
  },
18
19
  "engines": {
19
- "node": ">= 18.0.0"
20
+ "node": ">= 24.0.0"
20
21
  },
21
22
  "publishConfig": {
22
23
  "access": "public"
23
24
  },
24
25
  "main": "src/index.js",
25
- "types": "types/index.d.ts",
26
+ "exports": {
27
+ ".": "./src/index.js",
28
+ "./stage": {
29
+ "types": "./stage/types/index.d.ts",
30
+ "default": "./stage/src/index.js"
31
+ },
32
+ "./package.json": "./package.json"
33
+ },
26
34
  "dependencies": {
27
- "@toa.io/boot": "1.0.0-alpha.29",
28
- "@toa.io/core": "1.0.0-alpha.29",
29
- "@toa.io/filesystem": "1.0.0-alpha.29",
30
- "@toa.io/generic": "1.0.0-alpha.29",
31
- "@toa.io/norm": "1.0.0-alpha.29",
32
- "@toa.io/schemas": "1.0.0-alpha.29",
35
+ "@toa.io/boot": "1.0.0-alpha.291",
36
+ "@toa.io/core": "1.0.0-alpha.291",
37
+ "@toa.io/generic": "1.0.0-alpha.291",
38
+ "@toa.io/norm": "1.0.0-alpha.291",
39
+ "@toa.io/schemas": "1.0.0-alpha.291",
33
40
  "@toa.io/storages.null": "0.22.0",
34
- "@toa.io/yaml": "1.0.0-alpha.29",
35
- "tap": "16.3.4"
41
+ "js-yaml": "5.4.1"
36
42
  },
37
- "gitHead": "186d0e17c12a9016b8076edf925963cbb32b9219"
43
+ "gitHead": "bcd8d8d07bd3e64fc826af8719472404662cc37f"
38
44
  }
@@ -1,4 +1,5 @@
1
1
  {
2
+ "type": "module",
2
3
  "private": true,
3
4
  "main": "src/index.js",
4
5
  "types": "types/index.d.ts"
@@ -1,12 +1,8 @@
1
- 'use strict'
2
-
3
- const boot = require('@toa.io/boot')
4
- const { state } = require('./state')
1
+ import * as boot from '@toa.io/boot'
2
+ import { state } from './state.js'
5
3
 
6
4
  /** @type {toa.stage.Component} */
7
- const component = async (path, options) => {
8
- options = Object.assign({}, DEFAULTS, options)
9
-
5
+ export const component = async (path, options) => {
10
6
  const manifest = await boot.manifest(path, options)
11
7
  const component = await boot.component(manifest)
12
8
 
@@ -16,8 +12,3 @@ const component = async (path, options) => {
16
12
 
17
13
  return component
18
14
  }
19
-
20
- const binding = require.resolve('./binding')
21
- const DEFAULTS = { bindings: [binding] }
22
-
23
- exports.component = component
@@ -1,19 +1,11 @@
1
- 'use strict'
2
-
3
- const boot = require('@toa.io/boot')
4
- const { state } = require('./state')
5
-
6
- const binding = require.resolve('./binding')
1
+ import * as boot from '@toa.io/boot'
2
+ import { state } from './state.js'
7
3
 
8
4
  /** @type {toa.stage.Composition} */
9
- const composition = async (paths, options) => {
10
- options ??= { bindings: [binding] }
11
-
5
+ export const composition = async (paths, options) => {
12
6
  const composition = await boot.composition(paths, options)
13
7
 
14
8
  await composition.connect()
15
9
 
16
10
  state.compositions.push(composition)
17
11
  }
18
-
19
- exports.composition = composition
@@ -1,18 +1,16 @@
1
- 'use strict'
1
+ import { manifest } from './manifest.js'
2
+ import { component } from './component.js'
3
+ import { composition } from './composition.js'
4
+ import { service } from './service.js'
5
+ import { remote } from './remote.js'
6
+ import { shutdown } from './shutdown.js'
2
7
 
3
- const { manifest } = require('./manifest')
4
- const { component } = require('./component')
5
- const { composition } = require('./composition')
6
- const { service } = require('./service')
7
- const { remote } = require('./remote')
8
- const { shutdown } = require('./shutdown')
9
- const binding = require('./binding')
10
-
11
- exports.manifest = manifest
12
- exports.component = component
13
- exports.composition = composition
14
- exports.compose = composition
15
- exports.serve = service
16
- exports.remote = remote
17
- exports.shutdown = shutdown
18
- exports.binding = binding
8
+ export {
9
+ manifest,
10
+ component,
11
+ composition,
12
+ composition as compose,
13
+ service as serve,
14
+ remote,
15
+ shutdown
16
+ }