comq 0.18.0 → 0.20.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/discussions/poison-message-handling.md +14 -10
- package/docs/headers.md +1 -0
- package/features/events.poison.feature +11 -0
- package/features/rpc.call.feature +71 -0
- package/features/rpc.call.recovery.feature +75 -0
- package/features/rpc.call.shards.feature +24 -0
- package/features/rpc.parked.feature +20 -0
- package/features/rpc.timeout.feature +19 -0
- package/features/steps/call.js +462 -0
- package/features/steps/context.js +4 -0
- package/features/steps/events.js +15 -0
- package/features/steps/network.js +8 -0
- package/features/steps/networks.js +24 -4
- package/features/steps/parked.js +102 -0
- package/features/steps/poison.js +3 -2
- package/package.json +1 -1
- package/readme.md +176 -31
- package/source/attributes/recall.js +2 -1
- package/source/channel.js +138 -3
- package/source/events.js +1 -1
- package/source/index.js +6 -0
- package/source/io.js +239 -14
- package/source/shards/channel.js +20 -3
- package/source/unroutable.js +27 -0
- package/source/verdicts.js +54 -0
- package/test/amqplib.mock.js +1 -0
- package/test/channel.held.test.js +237 -0
- package/test/channel.test.js +104 -0
- package/test/connection.mock.js +1 -0
- package/test/io.call.test.js +161 -0
- package/test/io.reply.test.js +66 -0
- package/test/io.request.terms.test.js +247 -0
- package/test/shards/channel.test.js +68 -0
- package/test/verdicts.test.js +57 -0
- package/types/channel.d.ts +3 -0
- package/types/diagnostic.d.ts +1 -1
- package/types/index.d.ts +26 -0
- package/types/io.d.ts +42 -2
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { generate } = require('randomstring')
|
|
4
|
+
const { Retry, Park, verdictOf, RETRY, PARK, VERDICT } = require('../source/verdicts')
|
|
5
|
+
|
|
6
|
+
it('should be', async () => {
|
|
7
|
+
expect(Retry).toBeDefined()
|
|
8
|
+
expect(Park).toBeDefined()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
describe('verdictOf', () => {
|
|
12
|
+
it.each([
|
|
13
|
+
['undefined', undefined],
|
|
14
|
+
['null', null],
|
|
15
|
+
['a string', 'nope'],
|
|
16
|
+
['a plain Error', new Error(generate())],
|
|
17
|
+
['an object branded with something else', { [Symbol.for('other.verdict')]: 'park' }],
|
|
18
|
+
['an object whose brand is not a verdict', { [VERDICT]: generate() }]
|
|
19
|
+
])('should read %s as a retry', async (_, exception) => {
|
|
20
|
+
expect(verdictOf(exception)).toStrictEqual(RETRY)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('should read Retry as a retry', async () => {
|
|
24
|
+
expect(verdictOf(new Retry(generate()))).toStrictEqual(RETRY)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('should read Park as a park', async () => {
|
|
28
|
+
expect(verdictOf(new Park(generate()))).toStrictEqual(PARK)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('should read a Park thrown by another copy of comq', async () => {
|
|
32
|
+
// a dependency pinning comq while the application resolves its own tree makes two
|
|
33
|
+
// copies the ordinary outcome, and `instanceof` is silently false across them
|
|
34
|
+
const foreign = { [Symbol.for('comq.verdict')]: 'park' }
|
|
35
|
+
|
|
36
|
+
expect(verdictOf(foreign)).toStrictEqual(PARK)
|
|
37
|
+
expect(foreign instanceof Park).toStrictEqual(false)
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
describe('the classes', () => {
|
|
42
|
+
it.each([['Retry', Retry], ['Park', Park]])('should make %s an Error', async (name, Verdict) => {
|
|
43
|
+
const message = generate()
|
|
44
|
+
const verdict = new Verdict(message)
|
|
45
|
+
|
|
46
|
+
expect(verdict).toBeInstanceOf(Error)
|
|
47
|
+
expect(verdict.message).toStrictEqual(message)
|
|
48
|
+
expect(verdict.name).toStrictEqual(name)
|
|
49
|
+
expect(typeof verdict.stack).toStrictEqual('string')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it.each([['Retry', Retry], ['Park', Park]])('should let %s carry a cause', async (_, Verdict) => {
|
|
53
|
+
const cause = new Error(generate())
|
|
54
|
+
|
|
55
|
+
expect(new Verdict(generate(), { cause }).cause).toStrictEqual(cause)
|
|
56
|
+
})
|
|
57
|
+
})
|
package/types/channel.d.ts
CHANGED
|
@@ -22,6 +22,9 @@ declare namespace comq {
|
|
|
22
22
|
/** consumes `queue`, bound to a routed `exchange` under `key` */
|
|
23
23
|
bound (exchange: string, queue: string, key: string, consumer: channels.Consumer): Promise<void>
|
|
24
24
|
|
|
25
|
+
/** consumes `queue`, exclusive to this connection and bound to a routed `exchange` under `key` */
|
|
26
|
+
held (exchange: string, queue: string, key: string, consumer: channels.Consumer): Promise<void>
|
|
27
|
+
|
|
25
28
|
send (queue: string, buffer: Buffer, options?: Options.Publish): Promise<void>
|
|
26
29
|
|
|
27
30
|
publish (exchange: string, buffer: Buffer, options?: Options.Publish): Promise<void>
|
package/types/diagnostic.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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'
|
|
4
|
+
'remove' | 'lost' | 'recover' | 'discard' | 'retry' | 'pause' | 'resume' | 'return' | 'taken'
|
|
5
5
|
|
|
6
6
|
interface Diagnosable {
|
|
7
7
|
diagnose(event: Event, listener: Function): void
|
package/types/index.d.ts
CHANGED
|
@@ -4,3 +4,29 @@ export type { IO, Consumer, Producer } from './io'
|
|
|
4
4
|
|
|
5
5
|
export const connect: Connect
|
|
6
6
|
export const assert: Connect
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Thrown by a consumer to say that whatever it needed was briefly not there. This is
|
|
10
|
+
* what an unclassified rejection already means, so throwing it changes nothing but the
|
|
11
|
+
* reader's certainty.
|
|
12
|
+
*/
|
|
13
|
+
export class Retry extends Error {}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Thrown by a consumer to say that it will never process this message, however many
|
|
17
|
+
* times it is handed over. The message is kept rather than retried.
|
|
18
|
+
*
|
|
19
|
+
* Not applicable to a `Producer` given to `IO.reply`: a Request has a caller awaiting a
|
|
20
|
+
* reply, so a verdict is a category error there and the message is retried and parked on
|
|
21
|
+
* the count like any other failure.
|
|
22
|
+
*/
|
|
23
|
+
export class Park extends Error {}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A Request `IO.call` sent under a key nobody holds. The broker returned it, so it reached no
|
|
27
|
+
* one and was not processed.
|
|
28
|
+
*/
|
|
29
|
+
export class Unroutable extends Error {
|
|
30
|
+
readonly exchange: string
|
|
31
|
+
readonly key: string
|
|
32
|
+
}
|
package/types/io.d.ts
CHANGED
|
@@ -34,14 +34,52 @@ declare namespace comq {
|
|
|
34
34
|
interface Request {
|
|
35
35
|
emitter: ReplyEmitter
|
|
36
36
|
properties: _amqp.Properties
|
|
37
|
+
signal?: AbortSignal
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** How long a caller waits for a Reply. */
|
|
41
|
+
interface RequestOptions {
|
|
42
|
+
encoding?: _encoding.Encoding
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Milliseconds to wait for the Reply. A Request nobody has taken by then is dropped by the
|
|
46
|
+
* broker; one already taken may still be processed, and its Reply is discarded.
|
|
47
|
+
*/
|
|
48
|
+
timeout?: number
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Ends the wait when aborted, within the timeout. The Request stays in its queue until the
|
|
52
|
+
* timeout passes, or, without one, until a Producer takes it.
|
|
53
|
+
*/
|
|
54
|
+
signal?: AbortSignal
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The options of a Request, settled once for all its attempts. */
|
|
58
|
+
interface Terms {
|
|
59
|
+
encoding?: _encoding.Encoding
|
|
60
|
+
expires?: number
|
|
61
|
+
signal?: AbortSignal
|
|
37
62
|
}
|
|
38
63
|
|
|
39
64
|
interface IO extends _diagnostics.Diagnosable {
|
|
40
65
|
reply(queue: string, produce: Producer): Promise<void>
|
|
41
66
|
|
|
42
|
-
request<Reply = any, Request = any>(queue: string, payload: Request,
|
|
67
|
+
request<Reply = any, Request = any>(queue: string, payload: Request, options?: _encoding.Encoding | RequestOptions): Promise<Reply> | Promise<Readable>
|
|
68
|
+
|
|
69
|
+
request(queue: string, stream: Readable, options?: _encoding.Encoding | RequestOptions): Promise<Readable>
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Sends a Request to whoever holds `key` on the routed `exchange`, through `back`. One that
|
|
73
|
+
* nobody holds is refused with `Unroutable`.
|
|
74
|
+
*/
|
|
75
|
+
call<Reply = any, Request = any>(exchange: string, key: string, payload: Request, options?: _encoding.Encoding | RequestOptions): Promise<Reply> | Promise<Readable>
|
|
43
76
|
|
|
44
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Answers the Requests `call` sends under `key`, from a queue this connection alone holds.
|
|
79
|
+
* While another connection holds it on a broker, claims it there again and again, emitting
|
|
80
|
+
* `taken`. Resolves once a broker holds it.
|
|
81
|
+
*/
|
|
82
|
+
back(exchange: string, key: string, produce: Producer): Promise<void>
|
|
45
83
|
|
|
46
84
|
consume<T = any>(exchange: string, group: string, consumer: Consumer<T>): Promise<void>
|
|
47
85
|
|
|
@@ -107,6 +145,8 @@ declare namespace comq {
|
|
|
107
145
|
|
|
108
146
|
diagnose(event: 'retry', listener: (channel: _topology.type, message: any, error: Error, attempt: number, index?: number) => void): void
|
|
109
147
|
|
|
148
|
+
diagnose(event: 'taken', listener: (channel: _topology.type, queue: string, index?: number) => void): void
|
|
149
|
+
|
|
110
150
|
diagnose(event: 'pause', listener: (channel: _topology.type) => void): void
|
|
111
151
|
|
|
112
152
|
diagnose(event: 'resume', listener: (channel: _topology.type) => void): void
|