navis.js 1.0.0 → 3.0.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.
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Navis.js v3 Features Demo
3
+ * Demonstrates async messaging, observability, and enhanced features
4
+ */
5
+
6
+ const {
7
+ Logger,
8
+ Metrics,
9
+ Tracer,
10
+ SQSMessaging,
11
+ KafkaMessaging,
12
+ NATSMessaging,
13
+ } = require('../src/index');
14
+
15
+ // Example 1: Structured Logging
16
+ async function demoLogger() {
17
+ console.log('\n=== Structured Logging (v3) ===\n');
18
+
19
+ const logger = new Logger({
20
+ level: 'DEBUG',
21
+ format: 'text',
22
+ enableColors: true,
23
+ context: { service: 'demo-service', version: '1.0.0' },
24
+ });
25
+
26
+ logger.debug('Debug message', { userId: 123 });
27
+ logger.info('User logged in', { userId: 123, ip: '192.168.1.1' });
28
+ logger.warn('Rate limit approaching', { userId: 123, requests: 95 });
29
+ logger.error('Failed to process request', new Error('Connection timeout'), { userId: 123 });
30
+
31
+ // Child logger with additional context
32
+ const requestLogger = logger.child({ requestId: 'req-123' });
33
+ requestLogger.info('Processing request');
34
+ }
35
+
36
+ // Example 2: Metrics Collection
37
+ async function demoMetrics() {
38
+ console.log('\n=== Metrics Collection (v3) ===\n');
39
+
40
+ const metrics = new Metrics();
41
+
42
+ // Record some metrics
43
+ metrics.increment('api_calls_total', 1, { endpoint: '/users', method: 'GET' });
44
+ metrics.increment('api_calls_total', 1, { endpoint: '/users', method: 'POST' });
45
+ metrics.gauge('active_connections', 42);
46
+ metrics.histogram('response_time_ms', 150, { endpoint: '/users' });
47
+ metrics.histogram('response_time_ms', 200, { endpoint: '/users' });
48
+ metrics.histogram('response_time_ms', 120, { endpoint: '/users' });
49
+
50
+ // Record HTTP request
51
+ metrics.recordRequest('GET', '/users', 150, 200);
52
+ metrics.recordRequest('POST', '/users', 250, 201);
53
+
54
+ // Get all metrics
55
+ const allMetrics = metrics.getAll();
56
+ console.log('All metrics:', JSON.stringify(allMetrics, null, 2));
57
+
58
+ // Prometheus format
59
+ console.log('\nPrometheus format:');
60
+ console.log(metrics.toPrometheus());
61
+ }
62
+
63
+ // Example 3: Distributed Tracing
64
+ async function demoTracer() {
65
+ console.log('\n=== Distributed Tracing (v3) ===\n');
66
+
67
+ const tracer = new Tracer({ serviceName: 'demo-service' });
68
+
69
+ // Start a trace
70
+ const traceId = tracer.startTrace('user-request');
71
+ console.log('Trace ID:', traceId);
72
+
73
+ // Start spans
74
+ const span1 = tracer.startSpan('database-query', { traceId });
75
+ tracer.addTag(span1, 'db.type', 'postgres');
76
+ tracer.addTag(span1, 'db.query', 'SELECT * FROM users');
77
+ tracer.addLog(span1, 'Query executed', { rows: 10 });
78
+
79
+ // Simulate work
80
+ await new Promise(resolve => setTimeout(resolve, 100));
81
+
82
+ tracer.finishSpan(span1, { status: 'ok' });
83
+
84
+ const span2 = tracer.startSpan('cache-lookup', { traceId, parentSpanId: span1 });
85
+ tracer.addTag(span2, 'cache.type', 'redis');
86
+ tracer.finishSpan(span2, { status: 'ok' });
87
+
88
+ // Get trace
89
+ const trace = tracer.getTrace(traceId);
90
+ console.log('Trace data:', JSON.stringify(trace, null, 2));
91
+ }
92
+
93
+ // Example 4: SQS Messaging (requires AWS SDK)
94
+ async function demoSQS() {
95
+ console.log('\n=== SQS Messaging (v3) ===\n');
96
+ console.log('Note: Requires @aws-sdk/client-sqs and AWS credentials');
97
+
98
+ try {
99
+ const sqs = new SQSMessaging({
100
+ region: 'us-east-1',
101
+ queueUrl: process.env.SQS_QUEUE_URL,
102
+ });
103
+
104
+ await sqs.connect();
105
+ console.log('✅ Connected to SQS');
106
+
107
+ // Publish message
108
+ const result = await sqs.publish(process.env.SQS_QUEUE_URL, {
109
+ userId: 123,
110
+ action: 'user.created',
111
+ data: { name: 'John Doe' },
112
+ });
113
+ console.log('Published message:', result);
114
+
115
+ // Subscribe (would need actual queue)
116
+ // await sqs.subscribe(process.env.SQS_QUEUE_URL, async (message, metadata) => {
117
+ // console.log('Received:', message);
118
+ // });
119
+
120
+ } catch (error) {
121
+ if (error.message.includes('@aws-sdk/client-sqs')) {
122
+ console.log('⚠️ AWS SDK not installed. Install with: npm install @aws-sdk/client-sqs');
123
+ } else {
124
+ console.log('⚠️ SQS demo requires AWS credentials and queue URL');
125
+ }
126
+ }
127
+ }
128
+
129
+ // Example 5: Kafka Messaging (requires kafkajs)
130
+ async function demoKafka() {
131
+ console.log('\n=== Kafka Messaging (v3) ===\n');
132
+ console.log('Note: Requires kafkajs and Kafka broker');
133
+
134
+ try {
135
+ const kafka = new KafkaMessaging({
136
+ brokers: ['localhost:9092'],
137
+ clientId: 'navis-demo',
138
+ });
139
+
140
+ await kafka.connect();
141
+ console.log('✅ Connected to Kafka');
142
+
143
+ // Publish message
144
+ const result = await kafka.publish('user-events', {
145
+ userId: 123,
146
+ event: 'user.created',
147
+ data: { name: 'Jane Doe' },
148
+ });
149
+ console.log('Published to Kafka:', result);
150
+
151
+ } catch (error) {
152
+ if (error.message.includes('kafkajs')) {
153
+ console.log('⚠️ kafkajs not installed. Install with: npm install kafkajs');
154
+ } else {
155
+ console.log('⚠️ Kafka demo requires running Kafka broker');
156
+ }
157
+ }
158
+ }
159
+
160
+ // Example 6: NATS Messaging (requires nats)
161
+ async function demoNATS() {
162
+ console.log('\n=== NATS Messaging (v3) ===\n');
163
+ console.log('Note: Requires nats and NATS server');
164
+
165
+ try {
166
+ const nats = new NATSMessaging({
167
+ servers: ['nats://localhost:4222'],
168
+ });
169
+
170
+ await nats.connect();
171
+ console.log('✅ Connected to NATS');
172
+
173
+ // Publish message
174
+ const result = await nats.publish('user.created', {
175
+ userId: 123,
176
+ name: 'Bob Smith',
177
+ });
178
+ console.log('Published to NATS:', result);
179
+
180
+ // Request-reply pattern
181
+ // const response = await nats.request('user.get', { userId: 123 });
182
+ // console.log('Response:', response);
183
+
184
+ } catch (error) {
185
+ if (error.message.includes('nats')) {
186
+ console.log('⚠️ nats not installed. Install with: npm install nats');
187
+ } else {
188
+ console.log('⚠️ NATS demo requires running NATS server');
189
+ }
190
+ }
191
+ }
192
+
193
+ // Run all demos
194
+ async function runDemos() {
195
+ console.log('🚀 Navis.js v3 Features Demo\n');
196
+ console.log('='.repeat(50));
197
+
198
+ await demoLogger();
199
+ await demoMetrics();
200
+ await demoTracer();
201
+ await demoSQS();
202
+ await demoKafka();
203
+ await demoNATS();
204
+
205
+ console.log('\n' + '='.repeat(50));
206
+ console.log('✅ v3 features demo completed!');
207
+ console.log('\nNote: Messaging adapters require their respective dependencies:');
208
+ console.log(' - SQS: npm install @aws-sdk/client-sqs');
209
+ console.log(' - Kafka: npm install kafkajs');
210
+ console.log(' - NATS: npm install nats');
211
+ }
212
+
213
+ // Run if called directly
214
+ if (require.main === module) {
215
+ runDemos().catch(console.error);
216
+ }
217
+
218
+ module.exports = {
219
+ demoLogger,
220
+ demoMetrics,
221
+ demoTracer,
222
+ demoSQS,
223
+ demoKafka,
224
+ demoNATS,
225
+ };
226
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "navis.js",
3
- "version": "1.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "A lightweight, serverless-first, microservice API framework designed for AWS Lambda and Node.js",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/index.js CHANGED
@@ -1,12 +1,50 @@
1
- const NavisApp = require('./core/app');
2
- const ServiceClient = require('./utils/service-client');
3
- const { success, error } = require('./utils/response');
4
-
5
- module.exports = {
6
- NavisApp,
7
- ServiceClient,
8
- response: {
9
- success,
10
- error,
11
- },
12
- };
1
+ const NavisApp = require('./core/app');
2
+ const ServiceClient = require('./utils/service-client');
3
+ const ServiceConfig = require('./utils/service-config');
4
+ const ServiceDiscovery = require('./utils/service-discovery');
5
+ const CircuitBreaker = require('./utils/circuit-breaker');
6
+ const { success, error } = require('./utils/response');
7
+ const { retry, shouldRetryHttpStatus } = require('./utils/retry');
8
+
9
+ // v3: Async Messaging
10
+ const SQSMessaging = require('./messaging/sqs-adapter');
11
+ const KafkaMessaging = require('./messaging/kafka-adapter');
12
+ const NATSMessaging = require('./messaging/nats-adapter');
13
+
14
+ // v3: Observability
15
+ const Logger = require('./observability/logger');
16
+ const Metrics = require('./observability/metrics');
17
+ const Tracer = require('./observability/tracer');
18
+
19
+ module.exports = {
20
+ // Core
21
+ NavisApp,
22
+
23
+ // Service Client (v2 enhanced)
24
+ ServiceClient,
25
+
26
+ // v2 Features
27
+ ServiceConfig,
28
+ ServiceDiscovery,
29
+ CircuitBreaker,
30
+
31
+ // v3: Async Messaging
32
+ SQSMessaging,
33
+ KafkaMessaging,
34
+ NATSMessaging,
35
+
36
+ // v3: Observability
37
+ Logger,
38
+ Metrics,
39
+ Tracer,
40
+
41
+ // Utilities
42
+ response: {
43
+ success,
44
+ error,
45
+ },
46
+ retry: {
47
+ retry,
48
+ shouldRetryHttpStatus,
49
+ },
50
+ };
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Base Messaging Interface
3
+ * v3: Abstract base class for async messaging adapters
4
+ */
5
+
6
+ class BaseMessaging {
7
+ constructor(options = {}) {
8
+ this.options = options;
9
+ this.subscribers = new Map(); // topic -> [handlers]
10
+ this.isConnected = false;
11
+ }
12
+
13
+ /**
14
+ * Connect to messaging broker
15
+ * @abstract
16
+ */
17
+ async connect() {
18
+ throw new Error('connect() must be implemented by subclass');
19
+ }
20
+
21
+ /**
22
+ * Disconnect from messaging broker
23
+ * @abstract
24
+ */
25
+ async disconnect() {
26
+ throw new Error('disconnect() must be implemented by subclass');
27
+ }
28
+
29
+ /**
30
+ * Publish a message to a topic/queue
31
+ * @abstract
32
+ * @param {string} topic - Topic/queue name
33
+ * @param {Object} message - Message payload
34
+ * @param {Object} options - Publishing options
35
+ */
36
+ async publish(topic, message, options = {}) {
37
+ throw new Error('publish() must be implemented by subclass');
38
+ }
39
+
40
+ /**
41
+ * Subscribe to a topic/queue
42
+ * @abstract
43
+ * @param {string} topic - Topic/queue name
44
+ * @param {Function} handler - Message handler function
45
+ * @param {Object} options - Subscription options
46
+ */
47
+ async subscribe(topic, handler, options = {}) {
48
+ throw new Error('subscribe() must be implemented by subclass');
49
+ }
50
+
51
+ /**
52
+ * Unsubscribe from a topic/queue
53
+ * @abstract
54
+ * @param {string} topic - Topic/queue name
55
+ * @param {Function} handler - Optional specific handler to unsubscribe
56
+ */
57
+ async unsubscribe(topic, handler = null) {
58
+ throw new Error('unsubscribe() must be implemented by subclass');
59
+ }
60
+
61
+ /**
62
+ * Register a handler for a topic
63
+ * @protected
64
+ */
65
+ _registerHandler(topic, handler) {
66
+ if (!this.subscribers.has(topic)) {
67
+ this.subscribers.set(topic, []);
68
+ }
69
+ this.subscribers.get(topic).push(handler);
70
+ }
71
+
72
+ /**
73
+ * Get all handlers for a topic
74
+ * @protected
75
+ */
76
+ _getHandlers(topic) {
77
+ return this.subscribers.get(topic) || [];
78
+ }
79
+ }
80
+
81
+ module.exports = BaseMessaging;
82
+
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Kafka Messaging Adapter
3
+ * v3: Kafka integration for async messaging
4
+ *
5
+ * Note: Requires kafkajs to be installed
6
+ * npm install kafkajs
7
+ */
8
+
9
+ const BaseMessaging = require('./base-messaging');
10
+
11
+ class KafkaMessaging extends BaseMessaging {
12
+ constructor(options = {}) {
13
+ super(options);
14
+ this.brokers = options.brokers || ['localhost:9092'];
15
+ this.clientId = options.clientId || 'navis-client';
16
+ this.consumerGroupId = options.consumerGroupId || 'navis-group';
17
+ this.kafka = null;
18
+ this.producer = null;
19
+ this.consumer = null;
20
+ }
21
+
22
+ /**
23
+ * Connect to Kafka
24
+ */
25
+ async connect() {
26
+ try {
27
+ const { Kafka } = require('kafkajs');
28
+ this.Kafka = Kafka;
29
+ } catch (err) {
30
+ throw new Error('kafkajs is required. Install it with: npm install kafkajs');
31
+ }
32
+
33
+ this.kafka = new this.Kafka({
34
+ clientId: this.clientId,
35
+ brokers: this.brokers,
36
+ ...this.options.kafkaConfig,
37
+ });
38
+
39
+ this.producer = this.kafka.producer();
40
+ await this.producer.connect();
41
+
42
+ this.consumer = this.kafka.consumer({ groupId: this.consumerGroupId });
43
+ await this.consumer.connect();
44
+
45
+ this.isConnected = true;
46
+ return this;
47
+ }
48
+
49
+ /**
50
+ * Disconnect from Kafka
51
+ */
52
+ async disconnect() {
53
+ this.isConnected = false;
54
+
55
+ if (this.producer) {
56
+ await this.producer.disconnect();
57
+ this.producer = null;
58
+ }
59
+
60
+ if (this.consumer) {
61
+ await this.consumer.disconnect();
62
+ this.consumer = null;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Publish message to Kafka topic
68
+ * @param {string} topic - Kafka topic name
69
+ * @param {Object} message - Message payload
70
+ * @param {Object} options - Publishing options
71
+ */
72
+ async publish(topic, message, options = {}) {
73
+ if (!this.isConnected) {
74
+ await this.connect();
75
+ }
76
+
77
+ const result = await this.producer.send({
78
+ topic,
79
+ messages: [{
80
+ key: options.key || null,
81
+ value: JSON.stringify(message),
82
+ headers: options.headers || {},
83
+ }],
84
+ });
85
+
86
+ return {
87
+ topic,
88
+ partition: result[0].partition,
89
+ offset: result[0].offset,
90
+ };
91
+ }
92
+
93
+ /**
94
+ * Subscribe to Kafka topic
95
+ * @param {string} topic - Kafka topic name
96
+ * @param {Function} handler - Message handler function
97
+ * @param {Object} options - Subscription options
98
+ */
99
+ async subscribe(topic, handler, options = {}) {
100
+ if (!this.isConnected) {
101
+ await this.connect();
102
+ }
103
+
104
+ this._registerHandler(topic, handler);
105
+
106
+ await this.consumer.subscribe({
107
+ topic,
108
+ fromBeginning: options.fromBeginning || false,
109
+ });
110
+
111
+ await this.consumer.run({
112
+ eachMessage: async ({ topic, partition, message }) => {
113
+ try {
114
+ const body = JSON.parse(message.value.toString());
115
+ const handlers = this._getHandlers(topic);
116
+
117
+ for (const h of handlers) {
118
+ await h(body, {
119
+ topic,
120
+ partition,
121
+ offset: message.offset,
122
+ key: message.key?.toString(),
123
+ headers: message.headers,
124
+ });
125
+ }
126
+ } catch (error) {
127
+ console.error('Error processing Kafka message:', error);
128
+ }
129
+ },
130
+ });
131
+
132
+ return { topic, handler };
133
+ }
134
+
135
+ /**
136
+ * Unsubscribe from topic
137
+ */
138
+ async unsubscribe(topic, handler = null) {
139
+ if (handler) {
140
+ const handlers = this._getHandlers(topic);
141
+ const index = handlers.indexOf(handler);
142
+ if (index > -1) {
143
+ handlers.splice(index, 1);
144
+ }
145
+ } else {
146
+ this.subscribers.delete(topic);
147
+ }
148
+ }
149
+ }
150
+
151
+ module.exports = KafkaMessaging;
152
+
@@ -0,0 +1,141 @@
1
+ /**
2
+ * NATS Messaging Adapter
3
+ * v3: NATS integration for async messaging
4
+ *
5
+ * Note: Requires nats to be installed
6
+ * npm install nats
7
+ */
8
+
9
+ const BaseMessaging = require('./base-messaging');
10
+
11
+ class NATSMessaging extends BaseMessaging {
12
+ constructor(options = {}) {
13
+ super(options);
14
+ this.servers = options.servers || ['nats://localhost:4222'];
15
+ this.nc = null;
16
+ }
17
+
18
+ /**
19
+ * Connect to NATS
20
+ */
21
+ async connect() {
22
+ try {
23
+ const { connect } = require('nats');
24
+ this.connectNATS = connect;
25
+ } catch (err) {
26
+ throw new Error('nats is required. Install it with: npm install nats');
27
+ }
28
+
29
+ this.nc = await this.connectNATS({
30
+ servers: this.servers,
31
+ ...this.options.natsConfig,
32
+ });
33
+
34
+ this.isConnected = true;
35
+ return this;
36
+ }
37
+
38
+ /**
39
+ * Disconnect from NATS
40
+ */
41
+ async disconnect() {
42
+ this.isConnected = false;
43
+ if (this.nc) {
44
+ await this.nc.close();
45
+ this.nc = null;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Publish message to NATS subject
51
+ * @param {string} subject - NATS subject
52
+ * @param {Object} message - Message payload
53
+ * @param {Object} options - Publishing options
54
+ */
55
+ async publish(subject, message, options = {}) {
56
+ if (!this.isConnected) {
57
+ await this.connect();
58
+ }
59
+
60
+ const data = Buffer.from(JSON.stringify(message));
61
+ this.nc.publish(subject, data);
62
+
63
+ return { subject, published: true };
64
+ }
65
+
66
+ /**
67
+ * Subscribe to NATS subject
68
+ * @param {string} subject - NATS subject
69
+ * @param {Function} handler - Message handler function
70
+ * @param {Object} options - Subscription options
71
+ */
72
+ async subscribe(subject, handler, options = {}) {
73
+ if (!this.isConnected) {
74
+ await this.connect();
75
+ }
76
+
77
+ this._registerHandler(subject, handler);
78
+
79
+ const subscription = this.nc.subscribe(subject, {
80
+ queue: options.queue || undefined,
81
+ });
82
+
83
+ (async () => {
84
+ for await (const msg of subscription) {
85
+ try {
86
+ const body = JSON.parse(msg.data.toString());
87
+ const handlers = this._getHandlers(subject);
88
+
89
+ for (const h of handlers) {
90
+ await h(body, {
91
+ subject: msg.subject,
92
+ reply: msg.reply,
93
+ sid: msg.sid,
94
+ });
95
+ }
96
+ } catch (error) {
97
+ console.error('Error processing NATS message:', error);
98
+ }
99
+ }
100
+ })().catch(console.error);
101
+
102
+ return { subject, handler, subscription };
103
+ }
104
+
105
+ /**
106
+ * Unsubscribe from subject
107
+ */
108
+ async unsubscribe(subject, handler = null) {
109
+ if (handler) {
110
+ const handlers = this._getHandlers(subject);
111
+ const index = handlers.indexOf(handler);
112
+ if (index > -1) {
113
+ handlers.splice(index, 1);
114
+ }
115
+ } else {
116
+ this.subscribers.delete(subject);
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Request-reply pattern
122
+ * @param {string} subject - NATS subject
123
+ * @param {Object} message - Request message
124
+ * @param {Object} options - Request options
125
+ */
126
+ async request(subject, message, options = {}) {
127
+ if (!this.isConnected) {
128
+ await this.connect();
129
+ }
130
+
131
+ const data = Buffer.from(JSON.stringify(message));
132
+ const response = await this.nc.request(subject, data, {
133
+ timeout: options.timeout || 5000,
134
+ });
135
+
136
+ return JSON.parse(response.data.toString());
137
+ }
138
+ }
139
+
140
+ module.exports = NATSMessaging;
141
+