@layered-loader/sqs 1.0.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/LICENSE +21 -0
- package/README.md +433 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/SqsGroupNotificationConsumer.d.ts +28 -0
- package/dist/lib/SqsGroupNotificationConsumer.js +107 -0
- package/dist/lib/SqsGroupNotificationConsumer.js.map +1 -0
- package/dist/lib/SqsGroupNotificationFactory.d.ts +22 -0
- package/dist/lib/SqsGroupNotificationFactory.js +40 -0
- package/dist/lib/SqsGroupNotificationFactory.js.map +1 -0
- package/dist/lib/SqsGroupNotificationPublisher.d.ts +38 -0
- package/dist/lib/SqsGroupNotificationPublisher.js +102 -0
- package/dist/lib/SqsGroupNotificationPublisher.js.map +1 -0
- package/dist/lib/SqsNotificationConsumer.d.ts +44 -0
- package/dist/lib/SqsNotificationConsumer.js +123 -0
- package/dist/lib/SqsNotificationConsumer.js.map +1 -0
- package/dist/lib/SqsNotificationFactory.d.ts +29 -0
- package/dist/lib/SqsNotificationFactory.js +40 -0
- package/dist/lib/SqsNotificationFactory.js.map +1 -0
- package/dist/lib/SqsNotificationPublisher.d.ts +39 -0
- package/dist/lib/SqsNotificationPublisher.js +109 -0
- package/dist/lib/SqsNotificationPublisher.js.map +1 -0
- package/dist/lib/channelNameResolver.d.ts +12 -0
- package/dist/lib/channelNameResolver.js +18 -0
- package/dist/lib/channelNameResolver.js.map +1 -0
- package/dist/lib/groupNotificationSchemas.d.ts +49 -0
- package/dist/lib/groupNotificationSchemas.js +31 -0
- package/dist/lib/groupNotificationSchemas.js.map +1 -0
- package/dist/lib/notificationSchemas.d.ts +66 -0
- package/dist/lib/notificationSchemas.js +40 -0
- package/dist/lib/notificationSchemas.js.map +1 -0
- package/dist/lib/triggers/AbstractSqsTrigger.d.ts +62 -0
- package/dist/lib/triggers/AbstractSqsTrigger.js +104 -0
- package/dist/lib/triggers/AbstractSqsTrigger.js.map +1 -0
- package/dist/lib/triggers/SqsGroupInvalidationTrigger.d.ts +39 -0
- package/dist/lib/triggers/SqsGroupInvalidationTrigger.js +46 -0
- package/dist/lib/triggers/SqsGroupInvalidationTrigger.js.map +1 -0
- package/dist/lib/triggers/SqsInvalidationTrigger.d.ts +56 -0
- package/dist/lib/triggers/SqsInvalidationTrigger.js +47 -0
- package/dist/lib/triggers/SqsInvalidationTrigger.js.map +1 -0
- package/dist/lib/triggers/dispatch.d.ts +19 -0
- package/dist/lib/triggers/dispatch.js +69 -0
- package/dist/lib/triggers/dispatch.js.map +1 -0
- package/dist/lib/triggers/types.d.ts +54 -0
- package/dist/lib/triggers/types.js +12 -0
- package/dist/lib/triggers/types.js.map +1 -0
- package/package.json +75 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021-2024 Igor Savin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
# @layered-loader/sqs
|
|
2
|
+
|
|
3
|
+
SNS/SQS remote-invalidation adapter for [`layered-loader`](https://github.com/kibertoad/layered-loader).
|
|
4
|
+
|
|
5
|
+
This package provides:
|
|
6
|
+
|
|
7
|
+
- **Notification publishers and consumers** that fan cache invalidations out across a cluster via an SNS topic and per-instance SQS queues — a drop-in alternative to the built-in Redis adapter for AWS-native deployments.
|
|
8
|
+
- **Flexible invalidation triggers** that subscribe to an *existing* upstream SNS topic or SQS queue (one that knows nothing about the caching layer) and translate domain events such as `user.updated` into cache invalidations propagated through your notification pair.
|
|
9
|
+
|
|
10
|
+
The implementation is built on top of [`@message-queue-toolkit/sns`](https://github.com/kibertoad/message-queue-toolkit) and is tested against [fauxqs](https://github.com/kibertoad/fauxqs), an in-process SNS/SQS emulator.
|
|
11
|
+
|
|
12
|
+
## Contents
|
|
13
|
+
|
|
14
|
+
- [Installation](#installation)
|
|
15
|
+
- [Quick start: notification pair](#quick-start-notification-pair)
|
|
16
|
+
- [Group notification pair](#group-notification-pair)
|
|
17
|
+
- [Locator vs creation config](#locator-vs-creation-config)
|
|
18
|
+
- [How invalidation flows through SNS/SQS](#how-invalidation-flows-through-snssqs)
|
|
19
|
+
- [Self-message filtering and `serverUuid`](#self-message-filtering-and-serveruuid)
|
|
20
|
+
- [Flexible invalidation triggers](#flexible-invalidation-triggers)
|
|
21
|
+
- [Triggering from an existing SNS topic](#triggering-from-an-existing-sns-topic)
|
|
22
|
+
- [Triggering from an existing SQS queue](#triggering-from-an-existing-sqs-queue)
|
|
23
|
+
- [Group triggers](#group-triggers)
|
|
24
|
+
- [Resolver semantics](#resolver-semantics)
|
|
25
|
+
- [The trigger-publisher rule](#the-trigger-publisher-rule)
|
|
26
|
+
- [Error handling and retries](#error-handling-and-retries)
|
|
27
|
+
- [Testing with fauxqs](#testing-with-fauxqs)
|
|
28
|
+
- [API reference](#api-reference)
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install @layered-loader/sqs layered-loader
|
|
34
|
+
# Plus the AWS SDK clients and message-queue-toolkit (peer deps):
|
|
35
|
+
npm install \
|
|
36
|
+
@aws-sdk/client-sns @aws-sdk/client-sqs @aws-sdk/client-sts \
|
|
37
|
+
@lokalise/node-core \
|
|
38
|
+
@message-queue-toolkit/core @message-queue-toolkit/sns @message-queue-toolkit/sqs \
|
|
39
|
+
zod
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Node 20+ is required.
|
|
43
|
+
|
|
44
|
+
## Quick start: notification pair
|
|
45
|
+
|
|
46
|
+
The simplest setup mirrors the built-in Redis pair: each application instance gets a `publisher` (sends invalidations to a shared SNS topic) and a `consumer` (reads its own SQS queue subscribed to that topic and applies invalidations to its in-memory cache).
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { SNSClient } from '@aws-sdk/client-sns'
|
|
50
|
+
import { SQSClient } from '@aws-sdk/client-sqs'
|
|
51
|
+
import { STSClient } from '@aws-sdk/client-sts'
|
|
52
|
+
import { globalLogger, NoopObservabilityManager } from '@lokalise/node-core'
|
|
53
|
+
import { SnsConsumerErrorResolver } from '@message-queue-toolkit/sns'
|
|
54
|
+
import { Loader } from 'layered-loader'
|
|
55
|
+
import { createNotificationPair } from '@layered-loader/sqs'
|
|
56
|
+
import type { User } from './types'
|
|
57
|
+
|
|
58
|
+
const region = 'us-east-1'
|
|
59
|
+
const snsClient = new SNSClient({ region })
|
|
60
|
+
const sqsClient = new SQSClient({ region })
|
|
61
|
+
const stsClient = new STSClient({ region })
|
|
62
|
+
|
|
63
|
+
const errorReporter = { report: () => {} }
|
|
64
|
+
|
|
65
|
+
const { publisher: notificationPublisher, consumer: notificationConsumer } =
|
|
66
|
+
createNotificationPair<User>({
|
|
67
|
+
publisher: {
|
|
68
|
+
dependencies: { snsClient, stsClient, logger: globalLogger, errorReporter },
|
|
69
|
+
creationConfig: { topic: { Name: 'user-cache-invalidations' } },
|
|
70
|
+
},
|
|
71
|
+
consumer: {
|
|
72
|
+
dependencies: {
|
|
73
|
+
snsClient,
|
|
74
|
+
sqsClient,
|
|
75
|
+
stsClient,
|
|
76
|
+
logger: globalLogger,
|
|
77
|
+
errorReporter,
|
|
78
|
+
consumerErrorResolver: new SnsConsumerErrorResolver(),
|
|
79
|
+
transactionObservabilityManager: new NoopObservabilityManager(),
|
|
80
|
+
},
|
|
81
|
+
creationConfig: {
|
|
82
|
+
topic: { Name: 'user-cache-invalidations' },
|
|
83
|
+
// Each instance MUST use a unique queue name (e.g. include the host id):
|
|
84
|
+
queue: { QueueName: `user-cache-invalidations-${process.env.HOSTNAME}` },
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
const userLoader = new Loader<User>({
|
|
90
|
+
inMemoryCache: { ttlInMsecs: 1000 * 60 * 5 },
|
|
91
|
+
asyncCache: yourAsyncCache,
|
|
92
|
+
notificationConsumer,
|
|
93
|
+
notificationPublisher,
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
await userLoader.init()
|
|
97
|
+
await userLoader.invalidateCacheFor('123') // fans out to every other instance
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Each consumer needs its **own** SQS queue subscribed to the shared topic; SNS handles the fan-out. If two instances share a queue, only one of them will receive each invalidation message.
|
|
101
|
+
|
|
102
|
+
## Group notification pair
|
|
103
|
+
|
|
104
|
+
For `GroupLoader`, use `createGroupNotificationPair` with the same shape:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { GroupLoader } from 'layered-loader'
|
|
108
|
+
import { createGroupNotificationPair } from '@layered-loader/sqs'
|
|
109
|
+
|
|
110
|
+
const { publisher: notificationPublisher, consumer: notificationConsumer } =
|
|
111
|
+
createGroupNotificationPair<User>({
|
|
112
|
+
publisher: { dependencies, creationConfig: { topic: { Name: 'tenant-cache-invalidations' } } },
|
|
113
|
+
consumer: {
|
|
114
|
+
dependencies: consumerDependencies,
|
|
115
|
+
creationConfig: {
|
|
116
|
+
topic: { Name: 'tenant-cache-invalidations' },
|
|
117
|
+
queue: { QueueName: `tenant-cache-invalidations-${process.env.HOSTNAME}` },
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
const userLoader = new GroupLoader<User>({
|
|
123
|
+
inMemoryCache: { ttlInMsecs: 1000 * 60 * 5 },
|
|
124
|
+
asyncCache: yourAsyncCache,
|
|
125
|
+
notificationConsumer,
|
|
126
|
+
notificationPublisher,
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
await userLoader.invalidateCacheFor('user-1', 'tenant-A')
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Locator vs creation config
|
|
133
|
+
|
|
134
|
+
Both publisher and consumer accept a discriminated config:
|
|
135
|
+
|
|
136
|
+
| Field | Behaviour |
|
|
137
|
+
| --- | --- |
|
|
138
|
+
| `creationConfig` | Auto-creates the resource (`topic`, `queue`, `subscription`) on `subscribe()` if it does not exist. |
|
|
139
|
+
| `locatorConfig` | Reuses pre-provisioned resources. Throws if they are missing. |
|
|
140
|
+
|
|
141
|
+
For a consumer in `locatorConfig` mode, you must supply enough information to resolve the topic, queue, and subscription:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
consumer: {
|
|
145
|
+
dependencies: consumerDependencies,
|
|
146
|
+
locatorConfig: {
|
|
147
|
+
topicArn: 'arn:aws:sns:us-east-1:000000000000:user-cache-invalidations',
|
|
148
|
+
queueUrl: 'https://sqs.us-east-1.amazonaws.com/000000000000/cache-q-host-1',
|
|
149
|
+
subscriptionArn: 'arn:aws:sns:...:subscription/...',
|
|
150
|
+
},
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
You can grab those identifiers off a previously-initialised consumer/publisher:
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
await pair.publisher.subscribe()
|
|
158
|
+
await pair.consumer.subscribe()
|
|
159
|
+
console.log(pair.publisher.topicArn)
|
|
160
|
+
console.log(pair.consumer.subscriptionArn, pair.consumer.queueUrl)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
If you need to override defaults of the SNS subscription (filter policy, raw delivery, etc.), pass `subscriptionConfig: SqsSubscriptionOptions` to the consumer config.
|
|
164
|
+
|
|
165
|
+
## How invalidation flows through SNS/SQS
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
┌──────────────────┐ SNS topic ┌──────────────────┐
|
|
169
|
+
│ Instance A │ publisher ────────────────────────────▶ ┌──── │ Instance B │
|
|
170
|
+
│ Loader │ │ ───┴── consumer.delete(key)
|
|
171
|
+
│ │ consumer ──── (own queue, self-skip) │
|
|
172
|
+
└──────────────────┘ │ ───┬── consumer.delete(key)
|
|
173
|
+
└──── │ Instance C │
|
|
174
|
+
└──────────────────┘
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Each `Loader.invalidateCacheFor(...)` call publishes a JSON command (`DELETE`, `DELETE_MANY`, `SET`, `CLEAR`) to the SNS topic. SNS fan-outs to every subscribed SQS queue, and each consumer applies the command to its local in-memory cache.
|
|
178
|
+
|
|
179
|
+
## Self-message filtering and `serverUuid`
|
|
180
|
+
|
|
181
|
+
Every published command carries an `originUuid`. A consumer skips a command whose `originUuid` matches its own `serverUuid`, preventing instance A from re-applying its own invalidations bouncing back through SNS.
|
|
182
|
+
|
|
183
|
+
`createNotificationPair` (and `createGroupNotificationPair`) generate one `serverUuid` shared by both the publisher and the consumer it returns. You can override it with the `serverUuid` field if you need stable identifiers across restarts (e.g. when locating an existing subscription).
|
|
184
|
+
|
|
185
|
+
## Flexible invalidation triggers
|
|
186
|
+
|
|
187
|
+
A *trigger* lets you treat any upstream messaging system as a source of cache-invalidation events without that system knowing the cache exists. The trigger:
|
|
188
|
+
|
|
189
|
+
1. Subscribes to a queue or topic you do not own.
|
|
190
|
+
2. Validates each message with a Zod schema.
|
|
191
|
+
3. Runs your **resolver** to extract entity ids (and optionally a group).
|
|
192
|
+
4. Forwards the resulting actions through a configured `NotificationPublisher`, fanning them out to every cache instance.
|
|
193
|
+
|
|
194
|
+
The actions and resolver shape are transport-agnostic — the same `InvalidationResolver`, `InvalidationAction`, dispatch helpers, and `InvalidationTrigger` interface can power future RabbitMQ / Kafka / Pub/Sub adapters. The SNS/SQS adapters live in this package.
|
|
195
|
+
|
|
196
|
+
### Triggering from an existing SNS topic
|
|
197
|
+
|
|
198
|
+
Use `sourceType: 'sns-topic'` with either creation or locator config. The trigger creates (or reuses) an SQS queue and subscribes it to the upstream topic.
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
import { randomUUID } from 'node:crypto'
|
|
202
|
+
import { z } from 'zod'
|
|
203
|
+
import {
|
|
204
|
+
createNotificationPair,
|
|
205
|
+
SqsInvalidationTrigger,
|
|
206
|
+
SqsNotificationPublisher,
|
|
207
|
+
} from '@layered-loader/sqs'
|
|
208
|
+
|
|
209
|
+
const USER_EVENT_SCHEMA = z.object({
|
|
210
|
+
type: z.enum(['user.updated', 'user.deleted', 'user.bulk-updated']),
|
|
211
|
+
userId: z.string().optional(),
|
|
212
|
+
userIds: z.array(z.string()).optional(),
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
// 1. The cache cluster's own invalidation pair (same as any deployment):
|
|
216
|
+
const cachePair = createNotificationPair<User>({
|
|
217
|
+
publisher: { dependencies: pubDeps, creationConfig: { topic: { Name: 'user-cache-invalidations' } } },
|
|
218
|
+
consumer: {
|
|
219
|
+
dependencies: consumerDeps,
|
|
220
|
+
creationConfig: {
|
|
221
|
+
topic: { Name: 'user-cache-invalidations' },
|
|
222
|
+
queue: { QueueName: `user-cache-invalidations-${process.env.HOSTNAME}` },
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
// 2. A separate publisher dedicated to trigger-emitted messages.
|
|
228
|
+
// Its serverUuid MUST be different from cachePair's so the local consumer
|
|
229
|
+
// treats trigger messages as foreign and applies them.
|
|
230
|
+
const triggerPublisher = new SqsNotificationPublisher<User>({
|
|
231
|
+
serverUuid: randomUUID(),
|
|
232
|
+
dependencies: pubDeps,
|
|
233
|
+
locatorConfig: { topicName: 'user-cache-invalidations' },
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
// 3. The trigger itself, subscribed to an upstream domain-event topic
|
|
237
|
+
// owned by another service:
|
|
238
|
+
const trigger = new SqsInvalidationTrigger<z.infer<typeof USER_EVENT_SCHEMA>>({
|
|
239
|
+
sourceType: 'sns-topic',
|
|
240
|
+
dependencies: consumerDeps,
|
|
241
|
+
creationConfig: {
|
|
242
|
+
topic: { Name: 'domain-events.users' }, // owned by an upstream service
|
|
243
|
+
queue: { QueueName: `cache-trigger-${process.env.HOSTNAME}` },
|
|
244
|
+
},
|
|
245
|
+
messageSchema: USER_EVENT_SCHEMA,
|
|
246
|
+
publisher: triggerPublisher,
|
|
247
|
+
resolver: (msg) => {
|
|
248
|
+
switch (msg.type) {
|
|
249
|
+
case 'user.updated':
|
|
250
|
+
case 'user.deleted':
|
|
251
|
+
return msg.userId ? { kind: 'delete', key: msg.userId } : null
|
|
252
|
+
case 'user.bulk-updated':
|
|
253
|
+
return msg.userIds?.length
|
|
254
|
+
? { kind: 'deleteMany', keys: msg.userIds }
|
|
255
|
+
: null
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
await trigger.start()
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Triggering from an existing SQS queue
|
|
264
|
+
|
|
265
|
+
If the upstream system writes directly to an SQS queue (no SNS topic in the middle), use `sourceType: 'sqs-queue'`:
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
import { SqsInvalidationTrigger } from '@layered-loader/sqs'
|
|
269
|
+
|
|
270
|
+
const trigger = new SqsInvalidationTrigger<DomainEvent>({
|
|
271
|
+
sourceType: 'sqs-queue',
|
|
272
|
+
dependencies: sqsConsumerDeps,
|
|
273
|
+
locatorConfig: {
|
|
274
|
+
queueUrl: 'https://sqs.us-east-1.amazonaws.com/000000000000/domain-events',
|
|
275
|
+
},
|
|
276
|
+
messageSchema: DOMAIN_EVENT_SCHEMA,
|
|
277
|
+
publisher: triggerPublisher,
|
|
278
|
+
resolver: (msg) => /* ... */,
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
await trigger.start()
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
The pure-SQS source uses `@message-queue-toolkit/sqs`'s consumer directly and does not require SNS / STS clients in its dependencies.
|
|
285
|
+
|
|
286
|
+
### Group triggers
|
|
287
|
+
|
|
288
|
+
`SqsGroupInvalidationTrigger` mirrors the flat trigger but emits `GroupInvalidationAction`s:
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
import { SqsGroupInvalidationTrigger, SqsGroupNotificationPublisher } from '@layered-loader/sqs'
|
|
292
|
+
|
|
293
|
+
const triggerPublisher = new SqsGroupNotificationPublisher<User>({
|
|
294
|
+
serverUuid: randomUUID(),
|
|
295
|
+
dependencies: pubDeps,
|
|
296
|
+
locatorConfig: { topicName: 'tenant-cache-invalidations' },
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
const trigger = new SqsGroupInvalidationTrigger<TenantEvent>({
|
|
300
|
+
sourceType: 'sns-topic',
|
|
301
|
+
dependencies: consumerDeps,
|
|
302
|
+
creationConfig: {
|
|
303
|
+
topic: { Name: 'tenant-events' },
|
|
304
|
+
queue: { QueueName: `tenant-trigger-${process.env.HOSTNAME}` },
|
|
305
|
+
},
|
|
306
|
+
messageSchema: TENANT_EVENT_SCHEMA,
|
|
307
|
+
publisher: triggerPublisher,
|
|
308
|
+
resolver: (msg) => {
|
|
309
|
+
if (msg.type === 'tenant.purged') return { kind: 'deleteGroup', group: msg.tenantId }
|
|
310
|
+
if (msg.type === 'tenant.user.updated' && msg.userId) {
|
|
311
|
+
return { kind: 'deleteFromGroup', key: msg.userId, group: msg.tenantId }
|
|
312
|
+
}
|
|
313
|
+
return null
|
|
314
|
+
},
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
await trigger.start()
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Resolver semantics
|
|
321
|
+
|
|
322
|
+
A resolver receives the validated `TMessage` and returns:
|
|
323
|
+
|
|
324
|
+
- A single `InvalidationAction` / `GroupInvalidationAction` — applied immediately.
|
|
325
|
+
- An array of actions — applied sequentially, preserving emission order.
|
|
326
|
+
- `null` or `undefined` — skip the message (the source treats it as successfully processed).
|
|
327
|
+
|
|
328
|
+
Flat actions:
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
type InvalidationAction =
|
|
332
|
+
| { kind: 'delete'; key: string }
|
|
333
|
+
| { kind: 'deleteMany'; keys: readonly string[] }
|
|
334
|
+
| { kind: 'set'; key: string; value: unknown }
|
|
335
|
+
| { kind: 'clear' }
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Group actions:
|
|
339
|
+
|
|
340
|
+
```ts
|
|
341
|
+
type GroupInvalidationAction =
|
|
342
|
+
| { kind: 'deleteFromGroup'; key: string; group: string }
|
|
343
|
+
| { kind: 'deleteGroup'; group: string }
|
|
344
|
+
| { kind: 'clear' }
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Resolvers may be `async`; the trigger awaits before publishing.
|
|
348
|
+
|
|
349
|
+
### The trigger-publisher rule
|
|
350
|
+
|
|
351
|
+
> **The trigger's publisher MUST have a `serverUuid` distinct from any local notification pair.**
|
|
352
|
+
|
|
353
|
+
Why: a `Loader` invalidates its own in-memory cache *before* publishing, so the pair's consumer is configured to skip messages with a matching `originUuid` (otherwise the pair would re-process its own invalidations). Trigger-emitted invalidations come from outside any Loader, so the pair's consumer must treat them as foreign and apply them. Sharing `serverUuid` means the local in-memory cache silently misses every trigger-driven invalidation.
|
|
354
|
+
|
|
355
|
+
In practice: build the trigger publisher with `randomUUID()` even when it points at the same SNS topic as your `createNotificationPair` publisher. The example snippets above all show this pattern.
|
|
356
|
+
|
|
357
|
+
### Error handling and retries
|
|
358
|
+
|
|
359
|
+
If the resolver or publish step throws, the trigger:
|
|
360
|
+
|
|
361
|
+
1. Invokes the optional `errorHandler(err, channel)` for observability.
|
|
362
|
+
2. Re-throws so `message-queue-toolkit` can apply its standard SQS retry / dead-letter behaviour.
|
|
363
|
+
|
|
364
|
+
For schema-violation errors, the message is failed by `message-queue-toolkit` before the resolver runs and goes back to the queue (and ultimately to a DLQ if you configured one). Configure DLQ on the trigger's queue creation config to bound retries.
|
|
365
|
+
|
|
366
|
+
### Lifecycle
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
const trigger = new SqsInvalidationTrigger({ ... })
|
|
370
|
+
|
|
371
|
+
await trigger.start() // idempotent; concurrent calls share one start
|
|
372
|
+
await trigger.stop() // idempotent; awaits any in-flight start
|
|
373
|
+
await trigger.start() // restart is supported
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
## Testing with fauxqs
|
|
377
|
+
|
|
378
|
+
For local development and tests, swap the AWS SDK endpoint for [fauxqs](https://github.com/kibertoad/fauxqs). Two modes are useful in practice:
|
|
379
|
+
|
|
380
|
+
### In-process (recommended for tests)
|
|
381
|
+
|
|
382
|
+
Sub-second startup, no daemon required, isolated per test process. This package's own integration tests run this way — see `packages/sqs/test/globalSetup.ts`.
|
|
383
|
+
|
|
384
|
+
```ts
|
|
385
|
+
import { startFauxqs } from 'fauxqs'
|
|
386
|
+
import { SQSClient } from '@aws-sdk/client-sqs'
|
|
387
|
+
|
|
388
|
+
const server = await startFauxqs({ port: 0, logger: false })
|
|
389
|
+
const credentials = { accessKeyId: 'test', secretAccessKey: 'test' }
|
|
390
|
+
const region = 'us-east-1'
|
|
391
|
+
|
|
392
|
+
const sqsClient = new SQSClient({ endpoint: server.address, region, credentials })
|
|
393
|
+
// ...
|
|
394
|
+
await server.stop()
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Docker-compose (for app smoke testing)
|
|
398
|
+
|
|
399
|
+
The repository root's `docker-compose.yml` includes a `fauxqs` service on port `4566` for local app runs against a stable endpoint:
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
docker compose up fauxqs
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Then point your AWS SDK clients at `http://localhost:4566`.
|
|
406
|
+
|
|
407
|
+
## API reference
|
|
408
|
+
|
|
409
|
+
### Notification pair
|
|
410
|
+
|
|
411
|
+
| Symbol | Purpose |
|
|
412
|
+
| --- | --- |
|
|
413
|
+
| `createNotificationPair<T>(config)` | Returns `{ publisher, consumer }` for flat-cache invalidation. |
|
|
414
|
+
| `createGroupNotificationPair<T>(config)` | Same, but for group caches. |
|
|
415
|
+
| `SqsNotificationPublisher<T>` | Lower-level constructor; useful for trigger publishers (independent UUID). |
|
|
416
|
+
| `SqsNotificationConsumer<T>` | Lower-level constructor; rarely used directly. |
|
|
417
|
+
| `SqsGroupNotificationPublisher<T>` / `SqsGroupNotificationConsumer<T>` | Group-cache equivalents. |
|
|
418
|
+
| `SqsSubscriptionOptions` | Type for `subscriptionConfig` overrides. |
|
|
419
|
+
|
|
420
|
+
### Triggers
|
|
421
|
+
|
|
422
|
+
| Symbol | Purpose |
|
|
423
|
+
| --- | --- |
|
|
424
|
+
| `SqsInvalidationTrigger<TMessage>` | Flat-cache trigger (SQS or SNS+SQS source). |
|
|
425
|
+
| `SqsGroupInvalidationTrigger<TMessage>` | Group-cache trigger. |
|
|
426
|
+
| `SqsTriggerSource` | Discriminated source config (`'sqs-queue'` or `'sns-topic'`, with `creationConfig` or `locatorConfig`). |
|
|
427
|
+
| `InvalidationAction` / `GroupInvalidationAction` | Action ADTs returned by resolvers. |
|
|
428
|
+
| `InvalidationResolver<TMessage, TAction>` | Resolver signature. |
|
|
429
|
+
| `InvalidationTrigger` | `start()` / `stop()` lifecycle interface. |
|
|
430
|
+
| `runFlatPipeline` / `runGroupPipeline` | Reusable resolver + dispatch helpers (transport-agnostic). |
|
|
431
|
+
| `applyFlatAction` / `applyGroupAction` | Apply a single resolved action to a publisher. |
|
|
432
|
+
| `AbstractSqsTrigger` | Base class for building custom SQS-based triggers. |
|
|
433
|
+
| `deriveTriggerChannelName` | Helper that derives a logical channel name from a source config. |
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { SqsNotificationPublisher, type SqsNotificationPublisherConfig, type SqsNotificationPublisherParams, } from './lib/SqsNotificationPublisher.js';
|
|
2
|
+
export { SqsNotificationConsumer, type SqsNotificationConsumerConfig, type SqsNotificationConsumerParams, type SqsSubscriptionOptions, } from './lib/SqsNotificationConsumer.js';
|
|
3
|
+
export { SqsGroupNotificationPublisher, type SqsGroupNotificationPublisherConfig, type SqsGroupNotificationPublisherParams, } from './lib/SqsGroupNotificationPublisher.js';
|
|
4
|
+
export { SqsGroupNotificationConsumer, type SqsGroupNotificationConsumerConfig, type SqsGroupNotificationConsumerParams, } from './lib/SqsGroupNotificationConsumer.js';
|
|
5
|
+
export { createNotificationPair, type SqsNotificationConfig, } from './lib/SqsNotificationFactory.js';
|
|
6
|
+
export { createGroupNotificationPair, type SqsGroupNotificationConfig, } from './lib/SqsGroupNotificationFactory.js';
|
|
7
|
+
export { CLEAR_COMMAND, DELETE_COMMAND, DELETE_MANY_COMMAND, SET_COMMAND, type ClearNotificationCommand, type DeleteManyNotificationCommand, type DeleteNotificationCommand, type NotificationCommand, type SetNotificationCommand, } from './lib/notificationSchemas.js';
|
|
8
|
+
export { DELETE_FROM_GROUP_COMMAND, DELETE_GROUP_COMMAND, type ClearGroupNotificationCommand, type DeleteFromGroupNotificationCommand, type DeleteGroupNotificationCommand, type GroupNotificationCommand, } from './lib/groupNotificationSchemas.js';
|
|
9
|
+
export type { GroupInvalidationAction, InvalidationAction, InvalidationResolver, InvalidationTrigger, ResolverOutput, TriggerErrorHandler, } from './lib/triggers/types.js';
|
|
10
|
+
export { applyFlatAction, applyGroupAction, runFlatPipeline, runGroupPipeline, } from './lib/triggers/dispatch.js';
|
|
11
|
+
export { AbstractSqsTrigger, deriveTriggerChannelName, type SqsTriggerSource, } from './lib/triggers/AbstractSqsTrigger.js';
|
|
12
|
+
export { SqsInvalidationTrigger, type SqsInvalidationTriggerParams, type SqsTriggerSourceConfig, } from './lib/triggers/SqsInvalidationTrigger.js';
|
|
13
|
+
export { SqsGroupInvalidationTrigger, type SqsGroupInvalidationTriggerParams, type SqsGroupTriggerSourceConfig, } from './lib/triggers/SqsGroupInvalidationTrigger.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { SqsNotificationPublisher, } from './lib/SqsNotificationPublisher.js';
|
|
2
|
+
export { SqsNotificationConsumer, } from './lib/SqsNotificationConsumer.js';
|
|
3
|
+
export { SqsGroupNotificationPublisher, } from './lib/SqsGroupNotificationPublisher.js';
|
|
4
|
+
export { SqsGroupNotificationConsumer, } from './lib/SqsGroupNotificationConsumer.js';
|
|
5
|
+
export { createNotificationPair, } from './lib/SqsNotificationFactory.js';
|
|
6
|
+
export { createGroupNotificationPair, } from './lib/SqsGroupNotificationFactory.js';
|
|
7
|
+
export { CLEAR_COMMAND, DELETE_COMMAND, DELETE_MANY_COMMAND, SET_COMMAND, } from './lib/notificationSchemas.js';
|
|
8
|
+
export { DELETE_FROM_GROUP_COMMAND, DELETE_GROUP_COMMAND, } from './lib/groupNotificationSchemas.js';
|
|
9
|
+
export { applyFlatAction, applyGroupAction, runFlatPipeline, runGroupPipeline, } from './lib/triggers/dispatch.js';
|
|
10
|
+
export { AbstractSqsTrigger, deriveTriggerChannelName, } from './lib/triggers/AbstractSqsTrigger.js';
|
|
11
|
+
export { SqsInvalidationTrigger, } from './lib/triggers/SqsInvalidationTrigger.js';
|
|
12
|
+
export { SqsGroupInvalidationTrigger, } from './lib/triggers/SqsGroupInvalidationTrigger.js';
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,GAGzB,MAAM,mCAAmC,CAAA;AAC1C,OAAO,EACL,uBAAuB,GAIxB,MAAM,kCAAkC,CAAA;AACzC,OAAO,EACL,6BAA6B,GAG9B,MAAM,wCAAwC,CAAA;AAC/C,OAAO,EACL,4BAA4B,GAG7B,MAAM,uCAAuC,CAAA;AAC9C,OAAO,EACL,sBAAsB,GAEvB,MAAM,iCAAiC,CAAA;AACxC,OAAO,EACL,2BAA2B,GAE5B,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EACL,aAAa,EACb,cAAc,EACd,mBAAmB,EACnB,WAAW,GAMZ,MAAM,8BAA8B,CAAA;AACrC,OAAO,EACL,yBAAyB,EACzB,oBAAoB,GAKrB,MAAM,mCAAmC,CAAA;AAW1C,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,gBAAgB,GACjB,MAAM,4BAA4B,CAAA;AACnC,OAAO,EACL,kBAAkB,EAClB,wBAAwB,GAEzB,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EACL,sBAAsB,GAGvB,MAAM,0CAA0C,CAAA;AACjD,OAAO,EACL,2BAA2B,GAG5B,MAAM,+CAA+C,CAAA"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type SNSSQSConsumerDependencies, type SNSSQSCreationConfig, type SNSSQSQueueLocatorType } from '@message-queue-toolkit/sns';
|
|
2
|
+
import type { ConsumerErrorHandler, SynchronousGroupCache } from 'layered-loader';
|
|
3
|
+
import { AbstractNotificationConsumer } from 'layered-loader';
|
|
4
|
+
import type { SqsSubscriptionOptions } from './SqsNotificationConsumer.js';
|
|
5
|
+
export type SqsGroupNotificationConsumerConfig = {
|
|
6
|
+
creationConfig: SNSSQSCreationConfig;
|
|
7
|
+
locatorConfig?: never;
|
|
8
|
+
} | {
|
|
9
|
+
creationConfig?: never;
|
|
10
|
+
locatorConfig: SNSSQSQueueLocatorType;
|
|
11
|
+
};
|
|
12
|
+
export type SqsGroupNotificationConsumerParams = {
|
|
13
|
+
serverUuid: string;
|
|
14
|
+
errorHandler?: ConsumerErrorHandler;
|
|
15
|
+
dependencies: SNSSQSConsumerDependencies;
|
|
16
|
+
subscriptionConfig?: SqsSubscriptionOptions;
|
|
17
|
+
} & SqsGroupNotificationConsumerConfig;
|
|
18
|
+
export declare class SqsGroupNotificationConsumer<LoadedValue> extends AbstractNotificationConsumer<LoadedValue, SynchronousGroupCache<LoadedValue>> {
|
|
19
|
+
private readonly params;
|
|
20
|
+
private internalConsumer?;
|
|
21
|
+
private subscribePromise?;
|
|
22
|
+
constructor(params: SqsGroupNotificationConsumerParams);
|
|
23
|
+
subscribe(): Promise<unknown>;
|
|
24
|
+
close(): Promise<void>;
|
|
25
|
+
get topicArn(): string | undefined;
|
|
26
|
+
get subscriptionArn(): string | undefined;
|
|
27
|
+
get queueUrl(): string | undefined;
|
|
28
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { MessageHandlerConfigBuilder } from '@message-queue-toolkit/core';
|
|
2
|
+
import { AbstractSnsSqsConsumer, } from '@message-queue-toolkit/sns';
|
|
3
|
+
import { AbstractNotificationConsumer } from 'layered-loader';
|
|
4
|
+
import { CLEAR_GROUP_NOTIFICATION_SCHEMA, DELETE_FROM_GROUP_NOTIFICATION_SCHEMA, DELETE_GROUP_NOTIFICATION_SCHEMA, NOTIFICATION_ID_FIELD, NOTIFICATION_TIMESTAMP_FIELD, NOTIFICATION_TYPE_FIELD, } from './groupNotificationSchemas.js';
|
|
5
|
+
class SnsSqsGroupInvalidationConsumer extends AbstractSnsSqsConsumer {
|
|
6
|
+
constructor(dependencies, options, context) {
|
|
7
|
+
super(dependencies, options, context);
|
|
8
|
+
}
|
|
9
|
+
get publicTopicArn() {
|
|
10
|
+
return this.topicArn;
|
|
11
|
+
}
|
|
12
|
+
get publicSubscriptionArn() {
|
|
13
|
+
return this.subscriptionArn;
|
|
14
|
+
}
|
|
15
|
+
get publicQueueUrl() {
|
|
16
|
+
return this.queueUrl;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export class SqsGroupNotificationConsumer extends AbstractNotificationConsumer {
|
|
20
|
+
params;
|
|
21
|
+
internalConsumer;
|
|
22
|
+
subscribePromise;
|
|
23
|
+
constructor(params) {
|
|
24
|
+
super(params.serverUuid, params.errorHandler);
|
|
25
|
+
this.params = params;
|
|
26
|
+
}
|
|
27
|
+
async subscribe() {
|
|
28
|
+
if (this.internalConsumer) {
|
|
29
|
+
return this.internalConsumer;
|
|
30
|
+
}
|
|
31
|
+
if (this.subscribePromise) {
|
|
32
|
+
return this.subscribePromise;
|
|
33
|
+
}
|
|
34
|
+
if (!this.targetCache) {
|
|
35
|
+
throw new Error('targetCache must be set via setTargetCache() before subscribe() is called');
|
|
36
|
+
}
|
|
37
|
+
const handlers = new MessageHandlerConfigBuilder()
|
|
38
|
+
.addConfig(DELETE_FROM_GROUP_NOTIFICATION_SCHEMA, async (message, ctx) => {
|
|
39
|
+
if (message.originUuid !== ctx.serverUuid) {
|
|
40
|
+
ctx.targetCache.deleteFromGroup(message.key, message.group);
|
|
41
|
+
}
|
|
42
|
+
return { result: 'success' };
|
|
43
|
+
})
|
|
44
|
+
.addConfig(DELETE_GROUP_NOTIFICATION_SCHEMA, async (message, ctx) => {
|
|
45
|
+
if (message.originUuid !== ctx.serverUuid) {
|
|
46
|
+
ctx.targetCache.deleteGroup(message.group);
|
|
47
|
+
}
|
|
48
|
+
return { result: 'success' };
|
|
49
|
+
})
|
|
50
|
+
.addConfig(CLEAR_GROUP_NOTIFICATION_SCHEMA, async (message, ctx) => {
|
|
51
|
+
if (message.originUuid !== ctx.serverUuid) {
|
|
52
|
+
ctx.targetCache.clear();
|
|
53
|
+
}
|
|
54
|
+
return { result: 'success' };
|
|
55
|
+
})
|
|
56
|
+
.build();
|
|
57
|
+
const options = {
|
|
58
|
+
handlers,
|
|
59
|
+
messageTypeResolver: { messageTypePath: NOTIFICATION_TYPE_FIELD },
|
|
60
|
+
messageIdField: NOTIFICATION_ID_FIELD,
|
|
61
|
+
messageTimestampField: NOTIFICATION_TIMESTAMP_FIELD,
|
|
62
|
+
subscriptionConfig: this.params.subscriptionConfig ?? { updateAttributesIfExists: false },
|
|
63
|
+
...(this.params.creationConfig
|
|
64
|
+
? { creationConfig: this.params.creationConfig }
|
|
65
|
+
: { locatorConfig: this.params.locatorConfig }),
|
|
66
|
+
};
|
|
67
|
+
const consumer = new SnsSqsGroupInvalidationConsumer(this.params.dependencies, options, { serverUuid: this.serverUuid, targetCache: this.targetCache });
|
|
68
|
+
// Single-flight: concurrent subscribe() calls share one init/start; the
|
|
69
|
+
// internal consumer is only assigned once both succeed.
|
|
70
|
+
this.subscribePromise = (async () => {
|
|
71
|
+
try {
|
|
72
|
+
await consumer.init();
|
|
73
|
+
await consumer.start();
|
|
74
|
+
this.internalConsumer = consumer;
|
|
75
|
+
return consumer;
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
await consumer.close().catch(() => undefined);
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
this.subscribePromise = undefined;
|
|
83
|
+
}
|
|
84
|
+
})();
|
|
85
|
+
return this.subscribePromise;
|
|
86
|
+
}
|
|
87
|
+
async close() {
|
|
88
|
+
if (this.subscribePromise) {
|
|
89
|
+
await this.subscribePromise.catch(() => undefined);
|
|
90
|
+
}
|
|
91
|
+
if (!this.internalConsumer)
|
|
92
|
+
return;
|
|
93
|
+
const consumer = this.internalConsumer;
|
|
94
|
+
this.internalConsumer = undefined;
|
|
95
|
+
await consumer.close();
|
|
96
|
+
}
|
|
97
|
+
get topicArn() {
|
|
98
|
+
return this.internalConsumer?.publicTopicArn;
|
|
99
|
+
}
|
|
100
|
+
get subscriptionArn() {
|
|
101
|
+
return this.internalConsumer?.publicSubscriptionArn;
|
|
102
|
+
}
|
|
103
|
+
get queueUrl() {
|
|
104
|
+
return this.internalConsumer?.publicQueueUrl;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=SqsGroupNotificationConsumer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SqsGroupNotificationConsumer.js","sourceRoot":"","sources":["../../lib/SqsGroupNotificationConsumer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAA;AACzE,OAAO,EACL,sBAAsB,GAKvB,MAAM,4BAA4B,CAAA;AAEnC,OAAO,EAAE,4BAA4B,EAAE,MAAM,gBAAgB,CAAA;AAE7D,OAAO,EACL,+BAA+B,EAC/B,qCAAqC,EACrC,gCAAgC,EAEhC,qBAAqB,EACrB,4BAA4B,EAC5B,uBAAuB,GACxB,MAAM,+BAA+B,CAAA;AAkBtC,MAAM,+BAA6C,SAAQ,sBAG1D;IACC,YACE,YAAwC,EACxC,OAIC,EACD,OAAqC;QAErC,KAAK,CAAC,YAAY,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;IACvC,CAAC;IAED,IAAI,cAAc;QAChB,OAAO,IAAI,CAAC,QAAQ,CAAA;IACtB,CAAC;IAED,IAAI,qBAAqB;QACvB,OAAO,IAAI,CAAC,eAAe,CAAA;IAC7B,CAAC;IAED,IAAI,cAAc;QAChB,OAAO,IAAI,CAAC,QAAQ,CAAA;IACtB,CAAC;CACF;AAED,MAAM,OAAO,4BAA0C,SAAQ,4BAG9D;IACkB,MAAM,CAAoC;IACnD,gBAAgB,CAA+C;IAC/D,gBAAgB,CAAwD;IAEhF,YAAY,MAA0C;QACpD,KAAK,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,YAAY,CAAC,CAAA;QAC7C,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;IACtB,CAAC;IAED,KAAK,CAAC,SAAS;QACb,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC1B,OAAO,IAAI,CAAC,gBAAgB,CAAA;QAC9B,CAAC;QACD,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC1B,OAAO,IAAI,CAAC,gBAAgB,CAAA;QAC9B,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CACb,2EAA2E,CAC5E,CAAA;QACH,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,2BAA2B,EAG7C;aACA,SAAS,CAAC,qCAAqC,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE;YACvE,IAAI,OAAO,CAAC,UAAU,KAAK,GAAG,CAAC,UAAU,EAAE,CAAC;gBAC1C,GAAG,CAAC,WAAW,CAAC,eAAe,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,KAAK,CAAC,CAAA;YAC7D,CAAC;YACD,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAA;QAC9B,CAAC,CAAC;aACD,SAAS,CAAC,gCAAgC,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE;YAClE,IAAI,OAAO,CAAC,UAAU,KAAK,GAAG,CAAC,UAAU,EAAE,CAAC;gBAC1C,GAAG,CAAC,WAAW,CAAC,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;YAC5C,CAAC;YACD,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAA;QAC9B,CAAC,CAAC;aACD,SAAS,CAAC,+BAA+B,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE;YACjE,IAAI,OAAO,CAAC,UAAU,KAAK,GAAG,CAAC,UAAU,EAAE,CAAC;gBAC1C,GAAG,CAAC,WAAW,CAAC,KAAK,EAAE,CAAA;YACzB,CAAC;YACD,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAA;QAC9B,CAAC,CAAC;aACD,KAAK,EAAE,CAAA;QAEV,MAAM,OAAO,GAIT;YACF,QAAQ;YACR,mBAAmB,EAAE,EAAE,eAAe,EAAE,uBAAuB,EAAE;YACjE,cAAc,EAAE,qBAAqB;YACrC,qBAAqB,EAAE,4BAA4B;YACnD,kBAAkB,EAAE,IAAI,CAAC,MAAM,CAAC,kBAAkB,IAAI,EAAE,wBAAwB,EAAE,KAAK,EAAE;YACzF,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc;gBAC5B,CAAC,CAAC,EAAE,cAAc,EAAE,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE;gBAChD,CAAC,CAAC,EAAE,aAAa,EAAE,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;SAClD,CAAA;QAED,MAAM,QAAQ,GAAG,IAAI,+BAA+B,CAClD,IAAI,CAAC,MAAM,CAAC,YAAY,EACxB,OAAO,EACP,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAC/D,CAAA;QAED,wEAAwE;QACxE,wDAAwD;QACxD,IAAI,CAAC,gBAAgB,GAAG,CAAC,KAAK,IAAI,EAAE;YAClC,IAAI,CAAC;gBACH,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;gBACrB,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAA;gBACtB,IAAI,CAAC,gBAAgB,GAAG,QAAQ,CAAA;gBAChC,OAAO,QAAQ,CAAA;YACjB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;gBAC7C,MAAM,GAAG,CAAA;YACX,CAAC;oBAAS,CAAC;gBACT,IAAI,CAAC,gBAAgB,GAAG,SAAS,CAAA;YACnC,CAAC;QACH,CAAC,CAAC,EAAE,CAAA;QACJ,OAAO,IAAI,CAAC,gBAAgB,CAAA;IAC9B,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC1B,MAAM,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;QACpD,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,gBAAgB;YAAE,OAAM;QAClC,MAAM,QAAQ,GAAG,IAAI,CAAC,gBAAgB,CAAA;QACtC,IAAI,CAAC,gBAAgB,GAAG,SAAS,CAAA;QACjC,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAA;IACxB,CAAC;IAED,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,gBAAgB,EAAE,cAAc,CAAA;IAC9C,CAAC;IAED,IAAI,eAAe;QACjB,OAAO,IAAI,CAAC,gBAAgB,EAAE,qBAAqB,CAAA;IACrD,CAAC;IAED,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,gBAAgB,EAAE,cAAc,CAAA;IAC9C,CAAC;CACF"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { SNSDependencies, SNSSQSConsumerDependencies } from '@message-queue-toolkit/sns';
|
|
2
|
+
import type { ConsumerErrorHandler, PublisherErrorHandler } from 'layered-loader';
|
|
3
|
+
import { SqsGroupNotificationConsumer, type SqsGroupNotificationConsumerConfig } from './SqsGroupNotificationConsumer.js';
|
|
4
|
+
import { SqsGroupNotificationPublisher, type SqsGroupNotificationPublisherConfig } from './SqsGroupNotificationPublisher.js';
|
|
5
|
+
import type { SqsSubscriptionOptions } from './SqsNotificationConsumer.js';
|
|
6
|
+
export type SqsGroupNotificationConfig = {
|
|
7
|
+
channel?: string;
|
|
8
|
+
serverUuid?: string;
|
|
9
|
+
publisherErrorHandler?: PublisherErrorHandler;
|
|
10
|
+
consumerErrorHandler?: ConsumerErrorHandler;
|
|
11
|
+
publisher: {
|
|
12
|
+
dependencies: SNSDependencies;
|
|
13
|
+
} & SqsGroupNotificationPublisherConfig;
|
|
14
|
+
consumer: {
|
|
15
|
+
dependencies: SNSSQSConsumerDependencies;
|
|
16
|
+
subscriptionConfig?: SqsSubscriptionOptions;
|
|
17
|
+
} & SqsGroupNotificationConsumerConfig;
|
|
18
|
+
};
|
|
19
|
+
export declare function createGroupNotificationPair<LoadedValue>(config: SqsGroupNotificationConfig): {
|
|
20
|
+
publisher: SqsGroupNotificationPublisher<LoadedValue>;
|
|
21
|
+
consumer: SqsGroupNotificationConsumer<LoadedValue>;
|
|
22
|
+
};
|