@serve.zone/dcrouter 13.17.9 → 13.19.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.
Files changed (43) hide show
  1. package/dist_serve/bundle.js +6 -5
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/classes.dcrouter.d.ts +9 -5
  4. package/dist_ts/classes.dcrouter.js +152 -120
  5. package/dist_ts/config/classes.route-config-manager.d.ts +13 -5
  6. package/dist_ts/config/classes.route-config-manager.js +76 -36
  7. package/dist_ts/db/documents/classes.route.doc.d.ts +2 -0
  8. package/dist_ts/db/documents/classes.route.doc.js +11 -2
  9. package/dist_ts/email/classes.email-domain.manager.d.ts +7 -0
  10. package/dist_ts/email/classes.email-domain.manager.js +118 -55
  11. package/dist_ts/email/classes.smartmta-storage-manager.d.ts +13 -0
  12. package/dist_ts/email/classes.smartmta-storage-manager.js +101 -0
  13. package/dist_ts/email/email-dns-records.d.ts +14 -0
  14. package/dist_ts/email/email-dns-records.js +34 -0
  15. package/dist_ts/email/index.d.ts +2 -0
  16. package/dist_ts/email/index.js +3 -1
  17. package/dist_ts/opsserver/handlers/email-ops.handler.js +6 -15
  18. package/dist_ts/opsserver/handlers/route-management.handler.js +5 -7
  19. package/dist_ts/opsserver/handlers/stats.handler.js +41 -7
  20. package/dist_ts_interfaces/data/route-management.d.ts +2 -0
  21. package/dist_ts_migrations/index.js +25 -1
  22. package/dist_ts_web/00_commitinfo_data.js +1 -1
  23. package/dist_ts_web/appstate.js +13 -4
  24. package/dist_ts_web/elements/network/ops-view-routes.d.ts +2 -0
  25. package/dist_ts_web/elements/network/ops-view-routes.js +44 -21
  26. package/package.json +2 -2
  27. package/readme.md +190 -1543
  28. package/ts/00_commitinfo_data.ts +1 -1
  29. package/ts/classes.dcrouter.ts +190 -138
  30. package/ts/config/classes.route-config-manager.ts +97 -42
  31. package/ts/db/documents/classes.route.doc.ts +7 -0
  32. package/ts/email/classes.email-domain.manager.ts +136 -51
  33. package/ts/email/classes.smartmta-storage-manager.ts +108 -0
  34. package/ts/email/email-dns-records.ts +53 -0
  35. package/ts/email/index.ts +2 -0
  36. package/ts/opsserver/handlers/email-ops.handler.ts +5 -19
  37. package/ts/opsserver/handlers/route-management.handler.ts +4 -6
  38. package/ts/opsserver/handlers/stats.handler.ts +43 -7
  39. package/ts_apiclient/readme.md +69 -195
  40. package/ts_web/00_commitinfo_data.ts +1 -1
  41. package/ts_web/appstate.ts +16 -4
  42. package/ts_web/elements/network/ops-view-routes.ts +47 -29
  43. package/ts_web/readme.md +41 -242
@@ -0,0 +1,108 @@
1
+ import * as plugins from '../plugins.js';
2
+ import type { IStorageManagerLike } from '@push.rocks/smartmta';
3
+
4
+ export class SmartMtaStorageManager implements IStorageManagerLike {
5
+ private readonly resolvedRootDir: string;
6
+
7
+ constructor(private rootDir: string) {
8
+ this.resolvedRootDir = plugins.path.resolve(rootDir);
9
+ plugins.fsUtils.ensureDirSync(this.resolvedRootDir);
10
+ }
11
+
12
+ private normalizeKey(key: string): string {
13
+ return key.replace(/^\/+/, '').replace(/\\/g, '/');
14
+ }
15
+
16
+ private resolvePathForKey(key: string): string {
17
+ const normalizedKey = this.normalizeKey(key);
18
+ const resolvedPath = plugins.path.resolve(this.resolvedRootDir, normalizedKey);
19
+ if (
20
+ resolvedPath !== this.resolvedRootDir
21
+ && !resolvedPath.startsWith(`${this.resolvedRootDir}${plugins.path.sep}`)
22
+ ) {
23
+ throw new Error(`Storage key escapes root directory: ${key}`);
24
+ }
25
+ return resolvedPath;
26
+ }
27
+
28
+ private toStorageKey(filePath: string): string {
29
+ const relativePath = plugins.path.relative(this.resolvedRootDir, filePath).split(plugins.path.sep).join('/');
30
+ return `/${relativePath}`;
31
+ }
32
+
33
+ public async get(key: string): Promise<string | null> {
34
+ const filePath = this.resolvePathForKey(key);
35
+ try {
36
+ return await plugins.fs.promises.readFile(filePath, 'utf8');
37
+ } catch (error: unknown) {
38
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
39
+ return null;
40
+ }
41
+ throw error;
42
+ }
43
+ }
44
+
45
+ public async set(key: string, value: string): Promise<void> {
46
+ const filePath = this.resolvePathForKey(key);
47
+ await plugins.fs.promises.mkdir(plugins.path.dirname(filePath), { recursive: true });
48
+ await plugins.fs.promises.writeFile(filePath, value, 'utf8');
49
+ }
50
+
51
+ public async list(prefix: string): Promise<string[]> {
52
+ const prefixPath = this.resolvePathForKey(prefix);
53
+ try {
54
+ const stat = await plugins.fs.promises.stat(prefixPath);
55
+ if (stat.isFile()) {
56
+ return [this.toStorageKey(prefixPath)];
57
+ }
58
+ } catch (error: unknown) {
59
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
60
+ return [];
61
+ }
62
+ throw error;
63
+ }
64
+
65
+ const results: string[] = [];
66
+ const walk = async (currentPath: string): Promise<void> => {
67
+ const entries = await plugins.fs.promises.readdir(currentPath, { withFileTypes: true });
68
+ for (const entry of entries) {
69
+ const entryPath = plugins.path.join(currentPath, entry.name);
70
+ if (entry.isDirectory()) {
71
+ await walk(entryPath);
72
+ } else if (entry.isFile()) {
73
+ results.push(this.toStorageKey(entryPath));
74
+ }
75
+ }
76
+ };
77
+
78
+ await walk(prefixPath);
79
+ return results.sort();
80
+ }
81
+
82
+ public async delete(key: string): Promise<void> {
83
+ const targetPath = this.resolvePathForKey(key);
84
+ try {
85
+ const stat = await plugins.fs.promises.stat(targetPath);
86
+ if (stat.isDirectory()) {
87
+ await plugins.fs.promises.rm(targetPath, { recursive: true, force: true });
88
+ } else {
89
+ await plugins.fs.promises.unlink(targetPath);
90
+ }
91
+ } catch (error: unknown) {
92
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
93
+ return;
94
+ }
95
+ throw error;
96
+ }
97
+
98
+ let currentDir = plugins.path.dirname(targetPath);
99
+ while (currentDir.startsWith(this.resolvedRootDir) && currentDir !== this.resolvedRootDir) {
100
+ const entries = await plugins.fs.promises.readdir(currentDir);
101
+ if (entries.length > 0) {
102
+ break;
103
+ }
104
+ await plugins.fs.promises.rmdir(currentDir);
105
+ currentDir = plugins.path.dirname(currentDir);
106
+ }
107
+ }
108
+ }
@@ -0,0 +1,53 @@
1
+ import type {
2
+ IEmailDnsRecord,
3
+ TDnsRecordStatus,
4
+ } from '../../ts_interfaces/data/email-domain.js';
5
+
6
+ type TEmailDnsStatusKey = 'mx' | 'spf' | 'dkim' | 'dmarc';
7
+
8
+ export interface IBuildEmailDnsRecordsOptions {
9
+ domain: string;
10
+ hostname: string;
11
+ selector?: string;
12
+ dkimValue?: string;
13
+ mxPriority?: number;
14
+ dmarcPolicy?: string;
15
+ dmarcRua?: string;
16
+ statuses?: Partial<Record<TEmailDnsStatusKey, TDnsRecordStatus>>;
17
+ }
18
+
19
+ export function buildEmailDnsRecords(options: IBuildEmailDnsRecordsOptions): IEmailDnsRecord[] {
20
+ const statusFor = (key: TEmailDnsStatusKey): TDnsRecordStatus => options.statuses?.[key] ?? 'unchecked';
21
+ const selector = options.selector || 'default';
22
+ const records: IEmailDnsRecord[] = [
23
+ {
24
+ type: 'MX',
25
+ name: options.domain,
26
+ value: `${options.mxPriority ?? 10} ${options.hostname}`,
27
+ status: statusFor('mx'),
28
+ },
29
+ {
30
+ type: 'TXT',
31
+ name: options.domain,
32
+ value: 'v=spf1 a mx ~all',
33
+ status: statusFor('spf'),
34
+ },
35
+ {
36
+ type: 'TXT',
37
+ name: `_dmarc.${options.domain}`,
38
+ value: `v=DMARC1; p=${options.dmarcPolicy ?? 'none'}; rua=mailto:${options.dmarcRua ?? `dmarc@${options.domain}`}`,
39
+ status: statusFor('dmarc'),
40
+ },
41
+ ];
42
+
43
+ if (options.dkimValue) {
44
+ records.splice(2, 0, {
45
+ type: 'TXT',
46
+ name: `${selector}._domainkey.${options.domain}`,
47
+ value: options.dkimValue,
48
+ status: statusFor('dkim'),
49
+ });
50
+ }
51
+
52
+ return records;
53
+ }
package/ts/email/index.ts CHANGED
@@ -1 +1,3 @@
1
1
  export * from './classes.email-domain.manager.js';
2
+ export * from './classes.smartmta-storage-manager.js';
3
+ export * from './email-dns-records.js';
@@ -48,7 +48,7 @@ export class EmailOpsHandler {
48
48
  }
49
49
 
50
50
  const queue = emailServer.deliveryQueue;
51
- const item = queue.getItem(dataArg.emailId);
51
+ const item = emailServer.getQueueItem(dataArg.emailId);
52
52
 
53
53
  if (!item) {
54
54
  return { success: false, error: 'Email not found in queue' };
@@ -82,22 +82,10 @@ export class EmailOpsHandler {
82
82
  */
83
83
  private getAllQueueEmails(): interfaces.requests.IEmail[] {
84
84
  const emailServer = this.opsServerRef.dcRouterRef.emailServer;
85
- if (!emailServer?.deliveryQueue) {
85
+ if (!emailServer) {
86
86
  return [];
87
87
  }
88
-
89
- const queue = emailServer.deliveryQueue;
90
- const queueMap = (queue as any).queue as Map<string, any>;
91
-
92
- if (!queueMap) {
93
- return [];
94
- }
95
-
96
- const emails: interfaces.requests.IEmail[] = [];
97
-
98
- for (const [id, item] of queueMap.entries()) {
99
- emails.push(this.mapQueueItemToEmail(item));
100
- }
88
+ const emails = emailServer.getQueueItems().map((item) => this.mapQueueItemToEmail(item));
101
89
 
102
90
  // Sort by createdAt descending (newest first)
103
91
  emails.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
@@ -110,12 +98,10 @@ export class EmailOpsHandler {
110
98
  */
111
99
  private getEmailDetail(emailId: string): interfaces.requests.IEmailDetail | null {
112
100
  const emailServer = this.opsServerRef.dcRouterRef.emailServer;
113
- if (!emailServer?.deliveryQueue) {
101
+ if (!emailServer) {
114
102
  return null;
115
103
  }
116
-
117
- const queue = emailServer.deliveryQueue;
118
- const item = queue.getItem(emailId);
104
+ const item = emailServer.getQueueItem(emailId);
119
105
 
120
106
  if (!item) {
121
107
  return null;
@@ -87,12 +87,12 @@ export class RouteManagementHandler {
87
87
  if (!manager) {
88
88
  return { success: false, message: 'Route management not initialized' };
89
89
  }
90
- const ok = await manager.updateRoute(dataArg.id, {
90
+ const result = await manager.updateRoute(dataArg.id, {
91
91
  route: dataArg.route as any,
92
92
  enabled: dataArg.enabled,
93
93
  metadata: dataArg.metadata,
94
94
  });
95
- return { success: ok, message: ok ? undefined : 'Route not found' };
95
+ return result;
96
96
  },
97
97
  ),
98
98
  );
@@ -107,8 +107,7 @@ export class RouteManagementHandler {
107
107
  if (!manager) {
108
108
  return { success: false, message: 'Route management not initialized' };
109
109
  }
110
- const ok = await manager.deleteRoute(dataArg.id);
111
- return { success: ok, message: ok ? undefined : 'Route not found' };
110
+ return manager.deleteRoute(dataArg.id);
112
111
  },
113
112
  ),
114
113
  );
@@ -123,8 +122,7 @@ export class RouteManagementHandler {
123
122
  if (!manager) {
124
123
  return { success: false, message: 'Route management not initialized' };
125
124
  }
126
- const ok = await manager.toggleRoute(dataArg.id, dataArg.enabled);
127
- return { success: ok, message: ok ? undefined : 'Route not found' };
125
+ return manager.toggleRoute(dataArg.id, dataArg.enabled);
128
126
  },
129
127
  ),
130
128
  );
@@ -530,13 +530,49 @@ export class StatsHandler {
530
530
  nextRetry?: number;
531
531
  }>;
532
532
  }> {
533
- // TODO: Implement actual queue status collection
533
+ const emailServer = this.opsServerRef.dcRouterRef.emailServer;
534
+ if (!emailServer) {
535
+ return {
536
+ pending: 0,
537
+ active: 0,
538
+ failed: 0,
539
+ retrying: 0,
540
+ items: [],
541
+ };
542
+ }
543
+
544
+ const queueStats = emailServer.getQueueStats();
545
+ const items = emailServer.getQueueItems()
546
+ .sort((a, b) => {
547
+ const left = a.createdAt instanceof Date ? a.createdAt.getTime() : new Date(a.createdAt).getTime();
548
+ const right = b.createdAt instanceof Date ? b.createdAt.getTime() : new Date(b.createdAt).getTime();
549
+ return right - left;
550
+ })
551
+ .slice(0, 50)
552
+ .map((item) => {
553
+ const emailLike = item.processingResult;
554
+ const recipients = Array.isArray(emailLike?.to)
555
+ ? emailLike.to
556
+ : Array.isArray(emailLike?.email?.to)
557
+ ? emailLike.email.to
558
+ : [];
559
+ const subject = emailLike?.subject || emailLike?.email?.subject || '';
560
+ return {
561
+ id: item.id,
562
+ recipient: recipients[0] || '',
563
+ subject,
564
+ status: item.status,
565
+ attempts: item.attempts,
566
+ nextRetry: item.nextAttempt instanceof Date ? item.nextAttempt.getTime() : undefined,
567
+ };
568
+ });
569
+
534
570
  return {
535
- pending: 0,
536
- active: 0,
537
- failed: 0,
538
- retrying: 0,
539
- items: [],
571
+ pending: queueStats.status.pending,
572
+ active: queueStats.status.processing,
573
+ failed: queueStats.status.failed,
574
+ retrying: queueStats.status.deferred,
575
+ items,
540
576
  };
541
577
  }
542
578
 
@@ -600,4 +636,4 @@ export class StatsHandler {
600
636
  ],
601
637
  };
602
638
  }
603
- }
639
+ }
@@ -1,8 +1,8 @@
1
1
  # @serve.zone/dcrouter-apiclient
2
2
 
3
- A typed, object-oriented API client for DcRouter with a fluent builder pattern. 🔧
3
+ Typed, object-oriented API client for operating a running dcrouter instance. 🔧
4
4
 
5
- Programmatically manage your DcRouter instance routes, certificates, API tokens, remote ingress edges, RADIUS, email operations, and more all with full TypeScript type safety and an intuitive OO interface.
5
+ Use this package when you want a clean TypeScript client instead of manually firing TypedRequest calls. It wraps the OpsServer API in resource managers and resource classes such as routes, certificates, tokens, edges, emails, stats, logs, config, and RADIUS.
6
6
 
7
7
  ## Issue Reporting and Security
8
8
 
@@ -14,7 +14,7 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
14
14
  pnpm add @serve.zone/dcrouter-apiclient
15
15
  ```
16
16
 
17
- Or import directly from the main package:
17
+ Or import through the main package:
18
18
 
19
19
  ```typescript
20
20
  import { DcRouterApiClient } from '@serve.zone/dcrouter/apiclient';
@@ -23,239 +23,113 @@ import { DcRouterApiClient } from '@serve.zone/dcrouter/apiclient';
23
23
  ## Quick Start
24
24
 
25
25
  ```typescript
26
- import { DcRouterApiClient } from '@serve.zone/dcrouter/apiclient';
26
+ import { DcRouterApiClient } from '@serve.zone/dcrouter-apiclient';
27
27
 
28
- const client = new DcRouterApiClient({ baseUrl: 'https://dcrouter.example.com' });
28
+ const client = new DcRouterApiClient({
29
+ baseUrl: 'https://dcrouter.example.com',
30
+ });
29
31
 
30
- // Authenticate
31
32
  await client.login('admin', 'password');
32
33
 
33
- // List routes
34
- const { routes, warnings } = await client.routes.list();
35
- console.log(`${routes.length} routes, ${warnings.length} warnings`);
34
+ const { routes } = await client.routes.list();
35
+ console.log(routes.map((route) => `${route.origin}:${route.name}`));
36
36
 
37
- // Check health
38
- const { health } = await client.stats.getHealth();
39
- console.log(`Healthy: ${health.healthy}`);
37
+ await client.routes.build()
38
+ .setName('api-gateway')
39
+ .setMatch({ ports: 443, domains: ['api.example.com'] })
40
+ .setAction({ type: 'forward', targets: [{ host: '127.0.0.1', port: 8080 }] })
41
+ .save();
40
42
  ```
41
43
 
42
- ## Usage
44
+ ## Authentication Modes
43
45
 
44
- ### 🔐 Authentication
46
+ | Mode | How it works |
47
+ | --- | --- |
48
+ | Admin login | Call `login(username, password)` and the client stores the returned identity for later requests |
49
+ | API token | Pass `apiToken` into the constructor for token-based automation |
45
50
 
46
51
  ```typescript
47
- // Login with credentials — identity is stored and auto-injected into all subsequent requests
48
- const identity = await client.login('admin', 'password');
49
-
50
- // Verify current session
51
- const { valid } = await client.verifyIdentity();
52
-
53
- // Logout
54
- await client.logout();
55
-
56
- // Or use an API token for programmatic access (route management only)
57
52
  const client = new DcRouterApiClient({
58
53
  baseUrl: 'https://dcrouter.example.com',
59
54
  apiToken: 'dcr_your_token_here',
60
55
  });
61
56
  ```
62
57
 
63
- ### 🌐 Routes — OO Resources + Builder
58
+ ## Main Managers
64
59
 
65
- Routes are returned as `Route` instances with methods for update, delete, toggle, and overrides:
60
+ | Manager | Purpose |
61
+ | --- | --- |
62
+ | `client.routes` | List routes and create API-managed routes |
63
+ | `client.certificates` | Inspect and operate on certificate records |
64
+ | `client.apiTokens` | Create, list, toggle, roll, revoke API tokens |
65
+ | `client.remoteIngress` | Manage registered remote ingress edges |
66
+ | `client.stats` | Read operational metrics and health data |
67
+ | `client.config` | Read current configuration view |
68
+ | `client.logs` | Read recent logs or stream them |
69
+ | `client.emails` | List emails and trigger resend flows |
70
+ | `client.radius` | Operate on RADIUS clients, VLANs, sessions, and accounting |
66
71
 
67
- ```typescript
68
- // List all routes (hardcoded + programmatic)
69
- const { routes, warnings } = await client.routes.list();
72
+ ## Route Behavior
70
73
 
71
- // Inspect a route
72
- const route = routes[0];
73
- console.log(route.name, route.source, route.enabled);
74
+ Routes are returned as `Route` instances with:
74
75
 
75
- // Modify a programmatic route
76
- await route.update({ name: 'renamed-route' });
77
- await route.toggle(false);
78
- await route.delete();
76
+ - `id`
77
+ - `name`
78
+ - `enabled`
79
+ - `origin`
79
80
 
80
- // Override a hardcoded route (disable it)
81
- const hardcodedRoute = routes.find(r => r.source === 'hardcoded');
82
- await hardcodedRoute.setOverride(false);
83
- await hardcodedRoute.removeOverride();
84
- ```
81
+ Important behavior:
85
82
 
86
- **Builder pattern** for creating new routes:
83
+ - API routes can be created, updated, deleted, and toggled.
84
+ - System routes can be listed and toggled, but not edited or deleted.
85
+ - A system route is any route whose `origin !== 'api'`.
87
86
 
88
87
  ```typescript
89
- const newRoute = await client.routes.build()
90
- .setName('api-gateway')
91
- .setMatch({ ports: 443, domains: ['api.example.com'] })
92
- .setAction({ type: 'forward', targets: [{ host: 'backend', port: 8080 }] })
93
- .setTls({ mode: 'terminate', certificate: 'auto' })
94
- .setEnabled(true)
95
- .save();
88
+ const { routes } = await client.routes.list();
96
89
 
97
- // Or use quick creation
98
- const route = await client.routes.create(routeConfig);
90
+ for (const route of routes) {
91
+ if (route.origin !== 'api') {
92
+ await route.toggle(false);
93
+ }
94
+ }
99
95
  ```
100
96
 
101
- ### 🔑 API Tokens
97
+ ## Builder Example
102
98
 
103
99
  ```typescript
104
- // List existing tokens
105
- const tokens = await client.apiTokens.list();
106
-
107
- // Create with builder
108
- const token = await client.apiTokens.build()
109
- .setName('ci-pipeline')
110
- .setScopes(['routes:read', 'routes:write'])
111
- .addScope('config:read')
112
- .setExpiresInDays(90)
100
+ const route = await client.routes.build()
101
+ .setName('internal-app')
102
+ .setMatch({
103
+ ports: 80,
104
+ domains: ['internal.example.com'],
105
+ })
106
+ .setAction({
107
+ type: 'forward',
108
+ targets: [{ host: '127.0.0.1', port: 3000 }],
109
+ })
110
+ .setEnabled(true)
113
111
  .save();
114
112
 
115
- console.log(token.tokenValue); // Only available at creation time!
116
-
117
- // Manage tokens
118
- await token.toggle(false); // Disable
119
- const newValue = await token.roll(); // Regenerate secret
120
- await token.revoke(); // Delete
113
+ await route.toggle(false);
121
114
  ```
122
115
 
123
- ### 🔐 Certificates
116
+ ## Example: Certificates and Stats
124
117
 
125
118
  ```typescript
126
119
  const { certificates, summary } = await client.certificates.list();
127
- console.log(`${summary.valid} valid, ${summary.expiring} expiring, ${summary.failed} failed`);
128
-
129
- // Operate on individual certificates
130
- const cert = certificates[0];
131
- await cert.reprovision();
132
- const exported = await cert.export();
133
- await cert.delete();
134
-
135
- // Import a certificate
136
- await client.certificates.import({
137
- id: 'cert-id',
138
- domainName: 'example.com',
139
- created: Date.now(),
140
- validUntil: Date.now() + 90 * 24 * 3600 * 1000,
141
- privateKey: '...',
142
- publicKey: '...',
143
- csr: '...',
144
- });
145
- ```
120
+ console.log(summary.valid, summary.failed);
146
121
 
147
- ### 🌍 Remote Ingress
148
-
149
- ```typescript
150
- // List edges and their statuses
151
- const edges = await client.remoteIngress.list();
152
- const statuses = await client.remoteIngress.getStatuses();
153
-
154
- // Create with builder
155
- const edge = await client.remoteIngress.build()
156
- .setName('edge-nyc-01')
157
- .setListenPorts([80, 443])
158
- .setAutoDerivePorts(true)
159
- .setTags(['us-east'])
160
- .save();
161
-
162
- // Manage an edge
163
- await edge.update({ name: 'edge-nyc-02' });
164
- const newSecret = await edge.regenerateSecret();
165
- const token = await edge.getConnectionToken();
166
- await edge.delete();
167
- ```
168
-
169
- ### 📊 Statistics (Read-Only)
170
-
171
- ```typescript
172
- const serverStats = await client.stats.getServer({ timeRange: '24h', includeHistory: true });
173
- const emailStats = await client.stats.getEmail({ domain: 'example.com' });
174
- const dnsStats = await client.stats.getDns();
175
- const security = await client.stats.getSecurity({ includeDetails: true });
176
- const connections = await client.stats.getConnections({ protocol: 'https' });
177
- const queues = await client.stats.getQueues();
178
- const health = await client.stats.getHealth(true);
179
- const network = await client.stats.getNetwork();
180
- const combined = await client.stats.getCombined({ server: true, email: true });
181
- ```
182
-
183
- ### ⚙️ Configuration & Logs
184
-
185
- ```typescript
186
- // Read-only configuration
187
- const config = await client.config.get();
188
- const emailSection = await client.config.get('email');
189
-
190
- // Logs
191
- const { logs, total, hasMore } = await client.logs.getRecent({
192
- level: 'error',
193
- category: 'smtp',
194
- limit: 50,
195
- });
196
- ```
197
-
198
- ### 📧 Email Operations
199
-
200
- ```typescript
201
- const emails = await client.emails.list();
202
- const email = emails[0];
203
- const detail = await email.getDetail();
204
- await email.resend();
205
-
206
- // Or use the manager directly
207
- const detail2 = await client.emails.getDetail('email-id');
208
- await client.emails.resend('email-id');
209
- ```
210
-
211
- ### 📡 RADIUS
212
-
213
- ```typescript
214
- // Client management
215
- const clients = await client.radius.clients.list();
216
- await client.radius.clients.set({
217
- name: 'switch-1',
218
- ipRange: '192.168.1.0/24',
219
- secret: 'shared-secret',
220
- enabled: true,
221
- });
222
- await client.radius.clients.remove('switch-1');
223
-
224
- // VLAN management
225
- const { mappings, config: vlanConfig } = await client.radius.vlans.list();
226
- await client.radius.vlans.set({ mac: 'aa:bb:cc:dd:ee:ff', vlan: 10, enabled: true });
227
- const result = await client.radius.vlans.testAssignment('aa:bb:cc:dd:ee:ff');
228
- await client.radius.vlans.updateConfig({ defaultVlan: 200 });
229
-
230
- // Sessions
231
- const { sessions } = await client.radius.sessions.list({ vlanId: 10 });
232
- await client.radius.sessions.disconnect('session-id', 'Admin disconnect');
233
-
234
- // Statistics & Accounting
235
- const stats = await client.radius.getStatistics();
236
- const summary = await client.radius.getAccountingSummary(startTime, endTime);
122
+ const health = await client.stats.getHealth();
123
+ const recentLogs = await client.logs.getRecent({ level: 'error', limit: 20 });
237
124
  ```
238
125
 
239
- ## API Surface
240
-
241
- | Manager | Methods |
242
- |---------|---------|
243
- | `client.login()` / `logout()` / `verifyIdentity()` | Authentication |
244
- | `client.routes` | `list()`, `create()`, `build()` → Route: `update()`, `delete()`, `toggle()`, `setOverride()`, `removeOverride()` |
245
- | `client.certificates` | `list()`, `import()` → Certificate: `reprovision()`, `delete()`, `export()` |
246
- | `client.apiTokens` | `list()`, `create()`, `build()` → ApiToken: `revoke()`, `roll()`, `toggle()` |
247
- | `client.remoteIngress` | `list()`, `getStatuses()`, `create()`, `build()` → RemoteIngress: `update()`, `delete()`, `regenerateSecret()`, `getConnectionToken()` |
248
- | `client.stats` | `getServer()`, `getEmail()`, `getDns()`, `getRateLimits()`, `getSecurity()`, `getConnections()`, `getQueues()`, `getHealth()`, `getNetwork()`, `getCombined()` |
249
- | `client.config` | `get(section?)` |
250
- | `client.logs` | `getRecent()`, `getStream()` |
251
- | `client.emails` | `list()`, `getDetail()`, `resend()` → Email: `getDetail()`, `resend()` |
252
- | `client.radius` | `.clients.list/set/remove()`, `.vlans.list/set/remove/updateConfig/testAssignment()`, `.sessions.list/disconnect()`, `getStatistics()`, `getAccountingSummary()` |
253
-
254
- ## Architecture
126
+ ## What This Package Does Not Do
255
127
 
256
- The client uses HTTP-based [TypedRequest](https://code.foss.global/api.global/typedrequest) for transport. All requests are sent as POST to `{baseUrl}/typedrequest`. Authentication (JWT identity and/or API token) is automatically injected into every request payload via `buildRequestPayload()`.
128
+ - It does not start dcrouter.
129
+ - It does not embed the dashboard.
130
+ - It does not replace the request interfaces package if you only need raw types.
257
131
 
258
- Resource classes (`Route`, `Certificate`, `ApiToken`, `RemoteIngress`, `Email`) hold a reference to the client and provide instance methods that fire the appropriate TypedRequest operations. Builder classes (`RouteBuilder`, `ApiTokenBuilder`, `RemoteIngressBuilder`) use fluent chaining and a terminal `.save()` method.
132
+ Use `@serve.zone/dcrouter` to run the server, `@serve.zone/dcrouter-web` for the dashboard bundle/components, and `@serve.zone/dcrouter-interfaces` for raw API contracts.
259
133
 
260
134
  ## License and Legal Information
261
135
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '13.17.9',
6
+ version: '13.19.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }