• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hicommonwealth / commonwealth / 13497108514

24 Feb 2025 11:36AM UTC coverage: 46.449% (+0.08%) from 46.365%
13497108514

Pull #11078

github

web-flow
Merge 56f84aad3 into beadf67b7
Pull Request #11078: Improvements to Community Homepages

1317 of 3113 branches covered (42.31%)

Branch coverage included in aggregate %.

2490 of 5083 relevant lines covered (48.99%)

38.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.66
/libs/adapters/src/rabbitmq/createRmqConfig.ts
1
import { config as EnvConfig } from '@hicommonwealth/adapters';
2
import {
3
  EventSchemas,
4
  EventsHandlerMetadata,
5
  logger,
6
  outboxEvents,
7
} from '@hicommonwealth/core';
8
import { Events } from '@hicommonwealth/schemas';
9
import {
10
  BindingConfig,
11
  BrokerConfig,
12
  ConnectionConfig,
13
  QueueConfig,
14
} from 'rascal';
15

16
const log = logger(import.meta);
4✔
17

18
export enum RascalExchanges {
4✔
19
  DeadLetter = 'DeadLetterExchange',
4✔
20
  MessageRelayer = 'MessageRelayerExchange',
4✔
21
}
22

23
type Consumers =
24
  | {
25
      consumer: () => EventsHandlerMetadata<EventSchemas>;
26
      overrides: Record<string, string | null | undefined>;
27
    }
28
  | (() => EventsHandlerMetadata<EventSchemas>);
29

30
/**
31
 * Generates the RabbitMQ configuration on the fly given a set of policies
32
 * and/or projections. Queues will be automatically generated for each
33
 * policy/projection with bindings for each event in the input. Overrides can
34
 * be used to implement custom routing keys. If a custom routing key is set to
35
 * NULL then a routing/binding key for the corresponding handler in the policy
36
 * will NOT be created.
37
 *
38
 * @param rabbitMqUri
39
 * @param map
40
 */
41
export function createRmqConfig({
42
  rabbitMqUri,
43
  map,
44
}: {
45
  rabbitMqUri: string;
46
  // TODO: @Roger - add types so that override keys are a partial record of consumer input type
47
  map: Array<Consumers>;
48
}) {
49
  let vhost: string;
50
  if (rabbitMqUri.includes('localhost') || rabbitMqUri.includes('127.0.0.1')) {
1!
51
    vhost = '/';
1✔
52
  } else {
53
    const count = (rabbitMqUri.match(/\//g) || []).length;
×
54
    if (count == 3) {
×
55
      // this matches for a production URL
56
      const res = rabbitMqUri.split('/');
×
57
      vhost = res[res.length - 1];
×
58
    } else {
59
      throw new Error(
×
60
        "Can't create Rascal RabbitMQ Config with an invalid URI!",
61
      );
62
    }
63
  }
64

65
  const queueConfig = {
1✔
66
    assert: true,
67
    purge:
68
      ['local', 'CI'].includes(EnvConfig.APP_ENV) &&
2✔
69
      !EnvConfig.BROKER.DISABLE_LOCAL_QUEUE_PURGE,
70
  };
71
  const deadLetterRoutingKey = 'DeadLetter';
1✔
72
  const exchangeConfig = {
1✔
73
    assert: true,
74
    options: {
75
      durable: true,
76
    },
77
  };
78

79
  const deadLetterQueue = 'DeadLetterQueue';
1✔
80
  const config: BrokerConfig = {
1✔
81
    vhosts: {
82
      [vhost]: {
83
        connection: <ConnectionConfig>rabbitMqUri,
84
        exchanges: {
85
          [RascalExchanges.DeadLetter]: {
86
            ...exchangeConfig,
87
          },
88
          [RascalExchanges.MessageRelayer]: {
89
            type: 'topic',
90
            ...exchangeConfig,
91
          },
92
        },
93
        queues: {
94
          [deadLetterQueue]: {
95
            ...queueConfig,
96
          },
97
        },
98
        bindings: {
99
          DeadLetterBinding: {
100
            source: RascalExchanges.DeadLetter,
101
            destination: deadLetterQueue,
102
            destinationType: 'queue',
103
            bindingKey: deadLetterRoutingKey,
104
          },
105
        },
106
        publications: {
107
          MessageRelayer: {
108
            exchange: RascalExchanges.MessageRelayer,
109
            confirm: true,
110
            options: {
111
              persistent: true,
112
            },
113
          },
114
        },
115
        subscriptions: {},
116
      },
117
    },
118
  };
119

120
  const ignoredEvents = new Set<string>();
1✔
121
  for (const item of map) {
1✔
122
    let consumer,
123
      overrides: Record<string, string | null | undefined> | undefined;
124
    if (typeof item === 'function') consumer = item;
2!
125
    else {
126
      consumer = item.consumer;
×
127
      overrides = item.overrides;
×
128
    }
129

130
    const consumerName = consumer.name;
2✔
131
    const queue = `${consumerName}Queue`;
2✔
132
    const queues = config.vhosts![vhost].queues as {
2✔
133
      [key: string]: QueueConfig;
134
    };
135
    queues[queue] = {
2✔
136
      ...queueConfig,
137
      options: {
138
        arguments: {
139
          'x-dead-letter-exchange': RascalExchanges.DeadLetter,
140
          'x-dead-letter-routing-key': deadLetterRoutingKey,
141
        },
142
      },
143
    };
144
    const bindings = config.vhosts![vhost].bindings as {
2✔
145
      [key: string]: BindingConfig;
146
    };
147
    bindings[`${consumerName}Binding`] = {
2✔
148
      source: RascalExchanges.MessageRelayer,
149
      destination: queue,
150
      destinationType: 'queue',
151
      bindingKeys: Object.keys(consumer().inputs).reduce(
152
        (acc: string[], val) => {
153
          // if consumer handler does not have an associated event
154
          // from the Outbox exclude it automatically
155
          if (!outboxEvents.includes(<Events>val)) {
2!
156
            ignoredEvents.add(val);
×
157
            return acc;
×
158
          }
159

160
          if (!overrides) acc.push(val);
2!
161
          else if (overrides[val] !== null) {
162
            acc.push(overrides[val] || val);
×
163
          }
164
          return acc;
2✔
165
        },
166
        [],
167
      ),
168
    };
169
    config.vhosts![vhost].subscriptions![consumerName] = {
2✔
170
      queue,
171
      contentType: 'application/json',
172
      retry: {
173
        delay: 1000,
174
      },
175
      prefetch: 10,
176
    };
177
  }
178

179
  if (ignoredEvents.size > 0)
1!
180
    log.warn(
×
181
      `The following events are ignored because they are not part of the Outbox: ${Array.from(
182
        ignoredEvents,
183
      ).join(', ')}`,
184
    );
185
  return config;
1✔
186
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc