@toa.io/extensions.convergence 1.0.0-alpha.298 → 1.0.0-alpha.299
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/LICENSE +22 -0
- package/package.json +6 -5
- package/readme.md +51 -25
- package/transpiled/Destination.d.ts +10 -2
- package/transpiled/Destination.js +21 -3
- package/transpiled/Destination.js.map +1 -1
- package/transpiled/Factory.d.ts +8 -6
- package/transpiled/Factory.js +31 -8
- package/transpiled/Factory.js.map +1 -1
- package/transpiled/Storage.d.ts +2 -2
- package/transpiled/Storage.js +24 -14
- package/transpiled/Storage.js.map +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2020-present Artem Gurtovoi
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/extensions.convergence",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.299",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Toa Convergence extension",
|
|
6
6
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
@@ -28,10 +28,11 @@
|
|
|
28
28
|
"transpile": "tsc"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@toa.io/core": "1.0.0-alpha.
|
|
32
|
-
"@toa.io/definitions": "1.0.0-alpha.
|
|
31
|
+
"@toa.io/core": "1.0.0-alpha.299",
|
|
32
|
+
"@toa.io/definitions": "1.0.0-alpha.299",
|
|
33
33
|
"@toa.io/generic": "1.0.0-alpha.292",
|
|
34
|
-
"@toa.io/pointer": "1.0.0-alpha.
|
|
34
|
+
"@toa.io/pointer": "1.0.0-alpha.299",
|
|
35
35
|
"openspan": "1.0.0-alpha.293"
|
|
36
|
-
}
|
|
36
|
+
},
|
|
37
|
+
"gitHead": "2ba95b16f2cf06f82d6ff8d436761e843e5e4b43"
|
|
37
38
|
}
|
package/readme.md
CHANGED
|
@@ -5,39 +5,51 @@ entity state.
|
|
|
5
5
|
|
|
6
6
|
## Declaration
|
|
7
7
|
|
|
8
|
-
A region is a deployment of the context,
|
|
9
|
-
|
|
8
|
+
A region is a deployment of the context, and **the environment is which region it is** — the
|
|
9
|
+
same thing that already decides which database and which broker it uses. So a context declares
|
|
10
|
+
one of these per region, and they share nothing:
|
|
10
11
|
|
|
11
12
|
```yaml
|
|
12
13
|
# context.toa.yaml
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
mongodb@eu: mongodb://mongo.eu.example.com/store
|
|
15
|
+
mongodb@us: mongodb://mongo.us.example.com/store
|
|
16
|
+
|
|
17
|
+
convergence@eu:
|
|
18
|
+
priority: 0 # a rank: 0 outranks 1
|
|
19
|
+
binding:
|
|
20
|
+
provider: amqp
|
|
21
|
+
pointer: [amqp://cnv-eu-0.example.com, amqp://cnv-eu-1.example.com]
|
|
22
|
+
|
|
23
|
+
convergence@us:
|
|
24
|
+
priority: 1
|
|
25
|
+
binding: { provider: amqp, pointer: amqp://cnv-us.example.com }
|
|
22
26
|
```
|
|
23
27
|
|
|
28
|
+
```shell
|
|
29
|
+
$ toa deploy eu
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
An environment that declares no `convergence` is not a region and converges nothing, so the
|
|
33
|
+
same context still deploys to `staging` as one place.
|
|
34
|
+
|
|
24
35
|
Nothing is declared in a manifest. A context that declares convergence converges **every
|
|
25
|
-
component that stores anything
|
|
36
|
+
component that stores anything** — its own, and the ones its extensions ship: identity is what
|
|
37
|
+
holds users, their roles and the keys their tokens are read with, and a region without them is
|
|
38
|
+
one where nobody registered elsewhere exists.
|
|
39
|
+
|
|
40
|
+
**A storage that does not converge stands the component down, and says so at boot.** Nothing
|
|
41
|
+
else about the component changes and nothing else is held back; it is one line in the log,
|
|
42
|
+
because a component quietly not converging is two regions differing with nothing to notice it.
|
|
26
43
|
|
|
27
44
|
`pointer` is a [pointer](/libraries/pointer), so a URL carries no credentials — they are
|
|
28
45
|
deployed as secrets — and shards syntax works. It is flat: a URL or a list of them. A region has
|
|
29
46
|
one set of brokers, and they are that region's own, shared with nothing else.
|
|
30
47
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
$ TOA_CONVERGENCE_REGION=eu toa deploy production
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
An operator can write it in `.env` instead of prefixing every command. Without it, a context
|
|
40
|
-
that declares convergence is refused rather than deployed as a region it cannot name.
|
|
48
|
+
**`priority` is the same table in every region, and nothing checks that.** A deployment reads
|
|
49
|
+
only its own declaration, so two regions given one rank is a misconfiguration no deploy can see:
|
|
50
|
+
ties between those two would resolve for neither. What notices is the runtime — a record can
|
|
51
|
+
only carry the rank of the region that wrote it, so one arriving with this region's own rank is
|
|
52
|
+
reported as an error.
|
|
41
53
|
|
|
42
54
|
**The region an existing application is first deployed as is rank `0`.** Its records were
|
|
43
55
|
written by whatever region it is now becoming, and that is what they are recorded as.
|
|
@@ -56,6 +68,11 @@ Each region's broker holds two exchanges, the same two names everywhere:
|
|
|
56
68
|
| `convergence.in` | **federated** from every other region's `convergence.out`. |
|
|
57
69
|
| `convergence.<namespace>.<name>` | a durable queue per component, bound to `convergence.in` under that component's key. |
|
|
58
70
|
|
|
71
|
+
Nothing declares those queues for a region that is not running yet, which is what makes adding
|
|
72
|
+
one an order that matters, and adding a converging component to a running pair the same:
|
|
73
|
+
`toa export convergence <environment>` prints what a region's broker must carry, and
|
|
74
|
+
[operations](./operations.md) says when to declare it.
|
|
75
|
+
|
|
59
76
|
**Toa does not configure the federation.** On each region's broker, an upstream per other region
|
|
60
77
|
and one policy over them:
|
|
61
78
|
|
|
@@ -96,14 +113,19 @@ where a change that failed to publish would be lost rather than recovered, and t
|
|
|
96
113
|
differ with nothing to say so. Events degrade there; convergence refuses.
|
|
97
114
|
|
|
98
115
|
**[Atomicity](/connectors/atomicity).** Without it the outbox pump recovers nothing, so a
|
|
99
|
-
publication that fails is never retried and that change is lost for good. Nothing
|
|
100
|
-
|
|
116
|
+
publication that fails is never retried and that change is lost for good. Nothing can refuse at
|
|
117
|
+
boot over it — a replica owns no lane for a moment anyway — so what happens instead is that the
|
|
118
|
+
pump says so, every ten cycles it has owned none.
|
|
119
|
+
|
|
120
|
+
**Which delayed calls a region makes is `cadence.regions`,** not something held back from
|
|
121
|
+
convergence: every region holds every region's rows, and each makes the calls of the region it
|
|
122
|
+
is. See [cadence](/extensions/cadence#regions).
|
|
101
123
|
|
|
102
124
|
**The same components in every region.** A component deployed in one and not another has no
|
|
103
125
|
queue there, so its records cannot be routed; publishing is `mandatory`, so what had nowhere to
|
|
104
126
|
go is logged rather than dropped in silence.
|
|
105
127
|
|
|
106
|
-
**
|
|
128
|
+
**Uniqueness over anything but `id` is not safe.** Two regions can independently take the same
|
|
107
129
|
value, and the record that arrives second cannot be stored at all. It is logged and dropped;
|
|
108
130
|
redelivery would not help.
|
|
109
131
|
|
|
@@ -114,4 +136,8 @@ that region's rank. An incoming record wins where its version is greater, or whe
|
|
|
114
136
|
is equal and the region that wrote it outranks the one that wrote what is stored. `priority` is
|
|
115
137
|
read there and nowhere else.
|
|
116
138
|
|
|
139
|
+
**`context.region` is that rank**, which is what a component reads to tell where it is running.
|
|
140
|
+
It is zero where an application is deployed as one place, which is what such a deployment's
|
|
141
|
+
records carry.
|
|
142
|
+
|
|
117
143
|
An unmanaged operation reads the driver's own handle, and will see the field.
|
|
@@ -12,14 +12,22 @@ export declare class Destination extends Connector implements outbox.Destination
|
|
|
12
12
|
private readonly resolve;
|
|
13
13
|
private readonly span;
|
|
14
14
|
private outbound;
|
|
15
|
+
/** whether the storage under this component turned out to be one that does not converge */
|
|
16
|
+
private disabled;
|
|
15
17
|
constructor(locator: Locator, resolve: () => Promise<bindings.Outbound>);
|
|
16
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Stands down: the half that receives found a storage that does not converge, so neither
|
|
20
|
+
* does this component, and publishing what no region will consume would be returns and
|
|
21
|
+
* nothing else. The row settles at once rather than staying outstanding for good.
|
|
22
|
+
*/
|
|
23
|
+
disable(): void;
|
|
24
|
+
emit(row: outbox.Row): Promise<void>;
|
|
17
25
|
protected open(): Promise<void>;
|
|
18
26
|
}
|
|
19
27
|
/** What convergence puts on the wire. Owned by this extension at both ends. */
|
|
20
28
|
export interface Message {
|
|
21
29
|
/** the record as it stands — VERSION, timestamps, REGION and all */
|
|
22
30
|
record: object;
|
|
23
|
-
/** W3C traceparent, so the
|
|
31
|
+
/** W3C traceparent, so the far side continues the trace of the write that caused it */
|
|
24
32
|
trace?: string;
|
|
25
33
|
}
|
|
@@ -12,6 +12,8 @@ export class Destination extends Connector {
|
|
|
12
12
|
resolve;
|
|
13
13
|
span;
|
|
14
14
|
outbound;
|
|
15
|
+
/** whether the storage under this component turned out to be one that does not converge */
|
|
16
|
+
disabled = false;
|
|
15
17
|
constructor(locator, resolve) {
|
|
16
18
|
super();
|
|
17
19
|
this.label = locator.id;
|
|
@@ -22,21 +24,37 @@ export class Destination extends Connector {
|
|
|
22
24
|
attributes: { 'messaging.destination.name': CHANNEL }
|
|
23
25
|
};
|
|
24
26
|
}
|
|
25
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Stands down: the half that receives found a storage that does not converge, so neither
|
|
29
|
+
* does this component, and publishing what no region will consume would be returns and
|
|
30
|
+
* nothing else. The row settles at once rather than staying outstanding for good.
|
|
31
|
+
*/
|
|
32
|
+
disable() {
|
|
33
|
+
this.disabled = true;
|
|
34
|
+
}
|
|
35
|
+
async emit(row) {
|
|
36
|
+
if (this.disabled)
|
|
37
|
+
return;
|
|
26
38
|
await console.span(this.span, async () => {
|
|
27
39
|
const context = current();
|
|
28
40
|
/*
|
|
29
41
|
* The record says which region wrote it, so the message does not say it again. `trace`
|
|
30
42
|
* is convergence's own field rather than anything a binding puts there: it owns both
|
|
31
|
-
* ends, and it wants the
|
|
43
|
+
* ends, and it wants the far side to continue the trace of the write that caused it.
|
|
44
|
+
*
|
|
45
|
+
* `row.trail` is deliberately not carried. The far side writes the record through the
|
|
46
|
+
* storage rather than through an operation, so it makes no call and publishes no event —
|
|
47
|
+
* there is no chain there to continue, and nothing to refuse.
|
|
32
48
|
*/
|
|
33
|
-
const message = { record: event.state };
|
|
49
|
+
const message = { record: row.event.state };
|
|
34
50
|
if (context !== undefined)
|
|
35
51
|
message.trace = encode(context);
|
|
36
52
|
await this.outbound.send(this.label, message);
|
|
37
53
|
});
|
|
38
54
|
}
|
|
39
55
|
async open() {
|
|
56
|
+
if (this.disabled)
|
|
57
|
+
return;
|
|
40
58
|
this.outbound = await this.resolve();
|
|
41
59
|
await this.outbound.connect();
|
|
42
60
|
this.depends(this.outbound);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Destination.js","sourceRoot":"","sources":["../source/Destination.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAoB,MAAM,UAAU,CAAA;AACrE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,4CAA4C,CAAA;AAIpE;;;;GAIG;AACH,MAAM,OAAO,WAAY,SAAQ,SAAS;IACxB,IAAI,GAAG,OAAO,CAAA;IAEb,KAAK,CAAQ;IACb,OAAO,CAAkC;IACzC,IAAI,CAAa;IAE1B,QAAQ,CAAoB;IAEpC,YAAmB,OAAgB,EAAE,OAAyC;QAC5E,KAAK,EAAE,CAAA;QAEP,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,EAAE,CAAA;QACvB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QAEtB,IAAI,CAAC,IAAI,GAAG;YACV,IAAI,EAAE,GAAG,OAAO,OAAO;YACvB,IAAI,EAAE,UAAU;YAChB,UAAU,EAAE,EAAE,4BAA4B,EAAE,OAAO,EAAE;SACtD,CAAA;IACH,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"Destination.js","sourceRoot":"","sources":["../source/Destination.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAoB,MAAM,UAAU,CAAA;AACrE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,4CAA4C,CAAA;AAIpE;;;;GAIG;AACH,MAAM,OAAO,WAAY,SAAQ,SAAS;IACxB,IAAI,GAAG,OAAO,CAAA;IAEb,KAAK,CAAQ;IACb,OAAO,CAAkC;IACzC,IAAI,CAAa;IAE1B,QAAQ,CAAoB;IAEpC,2FAA2F;IACnF,QAAQ,GAAG,KAAK,CAAA;IAExB,YAAmB,OAAgB,EAAE,OAAyC;QAC5E,KAAK,EAAE,CAAA;QAEP,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,EAAE,CAAA;QACvB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QAEtB,IAAI,CAAC,IAAI,GAAG;YACV,IAAI,EAAE,GAAG,OAAO,OAAO;YACvB,IAAI,EAAE,UAAU;YAChB,UAAU,EAAE,EAAE,4BAA4B,EAAE,OAAO,EAAE;SACtD,CAAA;IACH,CAAC;IAED;;;;OAIG;IACI,OAAO;QACZ,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;IACtB,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,GAAe;QAC/B,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAM;QAEzB,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,IAAI,EAAE;YACvC,MAAM,OAAO,GAAG,OAAO,EAAE,CAAA;YAEzB;;;;;;;;eAQG;YACH,MAAM,OAAO,GAAY,EAAE,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;YAEpD,IAAI,OAAO,KAAK,SAAS;gBAAE,OAAO,CAAC,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,CAAA;YAE1D,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;QAC/C,CAAC,CAAC,CAAA;IACJ,CAAC;IAEkB,KAAK,CAAC,IAAI;QAC3B,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAM;QAEzB,IAAI,CAAC,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAA;QAEpC,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAA;QAE7B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IAC7B,CAAC;CACF"}
|
package/transpiled/Factory.d.ts
CHANGED
|
@@ -10,15 +10,17 @@ export declare class Factory implements extensions.Factory {
|
|
|
10
10
|
private readonly host;
|
|
11
11
|
/** one publisher for the process, however many components send through it */
|
|
12
12
|
private outbound;
|
|
13
|
+
/**
|
|
14
|
+
* What this extension was given, by component, until the storage of that component is made.
|
|
15
|
+
* What decorates a storage is every extension loaded in the process rather than the ones a
|
|
16
|
+
* manifest named, so this is what tells a component this extension converges from one it was
|
|
17
|
+
* never asked about — one that stores nothing, or one of another composition entirely.
|
|
18
|
+
*/
|
|
19
|
+
private readonly destinations;
|
|
13
20
|
constructor(host: extensions.Host);
|
|
14
21
|
/** out */
|
|
15
22
|
destination(locator: Locator, _: null, manifest: Manifest): Destination | undefined;
|
|
16
|
-
/**
|
|
17
|
-
* In. Guarded, because what decorates a storage is every extension loaded in the process
|
|
18
|
-
* rather than the ones a manifest named: once anything here converges, this is asked about
|
|
19
|
-
* everything. Convergence is a property of a deployment, so what it is deployed with is what
|
|
20
|
-
* says whether there is any.
|
|
21
|
-
*/
|
|
23
|
+
/** in, for the components this extension was given and no others */
|
|
22
24
|
storage(storage: storages.Storage, locator: Locator): storages.Storage;
|
|
23
25
|
private publisher;
|
|
24
26
|
private binding;
|
package/transpiled/Factory.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { console } from 'openspan';
|
|
1
2
|
import { environment } from '@toa.io/generic';
|
|
2
3
|
import { resolve } from '@toa.io/pointer';
|
|
3
4
|
import { BINDING, BROKERS, CHANNEL, ID } from '@toa.io/definitions/extensions.convergence';
|
|
@@ -12,6 +13,13 @@ export class Factory {
|
|
|
12
13
|
host;
|
|
13
14
|
/** one publisher for the process, however many components send through it */
|
|
14
15
|
outbound;
|
|
16
|
+
/**
|
|
17
|
+
* What this extension was given, by component, until the storage of that component is made.
|
|
18
|
+
* What decorates a storage is every extension loaded in the process rather than the ones a
|
|
19
|
+
* manifest named, so this is what tells a component this extension converges from one it was
|
|
20
|
+
* never asked about — one that stores nothing, or one of another composition entirely.
|
|
21
|
+
*/
|
|
22
|
+
destinations = new Map();
|
|
15
23
|
constructor(host) {
|
|
16
24
|
this.host = host;
|
|
17
25
|
}
|
|
@@ -19,17 +27,32 @@ export class Factory {
|
|
|
19
27
|
destination(locator, _, manifest) {
|
|
20
28
|
if (!converging() || manifest.entity === undefined)
|
|
21
29
|
return undefined;
|
|
22
|
-
|
|
30
|
+
const destination = new Destination(locator, async () => this.publisher());
|
|
31
|
+
this.destinations.set(locator.id, destination);
|
|
32
|
+
return destination;
|
|
23
33
|
}
|
|
24
|
-
/**
|
|
25
|
-
* In. Guarded, because what decorates a storage is every extension loaded in the process
|
|
26
|
-
* rather than the ones a manifest named: once anything here converges, this is asked about
|
|
27
|
-
* everything. Convergence is a property of a deployment, so what it is deployed with is what
|
|
28
|
-
* says whether there is any.
|
|
29
|
-
*/
|
|
34
|
+
/** in, for the components this extension was given and no others */
|
|
30
35
|
storage(storage, locator) {
|
|
31
|
-
|
|
36
|
+
const destination = this.destinations.get(locator.id);
|
|
37
|
+
// the pairing is for the component being built, and one is built once: an extension is
|
|
38
|
+
// loaded once for the life of a process, and holding a component's id past its build
|
|
39
|
+
// would decorate the next one to carry that id
|
|
40
|
+
this.destinations.delete(locator.id);
|
|
41
|
+
if (destination === undefined || !converging())
|
|
42
|
+
return storage;
|
|
43
|
+
/*
|
|
44
|
+
* A storage that does not converge has nowhere to put what another region wrote, so
|
|
45
|
+
* neither does this component — and the half that publishes stands down with it, because
|
|
46
|
+
* what it sent would reach a queue no region declares. Said out loud, because a component
|
|
47
|
+
* silently not converging is two regions differing with nothing to notice it.
|
|
48
|
+
*/
|
|
49
|
+
if (storage.converges !== true) {
|
|
50
|
+
destination.disable();
|
|
51
|
+
console.warn('Component does not converge: its storage cannot', {
|
|
52
|
+
component: locator.id
|
|
53
|
+
});
|
|
32
54
|
return storage;
|
|
55
|
+
}
|
|
33
56
|
return new Converging(storage, locator, async (sink) => this.host.inbound(this.binding(), CHANNEL, this.uris(), locator.id, sink));
|
|
34
57
|
}
|
|
35
58
|
async publisher() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Factory.js","sourceRoot":"","sources":["../source/Factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AACzC,OAAO,EACL,OAAO,EACP,OAAO,EACP,OAAO,EACP,EAAE,EACH,MAAM,4CAA4C,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAIzC;;;;GAIG;AACH,MAAM,OAAO,OAAO;IACD,IAAI,CAAiB;IAEtC,6EAA6E;IACrE,QAAQ,CAAwC;IAExD,YAAmB,IAAqB;QACtC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;IAClB,CAAC;IAED,UAAU;IACH,WAAW,CAChB,OAAgB,EAChB,CAAO,EACP,QAAkB;QAElB,IAAI,CAAC,UAAU,EAAE,IAAI,QAAQ,CAAC,MAAM,KAAK,SAAS;YAAE,OAAO,SAAS,CAAA;QAEpE,
|
|
1
|
+
{"version":3,"file":"Factory.js","sourceRoot":"","sources":["../source/Factory.ts"],"names":[],"mappings":"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"}
|
package/transpiled/Storage.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export declare class Converging extends Connector implements storages.Storage, b
|
|
|
27
27
|
get raw(): unknown;
|
|
28
28
|
get outbox(): storages.Storage['outbox'];
|
|
29
29
|
get migrates(): boolean | undefined;
|
|
30
|
-
get
|
|
30
|
+
get converges(): boolean | undefined;
|
|
31
31
|
get(query: storages.Query): Promise<storages.Record | null>;
|
|
32
32
|
find(query: storages.Query): Promise<storages.Record[]>;
|
|
33
33
|
stream(query?: storages.Query): Promise<Readable>;
|
|
@@ -35,5 +35,5 @@ export declare class Converging extends Connector implements storages.Storage, b
|
|
|
35
35
|
massStore(records: storages.Record[], rows?: outbox.Row[]): Promise<boolean>;
|
|
36
36
|
upsert(query: storages.Query, changeset: object, row?: outbox.Row): Promise<storages.Record | null>;
|
|
37
37
|
ensure(query: storages.Query | undefined, properties: object, record: storages.Record, row?: outbox.Row): Promise<storages.Record>;
|
|
38
|
-
|
|
38
|
+
converge(record: storages.Record): Promise<boolean>;
|
|
39
39
|
}
|
package/transpiled/Storage.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { console, decode, run } from 'openspan';
|
|
2
|
+
import { environment } from '@toa.io/generic';
|
|
2
3
|
import { Connector } from '@toa.io/core';
|
|
3
4
|
import { CHANNEL } from '@toa.io/definitions/extensions.convergence';
|
|
4
5
|
/**
|
|
@@ -31,7 +32,7 @@ export class Converging extends Connector {
|
|
|
31
32
|
attributes: { 'messaging.destination.name': CHANNEL }
|
|
32
33
|
};
|
|
33
34
|
this.processing = {
|
|
34
|
-
name: `${CHANNEL}
|
|
35
|
+
name: `${CHANNEL} process`,
|
|
35
36
|
kind: 'consumer',
|
|
36
37
|
service: locator.id,
|
|
37
38
|
attributes: { 'messaging.destination.name': CHANNEL }
|
|
@@ -43,12 +44,24 @@ export class Converging extends Connector {
|
|
|
43
44
|
*/
|
|
44
45
|
async accept(message) {
|
|
45
46
|
const { record, trace } = message;
|
|
47
|
+
/*
|
|
48
|
+
* A record carrying this region's own rank cannot have come from anywhere: a region is
|
|
49
|
+
* not bound to what it publishes, and it republishes nothing it converges, so the rank on a
|
|
50
|
+
* record that arrives is the rank of the region that wrote it. Two of them sharing one is
|
|
51
|
+
* a misconfiguration nothing else can see — no deployment knows what the others declared —
|
|
52
|
+
* and it means ties between those two resolve for neither.
|
|
53
|
+
*/
|
|
54
|
+
if (record.REGION === REGION)
|
|
55
|
+
console.error('Convergence received a record of this region\'s own rank', {
|
|
56
|
+
component: this.locator.id,
|
|
57
|
+
region: REGION
|
|
58
|
+
});
|
|
46
59
|
const remote = trace === undefined ? null : decode(trace);
|
|
47
60
|
const task = async () => console.span(this.delivery, async () => console.span(this.processing, async () => {
|
|
48
|
-
const
|
|
49
|
-
console.
|
|
61
|
+
const applied = await this.storage.converge(record);
|
|
62
|
+
console.trace('Convergence processed', {
|
|
50
63
|
component: this.locator.id,
|
|
51
|
-
outcome:
|
|
64
|
+
outcome: applied ? 'applied' : 'stale'
|
|
52
65
|
});
|
|
53
66
|
}));
|
|
54
67
|
if (remote === null)
|
|
@@ -57,18 +70,13 @@ export class Converging extends Connector {
|
|
|
57
70
|
await run(remote, task);
|
|
58
71
|
}
|
|
59
72
|
async open() {
|
|
60
|
-
if (this.storage.merges !== true)
|
|
61
|
-
throw new Error(`Component '${this.locator.id}' converges, which its storage does not do. ` +
|
|
62
|
-
'A record written in another region has nowhere to land.');
|
|
63
73
|
/*
|
|
64
74
|
* Without a durable outbox a publication is lost where the process dies before it lands,
|
|
65
75
|
* and a lost one here is two regions differing for good, with nothing that notices and
|
|
66
76
|
* nothing that repairs it. That is why this refuses where events only degrade.
|
|
67
77
|
*/
|
|
68
78
|
if (this.storage.outbox === undefined)
|
|
69
|
-
throw new Error(`Component '${this.locator.id}' converges, and its
|
|
70
|
-
'MongoDB is not a replica set, so a change that fails to publish is lost rather ' +
|
|
71
|
-
'than recovered, and the regions would differ with nothing to say so.');
|
|
79
|
+
throw new Error(`Component '${this.locator.id}' converges, and its storage offers no outbox.`);
|
|
72
80
|
const inbound = await this.subscribe(this);
|
|
73
81
|
await inbound.connect();
|
|
74
82
|
this.depends(inbound);
|
|
@@ -83,8 +91,8 @@ export class Converging extends Connector {
|
|
|
83
91
|
get migrates() {
|
|
84
92
|
return this.storage.migrates;
|
|
85
93
|
}
|
|
86
|
-
get
|
|
87
|
-
return this.storage.
|
|
94
|
+
get converges() {
|
|
95
|
+
return this.storage.converges;
|
|
88
96
|
}
|
|
89
97
|
async get(query) {
|
|
90
98
|
return this.storage.get(query);
|
|
@@ -108,8 +116,10 @@ export class Converging extends Connector {
|
|
|
108
116
|
async ensure(query, properties, record, row) {
|
|
109
117
|
return this.storage.ensure(query, properties, record, row);
|
|
110
118
|
}
|
|
111
|
-
async
|
|
112
|
-
return this.storage.
|
|
119
|
+
async converge(record) {
|
|
120
|
+
return this.storage.converge(record);
|
|
113
121
|
}
|
|
114
122
|
}
|
|
123
|
+
/** The rank of this region, which nothing that arrives should be carrying. */
|
|
124
|
+
const REGION = Number(environment.get('TOA_REGION') ?? 0);
|
|
115
125
|
//# sourceMappingURL=Storage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Storage.js","sourceRoot":"","sources":["../source/Storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAoB,MAAM,UAAU,CAAA;AACjE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,4CAA4C,CAAA;AAMpE;;;;;;;;GAQG;AACH,MAAM,OAAO,UAAW,SAAQ,SAAS;IACtB,OAAO,CAAkB;IACzB,OAAO,CAAS;IAChB,SAAS,CAA2C;IACpD,QAAQ,CAAa;IACrB,UAAU,CAAa;IAExC,YACE,OAAyB,EACzB,OAAgB,EAChB,OAAkD;QAElD,KAAK,EAAE,CAAA;QAEP,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,SAAS,GAAG,OAAO,CAAA;QAExB,yFAAyF;QACzF,6BAA6B;QAC7B,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;QAErB,IAAI,CAAC,QAAQ,GAAG;YACd,IAAI,EAAE,GAAG,OAAO,UAAU;YAC1B,IAAI,EAAE,UAAU;YAChB,OAAO,EAAE,OAAO;YAChB,UAAU,EAAE,EAAE,4BAA4B,EAAE,OAAO,EAAE;SACtD,CAAA;QAED,IAAI,CAAC,UAAU,GAAG;YAChB,IAAI,EAAE,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"Storage.js","sourceRoot":"","sources":["../source/Storage.ts"],"names":[],"mappings":"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"}
|