Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 77d40985f3 | |||
| adf9262ded | |||
| e2d182ca03 | |||
| 8cd713447e | |||
| 2cf3dbdd95 | |||
| 1c75bac44f |
22
changelog.md
22
changelog.md
@@ -1,5 +1,27 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-02-16 - 9.1.3 - fix(smartacme)
|
||||||
|
Include base domain alongside wildcard when building identifiers for wildcard certificate requests
|
||||||
|
|
||||||
|
- When isWildcardRequest is true, the base domain (e.g. example.com) is now added in addition to the wildcard (*.example.com) so the issued certificate covers both apex and wildcard entries.
|
||||||
|
- Prevents missing SAN for the apex domain when requesting wildcard certificates.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
- @push.rocks/smarttime: ^4.1.1 -> ^4.2.3
|
||||||
|
- @push.rocks/taskbuffer: ^6.1.0 -> ^6.1.2
|
||||||
|
- Only package.json dependency version updates; no code changes
|
||||||
|
|
||||||
## 2026-02-15 - 9.1.0 - feat(smartacme)
|
## 2026-02-15 - 9.1.0 - feat(smartacme)
|
||||||
Integrate @push.rocks/taskbuffer TaskManager to coordinate ACME certificate issuance with per-domain mutex, global concurrency cap, and account-level rate limiting; refactor issuance flow into a single reusable cert-issuance task, expose issuance events, and update lifecycle to start/stop the TaskManager. Add configuration for concurrent issuances and sliding-window order limits, export taskbuffer types/plugins, and update tests and docs accordingly.
|
Integrate @push.rocks/taskbuffer TaskManager to coordinate ACME certificate issuance with per-domain mutex, global concurrency cap, and account-level rate limiting; refactor issuance flow into a single reusable cert-issuance task, expose issuance events, and update lifecycle to start/stop the TaskManager. Add configuration for concurrent issuances and sliding-window order limits, export taskbuffer types/plugins, and update tests and docs accordingly.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartacme",
|
"name": "@push.rocks/smartacme",
|
||||||
"version": "9.1.0",
|
"version": "9.1.3",
|
||||||
"private": false,
|
"private": false,
|
||||||
"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.",
|
||||||
"main": "dist_ts/index.js",
|
"main": "dist_ts/index.js",
|
||||||
@@ -43,14 +43,14 @@
|
|||||||
"@peculiar/x509": "^1.14.3",
|
"@peculiar/x509": "^1.14.3",
|
||||||
"@push.rocks/lik": "^6.2.2",
|
"@push.rocks/lik": "^6.2.2",
|
||||||
"@push.rocks/smartdata": "^7.0.15",
|
"@push.rocks/smartdata": "^7.0.15",
|
||||||
"@push.rocks/taskbuffer": "^6.1.0",
|
|
||||||
"@push.rocks/smartdelay": "^3.0.5",
|
"@push.rocks/smartdelay": "^3.0.5",
|
||||||
"@push.rocks/smartdns": "^7.8.1",
|
"@push.rocks/smartdns": "^7.8.1",
|
||||||
"@push.rocks/smartlog": "^3.1.10",
|
"@push.rocks/smartlog": "^3.1.10",
|
||||||
"@push.rocks/smartnetwork": "^4.4.0",
|
"@push.rocks/smartnetwork": "^4.4.0",
|
||||||
"@push.rocks/smartstring": "^4.1.0",
|
"@push.rocks/smartstring": "^4.1.0",
|
||||||
"@push.rocks/smarttime": "^4.1.1",
|
"@push.rocks/smarttime": "^4.2.3",
|
||||||
"@push.rocks/smartunique": "^3.0.9",
|
"@push.rocks/smartunique": "^3.0.9",
|
||||||
|
"@push.rocks/taskbuffer": "^6.1.2",
|
||||||
"@tsclass/tsclass": "^9.3.0"
|
"@tsclass/tsclass": "^9.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
69
pnpm-lock.yaml
generated
69
pnpm-lock.yaml
generated
@@ -36,14 +36,14 @@ importers:
|
|||||||
specifier: ^4.1.0
|
specifier: ^4.1.0
|
||||||
version: 4.1.0
|
version: 4.1.0
|
||||||
'@push.rocks/smarttime':
|
'@push.rocks/smarttime':
|
||||||
specifier: ^4.1.1
|
specifier: ^4.2.3
|
||||||
version: 4.1.1
|
version: 4.2.3
|
||||||
'@push.rocks/smartunique':
|
'@push.rocks/smartunique':
|
||||||
specifier: ^3.0.9
|
specifier: ^3.0.9
|
||||||
version: 3.0.9
|
version: 3.0.9
|
||||||
'@push.rocks/taskbuffer':
|
'@push.rocks/taskbuffer':
|
||||||
specifier: ^6.1.0
|
specifier: ^6.1.2
|
||||||
version: 6.1.1
|
version: 6.1.2
|
||||||
'@tsclass/tsclass':
|
'@tsclass/tsclass':
|
||||||
specifier: ^9.3.0
|
specifier: ^9.3.0
|
||||||
version: 9.3.0
|
version: 9.3.0
|
||||||
@@ -1015,8 +1015,8 @@ packages:
|
|||||||
'@push.rocks/smartstring@4.1.0':
|
'@push.rocks/smartstring@4.1.0':
|
||||||
resolution: {integrity: sha512-Q4py/Nm3KTDhQ9EiC75yBtSTLR0KLMwhKM+8gGcutgKotZT6wJ3gncjmtD8LKFfNhb4lSaFMgPJgLrCHTOH6Iw==}
|
resolution: {integrity: sha512-Q4py/Nm3KTDhQ9EiC75yBtSTLR0KLMwhKM+8gGcutgKotZT6wJ3gncjmtD8LKFfNhb4lSaFMgPJgLrCHTOH6Iw==}
|
||||||
|
|
||||||
'@push.rocks/smarttime@4.1.1':
|
'@push.rocks/smarttime@4.2.3':
|
||||||
resolution: {integrity: sha512-Ha/3J/G+zfTl4ahpZgF6oUOZnUjpLhrBja0OQ2cloFxF9sKT8I1COaSqIfBGDtoK2Nly4UD4aTJ3JcJNOg/kgA==}
|
resolution: {integrity: sha512-8gMg8RUkrCG4p9NcEUZV7V6KpL24+jAMK02g7qyhfA6giz/JJWD0+8w8xjSR+G7qe16KVQ2y3RbvAL9TxmO36g==}
|
||||||
|
|
||||||
'@push.rocks/smartunique@3.0.9':
|
'@push.rocks/smartunique@3.0.9':
|
||||||
resolution: {integrity: sha512-q6DYQgT7/dqdWi9HusvtWCjdsFzLFXY9LTtaZV6IYNJt6teZOonoygxTdNt9XLn6niBSbLYrHSKvJNTRH/uK+g==}
|
resolution: {integrity: sha512-q6DYQgT7/dqdWi9HusvtWCjdsFzLFXY9LTtaZV6IYNJt6teZOonoygxTdNt9XLn6niBSbLYrHSKvJNTRH/uK+g==}
|
||||||
@@ -1039,8 +1039,8 @@ packages:
|
|||||||
'@push.rocks/taskbuffer@3.5.0':
|
'@push.rocks/taskbuffer@3.5.0':
|
||||||
resolution: {integrity: sha512-Y9WwIEIyp6oVFdj06j84tfrZIvjhbMb3DF52rYxlTeYLk3W7RPhSg1bGPCbtkXWeKdBrSe37V90BkOG7Qq8Pqg==}
|
resolution: {integrity: sha512-Y9WwIEIyp6oVFdj06j84tfrZIvjhbMb3DF52rYxlTeYLk3W7RPhSg1bGPCbtkXWeKdBrSe37V90BkOG7Qq8Pqg==}
|
||||||
|
|
||||||
'@push.rocks/taskbuffer@6.1.1':
|
'@push.rocks/taskbuffer@6.1.2':
|
||||||
resolution: {integrity: sha512-rEJxf+yIbHwztNkrL5QJFinf0wai1Fzs1xgonEOo9LmG/DDCanfLWHSd5zCVG0kXxzz4sHv87fgkg+w/TIHLpg==}
|
resolution: {integrity: sha512-sdqKd8N/GidztQ1k3r8A86rLvD8Afyir5FjYCNJXDD9837JLoqzHaOKGltUSBsCGh2gjsZn6GydsY6HhXQgvZQ==}
|
||||||
|
|
||||||
'@push.rocks/webrequest@3.0.37':
|
'@push.rocks/webrequest@3.0.37':
|
||||||
resolution: {integrity: sha512-fLN7kP6GeHFxE4UH4r9C9pjcQb0QkJxHeAMwXvbOqB9hh0MFNKhtGU7GoaTn8SVRGRMPc9UqZVNwo6u5l8Wn0A==}
|
resolution: {integrity: sha512-fLN7kP6GeHFxE4UH4r9C9pjcQb0QkJxHeAMwXvbOqB9hh0MFNKhtGU7GoaTn8SVRGRMPc9UqZVNwo6u5l8Wn0A==}
|
||||||
@@ -2293,6 +2293,10 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
croner@10.0.1:
|
||||||
|
resolution: {integrity: sha512-ixNtAJndqh173VQ4KodSdJEI6nuioBWI0V1ITNKhZZsO0pEMoDxz539T4FTTbSZ/xIOSuDnzxLVRqBVSvPNE2g==}
|
||||||
|
engines: {node: '>=18.0'}
|
||||||
|
|
||||||
croner@4.4.1:
|
croner@4.4.1:
|
||||||
resolution: {integrity: sha512-aqVeeIPCf5/NZFlz4mN4MLEOs9xf4ODCmHQDs+577JFj8mK3RkKJz77h7+Rn94AijUqKdFNOUHM+v88d8p02UQ==}
|
resolution: {integrity: sha512-aqVeeIPCf5/NZFlz4mN4MLEOs9xf4ODCmHQDs+577JFj8mK3RkKJz77h7+Rn94AijUqKdFNOUHM+v88d8p02UQ==}
|
||||||
|
|
||||||
@@ -2300,10 +2304,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-9pSLe+tDJnmNak2JeMkz6ZmTCXP5p6vCxSd4kvDqrTJkqAP62j2uAEIZjf8cPDZIakStujqVzh5Y5MIWH3yYAw==}
|
resolution: {integrity: sha512-9pSLe+tDJnmNak2JeMkz6ZmTCXP5p6vCxSd4kvDqrTJkqAP62j2uAEIZjf8cPDZIakStujqVzh5Y5MIWH3yYAw==}
|
||||||
engines: {node: '>=6.0'}
|
engines: {node: '>=6.0'}
|
||||||
|
|
||||||
croner@9.0.0:
|
|
||||||
resolution: {integrity: sha512-onMB0OkDjkXunhdW9htFjEhqrD54+M94i6ackoUkjHKbRnXdyEyKRelp4nJ1kAz32+s27jP1FsebpJCVl0BsvA==}
|
|
||||||
engines: {node: '>=18.0'}
|
|
||||||
|
|
||||||
cross-spawn@7.0.6:
|
cross-spawn@7.0.6:
|
||||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@@ -2322,6 +2322,9 @@ packages:
|
|||||||
dayjs@1.11.13:
|
dayjs@1.11.13:
|
||||||
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
|
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
|
||||||
|
|
||||||
|
dayjs@1.11.19:
|
||||||
|
resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==}
|
||||||
|
|
||||||
debug@2.6.9:
|
debug@2.6.9:
|
||||||
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
|
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -3587,8 +3590,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q==}
|
resolution: {integrity: sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q==}
|
||||||
engines: {node: '>=14.16'}
|
engines: {node: '>=14.16'}
|
||||||
|
|
||||||
pretty-ms@9.2.0:
|
pretty-ms@9.3.0:
|
||||||
resolution: {integrity: sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==}
|
resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
progress@2.0.3:
|
progress@2.0.3:
|
||||||
@@ -4295,7 +4298,7 @@ snapshots:
|
|||||||
'@push.rocks/smartrx': 3.0.10
|
'@push.rocks/smartrx': 3.0.10
|
||||||
'@push.rocks/smartsitemap': 2.0.4
|
'@push.rocks/smartsitemap': 2.0.4
|
||||||
'@push.rocks/smartstream': 3.2.5
|
'@push.rocks/smartstream': 3.2.5
|
||||||
'@push.rocks/smarttime': 4.1.1
|
'@push.rocks/smarttime': 4.2.3
|
||||||
'@push.rocks/taskbuffer': 3.5.0
|
'@push.rocks/taskbuffer': 3.5.0
|
||||||
'@push.rocks/webrequest': 3.0.37
|
'@push.rocks/webrequest': 3.0.37
|
||||||
'@push.rocks/webstore': 2.0.20
|
'@push.rocks/webstore': 2.0.20
|
||||||
@@ -5617,7 +5620,7 @@ snapshots:
|
|||||||
'@push.rocks/smartrequest': 5.0.1
|
'@push.rocks/smartrequest': 5.0.1
|
||||||
'@push.rocks/smarts3': 3.0.3
|
'@push.rocks/smarts3': 3.0.3
|
||||||
'@push.rocks/smartshell': 3.3.0
|
'@push.rocks/smartshell': 3.3.0
|
||||||
'@push.rocks/smarttime': 4.1.1
|
'@push.rocks/smarttime': 4.2.3
|
||||||
'@types/ws': 8.18.1
|
'@types/ws': 8.18.1
|
||||||
figures: 6.1.0
|
figures: 6.1.0
|
||||||
ws: 8.19.0
|
ws: 8.19.0
|
||||||
@@ -5972,7 +5975,7 @@ snapshots:
|
|||||||
'@push.rocks/smartmatch': 2.0.0
|
'@push.rocks/smartmatch': 2.0.0
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrx': 3.0.10
|
'@push.rocks/smartrx': 3.0.10
|
||||||
'@push.rocks/smarttime': 4.1.1
|
'@push.rocks/smarttime': 4.2.3
|
||||||
'@types/minimatch': 5.1.2
|
'@types/minimatch': 5.1.2
|
||||||
'@types/symbol-tree': 3.2.5
|
'@types/symbol-tree': 3.2.5
|
||||||
symbol-tree: 3.2.4
|
symbol-tree: 3.2.4
|
||||||
@@ -6127,7 +6130,7 @@ snapshots:
|
|||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrx': 3.0.10
|
'@push.rocks/smartrx': 3.0.10
|
||||||
'@push.rocks/smartstring': 4.1.0
|
'@push.rocks/smartstring': 4.1.0
|
||||||
'@push.rocks/smarttime': 4.1.1
|
'@push.rocks/smarttime': 4.2.3
|
||||||
'@push.rocks/smartunique': 3.0.9
|
'@push.rocks/smartunique': 3.0.9
|
||||||
'@push.rocks/taskbuffer': 3.5.0
|
'@push.rocks/taskbuffer': 3.5.0
|
||||||
'@tsclass/tsclass': 8.2.1
|
'@tsclass/tsclass': 8.2.1
|
||||||
@@ -6155,7 +6158,7 @@ snapshots:
|
|||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrx': 3.0.10
|
'@push.rocks/smartrx': 3.0.10
|
||||||
'@push.rocks/smartstring': 4.1.0
|
'@push.rocks/smartstring': 4.1.0
|
||||||
'@push.rocks/smarttime': 4.1.1
|
'@push.rocks/smarttime': 4.2.3
|
||||||
'@push.rocks/smartunique': 3.0.9
|
'@push.rocks/smartunique': 3.0.9
|
||||||
'@push.rocks/taskbuffer': 3.5.0
|
'@push.rocks/taskbuffer': 3.5.0
|
||||||
'@tsclass/tsclass': 9.3.0
|
'@tsclass/tsclass': 9.3.0
|
||||||
@@ -6326,7 +6329,7 @@ snapshots:
|
|||||||
'@push.rocks/smartfile': 11.2.7
|
'@push.rocks/smartfile': 11.2.7
|
||||||
'@push.rocks/smarthash': 3.2.6
|
'@push.rocks/smarthash': 3.2.6
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smarttime': 4.1.1
|
'@push.rocks/smarttime': 4.2.3
|
||||||
'@push.rocks/webrequest': 4.0.2
|
'@push.rocks/webrequest': 4.0.2
|
||||||
'@tsclass/tsclass': 9.3.0
|
'@tsclass/tsclass': 9.3.0
|
||||||
|
|
||||||
@@ -6412,7 +6415,7 @@ snapshots:
|
|||||||
'@push.rocks/smartpath': 6.0.0
|
'@push.rocks/smartpath': 6.0.0
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrequest': 4.4.2
|
'@push.rocks/smartrequest': 4.4.2
|
||||||
'@push.rocks/smarttime': 4.1.1
|
'@push.rocks/smarttime': 4.2.3
|
||||||
'@push.rocks/smartversion': 3.0.5
|
'@push.rocks/smartversion': 3.0.5
|
||||||
package-json: 8.1.1
|
package-json: 8.1.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -6585,7 +6588,7 @@ snapshots:
|
|||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.1.11
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrx': 3.0.10
|
'@push.rocks/smartrx': 3.0.10
|
||||||
'@push.rocks/smarttime': 4.1.1
|
'@push.rocks/smarttime': 4.2.3
|
||||||
engine.io: 6.6.4
|
engine.io: 6.6.4
|
||||||
socket.io: 4.8.1
|
socket.io: 4.8.1
|
||||||
socket.io-client: 4.8.1
|
socket.io-client: 4.8.1
|
||||||
@@ -6635,16 +6638,16 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/isounique': 1.0.5
|
'@push.rocks/isounique': 1.0.5
|
||||||
|
|
||||||
'@push.rocks/smarttime@4.1.1':
|
'@push.rocks/smarttime@4.2.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/lik': 6.2.2
|
'@push.rocks/lik': 6.2.2
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
croner: 9.0.0
|
croner: 10.0.1
|
||||||
date-fns: 4.1.0
|
date-fns: 4.1.0
|
||||||
dayjs: 1.11.13
|
dayjs: 1.11.19
|
||||||
is-nan: 1.3.2
|
is-nan: 1.3.2
|
||||||
pretty-ms: 9.2.0
|
pretty-ms: 9.3.0
|
||||||
|
|
||||||
'@push.rocks/smartunique@3.0.9':
|
'@push.rocks/smartunique@3.0.9':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6680,7 +6683,7 @@ snapshots:
|
|||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.1.11
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrx': 3.0.10
|
'@push.rocks/smartrx': 3.0.10
|
||||||
'@push.rocks/smarttime': 4.1.1
|
'@push.rocks/smarttime': 4.2.3
|
||||||
'@push.rocks/smartunique': 3.0.9
|
'@push.rocks/smartunique': 3.0.9
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@nuxt/kit'
|
- '@nuxt/kit'
|
||||||
@@ -6688,7 +6691,7 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- vue
|
- vue
|
||||||
|
|
||||||
'@push.rocks/taskbuffer@6.1.1':
|
'@push.rocks/taskbuffer@6.1.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@design.estate/dees-element': 2.1.6
|
'@design.estate/dees-element': 2.1.6
|
||||||
'@push.rocks/lik': 6.2.2
|
'@push.rocks/lik': 6.2.2
|
||||||
@@ -6696,7 +6699,7 @@ snapshots:
|
|||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.1.11
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrx': 3.0.10
|
'@push.rocks/smartrx': 3.0.10
|
||||||
'@push.rocks/smarttime': 4.1.1
|
'@push.rocks/smarttime': 4.2.3
|
||||||
'@push.rocks/smartunique': 3.0.9
|
'@push.rocks/smartunique': 3.0.9
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@nuxt/kit'
|
- '@nuxt/kit'
|
||||||
@@ -8260,12 +8263,12 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|
||||||
|
croner@10.0.1: {}
|
||||||
|
|
||||||
croner@4.4.1: {}
|
croner@4.4.1: {}
|
||||||
|
|
||||||
croner@5.7.0: {}
|
croner@5.7.0: {}
|
||||||
|
|
||||||
croner@9.0.0: {}
|
|
||||||
|
|
||||||
cross-spawn@7.0.6:
|
cross-spawn@7.0.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
path-key: 3.1.1
|
path-key: 3.1.1
|
||||||
@@ -8282,6 +8285,8 @@ snapshots:
|
|||||||
|
|
||||||
dayjs@1.11.13: {}
|
dayjs@1.11.13: {}
|
||||||
|
|
||||||
|
dayjs@1.11.19: {}
|
||||||
|
|
||||||
debug@2.6.9:
|
debug@2.6.9:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.0.0
|
ms: 2.0.0
|
||||||
@@ -9765,7 +9770,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
parse-ms: 3.0.0
|
parse-ms: 3.0.0
|
||||||
|
|
||||||
pretty-ms@9.2.0:
|
pretty-ms@9.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
parse-ms: 4.0.0
|
parse-ms: 4.0.0
|
||||||
|
|
||||||
|
|||||||
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.0',
|
version: '9.1.3',
|
||||||
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.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -430,6 +430,7 @@ export class SmartAcme {
|
|||||||
|
|
||||||
if (isWildcardRequest) {
|
if (isWildcardRequest) {
|
||||||
identifiers.push({ type: 'dns', value: `*.${certDomainName}` });
|
identifiers.push({ type: 'dns', value: `*.${certDomainName}` });
|
||||||
|
identifiers.push({ type: 'dns', value: certDomainName });
|
||||||
} else {
|
} else {
|
||||||
identifiers.push({ type: 'dns', value: certDomainName });
|
identifiers.push({ type: 'dns', value: certDomainName });
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user