@serve.zone/dcrouter 13.18.0 → 13.19.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.
Files changed (35) hide show
  1. package/dist_serve/bundle.js +532 -531
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/classes.dcrouter.d.ts +2 -0
  4. package/dist_ts/classes.dcrouter.js +50 -39
  5. package/dist_ts/config/classes.route-config-manager.d.ts +14 -5
  6. package/dist_ts/config/classes.route-config-manager.js +121 -44
  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.js +9 -28
  10. package/dist_ts/email/email-dns-records.d.ts +14 -0
  11. package/dist_ts/email/email-dns-records.js +34 -0
  12. package/dist_ts/email/index.d.ts +1 -0
  13. package/dist_ts/email/index.js +2 -1
  14. package/dist_ts/opsserver/handlers/route-management.handler.js +5 -7
  15. package/dist_ts_interfaces/data/route-management.d.ts +2 -0
  16. package/dist_ts_migrations/index.js +25 -1
  17. package/dist_ts_web/00_commitinfo_data.js +1 -1
  18. package/dist_ts_web/appstate.js +13 -4
  19. package/dist_ts_web/elements/network/ops-view-routes.d.ts +2 -0
  20. package/dist_ts_web/elements/network/ops-view-routes.js +124 -36
  21. package/package.json +2 -3
  22. package/readme.md +190 -1543
  23. package/ts/00_commitinfo_data.ts +1 -1
  24. package/ts/classes.dcrouter.ts +61 -47
  25. package/ts/config/classes.route-config-manager.ts +148 -50
  26. package/ts/db/documents/classes.route.doc.ts +7 -0
  27. package/ts/email/classes.email-domain.manager.ts +8 -28
  28. package/ts/email/email-dns-records.ts +53 -0
  29. package/ts/email/index.ts +1 -0
  30. package/ts/opsserver/handlers/route-management.handler.ts +4 -6
  31. package/ts_apiclient/readme.md +69 -195
  32. package/ts_web/00_commitinfo_data.ts +1 -1
  33. package/ts_web/appstate.ts +16 -4
  34. package/ts_web/elements/network/ops-view-routes.ts +136 -44
  35. package/ts_web/readme.md +41 -242
@@ -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
  );
@@ -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.18.0',
6
+ version: '13.19.1',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -2150,7 +2150,7 @@ export const updateRouteAction = routeManagementStatePart.createAction<{
2150
2150
  interfaces.requests.IReq_UpdateRoute
2151
2151
  >('/typedrequest', 'updateRoute');
2152
2152
 
2153
- await request.fire({
2153
+ const response = await request.fire({
2154
2154
  identity: context.identity!,
2155
2155
  id: dataArg.id,
2156
2156
  route: dataArg.route,
@@ -2158,6 +2158,10 @@ export const updateRouteAction = routeManagementStatePart.createAction<{
2158
2158
  metadata: dataArg.metadata,
2159
2159
  });
2160
2160
 
2161
+ if (!response.success) {
2162
+ throw new Error(response.message || 'Failed to update route');
2163
+ }
2164
+
2161
2165
  return await actionContext!.dispatch(fetchMergedRoutesAction, null);
2162
2166
  } catch (error: unknown) {
2163
2167
  return {
@@ -2177,11 +2181,15 @@ export const deleteRouteAction = routeManagementStatePart.createAction<string>(
2177
2181
  interfaces.requests.IReq_DeleteRoute
2178
2182
  >('/typedrequest', 'deleteRoute');
2179
2183
 
2180
- await request.fire({
2184
+ const response = await request.fire({
2181
2185
  identity: context.identity!,
2182
2186
  id: routeId,
2183
2187
  });
2184
2188
 
2189
+ if (!response.success) {
2190
+ throw new Error(response.message || 'Failed to delete route');
2191
+ }
2192
+
2185
2193
  return await actionContext!.dispatch(fetchMergedRoutesAction, null);
2186
2194
  } catch (error: unknown) {
2187
2195
  return {
@@ -2204,12 +2212,16 @@ export const toggleRouteAction = routeManagementStatePart.createAction<{
2204
2212
  interfaces.requests.IReq_ToggleRoute
2205
2213
  >('/typedrequest', 'toggleRoute');
2206
2214
 
2207
- await request.fire({
2215
+ const response = await request.fire({
2208
2216
  identity: context.identity!,
2209
2217
  id: dataArg.id,
2210
2218
  enabled: dataArg.enabled,
2211
2219
  });
2212
2220
 
2221
+ if (!response.success) {
2222
+ throw new Error(response.message || 'Failed to toggle route');
2223
+ }
2224
+
2213
2225
  return await actionContext!.dispatch(fetchMergedRoutesAction, null);
2214
2226
  } catch (error: unknown) {
2215
2227
  return {
@@ -2765,4 +2777,4 @@ startAutoRefresh();
2765
2777
  // Connect TypedSocket if already logged in (e.g., persistent session)
2766
2778
  if (loginStatePart.getState()!.isLoggedIn) {
2767
2779
  connectSocket();
2768
- }
2780
+ }