@serve.zone/dcrouter 8.0.0 → 9.1.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 (88) hide show
  1. package/dist_serve/bundle.js +2420 -1227
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/classes.dcrouter.d.ts +9 -0
  4. package/dist_ts/classes.dcrouter.js +27 -1
  5. package/dist_ts/config/classes.api-token-manager.d.ts +38 -0
  6. package/dist_ts/config/classes.api-token-manager.js +134 -0
  7. package/dist_ts/config/classes.route-config-manager.d.ts +35 -0
  8. package/dist_ts/config/classes.route-config-manager.js +231 -0
  9. package/dist_ts/config/index.d.ts +2 -0
  10. package/dist_ts/config/index.js +3 -1
  11. package/dist_ts/opsserver/classes.opsserver.d.ts +2 -0
  12. package/dist_ts/opsserver/classes.opsserver.js +5 -1
  13. package/dist_ts/opsserver/handlers/{config.handler.d.ts → api-token.handler.d.ts} +5 -2
  14. package/dist_ts/opsserver/handlers/api-token.handler.js +66 -0
  15. package/dist_ts/opsserver/handlers/index.d.ts +2 -0
  16. package/dist_ts/opsserver/handlers/index.js +3 -1
  17. package/dist_ts/opsserver/handlers/route-management.handler.d.ts +13 -0
  18. package/dist_ts/opsserver/handlers/route-management.handler.js +117 -0
  19. package/dist_ts_interfaces/data/index.d.ts +1 -0
  20. package/dist_ts_interfaces/data/index.js +2 -1
  21. package/dist_ts_interfaces/data/route-management.d.ts +68 -0
  22. package/dist_ts_interfaces/data/route-management.js +2 -0
  23. package/dist_ts_interfaces/requests/api-tokens.d.ts +63 -0
  24. package/dist_ts_interfaces/requests/api-tokens.js +2 -0
  25. package/dist_ts_interfaces/requests/config.d.ts +77 -1
  26. package/dist_ts_interfaces/requests/index.d.ts +2 -0
  27. package/dist_ts_interfaces/requests/index.js +3 -1
  28. package/dist_ts_interfaces/requests/route-management.d.ts +114 -0
  29. package/dist_ts_interfaces/requests/route-management.js +2 -0
  30. package/dist_ts_web/00_commitinfo_data.js +1 -1
  31. package/dist_ts_web/appstate.d.ts +37 -1
  32. package/dist_ts_web/appstate.js +220 -2
  33. package/dist_ts_web/elements/index.d.ts +2 -0
  34. package/dist_ts_web/elements/index.js +3 -1
  35. package/dist_ts_web/elements/ops-dashboard.js +23 -3
  36. package/dist_ts_web/elements/ops-view-apitokens.d.ts +12 -0
  37. package/dist_ts_web/elements/ops-view-apitokens.js +310 -0
  38. package/dist_ts_web/elements/ops-view-config.d.ts +10 -8
  39. package/dist_ts_web/elements/ops-view-config.js +215 -297
  40. package/dist_ts_web/elements/ops-view-routes.d.ts +12 -0
  41. package/dist_ts_web/elements/ops-view-routes.js +404 -0
  42. package/dist_ts_web/router.d.ts +1 -1
  43. package/dist_ts_web/router.js +2 -2
  44. package/package.json +2 -2
  45. package/ts/00_commitinfo_data.ts +1 -1
  46. package/ts/classes.dcrouter.ts +37 -1
  47. package/ts/config/classes.api-token-manager.ts +155 -0
  48. package/ts/config/classes.route-config-manager.ts +271 -0
  49. package/ts/config/index.ts +3 -1
  50. package/ts/opsserver/classes.opsserver.ts +4 -0
  51. package/ts/opsserver/handlers/api-token.handler.ts +96 -0
  52. package/ts/opsserver/handlers/config.handler.ts +154 -72
  53. package/ts/opsserver/handlers/index.ts +3 -1
  54. package/ts/opsserver/handlers/route-management.handler.ts +163 -0
  55. package/ts_web/00_commitinfo_data.ts +1 -1
  56. package/ts_web/appstate.ts +309 -2
  57. package/ts_web/elements/index.ts +2 -0
  58. package/ts_web/elements/ops-dashboard.ts +22 -2
  59. package/ts_web/elements/ops-view-apitokens.ts +285 -0
  60. package/ts_web/elements/ops-view-config.ts +237 -299
  61. package/ts_web/elements/ops-view-routes.ts +389 -0
  62. package/ts_web/router.ts +1 -1
  63. package/dist_ts/cache/classes.cache.cleaner.d.ts +0 -47
  64. package/dist_ts/cache/classes.cache.cleaner.js +0 -130
  65. package/dist_ts/cache/classes.cached.document.d.ts +0 -76
  66. package/dist_ts/cache/classes.cached.document.js +0 -100
  67. package/dist_ts/cache/classes.cachedb.d.ts +0 -60
  68. package/dist_ts/cache/classes.cachedb.js +0 -126
  69. package/dist_ts/cache/documents/classes.cached.email.d.ts +0 -125
  70. package/dist_ts/cache/documents/classes.cached.email.js +0 -337
  71. package/dist_ts/cache/documents/classes.cached.ip.reputation.d.ts +0 -119
  72. package/dist_ts/cache/documents/classes.cached.ip.reputation.js +0 -323
  73. package/dist_ts/cache/documents/index.d.ts +0 -2
  74. package/dist_ts/cache/documents/index.js +0 -3
  75. package/dist_ts/cache/index.d.ts +0 -4
  76. package/dist_ts/cache/index.js +0 -7
  77. package/dist_ts/monitoring/classes.metricscache.d.ts +0 -32
  78. package/dist_ts/monitoring/classes.metricscache.js +0 -63
  79. package/dist_ts/opsserver/handlers/admin.handler.d.ts +0 -31
  80. package/dist_ts/opsserver/handlers/admin.handler.js +0 -180
  81. package/dist_ts/opsserver/handlers/config.handler.js +0 -67
  82. package/dist_ts/opsserver/handlers/logs.handler.d.ts +0 -17
  83. package/dist_ts/opsserver/handlers/logs.handler.js +0 -215
  84. package/dist_ts/security/classes.securitylogger.js +0 -235
  85. package/dist_ts/storage/classes.storagemanager.d.ts +0 -82
  86. package/dist_ts/storage/classes.storagemanager.js +0 -344
  87. package/dist_ts/storage/index.d.ts +0 -1
  88. package/dist_ts/storage/index.js +0 -3
@@ -0,0 +1,389 @@
1
+ import * as appstate from '../appstate.js';
2
+ import * as interfaces from '../../dist_ts_interfaces/index.js';
3
+ import { viewHostCss } from './shared/css.js';
4
+ import { type IStatsTile } from '@design.estate/dees-catalog';
5
+
6
+ import {
7
+ DeesElement,
8
+ css,
9
+ cssManager,
10
+ customElement,
11
+ html,
12
+ state,
13
+ type TemplateResult,
14
+ } from '@design.estate/dees-element';
15
+
16
+ @customElement('ops-view-routes')
17
+ export class OpsViewRoutes extends DeesElement {
18
+ @state() accessor routeState: appstate.IRouteManagementState = {
19
+ mergedRoutes: [],
20
+ warnings: [],
21
+ apiTokens: [],
22
+ isLoading: false,
23
+ error: null,
24
+ lastUpdated: 0,
25
+ };
26
+
27
+ constructor() {
28
+ super();
29
+ const sub = appstate.routeManagementStatePart
30
+ .select((s) => s)
31
+ .subscribe((routeState) => {
32
+ this.routeState = routeState;
33
+ });
34
+ this.rxSubscriptions.push(sub);
35
+
36
+ // Re-fetch routes when user logs in (fixes race condition where
37
+ // the view is created before authentication completes)
38
+ const loginSub = appstate.loginStatePart
39
+ .select((s) => s.isLoggedIn)
40
+ .subscribe((isLoggedIn) => {
41
+ if (isLoggedIn) {
42
+ appstate.routeManagementStatePart.dispatchAction(appstate.fetchMergedRoutesAction, null);
43
+ }
44
+ });
45
+ this.rxSubscriptions.push(loginSub);
46
+ }
47
+
48
+ public static styles = [
49
+ cssManager.defaultStyles,
50
+ viewHostCss,
51
+ css`
52
+ .routesContainer {
53
+ display: flex;
54
+ flex-direction: column;
55
+ gap: 24px;
56
+ }
57
+
58
+ .warnings-bar {
59
+ background: ${cssManager.bdTheme('rgba(255, 170, 0, 0.08)', 'rgba(255, 170, 0, 0.1)')};
60
+ border: 1px solid ${cssManager.bdTheme('rgba(255, 170, 0, 0.25)', 'rgba(255, 170, 0, 0.3)')};
61
+ border-radius: 8px;
62
+ padding: 12px 16px;
63
+ }
64
+
65
+ .warning-item {
66
+ display: flex;
67
+ align-items: center;
68
+ gap: 8px;
69
+ padding: 4px 0;
70
+ font-size: 13px;
71
+ color: ${cssManager.bdTheme('#b45309', '#fa0')};
72
+ }
73
+
74
+ .warning-icon {
75
+ flex-shrink: 0;
76
+ }
77
+
78
+ .empty-state {
79
+ text-align: center;
80
+ padding: 48px 24px;
81
+ color: ${cssManager.bdTheme('#6b7280', '#666')};
82
+ }
83
+
84
+ .empty-state p {
85
+ margin: 8px 0;
86
+ }
87
+ `,
88
+ ];
89
+
90
+ public render(): TemplateResult {
91
+ const { mergedRoutes, warnings } = this.routeState;
92
+
93
+ const hardcodedCount = mergedRoutes.filter((mr) => mr.source === 'hardcoded').length;
94
+ const programmaticCount = mergedRoutes.filter((mr) => mr.source === 'programmatic').length;
95
+ const disabledCount = mergedRoutes.filter((mr) => !mr.enabled).length;
96
+
97
+ const statsTiles: IStatsTile[] = [
98
+ {
99
+ id: 'totalRoutes',
100
+ title: 'Total Routes',
101
+ type: 'number',
102
+ value: mergedRoutes.length,
103
+ icon: 'lucide:route',
104
+ description: 'All configured routes',
105
+ color: '#3b82f6',
106
+ },
107
+ {
108
+ id: 'hardcoded',
109
+ title: 'Hardcoded',
110
+ type: 'number',
111
+ value: hardcodedCount,
112
+ icon: 'lucide:lock',
113
+ description: 'Routes from constructor config',
114
+ color: '#8b5cf6',
115
+ },
116
+ {
117
+ id: 'programmatic',
118
+ title: 'Programmatic',
119
+ type: 'number',
120
+ value: programmaticCount,
121
+ icon: 'lucide:code',
122
+ description: 'Routes added via API',
123
+ color: '#0ea5e9',
124
+ },
125
+ {
126
+ id: 'disabled',
127
+ title: 'Disabled',
128
+ type: 'number',
129
+ value: disabledCount,
130
+ icon: 'lucide:pauseCircle',
131
+ description: 'Currently disabled routes',
132
+ color: disabledCount > 0 ? '#ef4444' : '#6b7280',
133
+ },
134
+ ];
135
+
136
+ // Map merged routes to sz-route-list-view format
137
+ const szRoutes = mergedRoutes.map((mr) => {
138
+ const tags = [...(mr.route.tags || [])];
139
+ tags.push(mr.source);
140
+ if (!mr.enabled) tags.push('disabled');
141
+ if (mr.overridden) tags.push('overridden');
142
+
143
+ return {
144
+ ...mr.route,
145
+ enabled: mr.enabled,
146
+ tags,
147
+ id: mr.storedRouteId || mr.route.name || undefined,
148
+ };
149
+ });
150
+
151
+ return html`
152
+ <ops-sectionheading>Route Management</ops-sectionheading>
153
+
154
+ <div class="routesContainer">
155
+ <dees-statsgrid
156
+ .tiles=${statsTiles}
157
+ .gridActions=${[
158
+ {
159
+ name: 'Add Route',
160
+ iconName: 'lucide:plus',
161
+ action: () => this.showCreateRouteDialog(),
162
+ },
163
+ {
164
+ name: 'Refresh',
165
+ iconName: 'lucide:refreshCw',
166
+ action: () => this.refreshData(),
167
+ },
168
+ ]}
169
+ ></dees-statsgrid>
170
+
171
+ ${warnings.length > 0
172
+ ? html`
173
+ <div class="warnings-bar">
174
+ ${warnings.map(
175
+ (w) => html`
176
+ <div class="warning-item">
177
+ <span class="warning-icon">&#9888;</span>
178
+ <span>${w.message}</span>
179
+ </div>
180
+ `,
181
+ )}
182
+ </div>
183
+ `
184
+ : ''}
185
+
186
+ ${szRoutes.length > 0
187
+ ? html`
188
+ <sz-route-list-view
189
+ .routes=${szRoutes}
190
+ @route-click=${(e: CustomEvent) => this.handleRouteClick(e)}
191
+ ></sz-route-list-view>
192
+ `
193
+ : html`
194
+ <div class="empty-state">
195
+ <p>No routes configured</p>
196
+ <p>Add a programmatic route or check your constructor configuration.</p>
197
+ </div>
198
+ `}
199
+ </div>
200
+ `;
201
+ }
202
+
203
+ private async handleRouteClick(e: CustomEvent) {
204
+ const clickedRoute = e.detail;
205
+ if (!clickedRoute) return;
206
+
207
+ // Find the corresponding merged route
208
+ const merged = this.routeState.mergedRoutes.find(
209
+ (mr) => mr.route.name === clickedRoute.name,
210
+ );
211
+ if (!merged) return;
212
+
213
+ const { DeesModal } = await import('@design.estate/dees-catalog');
214
+
215
+ if (merged.source === 'hardcoded') {
216
+ const menuOptions = merged.enabled
217
+ ? [
218
+ {
219
+ name: 'Disable Route',
220
+ iconName: 'lucide:pause',
221
+ action: async (modalArg: any) => {
222
+ await appstate.routeManagementStatePart.dispatchAction(
223
+ appstate.setRouteOverrideAction,
224
+ { routeName: merged.route.name!, enabled: false },
225
+ );
226
+ await modalArg.destroy();
227
+ },
228
+ },
229
+ {
230
+ name: 'Close',
231
+ iconName: 'lucide:x',
232
+ action: async (modalArg: any) => await modalArg.destroy(),
233
+ },
234
+ ]
235
+ : [
236
+ {
237
+ name: 'Enable Route',
238
+ iconName: 'lucide:play',
239
+ action: async (modalArg: any) => {
240
+ await appstate.routeManagementStatePart.dispatchAction(
241
+ appstate.setRouteOverrideAction,
242
+ { routeName: merged.route.name!, enabled: true },
243
+ );
244
+ await modalArg.destroy();
245
+ },
246
+ },
247
+ {
248
+ name: 'Remove Override',
249
+ iconName: 'lucide:undo',
250
+ action: async (modalArg: any) => {
251
+ await appstate.routeManagementStatePart.dispatchAction(
252
+ appstate.removeRouteOverrideAction,
253
+ merged.route.name!,
254
+ );
255
+ await modalArg.destroy();
256
+ },
257
+ },
258
+ {
259
+ name: 'Close',
260
+ iconName: 'lucide:x',
261
+ action: async (modalArg: any) => await modalArg.destroy(),
262
+ },
263
+ ];
264
+
265
+ await DeesModal.createAndShow({
266
+ heading: `Route: ${merged.route.name}`,
267
+ content: html`
268
+ <div style="color: #ccc; padding: 8px 0;">
269
+ <p>Source: <strong style="color: #88f;">hardcoded</strong></p>
270
+ <p>Status: <strong>${merged.enabled ? 'Enabled' : 'Disabled (overridden)'}</strong></p>
271
+ <p style="color: #888; font-size: 13px;">Hardcoded routes cannot be edited or deleted, but they can be disabled via an override.</p>
272
+ </div>
273
+ `,
274
+ menuOptions,
275
+ });
276
+ } else {
277
+ // Programmatic route
278
+ await DeesModal.createAndShow({
279
+ heading: `Route: ${merged.route.name}`,
280
+ content: html`
281
+ <div style="color: #ccc; padding: 8px 0;">
282
+ <p>Source: <strong style="color: #0af;">programmatic</strong></p>
283
+ <p>Status: <strong>${merged.enabled ? 'Enabled' : 'Disabled'}</strong></p>
284
+ <p>ID: <code style="color: #888;">${merged.storedRouteId}</code></p>
285
+ </div>
286
+ `,
287
+ menuOptions: [
288
+ {
289
+ name: merged.enabled ? 'Disable' : 'Enable',
290
+ iconName: merged.enabled ? 'lucide:pause' : 'lucide:play',
291
+ action: async (modalArg: any) => {
292
+ await appstate.routeManagementStatePart.dispatchAction(
293
+ appstate.toggleRouteAction,
294
+ { id: merged.storedRouteId!, enabled: !merged.enabled },
295
+ );
296
+ await modalArg.destroy();
297
+ },
298
+ },
299
+ {
300
+ name: 'Delete',
301
+ iconName: 'lucide:trash-2',
302
+ action: async (modalArg: any) => {
303
+ await appstate.routeManagementStatePart.dispatchAction(
304
+ appstate.deleteRouteAction,
305
+ merged.storedRouteId!,
306
+ );
307
+ await modalArg.destroy();
308
+ },
309
+ },
310
+ {
311
+ name: 'Close',
312
+ iconName: 'lucide:x',
313
+ action: async (modalArg: any) => await modalArg.destroy(),
314
+ },
315
+ ],
316
+ });
317
+ }
318
+ }
319
+
320
+ private async showCreateRouteDialog() {
321
+ const { DeesModal } = await import('@design.estate/dees-catalog');
322
+
323
+ await DeesModal.createAndShow({
324
+ heading: 'Add Programmatic Route',
325
+ content: html`
326
+ <dees-form>
327
+ <dees-input-text .key=${'name'} .label=${'Route Name'} .required=${true}></dees-input-text>
328
+ <dees-input-text .key=${'ports'} .label=${'Ports (comma-separated)'} .required=${true}></dees-input-text>
329
+ <dees-input-text .key=${'domains'} .label=${'Domains (comma-separated, optional)'}></dees-input-text>
330
+ <dees-input-text .key=${'targetHost'} .label=${'Target Host'} .value=${'localhost'} .required=${true}></dees-input-text>
331
+ <dees-input-text .key=${'targetPort'} .label=${'Target Port'} .required=${true}></dees-input-text>
332
+ </dees-form>
333
+ `,
334
+ menuOptions: [
335
+ {
336
+ name: 'Cancel',
337
+ iconName: 'lucide:x',
338
+ action: async (modalArg: any) => await modalArg.destroy(),
339
+ },
340
+ {
341
+ name: 'Create',
342
+ iconName: 'lucide:plus',
343
+ action: async (modalArg: any) => {
344
+ const form = modalArg.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
345
+ if (!form) return;
346
+ const formData = await form.collectFormData();
347
+ if (!formData.name || !formData.ports) return;
348
+
349
+ const ports = formData.ports.split(',').map((p: string) => parseInt(p.trim(), 10)).filter((p: number) => !isNaN(p));
350
+ const domains = formData.domains
351
+ ? formData.domains.split(',').map((d: string) => d.trim()).filter(Boolean)
352
+ : undefined;
353
+
354
+ const route: any = {
355
+ name: formData.name,
356
+ match: {
357
+ ports,
358
+ ...(domains && domains.length > 0 ? { domains } : {}),
359
+ },
360
+ action: {
361
+ type: 'forward',
362
+ targets: [
363
+ {
364
+ host: formData.targetHost || 'localhost',
365
+ port: parseInt(formData.targetPort, 10),
366
+ },
367
+ ],
368
+ },
369
+ };
370
+
371
+ await appstate.routeManagementStatePart.dispatchAction(
372
+ appstate.createRouteAction,
373
+ { route },
374
+ );
375
+ await modalArg.destroy();
376
+ },
377
+ },
378
+ ],
379
+ });
380
+ }
381
+
382
+ private refreshData() {
383
+ appstate.routeManagementStatePart.dispatchAction(appstate.fetchMergedRoutesAction, null);
384
+ }
385
+
386
+ async firstUpdated() {
387
+ await appstate.routeManagementStatePart.dispatchAction(appstate.fetchMergedRoutesAction, null);
388
+ }
389
+ }
package/ts_web/router.ts CHANGED
@@ -3,7 +3,7 @@ import * as appstate from './appstate.js';
3
3
 
4
4
  const SmartRouter = plugins.domtools.plugins.smartrouter.SmartRouter;
5
5
 
6
- export const validViews = ['overview', 'network', 'emails', 'logs', 'configuration', 'security', 'certificates', 'remoteingress'] as const;
6
+ export const validViews = ['overview', 'network', 'emails', 'logs', 'routes', 'apitokens', 'configuration', 'security', 'certificates', 'remoteingress'] as const;
7
7
 
8
8
  export type TValidView = typeof validViews[number];
9
9
 
@@ -1,47 +0,0 @@
1
- import { CacheDb } from './classes.cachedb.js';
2
- /**
3
- * Configuration for the cache cleaner
4
- */
5
- export interface ICacheCleanerOptions {
6
- /** Cleanup interval in milliseconds (default: 1 hour) */
7
- intervalMs?: number;
8
- /** Enable verbose logging */
9
- verbose?: boolean;
10
- }
11
- /**
12
- * CacheCleaner - Periodically removes expired documents from the cache
13
- *
14
- * Runs on a configurable interval (default: hourly) and queries each
15
- * collection for documents where expiresAt < now(), then deletes them.
16
- */
17
- export declare class CacheCleaner {
18
- private cleanupInterval;
19
- private isRunning;
20
- private options;
21
- private cacheDb;
22
- constructor(cacheDb: CacheDb, options?: ICacheCleanerOptions);
23
- /**
24
- * Start the periodic cleanup process
25
- */
26
- start(): void;
27
- /**
28
- * Stop the periodic cleanup process
29
- */
30
- stop(): void;
31
- /**
32
- * Run a single cleanup cycle
33
- */
34
- runCleanup(): Promise<void>;
35
- /**
36
- * Clean expired documents from a specific collection using smartdata API
37
- */
38
- private cleanExpiredDocuments;
39
- /**
40
- * Check if the cleaner is running
41
- */
42
- isActive(): boolean;
43
- /**
44
- * Get the cleanup interval in milliseconds
45
- */
46
- getIntervalMs(): number;
47
- }
@@ -1,130 +0,0 @@
1
- import * as plugins from '../plugins.js';
2
- import { logger } from '../logger.js';
3
- import { CacheDb } from './classes.cachedb.js';
4
- // Import document classes for cleanup
5
- import { CachedEmail } from './documents/classes.cached.email.js';
6
- import { CachedIPReputation } from './documents/classes.cached.ip.reputation.js';
7
- /**
8
- * CacheCleaner - Periodically removes expired documents from the cache
9
- *
10
- * Runs on a configurable interval (default: hourly) and queries each
11
- * collection for documents where expiresAt < now(), then deletes them.
12
- */
13
- export class CacheCleaner {
14
- cleanupInterval = null;
15
- isRunning = false;
16
- options;
17
- cacheDb;
18
- constructor(cacheDb, options = {}) {
19
- this.cacheDb = cacheDb;
20
- this.options = {
21
- intervalMs: options.intervalMs || 60 * 60 * 1000, // 1 hour default
22
- verbose: options.verbose || false,
23
- };
24
- }
25
- /**
26
- * Start the periodic cleanup process
27
- */
28
- start() {
29
- if (this.isRunning) {
30
- logger.log('warn', 'CacheCleaner already running');
31
- return;
32
- }
33
- this.isRunning = true;
34
- // Run cleanup immediately on start
35
- this.runCleanup().catch((error) => {
36
- logger.log('error', `Initial cache cleanup failed: ${error.message}`);
37
- });
38
- // Schedule periodic cleanup
39
- this.cleanupInterval = setInterval(() => {
40
- this.runCleanup().catch((error) => {
41
- logger.log('error', `Cache cleanup failed: ${error.message}`);
42
- });
43
- }, this.options.intervalMs);
44
- logger.log('info', `CacheCleaner started with interval: ${this.options.intervalMs / 1000 / 60} minutes`);
45
- }
46
- /**
47
- * Stop the periodic cleanup process
48
- */
49
- stop() {
50
- if (!this.isRunning) {
51
- return;
52
- }
53
- if (this.cleanupInterval) {
54
- clearInterval(this.cleanupInterval);
55
- this.cleanupInterval = null;
56
- }
57
- this.isRunning = false;
58
- logger.log('info', 'CacheCleaner stopped');
59
- }
60
- /**
61
- * Run a single cleanup cycle
62
- */
63
- async runCleanup() {
64
- if (!this.cacheDb.isReady()) {
65
- logger.log('warn', 'CacheDb not ready, skipping cleanup');
66
- return;
67
- }
68
- const now = new Date();
69
- const results = [];
70
- try {
71
- const emailsDeleted = await this.cleanExpiredDocuments(CachedEmail, now);
72
- results.push({ collection: 'CachedEmail', deleted: emailsDeleted });
73
- const ipReputationDeleted = await this.cleanExpiredDocuments(CachedIPReputation, now);
74
- results.push({ collection: 'CachedIPReputation', deleted: ipReputationDeleted });
75
- // Log results
76
- const totalDeleted = results.reduce((sum, r) => sum + r.deleted, 0);
77
- if (totalDeleted > 0 || this.options.verbose) {
78
- const summary = results
79
- .filter((r) => r.deleted > 0)
80
- .map((r) => `${r.collection}: ${r.deleted}`)
81
- .join(', ');
82
- logger.log('info', `Cache cleanup completed. Deleted ${totalDeleted} expired documents. ${summary || 'No deletions.'}`);
83
- }
84
- }
85
- catch (error) {
86
- logger.log('error', `Cache cleanup error: ${error.message}`);
87
- throw error;
88
- }
89
- }
90
- /**
91
- * Clean expired documents from a specific collection using smartdata API
92
- */
93
- async cleanExpiredDocuments(documentClass, now) {
94
- try {
95
- // Find all expired documents
96
- const expiredDocs = await documentClass.getInstances({
97
- expiresAt: { $lt: now },
98
- });
99
- // Delete each expired document
100
- let deletedCount = 0;
101
- for (const doc of expiredDocs) {
102
- try {
103
- await doc.delete();
104
- deletedCount++;
105
- }
106
- catch (deleteError) {
107
- logger.log('warn', `Failed to delete expired document: ${deleteError.message}`);
108
- }
109
- }
110
- return deletedCount;
111
- }
112
- catch (error) {
113
- logger.log('error', `Error cleaning collection: ${error.message}`);
114
- return 0;
115
- }
116
- }
117
- /**
118
- * Check if the cleaner is running
119
- */
120
- isActive() {
121
- return this.isRunning;
122
- }
123
- /**
124
- * Get the cleanup interval in milliseconds
125
- */
126
- getIntervalMs() {
127
- return this.options.intervalMs;
128
- }
129
- }
130
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5jYWNoZS5jbGVhbmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvY2FjaGUvY2xhc3Nlcy5jYWNoZS5jbGVhbmVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sZUFBZSxDQUFDO0FBQ3pDLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFDdEMsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBRS9DLHNDQUFzQztBQUN0QyxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0scUNBQXFDLENBQUM7QUFDbEUsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sNkNBQTZDLENBQUM7QUFZakY7Ozs7O0dBS0c7QUFDSCxNQUFNLE9BQU8sWUFBWTtJQUNmLGVBQWUsR0FBMEMsSUFBSSxDQUFDO0lBQzlELFNBQVMsR0FBWSxLQUFLLENBQUM7SUFDM0IsT0FBTyxDQUFpQztJQUN4QyxPQUFPLENBQVU7SUFFekIsWUFBWSxPQUFnQixFQUFFLFVBQWdDLEVBQUU7UUFDOUQsSUFBSSxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUM7UUFDdkIsSUFBSSxDQUFDLE9BQU8sR0FBRztZQUNiLFVBQVUsRUFBRSxPQUFPLENBQUMsVUFBVSxJQUFJLEVBQUUsR0FBRyxFQUFFLEdBQUcsSUFBSSxFQUFFLGlCQUFpQjtZQUNuRSxPQUFPLEVBQUUsT0FBTyxDQUFDLE9BQU8sSUFBSSxLQUFLO1NBQ2xDLENBQUM7SUFDSixDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLO1FBQ1YsSUFBSSxJQUFJLENBQUMsU0FBUyxFQUFFLENBQUM7WUFDbkIsTUFBTSxDQUFDLEdBQUcsQ0FBQyxNQUFNLEVBQUUsOEJBQThCLENBQUMsQ0FBQztZQUNuRCxPQUFPO1FBQ1QsQ0FBQztRQUVELElBQUksQ0FBQyxTQUFTLEdBQUcsSUFBSSxDQUFDO1FBRXRCLG1DQUFtQztRQUNuQyxJQUFJ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
@@ -1,76 +0,0 @@
1
- import * as plugins from '../plugins.js';
2
- /**
3
- * Base class for all cached documents with TTL support
4
- *
5
- * Extends smartdata's SmartDataDbDoc to add:
6
- * - Automatic timestamps (createdAt, lastAccessedAt)
7
- * - TTL/expiration support (expiresAt)
8
- * - Helper methods for TTL management
9
- *
10
- * NOTE: Subclasses MUST add @svDb() decorators to createdAt, expiresAt, and lastAccessedAt
11
- * since decorators on abstract classes don't propagate correctly.
12
- */
13
- export declare abstract class CachedDocument<T extends CachedDocument<T>> extends plugins.smartdata.SmartDataDbDoc<T, T> {
14
- /**
15
- * Timestamp when the document was created
16
- * NOTE: Subclasses must add @svDb() decorator
17
- */
18
- createdAt: Date;
19
- /**
20
- * Timestamp when the document expires and should be cleaned up
21
- * NOTE: Subclasses must add @svDb() decorator
22
- */
23
- expiresAt: Date;
24
- /**
25
- * Timestamp of last access (for LRU-style eviction if needed)
26
- * NOTE: Subclasses must add @svDb() decorator
27
- */
28
- lastAccessedAt: Date;
29
- /**
30
- * Set the TTL (time to live) for this document
31
- * @param ttlMs Time to live in milliseconds
32
- */
33
- setTTL(ttlMs: number): void;
34
- /**
35
- * Set TTL using days
36
- * @param days Number of days until expiration
37
- */
38
- setTTLDays(days: number): void;
39
- /**
40
- * Set TTL using hours
41
- * @param hours Number of hours until expiration
42
- */
43
- setTTLHours(hours: number): void;
44
- /**
45
- * Check if this document has expired
46
- */
47
- isExpired(): boolean;
48
- /**
49
- * Update the lastAccessedAt timestamp
50
- */
51
- touch(): void;
52
- /**
53
- * Get remaining TTL in milliseconds
54
- * Returns 0 if expired, -1 if no expiration set
55
- */
56
- getRemainingTTL(): number;
57
- /**
58
- * Extend the TTL by the specified milliseconds from now
59
- * @param ttlMs Additional time to live in milliseconds
60
- */
61
- extendTTL(ttlMs: number): void;
62
- /**
63
- * Set the document to never expire (100 years in the future)
64
- */
65
- setNeverExpires(): void;
66
- }
67
- /**
68
- * TTL constants in milliseconds
69
- */
70
- export declare const TTL: {
71
- readonly HOURS_1: number;
72
- readonly HOURS_24: number;
73
- readonly DAYS_7: number;
74
- readonly DAYS_30: number;
75
- readonly DAYS_90: number;
76
- };