@arbitro/client 0.2.0 → 0.4.1

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,43 @@ 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
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:
222
+ ## Cron Scheduling
225
223
 
226
- - Docker restarts
227
- - broker failover tests
228
- - chaos scenarios with durable consumers
224
+ Register distributed cron jobs with queue semantics — multiple workers, single delivery per fire.
229
225
 
230
- ## Benchmarks
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
+ ```
231
238
 
232
- Three primary bench families:
239
+ Crons re-register automatically on reconnect. No persistence — if the broker restarts, clients re-register their crons when they reconnect.
233
240
 
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. |
241
+ ## Delayed Publish
239
242
 
240
- Examples:
243
+ Schedule message delivery for the future:
241
244
 
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
245
+ ```typescript
246
+ await client.publishDelayed("ORDERS", "orders.reminder", payload, 5000); // 5s delay
247
247
  ```
248
248
 
249
+ Messages are persisted immediately — survives broker restart.
250
+
251
+ ## Reconnect behavior
252
+
253
+ 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:
254
+
255
+ - Docker restarts
256
+ - broker failover tests
257
+ - chaos scenarios with durable consumers
258
+
249
259
  ## Validation
250
260
 
251
261
  ```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-6BCX2E2R.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"],"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-6BCX2E2R.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-SKCXQO7R.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-6BCX2E2R.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-KF57DJ2L.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/dist/index.d.mts CHANGED
@@ -220,6 +220,35 @@ declare class ClientMetrics {
220
220
  snapshot(): ClientMetricsSnapshot;
221
221
  }
222
222
 
223
+ interface CreateCronBody {
224
+ readonly name: string;
225
+ readonly every: string;
226
+ readonly tz?: string | undefined;
227
+ readonly timeout_ms: number;
228
+ readonly overlap: boolean;
229
+ }
230
+
231
+ type CronHandler = (ctx: CronContext) => Promise<void>;
232
+ /** Context passed to the cron handler on each fire. */
233
+ interface CronContext {
234
+ /** Cron job name. */
235
+ readonly name: string;
236
+ /** UTC timestamp (ms since epoch) when the broker intended this fire. */
237
+ readonly fireTime: bigint;
238
+ /** Monotonic fire counter (1-based). */
239
+ readonly fireCount: bigint;
240
+ }
241
+ declare class CronState {
242
+ private readonly handlers;
243
+ register(name: string, config: CreateCronBody, handler: CronHandler): void;
244
+ remove(name: string): void;
245
+ getHandler(name: string): CronHandler | undefined;
246
+ allConfigs(): ReadonlyArray<{
247
+ name: string;
248
+ config: CreateCronBody;
249
+ }>;
250
+ }
251
+
223
252
  type DeliveryHandler = (frame: Buffer) => void;
224
253
  declare class Connection {
225
254
  private readonly reconnectAddr?;
@@ -234,6 +263,7 @@ declare class Connection {
234
263
  private readonly log;
235
264
  private activeSubs;
236
265
  private metrics?;
266
+ private cronState?;
237
267
  private constructor();
238
268
  private attachSocket;
239
269
  static connect(addr: string, timeoutMs?: number, tlsCfg?: TlsConfig, reconnectCfg?: ReconnectConfig, logger?: Logger): Promise<Connection>;
@@ -244,13 +274,17 @@ declare class Connection {
244
274
  * entry and `reconnects` on successful reconnections. Unset = no-op.
245
275
  */
246
276
  setMetrics(m: ClientMetrics): void;
277
+ /** Attach cron state so the connection can dispatch CronFire frames. */
278
+ setCronState(s: CronState): void;
247
279
  private resolvePending;
248
280
  private rejectPending;
249
281
  private onFrame;
250
282
  private handleBatchDeliver;
283
+ private dispatchCronFire;
251
284
  sendSubscribeV2(consumerId: number, filter: Buffer, handler: DeliveryHandler, onRenew?: (newConsumerId: number) => void): Promise<number>;
252
285
  cancelSubscription(consumerId: number): void;
253
286
  private resubscribeAll;
287
+ private replayCrons;
254
288
  registerRoute(consumerId: number, handler: DeliveryHandler): void;
255
289
  unregisterRoute(consumerId: number): void;
256
290
  send(frame: Buffer): void;
@@ -431,6 +465,30 @@ declare class Consumer {
431
465
  subscribe<T extends Record<string, unknown>>(codec: Encoding<T>, cb: (msg: LazyMessage<T>) => void, opts?: SubscribeOptions): Promise<Subscription>;
432
466
  }
433
467
 
468
+ declare class CronBuilder {
469
+ private readonly conn;
470
+ private readonly cronState;
471
+ private readonly cronName;
472
+ private expr;
473
+ private timezone;
474
+ private timeoutMs;
475
+ private allowOverlap;
476
+ constructor(conn: Connection, cronState: CronState, cronName: string);
477
+ every(expression: string): this;
478
+ tz(timezone: string): this;
479
+ timeout(ms: number): this;
480
+ overlap(allow: boolean): this;
481
+ run(handler: CronHandler): Promise<CronHandle>;
482
+ }
483
+ declare class CronHandle {
484
+ private readonly conn;
485
+ private readonly cronState;
486
+ private readonly cronName;
487
+ constructor(conn: Connection, cronState: CronState, cronName: string);
488
+ get name(): string;
489
+ stop(): Promise<void>;
490
+ }
491
+
434
492
  type MsgCallback = (msg: Message) => void;
435
493
  declare class ArbitroClient {
436
494
  private conn;
@@ -439,6 +497,7 @@ declare class ArbitroClient {
439
497
  private readonly logger;
440
498
  private readonly sidCache;
441
499
  private readonly _metrics;
500
+ private readonly _cronState;
442
501
  constructor(config: ClientConfig);
443
502
  connect(): Promise<this>;
444
503
  /**
@@ -494,6 +553,13 @@ declare class ArbitroClient {
494
553
  publishBatch(streamName: string, messages: BatchPublishEntry[]): Promise<bigint>;
495
554
  /** Request-reply. */
496
555
  request(streamName: string, subject: string, data: Buffer, timeoutMs?: number): Promise<Buffer>;
556
+ /**
557
+ * Publish a message with a delivery delay. The broker parks the message
558
+ * in its delayed journal and delivers it to consumers after `delayMs`
559
+ * milliseconds. Returns a Promise that resolves once the broker confirms
560
+ * receipt.
561
+ */
562
+ publishDelayed(streamName: string, subject: string, data: Buffer, delayMs: number): Promise<void>;
497
563
  /** Subscribe with explicit consumer config. */
498
564
  subscribe(streamName: string, config: ConsumerConfig, callback?: MsgCallback, opts?: SubscribeOptions): Promise<Subscription>;
499
565
  /** Subscribe by consumer name only (consumer must already exist). */
@@ -533,6 +599,8 @@ declare class ArbitroClient {
533
599
  /** Pre-resolve stream_id from server (GetStream). Required before sync publish(). */
534
600
  resolveStream(name: string): Promise<void>;
535
601
  stream(name: string, config?: StreamConfig): Stream;
602
+ /** Start building a cron job. Call `.every()` then `.run()` to register. */
603
+ cron(name: string): CronBuilder;
536
604
  close(): Promise<void>;
537
605
  }
538
606
 
@@ -599,4 +667,4 @@ declare function zodCodec<S extends ZodRawShape>(zodSchema: ZodObject<S>): Encod
599
667
  readonly fields: string[];
600
668
  };
601
669
 
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 };
670
+ export { AckPolicy, ArbitroClient, ArbitroError, type BatchPublishEntry, type ClientConfig, type ClientMetricsSnapshot, Codec, Consumer, type ConsumerConfig, type ConsumerInfo, CronBuilder, type CronContext, CronHandle, type CronHandler, 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 };
package/dist/index.d.ts CHANGED
@@ -220,6 +220,35 @@ declare class ClientMetrics {
220
220
  snapshot(): ClientMetricsSnapshot;
221
221
  }
222
222
 
223
+ interface CreateCronBody {
224
+ readonly name: string;
225
+ readonly every: string;
226
+ readonly tz?: string | undefined;
227
+ readonly timeout_ms: number;
228
+ readonly overlap: boolean;
229
+ }
230
+
231
+ type CronHandler = (ctx: CronContext) => Promise<void>;
232
+ /** Context passed to the cron handler on each fire. */
233
+ interface CronContext {
234
+ /** Cron job name. */
235
+ readonly name: string;
236
+ /** UTC timestamp (ms since epoch) when the broker intended this fire. */
237
+ readonly fireTime: bigint;
238
+ /** Monotonic fire counter (1-based). */
239
+ readonly fireCount: bigint;
240
+ }
241
+ declare class CronState {
242
+ private readonly handlers;
243
+ register(name: string, config: CreateCronBody, handler: CronHandler): void;
244
+ remove(name: string): void;
245
+ getHandler(name: string): CronHandler | undefined;
246
+ allConfigs(): ReadonlyArray<{
247
+ name: string;
248
+ config: CreateCronBody;
249
+ }>;
250
+ }
251
+
223
252
  type DeliveryHandler = (frame: Buffer) => void;
224
253
  declare class Connection {
225
254
  private readonly reconnectAddr?;
@@ -234,6 +263,7 @@ declare class Connection {
234
263
  private readonly log;
235
264
  private activeSubs;
236
265
  private metrics?;
266
+ private cronState?;
237
267
  private constructor();
238
268
  private attachSocket;
239
269
  static connect(addr: string, timeoutMs?: number, tlsCfg?: TlsConfig, reconnectCfg?: ReconnectConfig, logger?: Logger): Promise<Connection>;
@@ -244,13 +274,17 @@ declare class Connection {
244
274
  * entry and `reconnects` on successful reconnections. Unset = no-op.
245
275
  */
246
276
  setMetrics(m: ClientMetrics): void;
277
+ /** Attach cron state so the connection can dispatch CronFire frames. */
278
+ setCronState(s: CronState): void;
247
279
  private resolvePending;
248
280
  private rejectPending;
249
281
  private onFrame;
250
282
  private handleBatchDeliver;
283
+ private dispatchCronFire;
251
284
  sendSubscribeV2(consumerId: number, filter: Buffer, handler: DeliveryHandler, onRenew?: (newConsumerId: number) => void): Promise<number>;
252
285
  cancelSubscription(consumerId: number): void;
253
286
  private resubscribeAll;
287
+ private replayCrons;
254
288
  registerRoute(consumerId: number, handler: DeliveryHandler): void;
255
289
  unregisterRoute(consumerId: number): void;
256
290
  send(frame: Buffer): void;
@@ -431,6 +465,30 @@ declare class Consumer {
431
465
  subscribe<T extends Record<string, unknown>>(codec: Encoding<T>, cb: (msg: LazyMessage<T>) => void, opts?: SubscribeOptions): Promise<Subscription>;
432
466
  }
433
467
 
468
+ declare class CronBuilder {
469
+ private readonly conn;
470
+ private readonly cronState;
471
+ private readonly cronName;
472
+ private expr;
473
+ private timezone;
474
+ private timeoutMs;
475
+ private allowOverlap;
476
+ constructor(conn: Connection, cronState: CronState, cronName: string);
477
+ every(expression: string): this;
478
+ tz(timezone: string): this;
479
+ timeout(ms: number): this;
480
+ overlap(allow: boolean): this;
481
+ run(handler: CronHandler): Promise<CronHandle>;
482
+ }
483
+ declare class CronHandle {
484
+ private readonly conn;
485
+ private readonly cronState;
486
+ private readonly cronName;
487
+ constructor(conn: Connection, cronState: CronState, cronName: string);
488
+ get name(): string;
489
+ stop(): Promise<void>;
490
+ }
491
+
434
492
  type MsgCallback = (msg: Message) => void;
435
493
  declare class ArbitroClient {
436
494
  private conn;
@@ -439,6 +497,7 @@ declare class ArbitroClient {
439
497
  private readonly logger;
440
498
  private readonly sidCache;
441
499
  private readonly _metrics;
500
+ private readonly _cronState;
442
501
  constructor(config: ClientConfig);
443
502
  connect(): Promise<this>;
444
503
  /**
@@ -494,6 +553,13 @@ declare class ArbitroClient {
494
553
  publishBatch(streamName: string, messages: BatchPublishEntry[]): Promise<bigint>;
495
554
  /** Request-reply. */
496
555
  request(streamName: string, subject: string, data: Buffer, timeoutMs?: number): Promise<Buffer>;
556
+ /**
557
+ * Publish a message with a delivery delay. The broker parks the message
558
+ * in its delayed journal and delivers it to consumers after `delayMs`
559
+ * milliseconds. Returns a Promise that resolves once the broker confirms
560
+ * receipt.
561
+ */
562
+ publishDelayed(streamName: string, subject: string, data: Buffer, delayMs: number): Promise<void>;
497
563
  /** Subscribe with explicit consumer config. */
498
564
  subscribe(streamName: string, config: ConsumerConfig, callback?: MsgCallback, opts?: SubscribeOptions): Promise<Subscription>;
499
565
  /** Subscribe by consumer name only (consumer must already exist). */
@@ -533,6 +599,8 @@ declare class ArbitroClient {
533
599
  /** Pre-resolve stream_id from server (GetStream). Required before sync publish(). */
534
600
  resolveStream(name: string): Promise<void>;
535
601
  stream(name: string, config?: StreamConfig): Stream;
602
+ /** Start building a cron job. Call `.every()` then `.run()` to register. */
603
+ cron(name: string): CronBuilder;
536
604
  close(): Promise<void>;
537
605
  }
538
606
 
@@ -599,4 +667,4 @@ declare function zodCodec<S extends ZodRawShape>(zodSchema: ZodObject<S>): Encod
599
667
  readonly fields: string[];
600
668
  };
601
669
 
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 };
670
+ export { AckPolicy, ArbitroClient, ArbitroError, type BatchPublishEntry, type ClientConfig, type ClientMetricsSnapshot, Codec, Consumer, type ConsumerConfig, type ConsumerInfo, CronBuilder, type CronContext, CronHandle, type CronHandler, 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 };