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.
@@ -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
+ })
@@ -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>
@@ -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, encoding?: _encoding.Encoding): Promise<Reply> | Promise<Readable>
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
- request(queue: string, stream: Readable, encoding?: _encoding.Encoding): Promise<Readable>
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