@devminister/applog-client 0.0.1
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 +214 -0
- package/dist/applog-nestjs.service.d.ts +11 -0
- package/dist/applog-nestjs.service.js +44 -0
- package/dist/applog.interceptor.d.ts +24 -0
- package/dist/applog.interceptor.js +80 -0
- package/dist/applog.module.d.ts +33 -0
- package/dist/applog.module.js +71 -0
- package/dist/applog.service.d.ts +55 -0
- package/dist/applog.service.js +154 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +17 -0
- package/dist/interfaces/applog-config.interface.d.ts +31 -0
- package/dist/interfaces/applog-config.interface.js +4 -0
- package/dist/standalone.d.ts +19 -0
- package/dist/standalone.js +23 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# @devminister/applog-client
|
|
2
|
+
|
|
3
|
+
Application log client for the **Monitoring** platform. Send structured business logs (auth, payments, orders, notifications) with buffered, batched delivery, retry, and duration tracking.
|
|
4
|
+
|
|
5
|
+
Works as a **standalone Node.js client** or as a **NestJS module**.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @devminister/applog-client
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
### Standalone (Express, Fastify, any Node.js app)
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { createAppLogClient } from '@devminister/applog-client';
|
|
19
|
+
|
|
20
|
+
const appLog = createAppLogClient({
|
|
21
|
+
apiUrl: 'https://monitor.example.com',
|
|
22
|
+
clientId: process.env.MONITOR_CLIENT_ID!,
|
|
23
|
+
clientSecret: process.env.MONITOR_CLIENT_SECRET!,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Simple logging
|
|
27
|
+
appLog.info('auth', 'user.login', {
|
|
28
|
+
message: 'User logged in',
|
|
29
|
+
userId: 'user-123',
|
|
30
|
+
metadata: { method: 'oauth', provider: 'google' },
|
|
31
|
+
tags: ['web'],
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
appLog.error('payment', 'charge.failed', {
|
|
35
|
+
message: 'Card declined',
|
|
36
|
+
userId: 'user-123',
|
|
37
|
+
metadata: { reason: 'insufficient_funds', amount: 99.99 },
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Duration tracking
|
|
41
|
+
const end = appLog.startTimer('payment', 'payment.charge');
|
|
42
|
+
await processPayment(order);
|
|
43
|
+
end({ userId: order.userId, metadata: { orderId: order.id } });
|
|
44
|
+
|
|
45
|
+
// Shutdown (flushes remaining buffer)
|
|
46
|
+
process.on('SIGTERM', async () => {
|
|
47
|
+
await appLog.shutdown();
|
|
48
|
+
process.exit(0);
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### NestJS Module
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
// app.module.ts
|
|
56
|
+
import { AppLogModule } from '@devminister/applog-client';
|
|
57
|
+
|
|
58
|
+
@Module({
|
|
59
|
+
imports: [
|
|
60
|
+
AppLogModule.register({
|
|
61
|
+
apiUrl: process.env.MONITOR_API_URL,
|
|
62
|
+
clientId: process.env.APPLOG_CLIENT_ID,
|
|
63
|
+
clientSecret: process.env.APPLOG_CLIENT_SECRET,
|
|
64
|
+
}),
|
|
65
|
+
],
|
|
66
|
+
})
|
|
67
|
+
export class AppModule {}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
#### With ConfigService (async)
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
AppLogModule.registerAsync({
|
|
74
|
+
inject: [ConfigService],
|
|
75
|
+
useFactory: (config: ConfigService) => ({
|
|
76
|
+
apiUrl: config.get('MONITOR_API_URL'),
|
|
77
|
+
clientId: config.get('APPLOG_CLIENT_ID'),
|
|
78
|
+
clientSecret: config.get('APPLOG_CLIENT_SECRET'),
|
|
79
|
+
defaultCategory: 'my-service',
|
|
80
|
+
defaultTags: ['production'],
|
|
81
|
+
}),
|
|
82
|
+
})
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
#### Inject and use in services
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { AppLogNestService } from '@devminister/applog-client';
|
|
89
|
+
|
|
90
|
+
@Injectable()
|
|
91
|
+
export class PaymentService {
|
|
92
|
+
constructor(private readonly appLog: AppLogNestService) {}
|
|
93
|
+
|
|
94
|
+
async charge(order: Order) {
|
|
95
|
+
const end = this.appLog.startTimer('payment', 'payment.charge', {
|
|
96
|
+
userId: order.userId,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const result = await this.stripe.charge(order);
|
|
101
|
+
end({ message: `Charged $${order.total}`, metadata: { orderId: order.id } });
|
|
102
|
+
return result;
|
|
103
|
+
} catch (err) {
|
|
104
|
+
this.appLog.error('payment', 'payment.charge.failed', {
|
|
105
|
+
message: err.message,
|
|
106
|
+
userId: order.userId,
|
|
107
|
+
metadata: { orderId: order.id, error: err.message },
|
|
108
|
+
});
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
#### Auto-log all HTTP requests (interceptor)
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { AppLogInterceptor } from '@devminister/applog-client';
|
|
119
|
+
|
|
120
|
+
// app.module.ts
|
|
121
|
+
@Module({
|
|
122
|
+
providers: [
|
|
123
|
+
{
|
|
124
|
+
provide: APP_INTERCEPTOR,
|
|
125
|
+
useClass: AppLogInterceptor,
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
})
|
|
129
|
+
export class AppModule {}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
This will automatically log every request with controller name, handler, method, path, status code, duration, and userId.
|
|
133
|
+
|
|
134
|
+
## API
|
|
135
|
+
|
|
136
|
+
### Log Methods
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
appLog.debug(category, action, options?)
|
|
140
|
+
appLog.info(category, action, options?)
|
|
141
|
+
appLog.warn(category, action, options?)
|
|
142
|
+
appLog.error(category, action, options?)
|
|
143
|
+
appLog.fatal(category, action, options?)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Options
|
|
147
|
+
|
|
148
|
+
| Field | Type | Description |
|
|
149
|
+
|------------|----------|------------------------------------------|
|
|
150
|
+
| `message` | string | Human-readable log message |
|
|
151
|
+
| `metadata` | object | Any structured data (JSON) |
|
|
152
|
+
| `userId` | string | User identifier |
|
|
153
|
+
| `duration` | number | Duration in milliseconds |
|
|
154
|
+
| `tags` | string[] | Searchable tags |
|
|
155
|
+
|
|
156
|
+
### Timer
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
const end = appLog.startTimer(category, action, options?);
|
|
160
|
+
// ... do work ...
|
|
161
|
+
end(extraOptions?); // logs with duration automatically
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Low-level
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
appLog.push(payload) // Push a raw AppLogPayload
|
|
168
|
+
appLog.flush() // Force flush buffer
|
|
169
|
+
appLog.shutdown() // Stop timer + flush
|
|
170
|
+
appLog.bufferSize // Current buffer length
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Configuration
|
|
174
|
+
|
|
175
|
+
| Option | Type | Default | Description |
|
|
176
|
+
|--------------------|----------|---------|--------------------------------------------------|
|
|
177
|
+
| `apiUrl` | string | — | Monitoring API base URL (required) |
|
|
178
|
+
| `clientId` | string | — | Environment client ID (required) |
|
|
179
|
+
| `clientSecret` | string | — | Environment client secret (required) |
|
|
180
|
+
| `batchSize` | number | `50` | Auto-flush when buffer reaches this size |
|
|
181
|
+
| `flushInterval` | number | `5000` | Flush interval in ms |
|
|
182
|
+
| `maxBufferSize` | number | `1000` | Max buffer size (oldest logs dropped when full) |
|
|
183
|
+
| `timeout` | number | `10000` | HTTP request timeout in ms |
|
|
184
|
+
| `maxRetries` | number | `3` | Retry attempts with exponential backoff |
|
|
185
|
+
| `defaultCategory` | string | — | Default category when none specified |
|
|
186
|
+
| `defaultTags` | string[] | `[]` | Tags added to every log |
|
|
187
|
+
| `disabled` | boolean | `false` | Disable all logging |
|
|
188
|
+
| `logger` | object | console | Custom logger `{ debug, warn, error }` |
|
|
189
|
+
|
|
190
|
+
## Category & Action Conventions
|
|
191
|
+
|
|
192
|
+
| Category | Example Actions |
|
|
193
|
+
|----------------|--------------------------------------------------------------|
|
|
194
|
+
| `auth` | `user.login`, `user.logout`, `user.register`, `token.refresh` |
|
|
195
|
+
| `payment` | `payment.charge`, `payment.refund`, `subscription.create` |
|
|
196
|
+
| `order` | `order.create`, `order.update`, `order.cancel`, `order.ship` |
|
|
197
|
+
| `notification` | `email.send`, `sms.send`, `push.send` |
|
|
198
|
+
| `file` | `file.upload`, `file.download`, `file.delete` |
|
|
199
|
+
| `admin` | `user.ban`, `config.update`, `data.export` |
|
|
200
|
+
| `integration` | `webhook.receive`, `api.call`, `sync.complete` |
|
|
201
|
+
|
|
202
|
+
## How It Works
|
|
203
|
+
|
|
204
|
+
1. Logs are buffered in memory
|
|
205
|
+
2. Buffer is flushed when it reaches `batchSize` or every `flushInterval` ms
|
|
206
|
+
3. Logs are sent as a batch POST to `/api/app-logs/ingest` with Basic Auth
|
|
207
|
+
4. On failure, retries with exponential backoff (200ms, 400ms, 800ms)
|
|
208
|
+
5. Failed batches are re-added to the buffer if space is available
|
|
209
|
+
6. Buffer has a hard cap (`maxBufferSize`) — oldest logs are dropped when full
|
|
210
|
+
7. On shutdown, remaining buffer is flushed
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
MIT
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { OnModuleDestroy } from '@nestjs/common';
|
|
2
|
+
import { AppLogClientConfig } from './interfaces/applog-config.interface';
|
|
3
|
+
import { AppLogClientService } from './applog.service';
|
|
4
|
+
/**
|
|
5
|
+
* NestJS-managed AppLog service. Wraps AppLogClientService with
|
|
6
|
+
* NestJS lifecycle hooks and the NestJS Logger.
|
|
7
|
+
*/
|
|
8
|
+
export declare class AppLogNestService extends AppLogClientService implements OnModuleDestroy {
|
|
9
|
+
constructor(config: AppLogClientConfig);
|
|
10
|
+
onModuleDestroy(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.AppLogNestService = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const applog_config_interface_1 = require("./interfaces/applog-config.interface");
|
|
18
|
+
const applog_service_1 = require("./applog.service");
|
|
19
|
+
/**
|
|
20
|
+
* NestJS-managed AppLog service. Wraps AppLogClientService with
|
|
21
|
+
* NestJS lifecycle hooks and the NestJS Logger.
|
|
22
|
+
*/
|
|
23
|
+
let AppLogNestService = class AppLogNestService extends applog_service_1.AppLogClientService {
|
|
24
|
+
constructor(config) {
|
|
25
|
+
const nestLogger = new common_1.Logger('AppLog');
|
|
26
|
+
super({
|
|
27
|
+
...config,
|
|
28
|
+
logger: {
|
|
29
|
+
debug: (msg) => nestLogger.debug(msg),
|
|
30
|
+
warn: (msg) => nestLogger.warn(msg),
|
|
31
|
+
error: (msg) => nestLogger.error(msg),
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
async onModuleDestroy() {
|
|
36
|
+
await this.shutdown();
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
exports.AppLogNestService = AppLogNestService;
|
|
40
|
+
exports.AppLogNestService = AppLogNestService = __decorate([
|
|
41
|
+
(0, common_1.Injectable)(),
|
|
42
|
+
__param(0, (0, common_1.Inject)(applog_config_interface_1.APPLOG_CLIENT_CONFIG)),
|
|
43
|
+
__metadata("design:paramtypes", [Object])
|
|
44
|
+
], AppLogNestService);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common';
|
|
2
|
+
import { Observable } from 'rxjs';
|
|
3
|
+
import { AppLogNestService } from './applog-nestjs.service';
|
|
4
|
+
/**
|
|
5
|
+
* NestJS interceptor that automatically logs every HTTP request as an app log.
|
|
6
|
+
*
|
|
7
|
+
* Captures: controller name, handler name, method, path, status, duration, userId.
|
|
8
|
+
* Errors are logged at level 'error', successful requests at 'info'.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* // Global interceptor
|
|
12
|
+
* app.useGlobalInterceptors(app.get(AppLogInterceptor));
|
|
13
|
+
*
|
|
14
|
+
* // Or via module providers
|
|
15
|
+
* { provide: APP_INTERCEPTOR, useClass: AppLogInterceptor }
|
|
16
|
+
*
|
|
17
|
+
* // Or per-controller
|
|
18
|
+
* @UseInterceptors(AppLogInterceptor)
|
|
19
|
+
*/
|
|
20
|
+
export declare class AppLogInterceptor implements NestInterceptor {
|
|
21
|
+
private readonly appLog;
|
|
22
|
+
constructor(appLog: AppLogNestService);
|
|
23
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable<any>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.AppLogInterceptor = void 0;
|
|
13
|
+
const common_1 = require("@nestjs/common");
|
|
14
|
+
const rxjs_1 = require("rxjs");
|
|
15
|
+
const operators_1 = require("rxjs/operators");
|
|
16
|
+
const applog_nestjs_service_1 = require("./applog-nestjs.service");
|
|
17
|
+
/**
|
|
18
|
+
* NestJS interceptor that automatically logs every HTTP request as an app log.
|
|
19
|
+
*
|
|
20
|
+
* Captures: controller name, handler name, method, path, status, duration, userId.
|
|
21
|
+
* Errors are logged at level 'error', successful requests at 'info'.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* // Global interceptor
|
|
25
|
+
* app.useGlobalInterceptors(app.get(AppLogInterceptor));
|
|
26
|
+
*
|
|
27
|
+
* // Or via module providers
|
|
28
|
+
* { provide: APP_INTERCEPTOR, useClass: AppLogInterceptor }
|
|
29
|
+
*
|
|
30
|
+
* // Or per-controller
|
|
31
|
+
* @UseInterceptors(AppLogInterceptor)
|
|
32
|
+
*/
|
|
33
|
+
let AppLogInterceptor = class AppLogInterceptor {
|
|
34
|
+
constructor(appLog) {
|
|
35
|
+
this.appLog = appLog;
|
|
36
|
+
}
|
|
37
|
+
intercept(context, next) {
|
|
38
|
+
if (this.appLog.getConfig().disabled)
|
|
39
|
+
return next.handle();
|
|
40
|
+
const ctx = context.switchToHttp();
|
|
41
|
+
const request = ctx.getRequest();
|
|
42
|
+
const controller = context.getClass().name;
|
|
43
|
+
const handler = context.getHandler().name;
|
|
44
|
+
const start = Date.now();
|
|
45
|
+
const userId = request.user?.id || request.user?.sub || undefined;
|
|
46
|
+
return next.handle().pipe((0, operators_1.tap)(() => {
|
|
47
|
+
const response = ctx.getResponse();
|
|
48
|
+
this.appLog.info('api', `${controller}.${handler}`, {
|
|
49
|
+
userId,
|
|
50
|
+
duration: Date.now() - start,
|
|
51
|
+
metadata: {
|
|
52
|
+
method: request.method,
|
|
53
|
+
path: request.url,
|
|
54
|
+
statusCode: response.statusCode,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}), (0, operators_1.catchError)((error) => {
|
|
58
|
+
const statusCode = error instanceof common_1.HttpException
|
|
59
|
+
? error.getStatus()
|
|
60
|
+
: common_1.HttpStatus.INTERNAL_SERVER_ERROR;
|
|
61
|
+
this.appLog.error('api', `${controller}.${handler}`, {
|
|
62
|
+
message: error.message,
|
|
63
|
+
userId,
|
|
64
|
+
duration: Date.now() - start,
|
|
65
|
+
metadata: {
|
|
66
|
+
method: request.method,
|
|
67
|
+
path: request.url,
|
|
68
|
+
statusCode,
|
|
69
|
+
error: error.message,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
return (0, rxjs_1.throwError)(() => error);
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
exports.AppLogInterceptor = AppLogInterceptor;
|
|
77
|
+
exports.AppLogInterceptor = AppLogInterceptor = __decorate([
|
|
78
|
+
(0, common_1.Injectable)(),
|
|
79
|
+
__metadata("design:paramtypes", [applog_nestjs_service_1.AppLogNestService])
|
|
80
|
+
], AppLogInterceptor);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { DynamicModule } from '@nestjs/common';
|
|
2
|
+
import { AppLogClientConfig } from './interfaces/applog-config.interface';
|
|
3
|
+
export declare class AppLogModule {
|
|
4
|
+
/**
|
|
5
|
+
* Register the app log client with static config.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* AppLogModule.register({
|
|
9
|
+
* apiUrl: 'http://localhost:4000',
|
|
10
|
+
* clientId: 'your-client-id',
|
|
11
|
+
* clientSecret: 'your-client-secret',
|
|
12
|
+
* })
|
|
13
|
+
*/
|
|
14
|
+
static register(config: AppLogClientConfig): DynamicModule;
|
|
15
|
+
/**
|
|
16
|
+
* Register the app log client with async config (e.g. from ConfigService).
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* AppLogModule.registerAsync({
|
|
20
|
+
* inject: [ConfigService],
|
|
21
|
+
* useFactory: (config: ConfigService) => ({
|
|
22
|
+
* apiUrl: config.get('MONITOR_API_URL'),
|
|
23
|
+
* clientId: config.get('APPLOG_CLIENT_ID'),
|
|
24
|
+
* clientSecret: config.get('APPLOG_CLIENT_SECRET'),
|
|
25
|
+
* }),
|
|
26
|
+
* })
|
|
27
|
+
*/
|
|
28
|
+
static registerAsync(options: {
|
|
29
|
+
inject?: any[];
|
|
30
|
+
useFactory: (...args: any[]) => AppLogClientConfig | Promise<AppLogClientConfig>;
|
|
31
|
+
imports?: any[];
|
|
32
|
+
}): DynamicModule;
|
|
33
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var AppLogModule_1;
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.AppLogModule = void 0;
|
|
11
|
+
const common_1 = require("@nestjs/common");
|
|
12
|
+
const applog_config_interface_1 = require("./interfaces/applog-config.interface");
|
|
13
|
+
const applog_nestjs_service_1 = require("./applog-nestjs.service");
|
|
14
|
+
const applog_interceptor_1 = require("./applog.interceptor");
|
|
15
|
+
let AppLogModule = AppLogModule_1 = class AppLogModule {
|
|
16
|
+
/**
|
|
17
|
+
* Register the app log client with static config.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* AppLogModule.register({
|
|
21
|
+
* apiUrl: 'http://localhost:4000',
|
|
22
|
+
* clientId: 'your-client-id',
|
|
23
|
+
* clientSecret: 'your-client-secret',
|
|
24
|
+
* })
|
|
25
|
+
*/
|
|
26
|
+
static register(config) {
|
|
27
|
+
return {
|
|
28
|
+
module: AppLogModule_1,
|
|
29
|
+
providers: [
|
|
30
|
+
{ provide: applog_config_interface_1.APPLOG_CLIENT_CONFIG, useValue: config },
|
|
31
|
+
applog_nestjs_service_1.AppLogNestService,
|
|
32
|
+
applog_interceptor_1.AppLogInterceptor,
|
|
33
|
+
],
|
|
34
|
+
exports: [applog_nestjs_service_1.AppLogNestService, applog_interceptor_1.AppLogInterceptor],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Register the app log client with async config (e.g. from ConfigService).
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* AppLogModule.registerAsync({
|
|
42
|
+
* inject: [ConfigService],
|
|
43
|
+
* useFactory: (config: ConfigService) => ({
|
|
44
|
+
* apiUrl: config.get('MONITOR_API_URL'),
|
|
45
|
+
* clientId: config.get('APPLOG_CLIENT_ID'),
|
|
46
|
+
* clientSecret: config.get('APPLOG_CLIENT_SECRET'),
|
|
47
|
+
* }),
|
|
48
|
+
* })
|
|
49
|
+
*/
|
|
50
|
+
static registerAsync(options) {
|
|
51
|
+
return {
|
|
52
|
+
module: AppLogModule_1,
|
|
53
|
+
imports: [...(options.imports || [])],
|
|
54
|
+
providers: [
|
|
55
|
+
{
|
|
56
|
+
provide: applog_config_interface_1.APPLOG_CLIENT_CONFIG,
|
|
57
|
+
inject: options.inject || [],
|
|
58
|
+
useFactory: options.useFactory,
|
|
59
|
+
},
|
|
60
|
+
applog_nestjs_service_1.AppLogNestService,
|
|
61
|
+
applog_interceptor_1.AppLogInterceptor,
|
|
62
|
+
],
|
|
63
|
+
exports: [applog_nestjs_service_1.AppLogNestService, applog_interceptor_1.AppLogInterceptor],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
exports.AppLogModule = AppLogModule;
|
|
68
|
+
exports.AppLogModule = AppLogModule = AppLogModule_1 = __decorate([
|
|
69
|
+
(0, common_1.Global)(),
|
|
70
|
+
(0, common_1.Module)({})
|
|
71
|
+
], AppLogModule);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { AppLogClientConfig } from './interfaces/applog-config.interface';
|
|
2
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
|
3
|
+
export interface AppLogPayload {
|
|
4
|
+
level: LogLevel;
|
|
5
|
+
category: string;
|
|
6
|
+
action: string;
|
|
7
|
+
message?: string;
|
|
8
|
+
metadata?: Record<string, any>;
|
|
9
|
+
userId?: string;
|
|
10
|
+
duration?: number;
|
|
11
|
+
tags?: string[];
|
|
12
|
+
createdAt?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface AppLogOptions {
|
|
15
|
+
message?: string;
|
|
16
|
+
metadata?: Record<string, any>;
|
|
17
|
+
userId?: string;
|
|
18
|
+
duration?: number;
|
|
19
|
+
tags?: string[];
|
|
20
|
+
}
|
|
21
|
+
export declare class AppLogClientService {
|
|
22
|
+
private readonly config;
|
|
23
|
+
private buffer;
|
|
24
|
+
private flushTimer;
|
|
25
|
+
private flushing;
|
|
26
|
+
private readonly authHeader;
|
|
27
|
+
private readonly batchSize;
|
|
28
|
+
private readonly maxBufferSize;
|
|
29
|
+
private readonly timeout;
|
|
30
|
+
private readonly maxRetries;
|
|
31
|
+
private readonly defaultCategory?;
|
|
32
|
+
private readonly defaultTags;
|
|
33
|
+
private readonly log;
|
|
34
|
+
constructor(config: AppLogClientConfig);
|
|
35
|
+
debug(category: string, action: string, opts?: AppLogOptions): void;
|
|
36
|
+
info(category: string, action: string, opts?: AppLogOptions): void;
|
|
37
|
+
warn(category: string, action: string, opts?: AppLogOptions): void;
|
|
38
|
+
error(category: string, action: string, opts?: AppLogOptions): void;
|
|
39
|
+
fatal(category: string, action: string, opts?: AppLogOptions): void;
|
|
40
|
+
/**
|
|
41
|
+
* Start a timer. Returns a function that, when called, logs the action with the elapsed duration.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* const end = appLog.startTimer('payment', 'payment.charge', { userId: 'u1' });
|
|
45
|
+
* await processPayment();
|
|
46
|
+
* end(); // logs with duration automatically
|
|
47
|
+
*/
|
|
48
|
+
startTimer(category: string, action: string, opts?: Omit<AppLogOptions, 'duration'>): (extra?: Partial<AppLogOptions>) => void;
|
|
49
|
+
push(log: AppLogPayload): void;
|
|
50
|
+
flush(): Promise<void>;
|
|
51
|
+
private sendWithRetry;
|
|
52
|
+
shutdown(): Promise<void>;
|
|
53
|
+
get bufferSize(): number;
|
|
54
|
+
getConfig(): AppLogClientConfig;
|
|
55
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AppLogClientService = void 0;
|
|
4
|
+
// ─── Core Client ────────────────────────────────────────
|
|
5
|
+
class AppLogClientService {
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.config = config;
|
|
8
|
+
this.buffer = [];
|
|
9
|
+
this.flushTimer = null;
|
|
10
|
+
this.flushing = false;
|
|
11
|
+
this.batchSize = config.batchSize ?? 50;
|
|
12
|
+
this.maxBufferSize = config.maxBufferSize ?? 1000;
|
|
13
|
+
this.timeout = config.timeout ?? 10000;
|
|
14
|
+
this.maxRetries = config.maxRetries ?? 3;
|
|
15
|
+
this.defaultCategory = config.defaultCategory;
|
|
16
|
+
this.defaultTags = config.defaultTags ?? [];
|
|
17
|
+
this.log = {
|
|
18
|
+
debug: config.logger?.debug ?? (() => { }),
|
|
19
|
+
warn: config.logger?.warn ?? console.warn.bind(console),
|
|
20
|
+
error: config.logger?.error ?? console.error.bind(console),
|
|
21
|
+
};
|
|
22
|
+
if (config.disabled) {
|
|
23
|
+
this.authHeader = '';
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
this.authHeader =
|
|
27
|
+
'Basic ' + Buffer.from(`${config.clientId}:${config.clientSecret}`).toString('base64');
|
|
28
|
+
const interval = config.flushInterval ?? 5000;
|
|
29
|
+
this.flushTimer = setInterval(() => this.flush(), interval);
|
|
30
|
+
}
|
|
31
|
+
// ─── Convenience Methods ────────────────────────────────
|
|
32
|
+
debug(category, action, opts) {
|
|
33
|
+
this.push({ level: 'debug', category, action, ...opts });
|
|
34
|
+
}
|
|
35
|
+
info(category, action, opts) {
|
|
36
|
+
this.push({ level: 'info', category, action, ...opts });
|
|
37
|
+
}
|
|
38
|
+
warn(category, action, opts) {
|
|
39
|
+
this.push({ level: 'warn', category, action, ...opts });
|
|
40
|
+
}
|
|
41
|
+
error(category, action, opts) {
|
|
42
|
+
this.push({ level: 'error', category, action, ...opts });
|
|
43
|
+
}
|
|
44
|
+
fatal(category, action, opts) {
|
|
45
|
+
this.push({ level: 'fatal', category, action, ...opts });
|
|
46
|
+
}
|
|
47
|
+
// ─── Duration Tracking ─────────────────────────────────
|
|
48
|
+
/**
|
|
49
|
+
* Start a timer. Returns a function that, when called, logs the action with the elapsed duration.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* const end = appLog.startTimer('payment', 'payment.charge', { userId: 'u1' });
|
|
53
|
+
* await processPayment();
|
|
54
|
+
* end(); // logs with duration automatically
|
|
55
|
+
*/
|
|
56
|
+
startTimer(category, action, opts) {
|
|
57
|
+
const start = Date.now();
|
|
58
|
+
return (extra) => {
|
|
59
|
+
const duration = Date.now() - start;
|
|
60
|
+
this.info(category, action, { ...opts, ...extra, duration });
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
// ─── Core Push ──────────────────────────────────────────
|
|
64
|
+
push(log) {
|
|
65
|
+
if (this.config.disabled)
|
|
66
|
+
return;
|
|
67
|
+
// Apply defaults
|
|
68
|
+
if (this.defaultCategory && !log.category) {
|
|
69
|
+
log.category = this.defaultCategory;
|
|
70
|
+
}
|
|
71
|
+
if (this.defaultTags.length > 0) {
|
|
72
|
+
log.tags = [...this.defaultTags, ...(log.tags ?? [])];
|
|
73
|
+
}
|
|
74
|
+
this.buffer.push(log);
|
|
75
|
+
// Enforce max buffer size — drop oldest
|
|
76
|
+
if (this.buffer.length > this.maxBufferSize) {
|
|
77
|
+
const dropped = this.buffer.length - this.maxBufferSize;
|
|
78
|
+
this.buffer = this.buffer.slice(dropped);
|
|
79
|
+
this.log.warn(`[AppLog] Buffer full, dropped ${dropped} oldest logs`);
|
|
80
|
+
}
|
|
81
|
+
if (this.buffer.length >= this.batchSize) {
|
|
82
|
+
this.flush().catch(() => { });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// ─── Flush ──────────────────────────────────────────────
|
|
86
|
+
async flush() {
|
|
87
|
+
if (this.buffer.length === 0 || this.flushing)
|
|
88
|
+
return;
|
|
89
|
+
if (!this.config.clientId || !this.config.clientSecret) {
|
|
90
|
+
this.log.warn('[AppLog] Credentials not configured, skipping flush');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
this.flushing = true;
|
|
94
|
+
const batch = this.buffer.splice(0, Math.min(this.buffer.length, 1000));
|
|
95
|
+
try {
|
|
96
|
+
await this.sendWithRetry(batch);
|
|
97
|
+
this.log.debug(`[AppLog] Flushed ${batch.length} logs`);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
this.log.error(`[AppLog] Failed to flush ${batch.length} logs after ${this.maxRetries} retries: ${err.message}`);
|
|
101
|
+
// Re-add failed batch if space available
|
|
102
|
+
const available = this.maxBufferSize - this.buffer.length;
|
|
103
|
+
if (available > 0) {
|
|
104
|
+
this.buffer.unshift(...batch.slice(0, available));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
this.flushing = false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// ─── Retry Logic ────────────────────────────────────────
|
|
112
|
+
async sendWithRetry(batch, attempt = 1) {
|
|
113
|
+
try {
|
|
114
|
+
const res = await fetch(`${this.config.apiUrl}/api/app-logs/ingest`, {
|
|
115
|
+
method: 'POST',
|
|
116
|
+
headers: {
|
|
117
|
+
'Content-Type': 'application/json',
|
|
118
|
+
Authorization: this.authHeader,
|
|
119
|
+
},
|
|
120
|
+
body: JSON.stringify({ logs: batch }),
|
|
121
|
+
signal: AbortSignal.timeout(this.timeout),
|
|
122
|
+
});
|
|
123
|
+
if (!res.ok) {
|
|
124
|
+
const body = await res.text().catch(() => '');
|
|
125
|
+
throw new Error(`HTTP ${res.status}: ${body.slice(0, 200)}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
if (attempt < this.maxRetries) {
|
|
130
|
+
// Exponential backoff: 200ms, 400ms, 800ms...
|
|
131
|
+
const delay = 200 * Math.pow(2, attempt - 1);
|
|
132
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
133
|
+
return this.sendWithRetry(batch, attempt + 1);
|
|
134
|
+
}
|
|
135
|
+
throw err;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// ─── Shutdown ───────────────────────────────────────────
|
|
139
|
+
async shutdown() {
|
|
140
|
+
if (this.flushTimer) {
|
|
141
|
+
clearInterval(this.flushTimer);
|
|
142
|
+
this.flushTimer = null;
|
|
143
|
+
}
|
|
144
|
+
await this.flush();
|
|
145
|
+
}
|
|
146
|
+
// ─── Stats ──────────────────────────────────────────────
|
|
147
|
+
get bufferSize() {
|
|
148
|
+
return this.buffer.length;
|
|
149
|
+
}
|
|
150
|
+
getConfig() {
|
|
151
|
+
return this.config;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
exports.AppLogClientService = AppLogClientService;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { AppLogClientService, AppLogPayload, AppLogOptions, LogLevel } from './applog.service';
|
|
2
|
+
export { AppLogClientConfig, APPLOG_CLIENT_CONFIG } from './interfaces/applog-config.interface';
|
|
3
|
+
export { createAppLogClient } from './standalone';
|
|
4
|
+
export { AppLogModule } from './applog.module';
|
|
5
|
+
export { AppLogNestService } from './applog-nestjs.service';
|
|
6
|
+
export { AppLogInterceptor } from './applog.interceptor';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AppLogInterceptor = exports.AppLogNestService = exports.AppLogModule = exports.createAppLogClient = exports.APPLOG_CLIENT_CONFIG = exports.AppLogClientService = void 0;
|
|
4
|
+
// Core client (works anywhere)
|
|
5
|
+
var applog_service_1 = require("./applog.service");
|
|
6
|
+
Object.defineProperty(exports, "AppLogClientService", { enumerable: true, get: function () { return applog_service_1.AppLogClientService; } });
|
|
7
|
+
var applog_config_interface_1 = require("./interfaces/applog-config.interface");
|
|
8
|
+
Object.defineProperty(exports, "APPLOG_CLIENT_CONFIG", { enumerable: true, get: function () { return applog_config_interface_1.APPLOG_CLIENT_CONFIG; } });
|
|
9
|
+
var standalone_1 = require("./standalone");
|
|
10
|
+
Object.defineProperty(exports, "createAppLogClient", { enumerable: true, get: function () { return standalone_1.createAppLogClient; } });
|
|
11
|
+
// NestJS integration
|
|
12
|
+
var applog_module_1 = require("./applog.module");
|
|
13
|
+
Object.defineProperty(exports, "AppLogModule", { enumerable: true, get: function () { return applog_module_1.AppLogModule; } });
|
|
14
|
+
var applog_nestjs_service_1 = require("./applog-nestjs.service");
|
|
15
|
+
Object.defineProperty(exports, "AppLogNestService", { enumerable: true, get: function () { return applog_nestjs_service_1.AppLogNestService; } });
|
|
16
|
+
var applog_interceptor_1 = require("./applog.interceptor");
|
|
17
|
+
Object.defineProperty(exports, "AppLogInterceptor", { enumerable: true, get: function () { return applog_interceptor_1.AppLogInterceptor; } });
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface AppLogClientConfig {
|
|
2
|
+
/** The monitoring API base URL (e.g. http://localhost:4000) */
|
|
3
|
+
apiUrl: string;
|
|
4
|
+
/** Client ID from the monitoring environment credentials */
|
|
5
|
+
clientId: string;
|
|
6
|
+
/** Client Secret from the monitoring environment credentials */
|
|
7
|
+
clientSecret: string;
|
|
8
|
+
/** Batch size before auto-flushing (default: 50) */
|
|
9
|
+
batchSize?: number;
|
|
10
|
+
/** Flush interval in ms (default: 5000) */
|
|
11
|
+
flushInterval?: number;
|
|
12
|
+
/** Max buffer size to prevent memory leaks (default: 1000) */
|
|
13
|
+
maxBufferSize?: number;
|
|
14
|
+
/** Request timeout in ms (default: 10000) */
|
|
15
|
+
timeout?: number;
|
|
16
|
+
/** Max retry attempts on failure (default: 3) */
|
|
17
|
+
maxRetries?: number;
|
|
18
|
+
/** Default category for logs (optional) */
|
|
19
|
+
defaultCategory?: string;
|
|
20
|
+
/** Default tags added to every log (optional) */
|
|
21
|
+
defaultTags?: string[];
|
|
22
|
+
/** Disable all logging when true */
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
/** Custom logger for internal messages (default: console) */
|
|
25
|
+
logger?: {
|
|
26
|
+
debug?: (msg: string) => void;
|
|
27
|
+
warn?: (msg: string) => void;
|
|
28
|
+
error?: (msg: string) => void;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export declare const APPLOG_CLIENT_CONFIG = "APPLOG_CLIENT_CONFIG";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { AppLogClientConfig } from './interfaces/applog-config.interface';
|
|
2
|
+
import { AppLogClientService } from './applog.service';
|
|
3
|
+
/**
|
|
4
|
+
* Create a standalone AppLog client (no NestJS required).
|
|
5
|
+
* Works in any Node.js application, Express, Fastify, etc.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const appLog = createAppLogClient({
|
|
9
|
+
* apiUrl: 'https://monitor.example.com',
|
|
10
|
+
* clientId: process.env.MONITOR_CLIENT_ID,
|
|
11
|
+
* clientSecret: process.env.MONITOR_CLIENT_SECRET,
|
|
12
|
+
* });
|
|
13
|
+
*
|
|
14
|
+
* appLog.info('auth', 'user.login', { userId: 'u1', message: 'Logged in' });
|
|
15
|
+
*
|
|
16
|
+
* // On app shutdown:
|
|
17
|
+
* await appLog.shutdown();
|
|
18
|
+
*/
|
|
19
|
+
export declare function createAppLogClient(config: AppLogClientConfig): AppLogClientService;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createAppLogClient = createAppLogClient;
|
|
4
|
+
const applog_service_1 = require("./applog.service");
|
|
5
|
+
/**
|
|
6
|
+
* Create a standalone AppLog client (no NestJS required).
|
|
7
|
+
* Works in any Node.js application, Express, Fastify, etc.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const appLog = createAppLogClient({
|
|
11
|
+
* apiUrl: 'https://monitor.example.com',
|
|
12
|
+
* clientId: process.env.MONITOR_CLIENT_ID,
|
|
13
|
+
* clientSecret: process.env.MONITOR_CLIENT_SECRET,
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* appLog.info('auth', 'user.login', { userId: 'u1', message: 'Logged in' });
|
|
17
|
+
*
|
|
18
|
+
* // On app shutdown:
|
|
19
|
+
* await appLog.shutdown();
|
|
20
|
+
*/
|
|
21
|
+
function createAppLogClient(config) {
|
|
22
|
+
return new applog_service_1.AppLogClientService(config);
|
|
23
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@devminister/applog-client",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Application log client for the Monitoring platform. Buffered, batched delivery with retry. Works standalone or as a NestJS module.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"prepublishOnly": "tsc"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"nestjs",
|
|
17
|
+
"logging",
|
|
18
|
+
"monitoring",
|
|
19
|
+
"application-logs",
|
|
20
|
+
"observability",
|
|
21
|
+
"audit-trail",
|
|
22
|
+
"structured-logging"
|
|
23
|
+
],
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
|
30
|
+
"@nestjs/core": "^10.0.0 || ^11.0.0",
|
|
31
|
+
"rxjs": "^7.0.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependenciesMeta": {
|
|
34
|
+
"@nestjs/common": { "optional": true },
|
|
35
|
+
"@nestjs/core": { "optional": true },
|
|
36
|
+
"rxjs": { "optional": true }
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@nestjs/common": "^11.1.3",
|
|
40
|
+
"@nestjs/core": "^11.1.3",
|
|
41
|
+
"@types/node": "^24.0.0",
|
|
42
|
+
"rxjs": "^7.8.2",
|
|
43
|
+
"typescript": "^5.8.3"
|
|
44
|
+
}
|
|
45
|
+
}
|