@serve.zone/dcrouter 11.8.11 → 11.9.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.
@@ -252,6 +252,10 @@ export class DcRouter {
252
252
  // Certificate provisioning scheduler with per-domain backoff
253
253
  public certProvisionScheduler?: CertProvisionScheduler;
254
254
 
255
+ // Service lifecycle management
256
+ public serviceManager: plugins.taskbuffer.ServiceManager;
257
+ public smartAcmeReady = false;
258
+
255
259
  // TypedRouter for API endpoints
256
260
  public typedrouter = new plugins.typedrequest.TypedRouter();
257
261
 
@@ -279,67 +283,253 @@ export class DcRouter {
279
283
 
280
284
  // Initialize storage manager
281
285
  this.storageManager = new StorageManager(this.options.storage);
286
+
287
+ // Initialize service manager and register all services
288
+ this.serviceManager = new plugins.taskbuffer.ServiceManager({
289
+ name: 'dcrouter',
290
+ startupTimeoutMs: 120_000,
291
+ shutdownTimeoutMs: 30_000,
292
+ });
293
+ this.registerServices();
282
294
  }
283
295
 
284
- public async start() {
285
- logger.log('info', 'Starting DcRouter Services');
286
-
296
+ /**
297
+ * Register all dcrouter services with the ServiceManager.
298
+ * Services are started in dependency order, with failure isolation for optional services.
299
+ */
300
+ private registerServices(): void {
301
+ // OpsServer: critical, no dependencies — provides visibility
302
+ this.serviceManager.addService(
303
+ new plugins.taskbuffer.Service('OpsServer')
304
+ .critical()
305
+ .withStart(async () => {
306
+ this.opsServer = new OpsServer(this);
307
+ await this.opsServer.start();
308
+ })
309
+ .withStop(async () => {
310
+ await this.opsServer?.stop();
311
+ })
312
+ .withRetry({ maxRetries: 0 }),
313
+ );
287
314
 
288
- this.opsServer = new OpsServer(this);
289
- await this.opsServer.start();
315
+ // CacheDb: optional, no dependencies
316
+ if (this.options.cacheConfig?.enabled !== false) {
317
+ this.serviceManager.addService(
318
+ new plugins.taskbuffer.Service('CacheDb')
319
+ .optional()
320
+ .withStart(async () => {
321
+ await this.setupCacheDb();
322
+ })
323
+ .withStop(async () => {
324
+ if (this.cacheCleaner) {
325
+ this.cacheCleaner.stop();
326
+ this.cacheCleaner = undefined;
327
+ }
328
+ if (this.cacheDb) {
329
+ await this.cacheDb.stop();
330
+ CacheDb.resetInstance();
331
+ this.cacheDb = undefined;
332
+ }
333
+ })
334
+ .withRetry({ maxRetries: 2, baseDelayMs: 1000, maxDelayMs: 5000 }),
335
+ );
336
+ }
290
337
 
291
- try {
292
- // Initialize cache database if enabled (default: enabled)
293
- if (this.options.cacheConfig?.enabled !== false) {
294
- await this.setupCacheDb();
295
- }
338
+ // MetricsManager: optional, depends on OpsServer
339
+ this.serviceManager.addService(
340
+ new plugins.taskbuffer.Service('MetricsManager')
341
+ .optional()
342
+ .dependsOn('OpsServer')
343
+ .withStart(async () => {
344
+ this.metricsManager = new MetricsManager(this);
345
+ await this.metricsManager.start();
346
+ })
347
+ .withStop(async () => {
348
+ if (this.metricsManager) {
349
+ await this.metricsManager.stop();
350
+ this.metricsManager = undefined;
351
+ }
352
+ })
353
+ .withRetry({ maxRetries: 1, baseDelayMs: 1000 }),
354
+ );
296
355
 
297
- // Initialize MetricsManager
298
- this.metricsManager = new MetricsManager(this);
299
- await this.metricsManager.start();
300
-
301
- // Set up SmartProxy for HTTP/HTTPS and all traffic including email routes
302
- await this.setupSmartProxy();
303
-
304
- // Initialize programmatic config API managers
305
- this.routeConfigManager = new RouteConfigManager(
306
- this.storageManager,
307
- () => this.getConstructorRoutes(),
308
- () => this.smartProxy,
309
- () => this.options.http3,
356
+ // SmartProxy: critical, depends on CacheDb (if enabled)
357
+ const smartProxyDeps: string[] = [];
358
+ if (this.options.cacheConfig?.enabled !== false) {
359
+ smartProxyDeps.push('CacheDb');
360
+ }
361
+ this.serviceManager.addService(
362
+ new plugins.taskbuffer.Service('SmartProxy')
363
+ .critical()
364
+ .dependsOn(...smartProxyDeps)
365
+ .withStart(async () => {
366
+ await this.setupSmartProxy();
367
+ })
368
+ .withStop(async () => {
369
+ if (this.smartProxy) {
370
+ this.smartProxy.removeAllListeners();
371
+ await this.smartProxy.stop();
372
+ this.smartProxy = undefined;
373
+ }
374
+ })
375
+ .withRetry({ maxRetries: 0 }),
376
+ );
377
+
378
+ // SmartAcme: optional, depends on SmartProxy — aggressive retry for rate limits
379
+ // Only registered if DNS challenge is configured
380
+ if (this.options.dnsChallenge?.cloudflareApiKey) {
381
+ this.serviceManager.addService(
382
+ new plugins.taskbuffer.Service('SmartAcme')
383
+ .optional()
384
+ .dependsOn('SmartProxy')
385
+ .withStart(async () => {
386
+ if (this.smartAcme) {
387
+ await this.smartAcme.start();
388
+ this.smartAcmeReady = true;
389
+ logger.log('info', 'SmartAcme DNS-01 provider is now ready');
390
+ }
391
+ })
392
+ .withStop(async () => {
393
+ this.smartAcmeReady = false;
394
+ if (this.smartAcme) {
395
+ await this.smartAcme.stop();
396
+ this.smartAcme = undefined;
397
+ }
398
+ })
399
+ .withRetry({ maxRetries: 20, baseDelayMs: 5000, maxDelayMs: 3_600_000, backoffFactor: 2 }),
310
400
  );
311
- this.apiTokenManager = new ApiTokenManager(this.storageManager);
312
- await this.apiTokenManager.initialize();
313
- await this.routeConfigManager.initialize();
401
+ }
314
402
 
315
- // Set up unified email handling if configured
316
- if (this.options.emailConfig) {
317
- await this.setupUnifiedEmailHandling();
318
- }
319
-
320
- // Set up DNS server if configured with nameservers and scopes
321
- if (this.options.dnsNsDomains && this.options.dnsNsDomains.length > 0 &&
322
- this.options.dnsScopes && this.options.dnsScopes.length > 0) {
323
- await this.setupDnsWithSocketHandler();
324
- }
403
+ // ConfigManagers: optional, depends on SmartProxy
404
+ this.serviceManager.addService(
405
+ new plugins.taskbuffer.Service('ConfigManagers')
406
+ .optional()
407
+ .dependsOn('SmartProxy')
408
+ .withStart(async () => {
409
+ this.routeConfigManager = new RouteConfigManager(
410
+ this.storageManager,
411
+ () => this.getConstructorRoutes(),
412
+ () => this.smartProxy,
413
+ () => this.options.http3,
414
+ );
415
+ this.apiTokenManager = new ApiTokenManager(this.storageManager);
416
+ await this.apiTokenManager.initialize();
417
+ await this.routeConfigManager.initialize();
418
+ })
419
+ .withStop(async () => {
420
+ this.routeConfigManager = undefined;
421
+ this.apiTokenManager = undefined;
422
+ })
423
+ .withRetry({ maxRetries: 2, baseDelayMs: 1000 }),
424
+ );
325
425
 
326
- // Set up RADIUS server if configured
327
- if (this.options.radiusConfig) {
328
- await this.setupRadiusServer();
329
- }
426
+ // Email Server: optional, depends on SmartProxy
427
+ if (this.options.emailConfig) {
428
+ this.serviceManager.addService(
429
+ new plugins.taskbuffer.Service('EmailServer')
430
+ .optional()
431
+ .dependsOn('SmartProxy')
432
+ .withStart(async () => {
433
+ await this.setupUnifiedEmailHandling();
434
+ })
435
+ .withStop(async () => {
436
+ if (this.emailServer) {
437
+ if ((this.emailServer as any).deliverySystem) {
438
+ (this.emailServer as any).deliverySystem.removeAllListeners();
439
+ }
440
+ this.emailServer.removeAllListeners();
441
+ await this.emailServer.stop();
442
+ this.emailServer = undefined;
443
+ }
444
+ })
445
+ .withRetry({ maxRetries: 3, baseDelayMs: 2000, maxDelayMs: 30_000 }),
446
+ );
447
+ }
330
448
 
331
- // Set up Remote Ingress hub if configured
332
- if (this.options.remoteIngressConfig?.enabled) {
333
- await this.setupRemoteIngress();
334
- }
449
+ // DNS Server: optional, depends on SmartProxy
450
+ if (this.options.dnsNsDomains?.length > 0 && this.options.dnsScopes?.length > 0) {
451
+ this.serviceManager.addService(
452
+ new plugins.taskbuffer.Service('DnsServer')
453
+ .optional()
454
+ .dependsOn('SmartProxy')
455
+ .withStart(async () => {
456
+ await this.setupDnsWithSocketHandler();
457
+ })
458
+ .withStop(async () => {
459
+ // Flush pending DNS batch log
460
+ if (this.dnsBatchTimer) {
461
+ clearTimeout(this.dnsBatchTimer);
462
+ if (this.dnsBatchCount > 0) {
463
+ logger.log('info', `DNS: ${this.dnsBatchCount} queries processed (final flush)`, { zone: 'dns' });
464
+ }
465
+ this.dnsBatchTimer = null;
466
+ this.dnsBatchCount = 0;
467
+ this.dnsLogWindowSecond = 0;
468
+ this.dnsLogWindowCount = 0;
469
+ }
470
+ if (this.dnsServer) {
471
+ this.dnsServer.removeAllListeners();
472
+ await this.dnsServer.stop();
473
+ this.dnsServer = undefined;
474
+ }
475
+ })
476
+ .withRetry({ maxRetries: 3, baseDelayMs: 2000, maxDelayMs: 30_000 }),
477
+ );
478
+ }
335
479
 
336
- this.logStartupSummary();
337
- } catch (error) {
338
- logger.log('error', 'Error starting DcRouter', { error: String(error) });
339
- // Try to clean up any services that may have started
340
- await this.stop();
341
- throw error;
480
+ // RADIUS Server: optional, no dependency on SmartProxy
481
+ if (this.options.radiusConfig) {
482
+ this.serviceManager.addService(
483
+ new plugins.taskbuffer.Service('RadiusServer')
484
+ .optional()
485
+ .withStart(async () => {
486
+ await this.setupRadiusServer();
487
+ })
488
+ .withStop(async () => {
489
+ if (this.radiusServer) {
490
+ await this.radiusServer.stop();
491
+ this.radiusServer = undefined;
492
+ }
493
+ })
494
+ .withRetry({ maxRetries: 3, baseDelayMs: 2000, maxDelayMs: 30_000 }),
495
+ );
342
496
  }
497
+
498
+ // Remote Ingress: optional, depends on SmartProxy
499
+ if (this.options.remoteIngressConfig?.enabled) {
500
+ this.serviceManager.addService(
501
+ new plugins.taskbuffer.Service('RemoteIngress')
502
+ .optional()
503
+ .dependsOn('SmartProxy')
504
+ .withStart(async () => {
505
+ await this.setupRemoteIngress();
506
+ })
507
+ .withStop(async () => {
508
+ if (this.tunnelManager) {
509
+ await this.tunnelManager.stop();
510
+ this.tunnelManager = undefined;
511
+ }
512
+ this.remoteIngressManager = undefined;
513
+ })
514
+ .withRetry({ maxRetries: 3, baseDelayMs: 2000, maxDelayMs: 30_000 }),
515
+ );
516
+ }
517
+
518
+ // Wire up aggregated events for logging
519
+ this.serviceManager.serviceSubject.subscribe((event) => {
520
+ const level = event.type === 'failed' ? 'error' : event.type === 'retrying' ? 'warn' : 'info';
521
+ logger.log(level as any, `Service '${event.serviceName}': ${event.type}`, {
522
+ state: event.state,
523
+ ...(event.error ? { error: event.error } : {}),
524
+ ...(event.attempt ? { attempt: event.attempt } : {}),
525
+ });
526
+ });
527
+ }
528
+
529
+ public async start() {
530
+ logger.log('info', 'Starting DcRouter Services');
531
+ await this.serviceManager.start();
532
+ this.logStartupSummary();
343
533
  }
344
534
 
345
535
  /**
@@ -399,7 +589,21 @@ export class DcRouter {
399
589
  logger.log('info', `Cache Database: storage=${this.cacheDb.getStoragePath()}, db=${this.cacheDb.getDbName()}, cleaner=${this.cacheCleaner?.isActive() ? 'active' : 'inactive'} (${(this.options.cacheConfig?.cleanupIntervalHours || 1)}h interval)`);
400
590
  }
401
591
 
402
- logger.log('info', 'All services are running');
592
+ // Service status summary from ServiceManager
593
+ const health = this.serviceManager.getHealth();
594
+ const statuses = health.services;
595
+ const running = statuses.filter(s => s.state === 'running').length;
596
+ const failed = statuses.filter(s => s.state === 'failed').length;
597
+ const retrying = statuses.filter(s => s.state === 'starting' || s.state === 'degraded').length;
598
+
599
+ if (failed > 0) {
600
+ const failedNames = statuses.filter(s => s.state === 'failed').map(s => `${s.name}: ${s.lastError || 'unknown'}`);
601
+ logger.log('warn', `DcRouter started in degraded mode — ${running} running, ${failed} failed: ${failedNames.join('; ')}`);
602
+ } else if (retrying > 0) {
603
+ logger.log('info', `DcRouter started — ${running} running, ${retrying} still initializing`);
604
+ } else {
605
+ logger.log('info', `All ${running} services are running`);
606
+ }
403
607
  }
404
608
 
405
609
  /**
@@ -535,10 +739,13 @@ export class DcRouter {
535
739
  // Initialize cert provision scheduler
536
740
  this.certProvisionScheduler = new CertProvisionScheduler(this.storageManager);
537
741
 
538
- // If we have DNS challenge handlers, create SmartAcme and wire to certProvisionFunction
742
+ // If we have DNS challenge handlers, create SmartAcme instance and wire certProvisionFunction
743
+ // Note: SmartAcme.start() is NOT called here — it runs as a separate optional service
744
+ // via the ServiceManager, with aggressive retry for rate-limit resilience.
539
745
  if (challengeHandlers.length > 0) {
540
746
  // Stop old SmartAcme if it exists (e.g., during updateSmartProxyConfig)
541
747
  if (this.smartAcme) {
748
+ this.smartAcmeReady = false;
542
749
  await this.smartAcme.stop().catch(err =>
543
750
  logger.log('error', 'Error stopping old SmartAcme', { error: String(err) })
544
751
  );
@@ -550,10 +757,15 @@ export class DcRouter {
550
757
  challengeHandlers: challengeHandlers,
551
758
  challengePriority: ['dns-01'],
552
759
  });
553
- await this.smartAcme.start();
554
760
 
555
761
  const scheduler = this.certProvisionScheduler;
556
762
  smartProxyConfig.certProvisionFunction = async (domain, eventComms) => {
763
+ // If SmartAcme is not yet ready (still starting or retrying), fall back to HTTP-01
764
+ if (!this.smartAcmeReady) {
765
+ eventComms.warn(`SmartAcme not yet initialized, falling back to http-01 for ${domain}`);
766
+ return 'http01';
767
+ }
768
+
557
769
  // Check backoff before attempting provision
558
770
  if (await scheduler.isInBackoff(domain)) {
559
771
  const info = await scheduler.getBackoffInfo(domain);
@@ -914,105 +1126,23 @@ export class DcRouter {
914
1126
  public async stop() {
915
1127
  logger.log('info', 'Stopping DcRouter services...');
916
1128
 
917
- // Flush pending DNS batch log
918
- if (this.dnsBatchTimer) {
919
- clearTimeout(this.dnsBatchTimer);
920
- if (this.dnsBatchCount > 0) {
921
- logger.log('info', `DNS: ${this.dnsBatchCount} queries processed (rate limited, final flush)`, { zone: 'dns' });
922
- }
923
- this.dnsBatchTimer = null;
924
- this.dnsBatchCount = 0;
925
- this.dnsLogWindowSecond = 0;
926
- this.dnsLogWindowCount = 0;
927
- }
928
-
929
- await this.opsServer.stop();
930
-
931
- try {
932
- // Remove event listeners before stopping services to prevent leaks
933
- if (this.smartProxy) {
934
- this.smartProxy.removeAllListeners();
935
- }
936
- if (this.emailServer) {
937
- if ((this.emailServer as any).deliverySystem) {
938
- (this.emailServer as any).deliverySystem.removeAllListeners();
939
- }
940
- this.emailServer.removeAllListeners();
941
- }
942
- if (this.dnsServer) {
943
- this.dnsServer.removeAllListeners();
944
- }
945
-
946
- // Stop all services in parallel for faster shutdown
947
- await Promise.all([
948
- // Stop cache cleaner if running
949
- this.cacheCleaner ? Promise.resolve(this.cacheCleaner.stop()) : Promise.resolve(),
950
-
951
- // Stop metrics manager if running
952
- this.metricsManager ? this.metricsManager.stop().catch(err => logger.log('error', 'Error stopping MetricsManager', { error: String(err) })) : Promise.resolve(),
953
-
954
- // Stop unified email server if running
955
- this.emailServer ? this.emailServer.stop().catch(err => logger.log('error', 'Error stopping email server', { error: String(err) })) : Promise.resolve(),
956
-
957
- // Stop SmartAcme if running
958
- this.smartAcme ? this.smartAcme.stop().catch(err => logger.log('error', 'Error stopping SmartAcme', { error: String(err) })) : Promise.resolve(),
959
-
960
- // Stop HTTP SmartProxy if running
961
- this.smartProxy ? this.smartProxy.stop().catch(err => logger.log('error', 'Error stopping SmartProxy', { error: String(err) })) : Promise.resolve(),
962
-
963
- // Stop DNS server if running
964
- this.dnsServer ?
965
- this.dnsServer.stop().catch(err => logger.log('error', 'Error stopping DNS server', { error: String(err) })) :
966
- Promise.resolve(),
967
-
968
- // Stop RADIUS server if running
969
- this.radiusServer ?
970
- this.radiusServer.stop().catch(err => logger.log('error', 'Error stopping RADIUS server', { error: String(err) })) :
971
- Promise.resolve(),
1129
+ // ServiceManager handles reverse-dependency-ordered shutdown
1130
+ await this.serviceManager.stop();
972
1131
 
973
- // Stop Remote Ingress tunnel manager if running
974
- this.tunnelManager ?
975
- this.tunnelManager.stop().catch(err => logger.log('error', 'Error stopping TunnelManager', { error: String(err) })) :
976
- Promise.resolve()
977
- ]);
978
-
979
- // Stop cache database after other services (they may need it during shutdown)
980
- if (this.cacheDb) {
981
- await this.cacheDb.stop().catch(err => logger.log('error', 'Error stopping CacheDb', { error: String(err) }));
982
- CacheDb.resetInstance();
983
- }
984
-
985
- // Clear backoff cache in cert scheduler
986
- if (this.certProvisionScheduler) {
987
- this.certProvisionScheduler.clear();
988
- }
989
-
990
- // Allow GC of stopped services by nulling references
991
- this.smartProxy = undefined;
992
- this.emailServer = undefined;
993
- this.dnsServer = undefined;
994
- this.metricsManager = undefined;
995
- this.cacheCleaner = undefined;
996
- this.cacheDb = undefined;
997
- this.tunnelManager = undefined;
998
- this.radiusServer = undefined;
999
- this.smartAcme = undefined;
1132
+ // Clear backoff cache in cert scheduler
1133
+ if (this.certProvisionScheduler) {
1134
+ this.certProvisionScheduler.clear();
1000
1135
  this.certProvisionScheduler = undefined;
1001
- this.remoteIngressManager = undefined;
1002
- this.routeConfigManager = undefined;
1003
- this.apiTokenManager = undefined;
1004
- this.certificateStatusMap.clear();
1136
+ }
1005
1137
 
1006
- // Reset security singletons to allow GC
1007
- SecurityLogger.resetInstance();
1008
- ContentScanner.resetInstance();
1009
- IPReputationChecker.resetInstance();
1138
+ this.certificateStatusMap.clear();
1010
1139
 
1011
- logger.log('info', 'All DcRouter services stopped');
1012
- } catch (error) {
1013
- logger.log('error', 'Error during DcRouter shutdown', { error: String(error) });
1014
- throw error;
1015
- }
1140
+ // Reset security singletons to allow GC
1141
+ SecurityLogger.resetInstance();
1142
+ ContentScanner.resetInstance();
1143
+ IPReputationChecker.resetInstance();
1144
+
1145
+ logger.log('info', 'All DcRouter services stopped');
1016
1146
  }
1017
1147
 
1018
1148
  /**
@@ -489,44 +489,41 @@ export class StatsHandler {
489
489
  message?: string;
490
490
  }>;
491
491
  }> {
492
- const services: Array<{
493
- name: string;
494
- status: 'healthy' | 'degraded' | 'unhealthy';
495
- message?: string;
496
- }> = [];
497
-
498
- // Check HTTP Proxy
499
- if (this.opsServerRef.dcRouterRef.smartProxy) {
500
- services.push({
501
- name: 'HTTP/HTTPS Proxy',
502
- status: 'healthy',
503
- });
504
- }
505
-
506
- // Check Email Server
507
- if (this.opsServerRef.dcRouterRef.emailServer) {
508
- services.push({
509
- name: 'Email Server',
510
- status: 'healthy',
511
- });
512
- }
513
-
514
- // Check DNS Server
515
- if (this.opsServerRef.dcRouterRef.dnsServer) {
516
- services.push({
517
- name: 'DNS Server',
518
- status: 'healthy',
519
- });
520
- }
521
-
522
- // Check OpsServer
523
- services.push({
524
- name: 'OpsServer',
525
- status: 'healthy',
492
+ const dcRouter = this.opsServerRef.dcRouterRef;
493
+ const health = dcRouter.serviceManager.getHealth();
494
+
495
+ const services = health.services.map((svc) => {
496
+ let status: 'healthy' | 'degraded' | 'unhealthy';
497
+ switch (svc.state) {
498
+ case 'running':
499
+ status = 'healthy';
500
+ break;
501
+ case 'starting':
502
+ case 'degraded':
503
+ status = 'degraded';
504
+ break;
505
+ case 'failed':
506
+ status = svc.criticality === 'critical' ? 'unhealthy' : 'degraded';
507
+ break;
508
+ case 'stopped':
509
+ case 'stopping':
510
+ default:
511
+ status = 'degraded';
512
+ break;
513
+ }
514
+
515
+ let message: string | undefined;
516
+ if (svc.state === 'failed' && svc.lastError) {
517
+ message = svc.lastError;
518
+ } else if (svc.retryCount > 0 && svc.state !== 'running') {
519
+ message = `Retry attempt ${svc.retryCount}`;
520
+ }
521
+
522
+ return { name: svc.name, status, message };
526
523
  });
527
-
528
- const healthy = services.every(s => s.status === 'healthy');
529
-
524
+
525
+ const healthy = health.overall === 'healthy';
526
+
530
527
  return {
531
528
  healthy,
532
529
  services,
package/ts/plugins.ts CHANGED
@@ -62,8 +62,9 @@ import * as smartradius from '@push.rocks/smartradius';
62
62
  import * as smartrequest from '@push.rocks/smartrequest';
63
63
  import * as smartrx from '@push.rocks/smartrx';
64
64
  import * as smartunique from '@push.rocks/smartunique';
65
+ import * as taskbuffer from '@push.rocks/taskbuffer';
65
66
 
66
- export { projectinfo, qenv, smartacme, smartdata, smartdns, smartfile, smartguard, smartjwt, smartlog, smartmetrics, smartmongo, smartmta, smartnetwork, smartpath, smartproxy, smartpromise, smartradius, smartrequest, smartrx, smartunique };
67
+ export { projectinfo, qenv, smartacme, smartdata, smartdns, smartfile, smartguard, smartjwt, smartlog, smartmetrics, smartmongo, smartmta, smartnetwork, smartpath, smartproxy, smartpromise, smartradius, smartrequest, smartrx, smartunique, taskbuffer };
67
68
 
68
69
  // Define SmartLog types for use in error handling
69
70
  export type TLogLevel = 'error' | 'warn' | 'info' | 'success' | 'debug';
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '11.8.11',
6
+ version: '11.9.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }