@celerity-sdk/topic 0.3.1 → 0.5.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/README.md +10 -12
- package/dist/chunk-T4LCI5X5.js +20 -0
- package/dist/chunk-T4LCI5X5.js.map +1 -0
- package/dist/index.cjs +524 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +194 -1
- package/dist/index.js +128 -0
- package/dist/index.js.map +1 -1
- package/dist/redis-topic-client-SPF63T34.js +158 -0
- package/dist/redis-topic-client-SPF63T34.js.map +1 -0
- package/dist/sns-topic-client-643MNMBF.js +161 -0
- package/dist/sns-topic-client-643MNMBF.js.map +1 -0
- package/package.json +20 -4
- package/dist/index.d.cts +0 -2
package/README.md
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# @celerity-sdk/topic
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Cloud-agnostic pub/sub topic abstraction for the Celerity Node SDK.
|
|
4
4
|
|
|
5
|
-
Provides a unified `TopicClient` interface for publishing messages to topics across cloud providers
|
|
5
|
+
Provides a unified `TopicClient` interface for publishing messages to topics across cloud providers. The Celerity runtime handles all consumption — this package is **publish-only** (`publish`, `publishBatch`).
|
|
6
6
|
|
|
7
|
-
- **AWS**:Amazon SNS
|
|
8
|
-
- **GCP**:Google Cloud Pub/Sub
|
|
9
|
-
- **Azure**:Azure Service Bus Topics
|
|
7
|
+
- **AWS**: Amazon SNS
|
|
8
|
+
- **GCP**: Google Cloud Pub/Sub *(planned)*
|
|
9
|
+
- **Azure**: Azure Service Bus Topics *(planned)*
|
|
10
|
+
- **Local**: Redis pub/sub channels via Celerity CLI
|
|
10
11
|
|
|
11
12
|
## Installation
|
|
12
13
|
|
|
@@ -20,17 +21,14 @@ Install the cloud SDK for your target platform as a peer dependency:
|
|
|
20
21
|
# AWS
|
|
21
22
|
pnpm add @aws-sdk/client-sns
|
|
22
23
|
|
|
23
|
-
#
|
|
24
|
-
pnpm add
|
|
25
|
-
|
|
26
|
-
# Azure
|
|
27
|
-
pnpm add @azure/service-bus
|
|
24
|
+
# Local development (managed by Celerity CLI, but needed for direct usage)
|
|
25
|
+
pnpm add ioredis
|
|
28
26
|
```
|
|
29
27
|
|
|
30
28
|
## Status
|
|
31
29
|
|
|
32
|
-
This package
|
|
30
|
+
This package implements the `TopicClient` interface with providers for AWS SNS and Redis (local development). Support for Google Cloud Pub/Sub and Azure Service Bus Topics will be added in future releases.
|
|
33
31
|
|
|
34
32
|
## Part of the Celerity Framework
|
|
35
33
|
|
|
36
|
-
See [celerityframework.
|
|
34
|
+
See [celerityframework.io](https://celerityframework.io) for full documentation.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/errors.ts
|
|
5
|
+
var TopicError = class extends Error {
|
|
6
|
+
static {
|
|
7
|
+
__name(this, "TopicError");
|
|
8
|
+
}
|
|
9
|
+
topic;
|
|
10
|
+
constructor(message, topic, options) {
|
|
11
|
+
super(message, options), this.topic = topic;
|
|
12
|
+
this.name = "TopicError";
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
__name,
|
|
18
|
+
TopicError
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=chunk-T4LCI5X5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Base error for topic operations. Wraps provider SDK errors and includes\n * the topic identifier for context.\n */\nexport class TopicError extends Error {\n constructor(\n message: string,\n public readonly topic: string,\n options?: { cause?: unknown },\n ) {\n super(message, options);\n this.name = \"TopicError\";\n }\n}\n"],"mappings":";;;;AAIO,IAAMA,aAAN,cAAyBC,MAAAA;EAJhC,OAIgCA;;;;EAC9B,YACEC,SACgBC,OAChBC,SACA;AACA,UAAMF,SAASE,OAAAA,GAAAA,KAHCD,QAAAA;AAIhB,SAAKE,OAAO;EACd;AACF;","names":["TopicError","Error","message","topic","options","name"]}
|
package/dist/index.cjs
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
6
16
|
var __copyProps = (to, from, except, desc) => {
|
|
7
17
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
18
|
for (let key of __getOwnPropNames(from))
|
|
@@ -11,9 +21,523 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
11
21
|
}
|
|
12
22
|
return to;
|
|
13
23
|
};
|
|
24
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
25
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
26
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
27
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
28
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
29
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
30
|
+
mod
|
|
31
|
+
));
|
|
14
32
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
15
33
|
|
|
34
|
+
// src/errors.ts
|
|
35
|
+
var TopicError;
|
|
36
|
+
var init_errors = __esm({
|
|
37
|
+
"src/errors.ts"() {
|
|
38
|
+
"use strict";
|
|
39
|
+
TopicError = class extends Error {
|
|
40
|
+
static {
|
|
41
|
+
__name(this, "TopicError");
|
|
42
|
+
}
|
|
43
|
+
topic;
|
|
44
|
+
constructor(message, topic, options) {
|
|
45
|
+
super(message, options), this.topic = topic;
|
|
46
|
+
this.name = "TopicError";
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// src/providers/sns/sns-topic.ts
|
|
53
|
+
function toSNSAttributes(attrs) {
|
|
54
|
+
const result = {};
|
|
55
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
56
|
+
result[key] = {
|
|
57
|
+
DataType: "String",
|
|
58
|
+
StringValue: value
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
var import_debug, import_client_sns, debug, SNS_MAX_BATCH_SIZE, SNSTopic;
|
|
64
|
+
var init_sns_topic = __esm({
|
|
65
|
+
"src/providers/sns/sns-topic.ts"() {
|
|
66
|
+
"use strict";
|
|
67
|
+
import_debug = __toESM(require("debug"), 1);
|
|
68
|
+
import_client_sns = require("@aws-sdk/client-sns");
|
|
69
|
+
init_errors();
|
|
70
|
+
debug = (0, import_debug.default)("celerity:topic:sns");
|
|
71
|
+
SNS_MAX_BATCH_SIZE = 10;
|
|
72
|
+
SNSTopic = class {
|
|
73
|
+
static {
|
|
74
|
+
__name(this, "SNSTopic");
|
|
75
|
+
}
|
|
76
|
+
topicArn;
|
|
77
|
+
client;
|
|
78
|
+
tracer;
|
|
79
|
+
constructor(topicArn, client, tracer) {
|
|
80
|
+
this.topicArn = topicArn;
|
|
81
|
+
this.client = client;
|
|
82
|
+
this.tracer = tracer;
|
|
83
|
+
}
|
|
84
|
+
async publish(body, options) {
|
|
85
|
+
debug("publish %s", this.topicArn);
|
|
86
|
+
return this.traced("celerity.topic.publish", {
|
|
87
|
+
"topic.arn": this.topicArn
|
|
88
|
+
}, async () => {
|
|
89
|
+
try {
|
|
90
|
+
const result = await this.client.send(new import_client_sns.PublishCommand({
|
|
91
|
+
TopicArn: this.topicArn,
|
|
92
|
+
Message: JSON.stringify(body),
|
|
93
|
+
MessageGroupId: options?.groupId,
|
|
94
|
+
MessageDeduplicationId: options?.deduplicationId,
|
|
95
|
+
Subject: options?.subject,
|
|
96
|
+
MessageAttributes: options?.attributes ? toSNSAttributes(options.attributes) : void 0
|
|
97
|
+
}));
|
|
98
|
+
return {
|
|
99
|
+
messageId: result.MessageId
|
|
100
|
+
};
|
|
101
|
+
} catch (error) {
|
|
102
|
+
throw new TopicError(`Failed to publish message to topic "${this.topicArn}"`, this.topicArn, {
|
|
103
|
+
cause: error
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
async publishBatch(entries) {
|
|
109
|
+
debug("publishBatch %s (%d entries)", this.topicArn, entries.length);
|
|
110
|
+
return this.traced("celerity.topic.publish_batch", {
|
|
111
|
+
"topic.arn": this.topicArn,
|
|
112
|
+
"topic.message_count": entries.length
|
|
113
|
+
}, async () => {
|
|
114
|
+
const successful = [];
|
|
115
|
+
const failed = [];
|
|
116
|
+
for (let i = 0; i < entries.length; i += SNS_MAX_BATCH_SIZE) {
|
|
117
|
+
const chunk = entries.slice(i, i + SNS_MAX_BATCH_SIZE);
|
|
118
|
+
const snsEntries = chunk.map((entry) => ({
|
|
119
|
+
Id: entry.id,
|
|
120
|
+
Message: JSON.stringify(entry.body),
|
|
121
|
+
MessageGroupId: entry.options?.groupId,
|
|
122
|
+
MessageDeduplicationId: entry.options?.deduplicationId,
|
|
123
|
+
Subject: entry.options?.subject,
|
|
124
|
+
MessageAttributes: entry.options?.attributes ? toSNSAttributes(entry.options.attributes) : void 0
|
|
125
|
+
}));
|
|
126
|
+
try {
|
|
127
|
+
const result = await this.client.send(new import_client_sns.PublishBatchCommand({
|
|
128
|
+
TopicArn: this.topicArn,
|
|
129
|
+
PublishBatchRequestEntries: snsEntries
|
|
130
|
+
}));
|
|
131
|
+
for (const s of result.Successful ?? []) {
|
|
132
|
+
successful.push({
|
|
133
|
+
id: s.Id,
|
|
134
|
+
messageId: s.MessageId
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
for (const f of result.Failed ?? []) {
|
|
138
|
+
failed.push({
|
|
139
|
+
id: f.Id,
|
|
140
|
+
code: f.Code,
|
|
141
|
+
message: f.Message ?? "Unknown error"
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
} catch (error) {
|
|
145
|
+
throw new TopicError(`Failed to publish message batch to topic "${this.topicArn}"`, this.topicArn, {
|
|
146
|
+
cause: error
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
successful,
|
|
152
|
+
failed
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
traced(name, attributes, fn) {
|
|
157
|
+
if (!this.tracer) return fn();
|
|
158
|
+
return this.tracer.withSpan(name, (span) => fn(span), attributes);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
__name(toSNSAttributes, "toSNSAttributes");
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// src/providers/sns/config.ts
|
|
166
|
+
function captureSNSConfig() {
|
|
167
|
+
return {
|
|
168
|
+
region: process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION,
|
|
169
|
+
endpoint: process.env.CELERITY_AWS_SNS_ENDPOINT ?? process.env.AWS_ENDPOINT_URL,
|
|
170
|
+
credentials: process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY ? {
|
|
171
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
172
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
|
|
173
|
+
} : void 0
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
var init_config = __esm({
|
|
177
|
+
"src/providers/sns/config.ts"() {
|
|
178
|
+
"use strict";
|
|
179
|
+
__name(captureSNSConfig, "captureSNSConfig");
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// src/providers/sns/sns-topic-client.ts
|
|
184
|
+
var sns_topic_client_exports = {};
|
|
185
|
+
__export(sns_topic_client_exports, {
|
|
186
|
+
SNSTopicClient: () => SNSTopicClient
|
|
187
|
+
});
|
|
188
|
+
var import_client_sns2, SNSTopicClient;
|
|
189
|
+
var init_sns_topic_client = __esm({
|
|
190
|
+
"src/providers/sns/sns-topic-client.ts"() {
|
|
191
|
+
"use strict";
|
|
192
|
+
import_client_sns2 = require("@aws-sdk/client-sns");
|
|
193
|
+
init_sns_topic();
|
|
194
|
+
init_config();
|
|
195
|
+
SNSTopicClient = class {
|
|
196
|
+
static {
|
|
197
|
+
__name(this, "SNSTopicClient");
|
|
198
|
+
}
|
|
199
|
+
tracer;
|
|
200
|
+
client = null;
|
|
201
|
+
config;
|
|
202
|
+
constructor(config, tracer) {
|
|
203
|
+
this.tracer = tracer;
|
|
204
|
+
this.config = config ?? captureSNSConfig();
|
|
205
|
+
}
|
|
206
|
+
topic(name) {
|
|
207
|
+
return new SNSTopic(name, this.getClient(), this.tracer);
|
|
208
|
+
}
|
|
209
|
+
close() {
|
|
210
|
+
this.client?.destroy();
|
|
211
|
+
this.client = null;
|
|
212
|
+
}
|
|
213
|
+
getClient() {
|
|
214
|
+
if (!this.client) {
|
|
215
|
+
this.client = new import_client_sns2.SNSClient({
|
|
216
|
+
region: this.config.region,
|
|
217
|
+
endpoint: this.config.endpoint,
|
|
218
|
+
credentials: this.config.credentials
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
return this.client;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// src/providers/redis/redis-topic.ts
|
|
228
|
+
function buildEnvelope(body, messageId, options) {
|
|
229
|
+
const envelope = {
|
|
230
|
+
body: JSON.stringify(body),
|
|
231
|
+
messageId
|
|
232
|
+
};
|
|
233
|
+
if (options?.subject) {
|
|
234
|
+
envelope.subject = options.subject;
|
|
235
|
+
}
|
|
236
|
+
if (options?.attributes && Object.keys(options.attributes).length > 0) {
|
|
237
|
+
envelope.attributes = options.attributes;
|
|
238
|
+
}
|
|
239
|
+
return JSON.stringify(envelope);
|
|
240
|
+
}
|
|
241
|
+
var import_node_crypto, import_debug2, debug2, RedisTopic;
|
|
242
|
+
var init_redis_topic = __esm({
|
|
243
|
+
"src/providers/redis/redis-topic.ts"() {
|
|
244
|
+
"use strict";
|
|
245
|
+
import_node_crypto = require("crypto");
|
|
246
|
+
import_debug2 = __toESM(require("debug"), 1);
|
|
247
|
+
init_errors();
|
|
248
|
+
debug2 = (0, import_debug2.default)("celerity:topic:redis");
|
|
249
|
+
RedisTopic = class {
|
|
250
|
+
static {
|
|
251
|
+
__name(this, "RedisTopic");
|
|
252
|
+
}
|
|
253
|
+
channelName;
|
|
254
|
+
client;
|
|
255
|
+
tracer;
|
|
256
|
+
constructor(channelName, client, tracer) {
|
|
257
|
+
this.channelName = channelName;
|
|
258
|
+
this.client = client;
|
|
259
|
+
this.tracer = tracer;
|
|
260
|
+
}
|
|
261
|
+
async publish(body, options) {
|
|
262
|
+
debug2("publish %s", this.channelName);
|
|
263
|
+
return this.traced("celerity.topic.publish", {
|
|
264
|
+
"topic.channel": this.channelName
|
|
265
|
+
}, async () => {
|
|
266
|
+
try {
|
|
267
|
+
const messageId = (0, import_node_crypto.randomUUID)();
|
|
268
|
+
const payload = buildEnvelope(body, messageId, options);
|
|
269
|
+
await this.client.publish(this.channelName, payload);
|
|
270
|
+
return {
|
|
271
|
+
messageId
|
|
272
|
+
};
|
|
273
|
+
} catch (error) {
|
|
274
|
+
throw new TopicError(`Failed to publish message to channel "${this.channelName}"`, this.channelName, {
|
|
275
|
+
cause: error
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
async publishBatch(entries) {
|
|
281
|
+
debug2("publishBatch %s (%d entries)", this.channelName, entries.length);
|
|
282
|
+
return this.traced("celerity.topic.publish_batch", {
|
|
283
|
+
"topic.channel": this.channelName,
|
|
284
|
+
"topic.message_count": entries.length
|
|
285
|
+
}, async () => {
|
|
286
|
+
const successful = [];
|
|
287
|
+
const failed = [];
|
|
288
|
+
const messageIds = [];
|
|
289
|
+
const pipeline = this.client.pipeline();
|
|
290
|
+
for (const entry of entries) {
|
|
291
|
+
const messageId = (0, import_node_crypto.randomUUID)();
|
|
292
|
+
messageIds.push(messageId);
|
|
293
|
+
const payload = buildEnvelope(entry.body, messageId, entry.options);
|
|
294
|
+
pipeline.publish(this.channelName, payload);
|
|
295
|
+
}
|
|
296
|
+
try {
|
|
297
|
+
const results = await pipeline.exec();
|
|
298
|
+
if (!results) {
|
|
299
|
+
throw new Error("Pipeline returned null");
|
|
300
|
+
}
|
|
301
|
+
for (let i = 0; i < entries.length; i++) {
|
|
302
|
+
const [err] = results[i];
|
|
303
|
+
if (err) {
|
|
304
|
+
failed.push({
|
|
305
|
+
id: entries[i].id,
|
|
306
|
+
code: err.name ?? "PipelineError",
|
|
307
|
+
message: err.message
|
|
308
|
+
});
|
|
309
|
+
} else {
|
|
310
|
+
successful.push({
|
|
311
|
+
id: entries[i].id,
|
|
312
|
+
messageId: messageIds[i]
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
} catch (error) {
|
|
317
|
+
throw new TopicError(`Failed to publish message batch to channel "${this.channelName}"`, this.channelName, {
|
|
318
|
+
cause: error
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
return {
|
|
322
|
+
successful,
|
|
323
|
+
failed
|
|
324
|
+
};
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
traced(name, attributes, fn) {
|
|
328
|
+
if (!this.tracer) return fn();
|
|
329
|
+
return this.tracer.withSpan(name, (span) => fn(span), attributes);
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
__name(buildEnvelope, "buildEnvelope");
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// src/providers/redis/config.ts
|
|
337
|
+
function captureRedisConfig() {
|
|
338
|
+
return {
|
|
339
|
+
url: process.env.CELERITY_REDIS_ENDPOINT ?? DEFAULT_REDIS_URL
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
var DEFAULT_REDIS_URL;
|
|
343
|
+
var init_config2 = __esm({
|
|
344
|
+
"src/providers/redis/config.ts"() {
|
|
345
|
+
"use strict";
|
|
346
|
+
DEFAULT_REDIS_URL = "redis://localhost:6379";
|
|
347
|
+
__name(captureRedisConfig, "captureRedisConfig");
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// src/providers/redis/redis-topic-client.ts
|
|
352
|
+
var redis_topic_client_exports = {};
|
|
353
|
+
__export(redis_topic_client_exports, {
|
|
354
|
+
RedisTopicClient: () => RedisTopicClient
|
|
355
|
+
});
|
|
356
|
+
var RedisTopicClient;
|
|
357
|
+
var init_redis_topic_client = __esm({
|
|
358
|
+
"src/providers/redis/redis-topic-client.ts"() {
|
|
359
|
+
"use strict";
|
|
360
|
+
init_redis_topic();
|
|
361
|
+
init_config2();
|
|
362
|
+
RedisTopicClient = class {
|
|
363
|
+
static {
|
|
364
|
+
__name(this, "RedisTopicClient");
|
|
365
|
+
}
|
|
366
|
+
tracer;
|
|
367
|
+
client = null;
|
|
368
|
+
ioredisModule = null;
|
|
369
|
+
config;
|
|
370
|
+
constructor(config, tracer) {
|
|
371
|
+
this.tracer = tracer;
|
|
372
|
+
this.config = config ?? captureRedisConfig();
|
|
373
|
+
}
|
|
374
|
+
topic(name) {
|
|
375
|
+
const channel = `celerity:topic:channel:${name}`;
|
|
376
|
+
return new RedisTopic(channel, this.getClient(), this.tracer);
|
|
377
|
+
}
|
|
378
|
+
async close() {
|
|
379
|
+
if (this.client) {
|
|
380
|
+
await this.client.quit();
|
|
381
|
+
this.client = null;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
async ensureIoRedis() {
|
|
385
|
+
if (!this.ioredisModule) {
|
|
386
|
+
const pkg = "ioredis";
|
|
387
|
+
this.ioredisModule = await import(pkg);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
getClient() {
|
|
391
|
+
if (!this.client) {
|
|
392
|
+
const ioredis = this.ioredisModule;
|
|
393
|
+
const Redis = ioredis.default ?? ioredis;
|
|
394
|
+
this.client = new Redis(this.config.url);
|
|
395
|
+
}
|
|
396
|
+
return this.client;
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
|
|
16
402
|
// src/index.ts
|
|
17
403
|
var index_exports = {};
|
|
404
|
+
__export(index_exports, {
|
|
405
|
+
DEFAULT_TOPIC_TOKEN: () => DEFAULT_TOPIC_TOKEN,
|
|
406
|
+
Topic: () => Topic,
|
|
407
|
+
TopicClient: () => TopicClient,
|
|
408
|
+
TopicError: () => TopicError,
|
|
409
|
+
TopicLayer: () => TopicLayer,
|
|
410
|
+
createTopicClient: () => createTopicClient,
|
|
411
|
+
getTopic: () => getTopic,
|
|
412
|
+
topicToken: () => topicToken
|
|
413
|
+
});
|
|
18
414
|
module.exports = __toCommonJS(index_exports);
|
|
415
|
+
|
|
416
|
+
// src/types.ts
|
|
417
|
+
var TopicClient = /* @__PURE__ */ Symbol.for("TopicClient");
|
|
418
|
+
|
|
419
|
+
// src/factory.ts
|
|
420
|
+
var import_config3 = require("@celerity-sdk/config");
|
|
421
|
+
async function createTopicClient(options) {
|
|
422
|
+
const resolved = (0, import_config3.resolveConfig)("topic");
|
|
423
|
+
const provider = options?.provider ?? resolved.provider;
|
|
424
|
+
switch (provider) {
|
|
425
|
+
case "aws": {
|
|
426
|
+
const { SNSTopicClient: SNSTopicClient2 } = await Promise.resolve().then(() => (init_sns_topic_client(), sns_topic_client_exports));
|
|
427
|
+
return new SNSTopicClient2(options?.aws, options?.tracer);
|
|
428
|
+
}
|
|
429
|
+
// Local environments always use Redis pub/sub regardless of deploy target.
|
|
430
|
+
// The Celerity CLI manages the Redis instance and local-events sidecar.
|
|
431
|
+
case "local": {
|
|
432
|
+
const { RedisTopicClient: RedisTopicClient2 } = await Promise.resolve().then(() => (init_redis_topic_client(), redis_topic_client_exports));
|
|
433
|
+
const client = new RedisTopicClient2(options?.local, options?.tracer);
|
|
434
|
+
await client.ensureIoRedis();
|
|
435
|
+
return client;
|
|
436
|
+
}
|
|
437
|
+
// case "gcp":
|
|
438
|
+
// v1: Google Cloud Pub/Sub
|
|
439
|
+
// case "azure":
|
|
440
|
+
// v1: Azure Service Bus Topics
|
|
441
|
+
default:
|
|
442
|
+
throw new Error(`Unsupported topic provider: "${provider}"`);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
__name(createTopicClient, "createTopicClient");
|
|
446
|
+
|
|
447
|
+
// src/decorators.ts
|
|
448
|
+
var import_reflect_metadata = require("reflect-metadata");
|
|
449
|
+
var import_common = require("@celerity-sdk/common");
|
|
450
|
+
function topicToken(resourceName) {
|
|
451
|
+
return /* @__PURE__ */ Symbol.for(`celerity:topic:${resourceName}`);
|
|
452
|
+
}
|
|
453
|
+
__name(topicToken, "topicToken");
|
|
454
|
+
var DEFAULT_TOPIC_TOKEN = /* @__PURE__ */ Symbol.for("celerity:topic:default");
|
|
455
|
+
function Topic(resourceName) {
|
|
456
|
+
return (target, _propertyKey, parameterIndex) => {
|
|
457
|
+
const token = resourceName ? topicToken(resourceName) : DEFAULT_TOPIC_TOKEN;
|
|
458
|
+
const existing = Reflect.getOwnMetadata(import_common.INJECT_METADATA, target) ?? /* @__PURE__ */ new Map();
|
|
459
|
+
existing.set(parameterIndex, token);
|
|
460
|
+
Reflect.defineMetadata(import_common.INJECT_METADATA, existing, target);
|
|
461
|
+
if (resourceName) {
|
|
462
|
+
const resources = Reflect.getOwnMetadata(import_common.USE_RESOURCE_METADATA, target) ?? [];
|
|
463
|
+
if (!resources.includes(resourceName)) {
|
|
464
|
+
Reflect.defineMetadata(import_common.USE_RESOURCE_METADATA, [
|
|
465
|
+
...resources,
|
|
466
|
+
resourceName
|
|
467
|
+
], target);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
__name(Topic, "Topic");
|
|
473
|
+
|
|
474
|
+
// src/helpers.ts
|
|
475
|
+
function getTopic(container, resourceName) {
|
|
476
|
+
const token = resourceName ? topicToken(resourceName) : DEFAULT_TOPIC_TOKEN;
|
|
477
|
+
return container.resolve(token);
|
|
478
|
+
}
|
|
479
|
+
__name(getTopic, "getTopic");
|
|
480
|
+
|
|
481
|
+
// src/layer.ts
|
|
482
|
+
var import_debug3 = __toESM(require("debug"), 1);
|
|
483
|
+
var import_common2 = require("@celerity-sdk/common");
|
|
484
|
+
var import_config4 = require("@celerity-sdk/config");
|
|
485
|
+
var debug3 = (0, import_debug3.default)("celerity:topic");
|
|
486
|
+
var TopicLayer = class {
|
|
487
|
+
static {
|
|
488
|
+
__name(this, "TopicLayer");
|
|
489
|
+
}
|
|
490
|
+
initialized = false;
|
|
491
|
+
async handle(context, next) {
|
|
492
|
+
if (!this.initialized) {
|
|
493
|
+
const tracer = context.container.has(import_common2.TRACER_TOKEN) ? await context.container.resolve(import_common2.TRACER_TOKEN) : void 0;
|
|
494
|
+
const client = await createTopicClient({
|
|
495
|
+
tracer
|
|
496
|
+
});
|
|
497
|
+
debug3("registering TopicClient");
|
|
498
|
+
context.container.register("TopicClient", {
|
|
499
|
+
useValue: client
|
|
500
|
+
});
|
|
501
|
+
const links = (0, import_config4.captureResourceLinks)();
|
|
502
|
+
const topicLinks = (0, import_config4.getLinksOfType)(links, "topic");
|
|
503
|
+
if (topicLinks.size > 0) {
|
|
504
|
+
const configService = await context.container.resolve(import_common2.CONFIG_SERVICE_TOKEN);
|
|
505
|
+
const resourceConfig = configService.namespace(import_config4.RESOURCE_CONFIG_NAMESPACE);
|
|
506
|
+
for (const [resourceName, configKey] of topicLinks) {
|
|
507
|
+
const actualName = await resourceConfig.getOrThrow(configKey);
|
|
508
|
+
debug3("registered topic resource %s \u2192 %s", resourceName, actualName);
|
|
509
|
+
context.container.register(topicToken(resourceName), {
|
|
510
|
+
useValue: client.topic(actualName)
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
if (topicLinks.size === 1) {
|
|
514
|
+
const [, configKey] = [
|
|
515
|
+
...topicLinks.entries()
|
|
516
|
+
][0];
|
|
517
|
+
const actualName = await resourceConfig.getOrThrow(configKey);
|
|
518
|
+
debug3("registered default topic \u2192 %s", actualName);
|
|
519
|
+
context.container.register(DEFAULT_TOPIC_TOKEN, {
|
|
520
|
+
useValue: client.topic(actualName)
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
this.initialized = true;
|
|
525
|
+
}
|
|
526
|
+
return next();
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
// src/index.ts
|
|
531
|
+
init_errors();
|
|
532
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
533
|
+
0 && (module.exports = {
|
|
534
|
+
DEFAULT_TOPIC_TOKEN,
|
|
535
|
+
Topic,
|
|
536
|
+
TopicClient,
|
|
537
|
+
TopicError,
|
|
538
|
+
TopicLayer,
|
|
539
|
+
createTopicClient,
|
|
540
|
+
getTopic,
|
|
541
|
+
topicToken
|
|
542
|
+
});
|
|
19
543
|
//# sourceMappingURL=index.cjs.map
|