comq 0.25.1 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/headers.md +16 -1
- package/features/properties.feature +39 -0
- package/features/shards.virtual.feature +89 -0
- package/features/steps/context.js +29 -2
- package/features/steps/hooks.js +1 -0
- package/features/steps/names.js +245 -0
- package/features/steps/networks.js +23 -5
- package/features/steps/rpc.js +14 -2
- package/package.json +1 -1
- package/readme.md +102 -4
- package/source/channel.js +147 -2
- package/source/connect.js +18 -15
- package/source/connection.js +8 -5
- package/source/events.js +7 -0
- package/source/io.js +31 -9
- package/source/shards/channel.js +289 -19
- package/source/shards/connection.js +404 -18
- package/source/shards/expand.js +57 -0
- package/source/shards/index.js +2 -0
- package/source/shards/resolve.js +134 -0
- package/source/shards/watch.js +257 -0
- package/source/singleton.js +3 -2
- package/test/amqplib.mock.js +1 -0
- package/test/assert.test.js +7 -2
- package/test/channel.quiet.test.js +301 -0
- package/test/connect.test.js +49 -8
- package/test/connection.mock.js +3 -0
- package/test/connection.test.js +15 -0
- package/test/io.call.test.js +1 -1
- package/test/io.diagnostics.test.js +1 -1
- package/test/io.reply.test.js +12 -1
- package/test/io.request.test.js +18 -0
- package/test/io.shards.test.js +85 -0
- package/test/shards/channel.test.js +411 -0
- package/test/shards/connection.test.js +372 -17
- package/test/shards/expand.test.js +58 -0
- package/test/shards/resolve.test.js +73 -0
- package/test/shards/watch.test.js +264 -0
- package/test/singleton.test.js +1 -1
- package/types/channel.d.ts +23 -0
- package/types/connection.d.ts +13 -2
- package/types/diagnostic.d.ts +2 -1
- package/types/io.d.ts +17 -1
- package/types/shards.d.ts +58 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { Watch } = require('../../source/shards/watch')
|
|
4
|
+
|
|
5
|
+
/** @type {Record<string, comq.shards.Resolution | Error>} */
|
|
6
|
+
let records
|
|
7
|
+
|
|
8
|
+
/** @type {Watch} */
|
|
9
|
+
let watch
|
|
10
|
+
|
|
11
|
+
/** @type {jest.MockedFunction<Function>} */
|
|
12
|
+
let change
|
|
13
|
+
|
|
14
|
+
/** @type {jest.MockedFunction<Function>} */
|
|
15
|
+
let error
|
|
16
|
+
|
|
17
|
+
const INTERVAL = 1000
|
|
18
|
+
const SETTLE = 5000
|
|
19
|
+
const hosts = ['one', 'two']
|
|
20
|
+
|
|
21
|
+
const tick = (ms = INTERVAL) => jest.advanceTimersByTimeAsync(ms)
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {comq.shards.Timing} [timing]
|
|
25
|
+
* @param {(host: string) => boolean} [urgent]
|
|
26
|
+
*/
|
|
27
|
+
async function start (timing = { interval: INTERVAL, settle: SETTLE }, urgent) {
|
|
28
|
+
watch = new Watch(hosts, timing, urgent)
|
|
29
|
+
change = jest.fn()
|
|
30
|
+
error = jest.fn()
|
|
31
|
+
|
|
32
|
+
watch.on('change', change)
|
|
33
|
+
watch.on('error', error)
|
|
34
|
+
|
|
35
|
+
return await watch.start()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
jest.useFakeTimers()
|
|
40
|
+
|
|
41
|
+
records = { one: { addresses: ['10.0.0.1'], ttl: 5 }, two: { addresses: ['10.0.0.2'], ttl: 5 } }
|
|
42
|
+
|
|
43
|
+
global.COMQ_TESTING_LOOKUP = async (host) => {
|
|
44
|
+
if (records[host] instanceof Error) throw records[host]
|
|
45
|
+
|
|
46
|
+
return records[host]
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
afterEach(() => {
|
|
51
|
+
watch?.stop()
|
|
52
|
+
jest.useRealTimers()
|
|
53
|
+
|
|
54
|
+
delete global.COMQ_TESTING_LOOKUP
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('should tell the address of each name', async () => {
|
|
58
|
+
const addresses = await start()
|
|
59
|
+
|
|
60
|
+
expect(Object.fromEntries(addresses)).toStrictEqual({ one: '10.0.0.1', two: '10.0.0.2' })
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('should not tell of what has not changed', async () => {
|
|
64
|
+
await start()
|
|
65
|
+
await tick(SETTLE * 2)
|
|
66
|
+
|
|
67
|
+
expect(change).not.toHaveBeenCalled()
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('should tell of a change once it has held for the settle time', async () => {
|
|
71
|
+
await start()
|
|
72
|
+
|
|
73
|
+
records.two = { addresses: ['10.0.0.3'], ttl: 5 }
|
|
74
|
+
|
|
75
|
+
await tick(SETTLE)
|
|
76
|
+
|
|
77
|
+
expect(change).not.toHaveBeenCalled()
|
|
78
|
+
|
|
79
|
+
await tick()
|
|
80
|
+
|
|
81
|
+
expect(change).toHaveBeenCalledTimes(1)
|
|
82
|
+
expect(Object.fromEntries(change.mock.calls[0][0])).toStrictEqual({ one: '10.0.0.1', two: '10.0.0.3' })
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('should start the settle time anew whenever the answer changes', async () => {
|
|
86
|
+
await start()
|
|
87
|
+
|
|
88
|
+
records.two = { addresses: ['10.0.0.3'], ttl: 5 }
|
|
89
|
+
|
|
90
|
+
await tick(SETTLE - INTERVAL)
|
|
91
|
+
|
|
92
|
+
records.one = { addresses: ['10.0.0.3'], ttl: 5 }
|
|
93
|
+
|
|
94
|
+
await tick(SETTLE - INTERVAL)
|
|
95
|
+
|
|
96
|
+
expect(change).not.toHaveBeenCalled()
|
|
97
|
+
|
|
98
|
+
await tick(INTERVAL * 2)
|
|
99
|
+
|
|
100
|
+
expect(change).toHaveBeenCalledTimes(1)
|
|
101
|
+
expect(Object.fromEntries(change.mock.calls[0][0])).toStrictEqual({ one: '10.0.0.3', two: '10.0.0.3' })
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('should not act on an answer that goes back and forth', async () => {
|
|
105
|
+
await start()
|
|
106
|
+
|
|
107
|
+
for (let i = 0; i < 20; i++) {
|
|
108
|
+
records.two = { addresses: [i % 2 === 0 ? '10.0.0.3' : '10.0.0.2'], ttl: 5 }
|
|
109
|
+
|
|
110
|
+
await tick()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
expect(change).not.toHaveBeenCalled()
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('should forget a change that was taken back', async () => {
|
|
117
|
+
await start()
|
|
118
|
+
|
|
119
|
+
records.two = { addresses: ['10.0.0.3'], ttl: 5 }
|
|
120
|
+
|
|
121
|
+
await tick(INTERVAL * 2)
|
|
122
|
+
|
|
123
|
+
records.two = { addresses: ['10.0.0.2'], ttl: 5 }
|
|
124
|
+
|
|
125
|
+
await tick(SETTLE * 2)
|
|
126
|
+
|
|
127
|
+
expect(change).not.toHaveBeenCalled()
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('should keep the address of a name that fails to resolve', async () => {
|
|
131
|
+
await start()
|
|
132
|
+
|
|
133
|
+
const exception = new Error('ENOTFOUND')
|
|
134
|
+
|
|
135
|
+
records.two = exception
|
|
136
|
+
|
|
137
|
+
await tick(SETTLE * 2)
|
|
138
|
+
|
|
139
|
+
expect(change).not.toHaveBeenCalled()
|
|
140
|
+
expect(error).toHaveBeenCalledWith(exception)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('should keep the address of a name that resolves to nothing', async () => {
|
|
144
|
+
await start()
|
|
145
|
+
|
|
146
|
+
records.two = { addresses: [] }
|
|
147
|
+
|
|
148
|
+
await tick(SETTLE * 2)
|
|
149
|
+
|
|
150
|
+
expect(change).not.toHaveBeenCalled()
|
|
151
|
+
expect(error).toHaveBeenCalled()
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('should keep the address in use while it is listed', async () => {
|
|
155
|
+
await start()
|
|
156
|
+
|
|
157
|
+
records.two = { addresses: ['10.0.0.1', '10.0.0.2'], ttl: 5 }
|
|
158
|
+
|
|
159
|
+
await tick(SETTLE * 2)
|
|
160
|
+
|
|
161
|
+
expect(change).not.toHaveBeenCalled()
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('should take what an urgent name resolves to at once', async () => {
|
|
165
|
+
await start(undefined, (host) => host === 'two')
|
|
166
|
+
|
|
167
|
+
records.one = { addresses: ['10.0.0.4'], ttl: 5 }
|
|
168
|
+
records.two = { addresses: ['10.0.0.3'], ttl: 5 }
|
|
169
|
+
|
|
170
|
+
await tick()
|
|
171
|
+
|
|
172
|
+
expect(change).toHaveBeenCalledTimes(1)
|
|
173
|
+
expect(Object.fromEntries(change.mock.calls[0][0])).toStrictEqual({ one: '10.0.0.1', two: '10.0.0.3' })
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('should look at once when poked', async () => {
|
|
177
|
+
await start(undefined, () => true)
|
|
178
|
+
|
|
179
|
+
records.two = { addresses: ['10.0.0.3'], ttl: 5 }
|
|
180
|
+
|
|
181
|
+
watch.poke()
|
|
182
|
+
|
|
183
|
+
await tick(0)
|
|
184
|
+
|
|
185
|
+
expect(change).toHaveBeenCalledTimes(1)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it('should resolve until every name is known', async () => {
|
|
189
|
+
global.COMQ_TESTING_RESOLVE_BACKOFF = { base: 10, dispersion: 0 }
|
|
190
|
+
|
|
191
|
+
records.two = new Error('ENOTFOUND')
|
|
192
|
+
|
|
193
|
+
let addresses
|
|
194
|
+
|
|
195
|
+
const starting = start().then((result) => { addresses = result })
|
|
196
|
+
|
|
197
|
+
await tick(100)
|
|
198
|
+
|
|
199
|
+
expect(addresses).toBeUndefined()
|
|
200
|
+
|
|
201
|
+
records.two = { addresses: ['10.0.0.2'], ttl: 5 }
|
|
202
|
+
|
|
203
|
+
await tick(100)
|
|
204
|
+
await starting
|
|
205
|
+
|
|
206
|
+
expect(addresses.get('two')).toStrictEqual('10.0.0.2')
|
|
207
|
+
|
|
208
|
+
delete global.COMQ_TESTING_RESOLVE_BACKOFF
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('should stop', async () => {
|
|
212
|
+
await start()
|
|
213
|
+
|
|
214
|
+
records.two = { addresses: ['10.0.0.3'], ttl: 5 }
|
|
215
|
+
watch.stop()
|
|
216
|
+
|
|
217
|
+
await tick(SETTLE * 2)
|
|
218
|
+
|
|
219
|
+
expect(change).not.toHaveBeenCalled()
|
|
220
|
+
expect(jest.getTimerCount()).toStrictEqual(0)
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
describe('timing', () => {
|
|
224
|
+
it('should settle and linger for the longest TTL seen', async () => {
|
|
225
|
+
records.one = { addresses: ['10.0.0.1'], ttl: 300 }
|
|
226
|
+
records.two = { addresses: ['10.0.0.2'], ttl: 120 }
|
|
227
|
+
|
|
228
|
+
await start({})
|
|
229
|
+
|
|
230
|
+
records.one = { addresses: ['10.0.0.1'], ttl: 10 }
|
|
231
|
+
|
|
232
|
+
await tick(10_000)
|
|
233
|
+
|
|
234
|
+
expect(watch.settle).toStrictEqual(300_000)
|
|
235
|
+
expect(watch.linger).toStrictEqual(300_000)
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('should settle for no less than 30 seconds', async () => {
|
|
239
|
+
await start({})
|
|
240
|
+
|
|
241
|
+
expect(watch.settle).toStrictEqual(30_000)
|
|
242
|
+
expect(watch.linger).toStrictEqual(30_000)
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
it('should give a name answered without a TTL 60 seconds', async () => {
|
|
246
|
+
records.two = { addresses: ['10.0.0.2'] }
|
|
247
|
+
|
|
248
|
+
await start({})
|
|
249
|
+
|
|
250
|
+
expect(watch.settle).toStrictEqual(60_000)
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
it('should resolve every 10 seconds unless told otherwise', async () => {
|
|
254
|
+
await start({})
|
|
255
|
+
|
|
256
|
+
expect(watch.interval).toStrictEqual(10_000)
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
it('should take what it is told', async () => {
|
|
260
|
+
await start({ interval: 1, settle: 2, linger: 3 })
|
|
261
|
+
|
|
262
|
+
expect([watch.interval, watch.settle, watch.linger]).toStrictEqual([1, 2, 3])
|
|
263
|
+
})
|
|
264
|
+
})
|
package/test/singleton.test.js
CHANGED
|
@@ -27,7 +27,7 @@ beforeEach(() => {
|
|
|
27
27
|
|
|
28
28
|
it('should extend Connection', async () => {
|
|
29
29
|
expect(connection).toBeInstanceOf(mock.Connection)
|
|
30
|
-
expect(mock.Connection.ctor).toHaveBeenCalledWith(url, {})
|
|
30
|
+
expect(mock.Connection.ctor).toHaveBeenCalledWith(url, {}, undefined)
|
|
31
31
|
})
|
|
32
32
|
|
|
33
33
|
describe('connection', () => {
|
package/types/channel.d.ts
CHANGED
|
@@ -44,6 +44,29 @@ declare namespace comq {
|
|
|
44
44
|
|
|
45
45
|
seal (): Promise<void>
|
|
46
46
|
|
|
47
|
+
/** resolves once what was delivered and what was published is done with */
|
|
48
|
+
settled? (): Promise<void>
|
|
49
|
+
|
|
50
|
+
/** since when there has been nothing to do, which is now while there is */
|
|
51
|
+
quiet? (): Promise<number>
|
|
52
|
+
|
|
53
|
+
/** over a sharded connection: whether a shard is one of the channel's */
|
|
54
|
+
has? (index: number): boolean
|
|
55
|
+
|
|
56
|
+
/** takes a shard in, consuming from it what is consumed from the rest */
|
|
57
|
+
join? (connection: any, index: number): Promise<void>
|
|
58
|
+
|
|
59
|
+
/** stops publishing through a shard, which goes on being consumed from */
|
|
60
|
+
retire? (index: number): void
|
|
61
|
+
|
|
62
|
+
restore? (index: number): void
|
|
63
|
+
|
|
64
|
+
/** lets a shard go, once what it has delivered is done with */
|
|
65
|
+
leave? (index: number): Promise<void>
|
|
66
|
+
|
|
67
|
+
/** told by whoever publishes whether a shard owes it something */
|
|
68
|
+
occupy? (occupied: (index: number) => boolean): void
|
|
69
|
+
|
|
47
70
|
close (): Promise<void>
|
|
48
71
|
|
|
49
72
|
readonly closed: boolean
|
package/types/connection.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import * as _channel from './channel'
|
|
|
2
2
|
import * as _io from './io'
|
|
3
3
|
import * as _diagnostics from './diagnostic'
|
|
4
4
|
import * as _topology from './topology'
|
|
5
|
+
import * as _shards from './shards'
|
|
5
6
|
|
|
6
7
|
declare namespace comq {
|
|
7
8
|
|
|
@@ -22,13 +23,23 @@ declare namespace comq {
|
|
|
22
23
|
forget(event: _diagnostics.Event, listener: Function): void
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
/** The topology presets overridden per channel type, and how the names of the shards are followed. */
|
|
27
|
+
type Options = _topology.Overrides & {
|
|
28
|
+
resolution?: _shards.Timing
|
|
29
|
+
}
|
|
30
|
+
|
|
25
31
|
type Connect = {
|
|
32
|
+
/**
|
|
33
|
+
* A range in a host is as many URLs: `amqp://rmq[0..32].example.com` is `rmq0` to `rmq31`.
|
|
34
|
+
* Several URLs are the names of the shards, and a connection is made to each broker they
|
|
35
|
+
* stand for.
|
|
36
|
+
*/
|
|
26
37
|
(...urls: string[]): Promise<_io.IO>
|
|
27
38
|
|
|
28
|
-
|
|
29
|
-
(...args: [...urls: string[], overrides: _topology.Overrides]): Promise<_io.IO>
|
|
39
|
+
(...args: [...urls: string[], options: Options]): Promise<_io.IO>
|
|
30
40
|
}
|
|
31
41
|
|
|
32
42
|
}
|
|
33
43
|
|
|
34
44
|
export type Connect = comq.Connect
|
|
45
|
+
export type Options = comq.Options
|
package/types/diagnostic.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
declare namespace comq.diagnostics {
|
|
2
2
|
|
|
3
3
|
type Event = 'open' | 'close' | 'error' | 'reconnect' | 'exhausted' | 'flow' | 'drain' |
|
|
4
|
-
'remove' | 'lost' | 'recover' | 'discard' | 'retry' | 'pause' | 'resume' | 'return' | 'taken'
|
|
4
|
+
'remove' | 'lost' | 'recover' | 'discard' | 'retry' | 'pause' | 'resume' | 'return' | 'taken' |
|
|
5
|
+
'join' | 'retire' | 'leave'
|
|
5
6
|
|
|
6
7
|
interface Diagnosable {
|
|
7
8
|
diagnose(event: Event, listener: Function): void
|
package/types/io.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import * as _amqp from './amqp'
|
|
|
7
7
|
|
|
8
8
|
declare namespace comq {
|
|
9
9
|
|
|
10
|
-
type Producer<Input = any, Output = any> = (message: Input) => Output | Promise<Output>
|
|
10
|
+
type Producer<Input = any, Output = any> = (message: Input, properties?: _amqp.Properties) => Output | Promise<Output>
|
|
11
11
|
type Consumer<T = any> = (message: T, headers?: _amqp.Properties) => void | Promise<void>
|
|
12
12
|
|
|
13
13
|
type ReplyHandler = (payload: any, properties: _amqp.Properties, size?: number) => void
|
|
@@ -66,6 +66,9 @@ declare namespace comq {
|
|
|
66
66
|
/** The options of a Request, settled once for all its attempts. */
|
|
67
67
|
interface Terms {
|
|
68
68
|
encoding?: _encoding.Encoding
|
|
69
|
+
|
|
70
|
+
/** What a Request is sent with besides what makes it one. */
|
|
71
|
+
properties?: _amqp.Properties
|
|
69
72
|
expires?: number
|
|
70
73
|
signal?: AbortSignal
|
|
71
74
|
}
|
|
@@ -76,8 +79,12 @@ declare namespace comq {
|
|
|
76
79
|
/** Waits for the Reply for as long as it takes. */
|
|
77
80
|
request<Reply = any, Request = any>(queue: string, payload: Request, encoding?: _encoding.Encoding): Promise<Reply> | Promise<Readable>
|
|
78
81
|
|
|
82
|
+
request<Reply = any, Request = any>(queue: string, payload: Request, properties?: _amqp.Properties): Promise<Reply> | Promise<Readable>
|
|
83
|
+
|
|
79
84
|
request(queue: string, stream: Readable, encoding?: _encoding.Encoding): Promise<Readable>
|
|
80
85
|
|
|
86
|
+
request(queue: string, stream: Readable, properties?: _amqp.Properties): Promise<Readable>
|
|
87
|
+
|
|
81
88
|
/**
|
|
82
89
|
* Sends a Request to whoever holds `key` on the routed `exchange`, through `back`. One that
|
|
83
90
|
* nobody holds is refused with `Unroutable`.
|
|
@@ -157,6 +164,15 @@ declare namespace comq {
|
|
|
157
164
|
|
|
158
165
|
diagnose(event: 'taken', listener: (channel: _topology.type, queue: string, index?: number) => void): void
|
|
159
166
|
|
|
167
|
+
/** a broker the names of the shards stand for has joined the pool, or is back in it */
|
|
168
|
+
diagnose(event: 'join', listener: (index: number, address: string) => void): void
|
|
169
|
+
|
|
170
|
+
/** no name stands for the broker any longer: nothing new is published to it */
|
|
171
|
+
diagnose(event: 'retire', listener: (index: number, address: string) => void): void
|
|
172
|
+
|
|
173
|
+
/** a retired broker has had nothing more to give, and is disconnected */
|
|
174
|
+
diagnose(event: 'leave', listener: (index: number, address: string) => void): void
|
|
175
|
+
|
|
160
176
|
diagnose(event: 'pause', listener: (channel: _topology.type) => void): void
|
|
161
177
|
|
|
162
178
|
diagnose(event: 'resume', listener: (channel: _topology.type) => void): void
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as _connection from './connection'
|
|
2
|
+
|
|
3
|
+
declare namespace comq {
|
|
4
|
+
|
|
5
|
+
namespace shards {
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* How the names of the shards are followed, in milliseconds. `settle` and `linger` default
|
|
9
|
+
* to the longest TTL the names are answered with, and to no less than 30 seconds.
|
|
10
|
+
*/
|
|
11
|
+
type Timing = {
|
|
12
|
+
/** how often the names are resolved, 10 seconds by default */
|
|
13
|
+
interval?: number
|
|
14
|
+
|
|
15
|
+
/** how long an answer is to hold before a broker is joined or retired on it */
|
|
16
|
+
settle?: number
|
|
17
|
+
|
|
18
|
+
/** how long a retired broker is to stay idle before it is left */
|
|
19
|
+
linger?: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type Resolution = {
|
|
23
|
+
addresses: string[]
|
|
24
|
+
|
|
25
|
+
/** in seconds, when the answer tells */
|
|
26
|
+
ttl?: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type Location = {
|
|
30
|
+
/** the URL of the address, which is what tells one broker from another */
|
|
31
|
+
url: string
|
|
32
|
+
address: string
|
|
33
|
+
|
|
34
|
+
/** the name the broker is verified by */
|
|
35
|
+
servername?: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type Shard = Location & {
|
|
39
|
+
index: number
|
|
40
|
+
connection: _connection.Connection
|
|
41
|
+
state: 'joining' | 'active' | 'retiring' | 'leaving'
|
|
42
|
+
|
|
43
|
+
/** whether channels are made over it */
|
|
44
|
+
joined: boolean
|
|
45
|
+
|
|
46
|
+
/** when it was retired */
|
|
47
|
+
since: number
|
|
48
|
+
|
|
49
|
+
/** since when it has been out of reach, while retiring */
|
|
50
|
+
down?: number
|
|
51
|
+
listeners: [string, Function][]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type Timing = comq.shards.Timing
|