fix(docs): document built-in concurrency control, rate limiting, and request deduplication in README
This commit is contained in:
@@ -1,5 +1,14 @@
|
|||||||
# Changelog
|
# 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)
|
## 2026-02-15 - 9.1.1 - fix(deps)
|
||||||
bump @push.rocks/smarttime to ^4.2.3 and @push.rocks/taskbuffer to ^6.1.2
|
bump @push.rocks/smarttime to ^4.2.3 and @push.rocks/taskbuffer to ^6.1.2
|
||||||
|
|
||||||
|
|||||||
93
readme.md
93
readme.md
@@ -16,7 +16,7 @@ Ensure your project uses TypeScript and ECMAScript Modules (ESM).
|
|||||||
|
|
||||||
## Usage
|
## 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
|
### 🚀 Quick Start
|
||||||
|
|
||||||
@@ -58,18 +58,22 @@ await smartAcme.stop();
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
interface ISmartAcmeOptions {
|
interface ISmartAcmeOptions {
|
||||||
accountEmail: string; // ACME account email
|
accountEmail: string; // ACME account email
|
||||||
accountPrivateKey?: string; // Optional account key (auto-generated if omitted)
|
accountPrivateKey?: string; // Optional account key (auto-generated if omitted)
|
||||||
certManager: ICertManager; // Certificate storage backend
|
certManager: ICertManager; // Certificate storage backend
|
||||||
environment: 'production' | 'integration'; // Let's Encrypt environment
|
environment: 'production' | 'integration'; // Let's Encrypt environment
|
||||||
challengeHandlers: IChallengeHandler[]; // At least one handler required
|
challengeHandlers: IChallengeHandler[]; // At least one handler required
|
||||||
challengePriority?: string[]; // e.g. ['dns-01', 'http-01']
|
challengePriority?: string[]; // e.g. ['dns-01', 'http-01']
|
||||||
retryOptions?: { // Optional retry/backoff config
|
retryOptions?: { // Optional retry/backoff config
|
||||||
retries?: number; // Default: 10
|
retries?: number; // Default: 10
|
||||||
factor?: number; // Default: 4
|
factor?: number; // Default: 4
|
||||||
minTimeoutMs?: number; // Default: 1000
|
minTimeoutMs?: number; // Default: 1000
|
||||||
maxTimeoutMs?: number; // Default: 60000
|
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
|
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
|
## Certificate Managers
|
||||||
|
|
||||||
SmartAcme uses the `ICertManager` interface for pluggable certificate storage.
|
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 |
|
| `AcmeError` | Structured error class with type URN, subproblems, Retry-After, retryability |
|
||||||
| `AcmeOrderManager` | Order lifecycle — create, poll, finalize, download certificate |
|
| `AcmeOrderManager` | Order lifecycle — create, poll, finalize, download certificate |
|
||||||
| `AcmeChallengeManager` | Key authorization computation and challenge completion |
|
| `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.
|
All cryptographic operations use `node:crypto`. The only external crypto dependency is `@peculiar/x509` for CSR generation.
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartacme',
|
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.'
|
description: 'A TypeScript-based ACME client for LetsEncrypt certificate management with a focus on simplicity and power.'
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user