@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 +32 -22
- package/dist/chunk-6BCX2E2R.mjs +102 -0
- package/dist/chunk-6BCX2E2R.mjs.map +1 -0
- package/dist/chunk-SKCXQO7R.mjs +124 -0
- package/dist/chunk-SKCXQO7R.mjs.map +1 -0
- package/dist/constants-KF57DJ2L.mjs +33 -0
- package/dist/constants-KF57DJ2L.mjs.map +1 -0
- package/dist/index.d.mts +69 -1
- package/dist/index.d.ts +69 -1
- package/dist/index.js +422 -94
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +184 -98
- package/dist/index.mjs.map +1 -1
- package/dist/publish-UA3YZGMK.mjs +14 -0
- package/dist/publish-UA3YZGMK.mjs.map +1 -0
- package/package.json +1 -1
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
|
|
1058
|
+
var EMPTY = Buffer.alloc(0);
|
|
1078
1059
|
function toMsgIdBuf(id) {
|
|
1079
|
-
if (id == null) return
|
|
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 ?
|
|
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,
|