@warlock.js/herald 5.0.1 → 5.1.0

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 (2) hide show
  1. package/CHANGELOG.md +56 -39
  2. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -1,9 +1,26 @@
1
- # Changelog — @warlock.js/herald
2
-
3
- All notable changes to `@warlock.js/herald` are documented in this file.
4
-
1
+ # Changelog — @warlock.js/herald
2
+
3
+ All notable changes to `@warlock.js/herald` are documented in this file.
4
+
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@warlock.js/*` packages are released in lockstep — every package shares the same version number, so a version below may list only the changes that affected this package.
6
6
 
7
+ ## 5.1.0
8
+
9
+ No changes to `@warlock.js/herald`. Released in lockstep with the `@warlock.js/web`
10
+ React-execution fix and the `@warlock.js/core` CLI additions — see those packages'
11
+ changelogs.
12
+
13
+ ## 5.0.2 - 2026-08-25
14
+
15
+ No changes to `@warlock.js/herald`. Released in lockstep with the `@warlock.js/web` SSR
16
+ fix (`ssr.noExternal`) — see that package's changelog.
17
+
18
+ ## 5.0.1 - 2026-08-25
19
+
20
+ No changes to `@warlock.js/herald`. Released in lockstep with the `create-warlock` vite
21
+ resolution pin and the `@warlock.js/web` peer narrowing — see those packages'
22
+ changelogs.
23
+
7
24
  ## 5.0.0 - 2026-08-25
8
25
 
9
26
  ### Changed
@@ -11,38 +28,38 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
11
28
  - This package is unchanged in 5.0.0; its version moved only because the Warlock family releases in lockstep.
12
29
 
13
30
  ## 4.16.0 - 2026-08-18
14
-
15
- ### Security
16
-
17
- - **Poison-message DoS: an `EventConsumer.handle()` that reliably throws was nack+requeued forever**, with no retry cap and no dead-letter escape hatch — a single bad message could pin a consumer in a hot ack/nack loop indefinitely, starving every other message behind it (worse with low prefetch). `prepareConsumerSubscription`'s catch now calls the channel's bounded `ctx.retry()` instead of an unconditional `ctx.nack(true)`, so redelivery is capped and the message is dead-lettered (if configured) or dropped with a loud `log.error` once the cap is hit — never silently, and never forever
18
- - **Fixed the retry counter never advancing on the automatic (non-explicit) nack path** (`RabbitMQChannel.subscribe`'s catch, used by any direct `channel.subscribe(handler, { retry, deadLetter })` caller whose handler throws instead of calling `ctx.retry()` itself). It read `x-retry-count` from the *original* message's headers and then plain-`nack(msg, false, true)`'d — which redelivers that same original message, so the header a developer's `retry.maxRetries`/`deadLetter` depended on never changed and the configured cap was silently never reached. Both the automatic path and the explicit `ctx.retry()` path now share one bounded-retry routine that republishes with an incremented header, so `maxRetries`/`deadLetter` are honored regardless of which path a handler takes
19
- - **Broker credentials no longer leak into thrown/logged connection errors.** `username`/`password` are now URI-encoded when building the `amqp://` URL (a reserved character like `@`/`:`/`/` in a generated secret previously produced a malformed URL whose parser error echoed the raw credential back), and any error surfaced from `connect()` — including one that embeds a caller-supplied `uri` with credentials — has `user:pass@` redacted before it's re-thrown, so a connection failure can no longer put a plaintext broker password in front of `console.error`/structured logging/an error tracker
20
-
21
- ### Dependencies
22
-
23
- - Bumped `@mongez/events` to `^2.2.7` (no breaking changes) and `@mongez/reinforcements` to `^4.0.1`. The reinforcements major makes `Random.string/nanoid/id/token/uuid` CSPRNG-backed (WebCrypto) and removes `Random.seed()` support — audited this package's source and tests for `Random.seed(` and for seeded/reproducible use of `Random.*`; none found, so no code changes were needed.
24
-
25
- ## 4.15.0 - 2026-08-16
26
-
27
- ### Fixed
28
-
29
- - **The RabbitMQ driver's lazy `amqplib` loader was not idempotent under concurrent callers.** It cached the resolved module in a module-level binding but nothing guarded the load itself, so two loads could be in flight at the same time and the last one to settle won the binding. The eager, unawaited `loadAmqplibModule()` call at module scope was one of those callers by construction — it started a load nobody was waiting on, which then raced the awaited call from `connect()`. That eager call has been removed: `connect()` already awaits the loader, so it bought nothing but the race. The loader now memoizes the in-flight promise itself, so the first caller starts the `import()` and every later caller awaits that same one
30
- - **No user-visible misbehaviour is known in production** — both racing paths resolve the same real `amqplib`, so whichever won, callers got the module they expected. The observable damage was in test isolation: when a test was aborted mid-`await import(...)`, the racing loads could leave the binding holding the real `amqplib` while the test file's `vi.mock("amqplib")` was still active, so every later test in that file silently bypassed the mock and opened a real socket. Proven by instrumentation — the driver held a live `ChannelModel` on `::1:5672` while `import("amqplib")` inside the same test still returned the mock, which is how a green test could be green for the wrong reason
31
- - **Verified by a timeout sweep, not by a passing suite.** The full suite passed both before and after (13 files / 137 tests), because the fault only surfaces when the first test is starved of time. Running `tests/connect-to-broker.test.ts` at `--testTimeout=3000` and `4000` previously timed out the first test *and* took `wraps a connection failure with the driver name` down with it, failing in ~50 ms with `promise resolved "Broker{…}" instead of rejecting` — the mock was gone. With the loader fixed, that test passes at every timeout even while the first test still times out: starving one test can no longer poison the next
32
- - **The first test also paid a cold-transform cost inside its own timed body**, since `connectToBroker` dynamically imports the driver, which pulls in `@warlock.js/seal` and `@warlock.js/logger` as raw TS source. That work moved to a `beforeAll` warm-up. This is a test-timing change only and carries none of the correctness weight above — the loader fix stands on its own without it
33
-
34
- ## 4.12.0
35
-
36
- ### Changed
37
-
38
- - Declares its own test runner and pins it to an exact version (`vitest@4.1.10`). The package is its own repository, so a runner resolved from a workspace root it may not be cloned with is a runner it cannot rely on. The pin is exact rather than a range because the version moved underneath the suite mid-development on an unrelated install — a suite whose runner can change without anyone choosing it proves less than it appears to
39
-
40
- ## 4.2.11
41
-
42
- ### Changed
43
-
44
- - Bumped `@mongez/reinforcements` to 3.3.0
45
-
46
- ## 4.1.15
47
-
48
- - Baseline — per-package changelog tracking starts at this version.
31
+
32
+ ### Security
33
+
34
+ - **Poison-message DoS: an `EventConsumer.handle()` that reliably throws was nack+requeued forever**, with no retry cap and no dead-letter escape hatch — a single bad message could pin a consumer in a hot ack/nack loop indefinitely, starving every other message behind it (worse with low prefetch). `prepareConsumerSubscription`'s catch now calls the channel's bounded `ctx.retry()` instead of an unconditional `ctx.nack(true)`, so redelivery is capped and the message is dead-lettered (if configured) or dropped with a loud `log.error` once the cap is hit — never silently, and never forever
35
+ - **Fixed the retry counter never advancing on the automatic (non-explicit) nack path** (`RabbitMQChannel.subscribe`'s catch, used by any direct `channel.subscribe(handler, { retry, deadLetter })` caller whose handler throws instead of calling `ctx.retry()` itself). It read `x-retry-count` from the *original* message's headers and then plain-`nack(msg, false, true)`'d — which redelivers that same original message, so the header a developer's `retry.maxRetries`/`deadLetter` depended on never changed and the configured cap was silently never reached. Both the automatic path and the explicit `ctx.retry()` path now share one bounded-retry routine that republishes with an incremented header, so `maxRetries`/`deadLetter` are honored regardless of which path a handler takes
36
+ - **Broker credentials no longer leak into thrown/logged connection errors.** `username`/`password` are now URI-encoded when building the `amqp://` URL (a reserved character like `@`/`:`/`/` in a generated secret previously produced a malformed URL whose parser error echoed the raw credential back), and any error surfaced from `connect()` — including one that embeds a caller-supplied `uri` with credentials — has `user:pass@` redacted before it's re-thrown, so a connection failure can no longer put a plaintext broker password in front of `console.error`/structured logging/an error tracker
37
+
38
+ ### Dependencies
39
+
40
+ - Bumped `@mongez/events` to `^2.2.7` (no breaking changes) and `@mongez/reinforcements` to `^4.0.1`. The reinforcements major makes `Random.string/nanoid/id/token/uuid` CSPRNG-backed (WebCrypto) and removes `Random.seed()` support — audited this package's source and tests for `Random.seed(` and for seeded/reproducible use of `Random.*`; none found, so no code changes were needed.
41
+
42
+ ## 4.15.0 - 2026-08-16
43
+
44
+ ### Fixed
45
+
46
+ - **The RabbitMQ driver's lazy `amqplib` loader was not idempotent under concurrent callers.** It cached the resolved module in a module-level binding but nothing guarded the load itself, so two loads could be in flight at the same time and the last one to settle won the binding. The eager, unawaited `loadAmqplibModule()` call at module scope was one of those callers by construction — it started a load nobody was waiting on, which then raced the awaited call from `connect()`. That eager call has been removed: `connect()` already awaits the loader, so it bought nothing but the race. The loader now memoizes the in-flight promise itself, so the first caller starts the `import()` and every later caller awaits that same one
47
+ - **No user-visible misbehaviour is known in production** — both racing paths resolve the same real `amqplib`, so whichever won, callers got the module they expected. The observable damage was in test isolation: when a test was aborted mid-`await import(...)`, the racing loads could leave the binding holding the real `amqplib` while the test file's `vi.mock("amqplib")` was still active, so every later test in that file silently bypassed the mock and opened a real socket. Proven by instrumentation — the driver held a live `ChannelModel` on `::1:5672` while `import("amqplib")` inside the same test still returned the mock, which is how a green test could be green for the wrong reason
48
+ - **Verified by a timeout sweep, not by a passing suite.** The full suite passed both before and after (13 files / 137 tests), because the fault only surfaces when the first test is starved of time. Running `tests/connect-to-broker.test.ts` at `--testTimeout=3000` and `4000` previously timed out the first test *and* took `wraps a connection failure with the driver name` down with it, failing in ~50 ms with `promise resolved "Broker{…}" instead of rejecting` — the mock was gone. With the loader fixed, that test passes at every timeout even while the first test still times out: starving one test can no longer poison the next
49
+ - **The first test also paid a cold-transform cost inside its own timed body**, since `connectToBroker` dynamically imports the driver, which pulls in `@warlock.js/seal` and `@warlock.js/logger` as raw TS source. That work moved to a `beforeAll` warm-up. This is a test-timing change only and carries none of the correctness weight above — the loader fix stands on its own without it
50
+
51
+ ## 4.12.0
52
+
53
+ ### Changed
54
+
55
+ - Declares its own test runner and pins it to an exact version (`vitest@4.1.10`). The package is its own repository, so a runner resolved from a workspace root it may not be cloned with is a runner it cannot rely on. The pin is exact rather than a range because the version moved underneath the suite mid-development on an unrelated install — a suite whose runner can change without anyone choosing it proves less than it appears to
56
+
57
+ ## 4.2.11
58
+
59
+ ### Changed
60
+
61
+ - Bumped `@mongez/reinforcements` to 3.3.0
62
+
63
+ ## 4.1.15
64
+
65
+ - Baseline — per-package changelog tracking starts at this version.
package/package.json CHANGED
@@ -5,8 +5,8 @@
5
5
  "@mongez/copper": "^2.1.2",
6
6
  "@mongez/events": "^2.2.7",
7
7
  "@mongez/reinforcements": "^4.0.1",
8
- "@warlock.js/logger": "5.0.1",
9
- "@warlock.js/seal": "5.0.1"
8
+ "@warlock.js/logger": "5.1.0",
9
+ "@warlock.js/seal": "5.1.0"
10
10
  },
11
11
  "repository": {
12
12
  "type": "git",
@@ -33,7 +33,7 @@
33
33
  ],
34
34
  "author": "hassanzohdy",
35
35
  "license": "MIT",
36
- "version": "5.0.1",
36
+ "version": "5.1.0",
37
37
  "main": "./cjs/index.cjs",
38
38
  "module": "./esm/index.mjs",
39
39
  "types": "./esm/index.d.mts",