@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/dist/index.js CHANGED
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
8
11
  var __export = (target, all) => {
9
12
  for (var name in all)
10
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,14 +30,248 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
30
  ));
28
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
32
 
33
+ // src/proto/constants.ts
34
+ var constants_exports = {};
35
+ __export(constants_exports, {
36
+ Action: () => Action,
37
+ CURRENT_VERSION: () => CURRENT_VERSION,
38
+ Cap: () => Cap,
39
+ EntryFlag: () => EntryFlag,
40
+ Flag: () => Flag,
41
+ HEADER_SIZE: () => HEADER_SIZE,
42
+ HELLO_SIZE: () => HELLO_SIZE,
43
+ MAGIC_V2: () => MAGIC_V2,
44
+ OFF_ACTION: () => OFF_ACTION,
45
+ OFF_ENTRY_FLAGS: () => OFF_ENTRY_FLAGS,
46
+ OFF_FLAGS: () => OFF_FLAGS,
47
+ OFF_MSG_LEN: () => OFF_MSG_LEN,
48
+ OFF_SEQ: () => OFF_SEQ,
49
+ Role: () => Role
50
+ });
51
+ var MAGIC_V2, HELLO_SIZE, CURRENT_VERSION, Role, Cap, HEADER_SIZE, OFF_ACTION, OFF_FLAGS, OFF_ENTRY_FLAGS, OFF_MSG_LEN, OFF_SEQ, Flag, EntryFlag, Action;
52
+ var init_constants = __esm({
53
+ "src/proto/constants.ts"() {
54
+ "use strict";
55
+ MAGIC_V2 = 843207233;
56
+ HELLO_SIZE = 8;
57
+ CURRENT_VERSION = 2;
58
+ Role = /* @__PURE__ */ ((Role2) => {
59
+ Role2[Role2["Client"] = 0] = "Client";
60
+ Role2[Role2["Server"] = 1] = "Server";
61
+ return Role2;
62
+ })(Role || {});
63
+ Cap = /* @__PURE__ */ ((Cap2) => {
64
+ Cap2[Cap2["Headers"] = 1] = "Headers";
65
+ Cap2[Cap2["Reply"] = 2] = "Reply";
66
+ Cap2[Cap2["BatchHeaders"] = 4] = "BatchHeaders";
67
+ Cap2[Cap2["CompressedPayload"] = 8] = "CompressedPayload";
68
+ return Cap2;
69
+ })(Cap || {});
70
+ HEADER_SIZE = 16;
71
+ OFF_ACTION = 0;
72
+ OFF_FLAGS = 2;
73
+ OFF_ENTRY_FLAGS = 3;
74
+ OFF_MSG_LEN = 4;
75
+ OFF_SEQ = 8;
76
+ Flag = /* @__PURE__ */ ((Flag3) => {
77
+ Flag3[Flag3["None"] = 0] = "None";
78
+ Flag3[Flag3["AckReq"] = 1] = "AckReq";
79
+ Flag3[Flag3["Dup"] = 2] = "Dup";
80
+ Flag3[Flag3["PriorityHigh"] = 4] = "PriorityHigh";
81
+ return Flag3;
82
+ })(Flag || {});
83
+ EntryFlag = /* @__PURE__ */ ((EntryFlag2) => {
84
+ EntryFlag2[EntryFlag2["None"] = 0] = "None";
85
+ EntryFlag2[EntryFlag2["Retain"] = 1] = "Retain";
86
+ EntryFlag2[EntryFlag2["Compressed"] = 2] = "Compressed";
87
+ EntryFlag2[EntryFlag2["NoBackpressure"] = 4] = "NoBackpressure";
88
+ return EntryFlag2;
89
+ })(EntryFlag || {});
90
+ Action = /* @__PURE__ */ ((Action2) => {
91
+ Action2[Action2["Hello"] = 1] = "Hello";
92
+ Action2[Action2["Auth"] = 2] = "Auth";
93
+ Action2[Action2["Publish"] = 257] = "Publish";
94
+ Action2[Action2["PublishAccumulate"] = 258] = "PublishAccumulate";
95
+ Action2[Action2["PublishBatch"] = 259] = "PublishBatch";
96
+ Action2[Action2["PublishWithReply"] = 260] = "PublishWithReply";
97
+ Action2[Action2["PublishWithHeaders"] = 261] = "PublishWithHeaders";
98
+ Action2[Action2["PublishBatchWithHeaders"] = 262] = "PublishBatchWithHeaders";
99
+ Action2[Action2["Deliver"] = 512] = "Deliver";
100
+ Action2[Action2["Ack"] = 513] = "Ack";
101
+ Action2[Action2["Nack"] = 514] = "Nack";
102
+ Action2[Action2["RepOk"] = 515] = "RepOk";
103
+ Action2[Action2["RepError"] = 516] = "RepError";
104
+ Action2[Action2["RepBatch"] = 517] = "RepBatch";
105
+ Action2[Action2["BatchAck"] = 518] = "BatchAck";
106
+ Action2[Action2["FanoutBatch"] = 519] = "FanoutBatch";
107
+ Action2[Action2["AckSync"] = 520] = "AckSync";
108
+ Action2[Action2["BatchAckSync"] = 521] = "BatchAckSync";
109
+ Action2[Action2["BatchNack"] = 522] = "BatchNack";
110
+ Action2[Action2["Subscribe"] = 769] = "Subscribe";
111
+ Action2[Action2["Unsubscribe"] = 770] = "Unsubscribe";
112
+ Action2[Action2["CreateStream"] = 1025] = "CreateStream";
113
+ Action2[Action2["DeleteStream"] = 1026] = "DeleteStream";
114
+ Action2[Action2["GetStream"] = 1027] = "GetStream";
115
+ Action2[Action2["ListStreams"] = 1028] = "ListStreams";
116
+ Action2[Action2["PurgeStream"] = 1029] = "PurgeStream";
117
+ Action2[Action2["DrainSubject"] = 1030] = "DrainSubject";
118
+ Action2[Action2["CreateConsumer"] = 1281] = "CreateConsumer";
119
+ Action2[Action2["DeleteConsumer"] = 1282] = "DeleteConsumer";
120
+ Action2[Action2["GetConsumer"] = 1283] = "GetConsumer";
121
+ Action2[Action2["ListConsumers"] = 1284] = "ListConsumers";
122
+ Action2[Action2["ConsumerStats"] = 1285] = "ConsumerStats";
123
+ Action2[Action2["PauseConsumer"] = 1286] = "PauseConsumer";
124
+ Action2[Action2["ResumeConsumer"] = 1287] = "ResumeConsumer";
125
+ Action2[Action2["PublishDelayed"] = 2049] = "PublishDelayed";
126
+ Action2[Action2["Ping"] = 1537] = "Ping";
127
+ Action2[Action2["Pong"] = 1538] = "Pong";
128
+ Action2[Action2["Connect"] = 1539] = "Connect";
129
+ Action2[Action2["Connected"] = 1540] = "Connected";
130
+ Action2[Action2["Disconnect"] = 1541] = "Disconnect";
131
+ Action2[Action2["CreateCron"] = 1793] = "CreateCron";
132
+ Action2[Action2["DeleteCron"] = 1794] = "DeleteCron";
133
+ Action2[Action2["ListCrons"] = 1795] = "ListCrons";
134
+ Action2[Action2["CronFire"] = 1796] = "CronFire";
135
+ Action2[Action2["CronAck"] = 1797] = "CronAck";
136
+ return Action2;
137
+ })(Action || {});
138
+ }
139
+ });
140
+
141
+ // src/proto/frame.ts
142
+ function frame(action, seq, bodyLen, flags = 0, entryFlags = 0) {
143
+ const buf = Buffer.allocUnsafe(HEADER_SIZE + bodyLen);
144
+ buf.writeUInt16LE(action, OFF_ACTION);
145
+ buf[OFF_FLAGS] = flags;
146
+ buf[OFF_ENTRY_FLAGS] = entryFlags;
147
+ buf.writeUInt32LE(bodyLen, OFF_MSG_LEN);
148
+ buf.writeBigUInt64LE(seq, OFF_SEQ);
149
+ return buf;
150
+ }
151
+ function packHello(caps = 2 /* Reply */) {
152
+ const buf = Buffer.allocUnsafe(HELLO_SIZE);
153
+ buf.writeUInt32LE(MAGIC_V2, 0);
154
+ buf[4] = CURRENT_VERSION;
155
+ buf[5] = 0 /* Client */;
156
+ buf.writeUInt16LE(caps, 6);
157
+ return buf;
158
+ }
159
+ function packDisconnect(seq) {
160
+ return frame(1541 /* Disconnect */, seq, 0);
161
+ }
162
+ var init_frame = __esm({
163
+ "src/proto/frame.ts"() {
164
+ "use strict";
165
+ init_constants();
166
+ }
167
+ });
168
+
169
+ // src/proto/publish.ts
170
+ var publish_exports = {};
171
+ __export(publish_exports, {
172
+ packPublish: () => packPublish,
173
+ packPublishBatch: () => packPublishBatch,
174
+ packPublishDelayed: () => packPublishDelayed,
175
+ packPublishWithReply: () => packPublishWithReply
176
+ });
177
+ function packPublish(seq, streamId2, subject, payload, flags = 0, entryFlags = 0, msgId = EMPTY) {
178
+ const buf = frame(
179
+ 257 /* Publish */,
180
+ seq,
181
+ 8 + subject.length + msgId.length + payload.length,
182
+ flags,
183
+ entryFlags
184
+ );
185
+ buf.writeUInt32LE(streamId2, HEADER_SIZE);
186
+ buf.writeUInt16LE(subject.length, HEADER_SIZE + 4);
187
+ buf.writeUInt16LE(msgId.length, HEADER_SIZE + 6);
188
+ let off = HEADER_SIZE + 8;
189
+ subject.copy(buf, off);
190
+ off += subject.length;
191
+ msgId.copy(buf, off);
192
+ off += msgId.length;
193
+ payload.copy(buf, off);
194
+ return buf;
195
+ }
196
+ function packPublishWithReply(seq, streamId2, subject, replyTo, payload, flags = 0, entryFlags = 0) {
197
+ const tail = subject.length + replyTo.length + payload.length;
198
+ const buf = frame(260 /* PublishWithReply */, seq, 12 + tail, flags, entryFlags);
199
+ buf.writeUInt32LE(streamId2, HEADER_SIZE);
200
+ buf.writeUInt16LE(subject.length, HEADER_SIZE + 4);
201
+ buf.writeUInt16LE(replyTo.length, HEADER_SIZE + 6);
202
+ buf.writeUInt32LE(0, HEADER_SIZE + 8);
203
+ let off = HEADER_SIZE + 12;
204
+ subject.copy(buf, off);
205
+ off += subject.length;
206
+ replyTo.copy(buf, off);
207
+ off += replyTo.length;
208
+ payload.copy(buf, off);
209
+ return buf;
210
+ }
211
+ function packPublishDelayed(seq, streamId2, subject, payload, delayMs, flags = 0, entryFlags = 0) {
212
+ const buf = frame(
213
+ 2049 /* PublishDelayed */,
214
+ seq,
215
+ 16 + subject.length + payload.length,
216
+ flags,
217
+ entryFlags
218
+ );
219
+ buf.writeUInt32LE(streamId2, HEADER_SIZE);
220
+ buf.writeUInt16LE(subject.length, HEADER_SIZE + 4);
221
+ buf.writeUInt16LE(0, HEADER_SIZE + 6);
222
+ buf.writeBigUInt64LE(delayMs, HEADER_SIZE + 8);
223
+ let off = HEADER_SIZE + 16;
224
+ subject.copy(buf, off);
225
+ off += subject.length;
226
+ payload.copy(buf, off);
227
+ return buf;
228
+ }
229
+ function packPublishBatch(seq, streamId2, entries, flags = 0, entryFlags = 0) {
230
+ let tail = 0;
231
+ for (const e of entries) {
232
+ const midLen = e.msgId ? e.msgId.length : 0;
233
+ tail += 8 + e.subject.length + midLen + e.payload.length;
234
+ }
235
+ const buf = frame(259 /* PublishBatch */, seq, 8 + tail, flags, entryFlags);
236
+ buf.writeUInt32LE(streamId2, HEADER_SIZE);
237
+ buf.writeUInt32LE(entries.length, HEADER_SIZE + 4);
238
+ let off = HEADER_SIZE + 8;
239
+ for (const e of entries) {
240
+ const mid = e.msgId ?? EMPTY;
241
+ buf.writeUInt16LE(e.subject.length, off);
242
+ buf.writeUInt16LE(mid.length, off + 2);
243
+ buf.writeUInt32LE(e.payload.length, off + 4);
244
+ off += 8;
245
+ buf.write(e.subject, off);
246
+ off += e.subject.length;
247
+ mid.copy(buf, off);
248
+ off += mid.length;
249
+ e.payload.copy(buf, off);
250
+ off += e.payload.length;
251
+ }
252
+ return buf;
253
+ }
254
+ var EMPTY;
255
+ var init_publish = __esm({
256
+ "src/proto/publish.ts"() {
257
+ "use strict";
258
+ init_constants();
259
+ init_frame();
260
+ EMPTY = Buffer.alloc(0);
261
+ }
262
+ });
263
+
30
264
  // src/index.ts
31
265
  var index_exports = {};
32
266
  __export(index_exports, {
33
267
  AckPolicy: () => AckPolicy,
34
268
  ArbitroClient: () => ArbitroClient,
35
269
  ArbitroError: () => ArbitroError,
270
+ COMPENSATION_BIT: () => COMPENSATION_BIT,
36
271
  Codec: () => Codec,
37
272
  Consumer: () => Consumer,
273
+ CronBuilder: () => CronBuilder,
274
+ CronHandle: () => CronHandle,
38
275
  DeliverPolicy: () => DeliverPolicy,
39
276
  ErrorCode: () => ErrorCode,
40
277
  JournalType: () => JournalType,
@@ -44,6 +281,8 @@ __export(index_exports, {
44
281
  StringCodec: () => StringCodec,
45
282
  Subscription: () => Subscription,
46
283
  Topic: () => Topic,
284
+ WorkflowBuilder: () => WorkflowBuilder,
285
+ WorkflowHandle: () => WorkflowHandle,
47
286
  decodeJson: () => decodeJson,
48
287
  decodeString: () => decodeString,
49
288
  encodeJson: () => encodeJson,
@@ -234,102 +473,16 @@ function makeLazyMessage(raw, codec, fields, onAck, onNack, onNackDelay) {
234
473
  return msg;
235
474
  }
236
475
 
237
- // src/proto/constants.ts
238
- var MAGIC_V2 = 843207233;
239
- var HELLO_SIZE = 8;
240
- var CURRENT_VERSION = 2;
241
- var HEADER_SIZE = 16;
242
- var OFF_ACTION = 0;
243
- var OFF_FLAGS = 2;
244
- var OFF_ENTRY_FLAGS = 3;
245
- var OFF_MSG_LEN = 4;
246
- var OFF_SEQ = 8;
247
-
248
- // src/proto/frame.ts
249
- function frame(action, seq, bodyLen, flags = 0, entryFlags = 0) {
250
- const buf = Buffer.allocUnsafe(HEADER_SIZE + bodyLen);
251
- buf.writeUInt16LE(action, OFF_ACTION);
252
- buf[OFF_FLAGS] = flags;
253
- buf[OFF_ENTRY_FLAGS] = entryFlags;
254
- buf.writeUInt32LE(bodyLen, OFF_MSG_LEN);
255
- buf.writeBigUInt64LE(seq, OFF_SEQ);
256
- return buf;
257
- }
258
- function packHello(caps = 2 /* Reply */) {
259
- const buf = Buffer.allocUnsafe(HELLO_SIZE);
260
- buf.writeUInt32LE(MAGIC_V2, 0);
261
- buf[4] = CURRENT_VERSION;
262
- buf[5] = 0 /* Client */;
263
- buf.writeUInt16LE(caps, 6);
264
- return buf;
265
- }
266
- function packDisconnect(seq) {
267
- return frame(1541 /* Disconnect */, seq, 0);
268
- }
476
+ // src/message/message.ts
477
+ init_constants();
269
478
 
270
- // src/proto/publish.ts
271
- var EMPTY = Buffer.alloc(0);
272
- function packPublish(seq, streamId2, subject, payload, flags = 0, entryFlags = 0, msgId = EMPTY) {
273
- const buf = frame(
274
- 257 /* Publish */,
275
- seq,
276
- 8 + subject.length + msgId.length + payload.length,
277
- flags,
278
- entryFlags
279
- );
280
- buf.writeUInt32LE(streamId2, HEADER_SIZE);
281
- buf.writeUInt16LE(subject.length, HEADER_SIZE + 4);
282
- buf.writeUInt16LE(msgId.length, HEADER_SIZE + 6);
283
- let off = HEADER_SIZE + 8;
284
- subject.copy(buf, off);
285
- off += subject.length;
286
- msgId.copy(buf, off);
287
- off += msgId.length;
288
- payload.copy(buf, off);
289
- return buf;
290
- }
291
- function packPublishWithReply(seq, streamId2, subject, replyTo, payload, flags = 0, entryFlags = 0) {
292
- const tail = subject.length + replyTo.length + payload.length;
293
- const buf = frame(260 /* PublishWithReply */, seq, 12 + tail, flags, entryFlags);
294
- buf.writeUInt32LE(streamId2, HEADER_SIZE);
295
- buf.writeUInt16LE(subject.length, HEADER_SIZE + 4);
296
- buf.writeUInt16LE(replyTo.length, HEADER_SIZE + 6);
297
- buf.writeUInt32LE(0, HEADER_SIZE + 8);
298
- let off = HEADER_SIZE + 12;
299
- subject.copy(buf, off);
300
- off += subject.length;
301
- replyTo.copy(buf, off);
302
- off += replyTo.length;
303
- payload.copy(buf, off);
304
- return buf;
305
- }
306
- function packPublishBatch(seq, streamId2, entries, flags = 0, entryFlags = 0) {
307
- let tail = 0;
308
- for (const e of entries) {
309
- const midLen = e.msgId ? e.msgId.length : 0;
310
- tail += 8 + e.subject.length + midLen + e.payload.length;
311
- }
312
- const buf = frame(259 /* PublishBatch */, seq, 8 + tail, flags, entryFlags);
313
- buf.writeUInt32LE(streamId2, HEADER_SIZE);
314
- buf.writeUInt32LE(entries.length, HEADER_SIZE + 4);
315
- let off = HEADER_SIZE + 8;
316
- for (const e of entries) {
317
- const mid = e.msgId ?? EMPTY;
318
- buf.writeUInt16LE(e.subject.length, off);
319
- buf.writeUInt16LE(mid.length, off + 2);
320
- buf.writeUInt32LE(e.payload.length, off + 4);
321
- off += 8;
322
- buf.write(e.subject, off);
323
- off += e.subject.length;
324
- mid.copy(buf, off);
325
- off += mid.length;
326
- e.payload.copy(buf, off);
327
- off += e.payload.length;
328
- }
329
- return buf;
330
- }
479
+ // src/proto/v2.ts
480
+ init_frame();
481
+ init_publish();
331
482
 
332
483
  // src/proto/delivery.ts
484
+ init_constants();
485
+ init_frame();
333
486
  function packCold(action, seq, body) {
334
487
  const utf8 = Buffer.from(JSON.stringify(body), "utf8");
335
488
  const buf = frame(action, seq, utf8.length);
@@ -376,6 +529,8 @@ function packBatchNack(seq, consumerId, entries) {
376
529
  }
377
530
 
378
531
  // src/proto/manage.ts
532
+ init_constants();
533
+ init_frame();
379
534
  function packCold2(action, seq, body) {
380
535
  const utf8 = Buffer.from(JSON.stringify(body), "utf8");
381
536
  const buf = frame(action, seq, utf8.length);
@@ -583,6 +738,7 @@ var net = __toESM(require("net"));
583
738
  var tls = __toESM(require("tls"));
584
739
 
585
740
  // src/proto/framer.ts
741
+ init_constants();
586
742
  var ENVELOPE_MSG_LEN_OFF = 8;
587
743
  var Framer = class {
588
744
  buf = Buffer.allocUnsafe(65536);
@@ -614,6 +770,9 @@ var Framer = class {
614
770
  }
615
771
  };
616
772
 
773
+ // src/net/connection.ts
774
+ init_constants();
775
+
617
776
  // src/common/logger.ts
618
777
  var noop = (() => {
619
778
  });
@@ -629,6 +788,39 @@ function resolveLogger(logger) {
629
788
  return logger ?? NOOP;
630
789
  }
631
790
 
791
+ // src/cron/cron-frame.ts
792
+ init_constants();
793
+ init_frame();
794
+ function packCreateCron(seq, body) {
795
+ const json = Buffer.from(JSON.stringify(body), "utf8");
796
+ const buf = frame(1793 /* CreateCron */, seq, json.length);
797
+ json.copy(buf, HEADER_SIZE);
798
+ return buf;
799
+ }
800
+ function packDeleteCron(seq, name) {
801
+ const buf = frame(1794 /* DeleteCron */, seq, name.length);
802
+ name.copy(buf, HEADER_SIZE);
803
+ return buf;
804
+ }
805
+ var CRON_FIRE_FIXED = 2 + 8 + 8;
806
+ function decodeCronFire(body) {
807
+ if (body.length < CRON_FIRE_FIXED) return void 0;
808
+ const nameLen = body.readUInt16LE(0);
809
+ if (body.length < CRON_FIRE_FIXED + nameLen) return void 0;
810
+ const fireTimeMs = body.readBigUInt64LE(2);
811
+ const fireCount = body.readBigUInt64LE(10);
812
+ const name = body.subarray(18, 18 + nameLen).toString();
813
+ return { name, fireTimeMs, fireCount };
814
+ }
815
+ function packCronAck(seq, name, ok) {
816
+ const bodyLen = 3 + name.length;
817
+ const buf = frame(1797 /* CronAck */, seq, bodyLen);
818
+ buf.writeUInt16LE(name.length, HEADER_SIZE);
819
+ buf[HEADER_SIZE + 2] = ok ? 0 : 1;
820
+ name.copy(buf, HEADER_SIZE + 3);
821
+ return buf;
822
+ }
823
+
632
824
  // src/net/connection.ts
633
825
  function parseAddr(addr) {
634
826
  const i = addr.lastIndexOf(":");
@@ -652,6 +844,7 @@ var Connection = class _Connection {
652
844
  log;
653
845
  activeSubs = /* @__PURE__ */ new Map();
654
846
  metrics;
847
+ cronState;
655
848
  attachSocket(socket) {
656
849
  socket.setNoDelay(true);
657
850
  socket.on("data", (chunk) => this.framer.push(chunk, (f) => this.onFrame(f)));
@@ -706,6 +899,10 @@ var Connection = class _Connection {
706
899
  setMetrics(m) {
707
900
  this.metrics = m;
708
901
  }
902
+ /** Attach cron state so the connection can dispatch CronFire frames. */
903
+ setCronState(s) {
904
+ this.cronState = s;
905
+ }
709
906
  // ── Frame routing ─────────────────────────────────────────────────────────
710
907
  // Seq-based dispatch: match reply.header.seq → pending request. O(1).
711
908
  resolvePending(frame2) {
@@ -757,6 +954,10 @@ var Connection = class _Connection {
757
954
  this.handleBatchDeliver(frame2);
758
955
  return;
759
956
  }
957
+ case 1796 /* CronFire */: {
958
+ this.dispatchCronFire(frame2);
959
+ return;
960
+ }
760
961
  case 1538 /* Pong */:
761
962
  return;
762
963
  default: {
@@ -806,6 +1007,19 @@ var Connection = class _Connection {
806
1007
  off = tailEnd;
807
1008
  }
808
1009
  }
1010
+ // ── Cron dispatch ──────────────────────────────────────────────────────────
1011
+ dispatchCronFire(frame2) {
1012
+ const body = frame2.subarray(HEADER_SIZE);
1013
+ const view = decodeCronFire(body);
1014
+ if (!view) return;
1015
+ const handler = this.cronState?.getHandler(view.name);
1016
+ const nameBuf = Buffer.from(view.name);
1017
+ if (!handler) {
1018
+ this.send(packCronAck(this.nextSeq(), nameBuf, false));
1019
+ return;
1020
+ }
1021
+ handler({ name: view.name, fireTime: view.fireTimeMs, fireCount: view.fireCount }).then(() => this.send(packCronAck(this.nextSeq(), nameBuf, true))).catch(() => this.send(packCronAck(this.nextSeq(), nameBuf, false)));
1022
+ }
809
1023
  // ── Subscriptions ─────────────────────────────────────────────────────────
810
1024
  async sendSubscribeV2(consumerId, filter, handler, onRenew) {
811
1025
  return new Promise((resolve, reject) => {
@@ -848,6 +1062,14 @@ var Connection = class _Connection {
848
1062
  }).catch(() => {
849
1063
  });
850
1064
  }
1065
+ this.replayCrons();
1066
+ }
1067
+ replayCrons() {
1068
+ if (!this.cronState) return;
1069
+ for (const { config } of this.cronState.allConfigs()) {
1070
+ const seq = this.nextSeq();
1071
+ this.socket.write(packCreateCron(seq, config));
1072
+ }
851
1073
  }
852
1074
  // ── Routes (internal use) ─────────────────────────────────────────────────
853
1075
  registerRoute(consumerId, handler) {
@@ -1131,6 +1353,7 @@ var ClientMetrics = class {
1131
1353
  };
1132
1354
 
1133
1355
  // src/stream/publish.ts
1356
+ init_constants();
1134
1357
  var EMPTY2 = Buffer.alloc(0);
1135
1358
  function toMsgIdBuf(id) {
1136
1359
  if (id == null) return EMPTY2;
@@ -1162,6 +1385,89 @@ async function streamRequest(conn, sid, subject, data, timeoutMs) {
1162
1385
  return Buffer.alloc(0);
1163
1386
  }
1164
1387
 
1388
+ // src/client/client.ts
1389
+ init_constants();
1390
+
1391
+ // src/cron/cron-builder.ts
1392
+ var CronBuilder = class {
1393
+ constructor(conn, cronState, cronName) {
1394
+ this.conn = conn;
1395
+ this.cronState = cronState;
1396
+ this.cronName = cronName;
1397
+ }
1398
+ expr;
1399
+ timezone;
1400
+ timeoutMs = 3e4;
1401
+ allowOverlap = false;
1402
+ every(expression) {
1403
+ this.expr = expression;
1404
+ return this;
1405
+ }
1406
+ tz(timezone) {
1407
+ this.timezone = timezone;
1408
+ return this;
1409
+ }
1410
+ timeout(ms) {
1411
+ this.timeoutMs = ms;
1412
+ return this;
1413
+ }
1414
+ overlap(allow) {
1415
+ this.allowOverlap = allow;
1416
+ return this;
1417
+ }
1418
+ async run(handler) {
1419
+ if (!this.expr) throw new Error("cron expression required \u2014 call .every()");
1420
+ const body = {
1421
+ name: this.cronName,
1422
+ every: this.expr,
1423
+ tz: this.timezone,
1424
+ timeout_ms: this.timeoutMs,
1425
+ overlap: this.allowOverlap
1426
+ };
1427
+ const seq = this.conn.nextSeq();
1428
+ await this.conn.sendExpectReply(packCreateCron(seq, body));
1429
+ this.cronState.register(this.cronName, body, handler);
1430
+ return new CronHandle(this.conn, this.cronState, this.cronName);
1431
+ }
1432
+ };
1433
+ var CronHandle = class {
1434
+ constructor(conn, cronState, cronName) {
1435
+ this.conn = conn;
1436
+ this.cronState = cronState;
1437
+ this.cronName = cronName;
1438
+ }
1439
+ get name() {
1440
+ return this.cronName;
1441
+ }
1442
+ async stop() {
1443
+ const nameBuf = Buffer.from(this.cronName);
1444
+ const seq = this.conn.nextSeq();
1445
+ await this.conn.sendExpectReply(packDeleteCron(seq, nameBuf));
1446
+ this.cronState.remove(this.cronName);
1447
+ }
1448
+ };
1449
+
1450
+ // src/cron/cron-state.ts
1451
+ var CronState = class {
1452
+ handlers = /* @__PURE__ */ new Map();
1453
+ register(name, config, handler) {
1454
+ this.handlers.set(name, { handler, config });
1455
+ }
1456
+ remove(name) {
1457
+ this.handlers.delete(name);
1458
+ }
1459
+ getHandler(name) {
1460
+ return this.handlers.get(name)?.handler;
1461
+ }
1462
+ allConfigs() {
1463
+ const out = [];
1464
+ for (const [name, entry] of this.handlers) {
1465
+ out.push({ name, config: entry.config });
1466
+ }
1467
+ return out;
1468
+ }
1469
+ };
1470
+
1165
1471
  // src/client/client.ts
1166
1472
  var DEFAULT_CONFIG = {
1167
1473
  servers: ["127.0.0.1:9898"],
@@ -1176,6 +1482,7 @@ var ArbitroClient = class {
1176
1482
  logger;
1177
1483
  sidCache = /* @__PURE__ */ new Map();
1178
1484
  _metrics = new ClientMetrics();
1485
+ _cronState = new CronState();
1179
1486
  constructor(config) {
1180
1487
  this.cfg = { ...DEFAULT_CONFIG, ...config };
1181
1488
  this.tls = config.tls;
@@ -1192,6 +1499,7 @@ var ArbitroClient = class {
1192
1499
  this.logger
1193
1500
  );
1194
1501
  this.conn.setMetrics(this._metrics);
1502
+ this.conn.setCronState(this._cronState);
1195
1503
  return this;
1196
1504
  }
1197
1505
  /**
@@ -1277,6 +1585,22 @@ var ArbitroClient = class {
1277
1585
  const sid = await this.resolveStreamId(streamName);
1278
1586
  return streamRequest(this.conn, sid, subject, data, timeoutMs);
1279
1587
  }
1588
+ /**
1589
+ * Publish a message with a delivery delay. The broker parks the message
1590
+ * in its delayed journal and delivers it to consumers after `delayMs`
1591
+ * milliseconds. Returns a Promise that resolves once the broker confirms
1592
+ * receipt.
1593
+ */
1594
+ async publishDelayed(streamName, subject, data, delayMs) {
1595
+ const sid = await this.resolveStreamId(streamName);
1596
+ const { packPublishDelayed: packPublishDelayed2 } = await Promise.resolve().then(() => (init_publish(), publish_exports));
1597
+ const { Flag: Flag3 } = await Promise.resolve().then(() => (init_constants(), constants_exports));
1598
+ const subj = Buffer.from(this.prefixed(subject));
1599
+ await this.conn.sendExpectReply(
1600
+ packPublishDelayed2(this.conn.nextSeq(), sid, subj, data, BigInt(delayMs), Flag3.AckReq)
1601
+ );
1602
+ this._metrics.publishesSent++;
1603
+ }
1280
1604
  async subscribe(streamName, configOrCb, callbackOrOpts, opts) {
1281
1605
  let config;
1282
1606
  let callback;
@@ -1385,7 +1709,7 @@ var ArbitroClient = class {
1385
1709
  async createConsumerRaw(streamName, config) {
1386
1710
  const sid = await this.resolveStreamId(streamName);
1387
1711
  const name = Buffer.from(config.name ?? streamName);
1388
- const group = Buffer.from(config.name ?? streamName);
1712
+ const group = Buffer.from(config.group ?? config.name ?? streamName);
1389
1713
  const filter = Buffer.from(config.filter ?? "");
1390
1714
  const ackPolicyByte = config.ackPolicy === "none" /* None */ ? 0 : 1;
1391
1715
  const opts = {
@@ -1396,7 +1720,7 @@ var ArbitroClient = class {
1396
1720
  maxInflight: config.maxAckPending ?? 0,
1397
1721
  ackPolicy: ackPolicyByte,
1398
1722
  deliverPolicy: deliverPolicyToU8(config.deliverPolicy),
1399
- deliverMode: config.fanout ? 1 : 0,
1723
+ deliverMode: config.fanout ? 0 : 1,
1400
1724
  ackWaitMs: config.ackWaitMs ?? 0,
1401
1725
  startSeq: BigInt(config.startSeq ?? 0)
1402
1726
  };
@@ -1511,6 +1835,11 @@ var ArbitroClient = class {
1511
1835
  stream(name, config) {
1512
1836
  return new Stream(this, name, config);
1513
1837
  }
1838
+ // ── Cron ──────────────────────────────────────────────────────────────────
1839
+ /** Start building a cron job. Call `.every()` then `.run()` to register. */
1840
+ cron(name) {
1841
+ return new CronBuilder(this.conn, this._cronState, name);
1842
+ }
1514
1843
  // ── Lifecycle ─────────────────────────────────────────────────────────────
1515
1844
  async close() {
1516
1845
  await this.conn.close();
@@ -1590,6 +1919,257 @@ function streamId(name) {
1590
1919
  return h >>> 0;
1591
1920
  }
1592
1921
 
1922
+ // src/workflow/task.ts
1923
+ var TASK_HEADER = 7;
1924
+ var COMPENSATION_BIT = 32768;
1925
+ function encodeTask(instanceId, stepIndex, attempt, context) {
1926
+ const buf = Buffer.allocUnsafe(TASK_HEADER + context.length);
1927
+ buf.writeUInt32LE(instanceId, 0);
1928
+ buf.writeUInt16LE(stepIndex, 4);
1929
+ buf[6] = attempt;
1930
+ context.copy(buf, TASK_HEADER);
1931
+ return buf;
1932
+ }
1933
+ function decodeTask(payload) {
1934
+ if (payload.length < TASK_HEADER) return void 0;
1935
+ return {
1936
+ instanceId: payload.readUInt32LE(0),
1937
+ stepIndex: payload.readUInt16LE(4),
1938
+ attempt: payload[6],
1939
+ context: payload.subarray(TASK_HEADER)
1940
+ };
1941
+ }
1942
+
1943
+ // src/workflow/handle.ts
1944
+ var nextInstanceId = 1;
1945
+ function allocInstanceId() {
1946
+ return nextInstanceId++;
1947
+ }
1948
+ var WorkflowHandle = class {
1949
+ constructor(workflowName, taskStreamName, dlqStreamName, sub, triggerSub) {
1950
+ this.workflowName = workflowName;
1951
+ this.taskStreamName = taskStreamName;
1952
+ this.dlqStreamName = dlqStreamName;
1953
+ this.sub = sub;
1954
+ this.triggerSub = triggerSub;
1955
+ }
1956
+ get name() {
1957
+ return this.workflowName;
1958
+ }
1959
+ get taskStream() {
1960
+ return this.taskStreamName;
1961
+ }
1962
+ get dlqStream() {
1963
+ return this.dlqStreamName;
1964
+ }
1965
+ async trigger(client, context) {
1966
+ const instanceId = allocInstanceId();
1967
+ const msgId = `wf:${instanceId}:0:0`;
1968
+ const subject = `_wf.${this.workflowName}.step.0`;
1969
+ const task = encodeTask(instanceId, 0, 0, context);
1970
+ await client.publish(this.taskStreamName, subject, task, { msgId });
1971
+ return instanceId;
1972
+ }
1973
+ };
1974
+
1975
+ // src/workflow/processor.ts
1976
+ async function processMessage(cfg, msg) {
1977
+ const task = decodeTask(msg.data());
1978
+ if (!task) {
1979
+ msg.ack();
1980
+ return;
1981
+ }
1982
+ if (task.context.length > cfg.maxContextSize) {
1983
+ msg.ack();
1984
+ return;
1985
+ }
1986
+ const isCompensation = (task.stepIndex & COMPENSATION_BIT) !== 0;
1987
+ if (isCompensation) {
1988
+ await runCompensation(cfg, msg, task);
1989
+ return;
1990
+ }
1991
+ if (task.stepIndex >= cfg.steps.length) {
1992
+ msg.ack();
1993
+ return;
1994
+ }
1995
+ await runStep(cfg, msg, task);
1996
+ }
1997
+ async function runCompensation(cfg, msg, task) {
1998
+ const idx = task.stepIndex & ~COMPENSATION_BIT;
1999
+ const comp = cfg.steps[idx]?.compensation;
2000
+ if (comp) {
2001
+ try {
2002
+ await comp({ name: cfg.name, instanceId: task.instanceId, stepIndex: idx, attempt: task.attempt, context: task.context });
2003
+ } catch {
2004
+ }
2005
+ }
2006
+ msg.ack();
2007
+ }
2008
+ async function runStep(cfg, msg, task) {
2009
+ const handler = cfg.steps[task.stepIndex].handler;
2010
+ try {
2011
+ const result = await handler({
2012
+ name: cfg.name,
2013
+ instanceId: task.instanceId,
2014
+ stepIndex: task.stepIndex,
2015
+ attempt: task.attempt,
2016
+ context: task.context
2017
+ });
2018
+ if (result.context.length > cfg.maxContextSize) {
2019
+ msg.nack();
2020
+ return;
2021
+ }
2022
+ await advance(cfg, msg, task, result);
2023
+ } catch (err) {
2024
+ await onFailure(cfg, msg, task, err);
2025
+ }
2026
+ }
2027
+ async function advance(cfg, msg, task, result) {
2028
+ const nextStep = task.stepIndex + 1;
2029
+ if (nextStep < cfg.steps.length) {
2030
+ const msgId = `wf:${task.instanceId}:${nextStep}:0`;
2031
+ const subject = `_wf.${cfg.name}.step.${nextStep}`;
2032
+ const buf = encodeTask(task.instanceId, nextStep, 0, result.context);
2033
+ await cfg.client.publish(cfg.taskStreamName, subject, buf, { msgId });
2034
+ }
2035
+ msg.ack();
2036
+ }
2037
+ async function onFailure(cfg, msg, task, err) {
2038
+ if (task.attempt >= cfg.maxRetries) {
2039
+ await publishDlq(cfg, task, err);
2040
+ await publishCompensations(cfg, task);
2041
+ msg.ack();
2042
+ } else {
2043
+ msg.nack();
2044
+ }
2045
+ }
2046
+ async function publishDlq(cfg, task, err) {
2047
+ const dlqSubject = `_wf.${cfg.name}.dlq.${task.stepIndex}`;
2048
+ const errBytes = Buffer.from(String(err));
2049
+ const buf = Buffer.allocUnsafe(7 + 4 + errBytes.length + task.context.length);
2050
+ buf.writeUInt32LE(task.instanceId, 0);
2051
+ buf.writeUInt16LE(task.stepIndex, 4);
2052
+ buf[6] = task.attempt;
2053
+ buf.writeUInt32LE(errBytes.length, 7);
2054
+ errBytes.copy(buf, 11);
2055
+ task.context.copy(buf, 11 + errBytes.length);
2056
+ const msgId = `wf:${task.instanceId}:dlq:${task.stepIndex}`;
2057
+ await cfg.client.publish(cfg.dlqStreamName, dlqSubject, buf, { msgId }).catch(() => {
2058
+ });
2059
+ }
2060
+ async function publishCompensations(cfg, task) {
2061
+ for (let i = task.stepIndex - 1; i >= 0; i--) {
2062
+ const compStep = COMPENSATION_BIT | i;
2063
+ const subject = `_wf.${cfg.name}.compensate.${i}`;
2064
+ const buf = encodeTask(task.instanceId, compStep, 0, task.context);
2065
+ const msgId = `wf:${task.instanceId}:comp:${i}`;
2066
+ await cfg.client.publish(cfg.taskStreamName, subject, buf, { msgId }).catch(() => {
2067
+ });
2068
+ }
2069
+ }
2070
+
2071
+ // src/workflow/workflow.ts
2072
+ var nextWorkerUid = 1;
2073
+ var WorkflowBuilder = class {
2074
+ constructor(client, workflowName) {
2075
+ this.client = client;
2076
+ this.workflowName = workflowName;
2077
+ }
2078
+ triggerSubject;
2079
+ triggerStreamName;
2080
+ steps = [];
2081
+ ackWaitMs = 3e4;
2082
+ maxInflightVal = 10;
2083
+ maxRetriesVal = 3;
2084
+ maxContextSizeVal = 256 * 1024;
2085
+ trigger(subject) {
2086
+ this.triggerSubject = subject;
2087
+ return this;
2088
+ }
2089
+ triggerStream(streamName) {
2090
+ this.triggerStreamName = streamName;
2091
+ return this;
2092
+ }
2093
+ step(name, handler) {
2094
+ this.steps.push({ name, handler, compensation: void 0 });
2095
+ return this;
2096
+ }
2097
+ /** Compensation handler for the most recently added step. */
2098
+ compensate(_stepName, handler) {
2099
+ const last = this.steps[this.steps.length - 1];
2100
+ if (last) last.compensation = handler;
2101
+ return this;
2102
+ }
2103
+ ackWait(ms) {
2104
+ this.ackWaitMs = ms;
2105
+ return this;
2106
+ }
2107
+ inflight(n) {
2108
+ this.maxInflightVal = n;
2109
+ return this;
2110
+ }
2111
+ maxRetries(n) {
2112
+ this.maxRetriesVal = n;
2113
+ return this;
2114
+ }
2115
+ maxContextSize(bytes) {
2116
+ this.maxContextSizeVal = bytes;
2117
+ return this;
2118
+ }
2119
+ async start() {
2120
+ if (!this.triggerSubject) throw new Error("trigger subject required");
2121
+ if (this.steps.length === 0) throw new Error("at least one step required");
2122
+ const name = this.workflowName;
2123
+ const taskStream = `_wf.${name}.tasks`;
2124
+ const taskSubject = `_wf.${name}.>`;
2125
+ const dlqStream = `_wf.${name}.dlq`;
2126
+ const dlqSubject = `_wf.${name}.dlq.>`;
2127
+ await this.client.upsertStream(taskStream, { subjectFilter: taskSubject, idempotencyWindowMs: 3e5 });
2128
+ await this.client.upsertStream(dlqStream, { subjectFilter: dlqSubject });
2129
+ const cfg = {
2130
+ client: this.client,
2131
+ name,
2132
+ taskStreamName: taskStream,
2133
+ dlqStreamName: dlqStream,
2134
+ steps: this.steps,
2135
+ maxContextSize: this.maxContextSizeVal,
2136
+ maxRetries: this.maxRetriesVal
2137
+ };
2138
+ const sub = await this.subscribeWorker(cfg, taskStream, taskSubject);
2139
+ const triggerSub = await this.subscribeTrigger(taskStream, name);
2140
+ return new WorkflowHandle(name, taskStream, dlqStream, sub, triggerSub);
2141
+ }
2142
+ async subscribeWorker(cfg, taskStream, taskSubject) {
2143
+ const uid = nextWorkerUid++;
2144
+ return this.client.subscribe(taskStream, {
2145
+ name: `_wf_${cfg.name}_w${uid}`,
2146
+ group: `_wf_${cfg.name}_workers`,
2147
+ filter: taskSubject,
2148
+ ackPolicy: "explicit" /* Explicit */,
2149
+ ackWaitMs: this.ackWaitMs,
2150
+ maxAckPending: this.maxInflightVal
2151
+ }, (msg) => {
2152
+ void processMessage(cfg, msg);
2153
+ });
2154
+ }
2155
+ async subscribeTrigger(taskStream, name) {
2156
+ if (!this.triggerSubject || !this.triggerStreamName) return void 0;
2157
+ const subject = this.triggerSubject;
2158
+ return this.client.subscribe(this.triggerStreamName, {
2159
+ name: `_wf_${name}_trigger`,
2160
+ filter: subject,
2161
+ ackPolicy: "explicit" /* Explicit */,
2162
+ ackWaitMs: this.ackWaitMs,
2163
+ maxAckPending: 1
2164
+ }, async (msg) => {
2165
+ const id = allocInstanceId();
2166
+ const taskBuf = encodeTask(id, 0, 0, msg.data());
2167
+ await this.client.publish(taskStream, `_wf.${name}.step.0`, taskBuf, { msgId: `wf:${id}:0:0` });
2168
+ msg.ack();
2169
+ });
2170
+ }
2171
+ };
2172
+
1593
2173
  // src/utils/zod.ts
1594
2174
  var import_msgpackr2 = require("msgpackr");
1595
2175
  var packr = new import_msgpackr2.Packr({ structuredClone: false, useRecords: false });
@@ -1611,8 +2191,11 @@ function zodCodec(zodSchema) {
1611
2191
  AckPolicy,
1612
2192
  ArbitroClient,
1613
2193
  ArbitroError,
2194
+ COMPENSATION_BIT,
1614
2195
  Codec,
1615
2196
  Consumer,
2197
+ CronBuilder,
2198
+ CronHandle,
1616
2199
  DeliverPolicy,
1617
2200
  ErrorCode,
1618
2201
  JournalType,
@@ -1622,6 +2205,8 @@ function zodCodec(zodSchema) {
1622
2205
  StringCodec,
1623
2206
  Subscription,
1624
2207
  Topic,
2208
+ WorkflowBuilder,
2209
+ WorkflowHandle,
1625
2210
  decodeJson,
1626
2211
  decodeString,
1627
2212
  encodeJson,