@fbsm/saga-core 0.0.1-beta.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/dist/index.js ADDED
@@ -0,0 +1,690 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/errors/saga.error.ts
9
+ var SagaError = class extends Error {
10
+ isSagaError = true;
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = "SagaError";
14
+ }
15
+ };
16
+
17
+ // src/errors/saga-retryable.error.ts
18
+ var SagaRetryableError = class extends SagaError {
19
+ constructor(message, maxRetries = 3) {
20
+ super(message);
21
+ this.maxRetries = maxRetries;
22
+ this.name = "SagaRetryableError";
23
+ }
24
+ };
25
+
26
+ // src/logger/saga-logger.ts
27
+ var ConsoleSagaLogger = class {
28
+ info(message, ...args) {
29
+ console.log(message, ...args);
30
+ }
31
+ warn(message, ...args) {
32
+ console.warn(message, ...args);
33
+ }
34
+ error(message, ...args) {
35
+ console.error(message, ...args);
36
+ }
37
+ };
38
+
39
+ // src/context/saga-context.ts
40
+ import { AsyncLocalStorage } from "async_hooks";
41
+
42
+ // src/errors/saga-context-not-found.error.ts
43
+ var SagaContextNotFoundError = class extends SagaError {
44
+ constructor() {
45
+ super(
46
+ "No saga context found. Ensure you are inside a saga handler or after sagaPublisher.start()."
47
+ );
48
+ this.name = "SagaContextNotFoundError";
49
+ }
50
+ };
51
+
52
+ // src/context/saga-context.ts
53
+ var SagaContext = class _SagaContext {
54
+ static storage = new AsyncLocalStorage();
55
+ static run(data, fn) {
56
+ return _SagaContext.storage.run(data, fn);
57
+ }
58
+ static current() {
59
+ return _SagaContext.storage.getStore();
60
+ }
61
+ static require() {
62
+ const ctx = _SagaContext.current();
63
+ if (!ctx) {
64
+ throw new SagaContextNotFoundError();
65
+ }
66
+ return ctx;
67
+ }
68
+ };
69
+
70
+ // src/runner/saga-runner.ts
71
+ import { v7 as uuidv7 } from "uuid";
72
+ var SagaRunner = class {
73
+ constructor(registry, transport, publisher, parser, options, otelCtx, logger = new ConsoleSagaLogger()) {
74
+ this.registry = registry;
75
+ this.transport = transport;
76
+ this.publisher = publisher;
77
+ this.parser = parser;
78
+ this.options = options;
79
+ this.otelCtx = otelCtx;
80
+ this.logger = logger;
81
+ }
82
+ routeMap;
83
+ async start() {
84
+ this.routeMap = this.registry.buildRouteMap();
85
+ const prefix = this.options.topicPrefix ?? "";
86
+ const topics = Array.from(this.routeMap.keys()).map((et) => `${prefix}${et}`);
87
+ await this.transport.connect();
88
+ if (topics.length > 0) {
89
+ this.logger.info(`[SagaRunner] Subscribing to ${topics.length} topic(s): [${topics.join(", ")}]`);
90
+ await this.transport.subscribe(
91
+ topics,
92
+ (message) => this.handleMessage(message),
93
+ {
94
+ fromBeginning: this.options.fromBeginning,
95
+ groupId: `${this.options.serviceName}-group`
96
+ }
97
+ );
98
+ this.logger.info("[SagaRunner] Consumer running");
99
+ } else {
100
+ this.logger.warn("[SagaRunner] No handlers registered \u2014 nothing to subscribe");
101
+ }
102
+ }
103
+ async stop() {
104
+ await this.transport.disconnect();
105
+ }
106
+ async handleMessage(message) {
107
+ const event = this.parser.parse(message);
108
+ if (!event) return;
109
+ const route = this.routeMap.get(event.eventType);
110
+ if (!route) return;
111
+ const isFinalHandler = route.options?.final === true;
112
+ const incoming = {
113
+ sagaId: event.sagaId,
114
+ eventId: event.eventId,
115
+ causationId: event.causationId,
116
+ eventType: event.eventType,
117
+ stepName: event.stepName,
118
+ stepDescription: event.stepDescription,
119
+ occurredAt: event.occurredAt,
120
+ parentSagaId: event.parentSagaId,
121
+ rootSagaId: event.rootSagaId,
122
+ payload: event.payload,
123
+ key: event.key,
124
+ sagaName: event.sagaName,
125
+ sagaDescription: event.sagaDescription
126
+ };
127
+ const emit = this.publisher.forSaga(event.sagaId, {
128
+ parentSagaId: event.parentSagaId,
129
+ rootSagaId: event.rootSagaId
130
+ }, event.eventId, event.key);
131
+ const wrappedEmit = async (params) => {
132
+ const finalParams = isFinalHandler ? { ...params, hint: "final" } : params;
133
+ return emit(finalParams);
134
+ };
135
+ const forkConfig = route.options?.fork;
136
+ const finalEmit = forkConfig ? async (params) => {
137
+ const subSagaId = uuidv7();
138
+ const subEmit = this.publisher.forSaga(subSagaId, {
139
+ parentSagaId: event.sagaId,
140
+ rootSagaId: event.rootSagaId
141
+ }, event.eventId, event.key);
142
+ const forkMeta = typeof forkConfig === "object" ? forkConfig : {};
143
+ const forkCtx = {
144
+ sagaId: subSagaId,
145
+ rootSagaId: event.rootSagaId,
146
+ parentSagaId: event.sagaId,
147
+ causationId: event.eventId,
148
+ key: event.key,
149
+ sagaName: forkMeta.sagaName,
150
+ sagaDescription: forkMeta.sagaDescription
151
+ };
152
+ await SagaContext.run(forkCtx, () => subEmit({ ...params, hint: "fork" }));
153
+ } : wrappedEmit;
154
+ const spanAttrs = {
155
+ "saga.id": event.sagaId,
156
+ "saga.event.type": event.eventType,
157
+ "saga.step.name": event.stepName,
158
+ "saga.event.id": event.eventId,
159
+ "saga.root.id": event.rootSagaId,
160
+ "saga.handler.service": route.participant.serviceId
161
+ };
162
+ if (event.sagaName) spanAttrs["saga.name"] = event.sagaName;
163
+ if (event.sagaDescription) spanAttrs["saga.description"] = event.sagaDescription;
164
+ if (event.stepDescription) spanAttrs["saga.step.description"] = event.stepDescription;
165
+ if (event.parentSagaId) spanAttrs["saga.parent.id"] = event.parentSagaId;
166
+ const sagaCtxData = {
167
+ sagaId: event.sagaId,
168
+ rootSagaId: event.rootSagaId,
169
+ parentSagaId: event.parentSagaId,
170
+ causationId: event.eventId,
171
+ key: event.key,
172
+ sagaName: event.sagaName,
173
+ sagaDescription: event.sagaDescription
174
+ };
175
+ const runHandler = () => SagaContext.run(
176
+ sagaCtxData,
177
+ () => this.runWithRetry(route.handler, route.participant, incoming, finalEmit)
178
+ );
179
+ if (this.otelCtx) {
180
+ await this.otelCtx.withExtractedSpan(`saga.handle ${event.eventType}`, spanAttrs, message.headers, runHandler);
181
+ } else {
182
+ await runHandler();
183
+ }
184
+ }
185
+ async runWithRetry(handler, participant, event, emit) {
186
+ const maxRetries = this.options.retryPolicy?.maxRetries ?? 3;
187
+ const initialDelayMs = this.options.retryPolicy?.initialDelayMs ?? 200;
188
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
189
+ try {
190
+ await handler(event, emit);
191
+ return;
192
+ } catch (error) {
193
+ if (error instanceof SagaRetryableError) {
194
+ if (attempt < maxRetries) {
195
+ const delay = initialDelayMs * Math.pow(2, attempt);
196
+ await this.sleep(delay);
197
+ continue;
198
+ }
199
+ if (participant.onRetryExhausted) {
200
+ await participant.onRetryExhausted(event, error, emit);
201
+ }
202
+ return;
203
+ }
204
+ this.logger.error(
205
+ `[SagaRunner] Non-retryable error in handler for ${event.eventType}:`,
206
+ error
207
+ );
208
+ return;
209
+ }
210
+ }
211
+ }
212
+ sleep(ms) {
213
+ return new Promise((resolve) => setTimeout(resolve, ms));
214
+ }
215
+ };
216
+
217
+ // src/publisher/saga-publisher.ts
218
+ import { v7 as uuidv72 } from "uuid";
219
+
220
+ // src/errors/saga-no-parent.error.ts
221
+ var SagaNoParentError = class extends SagaError {
222
+ constructor() {
223
+ super("No parentSagaId in current saga context.");
224
+ this.name = "SagaNoParentError";
225
+ }
226
+ };
227
+
228
+ // src/publisher/message-builder.ts
229
+ function buildOutboundMessage(event, topicPrefix = "") {
230
+ const topic = `${topicPrefix}${event.eventType}`;
231
+ const key = event.key ?? event.rootSagaId;
232
+ const headers = {
233
+ "saga-id": event.sagaId,
234
+ "saga-causation-id": event.causationId,
235
+ "saga-event-id": event.eventId,
236
+ "saga-step-name": event.stepName,
237
+ "saga-published-at": event.publishedAt,
238
+ "saga-schema-version": String(event.schemaVersion),
239
+ "saga-root-id": event.rootSagaId
240
+ };
241
+ if (event.parentSagaId) {
242
+ headers["saga-parent-id"] = event.parentSagaId;
243
+ }
244
+ if (event.hint) {
245
+ headers["saga-event-hint"] = event.hint;
246
+ }
247
+ if (event.sagaName) {
248
+ headers["saga-name"] = event.sagaName;
249
+ }
250
+ if (event.sagaDescription) {
251
+ headers["saga-description"] = event.sagaDescription;
252
+ }
253
+ if (event.stepDescription) {
254
+ headers["saga-step-description"] = event.stepDescription;
255
+ }
256
+ if (event.key) {
257
+ headers["saga-key"] = event.key;
258
+ }
259
+ const value = JSON.stringify({
260
+ eventType: event.eventType,
261
+ occurredAt: event.occurredAt,
262
+ payload: event.payload
263
+ });
264
+ return { topic, key, value, headers };
265
+ }
266
+
267
+ // src/publisher/saga-publisher.ts
268
+ var SagaPublisher = class {
269
+ constructor(transport, otelCtx, topicPrefix = "") {
270
+ this.transport = transport;
271
+ this.otelCtx = otelCtx;
272
+ this.topicPrefix = topicPrefix;
273
+ }
274
+ async start(fn, opts) {
275
+ const sagaId = uuidv72();
276
+ const ctxData = {
277
+ sagaId,
278
+ rootSagaId: sagaId,
279
+ causationId: sagaId,
280
+ key: opts?.key,
281
+ sagaName: opts?.sagaName,
282
+ sagaDescription: opts?.sagaDescription
283
+ };
284
+ const result = await SagaContext.run(ctxData, fn);
285
+ return { sagaId, result };
286
+ }
287
+ async emit(params) {
288
+ const ctx = SagaContext.require();
289
+ const boundEmit = this.forSaga(ctx.sagaId, {
290
+ parentSagaId: ctx.parentSagaId,
291
+ rootSagaId: ctx.rootSagaId
292
+ }, ctx.causationId, ctx.key);
293
+ return boundEmit(params);
294
+ }
295
+ async startChild(fn, opts) {
296
+ const ctx = SagaContext.require();
297
+ const sagaId = uuidv72();
298
+ const childCtx = {
299
+ sagaId,
300
+ rootSagaId: ctx.rootSagaId,
301
+ parentSagaId: ctx.sagaId,
302
+ causationId: ctx.causationId,
303
+ key: opts?.key ?? ctx.key,
304
+ sagaName: opts?.sagaName ?? ctx.sagaName,
305
+ sagaDescription: opts?.sagaDescription ?? ctx.sagaDescription
306
+ };
307
+ const result = await SagaContext.run(childCtx, fn);
308
+ return { sagaId, result };
309
+ }
310
+ async emitToParent(paramsOrFn) {
311
+ const ctx = SagaContext.require();
312
+ if (!ctx.parentSagaId) {
313
+ throw new SagaNoParentError();
314
+ }
315
+ if (typeof paramsOrFn === "function") {
316
+ const parentCtx = {
317
+ sagaId: ctx.parentSagaId,
318
+ rootSagaId: ctx.rootSagaId,
319
+ parentSagaId: ctx.parentSagaId,
320
+ causationId: ctx.causationId,
321
+ key: ctx.key
322
+ };
323
+ await SagaContext.run(parentCtx, paramsOrFn);
324
+ return;
325
+ }
326
+ const parentEmit = this.forSaga(ctx.parentSagaId, {
327
+ parentSagaId: ctx.parentSagaId,
328
+ rootSagaId: ctx.rootSagaId
329
+ }, ctx.causationId, ctx.key);
330
+ return parentEmit(paramsOrFn);
331
+ }
332
+ forSaga(sagaId, parentCtx, causationId, baseKey) {
333
+ const rootSagaId = parentCtx?.rootSagaId ?? sagaId;
334
+ const parentSagaId = parentCtx?.parentSagaId;
335
+ const baseCausationId = causationId ?? sagaId;
336
+ return async ({
337
+ eventType,
338
+ stepName,
339
+ stepDescription,
340
+ payload,
341
+ hint,
342
+ key
343
+ }) => {
344
+ const ctx = SagaContext.current();
345
+ const resolvedKey = key ?? baseKey ?? ctx?.key;
346
+ const now = (/* @__PURE__ */ new Date()).toISOString();
347
+ const event = {
348
+ sagaId,
349
+ causationId: baseCausationId,
350
+ eventId: uuidv72(),
351
+ eventType,
352
+ stepName,
353
+ stepDescription,
354
+ occurredAt: now,
355
+ publishedAt: now,
356
+ schemaVersion: 1,
357
+ rootSagaId,
358
+ parentSagaId,
359
+ payload,
360
+ hint,
361
+ key: resolvedKey,
362
+ sagaName: ctx?.sagaName,
363
+ sagaDescription: ctx?.sagaDescription
364
+ };
365
+ await this.publish(event);
366
+ };
367
+ }
368
+ async publish(event) {
369
+ this.otelCtx.injectBaggage(event.sagaId, event.rootSagaId, event.parentSagaId);
370
+ const attrs = {
371
+ "saga.id": event.sagaId,
372
+ "saga.event.type": event.eventType,
373
+ "saga.step.name": event.stepName,
374
+ "saga.root.id": event.rootSagaId
375
+ };
376
+ if (event.sagaName) {
377
+ attrs["saga.name"] = event.sagaName;
378
+ }
379
+ if (event.sagaDescription) {
380
+ attrs["saga.description"] = event.sagaDescription;
381
+ }
382
+ if (event.stepDescription) {
383
+ attrs["saga.step.description"] = event.stepDescription;
384
+ }
385
+ if (event.parentSagaId) {
386
+ attrs["saga.parent.id"] = event.parentSagaId;
387
+ }
388
+ this.otelCtx.enrichSpan(attrs);
389
+ const message = buildOutboundMessage(event, this.topicPrefix);
390
+ this.otelCtx.injectTraceContext(message.headers);
391
+ await this.otelCtx.withSpan(
392
+ `saga.publish ${event.eventType}`,
393
+ attrs,
394
+ () => this.transport.publish(message)
395
+ );
396
+ }
397
+ };
398
+
399
+ // src/parser/saga-parser.ts
400
+ import { v7 as uuidv73 } from "uuid";
401
+ var SagaParser = class {
402
+ constructor(otelCtx) {
403
+ this.otelCtx = otelCtx;
404
+ }
405
+ parse(message) {
406
+ try {
407
+ if (message.headers["saga-id"]) {
408
+ return this.parseFromHeaders(message);
409
+ }
410
+ const baggageResult = this.parseFromBaggage(message);
411
+ if (baggageResult) return baggageResult;
412
+ const body = JSON.parse(message.value);
413
+ if (body && body.sagaId) {
414
+ return body;
415
+ }
416
+ return null;
417
+ } catch {
418
+ return null;
419
+ }
420
+ }
421
+ parseFromHeaders(message) {
422
+ const headers = message.headers;
423
+ const body = JSON.parse(message.value);
424
+ const sagaId = headers["saga-id"];
425
+ if (!sagaId) {
426
+ return null;
427
+ }
428
+ return {
429
+ sagaId,
430
+ causationId: headers["saga-causation-id"] ?? sagaId,
431
+ eventId: headers["saga-event-id"] ?? uuidv73(),
432
+ eventType: body.eventType,
433
+ stepName: headers["saga-step-name"] ?? "",
434
+ occurredAt: body.occurredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
435
+ publishedAt: headers["saga-published-at"] ?? (/* @__PURE__ */ new Date()).toISOString(),
436
+ schemaVersion: 1,
437
+ rootSagaId: headers["saga-root-id"] ?? sagaId,
438
+ parentSagaId: headers["saga-parent-id"] || void 0,
439
+ payload: body.payload,
440
+ sagaName: headers["saga-name"] || void 0,
441
+ sagaDescription: headers["saga-description"] || void 0,
442
+ stepDescription: headers["saga-step-description"] || void 0,
443
+ key: headers["saga-key"] || void 0
444
+ };
445
+ }
446
+ parseFromBaggage(message) {
447
+ let sagaId;
448
+ let rootSagaId;
449
+ let parentSagaId;
450
+ const baggageHeader = message.headers["baggage"];
451
+ if (baggageHeader) {
452
+ const entries = this.parseBaggageHeader(baggageHeader);
453
+ sagaId = entries["saga.id"];
454
+ rootSagaId = entries["saga.root.id"];
455
+ parentSagaId = entries["saga.parent.id"];
456
+ }
457
+ if (!sagaId) {
458
+ const extracted = this.otelCtx.extractBaggage();
459
+ sagaId = extracted.sagaId;
460
+ rootSagaId = extracted.rootSagaId;
461
+ parentSagaId = extracted.parentSagaId;
462
+ }
463
+ if (!sagaId) return null;
464
+ const body = JSON.parse(message.value);
465
+ return {
466
+ sagaId,
467
+ causationId: sagaId,
468
+ eventId: uuidv73(),
469
+ eventType: body.eventType,
470
+ stepName: "",
471
+ occurredAt: body.occurredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
472
+ publishedAt: (/* @__PURE__ */ new Date()).toISOString(),
473
+ schemaVersion: 1,
474
+ rootSagaId: rootSagaId ?? sagaId,
475
+ parentSagaId: parentSagaId || void 0,
476
+ payload: body.payload
477
+ };
478
+ }
479
+ parseBaggageHeader(baggage) {
480
+ const result = {};
481
+ for (const entry of baggage.split(",")) {
482
+ const [key, value] = entry.trim().split("=");
483
+ if (key && value) {
484
+ result[key.trim()] = decodeURIComponent(value.trim());
485
+ }
486
+ }
487
+ return result;
488
+ }
489
+ };
490
+
491
+ // src/errors/saga-duplicate-handler.error.ts
492
+ var SagaDuplicateHandlerError = class extends SagaError {
493
+ constructor(eventType, existingServiceId, newServiceId) {
494
+ super(
495
+ `Duplicate handler for event type "${eventType}": registered by "${existingServiceId}" and "${newServiceId}"`
496
+ );
497
+ this.name = "SagaDuplicateHandlerError";
498
+ }
499
+ };
500
+
501
+ // src/errors/saga-invalid-handler-config.error.ts
502
+ var SagaInvalidHandlerConfigError = class extends SagaError {
503
+ constructor(eventType, serviceId, reason) {
504
+ super(
505
+ `Invalid handler config for "${eventType}" in "${serviceId}": ${reason}`
506
+ );
507
+ this.name = "SagaInvalidHandlerConfigError";
508
+ }
509
+ };
510
+
511
+ // src/registry/saga-registry.ts
512
+ var SagaRegistry = class {
513
+ participants = [];
514
+ register(participant) {
515
+ this.participants.push(participant);
516
+ }
517
+ getAll() {
518
+ return [...this.participants];
519
+ }
520
+ buildRouteMap() {
521
+ const map = /* @__PURE__ */ new Map();
522
+ for (const participant of this.participants) {
523
+ for (const [eventType, handler] of Object.entries(participant.on)) {
524
+ if (map.has(eventType)) {
525
+ const existing = map.get(eventType);
526
+ throw new SagaDuplicateHandlerError(
527
+ eventType,
528
+ existing.participant.serviceId,
529
+ participant.serviceId
530
+ );
531
+ }
532
+ const options = participant.handlerOptions?.[eventType];
533
+ if (options?.final && options?.fork) {
534
+ throw new SagaInvalidHandlerConfigError(
535
+ eventType,
536
+ participant.serviceId,
537
+ "cannot have both final and fork options"
538
+ );
539
+ }
540
+ map.set(eventType, { participant, handler, options });
541
+ }
542
+ }
543
+ return map;
544
+ }
545
+ };
546
+
547
+ // src/errors/saga-parse.error.ts
548
+ var SagaParseError = class extends SagaError {
549
+ constructor(message) {
550
+ super(message);
551
+ this.name = "SagaParseError";
552
+ }
553
+ };
554
+
555
+ // src/errors/saga-transport-not-connected.error.ts
556
+ var SagaTransportNotConnectedError = class extends SagaError {
557
+ constructor() {
558
+ super("Transport not connected");
559
+ this.name = "SagaTransportNotConnectedError";
560
+ }
561
+ };
562
+
563
+ // src/otel/otel-context.ts
564
+ var NoopOtelContext = class {
565
+ injectBaggage() {
566
+ }
567
+ extractBaggage() {
568
+ return {};
569
+ }
570
+ enrichSpan() {
571
+ }
572
+ async withSpan(_name, _attrs, fn) {
573
+ return fn();
574
+ }
575
+ injectTraceContext() {
576
+ }
577
+ async withExtractedSpan(_name, _attrs, _headers, fn) {
578
+ return fn();
579
+ }
580
+ };
581
+ var W3cOtelContext = class {
582
+ api;
583
+ constructor(api) {
584
+ this.api = api;
585
+ }
586
+ injectBaggage(sagaId, rootSagaId, parentSagaId) {
587
+ const entries = {
588
+ "saga.id": { value: sagaId },
589
+ "saga.root.id": { value: rootSagaId }
590
+ };
591
+ if (parentSagaId) {
592
+ entries["saga.parent.id"] = { value: parentSagaId };
593
+ }
594
+ const baggage = this.api.propagation.createBaggage(entries);
595
+ const ctx = this.api.propagation.setBaggage(this.api.context.active(), baggage);
596
+ this.api.context.with(ctx, () => {
597
+ });
598
+ }
599
+ extractBaggage() {
600
+ const baggage = this.api.propagation.getBaggage(this.api.context.active());
601
+ if (!baggage) return {};
602
+ return {
603
+ sagaId: baggage.getEntry("saga.id")?.value,
604
+ rootSagaId: baggage.getEntry("saga.root.id")?.value,
605
+ parentSagaId: baggage.getEntry("saga.parent.id")?.value
606
+ };
607
+ }
608
+ enrichSpan(attrs) {
609
+ const span = this.api.trace.getActiveSpan();
610
+ if (span) {
611
+ span.setAttributes(attrs);
612
+ }
613
+ }
614
+ async withSpan(name, attrs, fn) {
615
+ const tracer = this.api.trace.getTracer("@fbsm/saga-core");
616
+ return tracer.startActiveSpan(name, async (span) => {
617
+ span.setAttributes(attrs);
618
+ try {
619
+ const result = await fn();
620
+ span.setStatus({ code: this.api.SpanStatusCode.OK });
621
+ return result;
622
+ } catch (error) {
623
+ span.setStatus({
624
+ code: this.api.SpanStatusCode.ERROR,
625
+ message: error instanceof Error ? error.message : String(error)
626
+ });
627
+ span.recordException(error instanceof Error ? error : new Error(String(error)));
628
+ throw error;
629
+ } finally {
630
+ span.end();
631
+ }
632
+ });
633
+ }
634
+ injectTraceContext(headers) {
635
+ this.api.propagation.inject(this.api.context.active(), headers);
636
+ }
637
+ async withExtractedSpan(name, attrs, headers, fn) {
638
+ const parentCtx = this.api.propagation.extract(this.api.ROOT_CONTEXT, headers);
639
+ const tracer = this.api.trace.getTracer("@fbsm/saga-core");
640
+ return this.api.context.with(
641
+ parentCtx,
642
+ () => tracer.startActiveSpan(name, { kind: this.api.SpanKind.CONSUMER }, async (span) => {
643
+ span.setAttributes(attrs);
644
+ try {
645
+ const result = await fn();
646
+ span.setStatus({ code: this.api.SpanStatusCode.OK });
647
+ return result;
648
+ } catch (error) {
649
+ span.setStatus({
650
+ code: this.api.SpanStatusCode.ERROR,
651
+ message: error instanceof Error ? error.message : String(error)
652
+ });
653
+ span.recordException(error instanceof Error ? error : new Error(String(error)));
654
+ throw error;
655
+ } finally {
656
+ span.end();
657
+ }
658
+ })
659
+ );
660
+ }
661
+ };
662
+ function createOtelContext(enabled) {
663
+ if (!enabled) return new NoopOtelContext();
664
+ try {
665
+ const api = __require("@opentelemetry/api");
666
+ return new W3cOtelContext(api);
667
+ } catch {
668
+ return new NoopOtelContext();
669
+ }
670
+ }
671
+ export {
672
+ ConsoleSagaLogger,
673
+ NoopOtelContext,
674
+ SagaContext,
675
+ SagaContextNotFoundError,
676
+ SagaDuplicateHandlerError,
677
+ SagaError,
678
+ SagaInvalidHandlerConfigError,
679
+ SagaNoParentError,
680
+ SagaParseError,
681
+ SagaParser,
682
+ SagaPublisher,
683
+ SagaRegistry,
684
+ SagaRetryableError,
685
+ SagaRunner,
686
+ SagaTransportNotConnectedError,
687
+ W3cOtelContext,
688
+ createOtelContext
689
+ };
690
+ //# sourceMappingURL=index.js.map