@qwickapps/server 1.1.6 → 1.1.7

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.
Files changed (66) hide show
  1. package/README.md +1 -1
  2. package/dist/core/control-panel.d.ts.map +1 -1
  3. package/dist/core/control-panel.js +5 -8
  4. package/dist/core/control-panel.js.map +1 -1
  5. package/dist/core/gateway.d.ts.map +1 -1
  6. package/dist/core/gateway.js +11 -23
  7. package/dist/core/gateway.js.map +1 -1
  8. package/dist/core/health-manager.d.ts.map +1 -1
  9. package/dist/core/health-manager.js +3 -9
  10. package/dist/core/health-manager.js.map +1 -1
  11. package/dist/core/logging.d.ts.map +1 -1
  12. package/dist/core/logging.js +1 -5
  13. package/dist/core/logging.js.map +1 -1
  14. package/dist/index.d.ts +2 -2
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +7 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/plugins/cache-plugin.d.ts +219 -0
  19. package/dist/plugins/cache-plugin.d.ts.map +1 -0
  20. package/dist/plugins/cache-plugin.js +326 -0
  21. package/dist/plugins/cache-plugin.js.map +1 -0
  22. package/dist/plugins/cache-plugin.test.d.ts +8 -0
  23. package/dist/plugins/cache-plugin.test.d.ts.map +1 -0
  24. package/dist/plugins/cache-plugin.test.js +188 -0
  25. package/dist/plugins/cache-plugin.test.js.map +1 -0
  26. package/dist/plugins/config-plugin.js +1 -1
  27. package/dist/plugins/config-plugin.js.map +1 -1
  28. package/dist/plugins/diagnostics-plugin.js +1 -1
  29. package/dist/plugins/diagnostics-plugin.js.map +1 -1
  30. package/dist/plugins/health-plugin.js +1 -1
  31. package/dist/plugins/health-plugin.js.map +1 -1
  32. package/dist/plugins/index.d.ts +6 -0
  33. package/dist/plugins/index.d.ts.map +1 -1
  34. package/dist/plugins/index.js +4 -0
  35. package/dist/plugins/index.js.map +1 -1
  36. package/dist/plugins/logs-plugin.d.ts.map +1 -1
  37. package/dist/plugins/logs-plugin.js +1 -3
  38. package/dist/plugins/logs-plugin.js.map +1 -1
  39. package/dist/plugins/postgres-plugin.d.ts +155 -0
  40. package/dist/plugins/postgres-plugin.d.ts.map +1 -0
  41. package/dist/plugins/postgres-plugin.js +244 -0
  42. package/dist/plugins/postgres-plugin.js.map +1 -0
  43. package/dist/plugins/postgres-plugin.test.d.ts +8 -0
  44. package/dist/plugins/postgres-plugin.test.d.ts.map +1 -0
  45. package/dist/plugins/postgres-plugin.test.js +165 -0
  46. package/dist/plugins/postgres-plugin.test.js.map +1 -0
  47. package/dist-ui/assets/{index-Bk7ypbI4.js → index-CW1BviRn.js} +2 -2
  48. package/dist-ui/assets/{index-Bk7ypbI4.js.map → index-CW1BviRn.js.map} +1 -1
  49. package/dist-ui/index.html +1 -1
  50. package/package.json +13 -2
  51. package/src/core/control-panel.ts +5 -8
  52. package/src/core/gateway.ts +12 -24
  53. package/src/core/health-manager.ts +3 -9
  54. package/src/core/logging.ts +1 -5
  55. package/src/index.ts +22 -0
  56. package/src/plugins/cache-plugin.test.ts +241 -0
  57. package/src/plugins/cache-plugin.ts +503 -0
  58. package/src/plugins/config-plugin.ts +1 -1
  59. package/src/plugins/diagnostics-plugin.ts +1 -1
  60. package/src/plugins/health-plugin.ts +1 -1
  61. package/src/plugins/index.ts +10 -0
  62. package/src/plugins/logs-plugin.ts +1 -3
  63. package/src/plugins/postgres-plugin.test.ts +213 -0
  64. package/src/plugins/postgres-plugin.ts +345 -0
  65. package/ui/src/api/controlPanelApi.ts +1 -1
  66. package/ui/src/pages/LogsPage.tsx +6 -10
@@ -0,0 +1,503 @@
1
+ /**
2
+ * Cache Plugin
3
+ *
4
+ * Provides Redis caching capabilities with connection pooling and health checks.
5
+ * Wraps the 'ioredis' library with a simple, reusable interface.
6
+ *
7
+ * ## Features
8
+ * - Connection management with automatic reconnection
9
+ * - Key prefixing for multi-tenant/multi-app scenarios
10
+ * - TTL-based caching with setex/get operations
11
+ * - Automatic health checks
12
+ * - Multiple named instances support
13
+ * - Graceful shutdown
14
+ *
15
+ * ## Usage
16
+ *
17
+ * ```typescript
18
+ * import { createGateway, createCachePlugin, getCache } from '@qwickapps/server';
19
+ *
20
+ * const gateway = createGateway({
21
+ * // ... config
22
+ * plugins: [
23
+ * createCachePlugin({
24
+ * url: process.env.REDIS_URL,
25
+ * keyPrefix: 'myapp:',
26
+ * }),
27
+ * ],
28
+ * });
29
+ *
30
+ * // In your service code:
31
+ * const cache = getCache();
32
+ * await cache.set('user:123', userData, 3600); // Cache for 1 hour
33
+ * const user = await cache.get<User>('user:123');
34
+ * ```
35
+ *
36
+ * ## Multiple Caches
37
+ *
38
+ * ```typescript
39
+ * // Register multiple caches with different names
40
+ * createCachePlugin({ url: primaryUrl, keyPrefix: 'session:' }, 'sessions');
41
+ * createCachePlugin({ url: cacheUrl, keyPrefix: 'cache:' }, 'content');
42
+ *
43
+ * // Access by name
44
+ * const sessions = getCache('sessions');
45
+ * const content = getCache('content');
46
+ * ```
47
+ *
48
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
49
+ */
50
+
51
+ import type { ControlPanelPlugin, PluginContext } from '../core/types.js';
52
+
53
+ // Dynamic import for ioredis (optional peer dependency)
54
+ type Redis = import('ioredis').default;
55
+ type RedisOptions = import('ioredis').RedisOptions;
56
+
57
+ /**
58
+ * Configuration for the cache plugin
59
+ */
60
+ export interface CachePluginConfig {
61
+ /** Redis connection URL (e.g., redis://localhost:6379) */
62
+ url: string;
63
+
64
+ /** Key prefix for all cache operations (default: '') */
65
+ keyPrefix?: string;
66
+
67
+ /** Default TTL in seconds for set operations (default: 3600 = 1 hour) */
68
+ defaultTtl?: number;
69
+
70
+ /** Maximum number of retry attempts (default: 3) */
71
+ maxRetries?: number;
72
+
73
+ /** Retry delay in milliseconds (default: 1000) */
74
+ retryDelayMs?: number;
75
+
76
+ /** Connection timeout in milliseconds (default: 5000) */
77
+ connectTimeoutMs?: number;
78
+
79
+ /** Command timeout in milliseconds (default: 5000) */
80
+ commandTimeoutMs?: number;
81
+
82
+ /** Register a health check for this cache (default: true) */
83
+ healthCheck?: boolean;
84
+
85
+ /** Name for the health check (default: 'redis') */
86
+ healthCheckName?: string;
87
+
88
+ /** Health check interval in milliseconds (default: 30000) */
89
+ healthCheckInterval?: number;
90
+
91
+ /** Called when connection is ready */
92
+ onConnect?: () => void;
93
+
94
+ /** Called on connection errors */
95
+ onError?: (error: Error) => void;
96
+
97
+ /** Enable lazy connect - don't connect until first command (default: false) */
98
+ lazyConnect?: boolean;
99
+ }
100
+
101
+ /**
102
+ * Cache instance returned by the plugin
103
+ */
104
+ export interface CacheInstance {
105
+ /**
106
+ * Get a value from cache
107
+ * @param key - Cache key (prefix is applied automatically)
108
+ * @returns Parsed JSON value or null if not found
109
+ */
110
+ get<T = unknown>(key: string): Promise<T | null>;
111
+
112
+ /**
113
+ * Get a raw string value from cache
114
+ * @param key - Cache key (prefix is applied automatically)
115
+ * @returns Raw string value or null if not found
116
+ */
117
+ getRaw(key: string): Promise<string | null>;
118
+
119
+ /**
120
+ * Set a value in cache with TTL
121
+ * @param key - Cache key (prefix is applied automatically)
122
+ * @param value - Value to cache (will be JSON stringified)
123
+ * @param ttlSeconds - Time to live in seconds (uses defaultTtl if not specified)
124
+ */
125
+ set<T = unknown>(key: string, value: T, ttlSeconds?: number): Promise<void>;
126
+
127
+ /**
128
+ * Set a raw string value in cache with TTL
129
+ * @param key - Cache key (prefix is applied automatically)
130
+ * @param value - Raw string value to cache
131
+ * @param ttlSeconds - Time to live in seconds (uses defaultTtl if not specified)
132
+ */
133
+ setRaw(key: string, value: string, ttlSeconds?: number): Promise<void>;
134
+
135
+ /**
136
+ * Delete a key from cache
137
+ * @param key - Cache key (prefix is applied automatically)
138
+ * @returns true if key was deleted, false if it didn't exist
139
+ */
140
+ delete(key: string): Promise<boolean>;
141
+
142
+ /**
143
+ * Delete multiple keys matching a pattern
144
+ * @param pattern - Pattern to match (prefix is applied automatically)
145
+ * @returns Number of keys deleted
146
+ */
147
+ deletePattern(pattern: string): Promise<number>;
148
+
149
+ /**
150
+ * Check if a key exists in cache
151
+ * @param key - Cache key (prefix is applied automatically)
152
+ * @returns true if key exists
153
+ */
154
+ exists(key: string): Promise<boolean>;
155
+
156
+ /**
157
+ * Set expiration time on a key
158
+ * @param key - Cache key (prefix is applied automatically)
159
+ * @param ttlSeconds - Time to live in seconds
160
+ * @returns true if timeout was set, false if key doesn't exist
161
+ */
162
+ expire(key: string, ttlSeconds: number): Promise<boolean>;
163
+
164
+ /**
165
+ * Get remaining TTL for a key
166
+ * @param key - Cache key (prefix is applied automatically)
167
+ * @returns TTL in seconds, -1 if no expiry, -2 if key doesn't exist
168
+ */
169
+ ttl(key: string): Promise<number>;
170
+
171
+ /**
172
+ * Increment a numeric value
173
+ * @param key - Cache key (prefix is applied automatically)
174
+ * @param delta - Amount to increment by (default: 1)
175
+ * @returns New value after increment
176
+ */
177
+ incr(key: string, delta?: number): Promise<number>;
178
+
179
+ /**
180
+ * Get all keys matching a pattern
181
+ * @param pattern - Pattern to match (prefix is applied automatically)
182
+ * @returns Array of matching keys (without prefix)
183
+ */
184
+ keys(pattern: string): Promise<string[]>;
185
+
186
+ /**
187
+ * Flush all keys with the configured prefix
188
+ * @returns Number of keys deleted
189
+ */
190
+ flush(): Promise<number>;
191
+
192
+ /**
193
+ * Get cache statistics
194
+ */
195
+ getStats(): Promise<{
196
+ connected: boolean;
197
+ keyCount: number;
198
+ usedMemory?: string;
199
+ }>;
200
+
201
+ /**
202
+ * Get the underlying Redis client (for advanced use cases)
203
+ */
204
+ getClient(): Redis;
205
+
206
+ /**
207
+ * Close the connection
208
+ */
209
+ close(): Promise<void>;
210
+ }
211
+
212
+ // Global registry of cache instances by name
213
+ const instances = new Map<string, CacheInstance>();
214
+
215
+ /**
216
+ * Get a cache instance by name
217
+ *
218
+ * @param name - Instance name (default: 'default')
219
+ * @returns The cache instance
220
+ * @throws Error if the instance is not registered
221
+ *
222
+ * @example
223
+ * ```typescript
224
+ * const cache = getCache();
225
+ * const user = await cache.get<User>('user:123');
226
+ * ```
227
+ */
228
+ export function getCache(name = 'default'): CacheInstance {
229
+ const instance = instances.get(name);
230
+ if (!instance) {
231
+ throw new Error(`Cache instance "${name}" not found. Did you register the cache plugin?`);
232
+ }
233
+ return instance;
234
+ }
235
+
236
+ /**
237
+ * Check if a cache instance is registered
238
+ *
239
+ * @param name - Instance name (default: 'default')
240
+ * @returns true if the instance exists
241
+ */
242
+ export function hasCache(name = 'default'): boolean {
243
+ return instances.has(name);
244
+ }
245
+
246
+ /**
247
+ * Create a cache plugin
248
+ *
249
+ * @param config - Cache configuration
250
+ * @param instanceName - Name for this cache instance (default: 'default')
251
+ * @returns A control panel plugin
252
+ *
253
+ * @example
254
+ * ```typescript
255
+ * createCachePlugin({
256
+ * url: process.env.REDIS_URL,
257
+ * keyPrefix: 'myapp:',
258
+ * defaultTtl: 3600,
259
+ * healthCheck: true,
260
+ * });
261
+ * ```
262
+ */
263
+ export function createCachePlugin(
264
+ config: CachePluginConfig,
265
+ instanceName = 'default'
266
+ ): ControlPanelPlugin {
267
+ let client: Redis | null = null;
268
+ const prefix = config.keyPrefix ?? '';
269
+ const defaultTtl = config.defaultTtl ?? 3600;
270
+
271
+ const prefixKey = (key: string): string => `${prefix}${key}`;
272
+ const unprefixKey = (key: string): string =>
273
+ prefix && key.startsWith(prefix) ? key.slice(prefix.length) : key;
274
+
275
+ const createInstance = async (): Promise<CacheInstance> => {
276
+ // Dynamic import of ioredis
277
+ const { default: Redis } = await import('ioredis');
278
+
279
+ const options: RedisOptions = {
280
+ maxRetriesPerRequest: config.maxRetries ?? 3,
281
+ retryStrategy: (times: number) => {
282
+ const maxRetries = config.maxRetries ?? 3;
283
+ if (times > maxRetries) {
284
+ console.error(`[cache:${instanceName}] Connection failed after ${maxRetries} retries`);
285
+ return null; // Stop retrying
286
+ }
287
+ return config.retryDelayMs ?? 1000;
288
+ },
289
+ connectTimeout: config.connectTimeoutMs ?? 5000,
290
+ commandTimeout: config.commandTimeoutMs ?? 5000,
291
+ lazyConnect: config.lazyConnect ?? false,
292
+ };
293
+
294
+ client = new Redis(config.url, options);
295
+
296
+ // Handle events
297
+ client.on('error', (err) => {
298
+ if (config.onError) {
299
+ config.onError(err);
300
+ } else {
301
+ console.error(`[cache:${instanceName}] Error:`, err.message);
302
+ }
303
+ });
304
+
305
+ client.on('connect', () => {
306
+ if (config.onConnect) {
307
+ config.onConnect();
308
+ }
309
+ });
310
+
311
+ const instance: CacheInstance = {
312
+ async get<T = unknown>(key: string): Promise<T | null> {
313
+ if (!client) throw new Error('Cache client not initialized');
314
+ const value = await client.get(prefixKey(key));
315
+ if (value === null) return null;
316
+ try {
317
+ return JSON.parse(value) as T;
318
+ } catch {
319
+ return value as unknown as T;
320
+ }
321
+ },
322
+
323
+ async getRaw(key: string): Promise<string | null> {
324
+ if (!client) throw new Error('Cache client not initialized');
325
+ return client.get(prefixKey(key));
326
+ },
327
+
328
+ async set<T = unknown>(key: string, value: T, ttlSeconds?: number): Promise<void> {
329
+ if (!client) throw new Error('Cache client not initialized');
330
+ const ttl = ttlSeconds ?? defaultTtl;
331
+ const serialized = typeof value === 'string' ? value : JSON.stringify(value);
332
+ await client.setex(prefixKey(key), ttl, serialized);
333
+ },
334
+
335
+ async setRaw(key: string, value: string, ttlSeconds?: number): Promise<void> {
336
+ if (!client) throw new Error('Cache client not initialized');
337
+ const ttl = ttlSeconds ?? defaultTtl;
338
+ await client.setex(prefixKey(key), ttl, value);
339
+ },
340
+
341
+ async delete(key: string): Promise<boolean> {
342
+ if (!client) throw new Error('Cache client not initialized');
343
+ const count = await client.del(prefixKey(key));
344
+ return count > 0;
345
+ },
346
+
347
+ async deletePattern(pattern: string): Promise<number> {
348
+ if (!client) throw new Error('Cache client not initialized');
349
+ const keys = await client.keys(prefixKey(pattern));
350
+ if (keys.length === 0) return 0;
351
+ return client.del(...keys);
352
+ },
353
+
354
+ async exists(key: string): Promise<boolean> {
355
+ if (!client) throw new Error('Cache client not initialized');
356
+ const count = await client.exists(prefixKey(key));
357
+ return count > 0;
358
+ },
359
+
360
+ async expire(key: string, ttlSeconds: number): Promise<boolean> {
361
+ if (!client) throw new Error('Cache client not initialized');
362
+ const result = await client.expire(prefixKey(key), ttlSeconds);
363
+ return result === 1;
364
+ },
365
+
366
+ async ttl(key: string): Promise<number> {
367
+ if (!client) throw new Error('Cache client not initialized');
368
+ return client.ttl(prefixKey(key));
369
+ },
370
+
371
+ async incr(key: string, delta = 1): Promise<number> {
372
+ if (!client) throw new Error('Cache client not initialized');
373
+ if (delta === 1) {
374
+ return client.incr(prefixKey(key));
375
+ }
376
+ return client.incrby(prefixKey(key), delta);
377
+ },
378
+
379
+ async keys(pattern: string): Promise<string[]> {
380
+ if (!client) throw new Error('Cache client not initialized');
381
+ const keys = await client.keys(prefixKey(pattern));
382
+ return keys.map(unprefixKey);
383
+ },
384
+
385
+ async flush(): Promise<number> {
386
+ if (!client) throw new Error('Cache client not initialized');
387
+ if (!prefix) {
388
+ // Without prefix, this would flush the entire database - dangerous!
389
+ throw new Error('Cannot flush without a keyPrefix configured');
390
+ }
391
+ const keys = await client.keys(`${prefix}*`);
392
+ if (keys.length === 0) return 0;
393
+ return client.del(...keys);
394
+ },
395
+
396
+ async getStats(): Promise<{ connected: boolean; keyCount: number; usedMemory?: string }> {
397
+ if (!client) {
398
+ return { connected: false, keyCount: 0 };
399
+ }
400
+ try {
401
+ const info = await client.info('memory');
402
+ const memoryMatch = info.match(/used_memory_human:(\S+)/);
403
+ const usedMemory = memoryMatch ? memoryMatch[1] : undefined;
404
+
405
+ const keys = prefix
406
+ ? await client.keys(`${prefix}*`)
407
+ : await client.dbsize();
408
+ const keyCount = typeof keys === 'number' ? keys : keys.length;
409
+
410
+ return {
411
+ connected: client.status === 'ready',
412
+ keyCount,
413
+ usedMemory,
414
+ };
415
+ } catch {
416
+ return {
417
+ connected: client.status === 'ready',
418
+ keyCount: 0,
419
+ };
420
+ }
421
+ },
422
+
423
+ getClient(): Redis {
424
+ if (!client) throw new Error('Cache client not initialized');
425
+ return client;
426
+ },
427
+
428
+ async close(): Promise<void> {
429
+ if (client) {
430
+ await client.quit();
431
+ client = null;
432
+ }
433
+ },
434
+ };
435
+
436
+ return instance;
437
+ };
438
+
439
+ return {
440
+ name: `cache:${instanceName}`,
441
+ order: 5, // Initialize early, before other plugins that may need cache
442
+
443
+ async onInit(context: PluginContext): Promise<void> {
444
+ const { registerHealthCheck, logger } = context;
445
+
446
+ // Create and register the instance
447
+ const instance = await createInstance();
448
+ instances.set(instanceName, instance);
449
+
450
+ // Test connection
451
+ try {
452
+ // Ping to verify connection
453
+ await instance.getClient().ping();
454
+ logger.debug(`Cache "${instanceName}" connected`);
455
+ } catch (err) {
456
+ logger.error(`Cache "${instanceName}" connection failed: ${err instanceof Error ? err.message : String(err)}`);
457
+ throw err;
458
+ }
459
+
460
+ // Register health check if enabled
461
+ if (config.healthCheck !== false) {
462
+ registerHealthCheck({
463
+ name: config.healthCheckName ?? 'redis',
464
+ type: 'custom',
465
+ interval: config.healthCheckInterval ?? 30000,
466
+ timeout: 5000,
467
+ check: async () => {
468
+ const start = Date.now();
469
+ try {
470
+ await instance.getClient().ping();
471
+ const stats = await instance.getStats();
472
+ return {
473
+ healthy: true,
474
+ latency: Date.now() - start,
475
+ details: {
476
+ connected: stats.connected,
477
+ keyCount: stats.keyCount,
478
+ usedMemory: stats.usedMemory,
479
+ },
480
+ };
481
+ } catch (err) {
482
+ return {
483
+ healthy: false,
484
+ latency: Date.now() - start,
485
+ details: {
486
+ error: err instanceof Error ? err.message : String(err),
487
+ },
488
+ };
489
+ }
490
+ },
491
+ });
492
+ }
493
+ },
494
+
495
+ async onShutdown(): Promise<void> {
496
+ const instance = instances.get(instanceName);
497
+ if (instance) {
498
+ await instance.close();
499
+ instances.delete(instanceName);
500
+ }
501
+ },
502
+ };
503
+ }
@@ -101,7 +101,7 @@ export function createConfigPlugin(config: ConfigPluginConfig): ControlPanelPlug
101
101
  ],
102
102
 
103
103
  async onInit(context: PluginContext): Promise<void> {
104
- context.logger.info(`[ConfigPlugin] Initialized with ${config.show.length} visible vars`);
104
+ context.logger.debug(`Config plugin initialized with ${config.show.length} vars`);
105
105
  },
106
106
  };
107
107
  }
@@ -142,7 +142,7 @@ export function createDiagnosticsPlugin(config: DiagnosticsPluginConfig = {}): C
142
142
  ],
143
143
 
144
144
  async onInit(context: PluginContext): Promise<void> {
145
- context.logger.info('[DiagnosticsPlugin] Initialized');
145
+ context.logger.debug('Diagnostics plugin initialized');
146
146
  },
147
147
  };
148
148
  }
@@ -29,7 +29,7 @@ export function createHealthPlugin(config: HealthPluginConfig): ControlPanelPlug
29
29
  registerHealthCheck(check);
30
30
  }
31
31
 
32
- logger.info(`[HealthPlugin] Registered ${config.checks.length} health checks`);
32
+ logger.debug(`Registered ${config.checks.length} health checks`);
33
33
  },
34
34
  };
35
35
  }
@@ -18,3 +18,13 @@ export type { DiagnosticsPluginConfig } from './diagnostics-plugin.js';
18
18
 
19
19
  export { createFrontendAppPlugin } from './frontend-app-plugin.js';
20
20
  export type { FrontendAppPluginConfig } from './frontend-app-plugin.js';
21
+
22
+ export { createPostgresPlugin, getPostgres, hasPostgres } from './postgres-plugin.js';
23
+ export type { PostgresPluginConfig, PostgresInstance, TransactionCallback } from './postgres-plugin.js';
24
+
25
+ // Backward compatibility aliases (deprecated)
26
+ export { createPostgresPlugin as createDatabasePlugin, getPostgres as getDatabase, hasPostgres as hasDatabase } from './postgres-plugin.js';
27
+ export type { PostgresPluginConfig as DatabasePluginConfig, PostgresInstance as DatabaseInstance } from './postgres-plugin.js';
28
+
29
+ export { createCachePlugin, getCache, hasCache } from './cache-plugin.js';
30
+ export type { CachePluginConfig, CacheInstance } from './cache-plugin.js';
@@ -156,9 +156,7 @@ export function createLogsPlugin(config: LogsPluginConfig = {}): ControlPanelPlu
156
156
 
157
157
  async onInit(context: PluginContext): Promise<void> {
158
158
  const sources = getSources();
159
- context.logger.info(`Initialized with ${sources.length} sources`, {
160
- sources: sources.map((s) => ({ name: s.name, type: s.type, path: s.path })),
161
- });
159
+ context.logger.debug(`Logs plugin initialized with ${sources.length} sources`);
162
160
  },
163
161
  };
164
162
  }