@geekmidas/events 10.0.0-alpha.78 → 10.0.0-alpha.80
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/dist/{SQSSubscriber-BXLE_1L3.mjs → SQSSubscriber-BEJBfNTe.mjs} +94 -10
- package/dist/SQSSubscriber-BEJBfNTe.mjs.map +1 -0
- package/dist/{SQSSubscriber-DtRUaZB3.cjs → SQSSubscriber-COTqWvOk.cjs} +101 -10
- package/dist/SQSSubscriber-COTqWvOk.cjs.map +1 -0
- package/dist/basic/index.cjs +16 -4
- package/dist/basic/index.cjs.map +1 -1
- package/dist/basic/index.d.cts.map +1 -1
- package/dist/basic/index.d.mts.map +1 -1
- package/dist/basic/index.mjs +16 -4
- package/dist/basic/index.mjs.map +1 -1
- package/dist/index.cjs +11 -4
- package/dist/index.d.cts +60 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +60 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3 -3
- package/dist/pgboss/index.cjs +50 -22
- package/dist/pgboss/index.cjs.map +1 -1
- package/dist/pgboss/index.d.cts.map +1 -1
- package/dist/pgboss/index.d.mts.map +1 -1
- package/dist/pgboss/index.mjs +50 -22
- package/dist/pgboss/index.mjs.map +1 -1
- package/dist/rabbitmq/index.cjs +32 -15
- package/dist/rabbitmq/index.cjs.map +1 -1
- package/dist/rabbitmq/index.d.cts.map +1 -1
- package/dist/rabbitmq/index.d.mts.map +1 -1
- package/dist/rabbitmq/index.mjs +32 -15
- package/dist/rabbitmq/index.mjs.map +1 -1
- package/dist/{registry-DLnflkMm.mjs → registry-DXLY1Xf6.mjs} +2 -2
- package/dist/{registry-DLnflkMm.mjs.map → registry-DXLY1Xf6.mjs.map} +1 -1
- package/dist/{registry-DNk5GnGq.cjs → registry-DlT36229.cjs} +3 -3
- package/dist/{registry-DNk5GnGq.cjs.map → registry-DlT36229.cjs.map} +1 -1
- package/dist/sns/index.cjs +17 -8
- package/dist/sns/index.cjs.map +1 -1
- package/dist/sns/index.d.cts.map +1 -1
- package/dist/sns/index.d.mts.map +1 -1
- package/dist/sns/index.mjs +17 -8
- package/dist/sns/index.mjs.map +1 -1
- package/dist/sqs/index.cjs +7 -72
- package/dist/sqs/index.cjs.map +1 -1
- package/dist/sqs/index.d.cts +1 -0
- package/dist/sqs/index.d.cts.map +1 -1
- package/dist/sqs/index.d.mts +1 -0
- package/dist/sqs/index.d.mts.map +1 -1
- package/dist/sqs/index.mjs +3 -66
- package/dist/sqs/index.mjs.map +1 -1
- package/dist/telemetry-DG3l807O.cjs +309 -0
- package/dist/telemetry-DG3l807O.cjs.map +1 -0
- package/dist/telemetry-DjjgJMul.mjs +244 -0
- package/dist/telemetry-DjjgJMul.mjs.map +1 -0
- package/package.json +5 -3
- package/dist/SQSSubscriber-BXLE_1L3.mjs.map +0 -1
- package/dist/SQSSubscriber-DtRUaZB3.cjs.map +0 -1
- package/dist/types-BcZjLxVV.cjs +0 -51
- package/dist/types-BcZjLxVV.cjs.map +0 -1
- package/dist/types-Bk5JM6MP.mjs +0 -40
- package/dist/types-Bk5JM6MP.mjs.map +0 -1
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { ROOT_CONTEXT, SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
|
|
2
|
+
//#region src/types.ts
|
|
3
|
+
/**
|
|
4
|
+
* Event publisher/subscriber types
|
|
5
|
+
*/
|
|
6
|
+
let EventPublisherType = /* @__PURE__ */ function(EventPublisherType) {
|
|
7
|
+
EventPublisherType["Basic"] = "basic";
|
|
8
|
+
EventPublisherType["EventBridge"] = "eventbridge";
|
|
9
|
+
EventPublisherType["SQS"] = "sqs";
|
|
10
|
+
EventPublisherType["SNS"] = "sns";
|
|
11
|
+
EventPublisherType["Kafka"] = "kafka";
|
|
12
|
+
EventPublisherType["RabbitMQ"] = "rabbitmq";
|
|
13
|
+
EventPublisherType["PgBoss"] = "pgboss";
|
|
14
|
+
return EventPublisherType;
|
|
15
|
+
}({});
|
|
16
|
+
/** The transports a publisher, subscriber or connection can be built for. */
|
|
17
|
+
const SUPPORTED = [
|
|
18
|
+
"basic",
|
|
19
|
+
"sqs",
|
|
20
|
+
"sns",
|
|
21
|
+
"rabbitmq",
|
|
22
|
+
"pgboss"
|
|
23
|
+
];
|
|
24
|
+
/**
|
|
25
|
+
* A connection string or connection whose transport nothing here implements —
|
|
26
|
+
* a typo'd scheme, or one of the listed-but-unbuilt ones (EventBridge, Kafka).
|
|
27
|
+
*/
|
|
28
|
+
var UnsupportedEventTransport = class extends Error {
|
|
29
|
+
transport;
|
|
30
|
+
role;
|
|
31
|
+
constructor(transport, role) {
|
|
32
|
+
super(`No event ${role} for "${transport}". Use one of: ${SUPPORTED.join(", ")} — e.g. pgboss://… or sns://….`);
|
|
33
|
+
this.transport = transport;
|
|
34
|
+
this.role = role;
|
|
35
|
+
this.name = "UnsupportedEventTransport";
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/telemetry.ts
|
|
40
|
+
/**
|
|
41
|
+
* Trace context across a broker: what makes request → queue → worker one trace.
|
|
42
|
+
*
|
|
43
|
+
* On publish, each message gets a PRODUCER span, and that span's context is
|
|
44
|
+
* written into the message through the global propagator — into the broker's
|
|
45
|
+
* own header or attribute field where it has one. On consume, the context is
|
|
46
|
+
* read back and the handler runs inside a CONSUMER span that is its child. A
|
|
47
|
+
* message carrying no context (an older publisher, a process with telemetry
|
|
48
|
+
* off) starts a new trace instead.
|
|
49
|
+
*
|
|
50
|
+
* Everything goes through `@opentelemetry/api`'s globals. Without a registered
|
|
51
|
+
* provider and propagator the spans are no-ops and nothing is written into a
|
|
52
|
+
* message, so a process that never asked for telemetry publishes exactly what
|
|
53
|
+
* it did before.
|
|
54
|
+
*
|
|
55
|
+
* @module
|
|
56
|
+
*/
|
|
57
|
+
/**
|
|
58
|
+
* The key a trace context travels under where a broker has no header field of
|
|
59
|
+
* its own — a pg-boss job's data. Reserved: it is removed before a handler sees
|
|
60
|
+
* the payload, and a payload of the application's own should not use it.
|
|
61
|
+
*/
|
|
62
|
+
const TRACE_CONTEXT_KEY = "__gkmTrace";
|
|
63
|
+
const TRACER = "@geekmidas/events";
|
|
64
|
+
function tracer() {
|
|
65
|
+
return trace.getTracer(TRACER);
|
|
66
|
+
}
|
|
67
|
+
function attributes(target, operation) {
|
|
68
|
+
return {
|
|
69
|
+
"messaging.system": target.system,
|
|
70
|
+
"messaging.destination.name": target.destination,
|
|
71
|
+
"messaging.operation": operation,
|
|
72
|
+
"messaging.operation.type": operation === "publish" ? "send" : "process",
|
|
73
|
+
...target.type && { "gkm.event.type": target.type },
|
|
74
|
+
...target.messageId && { "messaging.message.id": target.messageId }
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Start the PRODUCER span for one message, as a child of whatever is active,
|
|
79
|
+
* and write its context into a carrier for the broker.
|
|
80
|
+
*/
|
|
81
|
+
function startProducer(target) {
|
|
82
|
+
const active = context.active();
|
|
83
|
+
const span = tracer().startSpan(`${target.destination} publish`, {
|
|
84
|
+
kind: SpanKind.PRODUCER,
|
|
85
|
+
attributes: attributes(target, "publish")
|
|
86
|
+
}, active);
|
|
87
|
+
const carrier = {};
|
|
88
|
+
propagation.inject(trace.setSpan(active, span), carrier);
|
|
89
|
+
return {
|
|
90
|
+
span,
|
|
91
|
+
carrier: Object.keys(carrier).length > 0 ? carrier : void 0
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/** End producer spans, recording `error` on each when the send failed. */
|
|
95
|
+
function endProducers(spans, error) {
|
|
96
|
+
for (const span of spans) {
|
|
97
|
+
if (error !== void 0) recordError(span, error);
|
|
98
|
+
span.end();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Publish `items`, each with a PRODUCER span of its own, through `send` —
|
|
103
|
+
* which receives the carrier to attach to each item, in order.
|
|
104
|
+
*/
|
|
105
|
+
async function publishTraced(items, describe, send) {
|
|
106
|
+
const produced = items.map((item) => startProducer(describe(item)));
|
|
107
|
+
const spans = produced.map((p) => p.span);
|
|
108
|
+
try {
|
|
109
|
+
const result = await send(produced.map((p) => p.carrier));
|
|
110
|
+
endProducers(spans);
|
|
111
|
+
return result;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
endProducers(spans, error);
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* The context a carrier holds, or undefined when it holds none — no carrier, a
|
|
119
|
+
* malformed `traceparent`, or a propagator that throws.
|
|
120
|
+
*/
|
|
121
|
+
function extractTraceContext(carrier) {
|
|
122
|
+
if (!carrier || Object.keys(carrier).length === 0) return void 0;
|
|
123
|
+
try {
|
|
124
|
+
const extracted = propagation.extract(ROOT_CONTEXT, carrier);
|
|
125
|
+
return trace.getSpanContext(extracted) ? extracted : void 0;
|
|
126
|
+
} catch {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Run `fn` inside a CONSUMER span for one message: a child of the context the
|
|
132
|
+
* message carried, or a new trace when it carried none. The active context of
|
|
133
|
+
* the poller that received it is never the parent — a job is not part of
|
|
134
|
+
* whatever happened to start the poll loop.
|
|
135
|
+
*/
|
|
136
|
+
function consumeTraced(target, carrier, fn) {
|
|
137
|
+
return runConsumer(target, extractTraceContext(carrier) ?? ROOT_CONTEXT, [], fn);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* One CONSUMER span for a batch that arrived together — a Lambda's records.
|
|
141
|
+
* One message with a context is its child; several start a trace of their own
|
|
142
|
+
* that links to each message's producer, since a span has one parent.
|
|
143
|
+
*/
|
|
144
|
+
function consumeBatchTraced(target, carriers, fn) {
|
|
145
|
+
const contexts = carriers.map(extractTraceContext).filter((c) => c !== void 0);
|
|
146
|
+
if (carriers.length === 1 && contexts.length === 1) return runConsumer(target, contexts[0], [], fn);
|
|
147
|
+
const links = contexts.map((c) => ({ context: trace.getSpanContext(c) }));
|
|
148
|
+
return runConsumer(target, ROOT_CONTEXT, links, fn);
|
|
149
|
+
}
|
|
150
|
+
function runConsumer(target, parent, links, fn) {
|
|
151
|
+
const span = tracer().startSpan(`${target.destination} process`, {
|
|
152
|
+
kind: SpanKind.CONSUMER,
|
|
153
|
+
attributes: attributes(target, "process"),
|
|
154
|
+
links
|
|
155
|
+
}, parent);
|
|
156
|
+
return context.with(trace.setSpan(parent, span), async () => {
|
|
157
|
+
try {
|
|
158
|
+
return await fn();
|
|
159
|
+
} catch (error) {
|
|
160
|
+
recordError(span, error);
|
|
161
|
+
throw error;
|
|
162
|
+
} finally {
|
|
163
|
+
span.end();
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
function recordError(span, error) {
|
|
168
|
+
if (error instanceof Error) span.recordException(error);
|
|
169
|
+
span.setStatus({
|
|
170
|
+
code: SpanStatusCode.ERROR,
|
|
171
|
+
...error instanceof Error && { message: error.message }
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* A carrier from broker message attributes, in any of the shapes they arrive
|
|
176
|
+
* in: SQS's `{ StringValue }`, a Lambda SQS record's `{ stringValue }`, an SNS
|
|
177
|
+
* envelope's `{ Value }`, or plain strings (RabbitMQ headers).
|
|
178
|
+
*/
|
|
179
|
+
function carrierFromAttributes(attributes) {
|
|
180
|
+
if (!attributes) return void 0;
|
|
181
|
+
const carrier = {};
|
|
182
|
+
for (const field of PROPAGATION_FIELDS) {
|
|
183
|
+
const value = attributeValue(attributes[field]);
|
|
184
|
+
if (value) carrier[field] = value;
|
|
185
|
+
}
|
|
186
|
+
return Object.keys(carrier).length > 0 ? carrier : void 0;
|
|
187
|
+
}
|
|
188
|
+
/** The W3C fields; what the default propagator reads and writes. */
|
|
189
|
+
const PROPAGATION_FIELDS = ["traceparent", "tracestate"];
|
|
190
|
+
function attributeValue(value) {
|
|
191
|
+
if (typeof value === "string") return value;
|
|
192
|
+
if (Buffer.isBuffer(value)) return value.toString();
|
|
193
|
+
if (value && typeof value === "object") {
|
|
194
|
+
const v = value;
|
|
195
|
+
const found = v.StringValue ?? v.stringValue ?? v.Value;
|
|
196
|
+
return typeof found === "string" ? found : void 0;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/** A carrier as SQS/SNS message attributes. */
|
|
200
|
+
function carrierAsAttributes(carrier) {
|
|
201
|
+
const attributes = {};
|
|
202
|
+
if (!carrier) return attributes;
|
|
203
|
+
for (const [key, value] of Object.entries(carrier)) attributes[key] = {
|
|
204
|
+
DataType: "String",
|
|
205
|
+
StringValue: value
|
|
206
|
+
};
|
|
207
|
+
return attributes;
|
|
208
|
+
}
|
|
209
|
+
function isPlainObject(value) {
|
|
210
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
211
|
+
const proto = Object.getPrototypeOf(value);
|
|
212
|
+
return proto === Object.prototype || proto === null;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* `data` with the carrier under {@link TRACE_CONTEXT_KEY}, for a broker with no
|
|
216
|
+
* header field. Unchanged when there is no carrier, or when `data` is not a
|
|
217
|
+
* plain object a key can be added to — such a message starts its own trace.
|
|
218
|
+
*/
|
|
219
|
+
function withTraceKey(data, carrier) {
|
|
220
|
+
if (!carrier || !isPlainObject(data)) return data;
|
|
221
|
+
return {
|
|
222
|
+
...data,
|
|
223
|
+
[TRACE_CONTEXT_KEY]: carrier
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* `data` without {@link TRACE_CONTEXT_KEY}, and the carrier that was under it.
|
|
228
|
+
* What a handler sees never has the key.
|
|
229
|
+
*/
|
|
230
|
+
function withoutTraceKey(data) {
|
|
231
|
+
if (!isPlainObject(data) || !("__gkmTrace" in data)) return {
|
|
232
|
+
data,
|
|
233
|
+
carrier: void 0
|
|
234
|
+
};
|
|
235
|
+
const { [TRACE_CONTEXT_KEY]: raw, ...rest } = data;
|
|
236
|
+
return {
|
|
237
|
+
data: rest,
|
|
238
|
+
carrier: isPlainObject(raw) ? carrierFromAttributes(raw) : void 0
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
//#endregion
|
|
242
|
+
export { consumeTraced as a, withTraceKey as c, UnsupportedEventTransport as d, consumeBatchTraced as i, withoutTraceKey as l, carrierAsAttributes as n, extractTraceContext as o, carrierFromAttributes as r, publishTraced as s, TRACE_CONTEXT_KEY as t, EventPublisherType as u };
|
|
243
|
+
|
|
244
|
+
//# sourceMappingURL=telemetry-DjjgJMul.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry-DjjgJMul.mjs","names":[],"sources":["../src/types.ts","../src/telemetry.ts"],"sourcesContent":["import type { InferStandardSchema } from '@geekmidas/schema';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\n\n/**\n * Represents a publishable event message with a type and payload.\n *\n * @template TType - The event type/name (e.g., 'user.created')\n * @template TPayload - The event payload data\n */\nexport type PublishableMessage<TType extends string, TPayload> = {\n\ttype: TType;\n\tpayload: TPayload;\n};\n\n/**\n * Event publisher/subscriber types\n */\nexport enum EventPublisherType {\n\tBasic = 'basic',\n\tEventBridge = 'eventbridge',\n\tSQS = 'sqs',\n\tSNS = 'sns',\n\tKafka = 'kafka',\n\tRabbitMQ = 'rabbitmq',\n\tPgBoss = 'pgboss',\n}\n\n/** The transports a publisher, subscriber or connection can be built for. */\nconst SUPPORTED: readonly string[] = [\n\tEventPublisherType.Basic,\n\tEventPublisherType.SQS,\n\tEventPublisherType.SNS,\n\tEventPublisherType.RabbitMQ,\n\tEventPublisherType.PgBoss,\n];\n\n/**\n * A connection string or connection whose transport nothing here implements —\n * a typo'd scheme, or one of the listed-but-unbuilt ones (EventBridge, Kafka).\n */\nexport class UnsupportedEventTransport extends Error {\n\tconstructor(\n\t\treadonly transport: string,\n\t\treadonly role: 'publisher' | 'subscriber' | 'connection',\n\t) {\n\t\tsuper(\n\t\t\t`No event ${role} for \"${transport}\". Use one of: ${SUPPORTED.join(', ')} — e.g. pgboss://… or sns://….`,\n\t\t);\n\t\tthis.name = 'UnsupportedEventTransport';\n\t}\n}\n\n/**\n * Base interface for event connections\n * Connections manage the underlying transport (RabbitMQ channel, SQS client, etc.)\n */\nexport interface EventConnection {\n\treadonly type: EventPublisherType;\n\tconnect(): Promise<void>;\n\tclose(): Promise<void>;\n\tisConnected(): boolean;\n}\n\n/**\n * Interface for event publishers that handle the actual publishing of events.\n * Implementations can send to EventBridge, SQS, Kafka, or any other event system.\n *\n * @template TMessage - The union type of all publishable messages\n */\nexport type EventPublisher<TMessage extends PublishableMessage<string, any>> = {\n\tpublish: (message: TMessage[]) => Promise<void>;\n};\n\n/**\n * Interface for event subscribers that handle receiving and processing events.\n *\n * @template TMessage - The union type of all publishable messages\n */\nexport type EventSubscriber<TMessage extends PublishableMessage<string, any>> =\n\t{\n\t\tsubscribe: (\n\t\t\tmessages: TMessage['type'][],\n\t\t\tlistener: (payload: TMessage) => Promise<void>,\n\t\t) => Promise<void>;\n\t};\n/**\n * Utility type to extract the message from EventPublisher\n */\nexport type ExtractPublisherMessage<T> =\n\tT extends EventPublisher<infer M> ? M : never;\n\nexport type MappedEvent<\n\tT extends EventPublisher<any> | undefined,\n\tOutSchema extends StandardSchemaV1 | undefined = undefined,\n> = {\n\ttype: ExtractPublisherMessage<T>['type'];\n\tpayload: (\n\t\tctx: InferStandardSchema<OutSchema>,\n\t) => ExtractPublisherMessage<T>['payload'];\n\twhen?: (ctx: InferStandardSchema<OutSchema>) => boolean;\n};\n","/**\n * Trace context across a broker: what makes request → queue → worker one trace.\n *\n * On publish, each message gets a PRODUCER span, and that span's context is\n * written into the message through the global propagator — into the broker's\n * own header or attribute field where it has one. On consume, the context is\n * read back and the handler runs inside a CONSUMER span that is its child. A\n * message carrying no context (an older publisher, a process with telemetry\n * off) starts a new trace instead.\n *\n * Everything goes through `@opentelemetry/api`'s globals. Without a registered\n * provider and propagator the spans are no-ops and nothing is written into a\n * message, so a process that never asked for telemetry publishes exactly what\n * it did before.\n *\n * @module\n */\nimport {\n\ttype Attributes,\n\ttype Context,\n\tcontext,\n\ttype Link,\n\tpropagation,\n\tROOT_CONTEXT,\n\ttype Span,\n\tSpanKind,\n\tSpanStatusCode,\n\ttrace,\n} from '@opentelemetry/api';\n\n/**\n * The key a trace context travels under where a broker has no header field of\n * its own — a pg-boss job's data. Reserved: it is removed before a handler sees\n * the payload, and a payload of the application's own should not use it.\n */\nexport const TRACE_CONTEXT_KEY = '__gkmTrace';\n\n/** A propagated trace context: `traceparent`, and `tracestate` when set. */\nexport type TraceCarrier = Record<string, string>;\n\n/** What a message is, as its spans describe it. */\nexport interface MessagingTarget {\n\t/** `messaging.system`: `pgboss`, `aws_sqs`, `aws_sns`, `rabbitmq`, `basic`. */\n\tsystem: string;\n\t/** The queue or topic — `messaging.destination.name`. */\n\tdestination: string;\n\t/** The event's type, when the message has one. */\n\ttype?: string;\n\t/** The broker's id for the message, when it has one. */\n\tmessageId?: string;\n}\n\nconst TRACER = '@geekmidas/events';\n\nfunction tracer() {\n\treturn trace.getTracer(TRACER);\n}\n\nfunction attributes(\n\ttarget: MessagingTarget,\n\toperation: 'publish' | 'process',\n): Attributes {\n\treturn {\n\t\t'messaging.system': target.system,\n\t\t'messaging.destination.name': target.destination,\n\t\t'messaging.operation': operation,\n\t\t'messaging.operation.type': operation === 'publish' ? 'send' : 'process',\n\t\t...(target.type && { 'gkm.event.type': target.type }),\n\t\t...(target.messageId && { 'messaging.message.id': target.messageId }),\n\t};\n}\n\n/** A PRODUCER span for one message, and the context to send with it. */\nexport interface ProducedMessage {\n\tspan: Span;\n\t/** Undefined when there is nothing to propagate: no propagator registered. */\n\tcarrier: TraceCarrier | undefined;\n}\n\n/**\n * Start the PRODUCER span for one message, as a child of whatever is active,\n * and write its context into a carrier for the broker.\n */\nexport function startProducer(target: MessagingTarget): ProducedMessage {\n\tconst active = context.active();\n\tconst span = tracer().startSpan(\n\t\t`${target.destination} publish`,\n\t\t{ kind: SpanKind.PRODUCER, attributes: attributes(target, 'publish') },\n\t\tactive,\n\t);\n\n\tconst carrier: TraceCarrier = {};\n\tpropagation.inject(trace.setSpan(active, span), carrier);\n\n\treturn {\n\t\tspan,\n\t\tcarrier: Object.keys(carrier).length > 0 ? carrier : undefined,\n\t};\n}\n\n/** End producer spans, recording `error` on each when the send failed. */\nexport function endProducers(spans: readonly Span[], error?: unknown): void {\n\tfor (const span of spans) {\n\t\tif (error !== undefined) recordError(span, error);\n\t\tspan.end();\n\t}\n}\n\n/**\n * Publish `items`, each with a PRODUCER span of its own, through `send` —\n * which receives the carrier to attach to each item, in order.\n */\nexport async function publishTraced<T, R>(\n\titems: readonly T[],\n\tdescribe: (item: T) => MessagingTarget,\n\tsend: (carriers: (TraceCarrier | undefined)[]) => Promise<R>,\n): Promise<R> {\n\tconst produced = items.map((item) => startProducer(describe(item)));\n\tconst spans = produced.map((p) => p.span);\n\ttry {\n\t\tconst result = await send(produced.map((p) => p.carrier));\n\t\tendProducers(spans);\n\t\treturn result;\n\t} catch (error) {\n\t\tendProducers(spans, error);\n\t\tthrow error;\n\t}\n}\n\n/**\n * The context a carrier holds, or undefined when it holds none — no carrier, a\n * malformed `traceparent`, or a propagator that throws.\n */\nexport function extractTraceContext(\n\tcarrier: TraceCarrier | undefined,\n): Context | undefined {\n\tif (!carrier || Object.keys(carrier).length === 0) return undefined;\n\ttry {\n\t\tconst extracted = propagation.extract(ROOT_CONTEXT, carrier);\n\t\treturn trace.getSpanContext(extracted) ? extracted : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Run `fn` inside a CONSUMER span for one message: a child of the context the\n * message carried, or a new trace when it carried none. The active context of\n * the poller that received it is never the parent — a job is not part of\n * whatever happened to start the poll loop.\n */\nexport function consumeTraced<R>(\n\ttarget: MessagingTarget,\n\tcarrier: TraceCarrier | undefined,\n\tfn: () => Promise<R>,\n): Promise<R> {\n\tconst parent = extractTraceContext(carrier) ?? ROOT_CONTEXT;\n\treturn runConsumer(target, parent, [], fn);\n}\n\n/**\n * One CONSUMER span for a batch that arrived together — a Lambda's records.\n * One message with a context is its child; several start a trace of their own\n * that links to each message's producer, since a span has one parent.\n */\nexport function consumeBatchTraced<R>(\n\ttarget: MessagingTarget,\n\tcarriers: readonly (TraceCarrier | undefined)[],\n\tfn: () => Promise<R>,\n): Promise<R> {\n\tconst contexts = carriers\n\t\t.map(extractTraceContext)\n\t\t.filter((c): c is Context => c !== undefined);\n\n\tif (carriers.length === 1 && contexts.length === 1) {\n\t\treturn runConsumer(target, contexts[0]!, [], fn);\n\t}\n\n\tconst links: Link[] = contexts.map((c) => ({\n\t\tcontext: trace.getSpanContext(c)!,\n\t}));\n\treturn runConsumer(target, ROOT_CONTEXT, links, fn);\n}\n\nfunction runConsumer<R>(\n\ttarget: MessagingTarget,\n\tparent: Context,\n\tlinks: Link[],\n\tfn: () => Promise<R>,\n): Promise<R> {\n\tconst span = tracer().startSpan(\n\t\t`${target.destination} process`,\n\t\t{\n\t\t\tkind: SpanKind.CONSUMER,\n\t\t\tattributes: attributes(target, 'process'),\n\t\t\tlinks,\n\t\t},\n\t\tparent,\n\t);\n\n\treturn context.with(trace.setSpan(parent, span), async () => {\n\t\ttry {\n\t\t\treturn await fn();\n\t\t} catch (error) {\n\t\t\trecordError(span, error);\n\t\t\tthrow error;\n\t\t} finally {\n\t\t\tspan.end();\n\t\t}\n\t});\n}\n\nfunction recordError(span: Span, error: unknown): void {\n\tif (error instanceof Error) span.recordException(error);\n\tspan.setStatus({\n\t\tcode: SpanStatusCode.ERROR,\n\t\t...(error instanceof Error && { message: error.message }),\n\t});\n}\n\n/**\n * A carrier from broker message attributes, in any of the shapes they arrive\n * in: SQS's `{ StringValue }`, a Lambda SQS record's `{ stringValue }`, an SNS\n * envelope's `{ Value }`, or plain strings (RabbitMQ headers).\n */\nexport function carrierFromAttributes(\n\tattributes: Record<string, unknown> | undefined | null,\n): TraceCarrier | undefined {\n\tif (!attributes) return undefined;\n\tconst carrier: TraceCarrier = {};\n\tfor (const field of PROPAGATION_FIELDS) {\n\t\tconst value = attributeValue(attributes[field]);\n\t\tif (value) carrier[field] = value;\n\t}\n\treturn Object.keys(carrier).length > 0 ? carrier : undefined;\n}\n\n/** The W3C fields; what the default propagator reads and writes. */\nconst PROPAGATION_FIELDS = ['traceparent', 'tracestate'] as const;\n\nfunction attributeValue(value: unknown): string | undefined {\n\tif (typeof value === 'string') return value;\n\tif (Buffer.isBuffer(value)) return value.toString();\n\tif (value && typeof value === 'object') {\n\t\tconst v = value as {\n\t\t\tValue?: unknown;\n\t\t\tStringValue?: unknown;\n\t\t\tstringValue?: unknown;\n\t\t};\n\t\tconst found = v.StringValue ?? v.stringValue ?? v.Value;\n\t\treturn typeof found === 'string' ? found : undefined;\n\t}\n\treturn undefined;\n}\n\n/** A carrier as SQS/SNS message attributes. */\nexport function carrierAsAttributes(\n\tcarrier: TraceCarrier | undefined,\n): Record<string, { DataType: 'String'; StringValue: string }> {\n\tconst attributes: Record<\n\t\tstring,\n\t\t{ DataType: 'String'; StringValue: string }\n\t> = {};\n\tif (!carrier) return attributes;\n\tfor (const [key, value] of Object.entries(carrier)) {\n\t\tattributes[key] = { DataType: 'String', StringValue: value };\n\t}\n\treturn attributes;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\tif (!value || typeof value !== 'object' || Array.isArray(value)) return false;\n\tconst proto = Object.getPrototypeOf(value);\n\treturn proto === Object.prototype || proto === null;\n}\n\n/**\n * `data` with the carrier under {@link TRACE_CONTEXT_KEY}, for a broker with no\n * header field. Unchanged when there is no carrier, or when `data` is not a\n * plain object a key can be added to — such a message starts its own trace.\n */\nexport function withTraceKey<T>(data: T, carrier: TraceCarrier | undefined): T {\n\tif (!carrier || !isPlainObject(data)) return data;\n\treturn { ...data, [TRACE_CONTEXT_KEY]: carrier } as T;\n}\n\n/**\n * `data` without {@link TRACE_CONTEXT_KEY}, and the carrier that was under it.\n * What a handler sees never has the key.\n */\nexport function withoutTraceKey<T>(data: T): {\n\tdata: T;\n\tcarrier: TraceCarrier | undefined;\n} {\n\tif (!isPlainObject(data) || !(TRACE_CONTEXT_KEY in data)) {\n\t\treturn { data, carrier: undefined };\n\t}\n\tconst { [TRACE_CONTEXT_KEY]: raw, ...rest } = data;\n\treturn {\n\t\tdata: rest as T,\n\t\tcarrier: isPlainObject(raw) ? carrierFromAttributes(raw) : undefined,\n\t};\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geekmidas/events",
|
|
3
|
-
"version": "10.0.0-alpha.
|
|
3
|
+
"version": "10.0.0-alpha.80",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -96,6 +96,7 @@
|
|
|
96
96
|
"access": "public"
|
|
97
97
|
},
|
|
98
98
|
"dependencies": {
|
|
99
|
+
"@opentelemetry/api": "~1.9.1",
|
|
99
100
|
"lodash.chunk": "~4.2.0"
|
|
100
101
|
},
|
|
101
102
|
"peerDependencies": {
|
|
@@ -103,7 +104,7 @@
|
|
|
103
104
|
"@aws-sdk/client-sqs": ">=3.1136.0",
|
|
104
105
|
"@aws-sdk/client-sns": ">=3.1136.0",
|
|
105
106
|
"pg-boss": ">=12.33.3",
|
|
106
|
-
"@geekmidas/schema": "^10.0.0-alpha.
|
|
107
|
+
"@geekmidas/schema": "^10.0.0-alpha.80"
|
|
107
108
|
},
|
|
108
109
|
"peerDependenciesMeta": {
|
|
109
110
|
"@geekmidas/schema": {
|
|
@@ -123,12 +124,13 @@
|
|
|
123
124
|
}
|
|
124
125
|
},
|
|
125
126
|
"devDependencies": {
|
|
127
|
+
"@opentelemetry/sdk-trace-node": "~2.11.0",
|
|
126
128
|
"@types/amqplib": "~0.10.8",
|
|
127
129
|
"@types/lodash.chunk": "~4.2.9",
|
|
128
130
|
"@types/pg": "~8.23.1",
|
|
129
131
|
"pg": "~8.23.0",
|
|
130
132
|
"pg-boss": "~12.33.3",
|
|
131
|
-
"@geekmidas/schema": "^10.0.0-alpha.
|
|
133
|
+
"@geekmidas/schema": "^10.0.0-alpha.80"
|
|
132
134
|
},
|
|
133
135
|
"scripts": {
|
|
134
136
|
"build": "tsdown --config ../../tsdown.config.ts --filter ."
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SQSSubscriber-BXLE_1L3.mjs","names":["sqsUrl.parse"],"sources":["../src/sqs/SQSConnection.ts","../src/sqs/SQSSubscriber.ts"],"sourcesContent":["import { SQSClient, type SQSClientConfig } from '@aws-sdk/client-sqs';\nimport type { EventConnection } from '../types';\nimport { EventPublisherType } from '../types';\nimport * as sqsUrl from './sqsUrl';\n\nexport interface SQSConnectionConfig {\n\tqueueUrl: string;\n\tregion?: string;\n\tendpoint?: string; // Custom endpoint (e.g., for LocalStack)\n\tcredentials?: {\n\t\taccessKeyId: string;\n\t\tsecretAccessKey: string;\n\t\tsessionToken?: string;\n\t};\n}\n\nexport class SQSConnection implements EventConnection {\n\treadonly type = EventPublisherType.SQS;\n\tprivate client: SQSClient;\n\tprivate connected = false;\n\n\tconstructor(readonly config: SQSConnectionConfig) {\n\t\tthis.config = config;\n\n\t\tconst clientConfig: SQSClientConfig = {\n\t\t\tregion: config.region,\n\t\t\tendpoint: config.endpoint,\n\t\t\tcredentials: config.credentials,\n\t\t};\n\n\t\tthis.client = new SQSClient(clientConfig);\n\t}\n\n\t/**\n\t * Create an SQSConnection from a connection string\n\t * Format: sqs://?queueUrl=https://sqs.region.amazonaws.com/accountId/queueName®ion=us-east-1&endpoint=http://localhost:4566\n\t */\n\tstatic async fromConnectionString(\n\t\tconnectionString: string,\n\t): Promise<SQSConnection> {\n\t\t// Through the codec, not a second hand-rolled parse: the deploy target\n\t\t// composes these with `sqsUrl.build`, and a string it cannot read back is\n\t\t// exactly the drift one shared module rules out.\n\t\tconst { queueUrl, region, endpoint } = sqsUrl.parse(connectionString);\n\t\tconst params = new URL(connectionString).searchParams;\n\n\t\tconst config: SQSConnectionConfig = {\n\t\t\tqueueUrl,\n\t\t\tregion,\n\t\t\tendpoint,\n\t\t};\n\n\t\t// Credentials are not part of the address — see `sqsUrl` — but a caller\n\t\t// may still pin them here, so they are read beside it rather than in it.\n\t\tconst accessKeyId = params.get('accessKeyId');\n\t\tconst secretAccessKey = params.get('secretAccessKey');\n\t\tif (accessKeyId && secretAccessKey) {\n\t\t\tconfig.credentials = {\n\t\t\t\taccessKeyId,\n\t\t\t\tsecretAccessKey,\n\t\t\t\tsessionToken: params.get('sessionToken') || undefined,\n\t\t\t};\n\t\t}\n\n\t\tconst connection = new SQSConnection(config);\n\t\tawait connection.connect();\n\t\treturn connection;\n\t}\n\n\tasync connect(): Promise<void> {\n\t\t// SQS doesn't have a traditional connection, but we mark as connected\n\t\tthis.connected = true;\n\t}\n\n\tasync close(): Promise<void> {\n\t\tthis.client.destroy();\n\t\tthis.connected = false;\n\t}\n\n\tisConnected(): boolean {\n\t\treturn this.connected;\n\t}\n\n\t/**\n\t * Get the underlying SQS client\n\t */\n\tget sqsClient(): SQSClient {\n\t\treturn this.client;\n\t}\n\n\t/**\n\t * Get the queue URL for this connection\n\t */\n\tget queueUrl(): string {\n\t\treturn this.config.queueUrl;\n\t}\n}\n","import {\n\tDeleteMessageCommand,\n\ttype Message,\n\tReceiveMessageCommand,\n} from '@aws-sdk/client-sqs';\nimport type { EventSubscriber, PublishableMessage } from '../types';\nimport type { SQSConnection } from './SQSConnection';\n\nexport interface SQSSubscriberOptions {\n\twaitTimeSeconds?: number; // SQS long polling (default: 20)\n\tmaxMessages?: number; // Max messages per poll (default: 10)\n\texpectedTopicArn?: string; // Optional: verify messages are from specific SNS topic\n}\n\n/**\n * SQS Subscriber - receives messages from an SQS queue\n *\n * This subscriber can receive:\n * 1. Direct SQS messages\n * 2. SNS messages that were pushed to the queue (SNS → SQS pattern)\n *\n * When receiving SNS messages, it:\n * - Automatically detects and parses the SNS wrapper format\n * - Filters by TopicArn (if expectedTopicArn is provided)\n * - Filters by message type\n * - Processes the actual message payload\n */\nexport class SQSSubscriber<TMessage extends PublishableMessage<string, any>>\n\timplements EventSubscriber<TMessage>\n{\n\tprivate polling = false;\n\tprivate messageTypes: Set<TMessage['type']> = new Set();\n\tprivate listener?: (payload: TMessage) => Promise<void>;\n\tprivate options: Required<SQSSubscriberOptions>;\n\n\tconstructor(\n\t\tprivate connection: SQSConnection,\n\t\toptions: SQSSubscriberOptions = {},\n\t) {\n\t\tthis.options = {\n\t\t\twaitTimeSeconds: options.waitTimeSeconds ?? 20,\n\t\t\tmaxMessages: options.maxMessages ?? 10,\n\t\t\texpectedTopicArn: options.expectedTopicArn ?? '',\n\t\t};\n\t}\n\n\tasync subscribe(\n\t\tmessages: TMessage['type'][],\n\t\tlistener: (payload: TMessage) => Promise<void>,\n\t): Promise<void> {\n\t\tthis.messageTypes = new Set(messages);\n\t\tthis.listener = listener;\n\n\t\tif (!this.connection.isConnected()) {\n\t\t\tawait this.connection.connect();\n\t\t}\n\n\t\tthis.polling = true;\n\t\tthis.poll();\n\t}\n\n\tstop(): void {\n\t\tthis.polling = false;\n\t}\n\n\tprivate async poll(): Promise<void> {\n\t\twhile (this.polling) {\n\t\t\ttry {\n\t\t\t\tconst command = new ReceiveMessageCommand({\n\t\t\t\t\tQueueUrl: this.connection.queueUrl,\n\t\t\t\t\tMaxNumberOfMessages: this.options.maxMessages,\n\t\t\t\t\tWaitTimeSeconds: this.options.waitTimeSeconds,\n\t\t\t\t\tMessageAttributeNames: ['All'],\n\t\t\t\t});\n\n\t\t\t\tconst response = await this.connection.sqsClient.send(command);\n\n\t\t\t\tif (response.Messages && response.Messages.length > 0) {\n\t\t\t\t\tawait this.processMessages(response.Messages);\n\t\t\t\t}\n\t\t\t} catch (_error) {\n\t\t\t\tawait this.sleep(1000);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async processMessages(messages: Message[]): Promise<void> {\n\t\tfor (const message of messages) {\n\t\t\ttry {\n\t\t\t\tif (!message.Body) continue;\n\n\t\t\t\t// Try to parse as SNS message first\n\t\t\t\tconst parsedMessage = this.parseMessage(message.Body);\n\n\t\t\t\tif (!parsedMessage) {\n\t\t\t\t\t// Not a valid message format\n\t\t\t\t\tawait this.deleteMessage(message.ReceiptHandle!);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst { payload, messageType, topicArn } = parsedMessage;\n\n\t\t\t\t// If expectedTopicArn is set, verify the message is from that topic\n\t\t\t\tif (this.options.expectedTopicArn && topicArn) {\n\t\t\t\t\tif (topicArn !== this.options.expectedTopicArn) {\n\t\t\t\t\t\tawait this.deleteMessage(message.ReceiptHandle!);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Check if we're subscribed to this message type\n\t\t\t\tif (\n\t\t\t\t\tmessageType &&\n\t\t\t\t\tthis.messageTypes.has(messageType as TMessage['type'])\n\t\t\t\t) {\n\t\t\t\t\t// Call listener\n\t\t\t\t\tif (this.listener) {\n\t\t\t\t\t\tawait this.listener(payload as TMessage);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Delete message after successful processing\n\t\t\t\t\tawait this.deleteMessage(message.ReceiptHandle!);\n\t\t\t\t} else {\n\t\t\t\t\t// Delete messages we're not subscribed to\n\t\t\t\t\tawait this.deleteMessage(message.ReceiptHandle!);\n\t\t\t\t}\n\t\t\t} catch (_error) {\n\t\t\t\t// Message will become visible again\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Parse message body - handles both direct SQS messages and SNS-wrapped messages\n\t */\n\tprivate parseMessage(body: string): {\n\t\tpayload: unknown;\n\t\tmessageType: string | undefined;\n\t\ttopicArn?: string;\n\t} | null {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(body);\n\n\t\t\t// Check if this is an SNS notification wrapper\n\t\t\tif (parsed.Type === 'Notification' && parsed.Message) {\n\t\t\t\t// This is an SNS message\n\t\t\t\tconst snsMessage = JSON.parse(parsed.Message);\n\t\t\t\tconst messageType =\n\t\t\t\t\tparsed.MessageAttributes?.type?.Value || snsMessage.type;\n\n\t\t\t\treturn {\n\t\t\t\t\tpayload: snsMessage,\n\t\t\t\t\tmessageType,\n\t\t\t\t\ttopicArn: parsed.TopicArn,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// This is a direct SQS message\n\t\t\treturn {\n\t\t\t\tpayload: parsed,\n\t\t\t\tmessageType: parsed.type,\n\t\t\t};\n\t\t} catch (_error) {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate async deleteMessage(receiptHandle: string): Promise<void> {\n\t\ttry {\n\t\t\tconst command = new DeleteMessageCommand({\n\t\t\t\tQueueUrl: this.connection.queueUrl,\n\t\t\t\tReceiptHandle: receiptHandle,\n\t\t\t});\n\t\t\tawait this.connection.sqsClient.send(command);\n\t\t} catch (_error) {}\n\t}\n\n\tprivate sleep(ms: number): Promise<void> {\n\t\treturn new Promise((resolve) => setTimeout(resolve, ms));\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SQSSubscriber-DtRUaZB3.cjs","names":["SQSClient","ReceiveMessageCommand","DeleteMessageCommand"],"sources":["../src/sqs/SQSConnection.ts","../src/sqs/SQSSubscriber.ts"],"sourcesContent":["import { SQSClient, type SQSClientConfig } from '@aws-sdk/client-sqs';\nimport type { EventConnection } from '../types';\nimport { EventPublisherType } from '../types';\nimport * as sqsUrl from './sqsUrl';\n\nexport interface SQSConnectionConfig {\n\tqueueUrl: string;\n\tregion?: string;\n\tendpoint?: string; // Custom endpoint (e.g., for LocalStack)\n\tcredentials?: {\n\t\taccessKeyId: string;\n\t\tsecretAccessKey: string;\n\t\tsessionToken?: string;\n\t};\n}\n\nexport class SQSConnection implements EventConnection {\n\treadonly type = EventPublisherType.SQS;\n\tprivate client: SQSClient;\n\tprivate connected = false;\n\n\tconstructor(readonly config: SQSConnectionConfig) {\n\t\tthis.config = config;\n\n\t\tconst clientConfig: SQSClientConfig = {\n\t\t\tregion: config.region,\n\t\t\tendpoint: config.endpoint,\n\t\t\tcredentials: config.credentials,\n\t\t};\n\n\t\tthis.client = new SQSClient(clientConfig);\n\t}\n\n\t/**\n\t * Create an SQSConnection from a connection string\n\t * Format: sqs://?queueUrl=https://sqs.region.amazonaws.com/accountId/queueName®ion=us-east-1&endpoint=http://localhost:4566\n\t */\n\tstatic async fromConnectionString(\n\t\tconnectionString: string,\n\t): Promise<SQSConnection> {\n\t\t// Through the codec, not a second hand-rolled parse: the deploy target\n\t\t// composes these with `sqsUrl.build`, and a string it cannot read back is\n\t\t// exactly the drift one shared module rules out.\n\t\tconst { queueUrl, region, endpoint } = sqsUrl.parse(connectionString);\n\t\tconst params = new URL(connectionString).searchParams;\n\n\t\tconst config: SQSConnectionConfig = {\n\t\t\tqueueUrl,\n\t\t\tregion,\n\t\t\tendpoint,\n\t\t};\n\n\t\t// Credentials are not part of the address — see `sqsUrl` — but a caller\n\t\t// may still pin them here, so they are read beside it rather than in it.\n\t\tconst accessKeyId = params.get('accessKeyId');\n\t\tconst secretAccessKey = params.get('secretAccessKey');\n\t\tif (accessKeyId && secretAccessKey) {\n\t\t\tconfig.credentials = {\n\t\t\t\taccessKeyId,\n\t\t\t\tsecretAccessKey,\n\t\t\t\tsessionToken: params.get('sessionToken') || undefined,\n\t\t\t};\n\t\t}\n\n\t\tconst connection = new SQSConnection(config);\n\t\tawait connection.connect();\n\t\treturn connection;\n\t}\n\n\tasync connect(): Promise<void> {\n\t\t// SQS doesn't have a traditional connection, but we mark as connected\n\t\tthis.connected = true;\n\t}\n\n\tasync close(): Promise<void> {\n\t\tthis.client.destroy();\n\t\tthis.connected = false;\n\t}\n\n\tisConnected(): boolean {\n\t\treturn this.connected;\n\t}\n\n\t/**\n\t * Get the underlying SQS client\n\t */\n\tget sqsClient(): SQSClient {\n\t\treturn this.client;\n\t}\n\n\t/**\n\t * Get the queue URL for this connection\n\t */\n\tget queueUrl(): string {\n\t\treturn this.config.queueUrl;\n\t}\n}\n","import {\n\tDeleteMessageCommand,\n\ttype Message,\n\tReceiveMessageCommand,\n} from '@aws-sdk/client-sqs';\nimport type { EventSubscriber, PublishableMessage } from '../types';\nimport type { SQSConnection } from './SQSConnection';\n\nexport interface SQSSubscriberOptions {\n\twaitTimeSeconds?: number; // SQS long polling (default: 20)\n\tmaxMessages?: number; // Max messages per poll (default: 10)\n\texpectedTopicArn?: string; // Optional: verify messages are from specific SNS topic\n}\n\n/**\n * SQS Subscriber - receives messages from an SQS queue\n *\n * This subscriber can receive:\n * 1. Direct SQS messages\n * 2. SNS messages that were pushed to the queue (SNS → SQS pattern)\n *\n * When receiving SNS messages, it:\n * - Automatically detects and parses the SNS wrapper format\n * - Filters by TopicArn (if expectedTopicArn is provided)\n * - Filters by message type\n * - Processes the actual message payload\n */\nexport class SQSSubscriber<TMessage extends PublishableMessage<string, any>>\n\timplements EventSubscriber<TMessage>\n{\n\tprivate polling = false;\n\tprivate messageTypes: Set<TMessage['type']> = new Set();\n\tprivate listener?: (payload: TMessage) => Promise<void>;\n\tprivate options: Required<SQSSubscriberOptions>;\n\n\tconstructor(\n\t\tprivate connection: SQSConnection,\n\t\toptions: SQSSubscriberOptions = {},\n\t) {\n\t\tthis.options = {\n\t\t\twaitTimeSeconds: options.waitTimeSeconds ?? 20,\n\t\t\tmaxMessages: options.maxMessages ?? 10,\n\t\t\texpectedTopicArn: options.expectedTopicArn ?? '',\n\t\t};\n\t}\n\n\tasync subscribe(\n\t\tmessages: TMessage['type'][],\n\t\tlistener: (payload: TMessage) => Promise<void>,\n\t): Promise<void> {\n\t\tthis.messageTypes = new Set(messages);\n\t\tthis.listener = listener;\n\n\t\tif (!this.connection.isConnected()) {\n\t\t\tawait this.connection.connect();\n\t\t}\n\n\t\tthis.polling = true;\n\t\tthis.poll();\n\t}\n\n\tstop(): void {\n\t\tthis.polling = false;\n\t}\n\n\tprivate async poll(): Promise<void> {\n\t\twhile (this.polling) {\n\t\t\ttry {\n\t\t\t\tconst command = new ReceiveMessageCommand({\n\t\t\t\t\tQueueUrl: this.connection.queueUrl,\n\t\t\t\t\tMaxNumberOfMessages: this.options.maxMessages,\n\t\t\t\t\tWaitTimeSeconds: this.options.waitTimeSeconds,\n\t\t\t\t\tMessageAttributeNames: ['All'],\n\t\t\t\t});\n\n\t\t\t\tconst response = await this.connection.sqsClient.send(command);\n\n\t\t\t\tif (response.Messages && response.Messages.length > 0) {\n\t\t\t\t\tawait this.processMessages(response.Messages);\n\t\t\t\t}\n\t\t\t} catch (_error) {\n\t\t\t\tawait this.sleep(1000);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async processMessages(messages: Message[]): Promise<void> {\n\t\tfor (const message of messages) {\n\t\t\ttry {\n\t\t\t\tif (!message.Body) continue;\n\n\t\t\t\t// Try to parse as SNS message first\n\t\t\t\tconst parsedMessage = this.parseMessage(message.Body);\n\n\t\t\t\tif (!parsedMessage) {\n\t\t\t\t\t// Not a valid message format\n\t\t\t\t\tawait this.deleteMessage(message.ReceiptHandle!);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst { payload, messageType, topicArn } = parsedMessage;\n\n\t\t\t\t// If expectedTopicArn is set, verify the message is from that topic\n\t\t\t\tif (this.options.expectedTopicArn && topicArn) {\n\t\t\t\t\tif (topicArn !== this.options.expectedTopicArn) {\n\t\t\t\t\t\tawait this.deleteMessage(message.ReceiptHandle!);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Check if we're subscribed to this message type\n\t\t\t\tif (\n\t\t\t\t\tmessageType &&\n\t\t\t\t\tthis.messageTypes.has(messageType as TMessage['type'])\n\t\t\t\t) {\n\t\t\t\t\t// Call listener\n\t\t\t\t\tif (this.listener) {\n\t\t\t\t\t\tawait this.listener(payload as TMessage);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Delete message after successful processing\n\t\t\t\t\tawait this.deleteMessage(message.ReceiptHandle!);\n\t\t\t\t} else {\n\t\t\t\t\t// Delete messages we're not subscribed to\n\t\t\t\t\tawait this.deleteMessage(message.ReceiptHandle!);\n\t\t\t\t}\n\t\t\t} catch (_error) {\n\t\t\t\t// Message will become visible again\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Parse message body - handles both direct SQS messages and SNS-wrapped messages\n\t */\n\tprivate parseMessage(body: string): {\n\t\tpayload: unknown;\n\t\tmessageType: string | undefined;\n\t\ttopicArn?: string;\n\t} | null {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(body);\n\n\t\t\t// Check if this is an SNS notification wrapper\n\t\t\tif (parsed.Type === 'Notification' && parsed.Message) {\n\t\t\t\t// This is an SNS message\n\t\t\t\tconst snsMessage = JSON.parse(parsed.Message);\n\t\t\t\tconst messageType =\n\t\t\t\t\tparsed.MessageAttributes?.type?.Value || snsMessage.type;\n\n\t\t\t\treturn {\n\t\t\t\t\tpayload: snsMessage,\n\t\t\t\t\tmessageType,\n\t\t\t\t\ttopicArn: parsed.TopicArn,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// This is a direct SQS message\n\t\t\treturn {\n\t\t\t\tpayload: parsed,\n\t\t\t\tmessageType: parsed.type,\n\t\t\t};\n\t\t} catch (_error) {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate async deleteMessage(receiptHandle: string): Promise<void> {\n\t\ttry {\n\t\t\tconst command = new DeleteMessageCommand({\n\t\t\t\tQueueUrl: this.connection.queueUrl,\n\t\t\t\tReceiptHandle: receiptHandle,\n\t\t\t});\n\t\t\tawait this.connection.sqsClient.send(command);\n\t\t} catch (_error) {}\n\t}\n\n\tprivate sleep(ms: number): Promise<void> {\n\t\treturn new Promise((resolve) => setTimeout(resolve, ms));\n\t}\n}\n"],"mappings":"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"}
|
package/dist/types-BcZjLxVV.cjs
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
//#region src/types.ts
|
|
2
|
-
/**
|
|
3
|
-
* Event publisher/subscriber types
|
|
4
|
-
*/
|
|
5
|
-
let EventPublisherType = /* @__PURE__ */ function(EventPublisherType) {
|
|
6
|
-
EventPublisherType["Basic"] = "basic";
|
|
7
|
-
EventPublisherType["EventBridge"] = "eventbridge";
|
|
8
|
-
EventPublisherType["SQS"] = "sqs";
|
|
9
|
-
EventPublisherType["SNS"] = "sns";
|
|
10
|
-
EventPublisherType["Kafka"] = "kafka";
|
|
11
|
-
EventPublisherType["RabbitMQ"] = "rabbitmq";
|
|
12
|
-
EventPublisherType["PgBoss"] = "pgboss";
|
|
13
|
-
return EventPublisherType;
|
|
14
|
-
}({});
|
|
15
|
-
/** The transports a publisher, subscriber or connection can be built for. */
|
|
16
|
-
const SUPPORTED = [
|
|
17
|
-
"basic",
|
|
18
|
-
"sqs",
|
|
19
|
-
"sns",
|
|
20
|
-
"rabbitmq",
|
|
21
|
-
"pgboss"
|
|
22
|
-
];
|
|
23
|
-
/**
|
|
24
|
-
* A connection string or connection whose transport nothing here implements —
|
|
25
|
-
* a typo'd scheme, or one of the listed-but-unbuilt ones (EventBridge, Kafka).
|
|
26
|
-
*/
|
|
27
|
-
var UnsupportedEventTransport = class extends Error {
|
|
28
|
-
transport;
|
|
29
|
-
role;
|
|
30
|
-
constructor(transport, role) {
|
|
31
|
-
super(`No event ${role} for "${transport}". Use one of: ${SUPPORTED.join(", ")} — e.g. pgboss://… or sns://….`);
|
|
32
|
-
this.transport = transport;
|
|
33
|
-
this.role = role;
|
|
34
|
-
this.name = "UnsupportedEventTransport";
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
//#endregion
|
|
38
|
-
Object.defineProperty(exports, "EventPublisherType", {
|
|
39
|
-
enumerable: true,
|
|
40
|
-
get: function() {
|
|
41
|
-
return EventPublisherType;
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
Object.defineProperty(exports, "UnsupportedEventTransport", {
|
|
45
|
-
enumerable: true,
|
|
46
|
-
get: function() {
|
|
47
|
-
return UnsupportedEventTransport;
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
//# sourceMappingURL=types-BcZjLxVV.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types-BcZjLxVV.cjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["import type { InferStandardSchema } from '@geekmidas/schema';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\n\n/**\n * Represents a publishable event message with a type and payload.\n *\n * @template TType - The event type/name (e.g., 'user.created')\n * @template TPayload - The event payload data\n */\nexport type PublishableMessage<TType extends string, TPayload> = {\n\ttype: TType;\n\tpayload: TPayload;\n};\n\n/**\n * Event publisher/subscriber types\n */\nexport enum EventPublisherType {\n\tBasic = 'basic',\n\tEventBridge = 'eventbridge',\n\tSQS = 'sqs',\n\tSNS = 'sns',\n\tKafka = 'kafka',\n\tRabbitMQ = 'rabbitmq',\n\tPgBoss = 'pgboss',\n}\n\n/** The transports a publisher, subscriber or connection can be built for. */\nconst SUPPORTED: readonly string[] = [\n\tEventPublisherType.Basic,\n\tEventPublisherType.SQS,\n\tEventPublisherType.SNS,\n\tEventPublisherType.RabbitMQ,\n\tEventPublisherType.PgBoss,\n];\n\n/**\n * A connection string or connection whose transport nothing here implements —\n * a typo'd scheme, or one of the listed-but-unbuilt ones (EventBridge, Kafka).\n */\nexport class UnsupportedEventTransport extends Error {\n\tconstructor(\n\t\treadonly transport: string,\n\t\treadonly role: 'publisher' | 'subscriber' | 'connection',\n\t) {\n\t\tsuper(\n\t\t\t`No event ${role} for \"${transport}\". Use one of: ${SUPPORTED.join(', ')} — e.g. pgboss://… or sns://….`,\n\t\t);\n\t\tthis.name = 'UnsupportedEventTransport';\n\t}\n}\n\n/**\n * Base interface for event connections\n * Connections manage the underlying transport (RabbitMQ channel, SQS client, etc.)\n */\nexport interface EventConnection {\n\treadonly type: EventPublisherType;\n\tconnect(): Promise<void>;\n\tclose(): Promise<void>;\n\tisConnected(): boolean;\n}\n\n/**\n * Interface for event publishers that handle the actual publishing of events.\n * Implementations can send to EventBridge, SQS, Kafka, or any other event system.\n *\n * @template TMessage - The union type of all publishable messages\n */\nexport type EventPublisher<TMessage extends PublishableMessage<string, any>> = {\n\tpublish: (message: TMessage[]) => Promise<void>;\n};\n\n/**\n * Interface for event subscribers that handle receiving and processing events.\n *\n * @template TMessage - The union type of all publishable messages\n */\nexport type EventSubscriber<TMessage extends PublishableMessage<string, any>> =\n\t{\n\t\tsubscribe: (\n\t\t\tmessages: TMessage['type'][],\n\t\t\tlistener: (payload: TMessage) => Promise<void>,\n\t\t) => Promise<void>;\n\t};\n/**\n * Utility type to extract the message from EventPublisher\n */\nexport type ExtractPublisherMessage<T> =\n\tT extends EventPublisher<infer M> ? M : never;\n\nexport type MappedEvent<\n\tT extends EventPublisher<any> | undefined,\n\tOutSchema extends StandardSchemaV1 | undefined = undefined,\n> = {\n\ttype: ExtractPublisherMessage<T>['type'];\n\tpayload: (\n\t\tctx: InferStandardSchema<OutSchema>,\n\t) => ExtractPublisherMessage<T>['payload'];\n\twhen?: (ctx: InferStandardSchema<OutSchema>) => boolean;\n};\n"],"mappings":";;;;AAiBA,IAAY,qBAAL,yBAAA,oBAAA;CACN,mBAAA,WAAA;CACA,mBAAA,iBAAA;CACA,mBAAA,SAAA;CACA,mBAAA,SAAA;CACA,mBAAA,WAAA;CACA,mBAAA,cAAA;CACA,mBAAA,YAAA;;AACD,EAAA,CAAA,CAAA;;AAGA,MAAM,YAA+B;;;;;;AAMrC;;;;;AAMA,IAAa,4BAAb,cAA+C,MAAM;CAE1C;CACA;CAFV,YACC,WACA,MACC;EACD,MACC,YAAY,KAAK,QAAQ,UAAU,iBAAiB,UAAU,KAAK,IAAI,EAAE,+BAC1E;EALS,KAAA,YAAA;EACA,KAAA,OAAA;EAKT,KAAK,OAAO;CACb;AACD"}
|
package/dist/types-Bk5JM6MP.mjs
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
//#region src/types.ts
|
|
2
|
-
/**
|
|
3
|
-
* Event publisher/subscriber types
|
|
4
|
-
*/
|
|
5
|
-
let EventPublisherType = /* @__PURE__ */ function(EventPublisherType) {
|
|
6
|
-
EventPublisherType["Basic"] = "basic";
|
|
7
|
-
EventPublisherType["EventBridge"] = "eventbridge";
|
|
8
|
-
EventPublisherType["SQS"] = "sqs";
|
|
9
|
-
EventPublisherType["SNS"] = "sns";
|
|
10
|
-
EventPublisherType["Kafka"] = "kafka";
|
|
11
|
-
EventPublisherType["RabbitMQ"] = "rabbitmq";
|
|
12
|
-
EventPublisherType["PgBoss"] = "pgboss";
|
|
13
|
-
return EventPublisherType;
|
|
14
|
-
}({});
|
|
15
|
-
/** The transports a publisher, subscriber or connection can be built for. */
|
|
16
|
-
const SUPPORTED = [
|
|
17
|
-
"basic",
|
|
18
|
-
"sqs",
|
|
19
|
-
"sns",
|
|
20
|
-
"rabbitmq",
|
|
21
|
-
"pgboss"
|
|
22
|
-
];
|
|
23
|
-
/**
|
|
24
|
-
* A connection string or connection whose transport nothing here implements —
|
|
25
|
-
* a typo'd scheme, or one of the listed-but-unbuilt ones (EventBridge, Kafka).
|
|
26
|
-
*/
|
|
27
|
-
var UnsupportedEventTransport = class extends Error {
|
|
28
|
-
transport;
|
|
29
|
-
role;
|
|
30
|
-
constructor(transport, role) {
|
|
31
|
-
super(`No event ${role} for "${transport}". Use one of: ${SUPPORTED.join(", ")} — e.g. pgboss://… or sns://….`);
|
|
32
|
-
this.transport = transport;
|
|
33
|
-
this.role = role;
|
|
34
|
-
this.name = "UnsupportedEventTransport";
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
//#endregion
|
|
38
|
-
export { UnsupportedEventTransport as n, EventPublisherType as t };
|
|
39
|
-
|
|
40
|
-
//# sourceMappingURL=types-Bk5JM6MP.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types-Bk5JM6MP.mjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["import type { InferStandardSchema } from '@geekmidas/schema';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\n\n/**\n * Represents a publishable event message with a type and payload.\n *\n * @template TType - The event type/name (e.g., 'user.created')\n * @template TPayload - The event payload data\n */\nexport type PublishableMessage<TType extends string, TPayload> = {\n\ttype: TType;\n\tpayload: TPayload;\n};\n\n/**\n * Event publisher/subscriber types\n */\nexport enum EventPublisherType {\n\tBasic = 'basic',\n\tEventBridge = 'eventbridge',\n\tSQS = 'sqs',\n\tSNS = 'sns',\n\tKafka = 'kafka',\n\tRabbitMQ = 'rabbitmq',\n\tPgBoss = 'pgboss',\n}\n\n/** The transports a publisher, subscriber or connection can be built for. */\nconst SUPPORTED: readonly string[] = [\n\tEventPublisherType.Basic,\n\tEventPublisherType.SQS,\n\tEventPublisherType.SNS,\n\tEventPublisherType.RabbitMQ,\n\tEventPublisherType.PgBoss,\n];\n\n/**\n * A connection string or connection whose transport nothing here implements —\n * a typo'd scheme, or one of the listed-but-unbuilt ones (EventBridge, Kafka).\n */\nexport class UnsupportedEventTransport extends Error {\n\tconstructor(\n\t\treadonly transport: string,\n\t\treadonly role: 'publisher' | 'subscriber' | 'connection',\n\t) {\n\t\tsuper(\n\t\t\t`No event ${role} for \"${transport}\". Use one of: ${SUPPORTED.join(', ')} — e.g. pgboss://… or sns://….`,\n\t\t);\n\t\tthis.name = 'UnsupportedEventTransport';\n\t}\n}\n\n/**\n * Base interface for event connections\n * Connections manage the underlying transport (RabbitMQ channel, SQS client, etc.)\n */\nexport interface EventConnection {\n\treadonly type: EventPublisherType;\n\tconnect(): Promise<void>;\n\tclose(): Promise<void>;\n\tisConnected(): boolean;\n}\n\n/**\n * Interface for event publishers that handle the actual publishing of events.\n * Implementations can send to EventBridge, SQS, Kafka, or any other event system.\n *\n * @template TMessage - The union type of all publishable messages\n */\nexport type EventPublisher<TMessage extends PublishableMessage<string, any>> = {\n\tpublish: (message: TMessage[]) => Promise<void>;\n};\n\n/**\n * Interface for event subscribers that handle receiving and processing events.\n *\n * @template TMessage - The union type of all publishable messages\n */\nexport type EventSubscriber<TMessage extends PublishableMessage<string, any>> =\n\t{\n\t\tsubscribe: (\n\t\t\tmessages: TMessage['type'][],\n\t\t\tlistener: (payload: TMessage) => Promise<void>,\n\t\t) => Promise<void>;\n\t};\n/**\n * Utility type to extract the message from EventPublisher\n */\nexport type ExtractPublisherMessage<T> =\n\tT extends EventPublisher<infer M> ? M : never;\n\nexport type MappedEvent<\n\tT extends EventPublisher<any> | undefined,\n\tOutSchema extends StandardSchemaV1 | undefined = undefined,\n> = {\n\ttype: ExtractPublisherMessage<T>['type'];\n\tpayload: (\n\t\tctx: InferStandardSchema<OutSchema>,\n\t) => ExtractPublisherMessage<T>['payload'];\n\twhen?: (ctx: InferStandardSchema<OutSchema>) => boolean;\n};\n"],"mappings":";;;;AAiBA,IAAY,qBAAL,yBAAA,oBAAA;CACN,mBAAA,WAAA;CACA,mBAAA,iBAAA;CACA,mBAAA,SAAA;CACA,mBAAA,SAAA;CACA,mBAAA,WAAA;CACA,mBAAA,cAAA;CACA,mBAAA,YAAA;;AACD,EAAA,CAAA,CAAA;;AAGA,MAAM,YAA+B;;;;;;AAMrC;;;;;AAMA,IAAa,4BAAb,cAA+C,MAAM;CAE1C;CACA;CAFV,YACC,WACA,MACC;EACD,MACC,YAAY,KAAK,QAAQ,UAAU,iBAAiB,UAAU,KAAK,IAAI,EAAE,+BAC1E;EALS,KAAA,YAAA;EACA,KAAA,OAAA;EAKT,KAAK,OAAO;CACb;AACD"}
|