@serve.zone/dcrouter 11.0.31 → 11.0.33

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 (39) hide show
  1. package/dist_serve/bundle.js +1 -1
  2. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  3. package/dist_ts/00_commitinfo_data.js +9 -0
  4. package/dist_ts/cache/classes.cache.cleaner.d.ts +47 -0
  5. package/dist_ts/cache/classes.cache.cleaner.js +130 -0
  6. package/dist_ts/cache/classes.cached.document.d.ts +76 -0
  7. package/dist_ts/cache/classes.cached.document.js +100 -0
  8. package/dist_ts/cache/classes.cachedb.d.ts +60 -0
  9. package/dist_ts/cache/classes.cachedb.js +126 -0
  10. package/dist_ts/cache/documents/classes.cached.email.d.ts +125 -0
  11. package/dist_ts/cache/documents/classes.cached.email.js +337 -0
  12. package/dist_ts/cache/documents/classes.cached.ip.reputation.d.ts +119 -0
  13. package/dist_ts/cache/documents/classes.cached.ip.reputation.js +323 -0
  14. package/dist_ts/cache/documents/index.d.ts +2 -0
  15. package/dist_ts/cache/documents/index.js +3 -0
  16. package/dist_ts/cache/index.d.ts +4 -0
  17. package/dist_ts/cache/index.js +7 -0
  18. package/dist_ts/classes.cert-provision-scheduler.d.ts +53 -0
  19. package/dist_ts/classes.cert-provision-scheduler.js +110 -0
  20. package/dist_ts/classes.storage-cert-manager.d.ts +18 -0
  21. package/dist_ts/classes.storage-cert-manager.js +43 -0
  22. package/dist_ts/logger.d.ts +21 -0
  23. package/dist_ts/logger.js +81 -0
  24. package/dist_ts/opsserver/handlers/admin.handler.d.ts +31 -0
  25. package/dist_ts/opsserver/handlers/admin.handler.js +180 -0
  26. package/dist_ts/opsserver/handlers/config.handler.d.ts +7 -0
  27. package/dist_ts/opsserver/handlers/config.handler.js +192 -0
  28. package/dist_ts/paths.d.ts +26 -0
  29. package/dist_ts/paths.js +45 -0
  30. package/dist_ts/plugins.d.ts +79 -0
  31. package/dist_ts/plugins.js +113 -0
  32. package/dist_ts/storage/classes.storagemanager.d.ts +83 -0
  33. package/dist_ts/storage/classes.storagemanager.js +350 -0
  34. package/dist_ts/storage/index.d.ts +1 -0
  35. package/dist_ts/storage/index.js +3 -0
  36. package/dist_ts_web/00_commitinfo_data.js +1 -1
  37. package/package.json +2 -2
  38. package/ts/00_commitinfo_data.ts +1 -1
  39. package/ts_web/00_commitinfo_data.ts +1 -1
@@ -39328,4 +39328,4 @@ ibantools/jsnext/ibantools.js:
39328
39328
  * @preferred
39329
39329
  *)
39330
39330
  */
39331
- //# sourceMappingURL=bundle-1772722185785.js.map
39331
+ //# sourceMappingURL=bundle-1772722607518.js.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * autocreated commitinfo by @push.rocks/commitinfo
3
+ */
4
+ export declare const commitinfo: {
5
+ name: string;
6
+ version: string;
7
+ description: string;
8
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * autocreated commitinfo by @push.rocks/commitinfo
3
+ */
4
+ export const commitinfo = {
5
+ name: '@serve.zone/dcrouter',
6
+ version: '11.0.33',
7
+ description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
+ };
9
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSxzQkFBc0I7SUFDNUIsT0FBTyxFQUFFLFNBQVM7SUFDbEIsV0FBVyxFQUFFLDBFQUEwRTtDQUN4RixDQUFBIn0=
@@ -0,0 +1,47 @@
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
+ }
@@ -0,0 +1,130 @@
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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
@@ -0,0 +1,76 @@
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
+ };
@@ -0,0 +1,100 @@
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 class CachedDocument extends plugins.smartdata.SmartDataDbDoc {
14
+ /**
15
+ * Timestamp when the document was created
16
+ * NOTE: Subclasses must add @svDb() decorator
17
+ */
18
+ createdAt = new Date();
19
+ /**
20
+ * Timestamp when the document expires and should be cleaned up
21
+ * NOTE: Subclasses must add @svDb() decorator
22
+ */
23
+ expiresAt;
24
+ /**
25
+ * Timestamp of last access (for LRU-style eviction if needed)
26
+ * NOTE: Subclasses must add @svDb() decorator
27
+ */
28
+ lastAccessedAt = new Date();
29
+ /**
30
+ * Set the TTL (time to live) for this document
31
+ * @param ttlMs Time to live in milliseconds
32
+ */
33
+ setTTL(ttlMs) {
34
+ this.expiresAt = new Date(Date.now() + ttlMs);
35
+ }
36
+ /**
37
+ * Set TTL using days
38
+ * @param days Number of days until expiration
39
+ */
40
+ setTTLDays(days) {
41
+ this.setTTL(days * 24 * 60 * 60 * 1000);
42
+ }
43
+ /**
44
+ * Set TTL using hours
45
+ * @param hours Number of hours until expiration
46
+ */
47
+ setTTLHours(hours) {
48
+ this.setTTL(hours * 60 * 60 * 1000);
49
+ }
50
+ /**
51
+ * Check if this document has expired
52
+ */
53
+ isExpired() {
54
+ if (!this.expiresAt) {
55
+ return false; // No expiration set
56
+ }
57
+ return new Date() > this.expiresAt;
58
+ }
59
+ /**
60
+ * Update the lastAccessedAt timestamp
61
+ */
62
+ touch() {
63
+ this.lastAccessedAt = new Date();
64
+ }
65
+ /**
66
+ * Get remaining TTL in milliseconds
67
+ * Returns 0 if expired, -1 if no expiration set
68
+ */
69
+ getRemainingTTL() {
70
+ if (!this.expiresAt) {
71
+ return -1;
72
+ }
73
+ const remaining = this.expiresAt.getTime() - Date.now();
74
+ return remaining > 0 ? remaining : 0;
75
+ }
76
+ /**
77
+ * Extend the TTL by the specified milliseconds from now
78
+ * @param ttlMs Additional time to live in milliseconds
79
+ */
80
+ extendTTL(ttlMs) {
81
+ this.expiresAt = new Date(Date.now() + ttlMs);
82
+ }
83
+ /**
84
+ * Set the document to never expire (100 years in the future)
85
+ */
86
+ setNeverExpires() {
87
+ this.expiresAt = new Date(Date.now() + 100 * 365 * 24 * 60 * 60 * 1000);
88
+ }
89
+ }
90
+ /**
91
+ * TTL constants in milliseconds
92
+ */
93
+ export const TTL = {
94
+ HOURS_1: 1 * 60 * 60 * 1000,
95
+ HOURS_24: 24 * 60 * 60 * 1000,
96
+ DAYS_7: 7 * 24 * 60 * 60 * 1000,
97
+ DAYS_30: 30 * 24 * 60 * 60 * 1000,
98
+ DAYS_90: 90 * 24 * 60 * 60 * 1000,
99
+ };
100
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5jYWNoZWQuZG9jdW1lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9jYWNoZS9jbGFzc2VzLmNhY2hlZC5kb2N1bWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGVBQWUsQ0FBQztBQUV6Qzs7Ozs7Ozs7OztHQVVHO0FBQ0gsTUFBTSxPQUFnQixjQUE0QyxTQUFRLE9BQU8sQ0FBQyxTQUFTLENBQUMsY0FBb0I7SUFDOUc7OztPQUdHO0lBQ0ksU0FBUyxHQUFTLElBQUksSUFBSSxFQUFFLENBQUM7SUFFcEM7OztPQUdHO0lBQ0ksU0FBUyxDQUFPO0lBRXZCOzs7T0FHRztJQUNJLGNBQWMsR0FBUyxJQUFJLElBQUksRUFBRSxDQUFDO0lBRXpDOzs7T0FHRztJQUNJLE1BQU0sQ0FBQyxLQUFhO1FBQ3pCLElBQUksQ0FBQyxTQUFTLEdBQUcsSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLEtBQUssQ0FBQyxDQUFDO0lBQ2hELENBQUM7SUFFRDs7O09BR0c7SUFDSSxVQUFVLENBQUMsSUFBWTtRQUM1QixJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUMsQ0FBQztJQUMxQyxDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksV0FBVyxDQUFDLEtBQWE7UUFDOUIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUMsQ0FBQztJQUN0QyxDQUFDO0lBRUQ7O09BRUc7SUFDSSxTQUFTO1FBQ2QsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQztZQUNwQixPQUFPLEtBQUssQ0FBQyxDQUFDLG9CQUFvQjtRQUNwQyxDQUFDO1FBQ0QsT0FBTyxJQUFJLElBQUksRUFBRSxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUM7SUFDckMsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSztRQUNWLElBQUksQ0FBQyxjQUFjLEdBQUcsSUFBSSxJQUFJLEVBQUUsQ0FBQztJQUNuQyxDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksZUFBZTtRQUNwQixJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSxDQUFDO1lBQ3BCLE9BQU8sQ0FBQyxDQUFDLENBQUM7UUFDWixDQUFDO1FBQ0QsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxPQUFPLEVBQUUsR0FBRyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7UUFDeEQsT0FBTyxTQUFTLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUN2QyxDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksU0FBUyxDQUFDLEtBQWE7UUFDNUIsSUFBSSxDQUFDLFNBQVMsR0FBRyxJQUFJLElBQUksQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFLEdBQUcsS0FBSyxDQUFDLENBQUM7SUFDaEQsQ0FBQztJQUVEOztPQUVHO0lBQ0ksZUFBZTtRQUNwQixJQUFJLENBQUMsU0FBUyxHQUFHLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxHQUFHLEdBQUcsR0FBRyxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxDQUFDO0lBQzFFLENBQUM7Q0FDRjtBQUVEOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sR0FBRyxHQUFHO0lBQ2pCLE9BQU8sRUFBRSxDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxJQUFJO0lBQzNCLFFBQVEsRUFBRSxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxJQUFJO0lBQzdCLE1BQU0sRUFBRSxDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsSUFBSTtJQUMvQixPQUFPLEVBQUUsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLElBQUk7SUFDakMsT0FBTyxFQUFFLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxJQUFJO0NBQ3pCLENBQUMifQ==
@@ -0,0 +1,60 @@
1
+ import * as plugins from '../plugins.js';
2
+ /**
3
+ * Configuration options for CacheDb
4
+ */
5
+ export interface ICacheDbOptions {
6
+ /** Base storage path for TsmDB data (default: ~/.serve.zone/dcrouter/tsmdb) */
7
+ storagePath?: string;
8
+ /** Database name (default: dcrouter) */
9
+ dbName?: string;
10
+ /** Enable debug logging */
11
+ debug?: boolean;
12
+ }
13
+ /**
14
+ * CacheDb - Wrapper around LocalTsmDb and smartdata
15
+ *
16
+ * Provides persistent caching using smartdata as the ORM layer
17
+ * and LocalTsmDb as the embedded database engine.
18
+ */
19
+ export declare class CacheDb {
20
+ private static instance;
21
+ private localTsmDb;
22
+ private smartdataDb;
23
+ private options;
24
+ private isStarted;
25
+ constructor(options?: ICacheDbOptions);
26
+ /**
27
+ * Get or create the singleton instance
28
+ */
29
+ static getInstance(options?: ICacheDbOptions): CacheDb;
30
+ /**
31
+ * Reset the singleton instance (useful for testing)
32
+ */
33
+ static resetInstance(): void;
34
+ /**
35
+ * Start the cache database
36
+ * - Initializes LocalTsmDb with file persistence
37
+ * - Connects smartdata to the LocalTsmDb via Unix socket
38
+ */
39
+ start(): Promise<void>;
40
+ /**
41
+ * Stop the cache database
42
+ */
43
+ stop(): Promise<void>;
44
+ /**
45
+ * Get the smartdata database instance
46
+ */
47
+ getDb(): plugins.smartdata.SmartdataDb;
48
+ /**
49
+ * Check if the database is ready
50
+ */
51
+ isReady(): boolean;
52
+ /**
53
+ * Get the storage path
54
+ */
55
+ getStoragePath(): string;
56
+ /**
57
+ * Get the database name
58
+ */
59
+ getDbName(): string;
60
+ }
@@ -0,0 +1,126 @@
1
+ import * as plugins from '../plugins.js';
2
+ import { logger } from '../logger.js';
3
+ import { defaultTsmDbPath } from '../paths.js';
4
+ /**
5
+ * CacheDb - Wrapper around LocalTsmDb and smartdata
6
+ *
7
+ * Provides persistent caching using smartdata as the ORM layer
8
+ * and LocalTsmDb as the embedded database engine.
9
+ */
10
+ export class CacheDb {
11
+ static instance = null;
12
+ localTsmDb;
13
+ smartdataDb;
14
+ options;
15
+ isStarted = false;
16
+ constructor(options = {}) {
17
+ this.options = {
18
+ storagePath: options.storagePath || defaultTsmDbPath,
19
+ dbName: options.dbName || 'dcrouter',
20
+ debug: options.debug || false,
21
+ };
22
+ }
23
+ /**
24
+ * Get or create the singleton instance
25
+ */
26
+ static getInstance(options) {
27
+ if (!CacheDb.instance) {
28
+ CacheDb.instance = new CacheDb(options);
29
+ }
30
+ return CacheDb.instance;
31
+ }
32
+ /**
33
+ * Reset the singleton instance (useful for testing)
34
+ */
35
+ static resetInstance() {
36
+ CacheDb.instance = null;
37
+ }
38
+ /**
39
+ * Start the cache database
40
+ * - Initializes LocalTsmDb with file persistence
41
+ * - Connects smartdata to the LocalTsmDb via Unix socket
42
+ */
43
+ async start() {
44
+ if (this.isStarted) {
45
+ logger.log('warn', 'CacheDb already started');
46
+ return;
47
+ }
48
+ try {
49
+ // Ensure storage directory exists
50
+ await plugins.fsUtils.ensureDir(this.options.storagePath);
51
+ // Create LocalTsmDb instance
52
+ this.localTsmDb = new plugins.smartmongo.LocalTsmDb({
53
+ folderPath: this.options.storagePath,
54
+ });
55
+ // Start LocalTsmDb and get connection info
56
+ const connectionInfo = await this.localTsmDb.start();
57
+ if (this.options.debug) {
58
+ logger.log('debug', `LocalTsmDb started with URI: ${connectionInfo.connectionUri}`);
59
+ }
60
+ // Initialize smartdata with the connection URI
61
+ this.smartdataDb = new plugins.smartdata.SmartdataDb({
62
+ mongoDbUrl: connectionInfo.connectionUri,
63
+ mongoDbName: this.options.dbName,
64
+ });
65
+ await this.smartdataDb.init();
66
+ this.isStarted = true;
67
+ logger.log('info', `CacheDb started at ${this.options.storagePath}`);
68
+ }
69
+ catch (error) {
70
+ logger.log('error', `Failed to start CacheDb: ${error.message}`);
71
+ throw error;
72
+ }
73
+ }
74
+ /**
75
+ * Stop the cache database
76
+ */
77
+ async stop() {
78
+ if (!this.isStarted) {
79
+ return;
80
+ }
81
+ try {
82
+ // Close smartdata connection
83
+ if (this.smartdataDb) {
84
+ await this.smartdataDb.close();
85
+ }
86
+ // Stop LocalTsmDb
87
+ if (this.localTsmDb) {
88
+ await this.localTsmDb.stop();
89
+ }
90
+ this.isStarted = false;
91
+ logger.log('info', 'CacheDb stopped');
92
+ }
93
+ catch (error) {
94
+ logger.log('error', `Error stopping CacheDb: ${error.message}`);
95
+ throw error;
96
+ }
97
+ }
98
+ /**
99
+ * Get the smartdata database instance
100
+ */
101
+ getDb() {
102
+ if (!this.isStarted) {
103
+ throw new Error('CacheDb not started. Call start() first.');
104
+ }
105
+ return this.smartdataDb;
106
+ }
107
+ /**
108
+ * Check if the database is ready
109
+ */
110
+ isReady() {
111
+ return this.isStarted;
112
+ }
113
+ /**
114
+ * Get the storage path
115
+ */
116
+ getStoragePath() {
117
+ return this.options.storagePath;
118
+ }
119
+ /**
120
+ * Get the database name
121
+ */
122
+ getDbName() {
123
+ return this.options.dbName;
124
+ }
125
+ }
126
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5jYWNoZWRiLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvY2FjaGUvY2xhc3Nlcy5jYWNoZWRiLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sZUFBZSxDQUFDO0FBQ3pDLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFDdEMsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sYUFBYSxDQUFDO0FBYy9DOzs7OztHQUtHO0FBQ0gsTUFBTSxPQUFPLE9BQU87SUFDVixNQUFNLENBQUMsUUFBUSxHQUFtQixJQUFJLENBQUM7SUFFdkMsVUFBVSxDQUFnQztJQUMxQyxXQUFXLENBQWdDO0lBQzNDLE9BQU8sQ0FBNEI7SUFDbkMsU0FBUyxHQUFZLEtBQUssQ0FBQztJQUVuQyxZQUFZLFVBQTJCLEVBQUU7UUFDdkMsSUFBSSxDQUFDLE9BQU8sR0FBRztZQUNiLFdBQVcsRUFBRSxPQUFPLENBQUMsV0FBVyxJQUFJLGdCQUFnQjtZQUNwRCxNQUFNLEVBQUUsT0FBTyxDQUFDLE1BQU0sSUFBSSxVQUFVO1lBQ3BDLEtBQUssRUFBRSxPQUFPLENBQUMsS0FBSyxJQUFJLEtBQUs7U0FDOUIsQ0FBQztJQUNKLENBQUM7SUFFRDs7T0FFRztJQUNJLE1BQU0sQ0FBQyxXQUFXLENBQUMsT0FBeUI7UUFDakQsSUFBSSxDQUFDLE9BQU8sQ0FBQyxRQUFRLEVBQUUsQ0FBQztZQUN0QixPQUFPLENBQUMsUUFBUSxHQUFHLElBQUksT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQzFDLENBQUM7UUFDRCxPQUFPLE9BQU8sQ0FBQyxRQUFRLENBQUM7SUFDMUIsQ0FBQztJQUVEOztPQUVHO0lBQ0ksTUFBTSxDQUFDLGFBQWE7UUFDekIsT0FBTyxDQUFDLFFBQVEsR0FBRyxJQUFJLENBQUM7SUFDMUIsQ0FBQztJQUVEOzs7O09BSUc7SUFDSSxLQUFLLENBQUMsS0FBSztRQUNoQixJ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