@toa.io/core 1.0.0-alpha.28 → 1.0.0-alpha.282

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 (98) hide show
  1. package/CHANGELOG.md +168 -0
  2. package/package.json +8 -10
  3. package/src/assignment.js +5 -8
  4. package/src/call.js +35 -9
  5. package/src/cascade.js +9 -11
  6. package/src/component.js +61 -21
  7. package/src/composition.js +3 -7
  8. package/src/connector.js +29 -22
  9. package/src/context.js +8 -6
  10. package/src/contract/contract.js +18 -0
  11. package/src/contract/index.js +2 -7
  12. package/src/contract/reply.js +36 -15
  13. package/src/contract/request.js +20 -10
  14. package/src/contract/schemas/index.js +9 -5
  15. package/src/contract/schemas/query.yaml +14 -1
  16. package/src/contract/schemas/source.yaml +23 -0
  17. package/src/discovery.js +15 -13
  18. package/src/effect.js +15 -0
  19. package/src/emission.js +2 -6
  20. package/src/entities/changeset.js +7 -14
  21. package/src/entities/entity.js +75 -29
  22. package/src/entities/factory.js +21 -14
  23. package/src/entities/index.js +1 -5
  24. package/src/entities/newid.js +9 -0
  25. package/src/entities/set.js +13 -4
  26. package/src/event.js +21 -7
  27. package/src/exceptions.js +53 -39
  28. package/src/exposition.js +5 -8
  29. package/src/guard.js +13 -0
  30. package/src/index.js +28 -47
  31. package/src/locator.js +2 -6
  32. package/src/observation.js +3 -15
  33. package/src/operation.js +40 -14
  34. package/src/outbox/index.js +1 -0
  35. package/src/outbox/outbox.js +296 -0
  36. package/src/query/criteria.js +3 -7
  37. package/src/query/options.js +5 -8
  38. package/src/query.js +36 -7
  39. package/src/receiver.js +76 -18
  40. package/src/reflection.js +2 -6
  41. package/src/remote.js +9 -12
  42. package/src/state.js +89 -50
  43. package/src/transition.js +13 -25
  44. package/src/transmission.js +15 -10
  45. package/src/unmanaged.js +7 -0
  46. package/test/call.fixtures.js +8 -12
  47. package/test/call.test.js +21 -9
  48. package/test/component.fixtures.js +5 -8
  49. package/test/component.test.js +22 -10
  50. package/test/connector.fixtures.js +10 -8
  51. package/test/connector.test.js +59 -38
  52. package/test/context.fixtures.js +8 -12
  53. package/test/context.test.js +18 -7
  54. package/test/contract/conditions.test.js +11 -9
  55. package/test/contract/contract.fixtures.js +12 -16
  56. package/test/contract/request.test.js +84 -34
  57. package/test/discovery.test.js +64 -0
  58. package/test/emission.fixtures.js +6 -10
  59. package/test/emission.test.js +29 -12
  60. package/test/entities/entity.fixtures.js +7 -11
  61. package/test/entities/entity.test.js +70 -49
  62. package/test/entities/factory.fixtures.js +22 -10
  63. package/test/entities/factory.test.js +29 -15
  64. package/test/entities/set.fixtures.js +6 -8
  65. package/test/entities/set.test.js +6 -5
  66. package/test/event.fixtures.js +9 -14
  67. package/test/event.test.js +57 -31
  68. package/test/locator.test.js +25 -24
  69. package/test/outbox.test.js +121 -0
  70. package/test/query.fixtures.js +1 -5
  71. package/test/query.test.js +42 -14
  72. package/test/receiver.fixtures.js +9 -12
  73. package/test/receiver.test.js +45 -22
  74. package/test/reflection.test.js +7 -6
  75. package/test/state.fixtures.js +25 -30
  76. package/test/state.test.js +72 -19
  77. package/test/transmission.fixtures.js +4 -6
  78. package/test/transmission.test.js +33 -15
  79. package/types/atomicity.d.ts +58 -0
  80. package/types/bindings.d.ts +8 -6
  81. package/types/bridges.ts +5 -2
  82. package/types/component.d.ts +7 -4
  83. package/types/context.d.ts +6 -3
  84. package/types/entity.d.ts +4 -4
  85. package/types/event.d.ts +1 -1
  86. package/types/extensions.d.ts +35 -10
  87. package/types/index.ts +16 -13
  88. package/types/message.d.ts +1 -0
  89. package/types/operations.d.ts +6 -0
  90. package/types/outbox.d.ts +53 -0
  91. package/types/query.d.ts +2 -0
  92. package/types/receiver.d.ts +2 -2
  93. package/types/reflection.d.ts +1 -1
  94. package/types/remote.d.ts +18 -0
  95. package/types/request.d.ts +16 -1
  96. package/types/state.d.ts +11 -8
  97. package/types/storages.d.ts +36 -11
  98. package/src/contract/conditions.js +0 -21
@@ -1,9 +1,7 @@
1
- 'use strict'
1
+ import { parse } from '@rsql/parser'
2
+ import { QuerySyntaxException } from '../exceptions.js'
2
3
 
3
- const { parse } = require('@rsql/parser')
4
- const { QuerySyntaxException } = require('../exceptions')
5
-
6
- const criteria = (criteria, properties) => {
4
+ export const criteria = (criteria, properties) => {
7
5
  let ast
8
6
 
9
7
  try {
@@ -37,5 +35,3 @@ const COERCE = {
37
35
  integer: Number.parseInt,
38
36
  boolean: (value) => value === 'true'
39
37
  }
40
-
41
- exports.criteria = criteria
@@ -1,8 +1,6 @@
1
- 'use strict'
1
+ import { QuerySyntaxException } from '../exceptions.js'
2
2
 
3
- const { QuerySyntaxException } = require('../exceptions')
4
-
5
- const options = (options, properties, system) => {
3
+ export const options = (options, properties, system) => {
6
4
  if (options.sort !== undefined) options.sort = sort(options.sort, properties)
7
5
 
8
6
  if (options.projection !== undefined) projection(options.projection, properties)
@@ -33,8 +31,7 @@ const projection = (projection, properties) => {
33
31
  }
34
32
  }
35
33
 
36
- if (projection.includes('_version') === false)
37
- projection.push('_version')
34
+ for (const property of ['_version', '_created', '_updated', '_deleted'])
35
+ if (!projection.includes(property))
36
+ projection.push(property)
38
37
  }
39
-
40
- exports.options = options
package/src/query.js CHANGED
@@ -1,12 +1,16 @@
1
- 'use strict'
1
+ import { empty } from '@toa.io/generic'
2
+ import * as criteria from './query/criteria.js'
3
+ import * as options from './query/options.js'
2
4
 
3
- const { empty } = require('@toa.io/generic')
4
- const parse = { ...require('./query/criteria'), ...require('./query/options') }
5
+ const parse = { ...criteria, ...options }
5
6
 
6
- class Query {
7
+ export class Query {
7
8
  #properties
8
9
  #system
9
10
 
11
+ /** @type {Map<string, object>} parsed criteria by their expression */
12
+ #asts = new Map()
13
+
10
14
  constructor (properties) {
11
15
  this.#properties = properties
12
16
  this.#system = Object.keys(properties).filter((key) => properties[key].system === true)
@@ -19,13 +23,15 @@ class Query {
19
23
  parse (query) {
20
24
  /** @type {toa.core.storages.Query} */
21
25
  const result = {}
22
- const { id, version, criteria, ...rest } = query
26
+ const { id, ids, version, criteria, search, ...rest } = query
23
27
 
24
28
  const options = this.#options(rest)
25
29
 
26
30
  if (id !== undefined) result.id = id
31
+ if (ids !== undefined) result.ids = ids
27
32
  if (version !== undefined) result.version = version
28
- if (criteria !== undefined) result.criteria = parse.criteria(criteria, this.#properties)
33
+ if (criteria !== undefined) result.criteria = this.#criteria(criteria)
34
+ if (search !== undefined) result.search = search
29
35
  if (options !== undefined) result.options = options
30
36
 
31
37
  return result
@@ -36,6 +42,29 @@ class Query {
36
42
 
37
43
  return parse.options(options, this.#properties, this.#system)
38
44
  }
45
+
46
+ /**
47
+ * Lexing an RSQL expression is the most expensive thing this class does, and the same
48
+ * expression arrives over and over: a route builds it from its own declaration and the
49
+ * request parameters. The tree is only read downstream — a storage builds a fresh query
50
+ * from it — so it is kept rather than parsed again.
51
+ *
52
+ * Expressions come from the client, hence the bound. An invalid one throws before it
53
+ * reaches the cache.
54
+ */
55
+ #criteria (criteria) {
56
+ const known = this.#asts.get(criteria)
57
+
58
+ if (known !== undefined) return known
59
+
60
+ const ast = parse.criteria(criteria, this.#properties)
61
+
62
+ if (this.#asts.size >= LIMIT) this.#asts.clear()
63
+
64
+ this.#asts.set(criteria, ast)
65
+
66
+ return ast
67
+ }
39
68
  }
40
69
 
41
- exports.Query = Query
70
+ const LIMIT = 1024
package/src/receiver.js CHANGED
@@ -1,12 +1,11 @@
1
- 'use strict'
2
-
3
- const { add } = require('@toa.io/generic')
4
- const { Connector } = require('./connector')
1
+ import { console, decode, run } from 'openspan'
2
+ import { add } from '@toa.io/generic'
3
+ import { Connector } from './connector.js'
5
4
 
6
5
  /**
7
6
  * @implements {toa.core.Receiver}
8
7
  */
9
- class Receiver extends Connector {
8
+ export class Receiver extends Connector {
10
9
  /** @type {boolean} */
11
10
  #conditioned
12
11
 
@@ -16,18 +15,28 @@ class Receiver extends Connector {
16
15
  /** @type {string} */
17
16
  #endpoint
18
17
 
19
- /** @type {toa.core.Component} */
18
+ /** @type {string} */
19
+ #label
20
+
21
+ /** @type {string} */
22
+ #destination
23
+
24
+ /** @type {unknown[]} */
25
+ #arguments
26
+
27
+ /** @type {toa.core.Source} */
28
+ #origin
29
+
20
30
  #local
21
31
 
22
- /** @type {toa.core.bridges.Receiver} */
23
32
  #bridge
24
33
 
25
- /**
26
- *
27
- * @param {toa.norm.component.Receiver} definition
28
- * @param {toa.core.Component} local
29
- * @param {toa.core.bridges.Receiver} bridge
30
- */
34
+ /** @type {object} */
35
+ #delivery
36
+
37
+ /** @type {object} */
38
+ #processing
39
+
31
40
  constructor (definition, local, bridge) {
32
41
  super()
33
42
 
@@ -36,17 +45,42 @@ class Receiver extends Connector {
36
45
  this.#conditioned = conditioned
37
46
  this.#adaptive = adaptive
38
47
  this.#endpoint = operation
48
+ this.#label = definition.label ?? operation
49
+ this.#destination = definition.destination ?? this.#label
50
+ this.#arguments = definition.arguments
51
+ this.#origin = definition.origin
39
52
 
40
53
  this.#local = local
41
54
  this.#bridge = bridge
42
55
 
43
56
  this.depends(local)
44
57
  if (bridge !== undefined) this.depends(bridge)
58
+
59
+ /*
60
+ * The delivery span is created on behalf of the messaging destination, so that
61
+ * each consumer forms its own complete producer/consumer pair, and service graphs
62
+ * display fan-out correctly: producer -> destination -> each consumer
63
+ * (Tempo pairs spans one-to-one, thus multiple consumers can not pair
64
+ * with a single producer span, see grafana/tempo#5408)
65
+ */
66
+ this.#delivery = {
67
+ name: `${this.#label} deliver`,
68
+ kind: 'producer',
69
+ service: this.#destination,
70
+ attributes: { 'messaging.destination.name': this.#destination }
71
+ }
72
+
73
+ this.#processing = {
74
+ name: `${this.#label} process`,
75
+ kind: 'consumer',
76
+ service: local.locator.id,
77
+ attributes: { 'messaging.destination.name': this.#destination }
78
+ }
45
79
  }
46
80
 
47
81
  /** @hot */
48
82
  async receive (message) {
49
- const { payload, ...extensions } = message
83
+ const { payload, telemetry, ...extensions } = message
50
84
 
51
85
  if (this.#conditioned && await this.#bridge.condition(payload) === false) return
52
86
 
@@ -54,12 +88,36 @@ class Receiver extends Connector {
54
88
 
55
89
  add(request, extensions)
56
90
 
57
- await this.#local.invoke(this.#endpoint, request)
91
+ // set after `add`, so that a message field can not spoof the origin
92
+ if (this.#origin !== undefined) request.source = this.#origin
93
+
94
+ // continue the trace from the producer span
95
+ const remote = telemetry === undefined ? null : decode(telemetry)
96
+ const task = () => this.#process(request)
97
+
98
+ if (remote === null)
99
+ await task()
100
+ else
101
+ await run(remote, task)
102
+ }
103
+
104
+ async #process (request) {
105
+ return console.span(this.#delivery, async () => console.span(this.#processing, async () => {
106
+ try {
107
+ await this.#local.invoke(this.#endpoint, request)
108
+ } catch (error) {
109
+ console.error('Receiver error', {
110
+ component: this.#local.locator.id,
111
+ endpoint: this.#endpoint,
112
+ error
113
+ })
114
+
115
+ throw error
116
+ }
117
+ }))
58
118
  }
59
119
 
60
120
  async #request (payload) {
61
- return this.#adaptive ? await this.#bridge.request(payload) : { input: payload }
121
+ return this.#adaptive ? await this.#bridge.request(payload, ...(this.#arguments ?? [])) : { input: payload }
62
122
  }
63
123
  }
64
-
65
- exports.Receiver = Receiver
package/src/reflection.js CHANGED
@@ -1,11 +1,9 @@
1
- 'use strict'
2
-
3
- const { Connector } = require('./connector')
1
+ import { Connector } from './connector.js'
4
2
 
5
3
  /**
6
4
  * @implements {toa.core.Reflection}
7
5
  */
8
- class Reflection extends Connector {
6
+ export class Reflection extends Connector {
9
7
  /** @type {toa.core.reflection.Source} */
10
8
  #source
11
9
 
@@ -24,5 +22,3 @@ class Reflection extends Connector {
24
22
  this.value = await this.#source()
25
23
  }
26
24
  }
27
-
28
- exports.Reflection = Reflection
package/src/remote.js CHANGED
@@ -1,17 +1,14 @@
1
- 'use strict'
1
+ import assert from 'node:assert'
2
+ import { Component } from './component.js'
2
3
 
3
- const { console } = require('@toa.io/console')
4
+ export class Remote extends Component {
5
+ kind = 'client'
4
6
 
5
- const { Component } = require('./component')
7
+ explain (endpoint) {
8
+ if (!(endpoint in this.operations))
9
+ // `assert.fail`, not `assert.ok`: the message is built only when it is needed
10
+ assert.fail(`Endpoint '${endpoint}' is not provided by '${this.locator.id}'`)
6
11
 
7
- class Remote extends Component {
8
- async open () {
9
- console.info(`Remote '${this.locator.id}' connected`)
10
- }
11
-
12
- async dispose () {
13
- console.info(`Remote '${this.locator.id}' disconnected`)
12
+ return this.operations[endpoint].explain()
14
13
  }
15
14
  }
16
-
17
- exports.Remote = Remote
package/src/state.js CHANGED
@@ -1,82 +1,119 @@
1
- 'use strict'
1
+ import { StatePreconditionException, StateNotFoundException } from './exceptions.js'
2
2
 
3
- const { empty } = require('@toa.io/generic')
3
+ export class State {
4
+ storage
4
5
 
5
- const {
6
- StatePreconditionException,
7
- StateNotFoundException
8
- } = require('./exceptions')
9
-
10
- class State {
11
6
  #associated
12
- #storage
13
- #entity
14
- #emission
15
-
16
- constructor (storage, entity, emission, associated) {
17
- this.#storage = storage
18
- this.#entity = entity
19
- this.#emission = emission
7
+ #entities
8
+ #outbox
9
+
10
+ constructor (storage, entity, outbox, associated) {
11
+ this.storage = storage
12
+ this.#entities = entity
13
+ this.#outbox = outbox
20
14
  this.#associated = associated === true
21
15
  }
22
16
 
23
17
  init (id) {
24
- return this.#entity.init(id)
18
+ return this.#entities.init(id)
19
+ }
20
+
21
+ fit (values) {
22
+ return this.#entities.fit(values)
25
23
  }
26
24
 
27
- async object (query) {
28
- const record = await this.#storage.get(query)
25
+ async object (query, mutable = true) {
26
+ const record = await this.storage.get(query)
29
27
 
30
28
  if (record === null) {
31
- if (this.#associated && query.id !== undefined && query.version === undefined) {
29
+ if (this.#associated && query.id !== undefined && query.criteria === undefined && query.version === undefined)
32
30
  return this.init(query.id)
33
- } else if (query.version !== undefined) throw new StatePreconditionException()
34
- }
31
+ else if (query.version !== undefined)
32
+ throw new StatePreconditionException()
35
33
 
36
- if (record === null) {
37
34
  return null
38
- } else {
39
- return this.#entity.object(record)
40
- }
35
+ } else
36
+ return this.#entities.object(record, mutable)
41
37
  }
42
38
 
43
- async objects (query) {
44
- const recordset = await this.#storage.find(query)
39
+ async objects (query, mutable = true) {
40
+ const recordset = await this.storage.find(query)
41
+ const missing = this.#associated && query.ids !== undefined && recordset.length < query.ids.length
42
+ const init = missing ? query.ids.filter((id) => !recordset.some((record) => record.id === id)) : undefined
43
+
44
+ return this.#entities.objects(recordset, init, mutable)
45
+ }
45
46
 
46
- return this.#entity.objects(recordset)
47
+ async stream (query) {
48
+ return this.storage.stream(query)
47
49
  }
48
50
 
49
51
  changeset (query) {
50
- return this.#entity.changeset(query)
52
+ return this.#entities.changeset(query)
51
53
  }
52
54
 
53
55
  none () {
54
56
  return null
55
57
  }
56
58
 
57
- async commit (state) {
58
- const event = state.event()
59
+ async ensure (query, properties, input) {
60
+ const object = this.#entities.init()
61
+ const blank = object.get()
59
62
 
60
- let ok = true
63
+ Object.assign(blank, properties)
61
64
 
62
- if (!empty(event.changeset)) {
63
- const object = state.get()
65
+ object.set(blank)
64
66
 
65
- ok = await this.#storage.store(object)
67
+ const row = this.#outbox?.row(object.event(input))
68
+ const record = await this.storage.ensure(query, properties, object.get(), row)
66
69
 
67
- // #20
68
- if (ok === true) {
69
- await this.#emission.emit(event)
70
- }
71
- }
70
+ if (record.id !== blank.id) // exists
71
+ return this.#entities.object(record, NOT_MUTABLE)
72
+
73
+ if (row !== undefined)
74
+ await this.#outbox.publish(row)
75
+
76
+ return object
77
+ }
78
+
79
+ async commit (state, input) {
80
+ if (state.constructor.name === 'EntitySet')
81
+ return this.massCommit(state, input)
82
+
83
+ const data = state.get()
84
+
85
+ // the row is built before the write so that the storage can commit it in the same
86
+ // transaction, closing the window this used to have
87
+ const row = this.#outbox?.row(state.event(input))
88
+ const ok = await this.storage.store(data, row)
89
+
90
+ if (ok === true && row !== undefined)
91
+ await this.#outbox.publish(row)
92
+
93
+ return ok
94
+ }
95
+
96
+ async massCommit (state, input) {
97
+ const data = state.get()
98
+ const rows = this.#outbox === undefined
99
+ ? undefined
100
+ : state.events(input).map((event) => this.#outbox.row(event))
101
+
102
+ const ok = await this.storage.massStore(data, rows)
103
+
104
+ if (ok === true && rows !== undefined)
105
+ await Promise.all(rows.map((row) => this.#outbox.publish(row)))
72
106
 
73
107
  return ok
74
108
  }
75
109
 
76
- async apply (state) {
110
+ async apply (state, input) {
77
111
  const changeset = state.export()
78
112
 
79
- const result = await this.#storage.upsert(state.query, changeset)
113
+ // an assignment's event is the write's own images, so the storage fills them in;
114
+ // see `apply` in the outbox design
115
+ const row = this.#outbox?.row({ input })
116
+ const result = await this.storage.upsert(state.query, changeset, row)
80
117
 
81
118
  if (result === null) {
82
119
  if (state.query.version !== undefined) {
@@ -84,16 +121,18 @@ class State {
84
121
  } else {
85
122
  throw new StateNotFoundException()
86
123
  }
87
- } else {
88
- // same as above
89
- await this.#emission.emit({
90
- changeset,
91
- state: result
92
- })
124
+ } else if (row !== undefined) {
125
+ // the storage fills `origin` and `state` from its own write; a storage that does not
126
+ // know how leaves the event with what it was given
127
+ row.event.state ??= result
128
+ row.event.origin ??= null
129
+
130
+ await this.#outbox.publish(row)
93
131
  }
94
132
 
95
133
  return result
96
134
  }
97
135
  }
98
136
 
99
- exports.State = State
137
+ /** an effect never commits what `ensure` hands it, see `Effect` */
138
+ const NOT_MUTABLE = false
package/src/transition.js CHANGED
@@ -1,14 +1,11 @@
1
- 'use strict'
1
+ import { retry } from '@toa.io/generic'
2
+ import { Operation } from './operation.js'
3
+ import { StateConcurrencyException, StateNotFoundException } from './exceptions.js'
2
4
 
3
- const { retry } = require('@toa.io/generic')
5
+ export class Transition extends Operation {
6
+ /** a transition is the only operation that commits */
7
+ mutable = true
4
8
 
5
- const { Operation } = require('./operation')
6
- const {
7
- StateConcurrencyException,
8
- StateNotFoundException
9
- } = require('./exceptions')
10
-
11
- class Transition extends Operation {
12
9
  #concurrency
13
10
 
14
11
  constructor (cascade, scope, contract, query, definition) {
@@ -26,33 +23,26 @@ class Transition extends Operation {
26
23
 
27
24
  store.scope = request.query ? await this.query(request.query) : this.scope.init()
28
25
 
29
- if (store.scope === null) {
26
+ if (store.scope === null || (store.scope.deleted === true && request.query?.options?.deleted !== true))
30
27
  throw new StateNotFoundException()
31
- }
32
-
28
+
33
29
  store.state = store.scope.get()
34
30
  }
35
31
 
36
32
  async commit (store) {
37
- const {
38
- scope,
39
- state,
40
- reply,
41
- retry
42
- } = store
33
+ const { scope, state, reply, retry } = store
43
34
 
44
35
  if (reply.error !== undefined) return
45
36
 
46
37
  scope.set(state)
47
38
 
48
- const result = await this.scope.commit(scope)
39
+ const result = await this.scope.commit(scope, store.request.input)
49
40
 
50
41
  if (result === false) {
51
- if (this.#concurrency === 'retry') {
42
+ if (this.#concurrency === 'retry')
52
43
  return retry()
53
- } else {
44
+ else
54
45
  throw new StateConcurrencyException()
55
- }
56
46
  }
57
47
  }
58
48
 
@@ -67,7 +57,5 @@ const RETRY = {
67
57
  base: 10,
68
58
  max: 5000,
69
59
  dispersion: 1,
70
- retries: Infinity
60
+ retries: 32
71
61
  }
72
-
73
- exports.Transition = Transition
@@ -1,9 +1,7 @@
1
- 'use strict'
1
+ import { Connector } from './connector.js'
2
+ import { TransmissionException } from './exceptions.js'
2
3
 
3
- const { Connector } = require('./connector')
4
- const { TransmissionException } = require('./exceptions')
5
-
6
- class Transmission extends Connector {
4
+ export class Transmission extends Connector {
7
5
  #bindings
8
6
 
9
7
  constructor (bindings) {
@@ -18,16 +16,23 @@ class Transmission extends Connector {
18
16
  let i = 0
19
17
 
20
18
  while (reply === false && i < this.#bindings.length) {
21
- reply = await this.#bindings[i].request(request)
19
+ const binding = this.#bindings[i]
20
+
22
21
  i++
22
+
23
+ if (request?.task === true) {
24
+ if (binding.task === undefined)
25
+ continue
26
+
27
+ await binding.task(request)
28
+ reply = null
29
+ } else
30
+ reply = await binding.request(request)
23
31
  }
24
32
 
25
- if (reply === false) {
33
+ if (reply === false)
26
34
  throw new TransmissionException(`All (${this.#bindings.length}) bindings rejected.`)
27
- }
28
35
 
29
36
  return reply
30
37
  }
31
38
  }
32
-
33
- exports.Transmission = Transmission
@@ -0,0 +1,7 @@
1
+ import { Operation } from './operation.js'
2
+
3
+ export class Unmanaged extends Operation {
4
+ acquire (context) {
5
+ context.state = this.scope.storage.raw
6
+ }
7
+ }
@@ -1,18 +1,18 @@
1
- 'use strict'
1
+ import { mock } from 'node:test'
2
2
 
3
- const { generate } = require('randomstring')
3
+ import { generate } from 'randomstring'
4
4
 
5
5
  // noinspection JSCheckFunctionSignatures
6
- const transmission = {
7
- request: jest.fn((request) => ({ [request.invalid ? 'exception' : 'output']: generate() })),
8
- link: jest.fn()
6
+ export const transmission = {
7
+ request: mock.fn((request) => ({ [request.invalid ? 'exception' : 'output']: generate() })),
8
+ link: mock.fn()
9
9
  }
10
10
 
11
- const contract = {
12
- fit: jest.fn(() => null)
11
+ export const contract = {
12
+ fit: mock.fn(() => null)
13
13
  }
14
14
 
15
- const request = () => ({
15
+ export const request = () => ({
16
16
  ok: {
17
17
  input: { [generate()]: generate() },
18
18
  query: { [generate()]: generate() }
@@ -21,7 +21,3 @@ const request = () => ({
21
21
  invalid: true
22
22
  }
23
23
  })
24
-
25
- exports.transmission = transmission
26
- exports.contract = contract
27
- exports.request = request