@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/dist/index.mjs CHANGED
@@ -1,3 +1,18 @@
1
+ import {
2
+ frame,
3
+ packDisconnect,
4
+ packHello,
5
+ packPublish,
6
+ packPublishBatch,
7
+ packPublishWithReply
8
+ } from "./chunk-SKCXQO7R.mjs";
9
+ import {
10
+ HEADER_SIZE,
11
+ OFF_ACTION,
12
+ OFF_MSG_LEN,
13
+ OFF_SEQ
14
+ } from "./chunk-6BCX2E2R.mjs";
15
+
1
16
  // src/types/config.ts
2
17
  var DeliverPolicy = /* @__PURE__ */ ((DeliverPolicy2) => {
3
18
  DeliverPolicy2["All"] = "All";
@@ -177,101 +192,6 @@ function makeLazyMessage(raw, codec, fields, onAck, onNack, onNackDelay) {
177
192
  return msg;
178
193
  }
179
194
 
180
- // src/proto/constants.ts
181
- var MAGIC_V2 = 843207233;
182
- var HELLO_SIZE = 8;
183
- var CURRENT_VERSION = 2;
184
- var HEADER_SIZE = 16;
185
- var OFF_ACTION = 0;
186
- var OFF_FLAGS = 2;
187
- var OFF_ENTRY_FLAGS = 3;
188
- var OFF_MSG_LEN = 4;
189
- var OFF_SEQ = 8;
190
-
191
- // src/proto/frame.ts
192
- function frame(action, seq, bodyLen, flags = 0, entryFlags = 0) {
193
- const buf = Buffer.allocUnsafe(HEADER_SIZE + bodyLen);
194
- buf.writeUInt16LE(action, OFF_ACTION);
195
- buf[OFF_FLAGS] = flags;
196
- buf[OFF_ENTRY_FLAGS] = entryFlags;
197
- buf.writeUInt32LE(bodyLen, OFF_MSG_LEN);
198
- buf.writeBigUInt64LE(seq, OFF_SEQ);
199
- return buf;
200
- }
201
- function packHello(caps = 2 /* Reply */) {
202
- const buf = Buffer.allocUnsafe(HELLO_SIZE);
203
- buf.writeUInt32LE(MAGIC_V2, 0);
204
- buf[4] = CURRENT_VERSION;
205
- buf[5] = 0 /* Client */;
206
- buf.writeUInt16LE(caps, 6);
207
- return buf;
208
- }
209
- function packDisconnect(seq) {
210
- return frame(1541 /* Disconnect */, seq, 0);
211
- }
212
-
213
- // src/proto/publish.ts
214
- var EMPTY = Buffer.alloc(0);
215
- function packPublish(seq, streamId2, subject, payload, flags = 0, entryFlags = 0, msgId = EMPTY) {
216
- const buf = frame(
217
- 257 /* Publish */,
218
- seq,
219
- 8 + subject.length + msgId.length + payload.length,
220
- flags,
221
- entryFlags
222
- );
223
- buf.writeUInt32LE(streamId2, HEADER_SIZE);
224
- buf.writeUInt16LE(subject.length, HEADER_SIZE + 4);
225
- buf.writeUInt16LE(msgId.length, HEADER_SIZE + 6);
226
- let off = HEADER_SIZE + 8;
227
- subject.copy(buf, off);
228
- off += subject.length;
229
- msgId.copy(buf, off);
230
- off += msgId.length;
231
- payload.copy(buf, off);
232
- return buf;
233
- }
234
- function packPublishWithReply(seq, streamId2, subject, replyTo, payload, flags = 0, entryFlags = 0) {
235
- const tail = subject.length + replyTo.length + payload.length;
236
- const buf = frame(260 /* PublishWithReply */, seq, 12 + tail, flags, entryFlags);
237
- buf.writeUInt32LE(streamId2, HEADER_SIZE);
238
- buf.writeUInt16LE(subject.length, HEADER_SIZE + 4);
239
- buf.writeUInt16LE(replyTo.length, HEADER_SIZE + 6);
240
- buf.writeUInt32LE(0, HEADER_SIZE + 8);
241
- let off = HEADER_SIZE + 12;
242
- subject.copy(buf, off);
243
- off += subject.length;
244
- replyTo.copy(buf, off);
245
- off += replyTo.length;
246
- payload.copy(buf, off);
247
- return buf;
248
- }
249
- function packPublishBatch(seq, streamId2, entries, flags = 0, entryFlags = 0) {
250
- let tail = 0;
251
- for (const e of entries) {
252
- const midLen = e.msgId ? e.msgId.length : 0;
253
- tail += 8 + e.subject.length + midLen + e.payload.length;
254
- }
255
- const buf = frame(259 /* PublishBatch */, seq, 8 + tail, flags, entryFlags);
256
- buf.writeUInt32LE(streamId2, HEADER_SIZE);
257
- buf.writeUInt32LE(entries.length, HEADER_SIZE + 4);
258
- let off = HEADER_SIZE + 8;
259
- for (const e of entries) {
260
- const mid = e.msgId ?? EMPTY;
261
- buf.writeUInt16LE(e.subject.length, off);
262
- buf.writeUInt16LE(mid.length, off + 2);
263
- buf.writeUInt32LE(e.payload.length, off + 4);
264
- off += 8;
265
- buf.write(e.subject, off);
266
- off += e.subject.length;
267
- mid.copy(buf, off);
268
- off += mid.length;
269
- e.payload.copy(buf, off);
270
- off += e.payload.length;
271
- }
272
- return buf;
273
- }
274
-
275
195
  // src/proto/delivery.ts
276
196
  function packCold(action, seq, body) {
277
197
  const utf8 = Buffer.from(JSON.stringify(body), "utf8");
@@ -572,6 +492,37 @@ function resolveLogger(logger) {
572
492
  return logger ?? NOOP;
573
493
  }
574
494
 
495
+ // src/cron/cron-frame.ts
496
+ function packCreateCron(seq, body) {
497
+ const json = Buffer.from(JSON.stringify(body), "utf8");
498
+ const buf = frame(1793 /* CreateCron */, seq, json.length);
499
+ json.copy(buf, HEADER_SIZE);
500
+ return buf;
501
+ }
502
+ function packDeleteCron(seq, name) {
503
+ const buf = frame(1794 /* DeleteCron */, seq, name.length);
504
+ name.copy(buf, HEADER_SIZE);
505
+ return buf;
506
+ }
507
+ var CRON_FIRE_FIXED = 2 + 8 + 8;
508
+ function decodeCronFire(body) {
509
+ if (body.length < CRON_FIRE_FIXED) return void 0;
510
+ const nameLen = body.readUInt16LE(0);
511
+ if (body.length < CRON_FIRE_FIXED + nameLen) return void 0;
512
+ const fireTimeMs = body.readBigUInt64LE(2);
513
+ const fireCount = body.readBigUInt64LE(10);
514
+ const name = body.subarray(18, 18 + nameLen).toString();
515
+ return { name, fireTimeMs, fireCount };
516
+ }
517
+ function packCronAck(seq, name, ok) {
518
+ const bodyLen = 3 + name.length;
519
+ const buf = frame(1797 /* CronAck */, seq, bodyLen);
520
+ buf.writeUInt16LE(name.length, HEADER_SIZE);
521
+ buf[HEADER_SIZE + 2] = ok ? 0 : 1;
522
+ name.copy(buf, HEADER_SIZE + 3);
523
+ return buf;
524
+ }
525
+
575
526
  // src/net/connection.ts
576
527
  function parseAddr(addr) {
577
528
  const i = addr.lastIndexOf(":");
@@ -595,6 +546,7 @@ var Connection = class _Connection {
595
546
  log;
596
547
  activeSubs = /* @__PURE__ */ new Map();
597
548
  metrics;
549
+ cronState;
598
550
  attachSocket(socket) {
599
551
  socket.setNoDelay(true);
600
552
  socket.on("data", (chunk) => this.framer.push(chunk, (f) => this.onFrame(f)));
@@ -649,6 +601,10 @@ var Connection = class _Connection {
649
601
  setMetrics(m) {
650
602
  this.metrics = m;
651
603
  }
604
+ /** Attach cron state so the connection can dispatch CronFire frames. */
605
+ setCronState(s) {
606
+ this.cronState = s;
607
+ }
652
608
  // ── Frame routing ─────────────────────────────────────────────────────────
653
609
  // Seq-based dispatch: match reply.header.seq → pending request. O(1).
654
610
  resolvePending(frame2) {
@@ -700,6 +656,10 @@ var Connection = class _Connection {
700
656
  this.handleBatchDeliver(frame2);
701
657
  return;
702
658
  }
659
+ case 1796 /* CronFire */: {
660
+ this.dispatchCronFire(frame2);
661
+ return;
662
+ }
703
663
  case 1538 /* Pong */:
704
664
  return;
705
665
  default: {
@@ -749,6 +709,19 @@ var Connection = class _Connection {
749
709
  off = tailEnd;
750
710
  }
751
711
  }
712
+ // ── Cron dispatch ──────────────────────────────────────────────────────────
713
+ dispatchCronFire(frame2) {
714
+ const body = frame2.subarray(HEADER_SIZE);
715
+ const view = decodeCronFire(body);
716
+ if (!view) return;
717
+ const handler = this.cronState?.getHandler(view.name);
718
+ const nameBuf = Buffer.from(view.name);
719
+ if (!handler) {
720
+ this.send(packCronAck(this.nextSeq(), nameBuf, false));
721
+ return;
722
+ }
723
+ 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)));
724
+ }
752
725
  // ── Subscriptions ─────────────────────────────────────────────────────────
753
726
  async sendSubscribeV2(consumerId, filter, handler, onRenew) {
754
727
  return new Promise((resolve, reject) => {
@@ -791,6 +764,14 @@ var Connection = class _Connection {
791
764
  }).catch(() => {
792
765
  });
793
766
  }
767
+ this.replayCrons();
768
+ }
769
+ replayCrons() {
770
+ if (!this.cronState) return;
771
+ for (const { config } of this.cronState.allConfigs()) {
772
+ const seq = this.nextSeq();
773
+ this.socket.write(packCreateCron(seq, config));
774
+ }
794
775
  }
795
776
  // ── Routes (internal use) ─────────────────────────────────────────────────
796
777
  registerRoute(consumerId, handler) {
@@ -1074,9 +1055,9 @@ var ClientMetrics = class {
1074
1055
  };
1075
1056
 
1076
1057
  // src/stream/publish.ts
1077
- var EMPTY2 = Buffer.alloc(0);
1058
+ var EMPTY = Buffer.alloc(0);
1078
1059
  function toMsgIdBuf(id) {
1079
- if (id == null) return EMPTY2;
1060
+ if (id == null) return EMPTY;
1080
1061
  return typeof id === "string" ? Buffer.from(id) : id;
1081
1062
  }
1082
1063
  async function streamPublishAck(conn, sid, subject, data, opts) {
@@ -1105,6 +1086,86 @@ async function streamRequest(conn, sid, subject, data, timeoutMs) {
1105
1086
  return Buffer.alloc(0);
1106
1087
  }
1107
1088
 
1089
+ // src/cron/cron-builder.ts
1090
+ var CronBuilder = class {
1091
+ constructor(conn, cronState, cronName) {
1092
+ this.conn = conn;
1093
+ this.cronState = cronState;
1094
+ this.cronName = cronName;
1095
+ }
1096
+ expr;
1097
+ timezone;
1098
+ timeoutMs = 3e4;
1099
+ allowOverlap = false;
1100
+ every(expression) {
1101
+ this.expr = expression;
1102
+ return this;
1103
+ }
1104
+ tz(timezone) {
1105
+ this.timezone = timezone;
1106
+ return this;
1107
+ }
1108
+ timeout(ms) {
1109
+ this.timeoutMs = ms;
1110
+ return this;
1111
+ }
1112
+ overlap(allow) {
1113
+ this.allowOverlap = allow;
1114
+ return this;
1115
+ }
1116
+ async run(handler) {
1117
+ if (!this.expr) throw new Error("cron expression required \u2014 call .every()");
1118
+ const body = {
1119
+ name: this.cronName,
1120
+ every: this.expr,
1121
+ tz: this.timezone,
1122
+ timeout_ms: this.timeoutMs,
1123
+ overlap: this.allowOverlap
1124
+ };
1125
+ const seq = this.conn.nextSeq();
1126
+ await this.conn.sendExpectReply(packCreateCron(seq, body));
1127
+ this.cronState.register(this.cronName, body, handler);
1128
+ return new CronHandle(this.conn, this.cronState, this.cronName);
1129
+ }
1130
+ };
1131
+ var CronHandle = class {
1132
+ constructor(conn, cronState, cronName) {
1133
+ this.conn = conn;
1134
+ this.cronState = cronState;
1135
+ this.cronName = cronName;
1136
+ }
1137
+ get name() {
1138
+ return this.cronName;
1139
+ }
1140
+ async stop() {
1141
+ const nameBuf = Buffer.from(this.cronName);
1142
+ const seq = this.conn.nextSeq();
1143
+ await this.conn.sendExpectReply(packDeleteCron(seq, nameBuf));
1144
+ this.cronState.remove(this.cronName);
1145
+ }
1146
+ };
1147
+
1148
+ // src/cron/cron-state.ts
1149
+ var CronState = class {
1150
+ handlers = /* @__PURE__ */ new Map();
1151
+ register(name, config, handler) {
1152
+ this.handlers.set(name, { handler, config });
1153
+ }
1154
+ remove(name) {
1155
+ this.handlers.delete(name);
1156
+ }
1157
+ getHandler(name) {
1158
+ return this.handlers.get(name)?.handler;
1159
+ }
1160
+ allConfigs() {
1161
+ const out = [];
1162
+ for (const [name, entry] of this.handlers) {
1163
+ out.push({ name, config: entry.config });
1164
+ }
1165
+ return out;
1166
+ }
1167
+ };
1168
+
1108
1169
  // src/client/client.ts
1109
1170
  var DEFAULT_CONFIG = {
1110
1171
  servers: ["127.0.0.1:9898"],
@@ -1119,6 +1180,7 @@ var ArbitroClient = class {
1119
1180
  logger;
1120
1181
  sidCache = /* @__PURE__ */ new Map();
1121
1182
  _metrics = new ClientMetrics();
1183
+ _cronState = new CronState();
1122
1184
  constructor(config) {
1123
1185
  this.cfg = { ...DEFAULT_CONFIG, ...config };
1124
1186
  this.tls = config.tls;
@@ -1135,6 +1197,7 @@ var ArbitroClient = class {
1135
1197
  this.logger
1136
1198
  );
1137
1199
  this.conn.setMetrics(this._metrics);
1200
+ this.conn.setCronState(this._cronState);
1138
1201
  return this;
1139
1202
  }
1140
1203
  /**
@@ -1220,6 +1283,22 @@ var ArbitroClient = class {
1220
1283
  const sid = await this.resolveStreamId(streamName);
1221
1284
  return streamRequest(this.conn, sid, subject, data, timeoutMs);
1222
1285
  }
1286
+ /**
1287
+ * Publish a message with a delivery delay. The broker parks the message
1288
+ * in its delayed journal and delivers it to consumers after `delayMs`
1289
+ * milliseconds. Returns a Promise that resolves once the broker confirms
1290
+ * receipt.
1291
+ */
1292
+ async publishDelayed(streamName, subject, data, delayMs) {
1293
+ const sid = await this.resolveStreamId(streamName);
1294
+ const { packPublishDelayed: packPublishDelayed2 } = await import("./publish-UA3YZGMK.mjs");
1295
+ const { Flag: Flag3 } = await import("./constants-KF57DJ2L.mjs");
1296
+ const subj = Buffer.from(this.prefixed(subject));
1297
+ await this.conn.sendExpectReply(
1298
+ packPublishDelayed2(this.conn.nextSeq(), sid, subj, data, BigInt(delayMs), Flag3.AckReq)
1299
+ );
1300
+ this._metrics.publishesSent++;
1301
+ }
1223
1302
  async subscribe(streamName, configOrCb, callbackOrOpts, opts) {
1224
1303
  let config;
1225
1304
  let callback;
@@ -1339,7 +1418,7 @@ var ArbitroClient = class {
1339
1418
  maxInflight: config.maxAckPending ?? 0,
1340
1419
  ackPolicy: ackPolicyByte,
1341
1420
  deliverPolicy: deliverPolicyToU8(config.deliverPolicy),
1342
- deliverMode: config.fanout ? 1 : 0,
1421
+ deliverMode: config.fanout ? 0 : 1,
1343
1422
  ackWaitMs: config.ackWaitMs ?? 0,
1344
1423
  startSeq: BigInt(config.startSeq ?? 0)
1345
1424
  };
@@ -1454,6 +1533,11 @@ var ArbitroClient = class {
1454
1533
  stream(name, config) {
1455
1534
  return new Stream(this, name, config);
1456
1535
  }
1536
+ // ── Cron ──────────────────────────────────────────────────────────────────
1537
+ /** Start building a cron job. Call `.every()` then `.run()` to register. */
1538
+ cron(name) {
1539
+ return new CronBuilder(this.conn, this._cronState, name);
1540
+ }
1457
1541
  // ── Lifecycle ─────────────────────────────────────────────────────────────
1458
1542
  async close() {
1459
1543
  await this.conn.close();
@@ -1555,6 +1639,8 @@ export {
1555
1639
  ArbitroError,
1556
1640
  Codec,
1557
1641
  Consumer,
1642
+ CronBuilder,
1643
+ CronHandle,
1558
1644
  DeliverPolicy,
1559
1645
  ErrorCode,
1560
1646
  JournalType,