@arbitro/client 0.2.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 ADDED
@@ -0,0 +1,1634 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ AckPolicy: () => AckPolicy,
34
+ ArbitroClient: () => ArbitroClient,
35
+ ArbitroError: () => ArbitroError,
36
+ Codec: () => Codec,
37
+ Consumer: () => Consumer,
38
+ DeliverPolicy: () => DeliverPolicy,
39
+ ErrorCode: () => ErrorCode,
40
+ JournalType: () => JournalType,
41
+ JsonCodec: () => JsonCodec,
42
+ Message: () => Message,
43
+ Stream: () => Stream,
44
+ StringCodec: () => StringCodec,
45
+ Subscription: () => Subscription,
46
+ Topic: () => Topic,
47
+ decodeJson: () => decodeJson,
48
+ decodeString: () => decodeString,
49
+ encodeJson: () => encodeJson,
50
+ encodeString: () => encodeString,
51
+ makeLazyMessage: () => makeLazyMessage,
52
+ schema: () => schema,
53
+ streamId: () => streamId,
54
+ zodCodec: () => zodCodec
55
+ });
56
+ module.exports = __toCommonJS(index_exports);
57
+
58
+ // src/types/config.ts
59
+ var DeliverPolicy = /* @__PURE__ */ ((DeliverPolicy2) => {
60
+ DeliverPolicy2["All"] = "All";
61
+ DeliverPolicy2["Last"] = "Last";
62
+ DeliverPolicy2["New"] = "New";
63
+ DeliverPolicy2["BySeq"] = "ByStartSeq";
64
+ DeliverPolicy2["ByTime"] = "ByStartTime";
65
+ return DeliverPolicy2;
66
+ })(DeliverPolicy || {});
67
+ var JournalType = /* @__PURE__ */ ((JournalType2) => {
68
+ JournalType2["Memory"] = "Memory";
69
+ JournalType2["Tolerant"] = "Tolerant";
70
+ JournalType2["Strict"] = "Strict";
71
+ return JournalType2;
72
+ })(JournalType || {});
73
+ var AckPolicy = /* @__PURE__ */ ((AckPolicy2) => {
74
+ AckPolicy2["Explicit"] = "explicit";
75
+ AckPolicy2["None"] = "none";
76
+ return AckPolicy2;
77
+ })(AckPolicy || {});
78
+
79
+ // src/types/error.ts
80
+ var ArbitroError = class extends Error {
81
+ constructor(message, code, brokerName, brokerDetails, wireCode) {
82
+ super(message);
83
+ this.code = code;
84
+ this.brokerName = brokerName;
85
+ this.brokerDetails = brokerDetails;
86
+ this.wireCode = wireCode;
87
+ this.name = "ArbitroError";
88
+ }
89
+ };
90
+ var ErrorCode = {
91
+ // 0x00xx — Protocol
92
+ UnknownAction: 1,
93
+ BufferTooShort: 2,
94
+ InvalidLength: 3,
95
+ InvalidEntryCount: 4,
96
+ // 0x01xx — Auth
97
+ AuthRequired: 257,
98
+ AuthFailed: 258,
99
+ // 0x02xx — Stream
100
+ StreamNotFound: 513,
101
+ StreamAlreadyExists: 514,
102
+ StreamFull: 515,
103
+ StreamFilterOverlap: 516,
104
+ SubjectNotFound: 517,
105
+ /** Publish carried a `msgId` already seen for this stream within
106
+ * `idempotencyWindowMs`. Original write stands; safe to treat as
107
+ * a successful publish at the application level. */
108
+ IdempotencyDuplicate: 518,
109
+ // 0x03xx — Consumer
110
+ ConsumerNotFound: 769,
111
+ ConsumerAlreadyExists: 770,
112
+ ConsumerFilterOverlap: 771,
113
+ // 0x04xx — Delivery
114
+ InvalidSequence: 1025,
115
+ MaxInflightReached: 1026,
116
+ AckTimeout: 1027,
117
+ // 0x05xx — System
118
+ ServerShuttingDown: 1281,
119
+ InternalError: 1282
120
+ };
121
+
122
+ // src/utils/text.ts
123
+ function encodeString(s) {
124
+ return Buffer.from(s, "utf8");
125
+ }
126
+ function decodeString(buf) {
127
+ return buf.toString("utf8");
128
+ }
129
+
130
+ // src/utils/json.ts
131
+ function encodeJson(value) {
132
+ return Buffer.from(JSON.stringify(value), "utf8");
133
+ }
134
+ function decodeJson(buf) {
135
+ return JSON.parse(buf.toString("utf8"));
136
+ }
137
+
138
+ // src/utils/codec.ts
139
+ var import_msgpackr = require("msgpackr");
140
+ var TextEncoding = class {
141
+ encode(value) {
142
+ return Buffer.from(value, this.encoding);
143
+ }
144
+ decode(buf) {
145
+ return buf.toString(this.encoding);
146
+ }
147
+ };
148
+ var StringCodec = class extends TextEncoding {
149
+ encoding;
150
+ constructor(encoding = "utf8") {
151
+ super();
152
+ this.encoding = encoding;
153
+ }
154
+ };
155
+ var JsonCodec = class {
156
+ text;
157
+ constructor(encoding = "utf8") {
158
+ this.text = new StringCodec(encoding);
159
+ }
160
+ encode(value) {
161
+ return this.text.encode(JSON.stringify(value));
162
+ }
163
+ decode(buf) {
164
+ return JSON.parse(this.text.decode(buf));
165
+ }
166
+ };
167
+ var Codec = class {
168
+ fields;
169
+ packr;
170
+ unpackr;
171
+ constructor(schema2) {
172
+ this.fields = Object.keys(schema2);
173
+ this.packr = new import_msgpackr.Packr({ structuredClone: false, useRecords: false });
174
+ this.unpackr = new import_msgpackr.Unpackr({ structuredClone: false, useRecords: false });
175
+ }
176
+ // Encodes only known fields in definition order — no extras, no key enumeration.
177
+ encode(value) {
178
+ const obj = {};
179
+ for (const k of this.fields) obj[k] = value[k];
180
+ return Buffer.from(this.packr.pack(obj));
181
+ }
182
+ decode(buf) {
183
+ return this.unpackr.unpack(buf);
184
+ }
185
+ };
186
+ function schema(def) {
187
+ return new Codec(def);
188
+ }
189
+
190
+ // src/topic/topic.ts
191
+ var Topic = class {
192
+ constructor(stream, subject, codec) {
193
+ this.stream = stream;
194
+ this.subject = subject;
195
+ this.codec = codec;
196
+ this.fields = codec.fields ?? [];
197
+ }
198
+ fields;
199
+ publish(value) {
200
+ this.stream.publish(this.subject, this.codec.encode(value));
201
+ }
202
+ async publishAck(value) {
203
+ await this.stream.publishAck(this.subject, this.codec.encode(value));
204
+ }
205
+ publishBatch(values) {
206
+ this.stream.publishBatch(values.map((v) => ({
207
+ subject: this.subject,
208
+ payload: this.codec.encode(v)
209
+ })));
210
+ }
211
+ async subscribe(group, cb) {
212
+ const consumer = this.stream.consumer({ name: group });
213
+ return consumer.subscribe(this.codec, cb);
214
+ }
215
+ };
216
+
217
+ // src/topic/lazy-message.ts
218
+ function makeLazyMessage(raw, codec, fields, onAck, onNack, onNackDelay) {
219
+ let cache;
220
+ const lazy = () => cache ??= codec.decode(raw);
221
+ const msg = {
222
+ _raw: raw,
223
+ decode: lazy,
224
+ ack: onAck,
225
+ nack: onNack,
226
+ nackDelay: onNackDelay ?? onNack
227
+ };
228
+ for (const key of fields) {
229
+ Object.defineProperty(msg, key, {
230
+ get: () => lazy()[key],
231
+ enumerable: true
232
+ });
233
+ }
234
+ return msg;
235
+ }
236
+
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
+ }
269
+
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
+ }
331
+
332
+ // src/proto/delivery.ts
333
+ function packCold(action, seq, body) {
334
+ const utf8 = Buffer.from(JSON.stringify(body), "utf8");
335
+ const buf = frame(action, seq, utf8.length);
336
+ utf8.copy(buf, HEADER_SIZE);
337
+ return buf;
338
+ }
339
+ function packSubscribe(seq, _connId, consumerId, filter, _optionsFlags = 0) {
340
+ const filters = filter.length === 0 ? [] : [Array.from(filter)];
341
+ return packCold(769 /* Subscribe */, seq, {
342
+ consumer_id: consumerId >>> 0,
343
+ subscription_id: 0,
344
+ filters
345
+ });
346
+ }
347
+ function packUnsubscribe(seq, _connId, consumerId) {
348
+ return packCold(770 /* Unsubscribe */, seq, { consumer_id: consumerId >>> 0 });
349
+ }
350
+ function packAck(seq, consumerId, subjectHash, ackSeq) {
351
+ const buf = frame(513 /* Ack */, seq, 16);
352
+ buf.writeUInt32LE(consumerId, HEADER_SIZE);
353
+ buf.writeUInt32LE(subjectHash, HEADER_SIZE + 4);
354
+ buf.writeBigUInt64LE(ackSeq, HEADER_SIZE + 8);
355
+ return buf;
356
+ }
357
+ function packNack(seq, consumerId, subjectHash, nackSeq) {
358
+ const buf = frame(514 /* Nack */, seq, 16);
359
+ buf.writeUInt32LE(consumerId, HEADER_SIZE);
360
+ buf.writeUInt32LE(subjectHash, HEADER_SIZE + 4);
361
+ buf.writeBigUInt64LE(nackSeq, HEADER_SIZE + 8);
362
+ return buf;
363
+ }
364
+ function packBatchNack(seq, consumerId, entries) {
365
+ const buf = frame(522 /* BatchNack */, seq, 8 + entries.length * 16);
366
+ buf.writeUInt32LE(consumerId, HEADER_SIZE);
367
+ buf.writeUInt32LE(entries.length, HEADER_SIZE + 4);
368
+ let off = HEADER_SIZE + 8;
369
+ for (const e of entries) {
370
+ buf.writeBigUInt64LE(e.seq, off);
371
+ buf.writeUInt32LE(e.subjectHash, off + 8);
372
+ buf.writeUInt32LE(e.delayMs, off + 12);
373
+ off += 16;
374
+ }
375
+ return buf;
376
+ }
377
+
378
+ // src/proto/manage.ts
379
+ function packCold2(action, seq, body) {
380
+ const utf8 = Buffer.from(JSON.stringify(body), "utf8");
381
+ const buf = frame(action, seq, utf8.length);
382
+ utf8.copy(buf, HEADER_SIZE);
383
+ return buf;
384
+ }
385
+ function bytesArr(b) {
386
+ return Array.from(b);
387
+ }
388
+ function packCreateStream(seq, name, filter, maxMsgs, maxBytes, maxAgeSecs, replicas = 1, journalKind = 0, retention = 0, discard = 0, idempotencyWindowMs = 0) {
389
+ return packCold2(1025 /* CreateStream */, seq, {
390
+ name: bytesArr(name),
391
+ filter: bytesArr(filter),
392
+ max_msgs: Number(maxMsgs),
393
+ // u64 — fits in JS number if < 2^53
394
+ max_bytes: Number(maxBytes),
395
+ max_age_secs: Number(maxAgeSecs),
396
+ replicas,
397
+ journal_kind: journalKind,
398
+ retention,
399
+ discard,
400
+ idempotency_window_ms: idempotencyWindowMs >>> 0
401
+ });
402
+ }
403
+ var packDeleteStream = (seq, name) => packCold2(1026 /* DeleteStream */, seq, { name: bytesArr(name) });
404
+ var packGetStream = (seq, name) => packCold2(1027 /* GetStream */, seq, { name: bytesArr(name) });
405
+ var packPurgeStream = (seq, name) => packCold2(1029 /* PurgeStream */, seq, { name: bytesArr(name) });
406
+ var packDrainSubject = (seq, name, subject) => packCold2(1030 /* DrainSubject */, seq, {
407
+ name: bytesArr(name),
408
+ subject: bytesArr(subject)
409
+ });
410
+ var packListStreams = (seq, offset = 0, limit = 1e3) => packCold2(1028 /* ListStreams */, seq, { offset: offset >>> 0, limit: limit >>> 0 });
411
+ function packCreateConsumer(seq, opts) {
412
+ const limits = (opts.subjectLimits ?? []).map((l) => ({
413
+ pattern: bytesArr(l.pattern),
414
+ limit: l.limit >>> 0
415
+ }));
416
+ return packCold2(1281 /* CreateConsumer */, seq, {
417
+ stream_id: opts.streamId >>> 0,
418
+ name: bytesArr(opts.name),
419
+ group: bytesArr(opts.group),
420
+ subject: bytesArr(opts.filter),
421
+ max_inflight: Math.min(opts.maxInflight ?? 0, 65535),
422
+ ack_policy: opts.ackPolicy ?? 1,
423
+ deliver_policy: opts.deliverPolicy ?? 0,
424
+ deliver_mode: opts.deliverMode ?? 0,
425
+ ack_wait_ms: (opts.ackWaitMs ?? 0) >>> 0,
426
+ start_seq: Number(opts.startSeq ?? 0n),
427
+ subject_limits: limits
428
+ });
429
+ }
430
+ var packDeleteConsumer = (seq, consumerId) => packCold2(1282 /* DeleteConsumer */, seq, { consumer_id: consumerId >>> 0 });
431
+ var packGetConsumer = (seq, streamId2, name) => packCold2(1283 /* GetConsumer */, seq, {
432
+ stream_id: streamId2 >>> 0,
433
+ name: bytesArr(name)
434
+ });
435
+ var packListConsumers = (seq, streamId2 = 0, offset = 0, limit = 1e3) => packCold2(1284 /* ListConsumers */, seq, {
436
+ stream_id: streamId2 >>> 0,
437
+ offset: offset >>> 0,
438
+ limit: limit >>> 0
439
+ });
440
+ var packConsumerStats = (seq, consumerId) => packCold2(1285 /* ConsumerStats */, seq, { consumer_id: consumerId >>> 0 });
441
+
442
+ // src/message/message.ts
443
+ var BODY_OFF = HEADER_SIZE;
444
+ var BODY_SIZE = 12;
445
+ var TAIL_OFF = HEADER_SIZE + BODY_SIZE;
446
+ var OFF_CONSUMER = BODY_OFF;
447
+ var OFF_SUBJ_HASH = BODY_OFF + 4;
448
+ var OFF_SUBJ_LEN = BODY_OFF + 8;
449
+ var Message = class {
450
+ frame;
451
+ send;
452
+ seqFn;
453
+ _subjectLen;
454
+ constructor(frame2, send, seqFn) {
455
+ this.frame = frame2;
456
+ this.send = send;
457
+ this.seqFn = seqFn;
458
+ }
459
+ /** Delivery sequence — used to ack/nack this message. */
460
+ seq() {
461
+ return this.frame.readBigUInt64LE(OFF_SEQ);
462
+ }
463
+ /** Consumer ID that received this delivery. */
464
+ consumerId() {
465
+ return this.frame.readUInt32LE(OFF_CONSUMER);
466
+ }
467
+ /** Subject hash — echoed back in ack for O(1) credit release. */
468
+ subjectHash() {
469
+ return this.frame.readUInt32LE(OFF_SUBJ_HASH);
470
+ }
471
+ subjLen() {
472
+ return this._subjectLen ??= this.frame.readUInt16LE(OFF_SUBJ_LEN);
473
+ }
474
+ /** Zero-copy view of the subject bytes. */
475
+ subject() {
476
+ return this.frame.subarray(TAIL_OFF, TAIL_OFF + this.subjLen());
477
+ }
478
+ /** Zero-copy view of the payload bytes. */
479
+ data() {
480
+ return this.frame.subarray(TAIL_OFF + this.subjLen());
481
+ }
482
+ /** Acknowledge — fire-and-forget to broker. */
483
+ ack() {
484
+ this.send(packAck(
485
+ this.seqFn(),
486
+ this.consumerId(),
487
+ this.subjectHash(),
488
+ this.seq()
489
+ ));
490
+ }
491
+ /** Negative acknowledge — immediate requeue. */
492
+ nack() {
493
+ this.send(packNack(
494
+ this.seqFn(),
495
+ this.consumerId(),
496
+ this.subjectHash(),
497
+ this.seq()
498
+ ));
499
+ }
500
+ /** Negative acknowledge with redelivery delay (ms). */
501
+ nackDelay(ms) {
502
+ this.send(packBatchNack(
503
+ this.seqFn(),
504
+ this.consumerId(),
505
+ [{ seq: this.seq(), subjectHash: this.subjectHash(), delayMs: ms }]
506
+ ));
507
+ }
508
+ };
509
+
510
+ // src/subscription/subscription.ts
511
+ var Subscription = class {
512
+ constructor(consumerId, conn, streamName, fetchTimeoutMs) {
513
+ this.consumerId = consumerId;
514
+ this.conn = conn;
515
+ this.streamName = streamName;
516
+ this.fetchTimeoutMs = fetchTimeoutMs;
517
+ }
518
+ callback;
519
+ fetchQueue = [];
520
+ msgBuf = [];
521
+ closed = false;
522
+ /** Consumer ID assigned by the server. */
523
+ id() {
524
+ return this.consumerId;
525
+ }
526
+ // Called by the delivery handler.
527
+ deliver(frame2) {
528
+ if (this.closed) return;
529
+ const msg = new Message(
530
+ frame2,
531
+ (f) => this.conn.send(f),
532
+ () => this.conn.nextSeq()
533
+ );
534
+ if (this.callback) {
535
+ this.callback(msg);
536
+ return;
537
+ }
538
+ const pending = this.fetchQueue[0];
539
+ if (pending) {
540
+ pending.buf.push(msg);
541
+ if (pending.buf.length >= pending.count) {
542
+ clearTimeout(pending.timer);
543
+ this.fetchQueue.shift();
544
+ pending.resolve(pending.buf);
545
+ }
546
+ return;
547
+ }
548
+ this.msgBuf.push(msg);
549
+ }
550
+ // Push mode — set a callback to receive messages as they arrive.
551
+ onMessage(cb) {
552
+ this.callback = cb;
553
+ }
554
+ // Pull mode — fetch up to `count` messages, waiting up to `timeoutMs`.
555
+ fetch(count, timeoutMs = this.fetchTimeoutMs) {
556
+ if (this.msgBuf.length >= count) {
557
+ return Promise.resolve(this.msgBuf.splice(0, count));
558
+ }
559
+ return new Promise((resolve) => {
560
+ const buf = this.msgBuf.splice(0);
561
+ const timer = setTimeout(() => {
562
+ const idx = this.fetchQueue.findIndex((p) => p === pending);
563
+ if (idx >= 0) this.fetchQueue.splice(idx, 1);
564
+ resolve(buf);
565
+ }, timeoutMs);
566
+ const pending = { resolve, count, buf, timer };
567
+ this.fetchQueue.push(pending);
568
+ });
569
+ }
570
+ close() {
571
+ this.closed = true;
572
+ this.conn.cancelSubscription(this.consumerId);
573
+ for (const p of this.fetchQueue) {
574
+ clearTimeout(p.timer);
575
+ p.resolve(p.buf);
576
+ }
577
+ this.fetchQueue = [];
578
+ }
579
+ };
580
+
581
+ // src/net/connection.ts
582
+ var net = __toESM(require("net"));
583
+ var tls = __toESM(require("tls"));
584
+
585
+ // src/proto/framer.ts
586
+ var ENVELOPE_MSG_LEN_OFF = 8;
587
+ var Framer = class {
588
+ buf = Buffer.allocUnsafe(65536);
589
+ pos = 0;
590
+ push(chunk, onFrame) {
591
+ this.ensureCapacity(chunk.length);
592
+ chunk.copy(this.buf, this.pos);
593
+ this.pos += chunk.length;
594
+ let offset = 0;
595
+ while (offset < this.pos) {
596
+ if (this.pos - offset < HEADER_SIZE) break;
597
+ const action = this.buf.readUInt16LE(offset + OFF_ACTION);
598
+ const msgLen = action === 517 /* RepBatch */ || action === 519 /* FanoutBatch */ ? this.buf.readUInt32LE(offset + ENVELOPE_MSG_LEN_OFF) : this.buf.readUInt32LE(offset + OFF_MSG_LEN);
599
+ const frameLen = HEADER_SIZE + msgLen;
600
+ if (this.pos - offset < frameLen) break;
601
+ onFrame(Buffer.from(this.buf.subarray(offset, offset + frameLen)));
602
+ offset += frameLen;
603
+ }
604
+ if (offset > 0 && offset < this.pos) {
605
+ this.buf.copyWithin(0, offset, this.pos);
606
+ }
607
+ this.pos = offset < this.pos ? this.pos - offset : 0;
608
+ }
609
+ ensureCapacity(needed) {
610
+ if (this.pos + needed <= this.buf.length) return;
611
+ const next = Buffer.allocUnsafe(Math.max(this.buf.length * 2, this.pos + needed));
612
+ this.buf.copy(next, 0, 0, this.pos);
613
+ this.buf = next;
614
+ }
615
+ };
616
+
617
+ // src/common/logger.ts
618
+ var noop = (() => {
619
+ });
620
+ var NOOP = {
621
+ trace: noop,
622
+ debug: noop,
623
+ info: noop,
624
+ warn: noop,
625
+ error: noop,
626
+ child: () => NOOP
627
+ };
628
+ function resolveLogger(logger) {
629
+ return logger ?? NOOP;
630
+ }
631
+
632
+ // src/net/connection.ts
633
+ function parseAddr(addr) {
634
+ const i = addr.lastIndexOf(":");
635
+ return i === -1 ? { host: addr, port: 9898 } : { host: addr.slice(0, i), port: parseInt(addr.slice(i + 1), 10) };
636
+ }
637
+ var Connection = class _Connection {
638
+ constructor(socket, reconnectAddr, reconnectCfg, logger) {
639
+ this.reconnectAddr = reconnectAddr;
640
+ this.reconnectCfg = reconnectCfg;
641
+ this.log = resolveLogger(logger);
642
+ this.socket = socket;
643
+ this.attachSocket(socket);
644
+ }
645
+ seq = 1n;
646
+ connId = 0;
647
+ framer = new Framer();
648
+ routes = /* @__PURE__ */ new Map();
649
+ pending = /* @__PURE__ */ new Map();
650
+ socket;
651
+ closing = false;
652
+ log;
653
+ activeSubs = /* @__PURE__ */ new Map();
654
+ metrics;
655
+ attachSocket(socket) {
656
+ socket.setNoDelay(true);
657
+ socket.on("data", (chunk) => this.framer.push(chunk, (f) => this.onFrame(f)));
658
+ socket.on("error", (err) => this.drain(err));
659
+ socket.on("close", () => this.handleClose());
660
+ }
661
+ static connect(addr, timeoutMs = 5e3, tlsCfg, reconnectCfg, logger) {
662
+ const parsed = parseAddr(addr);
663
+ const { host, port } = parsed;
664
+ return new Promise((resolve, reject) => {
665
+ const timer = setTimeout(
666
+ () => reject(new ArbitroError("connect timeout", "connect")),
667
+ timeoutMs
668
+ );
669
+ const done = (socket) => {
670
+ clearTimeout(timer);
671
+ const conn = new _Connection(socket, parsed, reconnectCfg, logger);
672
+ socket.write(packHello());
673
+ conn.log.info({ host, port }, "arbitro connected (v2)");
674
+ resolve(conn);
675
+ };
676
+ const fail = (e) => {
677
+ clearTimeout(timer);
678
+ reject(e);
679
+ };
680
+ if (tlsCfg) {
681
+ const s = tls.connect({
682
+ host,
683
+ port,
684
+ ca: tlsCfg.ca,
685
+ cert: tlsCfg.cert,
686
+ key: tlsCfg.key,
687
+ rejectUnauthorized: true
688
+ });
689
+ s.once("secureConnect", () => done(s));
690
+ s.once("error", fail);
691
+ } else {
692
+ const s = net.createConnection({ host, port });
693
+ s.once("connect", () => done(s));
694
+ s.once("error", fail);
695
+ }
696
+ });
697
+ }
698
+ nextSeq() {
699
+ return this.seq++;
700
+ }
701
+ /**
702
+ * Attach a metrics sink. Called by `ArbitroClient` after `connect()`.
703
+ * The connection bumps `deliveriesReceived` on every Deliver/RepBatch
704
+ * entry and `reconnects` on successful reconnections. Unset = no-op.
705
+ */
706
+ setMetrics(m) {
707
+ this.metrics = m;
708
+ }
709
+ // ── Frame routing ─────────────────────────────────────────────────────────
710
+ // Seq-based dispatch: match reply.header.seq → pending request. O(1).
711
+ resolvePending(frame2) {
712
+ const reqSeq = frame2.readBigUInt64LE(OFF_SEQ);
713
+ const p = this.pending.get(reqSeq);
714
+ if (!p) return;
715
+ this.pending.delete(reqSeq);
716
+ p.resolve(frame2);
717
+ }
718
+ rejectPending(frame2) {
719
+ const reqSeq = frame2.readBigUInt64LE(OFF_SEQ);
720
+ const p = this.pending.get(reqSeq);
721
+ if (!p) return;
722
+ this.pending.delete(reqSeq);
723
+ const errorCode = frame2.length >= HEADER_SIZE + 10 ? frame2.readUInt16LE(HEADER_SIZE + 8) : 0;
724
+ p.reject(new ArbitroError(
725
+ `server error (code=0x${errorCode.toString(16).padStart(4, "0")})`,
726
+ "server",
727
+ void 0,
728
+ void 0,
729
+ errorCode
730
+ ));
731
+ }
732
+ onFrame(frame2) {
733
+ const action = frame2.readUInt16LE(OFF_ACTION);
734
+ switch (action) {
735
+ case 515 /* RepOk */:
736
+ case 1028 /* ListStreams */:
737
+ case 1284 /* ListConsumers */: {
738
+ this.resolvePending(frame2);
739
+ return;
740
+ }
741
+ case 516 /* RepError */: {
742
+ this.rejectPending(frame2);
743
+ return;
744
+ }
745
+ case 512 /* Deliver */: {
746
+ const consumerId = frame2.readUInt32LE(HEADER_SIZE);
747
+ const handler = this.routes.get(consumerId);
748
+ if (!handler) {
749
+ this.log.warn({ consumerId }, "delivery for unknown consumer");
750
+ return;
751
+ }
752
+ if (this.metrics) this.metrics.deliveriesReceived++;
753
+ handler(frame2);
754
+ return;
755
+ }
756
+ case 517 /* RepBatch */: {
757
+ this.handleBatchDeliver(frame2);
758
+ return;
759
+ }
760
+ case 1538 /* Pong */:
761
+ return;
762
+ default: {
763
+ this.log.debug({ action: `0x${action.toString(16)}` }, "unknown action, dropped");
764
+ }
765
+ }
766
+ }
767
+ handleBatchDeliver(frame2) {
768
+ if (frame2.length < HEADER_SIZE + 4) return;
769
+ const count = frame2.readUInt16LE(HEADER_SIZE);
770
+ let off = HEADER_SIZE + 4;
771
+ for (let i = 0; i < count; i++) {
772
+ if (off + 24 > frame2.length) break;
773
+ const consumerId = frame2.readUInt32LE(off);
774
+ const deliverSeq = frame2.readBigUInt64LE(off + 4);
775
+ const subjectLen = frame2.readUInt16LE(off + 12);
776
+ const replyLen = frame2.readUInt16LE(off + 14);
777
+ const dataLen = frame2.readUInt32LE(off + 16);
778
+ const subjectHash = frame2.readUInt32LE(off + 20);
779
+ off += 24;
780
+ const tailEnd = off + dataLen;
781
+ if (tailEnd > frame2.length) break;
782
+ const payloadLen = dataLen - subjectLen - replyLen;
783
+ const handler = this.routes.get(consumerId);
784
+ if (handler) {
785
+ const bodyLen = 12 + subjectLen + payloadLen;
786
+ const single = Buffer.allocUnsafe(HEADER_SIZE + bodyLen);
787
+ single.writeUInt16LE(512 /* Deliver */, 0);
788
+ single[2] = 0;
789
+ single[3] = 0;
790
+ single.writeUInt32LE(bodyLen, 4);
791
+ single.writeBigUInt64LE(deliverSeq, 8);
792
+ single.writeUInt32LE(consumerId, HEADER_SIZE);
793
+ single.writeUInt32LE(subjectHash, HEADER_SIZE + 4);
794
+ single.writeUInt16LE(subjectLen, HEADER_SIZE + 8);
795
+ single.writeUInt16LE(0, HEADER_SIZE + 10);
796
+ frame2.copy(single, HEADER_SIZE + 12, off, off + subjectLen);
797
+ frame2.copy(
798
+ single,
799
+ HEADER_SIZE + 12 + subjectLen,
800
+ off + subjectLen + replyLen,
801
+ tailEnd
802
+ );
803
+ if (this.metrics) this.metrics.deliveriesReceived++;
804
+ handler(single);
805
+ }
806
+ off = tailEnd;
807
+ }
808
+ }
809
+ // ── Subscriptions ─────────────────────────────────────────────────────────
810
+ async sendSubscribeV2(consumerId, filter, handler, onRenew) {
811
+ return new Promise((resolve, reject) => {
812
+ const seq = this.nextSeq();
813
+ const timer = setTimeout(
814
+ () => {
815
+ this.pending.delete(seq);
816
+ reject(new ArbitroError("subscribe timeout", "timeout"));
817
+ },
818
+ 5e3
819
+ );
820
+ this.pending.set(seq, {
821
+ resolve: (_frame) => {
822
+ clearTimeout(timer);
823
+ this.routes.set(consumerId, handler);
824
+ this.activeSubs.set(consumerId, { consumerId, filter, handler, onRenew });
825
+ resolve(consumerId);
826
+ },
827
+ reject: (err) => {
828
+ clearTimeout(timer);
829
+ reject(err);
830
+ }
831
+ });
832
+ this.socket.write(packSubscribe(seq, this.connId, consumerId, filter));
833
+ });
834
+ }
835
+ cancelSubscription(consumerId) {
836
+ this.routes.delete(consumerId);
837
+ this.activeSubs.delete(consumerId);
838
+ this.socket.write(packUnsubscribe(this.nextSeq(), this.connId, consumerId));
839
+ }
840
+ resubscribeAll() {
841
+ this.socket.write(packHello());
842
+ const subs = [...this.activeSubs.values()];
843
+ this.activeSubs.clear();
844
+ this.routes.clear();
845
+ for (const { consumerId, filter, handler, onRenew } of subs) {
846
+ this.sendSubscribeV2(consumerId, filter, handler, onRenew).then((id) => {
847
+ if (onRenew) onRenew(id);
848
+ }).catch(() => {
849
+ });
850
+ }
851
+ }
852
+ // ── Routes (internal use) ─────────────────────────────────────────────────
853
+ registerRoute(consumerId, handler) {
854
+ this.routes.set(consumerId, handler);
855
+ }
856
+ unregisterRoute(consumerId) {
857
+ this.routes.delete(consumerId);
858
+ }
859
+ // ── Write ─────────────────────────────────────────────────────────────────
860
+ send(frame2) {
861
+ this.socket.write(frame2);
862
+ }
863
+ /** Send frame and wait for RepOk. Returns the ref_seq from body. */
864
+ sendExpectReply(frame2, timeoutMs = 5e3) {
865
+ const seq = frame2.readBigUInt64LE(OFF_SEQ);
866
+ return new Promise((resolve, reject) => {
867
+ const timer = setTimeout(
868
+ () => {
869
+ this.pending.delete(seq);
870
+ reject(new ArbitroError("request timeout", "timeout"));
871
+ },
872
+ timeoutMs
873
+ );
874
+ this.pending.set(seq, {
875
+ resolve: (f) => {
876
+ clearTimeout(timer);
877
+ resolve(f.readBigUInt64LE(HEADER_SIZE));
878
+ },
879
+ reject: (e) => {
880
+ clearTimeout(timer);
881
+ reject(e);
882
+ }
883
+ });
884
+ this.socket.write(frame2);
885
+ });
886
+ }
887
+ /** Send frame and wait for full reply frame buffer. */
888
+ sendExpectReplyRaw(frame2, timeoutMs = 5e3) {
889
+ const seq = frame2.readBigUInt64LE(OFF_SEQ);
890
+ return new Promise((resolve, reject) => {
891
+ const timer = setTimeout(
892
+ () => {
893
+ this.pending.delete(seq);
894
+ reject(new ArbitroError("request timeout", "timeout"));
895
+ },
896
+ timeoutMs
897
+ );
898
+ this.pending.set(seq, {
899
+ resolve: (f) => {
900
+ clearTimeout(timer);
901
+ resolve(f);
902
+ },
903
+ reject: (e) => {
904
+ clearTimeout(timer);
905
+ reject(e);
906
+ }
907
+ });
908
+ this.socket.write(frame2);
909
+ });
910
+ }
911
+ // ── Lifecycle ─────────────────────────────────────────────────────────────
912
+ close() {
913
+ this.closing = true;
914
+ this.socket.write(packDisconnect(this.nextSeq()));
915
+ return new Promise((resolve) => this.socket.end(resolve));
916
+ }
917
+ drain(err) {
918
+ for (const p of this.pending.values()) p.reject(err);
919
+ this.pending.clear();
920
+ }
921
+ handleClose() {
922
+ this.log.debug("arbitro connection closed");
923
+ this.drain(new ArbitroError("connection closed", "closed"));
924
+ const cfg = this.reconnectCfg;
925
+ if (!this.closing && cfg && cfg.enabled !== false && this.reconnectAddr) {
926
+ this.tryReconnect(0);
927
+ }
928
+ }
929
+ tryReconnect(attempt) {
930
+ const cfg = this.reconnectCfg;
931
+ if (!cfg) return;
932
+ const max = cfg.maxAttempts ?? 10;
933
+ if (attempt >= max) {
934
+ this.log.warn({ attempt }, "reconnect exhausted");
935
+ return;
936
+ }
937
+ const base = cfg.intervalMs ?? 500;
938
+ const jitter = cfg.jitter !== false ? Math.random() * 100 : 0;
939
+ const delay = Math.min(base * 2 ** attempt, 3e4) + jitter;
940
+ this.log.debug({ attempt, delayMs: Math.round(delay) }, "reconnecting");
941
+ setTimeout(() => {
942
+ const { host, port } = this.reconnectAddr;
943
+ const socket = net.createConnection({ host, port });
944
+ socket.once("connect", () => {
945
+ this.log.info({ host, port, attempt }, "arbitro reconnected");
946
+ this.framer = new Framer();
947
+ this.socket = socket;
948
+ this.attachSocket(socket);
949
+ this.resubscribeAll();
950
+ });
951
+ socket.once("error", () => this.tryReconnect(attempt + 1));
952
+ }, delay);
953
+ }
954
+ };
955
+
956
+ // src/consumer/consumer.ts
957
+ var Consumer = class {
958
+ constructor(client, streamName, config, _consumerId) {
959
+ this.client = client;
960
+ this.streamName = streamName;
961
+ this.config = config;
962
+ this._consumerId = _consumerId;
963
+ }
964
+ get name() {
965
+ return this.config.name ?? this.streamName;
966
+ }
967
+ get consumerId() {
968
+ return this._consumerId;
969
+ }
970
+ /**
971
+ * Publish through this consumer's stream. Returns `Promise<void>` that
972
+ * resolves on broker `RepOk`. Await to wait, or ignore for
973
+ * fire-and-forget semantics — the caller's choice, on the same call.
974
+ */
975
+ publish(subject, data) {
976
+ return this.client.publish(this.streamName, subject, data);
977
+ }
978
+ async create() {
979
+ const result = await this.client.createConsumer(this.streamName, this.config);
980
+ this._consumerId = result.consumerId;
981
+ return this;
982
+ }
983
+ async upsert() {
984
+ const result = await this.client.upsertConsumer(this.streamName, this.config);
985
+ this._consumerId = result.consumerId;
986
+ return this;
987
+ }
988
+ async delete() {
989
+ if (this._consumerId == null) throw new Error("consumer not created");
990
+ await this.client.deleteConsumer(this._consumerId);
991
+ }
992
+ async exists() {
993
+ return this.client.consumerExists(this.streamName, this.name);
994
+ }
995
+ /**
996
+ * Live pending-ack count for this consumer (delivered, not yet acked).
997
+ * Uses the cached consumer ID when available; otherwise looks it up
998
+ * from `streamName + name`. Equivalent of NATS `num_ack_pending`.
999
+ */
1000
+ async getPendings() {
1001
+ if (this._consumerId != null) {
1002
+ return this.client.getPending(this._consumerId);
1003
+ }
1004
+ return this.client.getPending(this.streamName, this.name);
1005
+ }
1006
+ subscribe(codecOrCb, cbOrOpts, opts) {
1007
+ if (!codecOrCb || typeof codecOrCb === "function") {
1008
+ return this.client.subscribe(
1009
+ this.streamName,
1010
+ this.config,
1011
+ codecOrCb,
1012
+ cbOrOpts
1013
+ );
1014
+ }
1015
+ const codec = codecOrCb;
1016
+ const cb = cbOrOpts;
1017
+ const fields = codec.fields ?? [];
1018
+ return this.client.subscribe(this.streamName, this.config, (raw) => {
1019
+ cb(makeLazyMessage(
1020
+ raw.data(),
1021
+ codec,
1022
+ fields,
1023
+ () => raw.ack(),
1024
+ () => raw.nack(),
1025
+ (ms) => raw.nackDelay(ms)
1026
+ ));
1027
+ }, opts);
1028
+ }
1029
+ };
1030
+
1031
+ // src/stream/stream.ts
1032
+ var Stream = class {
1033
+ constructor(client, name, config) {
1034
+ this.client = client;
1035
+ this.name = name;
1036
+ this._config = config;
1037
+ }
1038
+ _config;
1039
+ get config() {
1040
+ return this._config;
1041
+ }
1042
+ async create(config) {
1043
+ const cfg = config ?? this._config;
1044
+ if (!cfg) throw new Error("StreamConfig required \u2014 pass to create() or constructor");
1045
+ this._config = cfg;
1046
+ await this.client.createStream(this.name, cfg);
1047
+ return this;
1048
+ }
1049
+ async upsert(config) {
1050
+ const cfg = config ?? this._config;
1051
+ if (!cfg) throw new Error("StreamConfig required \u2014 pass to upsert() or constructor");
1052
+ this._config = cfg;
1053
+ await this.client.upsertStream(this.name, cfg);
1054
+ return this;
1055
+ }
1056
+ async delete(opts) {
1057
+ await this.client.deleteStream(this.name, opts);
1058
+ }
1059
+ async exists() {
1060
+ return this.client.streamExists(this.name);
1061
+ }
1062
+ async info() {
1063
+ return this.client.getStreamInfo(this.name);
1064
+ }
1065
+ // ── Publish ─────────────────────────────────────────────────────────────
1066
+ /**
1067
+ * Publish to this stream. Returns a `Promise<void>` that resolves on
1068
+ * broker `RepOk`. Await it to wait for confirmation, or ignore the
1069
+ * returned promise for fire-and-forget semantics.
1070
+ *
1071
+ * Pass `opts.msgId` to opt this publish into broker-side dedup on
1072
+ * streams created with `idempotencyWindowMs > 0`. Duplicate ids
1073
+ * within the window are rejected with a `ClientError` carrying the
1074
+ * `IdempotencyDuplicate` code.
1075
+ */
1076
+ publish(subject, data, opts) {
1077
+ return this.client.publish(this.name, subject, data, opts);
1078
+ }
1079
+ /** @deprecated alias for {@link publish}. */
1080
+ publishAck(subject, data, opts) {
1081
+ return this.client.publish(this.name, subject, data, opts);
1082
+ }
1083
+ /** Batch publish — single V2 BatchPubFrame, ONE round-trip.
1084
+ * Resolves with `first_seq` (the N messages occupy
1085
+ * `[first_seq, first_seq + N - 1]`).
1086
+ *
1087
+ * Like {@link publish}, the call always exchanges request/response
1088
+ * with the broker; the caller decides whether to wait via `await`. */
1089
+ publishBatch(messages) {
1090
+ return this.client.publishBatch(this.name, messages);
1091
+ }
1092
+ request(subject, data, timeoutMs) {
1093
+ return this.client.request(this.name, subject, data, timeoutMs);
1094
+ }
1095
+ // ── Context factories ───────────────────────────────────────────────────
1096
+ consumer(overrides) {
1097
+ const config = {
1098
+ ...overrides,
1099
+ name: overrides?.name ?? this.name,
1100
+ filter: overrides?.filter ?? `${this.name}.>`
1101
+ };
1102
+ return new Consumer(this.client, this.name, config);
1103
+ }
1104
+ topic(subject, codec) {
1105
+ return new Topic(this, subject, codec);
1106
+ }
1107
+ };
1108
+
1109
+ // src/client/metrics.ts
1110
+ var ClientMetrics = class {
1111
+ publishesSent = 0;
1112
+ publishBatchEntries = 0;
1113
+ deliveriesReceived = 0;
1114
+ activeSubscriptions = 0;
1115
+ acksSent = 0;
1116
+ nacksSent = 0;
1117
+ reconnects = 0;
1118
+ pendingReplies = 0;
1119
+ snapshot() {
1120
+ return {
1121
+ publishesSent: this.publishesSent,
1122
+ publishBatchEntries: this.publishBatchEntries,
1123
+ deliveriesReceived: this.deliveriesReceived,
1124
+ activeSubscriptions: this.activeSubscriptions,
1125
+ acksSent: this.acksSent,
1126
+ nacksSent: this.nacksSent,
1127
+ reconnects: this.reconnects,
1128
+ pendingReplies: this.pendingReplies
1129
+ };
1130
+ }
1131
+ };
1132
+
1133
+ // src/stream/publish.ts
1134
+ var EMPTY2 = Buffer.alloc(0);
1135
+ function toMsgIdBuf(id) {
1136
+ if (id == null) return EMPTY2;
1137
+ return typeof id === "string" ? Buffer.from(id) : id;
1138
+ }
1139
+ async function streamPublishAck(conn, sid, subject, data, opts) {
1140
+ const subj = Buffer.from(subject);
1141
+ await conn.sendExpectReply(
1142
+ packPublish(conn.nextSeq(), sid, subj, data, 1 /* AckReq */, 0, toMsgIdBuf(opts?.msgId))
1143
+ );
1144
+ }
1145
+ async function streamPublishBatch(conn, sid, messages) {
1146
+ return await conn.sendExpectReply(
1147
+ packPublishBatch(conn.nextSeq(), sid, messages)
1148
+ );
1149
+ }
1150
+ async function streamRequest(conn, sid, subject, data, timeoutMs) {
1151
+ const subj = Buffer.from(subject);
1152
+ const replyTo = Buffer.from(`_INBOX.${conn.nextSeq().toString(36)}`);
1153
+ const frame2 = packPublishWithReply(
1154
+ conn.nextSeq(),
1155
+ sid,
1156
+ subj,
1157
+ replyTo,
1158
+ data,
1159
+ 1 /* AckReq */
1160
+ );
1161
+ await conn.sendExpectReply(frame2, timeoutMs);
1162
+ return Buffer.alloc(0);
1163
+ }
1164
+
1165
+ // src/client/client.ts
1166
+ var DEFAULT_CONFIG = {
1167
+ servers: ["127.0.0.1:9898"],
1168
+ prefix: "",
1169
+ timeout: 5e3,
1170
+ reconnect: { enabled: true, maxAttempts: 10, intervalMs: 500, jitter: true }
1171
+ };
1172
+ var ArbitroClient = class {
1173
+ conn;
1174
+ cfg;
1175
+ tls;
1176
+ logger;
1177
+ sidCache = /* @__PURE__ */ new Map();
1178
+ _metrics = new ClientMetrics();
1179
+ constructor(config) {
1180
+ this.cfg = { ...DEFAULT_CONFIG, ...config };
1181
+ this.tls = config.tls;
1182
+ this.logger = config.logger;
1183
+ }
1184
+ async connect() {
1185
+ const addr = this.cfg.servers[0];
1186
+ if (!addr) throw new ArbitroError("no servers configured", "connect");
1187
+ this.conn = await Connection.connect(
1188
+ addr,
1189
+ this.cfg.timeout,
1190
+ this.tls,
1191
+ this.cfg.reconnect,
1192
+ this.logger
1193
+ );
1194
+ this.conn.setMetrics(this._metrics);
1195
+ return this;
1196
+ }
1197
+ /**
1198
+ * Point-in-time snapshot of client counters: publishes sent, deliveries
1199
+ * received, acks/nacks, active subscriptions, reconnects. Cheap — just
1200
+ * reads plain integer fields. Call on a timer to chart throughput.
1201
+ */
1202
+ metrics() {
1203
+ return this._metrics.snapshot();
1204
+ }
1205
+ /** Internal connection accessor for Stream/Consumer publish methods. */
1206
+ _conn() {
1207
+ return this.conn;
1208
+ }
1209
+ /** Default timeout from config. */
1210
+ get timeout() {
1211
+ return this.cfg.timeout;
1212
+ }
1213
+ // ── Publish (direct to stream via V2 PubFrame) ────────────────────────────
1214
+ /** Apply prefix to subject if configured. */
1215
+ prefixed(subject) {
1216
+ return this.cfg.prefix ? `${this.cfg.prefix}.${subject}` : subject;
1217
+ }
1218
+ /**
1219
+ * Publish a message. Returns a `Promise<void>` that resolves once the
1220
+ * broker confirms receipt (`RepOk`). The TS idiom is "everything async,
1221
+ * the caller chooses to await":
1222
+ *
1223
+ * await client.publish(s, subj, data) // wait for ack
1224
+ * client.publish(s, subj, data) // fire-and-forget
1225
+ * client.publish(s, subj, data).catch(handleError) // async error path
1226
+ *
1227
+ * The broker always emits `RepOk` regardless of whether the caller
1228
+ * awaits — that's what enables the same call site to support both
1229
+ * semantics. A "no-reply" path doesn't save wire bytes, so it isn't
1230
+ * exposed.
1231
+ */
1232
+ async publish(streamName, subject, data, opts) {
1233
+ const sid = await this.resolveStreamId(streamName);
1234
+ await streamPublishAck(this.conn, sid, this.prefixed(subject), data, opts);
1235
+ this._metrics.publishesSent++;
1236
+ }
1237
+ /**
1238
+ * @deprecated alias for {@link publish}. The default `publish` already
1239
+ * waits for `RepOk` and returns a Promise, so this method is identical.
1240
+ */
1241
+ publishAck(streamName, subject, data, opts) {
1242
+ return this.publish(streamName, subject, data, opts);
1243
+ }
1244
+ /** Batch publish — single V2 BatchPubFrame, ONE round-trip. Resolves
1245
+ * to `first_seq` (sequence of the first message in the batch; the N
1246
+ * messages occupy `[first_seq, first_seq + N - 1]`).
1247
+ *
1248
+ * Mirrors `publish`: the call always exchanges request/response with
1249
+ * the broker. The caller decides whether to actually wait:
1250
+ *
1251
+ * await client.publishBatch(stream, msgs) // barrier
1252
+ * client.publishBatch(stream, msgs) // fire-and-forget (promise dropped)
1253
+ * client.publishBatch(stream, msgs) // (suppress unhandled rejection)
1254
+ * .catch(() => {})
1255
+ *
1256
+ * Each entry may carry an optional `msgId` for broker-side dedup on
1257
+ * streams created with `idempotencyWindowMs > 0`. If any entry
1258
+ * collides with a previously-stored id (or another entry in the
1259
+ * SAME batch), the whole batch is rejected — `publishBatch` is
1260
+ * atomic.
1261
+ */
1262
+ publishBatch(streamName, messages) {
1263
+ const sid = this.cachedSid(streamName);
1264
+ const prefixedMsgs = this.cfg.prefix ? messages.map((m) => {
1265
+ const entry = {
1266
+ subject: this.prefixed(m.subject),
1267
+ payload: m.payload
1268
+ };
1269
+ if (m.msgId !== void 0) entry.msgId = m.msgId;
1270
+ return entry;
1271
+ }) : messages;
1272
+ this._metrics.publishBatchEntries += messages.length;
1273
+ return streamPublishBatch(this.conn, sid, prefixedMsgs);
1274
+ }
1275
+ /** Request-reply. */
1276
+ async request(streamName, subject, data, timeoutMs = this.cfg.timeout) {
1277
+ const sid = await this.resolveStreamId(streamName);
1278
+ return streamRequest(this.conn, sid, subject, data, timeoutMs);
1279
+ }
1280
+ async subscribe(streamName, configOrCb, callbackOrOpts, opts) {
1281
+ let config;
1282
+ let callback;
1283
+ let subOpts;
1284
+ if (typeof configOrCb === "function") {
1285
+ config = { name: streamName, filter: "" };
1286
+ callback = configOrCb;
1287
+ subOpts = void 0;
1288
+ } else {
1289
+ config = configOrCb;
1290
+ callback = typeof callbackOrOpts === "function" ? callbackOrOpts : void 0;
1291
+ subOpts = typeof callbackOrOpts === "object" ? callbackOrOpts : opts;
1292
+ }
1293
+ const consumerId = await this.ensureConsumer(streamName, config);
1294
+ const filter = Buffer.from(config.filter ?? "");
1295
+ const sub = new Subscription(consumerId, this.conn, streamName, subOpts?.fetchTimeoutMs ?? 5e3);
1296
+ const handler = (frame2) => sub.deliver(frame2);
1297
+ await this.conn.sendSubscribeV2(consumerId, filter, handler);
1298
+ this._metrics.activeSubscriptions++;
1299
+ const origClose = sub.close.bind(sub);
1300
+ sub.close = () => {
1301
+ if (this._metrics.activeSubscriptions > 0) this._metrics.activeSubscriptions--;
1302
+ return origClose();
1303
+ };
1304
+ if (callback) sub.onMessage(callback);
1305
+ return sub;
1306
+ }
1307
+ // ── Stream management ─────────────────────────────────────────────────────
1308
+ async createStream(name, config) {
1309
+ const nameBuf = Buffer.from(name);
1310
+ const filterBuf = Buffer.from(config.subjectFilter ?? "");
1311
+ const maxMsgs = BigInt(config.maxMsgs ?? 0);
1312
+ const maxBytes = BigInt(config.maxBytes ?? 0);
1313
+ const maxAgeSecs = BigInt(config.maxAgeMs ? Math.ceil(config.maxAgeMs / 1e3) : 0);
1314
+ const journalKind = journalTypeToU8(config.journal?.type);
1315
+ await this.conn.sendExpectReply(packCreateStream(
1316
+ this.conn.nextSeq(),
1317
+ nameBuf,
1318
+ filterBuf,
1319
+ maxMsgs,
1320
+ maxBytes,
1321
+ maxAgeSecs,
1322
+ 1,
1323
+ journalKind,
1324
+ 0,
1325
+ 0,
1326
+ config.idempotencyWindowMs ?? 0
1327
+ ));
1328
+ await this.resolveStreamId(name);
1329
+ return new Stream(this, name, config);
1330
+ }
1331
+ async upsertStream(name, config) {
1332
+ try {
1333
+ return await this.createStream(name, config);
1334
+ } catch (e) {
1335
+ if (e?.message?.includes("code=")) {
1336
+ await this.resolveStreamId(name);
1337
+ return new Stream(this, name, config);
1338
+ }
1339
+ throw e;
1340
+ }
1341
+ }
1342
+ async deleteStream(name, _opts) {
1343
+ await this.conn.sendExpectReply(
1344
+ packDeleteStream(this.conn.nextSeq(), Buffer.from(name))
1345
+ );
1346
+ this.sidCache.delete(name);
1347
+ }
1348
+ async getStreamInfo(name) {
1349
+ try {
1350
+ const refSeq = await this.conn.sendExpectReply(
1351
+ packGetStream(this.conn.nextSeq(), Buffer.from(name))
1352
+ );
1353
+ this.sidCache.set(name, Number(refSeq & 0xFFFFFFFFn));
1354
+ return { name, config: { subjectFilter: "" }, lastSeq: Number(refSeq) };
1355
+ } catch {
1356
+ return null;
1357
+ }
1358
+ }
1359
+ async listStreams() {
1360
+ const raw = await this.conn.sendExpectReplyRaw(
1361
+ packListStreams(this.conn.nextSeq())
1362
+ );
1363
+ return parseListStreamsReply(raw);
1364
+ }
1365
+ async streamExists(name) {
1366
+ return await this.getStreamInfo(name) !== null;
1367
+ }
1368
+ async purgeStream(name) {
1369
+ const refSeq = await this.conn.sendExpectReply(
1370
+ packPurgeStream(this.conn.nextSeq(), Buffer.from(name))
1371
+ );
1372
+ return Number(refSeq);
1373
+ }
1374
+ async drainSubject(streamName, subject) {
1375
+ const refSeq = await this.conn.sendExpectReply(
1376
+ packDrainSubject(this.conn.nextSeq(), Buffer.from(streamName), Buffer.from(subject))
1377
+ );
1378
+ return Number(refSeq);
1379
+ }
1380
+ // ── Consumer management ───────────────────────────────────────────────────
1381
+ async createConsumer(streamName, config) {
1382
+ const consumerId = await this.createConsumerRaw(streamName, config);
1383
+ return new Consumer(this, streamName, config, consumerId);
1384
+ }
1385
+ async createConsumerRaw(streamName, config) {
1386
+ const sid = await this.resolveStreamId(streamName);
1387
+ const name = Buffer.from(config.name ?? streamName);
1388
+ const group = Buffer.from(config.name ?? streamName);
1389
+ const filter = Buffer.from(config.filter ?? "");
1390
+ const ackPolicyByte = config.ackPolicy === "none" /* None */ ? 0 : 1;
1391
+ const opts = {
1392
+ streamId: sid,
1393
+ name,
1394
+ group,
1395
+ filter,
1396
+ maxInflight: config.maxAckPending ?? 0,
1397
+ ackPolicy: ackPolicyByte,
1398
+ deliverPolicy: deliverPolicyToU8(config.deliverPolicy),
1399
+ deliverMode: config.fanout ? 1 : 0,
1400
+ ackWaitMs: config.ackWaitMs ?? 0,
1401
+ startSeq: BigInt(config.startSeq ?? 0)
1402
+ };
1403
+ if (ackPolicyByte === 1 && config.maxSubjectInflights?.length) {
1404
+ opts.subjectLimits = config.maxSubjectInflights.map((l) => ({
1405
+ pattern: Buffer.from(l.pattern),
1406
+ limit: l.limit >>> 0
1407
+ // u32
1408
+ }));
1409
+ }
1410
+ const refSeq = await this.conn.sendExpectReply(
1411
+ packCreateConsumer(this.conn.nextSeq(), opts)
1412
+ );
1413
+ return Number(refSeq);
1414
+ }
1415
+ async upsertConsumer(streamName, config) {
1416
+ try {
1417
+ return await this.createConsumer(streamName, config);
1418
+ } catch (e) {
1419
+ const consumerId = await this.getConsumerId(streamName, config.name ?? streamName);
1420
+ if (consumerId !== null) return new Consumer(this, streamName, config, consumerId);
1421
+ throw e;
1422
+ }
1423
+ }
1424
+ async deleteConsumer(idOrStream, name) {
1425
+ let consumerId;
1426
+ if (typeof idOrStream === "number") {
1427
+ consumerId = idOrStream;
1428
+ } else {
1429
+ const id = await this.getConsumerId(idOrStream, name);
1430
+ if (id === null) return;
1431
+ consumerId = id;
1432
+ }
1433
+ await this.conn.sendExpectReply(
1434
+ packDeleteConsumer(this.conn.nextSeq(), consumerId)
1435
+ );
1436
+ }
1437
+ async getConsumerId(streamName, name) {
1438
+ try {
1439
+ const sid = await this.resolveStreamId(streamName);
1440
+ const refSeq = await this.conn.sendExpectReply(
1441
+ packGetConsumer(this.conn.nextSeq(), sid, Buffer.from(name))
1442
+ );
1443
+ return Number(refSeq);
1444
+ } catch {
1445
+ return null;
1446
+ }
1447
+ }
1448
+ async consumerExists(streamName, name) {
1449
+ return await this.getConsumerId(streamName, name) !== null;
1450
+ }
1451
+ async getPending(idOrStream, name) {
1452
+ let consumerId;
1453
+ if (typeof idOrStream === "number") {
1454
+ consumerId = idOrStream;
1455
+ } else {
1456
+ const id = await this.getConsumerId(idOrStream, name);
1457
+ if (id === null) return 0;
1458
+ consumerId = id;
1459
+ }
1460
+ const refSeq = await this.conn.sendExpectReply(
1461
+ packConsumerStats(this.conn.nextSeq(), consumerId)
1462
+ );
1463
+ return Number(refSeq);
1464
+ }
1465
+ async getConsumerInfo(streamName, name) {
1466
+ const id = await this.getConsumerId(streamName, name);
1467
+ if (id === null) return null;
1468
+ return { group: name, stream: streamName, config: { name } };
1469
+ }
1470
+ async listConsumers(streamName) {
1471
+ const sid = streamName ? await this.resolveStreamId(streamName) : 0;
1472
+ const raw = await this.conn.sendExpectReplyRaw(
1473
+ packListConsumers(this.conn.nextSeq(), sid)
1474
+ );
1475
+ return parseListConsumersReply(raw);
1476
+ }
1477
+ // ── Internal helpers ──────────────────────────────────────────────────────
1478
+ /** Resolve stream name → server wire_hash_32. Caches the result. */
1479
+ async resolveStreamId(name) {
1480
+ const cached = this.sidCache.get(name);
1481
+ if (cached !== void 0) return cached;
1482
+ const refSeq = await this.conn.sendExpectReply(
1483
+ packGetStream(this.conn.nextSeq(), Buffer.from(name))
1484
+ );
1485
+ const sid = Number(refSeq & 0xFFFFFFFFn);
1486
+ this.sidCache.set(name, sid);
1487
+ return sid;
1488
+ }
1489
+ /** Get cached stream_id or throw (for sync fire-and-forget paths). */
1490
+ cachedSid(name) {
1491
+ const sid = this.sidCache.get(name);
1492
+ if (sid === void 0) {
1493
+ throw new ArbitroError(
1494
+ `stream "${name}" not resolved \u2014 call createStream/getStreamInfo first`,
1495
+ "protocol"
1496
+ );
1497
+ }
1498
+ return sid;
1499
+ }
1500
+ async ensureConsumer(streamName, config) {
1501
+ const name = config.name ?? streamName;
1502
+ const existing = await this.getConsumerId(streamName, name);
1503
+ if (existing !== null) return existing;
1504
+ return this.createConsumerRaw(streamName, config);
1505
+ }
1506
+ // ── Domain helpers ────────────────────────────────────────────────────────
1507
+ /** Pre-resolve stream_id from server (GetStream). Required before sync publish(). */
1508
+ async resolveStream(name) {
1509
+ await this.resolveStreamId(name);
1510
+ }
1511
+ stream(name, config) {
1512
+ return new Stream(this, name, config);
1513
+ }
1514
+ // ── Lifecycle ─────────────────────────────────────────────────────────────
1515
+ async close() {
1516
+ await this.conn.close();
1517
+ }
1518
+ };
1519
+ function parseListStreamsReply(frame2) {
1520
+ if (frame2.length < HEADER_SIZE + 4) return [];
1521
+ const count = frame2.readUInt32LE(HEADER_SIZE);
1522
+ const results = [];
1523
+ let off = HEADER_SIZE + 4;
1524
+ for (let i = 0; i < count; i++) {
1525
+ if (off + 6 > frame2.length) break;
1526
+ const wireId = frame2.readUInt32LE(off);
1527
+ const nameLen = frame2.readUInt16LE(off + 4);
1528
+ off += 6;
1529
+ const name = frame2.subarray(off, off + nameLen).toString();
1530
+ off += nameLen;
1531
+ results.push({ name, config: { subjectFilter: "" }, lastSeq: wireId });
1532
+ }
1533
+ return results;
1534
+ }
1535
+ function parseListConsumersReply(frame2) {
1536
+ if (frame2.length < HEADER_SIZE + 4) return [];
1537
+ const count = frame2.readUInt32LE(HEADER_SIZE);
1538
+ const results = [];
1539
+ let off = HEADER_SIZE + 4;
1540
+ for (let i = 0; i < count; i++) {
1541
+ if (off + 13 > frame2.length) break;
1542
+ const consumerId = frame2.readUInt32LE(off);
1543
+ const _streamId = frame2.readUInt32LE(off + 4);
1544
+ const _queueId = frame2.readUInt32LE(off + 8);
1545
+ const _paused = frame2[off + 12];
1546
+ off += 13;
1547
+ results.push({
1548
+ group: consumerId.toString(),
1549
+ stream: "",
1550
+ config: { name: consumerId.toString() }
1551
+ });
1552
+ }
1553
+ return results;
1554
+ }
1555
+ function deliverPolicyToU8(policy) {
1556
+ switch (policy) {
1557
+ case "All" /* All */:
1558
+ return 0;
1559
+ case "New" /* New */:
1560
+ return 1;
1561
+ case "ByStartSeq" /* BySeq */:
1562
+ return 2;
1563
+ case "ByStartTime" /* ByTime */:
1564
+ return 3;
1565
+ default:
1566
+ return 0;
1567
+ }
1568
+ }
1569
+ function journalTypeToU8(type) {
1570
+ switch (type) {
1571
+ case "Memory" /* Memory */:
1572
+ return 0;
1573
+ case "Tolerant" /* Tolerant */:
1574
+ return 1;
1575
+ case "Strict" /* Strict */:
1576
+ return 2;
1577
+ default:
1578
+ return 0;
1579
+ }
1580
+ }
1581
+
1582
+ // src/proto/fnv1a.ts
1583
+ function streamId(name) {
1584
+ const buf = typeof name === "string" ? Buffer.from(name) : name;
1585
+ let h = 2166136261;
1586
+ for (let i = 0; i < buf.length; i++) {
1587
+ h ^= buf[i];
1588
+ h = Math.imul(h, 16777619) >>> 0;
1589
+ }
1590
+ return h >>> 0;
1591
+ }
1592
+
1593
+ // src/utils/zod.ts
1594
+ var import_msgpackr2 = require("msgpackr");
1595
+ var packr = new import_msgpackr2.Packr({ structuredClone: false, useRecords: false });
1596
+ var unpackr = new import_msgpackr2.Unpackr({ structuredClone: false, useRecords: false });
1597
+ function zodCodec(zodSchema) {
1598
+ const fields = Object.keys(zodSchema.shape);
1599
+ return {
1600
+ fields,
1601
+ encode(value) {
1602
+ return Buffer.from(packr.pack(value));
1603
+ },
1604
+ decode(buf) {
1605
+ return zodSchema.parse(unpackr.unpack(buf));
1606
+ }
1607
+ };
1608
+ }
1609
+ // Annotate the CommonJS export names for ESM import in node:
1610
+ 0 && (module.exports = {
1611
+ AckPolicy,
1612
+ ArbitroClient,
1613
+ ArbitroError,
1614
+ Codec,
1615
+ Consumer,
1616
+ DeliverPolicy,
1617
+ ErrorCode,
1618
+ JournalType,
1619
+ JsonCodec,
1620
+ Message,
1621
+ Stream,
1622
+ StringCodec,
1623
+ Subscription,
1624
+ Topic,
1625
+ decodeJson,
1626
+ decodeString,
1627
+ encodeJson,
1628
+ encodeString,
1629
+ makeLazyMessage,
1630
+ schema,
1631
+ streamId,
1632
+ zodCodec
1633
+ });
1634
+ //# sourceMappingURL=index.js.map