BREAKING CHANGE(db): replace StorageManager and CacheDb with a unified smartdata-backed database layer

This commit is contained in:
2026-03-31 15:31:16 +00:00
parent 193a4bb180
commit bb6c26484d
49 changed files with 1475 additions and 1687 deletions

View File

@@ -1,5 +1,5 @@
import { logger } from './logger.js';
import type { StorageManager } from './storage/index.js';
import { CertBackoffDoc } from './db/index.js';
interface IBackoffEntry {
failures: number;
@@ -10,54 +10,68 @@ interface IBackoffEntry {
/**
* Manages certificate provisioning scheduling with:
* - Per-domain exponential backoff persisted in StorageManager
* - Per-domain exponential backoff persisted via CertBackoffDoc
*
* Note: Serial stagger queue was removed — smartacme v9 handles
* concurrency, per-domain dedup, and rate limiting internally.
*/
export class CertProvisionScheduler {
private storageManager: StorageManager;
private maxBackoffHours: number;
// In-memory backoff cache (mirrors storage for fast lookups)
private backoffCache = new Map<string, IBackoffEntry>();
constructor(
storageManager: StorageManager,
options?: { maxBackoffHours?: number }
) {
this.storageManager = storageManager;
this.maxBackoffHours = options?.maxBackoffHours ?? 24;
}
/**
* Storage key for a domain's backoff entry
* Sanitized domain key for storage lookups
*/
private backoffKey(domain: string): string {
const clean = domain.replace(/\*/g, '_wildcard_').replace(/[^a-zA-Z0-9._-]/g, '_');
return `/cert-backoff/${clean}`;
private sanitizeDomain(domain: string): string {
return domain.replace(/\*/g, '_wildcard_').replace(/[^a-zA-Z0-9._-]/g, '_');
}
/**
* Load backoff entry from storage (with in-memory cache)
* Load backoff entry from database (with in-memory cache)
*/
private async loadBackoff(domain: string): Promise<IBackoffEntry | null> {
const cached = this.backoffCache.get(domain);
if (cached) return cached;
const entry = await this.storageManager.getJSON<IBackoffEntry>(this.backoffKey(domain));
if (entry) {
const sanitized = this.sanitizeDomain(domain);
const doc = await CertBackoffDoc.findByDomain(sanitized);
if (doc) {
const entry: IBackoffEntry = {
failures: doc.failures,
lastFailure: doc.lastFailure,
retryAfter: doc.retryAfter,
lastError: doc.lastError,
};
this.backoffCache.set(domain, entry);
return entry;
}
return entry;
return null;
}
/**
* Save backoff entry to both cache and storage
* Save backoff entry to both cache and database
*/
private async saveBackoff(domain: string, entry: IBackoffEntry): Promise<void> {
this.backoffCache.set(domain, entry);
await this.storageManager.setJSON(this.backoffKey(domain), entry);
const sanitized = this.sanitizeDomain(domain);
let doc = await CertBackoffDoc.findByDomain(sanitized);
if (!doc) {
doc = new CertBackoffDoc();
doc.domain = sanitized;
}
doc.failures = entry.failures;
doc.lastFailure = entry.lastFailure;
doc.retryAfter = entry.retryAfter;
doc.lastError = entry.lastError || '';
await doc.save();
}
/**
@@ -107,9 +121,13 @@ export class CertProvisionScheduler {
async clearBackoff(domain: string): Promise<void> {
this.backoffCache.delete(domain);
try {
await this.storageManager.delete(this.backoffKey(domain));
const sanitized = this.sanitizeDomain(domain);
const doc = await CertBackoffDoc.findByDomain(sanitized);
if (doc) {
await doc.delete();
}
} catch {
// Ignore delete errors (key may not exist)
// Ignore delete errors (doc may not exist)
}
}