@arbitro/client 0.2.0 → 0.5.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
@@ -41,14 +41,14 @@ npm install @arbitro/client
41
41
  The broker ships as a public Docker image (musl-static, ~3 MB, scratch base):
42
42
 
43
43
  ```bash
44
- docker run --rm -p 9898:9898 ghcr.io/arbitro-io/arbitro-server:0.1.0
44
+ docker run --rm -p 9898:9898 ghcr.io/arbitro-io/arbitro-server:latest
45
45
  ```
46
46
 
47
47
  Pin a major/minor tag for production:
48
48
 
49
- - `ghcr.io/arbitro-io/arbitro-server:0.1.0` — immutable, recommended for prod
49
+ - `ghcr.io/arbitro-io/arbitro-server:0.1.0` — immutable release tag
50
50
  - `ghcr.io/arbitro-io/arbitro-server:0.1` — auto-updates within `0.1.*`
51
- - `ghcr.io/arbitro-io/arbitro-server:latest` — last tagged release
51
+ - `ghcr.io/arbitro-io/arbitro-server:latest` — latest tagged release
52
52
 
53
53
  ## Quick start
54
54
 
@@ -219,33 +219,111 @@ const sub = await client
219
219
 
220
220
  `zod` is an optional peer dependency. `@arbitro/client` references zod only via `import type`, so users who never call `zodCodec` pay zero runtime cost and don't need zod installed.
221
221
 
222
- ## Reconnect behavior
222
+ ## Cron Scheduling
223
223
 
224
- The TS client reconnects transport automatically and reattaches active subscriptions after reconnect. That behavior lives in the client, not in the benchmarks. This matters for:
224
+ Register distributed cron jobs with queue semantics multiple workers, single delivery per fire.
225
225
 
226
- - Docker restarts
227
- - broker failover tests
228
- - chaos scenarios with durable consumers
226
+ ```typescript
227
+ const cron = await client.cron("billing-monthly")
228
+ .every("0 0 1 * *")
229
+ .tz("America/New_York")
230
+ .run(async (ctx) => {
231
+ console.log(`fire #${ctx.fireCount} at ${ctx.fireTime}`);
232
+ await processBilling();
233
+ });
234
+
235
+ // Stop when done
236
+ await cron.stop();
237
+ ```
229
238
 
230
- ## Benchmarks
239
+ Crons re-register automatically on reconnect. No persistence — if the broker restarts, clients re-register their crons when they reconnect.
231
240
 
232
- Three primary bench families:
241
+ ## Delayed Publish
233
242
 
234
- | File | npm script | What it measures |
235
- |---|---|---|
236
- | `benches/throughput.ts` | `npm run bench` | Publish + delivery throughput across modes (fire-and-forget, batch, sync, replay-ack, replay-noack). |
237
- | `benches/limits.ts` | `npm run bench:limit` | Behaviour under `maxSubjectInflights` saturation. |
238
- | `benches/chaos.ts` | `npm run bench:chaos` | Restart / reconnect / persistence under failure injection. |
243
+ Schedule message delivery for the future:
239
244
 
240
- Examples:
245
+ ```typescript
246
+ await client.publishDelayed("ORDERS", "orders.reminder", payload, 5000); // 5s delay
247
+ ```
241
248
 
242
- ```bash
243
- npx tsx benches/throughput.ts --mode fire-and-forget --msgs 20000
244
- npx tsx benches/throughput.ts --mode perf --seconds 10 --rate 20000 --container arbitro-server
245
- npx tsx benches/limits.ts
246
- npx tsx benches/chaos.ts --duration 8 --rate 50 --container arbitro-server
249
+ Messages are persisted immediately — survives broker restart.
250
+
251
+ ## Workflow Orchestration
252
+
253
+ Client-side linear pipelines over Arbitro streams. The broker has no workflow-specific code -- everything uses streams, consumer groups, and idempotent publish.
254
+
255
+ ### WorkflowBuilder API
256
+
257
+ | Method | Signature | Description |
258
+ |--------|-----------|-------------|
259
+ | `trigger` | `(subject: string) => this` | Subject pattern that triggers new instances. Required. |
260
+ | `triggerStream` | *(not yet implemented)* | Planned: auto-subscribe to an external stream for trigger. |
261
+ | `step` | `(name: string, handler: StepHandler) => this` | Append a processing step. |
262
+ | `compensate` | *(not yet implemented)* | Planned: rollback handler per step (saga pattern). |
263
+ | `maxRetries` | *(not yet implemented)* | Planned: attempts before DLQ. |
264
+ | `maxContextSize` | *(not yet implemented)* | Planned: max context payload in bytes. |
265
+ | `ackWait` | `(ms: number) => this` | Ack timeout for failover (default: 30000). |
266
+ | `inflight` | `(n: number) => this` | Concurrent tasks per worker (default: 10). |
267
+ | `start` | `() => Promise<WorkflowHandle>` | Register streams, consumer, and start processing. |
268
+
269
+ ### WorkflowHandle API
270
+
271
+ | Method | Signature | Description |
272
+ |--------|-----------|-------------|
273
+ | `trigger` | `(client: ArbitroClient, context: Buffer) => Promise<number>` | Trigger a new workflow instance. Returns the instance ID. |
274
+ | `name` | `string` (getter) | Workflow name. |
275
+
276
+ ### Complete Example
277
+
278
+ ```typescript
279
+ import { ArbitroClient, WorkflowBuilder } from '@arbitro/client'
280
+ import type { StepContext, StepResult } from '@arbitro/client'
281
+
282
+ const client = new ArbitroClient({ servers: ['127.0.0.1:9898'] })
283
+ await client.connect()
284
+
285
+ const wf = await new WorkflowBuilder(client, 'order-process')
286
+ .trigger('orders.created')
287
+ // Step 1: validate
288
+ .step('validate', async (ctx: StepContext): Promise<StepResult> => {
289
+ const validated = await validateOrder(ctx.context)
290
+ return { context: validated }
291
+ })
292
+ // Step 2: charge
293
+ .step('charge', async (ctx: StepContext): Promise<StepResult> => {
294
+ const receipt = await chargePayment(ctx.context)
295
+ return { context: receipt }
296
+ })
297
+ // Step 3: ship
298
+ .step('ship', async (ctx: StepContext): Promise<StepResult> => {
299
+ const tracking = await createShipment(ctx.context)
300
+ return { context: tracking }
301
+ })
302
+ .ackWait(30_000)
303
+ .inflight(10)
304
+ .start()
305
+
306
+ // Manual trigger
307
+ const instanceId = await wf.trigger(client, Buffer.from('order-123-payload'))
308
+ console.log(`started instance ${instanceId}`)
247
309
  ```
248
310
 
311
+ ### Internals
312
+
313
+ - Tasks flow through `_wf.{name}.tasks` stream with a consumer `_wf.{name}.workers`.
314
+ - Each step transition publishes with `msgId` format `wf:{instance}:{step}:{attempt}` for deduplication.
315
+ - `ackWait` enables failover: if a worker dies, the broker redelivers to another subscriber.
316
+
317
+ > **Note:** The TypeScript workflow module currently implements the core step pipeline. Compensation (saga), DLQ, `triggerStream`, `maxRetries`, and `maxContextSize` are available in the Rust client and planned for the TS client.
318
+
319
+ ## Reconnect behavior
320
+
321
+ The TS client reconnects transport automatically and reattaches active subscriptions and cron jobs after reconnect. That behavior lives in the client, not in the benchmarks. This matters for:
322
+
323
+ - Docker restarts
324
+ - broker failover tests
325
+ - chaos scenarios with durable consumers
326
+
249
327
  ## Validation
250
328
 
251
329
  ```bash
@@ -0,0 +1,102 @@
1
+ // src/proto/constants.ts
2
+ var MAGIC_V2 = 843207233;
3
+ var HELLO_SIZE = 8;
4
+ var CURRENT_VERSION = 2;
5
+ var Role = /* @__PURE__ */ ((Role2) => {
6
+ Role2[Role2["Client"] = 0] = "Client";
7
+ Role2[Role2["Server"] = 1] = "Server";
8
+ return Role2;
9
+ })(Role || {});
10
+ var Cap = /* @__PURE__ */ ((Cap2) => {
11
+ Cap2[Cap2["Headers"] = 1] = "Headers";
12
+ Cap2[Cap2["Reply"] = 2] = "Reply";
13
+ Cap2[Cap2["BatchHeaders"] = 4] = "BatchHeaders";
14
+ Cap2[Cap2["CompressedPayload"] = 8] = "CompressedPayload";
15
+ return Cap2;
16
+ })(Cap || {});
17
+ var HEADER_SIZE = 16;
18
+ var OFF_ACTION = 0;
19
+ var OFF_FLAGS = 2;
20
+ var OFF_ENTRY_FLAGS = 3;
21
+ var OFF_MSG_LEN = 4;
22
+ var OFF_SEQ = 8;
23
+ var Flag = /* @__PURE__ */ ((Flag2) => {
24
+ Flag2[Flag2["None"] = 0] = "None";
25
+ Flag2[Flag2["AckReq"] = 1] = "AckReq";
26
+ Flag2[Flag2["Dup"] = 2] = "Dup";
27
+ Flag2[Flag2["PriorityHigh"] = 4] = "PriorityHigh";
28
+ return Flag2;
29
+ })(Flag || {});
30
+ var EntryFlag = /* @__PURE__ */ ((EntryFlag2) => {
31
+ EntryFlag2[EntryFlag2["None"] = 0] = "None";
32
+ EntryFlag2[EntryFlag2["Retain"] = 1] = "Retain";
33
+ EntryFlag2[EntryFlag2["Compressed"] = 2] = "Compressed";
34
+ EntryFlag2[EntryFlag2["NoBackpressure"] = 4] = "NoBackpressure";
35
+ return EntryFlag2;
36
+ })(EntryFlag || {});
37
+ var Action = /* @__PURE__ */ ((Action2) => {
38
+ Action2[Action2["Hello"] = 1] = "Hello";
39
+ Action2[Action2["Auth"] = 2] = "Auth";
40
+ Action2[Action2["Publish"] = 257] = "Publish";
41
+ Action2[Action2["PublishAccumulate"] = 258] = "PublishAccumulate";
42
+ Action2[Action2["PublishBatch"] = 259] = "PublishBatch";
43
+ Action2[Action2["PublishWithReply"] = 260] = "PublishWithReply";
44
+ Action2[Action2["PublishWithHeaders"] = 261] = "PublishWithHeaders";
45
+ Action2[Action2["PublishBatchWithHeaders"] = 262] = "PublishBatchWithHeaders";
46
+ Action2[Action2["Deliver"] = 512] = "Deliver";
47
+ Action2[Action2["Ack"] = 513] = "Ack";
48
+ Action2[Action2["Nack"] = 514] = "Nack";
49
+ Action2[Action2["RepOk"] = 515] = "RepOk";
50
+ Action2[Action2["RepError"] = 516] = "RepError";
51
+ Action2[Action2["RepBatch"] = 517] = "RepBatch";
52
+ Action2[Action2["BatchAck"] = 518] = "BatchAck";
53
+ Action2[Action2["FanoutBatch"] = 519] = "FanoutBatch";
54
+ Action2[Action2["AckSync"] = 520] = "AckSync";
55
+ Action2[Action2["BatchAckSync"] = 521] = "BatchAckSync";
56
+ Action2[Action2["BatchNack"] = 522] = "BatchNack";
57
+ Action2[Action2["Subscribe"] = 769] = "Subscribe";
58
+ Action2[Action2["Unsubscribe"] = 770] = "Unsubscribe";
59
+ Action2[Action2["CreateStream"] = 1025] = "CreateStream";
60
+ Action2[Action2["DeleteStream"] = 1026] = "DeleteStream";
61
+ Action2[Action2["GetStream"] = 1027] = "GetStream";
62
+ Action2[Action2["ListStreams"] = 1028] = "ListStreams";
63
+ Action2[Action2["PurgeStream"] = 1029] = "PurgeStream";
64
+ Action2[Action2["DrainSubject"] = 1030] = "DrainSubject";
65
+ Action2[Action2["CreateConsumer"] = 1281] = "CreateConsumer";
66
+ Action2[Action2["DeleteConsumer"] = 1282] = "DeleteConsumer";
67
+ Action2[Action2["GetConsumer"] = 1283] = "GetConsumer";
68
+ Action2[Action2["ListConsumers"] = 1284] = "ListConsumers";
69
+ Action2[Action2["ConsumerStats"] = 1285] = "ConsumerStats";
70
+ Action2[Action2["PauseConsumer"] = 1286] = "PauseConsumer";
71
+ Action2[Action2["ResumeConsumer"] = 1287] = "ResumeConsumer";
72
+ Action2[Action2["PublishDelayed"] = 2049] = "PublishDelayed";
73
+ Action2[Action2["Ping"] = 1537] = "Ping";
74
+ Action2[Action2["Pong"] = 1538] = "Pong";
75
+ Action2[Action2["Connect"] = 1539] = "Connect";
76
+ Action2[Action2["Connected"] = 1540] = "Connected";
77
+ Action2[Action2["Disconnect"] = 1541] = "Disconnect";
78
+ Action2[Action2["CreateCron"] = 1793] = "CreateCron";
79
+ Action2[Action2["DeleteCron"] = 1794] = "DeleteCron";
80
+ Action2[Action2["ListCrons"] = 1795] = "ListCrons";
81
+ Action2[Action2["CronFire"] = 1796] = "CronFire";
82
+ Action2[Action2["CronAck"] = 1797] = "CronAck";
83
+ return Action2;
84
+ })(Action || {});
85
+
86
+ export {
87
+ MAGIC_V2,
88
+ HELLO_SIZE,
89
+ CURRENT_VERSION,
90
+ Role,
91
+ Cap,
92
+ HEADER_SIZE,
93
+ OFF_ACTION,
94
+ OFF_FLAGS,
95
+ OFF_ENTRY_FLAGS,
96
+ OFF_MSG_LEN,
97
+ OFF_SEQ,
98
+ Flag,
99
+ EntryFlag,
100
+ Action
101
+ };
102
+ //# sourceMappingURL=chunk-C2QLJBAC.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/proto/constants.ts"],"sourcesContent":["// V2 wire protocol constants — must match arbitro-proto/src/action.rs exactly.\n\n// ── Hello handshake (only frame without a Header) ───────────────────────\nexport const MAGIC_V2 = 0x32425241 // \"ARB2\" as u32 LE\nexport const HELLO_SIZE = 8\nexport const CURRENT_VERSION = 2\n\nexport const enum Role {\n Client = 0,\n Server = 1,\n}\n\nexport const enum Cap {\n Headers = 1 << 0,\n Reply = 1 << 1,\n BatchHeaders = 1 << 2,\n CompressedPayload = 1 << 3,\n}\n\n// ── Header (16 bytes, every frame after Hello) ──────────────────────────\nexport const HEADER_SIZE = 16\n\n// Byte offsets within the 16-byte header\nexport const OFF_ACTION = 0 // u16 LE\nexport const OFF_FLAGS = 2 // u8\nexport const OFF_ENTRY_FLAGS = 3 // u8\nexport const OFF_MSG_LEN = 4 // u32 LE\nexport const OFF_SEQ = 8 // u64 LE\n\n// ── Transport flags (header.flags, offset 2) ────────────────────────────\nexport const enum Flag {\n None = 0x00,\n AckReq = 0x01,\n Dup = 0x02,\n PriorityHigh = 0x04,\n}\n\n// ── Per-message flags (header.entry_flags, offset 3) ────────────────────\nexport const enum EntryFlag {\n None = 0x00,\n Retain = 0x01,\n Compressed = 0x02,\n NoBackpressure = 0x04,\n}\n\n// ── Action codes (0xFFGG: FF=family, GG=variant) ────────────────────────\nexport const enum Action {\n // 0x00xx — Handshake / control\n Hello = 0x0001,\n Auth = 0x0002,\n\n // 0x01xx — Publish family\n Publish = 0x0101,\n PublishAccumulate = 0x0102,\n PublishBatch = 0x0103,\n PublishWithReply = 0x0104,\n PublishWithHeaders = 0x0105,\n PublishBatchWithHeaders = 0x0106,\n\n // 0x02xx — Delivery / Ack\n Deliver = 0x0200,\n Ack = 0x0201,\n Nack = 0x0202,\n RepOk = 0x0203,\n RepError = 0x0204,\n RepBatch = 0x0205,\n BatchAck = 0x0206,\n FanoutBatch = 0x0207,\n AckSync = 0x0208,\n BatchAckSync = 0x0209,\n BatchNack = 0x020A,\n\n // 0x03xx — Subscription\n Subscribe = 0x0301,\n Unsubscribe = 0x0302,\n\n // 0x04xx — Stream management\n CreateStream = 0x0401,\n DeleteStream = 0x0402,\n GetStream = 0x0403,\n ListStreams = 0x0404,\n PurgeStream = 0x0405,\n DrainSubject = 0x0406,\n\n // 0x05xx — Consumer management\n CreateConsumer = 0x0501,\n DeleteConsumer = 0x0502,\n GetConsumer = 0x0503,\n ListConsumers = 0x0504,\n ConsumerStats = 0x0505,\n PauseConsumer = 0x0506,\n ResumeConsumer = 0x0507,\n\n // 0x08xx — Delayed publish\n PublishDelayed = 0x0801,\n\n // 0x06xx — System\n Ping = 0x0601,\n Pong = 0x0602,\n Connect = 0x0603,\n Connected = 0x0604,\n Disconnect = 0x0605,\n\n // 0x07xx — Cron scheduling\n CreateCron = 0x0701,\n DeleteCron = 0x0702,\n ListCrons = 0x0703,\n CronFire = 0x0704,\n CronAck = 0x0705,\n\n // 0x09xx reserved (workflow removed — now a client-side library).\n}\n"],"mappings":"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","names":["Role","Cap","Flag","EntryFlag","Action"]}
@@ -0,0 +1,124 @@
1
+ import {
2
+ CURRENT_VERSION,
3
+ HEADER_SIZE,
4
+ HELLO_SIZE,
5
+ MAGIC_V2,
6
+ OFF_ACTION,
7
+ OFF_ENTRY_FLAGS,
8
+ OFF_FLAGS,
9
+ OFF_MSG_LEN,
10
+ OFF_SEQ
11
+ } from "./chunk-C2QLJBAC.mjs";
12
+
13
+ // src/proto/frame.ts
14
+ function frame(action, seq, bodyLen, flags = 0, entryFlags = 0) {
15
+ const buf = Buffer.allocUnsafe(HEADER_SIZE + bodyLen);
16
+ buf.writeUInt16LE(action, OFF_ACTION);
17
+ buf[OFF_FLAGS] = flags;
18
+ buf[OFF_ENTRY_FLAGS] = entryFlags;
19
+ buf.writeUInt32LE(bodyLen, OFF_MSG_LEN);
20
+ buf.writeBigUInt64LE(seq, OFF_SEQ);
21
+ return buf;
22
+ }
23
+ function packHello(caps = 2 /* Reply */) {
24
+ const buf = Buffer.allocUnsafe(HELLO_SIZE);
25
+ buf.writeUInt32LE(MAGIC_V2, 0);
26
+ buf[4] = CURRENT_VERSION;
27
+ buf[5] = 0 /* Client */;
28
+ buf.writeUInt16LE(caps, 6);
29
+ return buf;
30
+ }
31
+ function packDisconnect(seq) {
32
+ return frame(1541 /* Disconnect */, seq, 0);
33
+ }
34
+
35
+ // src/proto/publish.ts
36
+ var EMPTY = Buffer.alloc(0);
37
+ function packPublish(seq, streamId, subject, payload, flags = 0, entryFlags = 0, msgId = EMPTY) {
38
+ const buf = frame(
39
+ 257 /* Publish */,
40
+ seq,
41
+ 8 + subject.length + msgId.length + payload.length,
42
+ flags,
43
+ entryFlags
44
+ );
45
+ buf.writeUInt32LE(streamId, HEADER_SIZE);
46
+ buf.writeUInt16LE(subject.length, HEADER_SIZE + 4);
47
+ buf.writeUInt16LE(msgId.length, HEADER_SIZE + 6);
48
+ let off = HEADER_SIZE + 8;
49
+ subject.copy(buf, off);
50
+ off += subject.length;
51
+ msgId.copy(buf, off);
52
+ off += msgId.length;
53
+ payload.copy(buf, off);
54
+ return buf;
55
+ }
56
+ function packPublishWithReply(seq, streamId, subject, replyTo, payload, flags = 0, entryFlags = 0) {
57
+ const tail = subject.length + replyTo.length + payload.length;
58
+ const buf = frame(260 /* PublishWithReply */, seq, 12 + tail, flags, entryFlags);
59
+ buf.writeUInt32LE(streamId, HEADER_SIZE);
60
+ buf.writeUInt16LE(subject.length, HEADER_SIZE + 4);
61
+ buf.writeUInt16LE(replyTo.length, HEADER_SIZE + 6);
62
+ buf.writeUInt32LE(0, HEADER_SIZE + 8);
63
+ let off = HEADER_SIZE + 12;
64
+ subject.copy(buf, off);
65
+ off += subject.length;
66
+ replyTo.copy(buf, off);
67
+ off += replyTo.length;
68
+ payload.copy(buf, off);
69
+ return buf;
70
+ }
71
+ function packPublishDelayed(seq, streamId, subject, payload, delayMs, flags = 0, entryFlags = 0) {
72
+ const buf = frame(
73
+ 2049 /* PublishDelayed */,
74
+ seq,
75
+ 16 + subject.length + payload.length,
76
+ flags,
77
+ entryFlags
78
+ );
79
+ buf.writeUInt32LE(streamId, HEADER_SIZE);
80
+ buf.writeUInt16LE(subject.length, HEADER_SIZE + 4);
81
+ buf.writeUInt16LE(0, HEADER_SIZE + 6);
82
+ buf.writeBigUInt64LE(delayMs, HEADER_SIZE + 8);
83
+ let off = HEADER_SIZE + 16;
84
+ subject.copy(buf, off);
85
+ off += subject.length;
86
+ payload.copy(buf, off);
87
+ return buf;
88
+ }
89
+ function packPublishBatch(seq, streamId, entries, flags = 0, entryFlags = 0) {
90
+ let tail = 0;
91
+ for (const e of entries) {
92
+ const midLen = e.msgId ? e.msgId.length : 0;
93
+ tail += 8 + e.subject.length + midLen + e.payload.length;
94
+ }
95
+ const buf = frame(259 /* PublishBatch */, seq, 8 + tail, flags, entryFlags);
96
+ buf.writeUInt32LE(streamId, HEADER_SIZE);
97
+ buf.writeUInt32LE(entries.length, HEADER_SIZE + 4);
98
+ let off = HEADER_SIZE + 8;
99
+ for (const e of entries) {
100
+ const mid = e.msgId ?? EMPTY;
101
+ buf.writeUInt16LE(e.subject.length, off);
102
+ buf.writeUInt16LE(mid.length, off + 2);
103
+ buf.writeUInt32LE(e.payload.length, off + 4);
104
+ off += 8;
105
+ buf.write(e.subject, off);
106
+ off += e.subject.length;
107
+ mid.copy(buf, off);
108
+ off += mid.length;
109
+ e.payload.copy(buf, off);
110
+ off += e.payload.length;
111
+ }
112
+ return buf;
113
+ }
114
+
115
+ export {
116
+ frame,
117
+ packHello,
118
+ packDisconnect,
119
+ packPublish,
120
+ packPublishWithReply,
121
+ packPublishDelayed,
122
+ packPublishBatch
123
+ };
124
+ //# sourceMappingURL=chunk-GW36GP2C.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/proto/frame.ts","../src/proto/publish.ts"],"sourcesContent":["// Core frame allocator — single source for header layout.\n\nimport {\n HEADER_SIZE, HELLO_SIZE, MAGIC_V2, CURRENT_VERSION,\n OFF_ACTION, OFF_FLAGS, OFF_ENTRY_FLAGS, OFF_MSG_LEN, OFF_SEQ,\n Action, Role, Cap,\n} from './constants'\n\n/** Allocate HEADER_SIZE + bodyLen, write the 16-byte header, return buf. */\nexport function frame(\n action: Action, seq: bigint, bodyLen: number,\n flags = 0, entryFlags = 0,\n): Buffer {\n const buf = Buffer.allocUnsafe(HEADER_SIZE + bodyLen)\n buf.writeUInt16LE(action, OFF_ACTION)\n buf[OFF_FLAGS] = flags\n buf[OFF_ENTRY_FLAGS] = entryFlags\n buf.writeUInt32LE(bodyLen, OFF_MSG_LEN)\n buf.writeBigUInt64LE(seq, OFF_SEQ)\n return buf\n}\n\n/** Hello handshake — 8 bytes, no Header prefix. */\nexport function packHello(caps: number = Cap.Reply): Buffer {\n const buf = Buffer.allocUnsafe(HELLO_SIZE)\n buf.writeUInt32LE(MAGIC_V2, 0)\n buf[4] = CURRENT_VERSION\n buf[5] = Role.Client\n buf.writeUInt16LE(caps, 6)\n return buf\n}\n\nexport function packPing(seq: bigint): Buffer {\n return frame(Action.Ping, seq, 0)\n}\n\nexport function packDisconnect(seq: bigint): Buffer {\n return frame(Action.Disconnect, seq, 0)\n}\n","// Publish family — Publish (0x0101), PublishWithReply (0x0104), PublishBatch (0x0103).\n\nimport { HEADER_SIZE, Action } from './constants'\nimport { frame } from './frame'\n\n/**\n * One entry of a `publishBatch` call.\n *\n * `msgId` (optional) is an opaque byte string the broker uses for\n * per-stream deduplication when the target stream was created with\n * `idempotencyWindowMs > 0`. Leaving it empty/undefined disables\n * dedup for this entry (mixing dedup + non-dedup entries in a single\n * batch is allowed).\n */\nexport interface BatchPublishEntry {\n subject: string\n payload: Buffer\n msgId?: Buffer\n}\n\nconst EMPTY = Buffer.alloc(0)\n\n// Body: stream_id(4) + subject_len(2) + msg_id_len(2) = 8B + subject + msg_id + payload\nexport function packPublish(\n seq: bigint, streamId: number,\n subject: Buffer, payload: Buffer,\n flags = 0, entryFlags = 0,\n msgId: Buffer = EMPTY,\n): Buffer {\n const buf = frame(\n Action.Publish, seq,\n 8 + subject.length + msgId.length + payload.length,\n flags, entryFlags,\n )\n buf.writeUInt32LE(streamId, HEADER_SIZE)\n buf.writeUInt16LE(subject.length, HEADER_SIZE + 4)\n buf.writeUInt16LE(msgId.length, HEADER_SIZE + 6)\n let off = HEADER_SIZE + 8\n subject.copy(buf, off); off += subject.length\n msgId.copy(buf, off); off += msgId.length\n payload.copy(buf, off)\n return buf\n}\n\n// Body: stream_id(4) + subject_len(2) + reply_len(2) + _pad(4) = 12B\nexport function packPublishWithReply(\n seq: bigint, streamId: number,\n subject: Buffer, replyTo: Buffer, payload: Buffer,\n flags = 0, entryFlags = 0,\n): Buffer {\n const tail = subject.length + replyTo.length + payload.length\n const buf = frame(Action.PublishWithReply, seq, 12 + tail, flags, entryFlags)\n buf.writeUInt32LE(streamId, HEADER_SIZE)\n buf.writeUInt16LE(subject.length, HEADER_SIZE + 4)\n buf.writeUInt16LE(replyTo.length, HEADER_SIZE + 6)\n buf.writeUInt32LE(0, HEADER_SIZE + 8)\n let off = HEADER_SIZE + 12\n subject.copy(buf, off); off += subject.length\n replyTo.copy(buf, off); off += replyTo.length\n payload.copy(buf, off)\n return buf\n}\n\n// Body: stream_id(4) + subject_len(2) + msg_id_len(2) + delay_ms(8) = 16B + subject + msg_id + payload\nexport function packPublishDelayed(\n seq: bigint, streamId: number,\n subject: Buffer, payload: Buffer,\n delayMs: bigint,\n flags = 0, entryFlags = 0,\n): Buffer {\n const buf = frame(\n Action.PublishDelayed, seq,\n 16 + subject.length + payload.length,\n flags, entryFlags,\n )\n buf.writeUInt32LE(streamId, HEADER_SIZE)\n buf.writeUInt16LE(subject.length, HEADER_SIZE + 4)\n buf.writeUInt16LE(0, HEADER_SIZE + 6) // msg_id_len = 0\n buf.writeBigUInt64LE(delayMs, HEADER_SIZE + 8)\n let off = HEADER_SIZE + 16\n subject.copy(buf, off); off += subject.length\n payload.copy(buf, off)\n return buf\n}\n\n// Body: stream_id(4) + count(4) = 8B\n// + entries[subject_len(2)+msg_id_len(2)+payload_len(4) + subject + msg_id + payload]\nexport function packPublishBatch(\n seq: bigint, streamId: number,\n entries: ReadonlyArray<BatchPublishEntry>,\n flags = 0, entryFlags = 0,\n): Buffer {\n let tail = 0\n for (const e of entries) {\n const midLen = e.msgId ? e.msgId.length : 0\n tail += 8 + e.subject.length + midLen + e.payload.length\n }\n const buf = frame(Action.PublishBatch, seq, 8 + tail, flags, entryFlags)\n buf.writeUInt32LE(streamId, HEADER_SIZE)\n buf.writeUInt32LE(entries.length, HEADER_SIZE + 4)\n let off = HEADER_SIZE + 8\n for (const e of entries) {\n const mid = e.msgId ?? EMPTY\n buf.writeUInt16LE(e.subject.length, off)\n buf.writeUInt16LE(mid.length, off + 2)\n buf.writeUInt32LE(e.payload.length, off + 4)\n off += 8\n buf.write(e.subject, off); off += e.subject.length\n mid.copy(buf, off); off += mid.length\n e.payload.copy(buf, off); off += e.payload.length\n }\n return buf\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,33 @@
1
+ import {
2
+ Action,
3
+ CURRENT_VERSION,
4
+ Cap,
5
+ EntryFlag,
6
+ Flag,
7
+ HEADER_SIZE,
8
+ HELLO_SIZE,
9
+ MAGIC_V2,
10
+ OFF_ACTION,
11
+ OFF_ENTRY_FLAGS,
12
+ OFF_FLAGS,
13
+ OFF_MSG_LEN,
14
+ OFF_SEQ,
15
+ Role
16
+ } from "./chunk-C2QLJBAC.mjs";
17
+ export {
18
+ Action,
19
+ CURRENT_VERSION,
20
+ Cap,
21
+ EntryFlag,
22
+ Flag,
23
+ HEADER_SIZE,
24
+ HELLO_SIZE,
25
+ MAGIC_V2,
26
+ OFF_ACTION,
27
+ OFF_ENTRY_FLAGS,
28
+ OFF_FLAGS,
29
+ OFF_MSG_LEN,
30
+ OFF_SEQ,
31
+ Role
32
+ };
33
+ //# sourceMappingURL=constants-57DO6N3H.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/dist/index.d.mts CHANGED
@@ -86,6 +86,8 @@ interface SubjectInflightLimit {
86
86
  }
87
87
  interface ConsumerConfig {
88
88
  name?: string;
89
+ /** Shared consumer group name for round-robin delivery. Defaults to `name`. */
90
+ group?: string;
89
91
  filter?: string;
90
92
  fanout?: boolean;
91
93
  /** Consumer-side ACK policy. None = fire-and-forget delivery, Explicit = consumer must ACK. */
@@ -220,6 +222,35 @@ declare class ClientMetrics {
220
222
  snapshot(): ClientMetricsSnapshot;
221
223
  }
222
224
 
225
+ interface CreateCronBody {
226
+ readonly name: string;
227
+ readonly every: string;
228
+ readonly tz?: string | undefined;
229
+ readonly timeout_ms: number;
230
+ readonly overlap: boolean;
231
+ }
232
+
233
+ type CronHandler = (ctx: CronContext) => Promise<void>;
234
+ /** Context passed to the cron handler on each fire. */
235
+ interface CronContext {
236
+ /** Cron job name. */
237
+ readonly name: string;
238
+ /** UTC timestamp (ms since epoch) when the broker intended this fire. */
239
+ readonly fireTime: bigint;
240
+ /** Monotonic fire counter (1-based). */
241
+ readonly fireCount: bigint;
242
+ }
243
+ declare class CronState {
244
+ private readonly handlers;
245
+ register(name: string, config: CreateCronBody, handler: CronHandler): void;
246
+ remove(name: string): void;
247
+ getHandler(name: string): CronHandler | undefined;
248
+ allConfigs(): ReadonlyArray<{
249
+ name: string;
250
+ config: CreateCronBody;
251
+ }>;
252
+ }
253
+
223
254
  type DeliveryHandler = (frame: Buffer) => void;
224
255
  declare class Connection {
225
256
  private readonly reconnectAddr?;
@@ -234,6 +265,7 @@ declare class Connection {
234
265
  private readonly log;
235
266
  private activeSubs;
236
267
  private metrics?;
268
+ private cronState?;
237
269
  private constructor();
238
270
  private attachSocket;
239
271
  static connect(addr: string, timeoutMs?: number, tlsCfg?: TlsConfig, reconnectCfg?: ReconnectConfig, logger?: Logger): Promise<Connection>;
@@ -244,13 +276,17 @@ declare class Connection {
244
276
  * entry and `reconnects` on successful reconnections. Unset = no-op.
245
277
  */
246
278
  setMetrics(m: ClientMetrics): void;
279
+ /** Attach cron state so the connection can dispatch CronFire frames. */
280
+ setCronState(s: CronState): void;
247
281
  private resolvePending;
248
282
  private rejectPending;
249
283
  private onFrame;
250
284
  private handleBatchDeliver;
285
+ private dispatchCronFire;
251
286
  sendSubscribeV2(consumerId: number, filter: Buffer, handler: DeliveryHandler, onRenew?: (newConsumerId: number) => void): Promise<number>;
252
287
  cancelSubscription(consumerId: number): void;
253
288
  private resubscribeAll;
289
+ private replayCrons;
254
290
  registerRoute(consumerId: number, handler: DeliveryHandler): void;
255
291
  unregisterRoute(consumerId: number): void;
256
292
  send(frame: Buffer): void;
@@ -431,6 +467,30 @@ declare class Consumer {
431
467
  subscribe<T extends Record<string, unknown>>(codec: Encoding<T>, cb: (msg: LazyMessage<T>) => void, opts?: SubscribeOptions): Promise<Subscription>;
432
468
  }
433
469
 
470
+ declare class CronBuilder {
471
+ private readonly conn;
472
+ private readonly cronState;
473
+ private readonly cronName;
474
+ private expr;
475
+ private timezone;
476
+ private timeoutMs;
477
+ private allowOverlap;
478
+ constructor(conn: Connection, cronState: CronState, cronName: string);
479
+ every(expression: string): this;
480
+ tz(timezone: string): this;
481
+ timeout(ms: number): this;
482
+ overlap(allow: boolean): this;
483
+ run(handler: CronHandler): Promise<CronHandle>;
484
+ }
485
+ declare class CronHandle {
486
+ private readonly conn;
487
+ private readonly cronState;
488
+ private readonly cronName;
489
+ constructor(conn: Connection, cronState: CronState, cronName: string);
490
+ get name(): string;
491
+ stop(): Promise<void>;
492
+ }
493
+
434
494
  type MsgCallback = (msg: Message) => void;
435
495
  declare class ArbitroClient {
436
496
  private conn;
@@ -439,6 +499,7 @@ declare class ArbitroClient {
439
499
  private readonly logger;
440
500
  private readonly sidCache;
441
501
  private readonly _metrics;
502
+ private readonly _cronState;
442
503
  constructor(config: ClientConfig);
443
504
  connect(): Promise<this>;
444
505
  /**
@@ -494,6 +555,13 @@ declare class ArbitroClient {
494
555
  publishBatch(streamName: string, messages: BatchPublishEntry[]): Promise<bigint>;
495
556
  /** Request-reply. */
496
557
  request(streamName: string, subject: string, data: Buffer, timeoutMs?: number): Promise<Buffer>;
558
+ /**
559
+ * Publish a message with a delivery delay. The broker parks the message
560
+ * in its delayed journal and delivers it to consumers after `delayMs`
561
+ * milliseconds. Returns a Promise that resolves once the broker confirms
562
+ * receipt.
563
+ */
564
+ publishDelayed(streamName: string, subject: string, data: Buffer, delayMs: number): Promise<void>;
497
565
  /** Subscribe with explicit consumer config. */
498
566
  subscribe(streamName: string, config: ConsumerConfig, callback?: MsgCallback, opts?: SubscribeOptions): Promise<Subscription>;
499
567
  /** Subscribe by consumer name only (consumer must already exist). */
@@ -533,6 +601,8 @@ declare class ArbitroClient {
533
601
  /** Pre-resolve stream_id from server (GetStream). Required before sync publish(). */
534
602
  resolveStream(name: string): Promise<void>;
535
603
  stream(name: string, config?: StreamConfig): Stream;
604
+ /** Start building a cron job. Call `.every()` then `.run()` to register. */
605
+ cron(name: string): CronBuilder;
536
606
  close(): Promise<void>;
537
607
  }
538
608
 
@@ -586,6 +656,64 @@ declare class Topic<T extends Record<string, unknown>> {
586
656
 
587
657
  declare function streamId(name: Buffer | string): number;
588
658
 
659
+ declare class WorkflowHandle {
660
+ private readonly workflowName;
661
+ private readonly taskStreamName;
662
+ private readonly dlqStreamName;
663
+ private readonly sub;
664
+ private readonly triggerSub;
665
+ constructor(workflowName: string, taskStreamName: string, dlqStreamName: string, sub: unknown, triggerSub: unknown | undefined);
666
+ get name(): string;
667
+ get taskStream(): string;
668
+ get dlqStream(): string;
669
+ trigger(client: ArbitroClient, context: Buffer): Promise<number>;
670
+ }
671
+
672
+ interface StepContext {
673
+ readonly name: string;
674
+ readonly instanceId: number;
675
+ readonly stepIndex: number;
676
+ readonly attempt: number;
677
+ readonly context: Buffer;
678
+ }
679
+ interface StepResult {
680
+ readonly context: Buffer;
681
+ }
682
+ type StepHandler = (ctx: StepContext) => Promise<StepResult>;
683
+ declare class WorkflowBuilder {
684
+ private readonly client;
685
+ private readonly workflowName;
686
+ private triggerSubject;
687
+ private triggerStreamName;
688
+ private readonly steps;
689
+ private ackWaitMs;
690
+ private maxInflightVal;
691
+ private maxRetriesVal;
692
+ private maxContextSizeVal;
693
+ constructor(client: ArbitroClient, workflowName: string);
694
+ trigger(subject: string): this;
695
+ triggerStream(streamName: string): this;
696
+ step(name: string, handler: StepHandler): this;
697
+ /** Compensation handler for the most recently added step. */
698
+ compensate(_stepName: string, handler: StepHandler): this;
699
+ ackWait(ms: number): this;
700
+ inflight(n: number): this;
701
+ maxRetries(n: number): this;
702
+ maxContextSize(bytes: number): this;
703
+ start(): Promise<WorkflowHandle>;
704
+ private subscribeWorker;
705
+ private subscribeTrigger;
706
+ }
707
+
708
+ /** Bit flag set on stepIndex to mark compensation tasks. */
709
+ declare const COMPENSATION_BIT = 32768;
710
+ interface DecodedTask {
711
+ readonly instanceId: number;
712
+ readonly stepIndex: number;
713
+ readonly attempt: number;
714
+ readonly context: Buffer;
715
+ }
716
+
589
717
  /** Wraps a ZodObject schema as an Encoding<T>.
590
718
  *
591
719
  * - encode: msgpack (no Zod overhead — bytes out)
@@ -599,4 +727,4 @@ declare function zodCodec<S extends ZodRawShape>(zodSchema: ZodObject<S>): Encod
599
727
  readonly fields: string[];
600
728
  };
601
729
 
602
- export { AckPolicy, ArbitroClient, ArbitroError, type BatchPublishEntry, type ClientConfig, type ClientMetricsSnapshot, Codec, Consumer, type ConsumerConfig, type ConsumerInfo, type DeleteStreamOpts, DeliverPolicy, type Encoding, ErrorCode, type ErrorCodeValue, type FieldType, type FieldTypeMap, type FlushConfig, type InferSchema, type JournalConfig, JournalType, JsonCodec, type LazyMessage, type LogFn, type Logger, Message, type PublishOpts, type ReconnectConfig, type Schema, Stream, type StreamConfig, type StreamInfo, StringCodec, type SubjectInflightLimit, type SubscribeOptions, Subscription, type TlsConfig, Topic, decodeJson, decodeString, encodeJson, encodeString, makeLazyMessage, schema, streamId, zodCodec };
730
+ export { AckPolicy, ArbitroClient, ArbitroError, type BatchPublishEntry, COMPENSATION_BIT, type ClientConfig, type ClientMetricsSnapshot, Codec, Consumer, type ConsumerConfig, type ConsumerInfo, CronBuilder, type CronContext, CronHandle, type CronHandler, type DecodedTask, type DeleteStreamOpts, DeliverPolicy, type Encoding, ErrorCode, type ErrorCodeValue, type FieldType, type FieldTypeMap, type FlushConfig, type InferSchema, type JournalConfig, JournalType, JsonCodec, type LazyMessage, type LogFn, type Logger, Message, type PublishOpts, type ReconnectConfig, type Schema, type StepContext, type StepHandler, type StepResult, Stream, type StreamConfig, type StreamInfo, StringCodec, type SubjectInflightLimit, type SubscribeOptions, Subscription, type TlsConfig, Topic, WorkflowBuilder, WorkflowHandle, decodeJson, decodeString, encodeJson, encodeString, makeLazyMessage, schema, streamId, zodCodec };