@mttickets12/common 1.0.22 → 1.0.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,228 @@
1
+ # Common Package
2
+
3
+ Shared code package for microservices in the ticketing system.
4
+
5
+ ## Overview
6
+
7
+ The `@mttickets12/common` package contains shared code used across all microservices, including error classes, event definitions, middleware, and base classes for event handling.
8
+
9
+ ## Installation
10
+
11
+ This package is published to npm. Install it in your service:
12
+
13
+ ```bash
14
+ npm install @mttickets12/common
15
+ ```
16
+
17
+ For local development, use `npm link`:
18
+
19
+ ```bash
20
+ # In common directory
21
+ npm link
22
+
23
+ # In service directory
24
+ npm link @mttickets12/common
25
+ ```
26
+
27
+ ## Building
28
+
29
+ Build the package:
30
+
31
+ ```bash
32
+ npm run build
33
+ ```
34
+
35
+ This compiles TypeScript to JavaScript in the `build/` directory.
36
+
37
+ ## Publishing
38
+
39
+ Publish to npm:
40
+
41
+ ```bash
42
+ npm run pub
43
+ ```
44
+
45
+ This will:
46
+ 1. Clean the build directory
47
+ 2. Build the package
48
+ 3. Increment patch version
49
+ 4. Publish to npm
50
+
51
+ ## Contents
52
+
53
+ ### Errors
54
+
55
+ Custom error classes for consistent error handling:
56
+
57
+ - `BadRequestError` - 400 status code
58
+ - `NotAuthorizedError` - 401 status code
59
+ - `NotFoundError` - 404 status code
60
+ - `RequestValidationError` - 400 status code (validation errors)
61
+ - `DatabaseValidationError` - 400 status code (database errors)
62
+ - `CustomError` - Base error class
63
+
64
+ ### Middleware
65
+
66
+ Express middleware functions:
67
+
68
+ - `currentUser` - Extracts and validates JWT token, sets `req.currentUser`
69
+ - `requireAuth` - Ensures user is authenticated
70
+ - `validateRequest` - Validates request using express-validator
71
+ - `errorHandler` - Centralized error handling middleware
72
+
73
+ ### Events
74
+
75
+ Event-driven architecture components:
76
+
77
+ #### Base Classes
78
+ - `BaseListener` - Abstract base class for event listeners
79
+ - `BasePublisher` - Abstract base class for event publishers
80
+
81
+ #### Event Types
82
+ - `TicketCreatedEvent`
83
+ - `TicketUpdatedEvent`
84
+ - `OrderCreatedEvent`
85
+ - `OrderCancelledEvent`
86
+ - `ExpirationCompleteEvent`
87
+ - `PaymentCreatedEvent`
88
+
89
+ #### Subjects
90
+ Type-safe event subject constants:
91
+ - `Subject.TicketCreated`
92
+ - `Subject.TicketUpdated`
93
+ - `Subject.OrderCreated`
94
+ - `Subject.OrderCancelled`
95
+ - `Subject.ExpirationComplete`
96
+ - `Subject.PaymentCreated`
97
+
98
+ ## Usage Examples
99
+
100
+ ### Using Middleware
101
+
102
+ ```typescript
103
+ import { requireAuth, currentUser } from '@mttickets12/common';
104
+ import express from 'express';
105
+
106
+ const router = express.Router();
107
+
108
+ // Protected route
109
+ router.get('/api/orders', currentUser, requireAuth, async (req, res) => {
110
+ // req.currentUser is available here
111
+ res.send({ userId: req.currentUser!.id });
112
+ });
113
+ ```
114
+
115
+ ### Using Error Classes
116
+
117
+ ```typescript
118
+ import { NotFoundError, BadRequestError } from '@mttickets12/common';
119
+
120
+ if (!ticket) {
121
+ throw new NotFoundError();
122
+ }
123
+
124
+ if (price < 0) {
125
+ throw new BadRequestError('Price must be positive');
126
+ }
127
+ ```
128
+
129
+ ### Creating Event Listeners
130
+
131
+ ```typescript
132
+ import { Listener, TicketCreatedEvent, Subjects } from '@mttickets12/common';
133
+ import { Message } from 'node-nats-streaming';
134
+
135
+ class TicketCreatedListener extends Listener<TicketCreatedEvent> {
136
+ readonly subject = Subjects.TicketCreated;
137
+ queueGroupName = 'orders-service';
138
+
139
+ async onMessage(data: TicketCreatedEvent['data'], msg: Message) {
140
+ // Handle event
141
+ console.log('Ticket created:', data);
142
+ msg.ack();
143
+ }
144
+ }
145
+ ```
146
+
147
+ ### Creating Event Publishers
148
+
149
+ ```typescript
150
+ import { Publisher, TicketCreatedEvent, Subjects } from '@mttickets12/common';
151
+
152
+ class TicketCreatedPublisher extends Publisher<TicketCreatedEvent> {
153
+ readonly subject = Subjects.TicketCreated;
154
+ }
155
+
156
+ // Usage
157
+ await new TicketCreatedPublisher(natsClient).publish({
158
+ id: 'ticket_id',
159
+ title: 'Concert',
160
+ price: 50,
161
+ userId: 'user_id',
162
+ version: 0
163
+ });
164
+ ```
165
+
166
+ ### Using Validation Middleware
167
+
168
+ ```typescript
169
+ import { validateRequest } from '@mttickets12/common';
170
+ import { body } from 'express-validator';
171
+
172
+ router.post(
173
+ '/api/tickets',
174
+ [
175
+ body('title').notEmpty().withMessage('Title is required'),
176
+ body('price').isFloat({ gt: 0 }).withMessage('Price must be greater than 0')
177
+ ],
178
+ validateRequest,
179
+ async (req, res) => {
180
+ // Request is validated here
181
+ }
182
+ );
183
+ ```
184
+
185
+ ## Type Safety
186
+
187
+ The package is written in TypeScript and provides full type safety:
188
+ - Event data types
189
+ - Error types
190
+ - Middleware request types
191
+
192
+ ## Versioning
193
+
194
+ The package follows semantic versioning:
195
+ - **Patch** (1.0.x) - Bug fixes, no breaking changes
196
+ - **Minor** (1.x.0) - New features, backward compatible
197
+ - **Major** (x.0.0) - Breaking changes
198
+
199
+ When updating the package:
200
+ 1. Make changes
201
+ 2. Update version: `npm version patch|minor|major`
202
+ 3. Build: `npm run build`
203
+ 4. Publish: `npm publish --access public`
204
+
205
+ ## Dependencies
206
+
207
+ - **express** - Web framework types
208
+ - **express-validator** - Request validation
209
+ - **jsonwebtoken** - JWT types
210
+ - **cookie-session** - Session types
211
+ - **node-nats-streaming** - NATS types
212
+
213
+ ## Development
214
+
215
+ 1. **Make changes** to TypeScript files in `src/`
216
+ 2. **Build** the package: `npm run build`
217
+ 3. **Test** in consuming services
218
+ 4. **Publish** when ready: `npm run pub`
219
+
220
+ ## Contributing
221
+
222
+ When adding new shared code:
223
+ 1. Ensure it's truly shared across services
224
+ 2. Maintain backward compatibility
225
+ 3. Add proper TypeScript types
226
+ 4. Update this README
227
+ 5. Test in at least one consuming service
228
+
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Listener = void 0;
4
+ const api_1 = require("@opentelemetry/api");
5
+ const tracer = api_1.trace.getTracer('nats-listener');
4
6
  class Listener {
5
7
  constructor(client) {
6
8
  this.ackWait = 5 * 1000;
@@ -19,7 +21,39 @@ class Listener {
19
21
  subscription.on("message", (msg) => {
20
22
  console.log(`Message received: ${this.subject} / ${this.queueGroupName}`);
21
23
  const parsedData = this.parseMessage(msg);
22
- this.onMessage(parsedData, msg);
24
+ // Extract tracing context from message
25
+ const tracingContext = parsedData._tracing;
26
+ delete parsedData._tracing; // Remove tracing data from actual data
27
+ // Create span for message processing
28
+ const span = tracer.startSpan(`nats.listen.${this.subject}`, {
29
+ attributes: {
30
+ 'messaging.system': 'nats',
31
+ 'messaging.destination': this.subject,
32
+ 'messaging.operation': 'receive',
33
+ 'messaging.queue': this.queueGroupName,
34
+ },
35
+ });
36
+ // If we have tracing context, link to parent span
37
+ if (tracingContext) {
38
+ span.setAttributes({
39
+ 'messaging.trace_id': tracingContext.traceId,
40
+ 'messaging.parent_span_id': tracingContext.spanId,
41
+ });
42
+ }
43
+ // Execute message handler within span context
44
+ api_1.trace.setSpan(api_1.context.active(), span);
45
+ try {
46
+ this.onMessage(parsedData, msg);
47
+ span.setStatus({ code: 1 }); // OK
48
+ }
49
+ catch (error) {
50
+ span.recordException(error);
51
+ span.setStatus({ code: 2, message: error.message }); // ERROR
52
+ throw error;
53
+ }
54
+ finally {
55
+ span.end();
56
+ }
23
57
  });
24
58
  }
25
59
  parseMessage(msg) {
@@ -1,21 +1,53 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Publisher = void 0;
4
+ const api_1 = require("@opentelemetry/api");
5
+ const tracer = api_1.trace.getTracer('nats-publisher');
4
6
  class Publisher {
5
7
  constructor(client) {
6
8
  this.client = client;
7
9
  }
8
- publish(data) {
9
- return new Promise((resolve, reject) => {
10
- const dataString = JSON.stringify(data);
11
- this.client.publish(this.subject, dataString, (err) => {
12
- if (err) {
13
- return reject(err);
14
- }
15
- console.log("Event published to subject", this.subject);
16
- resolve();
17
- });
10
+ async publish(data) {
11
+ const span = tracer.startSpan(`nats.publish.${this.subject}`, {
12
+ attributes: {
13
+ 'messaging.system': 'nats',
14
+ 'messaging.destination': this.subject,
15
+ 'messaging.operation': 'publish',
16
+ },
18
17
  });
18
+ try {
19
+ // Inject tracing context into data
20
+ const spanContext = span.spanContext();
21
+ const dataWithTracing = {
22
+ ...data,
23
+ _tracing: {
24
+ traceId: spanContext.traceId,
25
+ spanId: spanContext.spanId,
26
+ traceFlags: spanContext.traceFlags.toString(),
27
+ },
28
+ };
29
+ return new Promise((resolve, reject) => {
30
+ const dataString = JSON.stringify(dataWithTracing);
31
+ this.client.publish(this.subject, dataString, (err) => {
32
+ if (err) {
33
+ span.recordException(err);
34
+ span.setStatus({ code: 2, message: err.message }); // ERROR
35
+ span.end();
36
+ return reject(err);
37
+ }
38
+ span.setStatus({ code: 1 }); // OK
39
+ span.end();
40
+ console.log("Event published to subject", this.subject);
41
+ resolve();
42
+ });
43
+ });
44
+ }
45
+ catch (error) {
46
+ span.recordException(error);
47
+ span.setStatus({ code: 2, message: error.message });
48
+ span.end();
49
+ throw error;
50
+ }
19
51
  }
20
52
  }
21
53
  exports.Publisher = Publisher;
@@ -0,0 +1,9 @@
1
+ import { Subjects } from "./subjects";
2
+ export interface PaymentCreatedEvent {
3
+ subject: Subjects.PaymentCreated;
4
+ data: {
5
+ id: string;
6
+ orderId: string;
7
+ stripeId: string;
8
+ };
9
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -3,5 +3,6 @@ export declare enum Subjects {
3
3
  TicketUpdated = "ticket:updated",
4
4
  OrderCreated = "order:created",
5
5
  OrderCancelled = "order:cancelled",
6
- ExpirationComplete = "expiration:complete"
6
+ ExpirationComplete = "expiration:complete",
7
+ PaymentCreated = "payment:created"
7
8
  }
@@ -8,4 +8,5 @@ var Subjects;
8
8
  Subjects["OrderCreated"] = "order:created";
9
9
  Subjects["OrderCancelled"] = "order:cancelled";
10
10
  Subjects["ExpirationComplete"] = "expiration:complete";
11
+ Subjects["PaymentCreated"] = "payment:created";
11
12
  })(Subjects || (exports.Subjects = Subjects = {}));
package/build/index.d.ts CHANGED
@@ -17,3 +17,7 @@ export * from "./events/types/order-status";
17
17
  export * from "./events/order-cancelled-event";
18
18
  export * from "./events/order-created-event";
19
19
  export * from "./events/expiration-complete-event";
20
+ export * from "./events/payment-created-event";
21
+ export * from "./tracing/tracer";
22
+ export * from "./tracing/nats-tracing";
23
+ export * from "./tracing/setup";
package/build/index.js CHANGED
@@ -33,3 +33,7 @@ __exportStar(require("./events/types/order-status"), exports);
33
33
  __exportStar(require("./events/order-cancelled-event"), exports);
34
34
  __exportStar(require("./events/order-created-event"), exports);
35
35
  __exportStar(require("./events/expiration-complete-event"), exports);
36
+ __exportStar(require("./events/payment-created-event"), exports);
37
+ __exportStar(require("./tracing/tracer"), exports);
38
+ __exportStar(require("./tracing/nats-tracing"), exports);
39
+ __exportStar(require("./tracing/setup"), exports);
@@ -0,0 +1,23 @@
1
+ import { Span } from '@opentelemetry/api';
2
+ import { Message } from 'node-nats-streaming';
3
+ export interface TracingContext {
4
+ traceId?: string;
5
+ spanId?: string;
6
+ traceFlags?: string;
7
+ }
8
+ /**
9
+ * Extract tracing context from NATS message
10
+ */
11
+ export declare const extractTracingContext: (msg: Message) => TracingContext | undefined;
12
+ /**
13
+ * Inject tracing context into message data
14
+ */
15
+ export declare const injectTracingContext: (data: any, span: Span) => any;
16
+ /**
17
+ * Create a span for NATS event publishing
18
+ */
19
+ export declare const createPublishSpan: (eventName: string, data: any) => Span;
20
+ /**
21
+ * Create a span for NATS event listening
22
+ */
23
+ export declare const createListenSpan: (eventName: string, msg: Message) => Span;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createListenSpan = exports.createPublishSpan = exports.injectTracingContext = exports.extractTracingContext = void 0;
4
+ const api_1 = require("@opentelemetry/api");
5
+ const tracer = api_1.trace.getTracer('nats-tracing');
6
+ /**
7
+ * Extract tracing context from NATS message
8
+ */
9
+ const extractTracingContext = (msg) => {
10
+ try {
11
+ const data = msg.getData();
12
+ const parsed = JSON.parse(data.toString());
13
+ return parsed._tracing;
14
+ }
15
+ catch (error) {
16
+ return undefined;
17
+ }
18
+ };
19
+ exports.extractTracingContext = extractTracingContext;
20
+ /**
21
+ * Inject tracing context into message data
22
+ */
23
+ const injectTracingContext = (data, span) => {
24
+ const spanContext = span.spanContext();
25
+ return {
26
+ ...data,
27
+ _tracing: {
28
+ traceId: spanContext.traceId,
29
+ spanId: spanContext.spanId,
30
+ traceFlags: spanContext.traceFlags.toString(),
31
+ },
32
+ };
33
+ };
34
+ exports.injectTracingContext = injectTracingContext;
35
+ /**
36
+ * Create a span for NATS event publishing
37
+ */
38
+ const createPublishSpan = (eventName, data) => {
39
+ return tracer.startSpan(`nats.publish.${eventName}`, {
40
+ attributes: {
41
+ 'messaging.system': 'nats',
42
+ 'messaging.destination': eventName,
43
+ 'messaging.operation': 'publish',
44
+ },
45
+ });
46
+ };
47
+ exports.createPublishSpan = createPublishSpan;
48
+ /**
49
+ * Create a span for NATS event listening
50
+ */
51
+ const createListenSpan = (eventName, msg) => {
52
+ const context = (0, exports.extractTracingContext)(msg);
53
+ return tracer.startSpan(`nats.listen.${eventName}`, {
54
+ attributes: {
55
+ 'messaging.system': 'nats',
56
+ 'messaging.destination': eventName,
57
+ 'messaging.operation': 'receive',
58
+ },
59
+ });
60
+ };
61
+ exports.createListenSpan = createListenSpan;
@@ -0,0 +1 @@
1
+ export declare const setupTracing: (serviceName: string) => import("@opentelemetry/sdk-node").NodeSDK | null;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ // This file should be imported at the very top of your service's index.ts file
3
+ // BEFORE any other imports to ensure tracing is initialized early
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.setupTracing = void 0;
6
+ const tracer_1 = require("./tracer");
7
+ const setupTracing = (serviceName) => {
8
+ // Only initialize if JAEGER_ENABLED is true (default: true in production)
9
+ if (process.env.JAEGER_ENABLED !== 'false') {
10
+ return (0, tracer_1.initTracer)(serviceName);
11
+ }
12
+ return null;
13
+ };
14
+ exports.setupTracing = setupTracing;
@@ -0,0 +1,2 @@
1
+ import { NodeSDK } from '@opentelemetry/sdk-node';
2
+ export declare const initTracer: (serviceName: string) => NodeSDK;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.initTracer = void 0;
4
+ const sdk_node_1 = require("@opentelemetry/sdk-node");
5
+ const auto_instrumentations_node_1 = require("@opentelemetry/auto-instrumentations-node");
6
+ const exporter_jaeger_1 = require("@opentelemetry/exporter-jaeger");
7
+ const resources_1 = require("@opentelemetry/resources");
8
+ const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
9
+ const initTracer = (serviceName) => {
10
+ const jaegerExporter = new exporter_jaeger_1.JaegerExporter({
11
+ endpoint: process.env.JAEGER_ENDPOINT || 'http://jaeger-srv:14268/api/traces',
12
+ });
13
+ const sdk = new sdk_node_1.NodeSDK({
14
+ resource: new resources_1.Resource({
15
+ [semantic_conventions_1.SemanticResourceAttributes.SERVICE_NAME]: serviceName,
16
+ }),
17
+ traceExporter: jaegerExporter,
18
+ instrumentations: [
19
+ (0, auto_instrumentations_node_1.getNodeAutoInstrumentations)({
20
+ // Disable fs instrumentation to reduce noise
21
+ '@opentelemetry/instrumentation-fs': {
22
+ enabled: false,
23
+ },
24
+ }),
25
+ ],
26
+ });
27
+ sdk.start();
28
+ console.log(`Tracing initialized for service: ${serviceName}`);
29
+ return sdk;
30
+ };
31
+ exports.initTracer = initTracer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mttickets12/common",
3
- "version": "1.0.22",
3
+ "version": "1.0.25",
4
4
  "main": "./build/index.js",
5
5
  "types": "./build/index.d.ts",
6
6
  "files": [
@@ -20,6 +20,13 @@
20
20
  "typescript": "^5.9.3"
21
21
  },
22
22
  "dependencies": {
23
+ "@opentelemetry/api": "^1.8.0",
24
+ "@opentelemetry/auto-instrumentations-node": "^0.45.0",
25
+ "@opentelemetry/core": "^1.24.0",
26
+ "@opentelemetry/exporter-jaeger": "^1.24.0",
27
+ "@opentelemetry/resources": "^1.24.0",
28
+ "@opentelemetry/sdk-node": "^0.50.0",
29
+ "@opentelemetry/semantic-conventions": "^1.24.0",
23
30
  "@types/cookie-session": "^2.0.49",
24
31
  "@types/express": "^4.17.21",
25
32
  "@types/jsonwebtoken": "^9.0.10",