@nimbus-cqrs/core 2.0.0-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/LICENSE +85 -0
- package/README.md +56 -0
- package/esm/_dnt.shims.d.ts +2 -0
- package/esm/_dnt.shims.d.ts.map +1 -0
- package/esm/_dnt.shims.js +57 -0
- package/esm/deps/jsr.io/@std/fmt/1.0.10/colors.d.ts +700 -0
- package/esm/deps/jsr.io/@std/fmt/1.0.10/colors.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/fmt/1.0.10/colors.js +903 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/_util.d.ts +14 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/_util.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/_util.js +65 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/decode_time.d.ts +20 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/decode_time.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/decode_time.js +43 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/mod.d.ts +44 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/mod.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/mod.js +47 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/monotonic_ulid.d.ts +44 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/monotonic_ulid.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/monotonic_ulid.js +51 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/ulid.d.ts +31 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/ulid.d.ts.map +1 -0
- package/esm/deps/jsr.io/@std/ulid/1.0.0/ulid.js +37 -0
- package/esm/index.d.ts +18 -0
- package/esm/index.d.ts.map +1 -0
- package/esm/index.js +17 -0
- package/esm/lib/eventBus/eventBus.d.ts +329 -0
- package/esm/lib/eventBus/eventBus.d.ts.map +1 -0
- package/esm/lib/eventBus/eventBus.js +473 -0
- package/esm/lib/exception/exception.d.ts +10 -0
- package/esm/lib/exception/exception.d.ts.map +1 -0
- package/esm/lib/exception/exception.js +26 -0
- package/esm/lib/exception/forbiddenException.d.ts +8 -0
- package/esm/lib/exception/forbiddenException.d.ts.map +1 -0
- package/esm/lib/exception/forbiddenException.js +9 -0
- package/esm/lib/exception/genericException.d.ts +8 -0
- package/esm/lib/exception/genericException.d.ts.map +1 -0
- package/esm/lib/exception/genericException.js +9 -0
- package/esm/lib/exception/invalidInputException.d.ts +18 -0
- package/esm/lib/exception/invalidInputException.d.ts.map +1 -0
- package/esm/lib/exception/invalidInputException.js +28 -0
- package/esm/lib/exception/notFoundException.d.ts +8 -0
- package/esm/lib/exception/notFoundException.d.ts.map +1 -0
- package/esm/lib/exception/notFoundException.js +9 -0
- package/esm/lib/exception/unauthorizedException.d.ts +8 -0
- package/esm/lib/exception/unauthorizedException.d.ts.map +1 -0
- package/esm/lib/exception/unauthorizedException.js +9 -0
- package/esm/lib/log/logFormatter.d.ts +26 -0
- package/esm/lib/log/logFormatter.d.ts.map +1 -0
- package/esm/lib/log/logFormatter.js +59 -0
- package/esm/lib/log/logLevel.d.ts +24 -0
- package/esm/lib/log/logLevel.d.ts.map +1 -0
- package/esm/lib/log/logLevel.js +27 -0
- package/esm/lib/log/logger.d.ts +377 -0
- package/esm/lib/log/logger.d.ts.map +1 -0
- package/esm/lib/log/logger.js +394 -0
- package/esm/lib/log/options.d.ts +34 -0
- package/esm/lib/log/options.d.ts.map +1 -0
- package/esm/lib/log/options.js +9 -0
- package/esm/lib/message/command.d.ts +100 -0
- package/esm/lib/message/command.d.ts.map +1 -0
- package/esm/lib/message/command.js +47 -0
- package/esm/lib/message/event.d.ts +103 -0
- package/esm/lib/message/event.d.ts.map +1 -0
- package/esm/lib/message/event.js +47 -0
- package/esm/lib/message/message.d.ts +18 -0
- package/esm/lib/message/message.d.ts.map +1 -0
- package/esm/lib/message/message.js +1 -0
- package/esm/lib/message/query.d.ts +95 -0
- package/esm/lib/message/query.d.ts.map +1 -0
- package/esm/lib/message/query.js +45 -0
- package/esm/lib/message/router.d.ts +233 -0
- package/esm/lib/message/router.d.ts.map +1 -0
- package/esm/lib/message/router.js +304 -0
- package/esm/lib/tracing/withSpan.d.ts +67 -0
- package/esm/lib/tracing/withSpan.d.ts.map +1 -0
- package/esm/lib/tracing/withSpan.js +87 -0
- package/esm/package.json +3 -0
- package/package.json +33 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { metrics, SpanKind, SpanStatusCode, trace } from '@opentelemetry/api';
|
|
2
|
+
import { InvalidInputException } from '../exception/invalidInputException.js';
|
|
3
|
+
import { NotFoundException } from '../exception/notFoundException.js';
|
|
4
|
+
import { getLogger } from '../log/logger.js';
|
|
5
|
+
const tracer = trace.getTracer('nimbus');
|
|
6
|
+
const meter = metrics.getMeter('nimbus');
|
|
7
|
+
const messagesRoutedCounter = meter.createCounter('router_messages_routed_total', {
|
|
8
|
+
description: 'Total number of messages routed',
|
|
9
|
+
});
|
|
10
|
+
const routingDuration = meter.createHistogram('router_routing_duration_seconds', {
|
|
11
|
+
description: 'Duration of message routing in seconds',
|
|
12
|
+
unit: 's',
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* The MessageRouter routes messages to their handlers based on the type value of the message.
|
|
16
|
+
*
|
|
17
|
+
* Messages are validated against their registered Zod schemas before being passed to handlers.
|
|
18
|
+
* All routing operations are instrumented with OpenTelemetry tracing and metrics for observability.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* import { createCommand, MessageRouter } from '@nimbus/core';
|
|
23
|
+
*
|
|
24
|
+
* const messageRouter = new MessageRouter({
|
|
25
|
+
* name: 'api',
|
|
26
|
+
* logInput: (input) => {
|
|
27
|
+
* console.log('Received message:', input.type);
|
|
28
|
+
* },
|
|
29
|
+
* logOutput: (output) => {
|
|
30
|
+
* console.log('Handler result:', output);
|
|
31
|
+
* },
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* // Register command handler
|
|
35
|
+
* messageRouter.register(
|
|
36
|
+
* 'at.overlap.nimbus.create-order',
|
|
37
|
+
* createOrderHandler,
|
|
38
|
+
* createOrderCommandSchema,
|
|
39
|
+
* );
|
|
40
|
+
*
|
|
41
|
+
* // Register query handler
|
|
42
|
+
* messageRouter.register(
|
|
43
|
+
* 'at.overlap.nimbus.get-order',
|
|
44
|
+
* getOrderHandler,
|
|
45
|
+
* getOrderQuerySchema,
|
|
46
|
+
* );
|
|
47
|
+
*
|
|
48
|
+
* // Route a command
|
|
49
|
+
* const command = createCommand({
|
|
50
|
+
* type: 'at.overlap.nimbus.create-order',
|
|
51
|
+
* source: 'https://api.example.com',
|
|
52
|
+
* data: { customerId: '123', items: ['item-1', 'item-2'] },
|
|
53
|
+
* });
|
|
54
|
+
*
|
|
55
|
+
* const result = await messageRouter.route(command);
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export class MessageRouter {
|
|
59
|
+
_handlers;
|
|
60
|
+
_name;
|
|
61
|
+
_logInput;
|
|
62
|
+
_logOutput;
|
|
63
|
+
constructor(options) {
|
|
64
|
+
this._handlers = new Map();
|
|
65
|
+
this._name = options?.name ?? 'default';
|
|
66
|
+
this._logInput = options?.logInput;
|
|
67
|
+
this._logOutput = options?.logOutput;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Register a handler for a specific message type.
|
|
71
|
+
*
|
|
72
|
+
* @param messageType - The message type as defined in the CloudEvents specification
|
|
73
|
+
* (e.g., 'at.overlap.nimbus.create-order').
|
|
74
|
+
* @param handler - The async handler function that processes the message and returns a result.
|
|
75
|
+
* @param schema - The Zod schema to validate the incoming message before passing to the handler.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* import { commandSchema, type Command, getRouter } from '@nimbus/core';
|
|
80
|
+
* import { z } from 'zod';
|
|
81
|
+
*
|
|
82
|
+
* // Define the command type and schema
|
|
83
|
+
* const CREATE_ORDER_TYPE = 'at.overlap.nimbus.create-order';
|
|
84
|
+
*
|
|
85
|
+
* const createOrderSchema = commandSchema.extend({
|
|
86
|
+
* type: z.literal(CREATE_ORDER_TYPE),
|
|
87
|
+
* data: z.object({
|
|
88
|
+
* customerId: z.string(),
|
|
89
|
+
* items: z.array(z.string()),
|
|
90
|
+
* }),
|
|
91
|
+
* });
|
|
92
|
+
* type CreateOrderCommand = z.infer<typeof createOrderSchema>;
|
|
93
|
+
*
|
|
94
|
+
* // Define the handler
|
|
95
|
+
* const createOrderHandler = async (command: CreateOrderCommand) => {
|
|
96
|
+
* // Process the command and return the result
|
|
97
|
+
* return { orderId: '12345', status: 'created' };
|
|
98
|
+
* };
|
|
99
|
+
*
|
|
100
|
+
* // Register the handler
|
|
101
|
+
* const router = getRouter('default');
|
|
102
|
+
* router.register(CREATE_ORDER_TYPE, createOrderHandler, createOrderSchema);
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
register(messageType, handler, schema) {
|
|
106
|
+
this._handlers.set(messageType, {
|
|
107
|
+
handler,
|
|
108
|
+
schema,
|
|
109
|
+
});
|
|
110
|
+
getLogger().debug({
|
|
111
|
+
category: 'Nimbus',
|
|
112
|
+
message: `Registered handler for: ${messageType}`,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Route a message to its handler.
|
|
117
|
+
*
|
|
118
|
+
* The message is validated against the registered schema before being passed to the handler.
|
|
119
|
+
* The routing operation is instrumented with OpenTelemetry tracing and metrics.
|
|
120
|
+
*
|
|
121
|
+
* @param input - The CloudEvents-compliant message to route (command, query, or event).
|
|
122
|
+
* @returns The result from the handler.
|
|
123
|
+
*
|
|
124
|
+
* @throws {NotFoundException} If no handler is registered for the message type.
|
|
125
|
+
* @throws {InvalidInputException} If the message has no type attribute or fails schema validation.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```ts
|
|
129
|
+
* import { createCommand, getRouter } from '@nimbus/core';
|
|
130
|
+
*
|
|
131
|
+
* const router = getRouter('default');
|
|
132
|
+
*
|
|
133
|
+
* // Create a command with all CloudEvents properties
|
|
134
|
+
* const command = createCommand({
|
|
135
|
+
* type: 'at.overlap.nimbus.create-order',
|
|
136
|
+
* source: 'https://api.example.com',
|
|
137
|
+
* correlationid: '550e8400-e29b-41d4-a716-446655440000',
|
|
138
|
+
* data: {
|
|
139
|
+
* customerId: '123',
|
|
140
|
+
* items: ['item-1', 'item-2'],
|
|
141
|
+
* },
|
|
142
|
+
* datacontenttype: 'application/json',
|
|
143
|
+
* });
|
|
144
|
+
*
|
|
145
|
+
* // Route the command to its registered handler
|
|
146
|
+
* const result = await router.route(command);
|
|
147
|
+
* console.log('Order created:', result);
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
async route(input) {
|
|
151
|
+
const startTime = performance.now();
|
|
152
|
+
const messageType = input?.type ?? 'unknown';
|
|
153
|
+
return await tracer.startActiveSpan('router.route', {
|
|
154
|
+
kind: SpanKind.INTERNAL,
|
|
155
|
+
attributes: {
|
|
156
|
+
'messaging.system': 'nimbusRouter',
|
|
157
|
+
'messaging.router_name': this._name,
|
|
158
|
+
'messaging.operation': 'route',
|
|
159
|
+
'messaging.destination': messageType,
|
|
160
|
+
...(input?.correlationid && {
|
|
161
|
+
correlation_id: input.correlationid,
|
|
162
|
+
}),
|
|
163
|
+
},
|
|
164
|
+
}, async (span) => {
|
|
165
|
+
try {
|
|
166
|
+
if (this._logInput) {
|
|
167
|
+
this._logInput(input);
|
|
168
|
+
}
|
|
169
|
+
if (!input?.type) {
|
|
170
|
+
throw new InvalidInputException('The provided input has no type attribute');
|
|
171
|
+
}
|
|
172
|
+
const registration = this._handlers.get(input.type);
|
|
173
|
+
if (!registration) {
|
|
174
|
+
throw new NotFoundException('Message handler not found', {
|
|
175
|
+
reason: `Could not find a handler for message type: "${input.type}"`,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
const { handler, schema } = registration;
|
|
179
|
+
const validationResult = schema.safeParse(input);
|
|
180
|
+
if (!validationResult.success) {
|
|
181
|
+
throw new InvalidInputException('The provided input is invalid').fromZodError(validationResult.error);
|
|
182
|
+
}
|
|
183
|
+
const result = await handler(validationResult.data);
|
|
184
|
+
if (this._logOutput) {
|
|
185
|
+
this._logOutput(result);
|
|
186
|
+
}
|
|
187
|
+
messagesRoutedCounter.add(1, {
|
|
188
|
+
router_name: this._name,
|
|
189
|
+
message_type: input.type,
|
|
190
|
+
status: 'success',
|
|
191
|
+
});
|
|
192
|
+
routingDuration.record((performance.now() - startTime) / 1000, { router_name: this._name, message_type: input.type });
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
messagesRoutedCounter.add(1, {
|
|
197
|
+
router_name: this._name,
|
|
198
|
+
message_type: messageType,
|
|
199
|
+
status: 'error',
|
|
200
|
+
});
|
|
201
|
+
routingDuration.record((performance.now() - startTime) / 1000, { router_name: this._name, message_type: messageType });
|
|
202
|
+
span.setStatus({
|
|
203
|
+
code: SpanStatusCode.ERROR,
|
|
204
|
+
message: error instanceof Error
|
|
205
|
+
? error.message
|
|
206
|
+
: 'Unknown error',
|
|
207
|
+
});
|
|
208
|
+
span.recordException(error instanceof Error
|
|
209
|
+
? error
|
|
210
|
+
: new Error('Unknown error'));
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
span.end();
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Registry to store named MessageRouter instances.
|
|
221
|
+
*/
|
|
222
|
+
const routerRegistry = new Map();
|
|
223
|
+
/**
|
|
224
|
+
* Setup a named MessageRouter instance and register it for later retrieval.
|
|
225
|
+
*
|
|
226
|
+
* Use this function to configure a MessageRouter with specific options at application
|
|
227
|
+
* startup, then retrieve it later using {@link getRouter}.
|
|
228
|
+
*
|
|
229
|
+
* @param name - The unique name for this MessageRouter instance.
|
|
230
|
+
* @param options - Optional configuration options for the MessageRouter.
|
|
231
|
+
* @param options.logInput - Optional callback invoked when a message is received for routing.
|
|
232
|
+
* @param options.logOutput - Optional callback invoked after a message has been successfully handled.
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* ```ts
|
|
236
|
+
* import { getLogger, setupRouter } from '@nimbus/core';
|
|
237
|
+
*
|
|
238
|
+
* // At application startup, configure the router with all options
|
|
239
|
+
* setupRouter('default', {
|
|
240
|
+
* logInput: (input) => {
|
|
241
|
+
* getLogger().debug({
|
|
242
|
+
* category: 'Router',
|
|
243
|
+
* message: 'Received message',
|
|
244
|
+
* data: { type: input.type, correlationId: input.correlationid },
|
|
245
|
+
* correlationId: input.correlationid,
|
|
246
|
+
* });
|
|
247
|
+
* },
|
|
248
|
+
* logOutput: (output) => {
|
|
249
|
+
* getLogger().debug({
|
|
250
|
+
* category: 'Router',
|
|
251
|
+
* message: 'Handler completed',
|
|
252
|
+
* data: { output },
|
|
253
|
+
* });
|
|
254
|
+
* },
|
|
255
|
+
* });
|
|
256
|
+
* ```
|
|
257
|
+
*/
|
|
258
|
+
export const setupRouter = (name, options) => {
|
|
259
|
+
routerRegistry.set(name, new MessageRouter({ ...options, name }));
|
|
260
|
+
};
|
|
261
|
+
/**
|
|
262
|
+
* Get a named MessageRouter instance.
|
|
263
|
+
*
|
|
264
|
+
* If a MessageRouter with the given name has been configured via {@link setupRouter},
|
|
265
|
+
* that instance is returned. Otherwise, a new MessageRouter with default options is created
|
|
266
|
+
* and registered.
|
|
267
|
+
*
|
|
268
|
+
* @param name - The name of the MessageRouter instance to retrieve. Defaults to 'default'.
|
|
269
|
+
* @returns The MessageRouter instance.
|
|
270
|
+
*
|
|
271
|
+
* @example
|
|
272
|
+
* ```ts
|
|
273
|
+
* import { createCommand, getRouter } from '@nimbus/core';
|
|
274
|
+
*
|
|
275
|
+
* // Get the router configured earlier with setupRouter
|
|
276
|
+
* const router = getRouter('default');
|
|
277
|
+
*
|
|
278
|
+
* // Register handlers
|
|
279
|
+
* router.register(
|
|
280
|
+
* 'at.overlap.nimbus.create-order',
|
|
281
|
+
* createOrderHandler,
|
|
282
|
+
* createOrderSchema,
|
|
283
|
+
* );
|
|
284
|
+
*
|
|
285
|
+
* // Route a message
|
|
286
|
+
* const command = createCommand({
|
|
287
|
+
* type: 'at.overlap.nimbus.create-order',
|
|
288
|
+
* source: 'https://api.example.com',
|
|
289
|
+
* correlationid: '550e8400-e29b-41d4-a716-446655440000',
|
|
290
|
+
* data: { customerId: '123', items: ['item-1'] },
|
|
291
|
+
* datacontenttype: 'application/json',
|
|
292
|
+
* });
|
|
293
|
+
*
|
|
294
|
+
* const result = await router.route(command);
|
|
295
|
+
* ```
|
|
296
|
+
*/
|
|
297
|
+
export const getRouter = (name = 'default') => {
|
|
298
|
+
let router = routerRegistry.get(name);
|
|
299
|
+
if (!router) {
|
|
300
|
+
router = new MessageRouter({ name });
|
|
301
|
+
routerRegistry.set(name, router);
|
|
302
|
+
}
|
|
303
|
+
return router;
|
|
304
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type Attributes, type Span, SpanKind } from '@opentelemetry/api';
|
|
2
|
+
/**
|
|
3
|
+
* Options for configuring a span created by withSpan.
|
|
4
|
+
*/
|
|
5
|
+
export type WithSpanOptions = {
|
|
6
|
+
/**
|
|
7
|
+
* The name of the span. This will be displayed in your tracing UI.
|
|
8
|
+
*/
|
|
9
|
+
name: string;
|
|
10
|
+
/**
|
|
11
|
+
* The name of the tracer. Defaults to "nimbus".
|
|
12
|
+
*/
|
|
13
|
+
tracerName?: string;
|
|
14
|
+
/**
|
|
15
|
+
* The kind of span. Defaults to SpanKind.INTERNAL.
|
|
16
|
+
*/
|
|
17
|
+
kind?: SpanKind;
|
|
18
|
+
/**
|
|
19
|
+
* Initial attributes to set on the span.
|
|
20
|
+
*/
|
|
21
|
+
attributes?: Attributes;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Higher-order function that wraps a function with OpenTelemetry tracing.
|
|
25
|
+
*
|
|
26
|
+
* Creates a child span within the current trace context. The span automatically:
|
|
27
|
+
* - Inherits the parent span from the active context
|
|
28
|
+
* - Records the function's execution time
|
|
29
|
+
* - Sets error status and records exceptions on failure
|
|
30
|
+
* - Ends when the function completes (success or failure)
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* import { withSpan } from '@nimbus/core';
|
|
35
|
+
*
|
|
36
|
+
* const fetchUser = withSpan(
|
|
37
|
+
* {
|
|
38
|
+
* name: 'fetchUser',
|
|
39
|
+
* attributes: {
|
|
40
|
+
* 'user.source': 'database'
|
|
41
|
+
* }
|
|
42
|
+
* },
|
|
43
|
+
* async (userId: string) => {
|
|
44
|
+
* return await db.users.findById(userId);
|
|
45
|
+
* }
|
|
46
|
+
* );
|
|
47
|
+
*
|
|
48
|
+
* const user = await fetchUser('123');
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* const processOrder = withSpan(
|
|
54
|
+
* { name: 'processOrder' },
|
|
55
|
+
* async (orderId: string, span: Span) => {
|
|
56
|
+
* const order = await db.orders.findById(orderId);
|
|
57
|
+
*
|
|
58
|
+
* span.setAttribute('order.total', order.total);
|
|
59
|
+
* span.setAttribute('order.items', order.items.length);
|
|
60
|
+
*
|
|
61
|
+
* return await processPayment(order);
|
|
62
|
+
* }
|
|
63
|
+
* );
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export declare const withSpan: <TArgs extends unknown[], TReturn>(options: WithSpanOptions, fn: (...args: [...TArgs, Span]) => TReturn) => (...args: TArgs) => TReturn;
|
|
67
|
+
//# sourceMappingURL=withSpan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"withSpan.d.ts","sourceRoot":"","sources":["../../../src/lib/tracing/withSpan.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,KAAK,UAAU,EAEf,KAAK,IAAI,EACT,QAAQ,EAGX,MAAM,oBAAoB,CAAC;AAE5B;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC1B;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB;;OAEG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;CAC3B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,eAAO,MAAM,QAAQ,GAAI,KAAK,SAAS,OAAO,EAAE,EAAE,OAAO,EACrD,SAAS,eAAe,EACxB,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,GAAG,KAAK,EAAE,IAAI,CAAC,KAAK,OAAO,KAC3C,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAkDtB,CAAC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { context, SpanKind, SpanStatusCode, trace, } from '@opentelemetry/api';
|
|
2
|
+
/**
|
|
3
|
+
* Higher-order function that wraps a function with OpenTelemetry tracing.
|
|
4
|
+
*
|
|
5
|
+
* Creates a child span within the current trace context. The span automatically:
|
|
6
|
+
* - Inherits the parent span from the active context
|
|
7
|
+
* - Records the function's execution time
|
|
8
|
+
* - Sets error status and records exceptions on failure
|
|
9
|
+
* - Ends when the function completes (success or failure)
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { withSpan } from '@nimbus/core';
|
|
14
|
+
*
|
|
15
|
+
* const fetchUser = withSpan(
|
|
16
|
+
* {
|
|
17
|
+
* name: 'fetchUser',
|
|
18
|
+
* attributes: {
|
|
19
|
+
* 'user.source': 'database'
|
|
20
|
+
* }
|
|
21
|
+
* },
|
|
22
|
+
* async (userId: string) => {
|
|
23
|
+
* return await db.users.findById(userId);
|
|
24
|
+
* }
|
|
25
|
+
* );
|
|
26
|
+
*
|
|
27
|
+
* const user = await fetchUser('123');
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const processOrder = withSpan(
|
|
33
|
+
* { name: 'processOrder' },
|
|
34
|
+
* async (orderId: string, span: Span) => {
|
|
35
|
+
* const order = await db.orders.findById(orderId);
|
|
36
|
+
*
|
|
37
|
+
* span.setAttribute('order.total', order.total);
|
|
38
|
+
* span.setAttribute('order.items', order.items.length);
|
|
39
|
+
*
|
|
40
|
+
* return await processPayment(order);
|
|
41
|
+
* }
|
|
42
|
+
* );
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export const withSpan = (options, fn) => {
|
|
46
|
+
const tracerName = options.tracerName ?? 'nimbus';
|
|
47
|
+
const tracer = trace.getTracer(tracerName);
|
|
48
|
+
return (...args) => {
|
|
49
|
+
const parentContext = context.active();
|
|
50
|
+
return tracer.startActiveSpan(options.name, {
|
|
51
|
+
kind: options.kind ?? SpanKind.INTERNAL,
|
|
52
|
+
attributes: options.attributes,
|
|
53
|
+
}, parentContext, (span) => {
|
|
54
|
+
try {
|
|
55
|
+
const result = fn(...args, span);
|
|
56
|
+
// Handle promises
|
|
57
|
+
if (result instanceof Promise) {
|
|
58
|
+
return result
|
|
59
|
+
.then((value) => {
|
|
60
|
+
span.end();
|
|
61
|
+
return value;
|
|
62
|
+
})
|
|
63
|
+
.catch((err) => {
|
|
64
|
+
span.setStatus({
|
|
65
|
+
code: SpanStatusCode.ERROR,
|
|
66
|
+
message: err.message,
|
|
67
|
+
});
|
|
68
|
+
span.recordException(err);
|
|
69
|
+
span.end();
|
|
70
|
+
throw err;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
span.end();
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
span.setStatus({
|
|
78
|
+
code: SpanStatusCode.ERROR,
|
|
79
|
+
message: err.message,
|
|
80
|
+
});
|
|
81
|
+
span.recordException(err);
|
|
82
|
+
span.end();
|
|
83
|
+
throw err;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
};
|
package/esm/package.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nimbus-cqrs/core",
|
|
3
|
+
"version": "2.0.0-beta.0",
|
|
4
|
+
"description": "Core building blocks of the Nimbus CQRS framework.",
|
|
5
|
+
"author": "Daniel Gördes <d.goerdes@overlap.at> (https://overlap.at)",
|
|
6
|
+
"homepage": "https://nimbus.overlap.at",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/overlap-dev/Nimbus"
|
|
10
|
+
},
|
|
11
|
+
"license": "Apache-2.0",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/overlap-dev/Nimbus/issues"
|
|
14
|
+
},
|
|
15
|
+
"module": "./esm/index.js",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"import": "./esm/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"scripts": {},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@opentelemetry/api": "^1.9.1",
|
|
27
|
+
"zod": "^4.3.6"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^22.0.0"
|
|
31
|
+
},
|
|
32
|
+
"_generatedBy": "dnt@dev"
|
|
33
|
+
}
|