@warlock.js/herald 5.24.0 β†’ 5.26.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.
package/README.md CHANGED
@@ -1,376 +1,376 @@
1
- # πŸ“― Warlock Herald
2
-
3
- > Let heralds carry your messages across services!
4
-
5
- A powerful, type-safe message bus library for RabbitMQ, Kafka, and more.
6
-
7
- ## πŸ“¦ Installation
8
-
9
- ```bash
10
- npm install @warlock.js/herald
11
- ```
12
-
13
- ```bash
14
- yarn add @warlock.js/herald
15
- ```
16
-
17
- ```bash
18
- pnpm add @warlock.js/herald
19
- ```
20
-
21
- ### Driver Dependencies
22
-
23
- Install the driver for your message broker using the Warlock CLI:
24
-
25
- ```bash
26
- # RabbitMQ (recommended)
27
- npx warlock add herald --driver=rabbitmq
28
-
29
- # Kafka (coming soon)
30
- npx warlock add herald --driver=kafka
31
- ```
32
-
33
- Or install manually:
34
-
35
- ```bash
36
- # RabbitMQ
37
- npm install amqplib
38
-
39
- # Kafka (coming soon)
40
- npm install kafkajs
41
- ```
42
-
43
- ## Server-only package
44
-
45
- `@warlock.js/herald`'s entire runtime surface is server-only. Its `package.json` declares `"warlock": { "environment": "server" }` β€” build-boundary metadata that `@warlock.js/web`'s Gate A (import resolution) and Gate C (emitted-bundle verification) read to keep it out of the browser bundle.
46
-
1
+ # πŸ“― Warlock Herald
2
+
3
+ > Let heralds carry your messages across services!
4
+
5
+ A powerful, type-safe message bus library for RabbitMQ, Kafka, and more.
6
+
7
+ ## πŸ“¦ Installation
8
+
9
+ ```bash
10
+ npm install @warlock.js/herald
11
+ ```
12
+
13
+ ```bash
14
+ yarn add @warlock.js/herald
15
+ ```
16
+
17
+ ```bash
18
+ pnpm add @warlock.js/herald
19
+ ```
20
+
21
+ ### Driver Dependencies
22
+
23
+ Install the driver for your message broker using the Warlock CLI:
24
+
25
+ ```bash
26
+ # RabbitMQ (recommended)
27
+ npx warlock add herald --driver=rabbitmq
28
+
29
+ # Kafka (coming soon)
30
+ npx warlock add herald --driver=kafka
31
+ ```
32
+
33
+ Or install manually:
34
+
35
+ ```bash
36
+ # RabbitMQ
37
+ npm install amqplib
38
+
39
+ # Kafka (coming soon)
40
+ npm install kafkajs
41
+ ```
42
+
43
+ ## Server-only package
44
+
45
+ `@warlock.js/herald`'s entire runtime surface is server-only. Its `package.json` declares `"warlock": { "environment": "server" }` β€” build-boundary metadata that `@warlock.js/web`'s Gate A (import resolution) and Gate C (emitted-bundle verification) read to keep it out of the browser bundle.
46
+
47
47
  - App code that reaches the client bundle (including page and layout modules) must not value-import `@warlock.js/herald` β€” Gate A refuses the build.
48
- - A type-only import (`import type { ... } from "@warlock.js/herald"`) is allowed β€” it carries no runtime edge.
49
- - Server loaders, controllers, and modules may import it freely.
50
- ```
51
-
52
- ## πŸš€ Quick Start
53
-
54
- ```typescript
55
- import { connectToBroker, herald } from "@warlock.js/herald";
56
-
57
- // Connect to RabbitMQ
58
- await connectToBroker({
59
- driver: "rabbitmq",
60
- host: "localhost",
61
- port: 5672,
62
- username: "guest",
63
- password: "guest",
64
- });
65
-
66
- // Publish a message
67
- await herald().channel("user.created").publish({
68
- userId: 1,
69
- email: "user@example.com",
70
- });
71
-
72
- // Subscribe to messages
73
- herald()
74
- .channel<{ userId: number; email: string }>("user.created")
75
- .subscribe(async (message, ctx) => {
76
- console.log("User created:", message.payload.userId);
77
- await ctx.ack();
78
- });
79
- ```
80
-
81
- ## 🎯 Core Concepts
82
-
83
- ### Brokers
84
-
85
- A broker wraps a message broker connection (e.g. RabbitMQ, Kafka). You can have multiple brokers for different clusters or purposes.
86
-
87
- ```typescript
88
- // Single broker (default)
89
- await connectToBroker({
90
- driver: "rabbitmq",
91
- host: "localhost",
92
- });
93
-
94
- // Multiple brokers
95
- await connectToBroker({
96
- driver: "rabbitmq",
97
- name: "notifications",
98
- isDefault: true,
99
- host: process.env.NOTIFICATIONS_HOST,
100
- });
101
-
102
- await connectToBroker({
103
- driver: "rabbitmq",
104
- name: "analytics",
105
- host: process.env.ANALYTICS_HOST,
106
- });
107
-
108
- // Use default
109
- herald().channel("notifications").publish({ ... });
110
-
111
- // Use specific broker
112
- herald("analytics").channel("events").publish({ ... });
113
- ```
114
-
115
- ### Channels
116
-
117
- Channels represent queues (RabbitMQ) or topics (Kafka). They provide a unified pub/sub interface via `herald()`.
118
-
119
- ```typescript
120
- // Get a channel
121
- const userChannel = herald().channel("user.created");
122
-
123
- // Publish
124
- await userChannel.publish({ userId: 1 });
125
-
126
- // Subscribe
127
- await userChannel.subscribe(async (message, ctx) => {
128
- console.log(message.payload);
129
- await ctx.ack();
130
- });
131
- ```
132
-
133
- ### Typed Channels
134
-
135
- Full TypeScript support with type inference:
136
-
137
- ```typescript
138
- interface UserPayload {
139
- userId: number;
140
- email: string;
141
- }
142
-
143
- // Typed channel
144
- const channel = herald().channel<UserPayload>("user.created");
145
-
146
- // TypeScript knows the payload type
147
- await channel.publish({ userId: 1, email: "test@example.com" });
148
-
149
- channel.subscribe(async (message, ctx) => {
150
- // message.payload is typed as UserPayload
151
- console.log(message.payload.userId);
152
- await ctx.ack();
153
- });
154
- ```
155
-
156
- ### Schema Validation with Seal
157
-
158
- Use `@warlock.js/seal` for runtime validation:
159
-
160
- ```typescript
161
- import { v } from "@warlock.js/seal";
162
-
163
- const UserSchema = v.object({
164
- userId: v.int().required(),
165
- email: v.string().email().required(),
166
- });
167
-
168
- // Channel with validation
169
- const channel = herald().channel("user.created", {
170
- schema: UserSchema,
171
- });
172
-
173
- // Publishing validates automatically
174
- await channel.publish({ userId: 1, email: "invalid" }); // Throws validation error
175
-
176
- // Subscribing receives validated data
177
- channel.subscribe(async (message, ctx) => {
178
- // message.payload is guaranteed valid
179
- await ctx.ack();
180
- });
181
- ```
182
-
183
- ## πŸ“š API Reference
184
-
185
- ### connectToBroker
186
-
187
- Connect to a message broker and register it:
188
-
189
- ```typescript
190
- const broker = await connectToBroker({
191
- driver: "rabbitmq",
192
- name: "default", // Optional, defaults to "default"
193
- isDefault: true, // Optional, defaults to true
194
- host: "localhost",
195
- port: 5672,
196
- username: "guest",
197
- password: "guest",
198
- vhost: "/",
199
- // Driver-specific options
200
- heartbeat: 60,
201
- prefetch: 10,
202
- reconnect: true,
203
- reconnectDelay: 5000,
204
- });
205
- ```
206
-
207
- ### herald(name?)
208
-
209
- Get a broker by name or the default one:
210
-
211
- ```typescript
212
- // Default broker
213
- herald().channel("events");
214
-
215
- // Named broker
216
- herald("analytics").channel("events");
217
- ```
218
-
219
- ### Channel Methods
220
-
221
- ```typescript
222
- const channel = herald().channel<MyPayload>("channel.name");
223
-
224
- // Publishing
225
- await channel.publish(payload, options?);
226
- await channel.publishBatch([payload1, payload2], options?);
227
-
228
- // Subscribing
229
- const subscription = await channel.subscribe(handler, options?);
230
- await subscription.unsubscribe();
231
- await subscription.pause();
232
-
233
- // Request-Response (RPC)
234
- const response = await channel.request<ResponseType>(payload, { timeout: 30000 });
235
- await channel.respond(async (message, ctx) => {
236
- return { result: "processed" };
237
- });
238
-
239
- // Queue management
240
- const stats = await channel.stats();
241
- await channel.purge();
242
- await channel.delete();
243
- ```
244
-
245
- ### Message Context
246
-
247
- The context object provides message flow control:
248
-
249
- ```typescript
250
- channel.subscribe(async (message, ctx) => {
251
- // Acknowledge (message processed successfully)
252
- await ctx.ack();
253
-
254
- // Negative acknowledge (requeue or dead-letter)
255
- await ctx.nack(requeue?: boolean);
256
-
257
- // Reject (don't requeue)
258
- await ctx.reject();
259
-
260
- // Reply (for RPC patterns)
261
- await ctx.reply({ result: "ok" });
262
-
263
- // Retry with delay
264
- await ctx.retry(5000);
265
- });
266
- ```
267
-
268
- ### Subscribe Options
269
-
270
- ```typescript
271
- await channel.subscribe(handler, {
272
- group: "my-consumer-group", // Consumer group/tag
273
- prefetch: 10, // Concurrency
274
- autoAck: false, // Manual ack (recommended)
275
- exclusive: false, // Exclusive consumer
276
- retry: {
277
- maxRetries: 3,
278
- delay: 1000, // Or: (attempt) => attempt * 1000
279
- },
280
- deadLetter: {
281
- channel: "channel.failed",
282
- preserveOriginal: true,
283
- },
284
- });
285
- ```
286
-
287
- ### Publish Options
288
-
289
- ```typescript
290
- await channel.publish(payload, {
291
- priority: 5, // 0-9 (9 highest)
292
- ttl: 60000, // Time-to-live in ms
293
- delay: 5000, // Delayed delivery
294
- persistent: true, // Survive broker restart
295
- correlationId: "123", // For tracking
296
- headers: { key: "value" },
297
- });
298
- ```
299
-
300
- ## πŸ”Œ Drivers
301
-
302
- ### RabbitMQ
303
-
304
- ```typescript
305
- ### RabbitMQ
306
-
307
- ```typescript
308
- await connectToBroker({
309
- driver: "rabbitmq",
310
- host: "localhost",
311
- port: 5672,
312
- username: "guest",
313
- password: "guest",
314
- vhost: "/",
315
- // Or use URI
316
- uri: "amqp://guest:guest@localhost:5672/",
317
- });
318
- ```
319
-
320
- ### Kafka (Coming Soon)
321
-
322
- ```typescript
323
- await connectToBroker({
324
- driver: "kafka",
325
- brokers: ["localhost:9092"],
326
- clientId: "my-app",
327
- ssl: true,
328
- sasl: {
329
- mechanism: "plain",
330
- username: "user",
331
- password: "pass",
332
- },
333
- });
334
- ```
335
-
336
- ## πŸ—οΈ Architecture
337
-
338
- ```
339
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
340
- β”‚ Your Application β”‚
341
- β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
342
- β”‚ herald() β†’ channel() β†’ publish() β”‚
343
- β”‚ subscribe() β”‚
344
- β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
345
- β”‚ Broker Registry β”‚
346
- β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
347
- β”‚ β”‚ default β”‚ β”‚analytics β”‚ β”‚ events β”‚ β”‚
348
- β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β”‚
349
- β”œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
350
- β”‚ β”‚ β”‚ β”‚ β”‚
351
- β”‚ β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β” β”‚
352
- β”‚ β”‚ RabbitMQ β”‚ β”‚ RabbitMQ β”‚ β”‚ Kafka β”‚ β”‚
353
- β”‚ β”‚ Driver β”‚ β”‚ Driver β”‚ β”‚ Driver β”‚ β”‚
354
- β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β”‚
355
- β”œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
356
- β”‚ β–Ό β–Ό β–Ό β”‚
357
- β”‚ RabbitMQ RabbitMQ Kafka β”‚
358
- β”‚ Broker Broker Cluster β”‚
359
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
360
- ```
361
-
362
- ## 🀝 Philosophy
363
-
364
- **Herald** is designed around three principles:
365
-
366
- 1. **Type Safety First** - Full TypeScript support with inference
367
- 2. **Simple DX** - Intuitive API that feels natural
368
- 3. **Driver Agnostic** - Same API for RabbitMQ, Kafka, and more
369
-
370
- ## πŸ“„ License
371
-
372
- MIT
373
-
374
- ---
375
-
376
- **Let your heralds carry your messages! πŸ“―βœ¨**
48
+ - A type-only import (`import type { ... } from "@warlock.js/herald"`) is allowed β€” it carries no runtime edge.
49
+ - Server loaders, controllers, and modules may import it freely.
50
+ ```
51
+
52
+ ## πŸš€ Quick Start
53
+
54
+ ```typescript
55
+ import { connectToBroker, herald } from "@warlock.js/herald";
56
+
57
+ // Connect to RabbitMQ
58
+ await connectToBroker({
59
+ driver: "rabbitmq",
60
+ host: "localhost",
61
+ port: 5672,
62
+ username: "guest",
63
+ password: "guest",
64
+ });
65
+
66
+ // Publish a message
67
+ await herald().channel("user.created").publish({
68
+ userId: 1,
69
+ email: "user@example.com",
70
+ });
71
+
72
+ // Subscribe to messages
73
+ herald()
74
+ .channel<{ userId: number; email: string }>("user.created")
75
+ .subscribe(async (message, ctx) => {
76
+ console.log("User created:", message.payload.userId);
77
+ await ctx.ack();
78
+ });
79
+ ```
80
+
81
+ ## 🎯 Core Concepts
82
+
83
+ ### Brokers
84
+
85
+ A broker wraps a message broker connection (e.g. RabbitMQ, Kafka). You can have multiple brokers for different clusters or purposes.
86
+
87
+ ```typescript
88
+ // Single broker (default)
89
+ await connectToBroker({
90
+ driver: "rabbitmq",
91
+ host: "localhost",
92
+ });
93
+
94
+ // Multiple brokers
95
+ await connectToBroker({
96
+ driver: "rabbitmq",
97
+ name: "notifications",
98
+ isDefault: true,
99
+ host: process.env.NOTIFICATIONS_HOST,
100
+ });
101
+
102
+ await connectToBroker({
103
+ driver: "rabbitmq",
104
+ name: "analytics",
105
+ host: process.env.ANALYTICS_HOST,
106
+ });
107
+
108
+ // Use default
109
+ herald().channel("notifications").publish({ ... });
110
+
111
+ // Use specific broker
112
+ herald("analytics").channel("events").publish({ ... });
113
+ ```
114
+
115
+ ### Channels
116
+
117
+ Channels represent queues (RabbitMQ) or topics (Kafka). They provide a unified pub/sub interface via `herald()`.
118
+
119
+ ```typescript
120
+ // Get a channel
121
+ const userChannel = herald().channel("user.created");
122
+
123
+ // Publish
124
+ await userChannel.publish({ userId: 1 });
125
+
126
+ // Subscribe
127
+ await userChannel.subscribe(async (message, ctx) => {
128
+ console.log(message.payload);
129
+ await ctx.ack();
130
+ });
131
+ ```
132
+
133
+ ### Typed Channels
134
+
135
+ Full TypeScript support with type inference:
136
+
137
+ ```typescript
138
+ interface UserPayload {
139
+ userId: number;
140
+ email: string;
141
+ }
142
+
143
+ // Typed channel
144
+ const channel = herald().channel<UserPayload>("user.created");
145
+
146
+ // TypeScript knows the payload type
147
+ await channel.publish({ userId: 1, email: "test@example.com" });
148
+
149
+ channel.subscribe(async (message, ctx) => {
150
+ // message.payload is typed as UserPayload
151
+ console.log(message.payload.userId);
152
+ await ctx.ack();
153
+ });
154
+ ```
155
+
156
+ ### Schema Validation with Seal
157
+
158
+ Use `@warlock.js/seal` for runtime validation:
159
+
160
+ ```typescript
161
+ import { v } from "@warlock.js/seal";
162
+
163
+ const UserSchema = v.object({
164
+ userId: v.int().required(),
165
+ email: v.string().email().required(),
166
+ });
167
+
168
+ // Channel with validation
169
+ const channel = herald().channel("user.created", {
170
+ schema: UserSchema,
171
+ });
172
+
173
+ // Publishing validates automatically
174
+ await channel.publish({ userId: 1, email: "invalid" }); // Throws validation error
175
+
176
+ // Subscribing receives validated data
177
+ channel.subscribe(async (message, ctx) => {
178
+ // message.payload is guaranteed valid
179
+ await ctx.ack();
180
+ });
181
+ ```
182
+
183
+ ## πŸ“š API Reference
184
+
185
+ ### connectToBroker
186
+
187
+ Connect to a message broker and register it:
188
+
189
+ ```typescript
190
+ const broker = await connectToBroker({
191
+ driver: "rabbitmq",
192
+ name: "default", // Optional, defaults to "default"
193
+ isDefault: true, // Optional, defaults to true
194
+ host: "localhost",
195
+ port: 5672,
196
+ username: "guest",
197
+ password: "guest",
198
+ vhost: "/",
199
+ // Driver-specific options
200
+ heartbeat: 60,
201
+ prefetch: 10,
202
+ reconnect: true,
203
+ reconnectDelay: 5000,
204
+ });
205
+ ```
206
+
207
+ ### herald(name?)
208
+
209
+ Get a broker by name or the default one:
210
+
211
+ ```typescript
212
+ // Default broker
213
+ herald().channel("events");
214
+
215
+ // Named broker
216
+ herald("analytics").channel("events");
217
+ ```
218
+
219
+ ### Channel Methods
220
+
221
+ ```typescript
222
+ const channel = herald().channel<MyPayload>("channel.name");
223
+
224
+ // Publishing
225
+ await channel.publish(payload, options?);
226
+ await channel.publishBatch([payload1, payload2], options?);
227
+
228
+ // Subscribing
229
+ const subscription = await channel.subscribe(handler, options?);
230
+ await subscription.unsubscribe();
231
+ await subscription.pause();
232
+
233
+ // Request-Response (RPC)
234
+ const response = await channel.request<ResponseType>(payload, { timeout: 30000 });
235
+ await channel.respond(async (message, ctx) => {
236
+ return { result: "processed" };
237
+ });
238
+
239
+ // Queue management
240
+ const stats = await channel.stats();
241
+ await channel.purge();
242
+ await channel.delete();
243
+ ```
244
+
245
+ ### Message Context
246
+
247
+ The context object provides message flow control:
248
+
249
+ ```typescript
250
+ channel.subscribe(async (message, ctx) => {
251
+ // Acknowledge (message processed successfully)
252
+ await ctx.ack();
253
+
254
+ // Negative acknowledge (requeue or dead-letter)
255
+ await ctx.nack(requeue?: boolean);
256
+
257
+ // Reject (don't requeue)
258
+ await ctx.reject();
259
+
260
+ // Reply (for RPC patterns)
261
+ await ctx.reply({ result: "ok" });
262
+
263
+ // Retry with delay
264
+ await ctx.retry(5000);
265
+ });
266
+ ```
267
+
268
+ ### Subscribe Options
269
+
270
+ ```typescript
271
+ await channel.subscribe(handler, {
272
+ group: "my-consumer-group", // Consumer group/tag
273
+ prefetch: 10, // Concurrency
274
+ autoAck: false, // Manual ack (recommended)
275
+ exclusive: false, // Exclusive consumer
276
+ retry: {
277
+ maxRetries: 3,
278
+ delay: 1000, // Or: (attempt) => attempt * 1000
279
+ },
280
+ deadLetter: {
281
+ channel: "channel.failed",
282
+ preserveOriginal: true,
283
+ },
284
+ });
285
+ ```
286
+
287
+ ### Publish Options
288
+
289
+ ```typescript
290
+ await channel.publish(payload, {
291
+ priority: 5, // 0-9 (9 highest)
292
+ ttl: 60000, // Time-to-live in ms
293
+ delay: 5000, // Delayed delivery
294
+ persistent: true, // Survive broker restart
295
+ correlationId: "123", // For tracking
296
+ headers: { key: "value" },
297
+ });
298
+ ```
299
+
300
+ ## πŸ”Œ Drivers
301
+
302
+ ### RabbitMQ
303
+
304
+ ```typescript
305
+ ### RabbitMQ
306
+
307
+ ```typescript
308
+ await connectToBroker({
309
+ driver: "rabbitmq",
310
+ host: "localhost",
311
+ port: 5672,
312
+ username: "guest",
313
+ password: "guest",
314
+ vhost: "/",
315
+ // Or use URI
316
+ uri: "amqp://guest:guest@localhost:5672/",
317
+ });
318
+ ```
319
+
320
+ ### Kafka (Coming Soon)
321
+
322
+ ```typescript
323
+ await connectToBroker({
324
+ driver: "kafka",
325
+ brokers: ["localhost:9092"],
326
+ clientId: "my-app",
327
+ ssl: true,
328
+ sasl: {
329
+ mechanism: "plain",
330
+ username: "user",
331
+ password: "pass",
332
+ },
333
+ });
334
+ ```
335
+
336
+ ## πŸ—οΈ Architecture
337
+
338
+ ```
339
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
340
+ β”‚ Your Application β”‚
341
+ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
342
+ β”‚ herald() β†’ channel() β†’ publish() β”‚
343
+ β”‚ subscribe() β”‚
344
+ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
345
+ β”‚ Broker Registry β”‚
346
+ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
347
+ β”‚ β”‚ default β”‚ β”‚analytics β”‚ β”‚ events β”‚ β”‚
348
+ β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β”‚
349
+ β”œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
350
+ β”‚ β”‚ β”‚ β”‚ β”‚
351
+ β”‚ β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β” β”‚
352
+ β”‚ β”‚ RabbitMQ β”‚ β”‚ RabbitMQ β”‚ β”‚ Kafka β”‚ β”‚
353
+ β”‚ β”‚ Driver β”‚ β”‚ Driver β”‚ β”‚ Driver β”‚ β”‚
354
+ β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β”‚
355
+ β”œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
356
+ β”‚ β–Ό β–Ό β–Ό β”‚
357
+ β”‚ RabbitMQ RabbitMQ Kafka β”‚
358
+ β”‚ Broker Broker Cluster β”‚
359
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
360
+ ```
361
+
362
+ ## 🀝 Philosophy
363
+
364
+ **Herald** is designed around three principles:
365
+
366
+ 1. **Type Safety First** - Full TypeScript support with inference
367
+ 2. **Simple DX** - Intuitive API that feels natural
368
+ 3. **Driver Agnostic** - Same API for RabbitMQ, Kafka, and more
369
+
370
+ ## πŸ“„ License
371
+
372
+ MIT
373
+
374
+ ---
375
+
376
+ **Let your heralds carry your messages! πŸ“―βœ¨**