@warlock.js/herald 5.24.0 β 5.25.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/CHANGELOG.md +6 -0
- package/README.md +375 -375
- package/cjs/index.cjs.map +1 -1
- package/esm/communicators/broker-registry.mjs.map +1 -1
- package/esm/communicators/broker.mjs.map +1 -1
- package/esm/drivers/rabbitmq/rabbitmq-channel.mjs.map +1 -1
- package/esm/drivers/rabbitmq/rabbitmq-driver.mjs.map +1 -1
- package/esm/message-managers/prepare-consumer-subscription.mjs.map +1 -1
- package/esm/utils/connect-to-broker.mjs.map +1 -1
- package/llms-full.txt +465 -465
- package/package.json +3 -3
- package/skills/consume-message/SKILL.md +219 -219
- package/skills/publish-message/SKILL.md +172 -172
- package/skills/request-and-respond/SKILL.md +74 -74
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! π―β¨**
|