@toa.io/core 1.0.0-alpha.287 → 1.0.0-alpha.289

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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,25 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [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)
7
+
8
+ * A deploy moves only what changed, and a component sees none of the runtime's environment (#1073) ([f38e3db](https://github.com/toa-io/toa/commit/f38e3db533f24db866c57bfa1ef294eff1eaf499)), closes [#1073](https://github.com/toa-io/toa/issues/1073) [#1064](https://github.com/toa-io/toa/issues/1064) [#1066](https://github.com/toa-io/toa/issues/1066) [#1067](https://github.com/toa-io/toa/issues/1067) [#1068](https://github.com/toa-io/toa/issues/1068) [#1069](https://github.com/toa-io/toa/issues/1069) [#1071](https://github.com/toa-io/toa/issues/1071) [#1070](https://github.com/toa-io/toa/issues/1070) [#1072](https://github.com/toa-io/toa/issues/1072)
9
+
10
+ ### BREAKING CHANGES
11
+
12
+ * a component that read `process.env.TOA_*` reads `context` instead;
13
+ `echo(input)` no longer substitutes from the environment; a bash operation sees no
14
+ `TOA_*`; images no longer set `USER node` — see migrations/289.md.
15
+
16
+
17
+ # [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)
18
+
19
+ **Note:** Version bump only for package @toa.io/core
20
+
21
+
22
+
23
+
24
+
6
25
  # [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)
7
26
 
8
27
  **Note:** Version bump only for package @toa.io/core
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/core",
3
- "version": "1.0.0-alpha.287",
3
+ "version": "1.0.0-alpha.289",
4
4
  "type": "module",
5
5
  "description": "Toa Core",
6
6
  "author": "temich <tema.gurtovoy@gmail.com>",
@@ -34,10 +34,10 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@rsql/parser": "1.6.0",
37
- "@toa.io/generic": "1.0.0-alpha.286",
38
- "@toa.io/schemas": "1.0.0-alpha.287",
37
+ "@toa.io/generic": "1.0.0-alpha.289",
38
+ "@toa.io/schemas": "1.0.0-alpha.289",
39
39
  "openspan": "1.0.0-alpha.286",
40
40
  "uuid": "14.0.2"
41
41
  },
42
- "gitHead": "5c7944a70c16065ab6372072385cc1f02f5db77c"
42
+ "gitHead": "4fcb482beac5896305d8084cdbc1d5fcf372d108"
43
43
  }
@@ -1,4 +1,5 @@
1
1
  import { console } from 'openspan'
2
+ import { environment } from '@toa.io/generic'
2
3
  import type { Locator } from './locator.js'
3
4
 
4
5
  /** Abstract connections hierarchy */
@@ -7,6 +8,7 @@ export class Connector {
7
8
  #links: Connector[] = []
8
9
  #connecting: Promise<void> | undefined
9
10
  #disconnecting: Promise<void> | undefined
11
+ #discarded: boolean = false
10
12
 
11
13
  public readonly id: string
12
14
  public connected: boolean = false
@@ -40,9 +42,30 @@ export class Connector {
40
42
  this.#dependencies.push(next)
41
43
  next.link(this)
42
44
 
45
+ // See .#discard()
46
+ if (this.#disconnecting !== undefined) next.#discard()
47
+
43
48
  return next
44
49
  }
45
50
 
51
+ /**
52
+ * Disconnects, now and again if a connection is still to land.
53
+ *
54
+ * A dependency taken on after its dependant was disconnected — a lookup that returns
55
+ * once the teardown walk has gone by — is one nothing is left to take down, as the
56
+ * walk that would have reached it is over. One that stays connected holds open
57
+ * everything it depends on with it: a dependency reads `connected` of every dependant
58
+ * before it closes, and a dependant that is connected for good is a dependency that
59
+ * never closes.
60
+ */
61
+ #discard(): void {
62
+ this.#discarded = true
63
+
64
+ // one that is up already, or on its way, is taken down here; one that is not is
65
+ // taken down by the connection when it comes, and until then there is nothing to do
66
+ if (this.#connecting !== undefined) void this.#drop()
67
+ }
68
+
46
69
  /**
47
70
  * Creates a backlink to another Connector
48
71
  *
@@ -72,7 +95,7 @@ export class Connector {
72
95
  }
73
96
 
74
97
  // anonymous grouping nodes are not worth a span
75
- this.#connecting =
98
+ const connecting =
76
99
  TRACE_BOOT && this.constructor.name !== 'Connector'
77
100
  ? console.span(
78
101
  {
@@ -84,13 +107,36 @@ export class Connector {
84
107
  )
85
108
  : work()
86
109
 
110
+ this.#connecting = connecting
111
+
87
112
  try {
88
- await this.#connecting
89
- this.connected = true
113
+ await connecting
114
+
115
+ /*
116
+ * A disconnection that did not wait for this connection emptied `#connecting` to
117
+ * say so. Reporting `connected` now would report it of a connector nothing takes
118
+ * down again — its `#disconnecting` has settled, so a later call returns at once —
119
+ * and what it depends on reads that of every dependant before it closes.
120
+ */
121
+ if (this.#connecting === connecting) this.connected = true
90
122
  } catch (e) {
91
123
  await this.disconnect(true)
92
124
  throw e
93
125
  }
126
+
127
+ // a connection asked for before this one was discarded, landing after. See .#discard()
128
+ if (this.#discarded) await this.#drop()
129
+ }
130
+
131
+ /** Disconnects a discarded connector, saying so rather than throwing at whoever is not waiting. */
132
+ async #drop(): Promise<void> {
133
+ this.#discarded = false
134
+
135
+ try {
136
+ await this.disconnect()
137
+ } catch (error) {
138
+ console.error('Discarded connector failed to disconnect', { id: this.id, error })
139
+ }
94
140
  }
95
141
 
96
142
  /**
@@ -168,4 +214,4 @@ export class Connector {
168
214
  }
169
215
 
170
216
  const DELAY = 5000
171
- const TRACE_BOOT = process.env.TOA_BOOT_TRACE === '1'
217
+ const TRACE_BOOT = environment.get('TOA_BOOT_TRACE') === '1'
package/source/context.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Connector } from './connector.js'
2
+ import { environment } from '@toa.io/generic'
2
3
  import type { Locator } from './locator.js'
3
4
  import type { Component } from './component.js'
4
5
  import type { Remote } from './remote.js'
@@ -20,8 +21,8 @@ export class Context extends Connector {
20
21
  public constructor(local: Component, discover: Discover, aspects: Aspect[] = []) {
21
22
  super()
22
23
 
23
- this.env = process.env.TOA_ENV
24
- this.name = process.env.TOA_CONTEXT
24
+ this.env = environment.get('TOA_ENV')
25
+ this.name = environment.get('TOA_CONTEXT')
25
26
  this.aspects = aspects
26
27
  this.locator = local?.locator
27
28
 
@@ -19,18 +19,37 @@ export interface Definition extends Explanation {
19
19
  query?: boolean
20
20
  }
21
21
 
22
+ /** What the operation works on, as the manifest declares it. */
23
+ export interface Entity {
24
+ properties: Record<string, JSONSchema>
25
+ required?: string[]
26
+ }
27
+
22
28
  export class Request extends Contract {
23
29
  public readonly discovery: Explanation = {}
24
30
 
25
31
  public static override Exception: Refusal =
26
32
  RequestContractException as unknown as Refusal
27
33
 
28
- public constructor(schema: Schema, definition: Definition) {
34
+ public constructor(schema: Schema, definition: Definition, entity?: Entity) {
29
35
  super(schema)
30
36
 
31
37
  for (const key of ['description', 'input', 'output', 'errors'] as const)
32
38
  if (definition[key] !== undefined)
33
39
  (this.discovery as Record<string, unknown>)[key] = definition[key]
40
+
41
+ /*
42
+ * An operation that states no output states an empty schema, which every reply fits —
43
+ * and which says nothing to whoever reads it. What its type answers does, so that is
44
+ * said here instead. Here and not in the definition, because the definition is what a
45
+ * reply is held to: a projection is a lawful answer, and describing one is not the same
46
+ * as requiring it.
47
+ */
48
+ if (empty(this.discovery.output)) {
49
+ const answered = answers(definition, entity)
50
+
51
+ if (answered !== undefined) this.discovery.output = answered
52
+ }
34
53
  }
35
54
 
36
55
  public static schema(
@@ -83,3 +102,39 @@ export class Request extends Contract {
83
102
  return schema
84
103
  }
85
104
  }
105
+
106
+ /**
107
+ * What an operation of this type answers, where it did not say.
108
+ *
109
+ * A transition, an observation and an assignment work on the Entity Object, so that is what
110
+ * they answer — a set of them where the scope is a set. A computation neither uses the scope
111
+ * nor produces one, and an effect answers whatever it computed, so neither is guessed at.
112
+ * A stream is not a value with a schema, and is not one either.
113
+ *
114
+ * What the entity requires is said as well. It is a description and nothing is held to it —
115
+ * an operation answering a projection says so by declaring its own output — and a shape
116
+ * whose every property reads as optional says less than it knows.
117
+ */
118
+ function answers(definition: Definition, entity?: Entity): JSONSchema | undefined {
119
+ if (entity === undefined || !ENTITY.has(definition.type as string)) return undefined
120
+
121
+ const object: JSONSchema = {
122
+ type: 'object',
123
+ properties: entity.properties,
124
+ ...(entity.required === undefined ? {} : { required: entity.required })
125
+ }
126
+
127
+ if (definition.scope === 'objects') return { type: 'array', items: object } as JSONSchema
128
+
129
+ return definition.scope === 'object' || definition.scope === 'changeset'
130
+ ? object
131
+ : undefined
132
+ }
133
+
134
+ /** The types whose subject is the Entity Object; see `documentation/design.md`. */
135
+ const ENTITY = new Set(['transition', 'observation', 'assignment'])
136
+
137
+ /** A schema that states nothing, which is what an operation saying nothing normalizes to. */
138
+ function empty(schema: JSONSchema | null | undefined): boolean {
139
+ return schema === undefined || schema === null || Object.keys(schema).length === 0
140
+ }
@@ -1,6 +1,7 @@
1
1
  import { Readable } from 'node:stream'
2
2
  import { Connector } from './connector.js'
3
3
  import { SystemException, RequestContractException } from './exceptions.js'
4
+ import { environment } from '@toa.io/generic'
4
5
  import type { Cascade } from './cascade.js'
5
6
  import type { State } from './state.js'
6
7
  import type { Query as Translator } from './query.js'
@@ -116,7 +117,7 @@ export class Operation extends Connector {
116
117
  const reply = await this.#cascade.run(request.input, state)
117
118
 
118
119
  // validate reply only on local environments
119
- if (process.env.TOA_ENV === 'local' && !(reply instanceof Readable))
120
+ if (environment.get('TOA_ENV') === 'local' && !(reply instanceof Readable))
120
121
  this.#contracts.reply.fit(reply)
121
122
 
122
123
  store.reply = reply
@@ -1,6 +1,7 @@
1
1
  import { console } from 'openspan'
2
2
  import { Connector } from '../connector.js'
3
3
  import { newid } from '../entities/newid.js'
4
+ import { environment } from '@toa.io/generic'
4
5
  import type { Emission } from '../emission.js'
5
6
  import type { Atom } from '../types/atomicity.js'
6
7
  import type { Storage } from '../types/storages.js'
@@ -64,7 +65,7 @@ export class Outbox extends Connector {
64
65
  this.#interval = number('TOA_OUTBOX_INTERVAL', options.interval, INTERVAL)
65
66
  this.#batch = number('TOA_OUTBOX_BATCH', options.batch, BATCH)
66
67
  this.#gap = options.gap ?? this.#interval * K
67
- this.#defer = process.env.TOA_OUTBOX_DEFER === '1'
68
+ this.#defer = environment.get('TOA_OUTBOX_DEFER') === '1'
68
69
 
69
70
  this.depends(emission)
70
71
  this.depends(atom)
@@ -304,7 +305,7 @@ function number(
304
305
  ): number {
305
306
  if (declared !== undefined) return declared
306
307
 
307
- const value = Number(process.env[variable])
308
+ const value = Number(environment.get(variable))
308
309
 
309
310
  return Number.isNaN(value) || value <= 0 ? fallback : value
310
311
  }
@@ -113,10 +113,15 @@ export interface Entity {
113
113
  migrations?: Migration[]
114
114
  }
115
115
 
116
- /** One file of a component's `migrations` directory, named after it. */
116
+ /**
117
+ * One file of a `migrations` directory, named after it — the component's own, or a prototype's,
118
+ * which every descendant applies to its collection ahead of its own.
119
+ */
117
120
  export interface Migration {
118
121
  id: string
119
122
  steps: unknown[]
123
+ /** the prototype it is inherited from; absent on the component's own */
124
+ prototype?: string
120
125
  }
121
126
 
122
127
  export interface StorageOptions {
@@ -34,6 +34,20 @@ export class FailingConnector extends Connector {
34
34
  }
35
35
  }
36
36
 
37
+ /** Stands for a connector whose connection lands once it has been disconnected. */
38
+ export class LateConnector extends Connector {
39
+ #arrive
40
+
41
+ async open() {
42
+ await new Promise((resolve) => (this.#arrive = resolve))
43
+ }
44
+
45
+ /** Lets the connection land. */
46
+ arrive() {
47
+ this.#arrive()
48
+ }
49
+ }
50
+
37
51
  /** Stands for a connector waiting for something that never arrives. */
38
52
  export class StuckConnector extends Connector {
39
53
  async open() {
@@ -180,6 +180,62 @@ describe('dependencies', () => {
180
180
  assert.deepStrictEqual(sequence, ['+b', '-b', '*b', '*a'])
181
181
  })
182
182
 
183
+ it('should disconnect a dependency taken on after disconnection', async () => {
184
+ a.depends(b)
185
+
186
+ await a.connect()
187
+ await a.disconnect()
188
+
189
+ // what a lookup that returns once the teardown walk has gone by amounts to
190
+ a.depends(c)
191
+
192
+ await c.connect()
193
+
194
+ assert.strictEqual(c.connected, false)
195
+ assert.ok(sequence.indexOf('*c') > sequence.indexOf('*a'))
196
+ })
197
+
198
+ it('should disconnect a connected dependency taken on after disconnection', async () => {
199
+ a.depends(b)
200
+
201
+ await a.connect()
202
+ await a.disconnect()
203
+ await c.connect()
204
+
205
+ a.depends(c)
206
+
207
+ // the discarding is not something the caller waits for
208
+ for (let i = 0; c.connected && i < 100; i++) await timeout(1)
209
+
210
+ assert.strictEqual(c.connected, false)
211
+ assert.ok(sequence.includes('*c'))
212
+ })
213
+
214
+ it('should not connect what was disconnected while connecting', async () => {
215
+ const late = new fixtures.LateConnector()
216
+
217
+ late.depends(c)
218
+ b.depends(c)
219
+
220
+ await b.connect()
221
+
222
+ const connecting = late.connect()
223
+
224
+ await late.disconnect()
225
+
226
+ late.arrive()
227
+
228
+ await connecting
229
+
230
+ // the connection landed after the disconnection, and nothing is left to say it again
231
+ assert.strictEqual(late.connected, false)
232
+
233
+ await b.disconnect()
234
+
235
+ assert.strictEqual(c.connected, false)
236
+ assert.ok(sequence.includes('*c'))
237
+ })
238
+
183
239
  describe('errors', () => {
184
240
  let f
185
241
 
@@ -1,10 +1,12 @@
1
1
  import { console } from 'openspan';
2
+ import { environment } from '@toa.io/generic';
2
3
  /** Abstract connections hierarchy */
3
4
  export class Connector {
4
5
  #dependencies = [];
5
6
  #links = [];
6
7
  #connecting;
7
8
  #disconnecting;
9
+ #discarded = false;
8
10
  id;
9
11
  connected = false;
10
12
  constructor() {
@@ -35,8 +37,28 @@ export class Connector {
35
37
  next = connector;
36
38
  this.#dependencies.push(next);
37
39
  next.link(this);
40
+ // See .#discard()
41
+ if (this.#disconnecting !== undefined)
42
+ next.#discard();
38
43
  return next;
39
44
  }
45
+ /**
46
+ * Disconnects, now and again if a connection is still to land.
47
+ *
48
+ * A dependency taken on after its dependant was disconnected — a lookup that returns
49
+ * once the teardown walk has gone by — is one nothing is left to take down, as the
50
+ * walk that would have reached it is over. One that stays connected holds open
51
+ * everything it depends on with it: a dependency reads `connected` of every dependant
52
+ * before it closes, and a dependant that is connected for good is a dependency that
53
+ * never closes.
54
+ */
55
+ #discard() {
56
+ this.#discarded = true;
57
+ // one that is up already, or on its way, is taken down here; one that is not is
58
+ // taken down by the connection when it comes, and until then there is nothing to do
59
+ if (this.#connecting !== undefined)
60
+ void this.#drop();
61
+ }
40
62
  /**
41
63
  * Creates a backlink to another Connector
42
64
  *
@@ -63,22 +85,42 @@ export class Connector {
63
85
  await this.open();
64
86
  };
65
87
  // anonymous grouping nodes are not worth a span
66
- this.#connecting =
67
- TRACE_BOOT && this.constructor.name !== 'Connector'
68
- ? console.span({
69
- name: `connect ${this.constructor.name}`,
70
- // a connector that has a locator is named by it; a grouping node has none
71
- attributes: { id: this.locator?.id ?? this.id }
72
- }, work)
73
- : work();
88
+ const connecting = TRACE_BOOT && this.constructor.name !== 'Connector'
89
+ ? console.span({
90
+ name: `connect ${this.constructor.name}`,
91
+ // a connector that has a locator is named by it; a grouping node has none
92
+ attributes: { id: this.locator?.id ?? this.id }
93
+ }, work)
94
+ : work();
95
+ this.#connecting = connecting;
74
96
  try {
75
- await this.#connecting;
76
- this.connected = true;
97
+ await connecting;
98
+ /*
99
+ * A disconnection that did not wait for this connection emptied `#connecting` to
100
+ * say so. Reporting `connected` now would report it of a connector nothing takes
101
+ * down again — its `#disconnecting` has settled, so a later call returns at once —
102
+ * and what it depends on reads that of every dependant before it closes.
103
+ */
104
+ if (this.#connecting === connecting)
105
+ this.connected = true;
77
106
  }
78
107
  catch (e) {
79
108
  await this.disconnect(true);
80
109
  throw e;
81
110
  }
111
+ // a connection asked for before this one was discarded, landing after. See .#discard()
112
+ if (this.#discarded)
113
+ await this.#drop();
114
+ }
115
+ /** Disconnects a discarded connector, saying so rather than throwing at whoever is not waiting. */
116
+ async #drop() {
117
+ this.#discarded = false;
118
+ try {
119
+ await this.disconnect();
120
+ }
121
+ catch (error) {
122
+ console.error('Discarded connector failed to disconnect', { id: this.id, error });
123
+ }
82
124
  }
83
125
  /**
84
126
  * Disconnects self then dependants
@@ -139,5 +181,5 @@ export class Connector {
139
181
  dispose() { }
140
182
  }
141
183
  const DELAY = 5000;
142
- const TRACE_BOOT = process.env.TOA_BOOT_TRACE === '1';
184
+ const TRACE_BOOT = environment.get('TOA_BOOT_TRACE') === '1';
143
185
  //# sourceMappingURL=connector.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"connector.js","sourceRoot":"","sources":["../source/connector.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"connector.js","sourceRoot":"","sources":["../source/connector.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { Connector } from './connector.js';
2
+ import { environment } from '@toa.io/generic';
2
3
  export class Context extends Connector {
3
4
  env;
4
5
  name;
@@ -9,8 +10,8 @@ export class Context extends Connector {
9
10
  #remotes = {};
10
11
  constructor(local, discover, aspects = []) {
11
12
  super();
12
- this.env = process.env.TOA_ENV;
13
- this.name = process.env.TOA_CONTEXT;
13
+ this.env = environment.get('TOA_ENV');
14
+ this.name = environment.get('TOA_CONTEXT');
14
15
  this.aspects = aspects;
15
16
  this.locator = local?.locator;
16
17
  this.#local = local;
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sourceRoot":"","sources":["../source/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAS1C,MAAM,OAAO,OAAQ,SAAQ,SAAS;IACpB,GAAG,CAAoB;IACvB,IAAI,CAAoB;IACxB,OAAO,CAAU;IACjB,OAAO,CAAS;IAEvB,MAAM,CAAW;IACjB,SAAS,CAAU;IACnB,QAAQ,GAAoC,EAAE,CAAA;IAEvD,YAAmB,KAAgB,EAAE,QAAkB,EAAE,OAAO,GAAa,EAAE;QAC7E,KAAK,EAAE,CAAA;QAEP,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAA;QAC9B,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAA;QACnC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,OAAO,GAAG,KAAK,EAAE,OAAO,CAAA;QAE7B,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;QACnB,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAA;QAEzB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;QAEnB,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IAC/C,CAAC;IAEM,KAAK,CAAC,KAAK,CAAC,QAAgB,EAAE,OAAgB;QACnD,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;IAC9C,CAAC;IAED,sCAAsC;IAC/B,KAAK,CAAC,IAAI,CACf,SAAiB,EACjB,IAAY,EACZ,QAAgB,EAChB,OAAgB;QAEhB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;QAElD,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;IACzC,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,SAAiB,EAAE,IAAY;QAC3C,MAAM,GAAG,GAAG,SAAS,GAAG,GAAG,GAAG,IAAI,CAAA;QAElC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;QAErD,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;IAC3B,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,SAAiB,EAAE,IAAY;QAC5C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;QAEpD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;QAEpB,OAAO,MAAM,CAAA;IACf,CAAC;CACF"}
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../source/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAS7C,MAAM,OAAO,OAAQ,SAAQ,SAAS;IACpB,GAAG,CAAoB;IACvB,IAAI,CAAoB;IACxB,OAAO,CAAU;IACjB,OAAO,CAAS;IAEvB,MAAM,CAAW;IACjB,SAAS,CAAU;IACnB,QAAQ,GAAoC,EAAE,CAAA;IAEvD,YAAmB,KAAgB,EAAE,QAAkB,EAAE,OAAO,GAAa,EAAE;QAC7E,KAAK,EAAE,CAAA;QAEP,IAAI,CAAC,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QACrC,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;QAC1C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,OAAO,GAAG,KAAK,EAAE,OAAO,CAAA;QAE7B,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;QACnB,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAA;QAEzB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;QAEnB,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IAC/C,CAAC;IAEM,KAAK,CAAC,KAAK,CAAC,QAAgB,EAAE,OAAgB;QACnD,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;IAC9C,CAAC;IAED,sCAAsC;IAC/B,KAAK,CAAC,IAAI,CACf,SAAiB,EACjB,IAAY,EACZ,QAAgB,EAChB,OAAgB;QAEhB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;QAElD,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;IACzC,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,SAAiB,EAAE,IAAY;QAC3C,MAAM,GAAG,GAAG,SAAS,GAAG,GAAG,GAAG,IAAI,CAAA;QAElC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;QAErD,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;IAC3B,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,SAAiB,EAAE,IAAY;QAC5C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;QAEpD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;QAEpB,OAAO,MAAM,CAAA;IACf,CAAC;CACF"}
@@ -14,10 +14,15 @@ export interface Definition extends Explanation {
14
14
  scope?: string;
15
15
  query?: boolean;
16
16
  }
17
+ /** What the operation works on, as the manifest declares it. */
18
+ export interface Entity {
19
+ properties: Record<string, JSONSchema>;
20
+ required?: string[];
21
+ }
17
22
  export declare class Request extends Contract {
18
23
  readonly discovery: Explanation;
19
24
  static Exception: Refusal;
20
- constructor(schema: Schema, definition: Definition);
25
+ constructor(schema: Schema, definition: Definition, entity?: Entity);
21
26
  static schema(definition: Definition, entity?: {
22
27
  properties: Record<string, JSONSchema>;
23
28
  }): JSONSchema;
@@ -4,11 +4,23 @@ import { Contract } from './contract.js';
4
4
  export class Request extends Contract {
5
5
  discovery = {};
6
6
  static Exception = RequestContractException;
7
- constructor(schema, definition) {
7
+ constructor(schema, definition, entity) {
8
8
  super(schema);
9
9
  for (const key of ['description', 'input', 'output', 'errors'])
10
10
  if (definition[key] !== undefined)
11
11
  this.discovery[key] = definition[key];
12
+ /*
13
+ * An operation that states no output states an empty schema, which every reply fits —
14
+ * and which says nothing to whoever reads it. What its type answers does, so that is
15
+ * said here instead. Here and not in the definition, because the definition is what a
16
+ * reply is held to: a projection is a lawful answer, and describing one is not the same
17
+ * as requiring it.
18
+ */
19
+ if (empty(this.discovery.output)) {
20
+ const answered = answers(definition, entity);
21
+ if (answered !== undefined)
22
+ this.discovery.output = answered;
23
+ }
12
24
  }
13
25
  static schema(definition, entity) {
14
26
  // `source` is not among these: the framework stamps it and whoever reads it takes the keys
@@ -56,4 +68,36 @@ export class Request extends Contract {
56
68
  return schema;
57
69
  }
58
70
  }
71
+ /**
72
+ * What an operation of this type answers, where it did not say.
73
+ *
74
+ * A transition, an observation and an assignment work on the Entity Object, so that is what
75
+ * they answer — a set of them where the scope is a set. A computation neither uses the scope
76
+ * nor produces one, and an effect answers whatever it computed, so neither is guessed at.
77
+ * A stream is not a value with a schema, and is not one either.
78
+ *
79
+ * What the entity requires is said as well. It is a description and nothing is held to it —
80
+ * an operation answering a projection says so by declaring its own output — and a shape
81
+ * whose every property reads as optional says less than it knows.
82
+ */
83
+ function answers(definition, entity) {
84
+ if (entity === undefined || !ENTITY.has(definition.type))
85
+ return undefined;
86
+ const object = {
87
+ type: 'object',
88
+ properties: entity.properties,
89
+ ...(entity.required === undefined ? {} : { required: entity.required })
90
+ };
91
+ if (definition.scope === 'objects')
92
+ return { type: 'array', items: object };
93
+ return definition.scope === 'object' || definition.scope === 'changeset'
94
+ ? object
95
+ : undefined;
96
+ }
97
+ /** The types whose subject is the Entity Object; see `documentation/design.md`. */
98
+ const ENTITY = new Set(['transition', 'observation', 'assignment']);
99
+ /** A schema that states nothing, which is what an operation saying nothing normalizes to. */
100
+ function empty(schema) {
101
+ return schema === undefined || schema === null || Object.keys(schema).length === 0;
102
+ }
59
103
  //# sourceMappingURL=request.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"request.js","sourceRoot":"","sources":["../../source/contract/request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAA;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAmBxC,MAAM,OAAO,OAAQ,SAAQ,QAAQ;IACnB,SAAS,GAAgB,EAAE,CAAA;IAEpC,MAAM,CAAU,SAAS,GAC9B,wBAA8C,CAAA;IAEhD,YAAmB,MAAc,EAAE,UAAsB;QACvD,KAAK,CAAC,MAAM,CAAC,CAAA;QAEb,KAAK,MAAM,GAAG,IAAI,CAAC,aAAa,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAU;YACrE,IAAI,UAAU,CAAC,GAAG,CAAC,KAAK,SAAS;gBAC9B,IAAI,CAAC,SAAqC,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,CAAA;IACxE,CAAC;IAEM,MAAM,CAAC,MAAM,CAClB,UAAsB,EACtB,MAAmD;QAEnD,2FAA2F;QAC3F,6FAA6F;QAC7F,MAAM,MAAM,GAAe;YACzB,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;gBAC9B,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;aAC1B;YACD,oBAAoB,EAAE,IAAI;SAC3B,CAAA;QAED,MAAM,QAAQ,GAAa,EAAE,CAAA;QAE7B,IAAI,UAAU,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YACnC,MAAM,CAAC,UAAU,CAAC,KAAK,GAAG,UAAU,CAAC,KAAK,CAAA;YAC1C,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACxB,CAAC;;YAAM,MAAM,CAAC,UAAU,CAAC,KAAK,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;QAEjD,IAAI,MAAM,KAAK,SAAS;YAAE,UAAU,CAAC,KAAK,GAAG,KAAK,CAAA;QAElD,IAAI,UAAU,CAAC,KAAK,KAAK,IAAI;YAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAErD,IAAI,UAAU,CAAC,KAAK,KAAK,KAAK;YAAE,MAAM,CAAC,UAAU,CAAC,KAAK,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;QAE1E,IAAI,UAAU,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;YAC/B,MAAM,KAAK,GAAG,eAAe,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;YAE5C,KAAK,CAAC,UAAU,CAAC,EAAE,GAAG,MAAM,EAAE,UAAU,CAAC,EAAE,CAAA;YAE3C,IAAI,UAAU,CAAC,IAAI,KAAK,aAAa;gBAAE,OAAO,KAAK,CAAC,UAAU,CAAC,OAAO,CAAA;;gBACjE,OAAO,KAAK,CAAC,UAAU,CAAC,UAAU,CAAA;YAEvC,IAAI,UAAU,CAAC,IAAI,KAAK,aAAa,IAAI,UAAU,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxE,OAAO,KAAK,CAAC,UAAU,CAAC,IAAI,CAAA;gBAC5B,OAAO,KAAK,CAAC,UAAU,CAAC,KAAK,CAAA;YAC/B,CAAC;iBAAM,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS;gBAAE,KAAK,CAAC,QAAQ,GAAG,CAAC,OAAO,CAAC,CAAA;;gBAC9D,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YAEjC,MAAM,CAAC,UAAU,CAAC,KAAK,GAAG,KAAK,CAAA;QACjC,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAA;QAEnD,OAAO,MAAM,CAAA;IACf,CAAC;CACF"}
1
+ {"version":3,"file":"request.js","sourceRoot":"","sources":["../../source/contract/request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AACvC,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAA;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAyBxC,MAAM,OAAO,OAAQ,SAAQ,QAAQ;IACnB,SAAS,GAAgB,EAAE,CAAA;IAEpC,MAAM,CAAU,SAAS,GAC9B,wBAA8C,CAAA;IAEhD,YAAmB,MAAc,EAAE,UAAsB,EAAE,MAAe;QACxE,KAAK,CAAC,MAAM,CAAC,CAAA;QAEb,KAAK,MAAM,GAAG,IAAI,CAAC,aAAa,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAU;YACrE,IAAI,UAAU,CAAC,GAAG,CAAC,KAAK,SAAS;gBAC9B,IAAI,CAAC,SAAqC,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,CAAA;QAEtE;;;;;;WAMG;QACH,IAAI,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;YACjC,MAAM,QAAQ,GAAG,OAAO,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;YAE5C,IAAI,QAAQ,KAAK,SAAS;gBAAE,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,QAAQ,CAAA;QAC9D,CAAC;IACH,CAAC;IAEM,MAAM,CAAC,MAAM,CAClB,UAAsB,EACtB,MAAmD;QAEnD,2FAA2F;QAC3F,6FAA6F;QAC7F,MAAM,MAAM,GAAe;YACzB,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;gBAC9B,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;aAC1B;YACD,oBAAoB,EAAE,IAAI;SAC3B,CAAA;QAED,MAAM,QAAQ,GAAa,EAAE,CAAA;QAE7B,IAAI,UAAU,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YACnC,MAAM,CAAC,UAAU,CAAC,KAAK,GAAG,UAAU,CAAC,KAAK,CAAA;YAC1C,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACxB,CAAC;;YAAM,MAAM,CAAC,UAAU,CAAC,KAAK,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;QAEjD,IAAI,MAAM,KAAK,SAAS;YAAE,UAAU,CAAC,KAAK,GAAG,KAAK,CAAA;QAElD,IAAI,UAAU,CAAC,KAAK,KAAK,IAAI;YAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAErD,IAAI,UAAU,CAAC,KAAK,KAAK,KAAK;YAAE,MAAM,CAAC,UAAU,CAAC,KAAK,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;QAE1E,IAAI,UAAU,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;YAC/B,MAAM,KAAK,GAAG,eAAe,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;YAE5C,KAAK,CAAC,UAAU,CAAC,EAAE,GAAG,MAAM,EAAE,UAAU,CAAC,EAAE,CAAA;YAE3C,IAAI,UAAU,CAAC,IAAI,KAAK,aAAa;gBAAE,OAAO,KAAK,CAAC,UAAU,CAAC,OAAO,CAAA;;gBACjE,OAAO,KAAK,CAAC,UAAU,CAAC,UAAU,CAAA;YAEvC,IAAI,UAAU,CAAC,IAAI,KAAK,aAAa,IAAI,UAAU,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxE,OAAO,KAAK,CAAC,UAAU,CAAC,IAAI,CAAA;gBAC5B,OAAO,KAAK,CAAC,UAAU,CAAC,KAAK,CAAA;YAC/B,CAAC;iBAAM,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS;gBAAE,KAAK,CAAC,QAAQ,GAAG,CAAC,OAAO,CAAC,CAAA;;gBAC9D,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YAEjC,MAAM,CAAC,UAAU,CAAC,KAAK,GAAG,KAAK,CAAA;QACjC,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAA;QAEnD,OAAO,MAAM,CAAA;IACf,CAAC;CACF;AAED;;;;;;;;;;;GAWG;AACH,SAAS,OAAO,CAAC,UAAsB,EAAE,MAAe;IACtD,IAAI,MAAM,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,IAAc,CAAC;QAAE,OAAO,SAAS,CAAA;IAEpF,MAAM,MAAM,GAAe;QACzB,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,GAAG,CAAC,MAAM,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;KACxE,CAAA;IAED,IAAI,UAAU,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAgB,CAAA;IAEzF,OAAO,UAAU,CAAC,KAAK,KAAK,QAAQ,IAAI,UAAU,CAAC,KAAK,KAAK,WAAW;QACtE,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,SAAS,CAAA;AACf,CAAC;AAED,mFAAmF;AACnF,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,aAAa,EAAE,YAAY,CAAC,CAAC,CAAA;AAEnE,6FAA6F;AAC7F,SAAS,KAAK,CAAC,MAAqC;IAClD,OAAO,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,CAAA;AACpF,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import { Readable } from 'node:stream';
2
2
  import { Connector } from './connector.js';
3
3
  import { SystemException, RequestContractException } from './exceptions.js';
4
+ import { environment } from '@toa.io/generic';
4
5
  export class Operation extends Connector {
5
6
  scope;
6
7
  /**
@@ -62,7 +63,7 @@ export class Operation extends Connector {
62
63
  const { request, state } = store;
63
64
  const reply = await this.#cascade.run(request.input, state);
64
65
  // validate reply only on local environments
65
- if (process.env.TOA_ENV === 'local' && !(reply instanceof Readable))
66
+ if (environment.get('TOA_ENV') === 'local' && !(reply instanceof Readable))
66
67
  this.#contracts.reply.fit(reply);
67
68
  store.reply = reply;
68
69
  }