From 8cd713447e26d7c0b91f8b8f8b2774ad80020113 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Sun, 15 Feb 2026 23:31:42 +0000 Subject: [PATCH] fix(docs): document built-in concurrency control, rate limiting, and request deduplication in README --- changelog.md | 9 ++++ readme.md | 93 +++++++++++++++++++++++++++++++++++----- ts/00_commitinfo_data.ts | 2 +- 3 files changed, 92 insertions(+), 12 deletions(-) diff --git a/changelog.md b/changelog.md index 89a9deb..3d58102 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,14 @@ # Changelog +## 2026-02-15 - 9.1.2 - fix(docs) +document built-in concurrency control, rate limiting, and request deduplication in README + +- Added a new 'Concurrency Control & Rate Limiting' section to the README describing per-domain mutex, global concurrency cap, and sliding-window account rate limiting (defaults: 1 per domain, 5 global, 250 per 3 hours). +- Documented new SmartAcme options in the interface: maxConcurrentIssuances, maxOrdersPerWindow, and orderWindowMs. +- Added example code showing configuration of the limits and an example of request deduplication behavior (multiple subdomain requests resolving to a single ACME order). +- Added an example subscription to certIssuanceEvents and updated the components table with TaskManager entry. +- Change is documentation-only (README) — no code changes; safe patch release. + ## 2026-02-15 - 9.1.1 - fix(deps) bump @push.rocks/smarttime to ^4.2.3 and @push.rocks/taskbuffer to ^6.1.2 diff --git a/readme.md b/readme.md index 0c306e4..23bfdb2 100644 --- a/readme.md +++ b/readme.md @@ -16,7 +16,7 @@ Ensure your project uses TypeScript and ECMAScript Modules (ESM). ## Usage -`@push.rocks/smartacme` automates the full ACME certificate lifecycle — obtaining, renewing, and storing SSL/TLS certificates from Let's Encrypt. It features a built-in RFC 8555-compliant ACME protocol implementation, pluggable challenge handlers (DNS-01, HTTP-01), pluggable certificate storage backends (MongoDB, in-memory, or your own), and structured error handling with smart retry logic. +`@push.rocks/smartacme` automates the full ACME certificate lifecycle — obtaining, renewing, and storing SSL/TLS certificates from Let's Encrypt. It features a built-in RFC 8555-compliant ACME protocol implementation, pluggable challenge handlers (DNS-01, HTTP-01), pluggable certificate storage backends (MongoDB, in-memory, or your own), structured error handling with smart retry logic, and built-in concurrency control with rate limiting to keep you safely within Let's Encrypt limits. ### 🚀 Quick Start @@ -58,18 +58,22 @@ await smartAcme.stop(); ```typescript interface ISmartAcmeOptions { - accountEmail: string; // ACME account email - accountPrivateKey?: string; // Optional account key (auto-generated if omitted) - certManager: ICertManager; // Certificate storage backend + accountEmail: string; // ACME account email + accountPrivateKey?: string; // Optional account key (auto-generated if omitted) + certManager: ICertManager; // Certificate storage backend environment: 'production' | 'integration'; // Let's Encrypt environment - challengeHandlers: IChallengeHandler[]; // At least one handler required - challengePriority?: string[]; // e.g. ['dns-01', 'http-01'] - retryOptions?: { // Optional retry/backoff config - retries?: number; // Default: 10 - factor?: number; // Default: 4 - minTimeoutMs?: number; // Default: 1000 - maxTimeoutMs?: number; // Default: 60000 + challengeHandlers: IChallengeHandler[]; // At least one handler required + challengePriority?: string[]; // e.g. ['dns-01', 'http-01'] + retryOptions?: { // Optional retry/backoff config + retries?: number; // Default: 10 + factor?: number; // Default: 4 + minTimeoutMs?: number; // Default: 1000 + maxTimeoutMs?: number; // Default: 60000 }; + // Concurrency & rate limiting + maxConcurrentIssuances?: number; // Global cap on parallel ACME ops (default: 5) + maxOrdersPerWindow?: number; // Max orders in sliding window (default: 250) + orderWindowMs?: number; // Sliding window duration in ms (default: 3 hours) } ``` @@ -112,6 +116,72 @@ cert.isStillValid(); // true if not expired cert.shouldBeRenewed(); // true if expires within 10 days ``` +## 🔀 Concurrency Control & Rate Limiting + +When many callers request certificates concurrently (e.g., hundreds of subdomains under the same TLD), SmartAcme automatically handles deduplication, concurrency, and rate limiting using a built-in task manager powered by `@push.rocks/taskbuffer`. + +### How It Works + +Three constraint layers protect your ACME account: + +| Layer | What It Does | Default | +|-------|-------------|---------| +| **Per-domain mutex** | Only one issuance runs per base domain at a time. Concurrent requests for the same domain automatically wait and receive the same certificate result. | 1 concurrent per domain | +| **Global concurrency cap** | Limits total parallel ACME operations across all domains. | 5 concurrent | +| **Account rate limit** | Sliding-window rate limiter that keeps you under Let's Encrypt's 300 orders/3h account limit. | 250 per 3 hours | + +### 🛡️ Automatic Request Deduplication + +If 100 requests come in for subdomains of `example.com` simultaneously, only **one** ACME issuance runs. All other callers automatically wait and receive the same certificate — no duplicate orders, no wasted rate limit budget. + +```typescript +// These all resolve to the same certificate with a single ACME order: +const results = await Promise.all([ + smartAcme.getCertificateForDomain('app.example.com'), + smartAcme.getCertificateForDomain('api.example.com'), + smartAcme.getCertificateForDomain('cdn.example.com'), +]); +``` + +### ⚡ Configuring Limits + +```typescript +const smartAcme = new SmartAcme({ + accountEmail: 'admin@example.com', + certManager, + environment: 'production', + challengeHandlers: [dnsHandler], + maxConcurrentIssuances: 10, // Allow up to 10 parallel ACME issuances + maxOrdersPerWindow: 200, // Cap at 200 orders per window + orderWindowMs: 2 * 60 * 60_000, // 2-hour sliding window +}); +``` + +### 📊 Observing Issuance Progress + +Subscribe to the `certIssuanceEvents` stream to observe certificate issuance progress in real-time: + +```typescript +smartAcme.certIssuanceEvents.subscribe((event) => { + switch (event.type) { + case 'started': + console.log(`🔄 Issuance started: ${event.task.name}`); + break; + case 'step': + console.log(`📍 Step: ${event.stepName} (${event.task.currentProgress}%)`); + break; + case 'completed': + console.log(`✅ Issuance completed: ${event.task.name}`); + break; + case 'failed': + console.log(`❌ Issuance failed: ${event.error}`); + break; + } +}); +``` + +Each issuance goes through four steps: **prepare** (10%) → **authorize** (40%) → **finalize** (30%) → **store** (20%). + ## Certificate Managers SmartAcme uses the `ICertManager` interface for pluggable certificate storage. @@ -314,6 +384,7 @@ Under the hood, SmartAcme uses a fully custom RFC 8555-compliant ACME protocol i | `AcmeError` | Structured error class with type URN, subproblems, Retry-After, retryability | | `AcmeOrderManager` | Order lifecycle — create, poll, finalize, download certificate | | `AcmeChallengeManager` | Key authorization computation and challenge completion | +| `TaskManager` | Constraint-based concurrency control, rate limiting, and request deduplication via `@push.rocks/taskbuffer` | All cryptographic operations use `node:crypto`. The only external crypto dependency is `@peculiar/x509` for CSR generation. diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index ef160d3..301e244 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/smartacme', - version: '9.1.1', + version: '9.1.2', description: 'A TypeScript-based ACME client for LetsEncrypt certificate management with a focus on simplicity and power.' }