@arbitro/client 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -111,6 +111,11 @@ await client.upsertConsumer('orders', { name: 'workers', filter: 'orders.>' })
111
111
  await client.deleteConsumer('workers')
112
112
  await client.deleteStream('orders') // default: delete metadata + data
113
113
  await client.deleteStream('orders', { deleteData: false }) // preserve journal bytes
114
+
115
+ // Per-message deletion (tombstones a single message by sequence number)
116
+ await client.deleteMessage('orders', 42n) // true if deleted, false if not found/already deleted
117
+ await stream.deleteMessage(42n) // convenience — delegates to client.deleteMessage
118
+ await consumer.deleteMessage(42n) // convenience — delegates to client.deleteMessage
114
119
  ```
115
120
 
116
121
  ## Stream / consumer sugar
@@ -339,6 +344,10 @@ npm run test:integration # requires Docker
339
344
  - `.agent/rules/*.md` — internal coding rules (hot-path discipline, wire protocol, etc.).
340
345
  - `CLAUDE.md` — index pointing at the rule files.
341
346
 
347
+ ## Replication
348
+
349
+ Replication is transparent to the client -- `replicas` is set at `create_stream` time. The client publishes normally; the broker handles replication internally.
350
+
342
351
  ## License
343
352
 
344
353
  MIT — see [LICENSE](./LICENSE).
@@ -62,6 +62,7 @@ var Action = /* @__PURE__ */ ((Action2) => {
62
62
  Action2[Action2["ListStreams"] = 1028] = "ListStreams";
63
63
  Action2[Action2["PurgeStream"] = 1029] = "PurgeStream";
64
64
  Action2[Action2["DrainSubject"] = 1030] = "DrainSubject";
65
+ Action2[Action2["DeleteMessage"] = 1031] = "DeleteMessage";
65
66
  Action2[Action2["CreateConsumer"] = 1281] = "CreateConsumer";
66
67
  Action2[Action2["DeleteConsumer"] = 1282] = "DeleteConsumer";
67
68
  Action2[Action2["GetConsumer"] = 1283] = "GetConsumer";
@@ -99,4 +100,4 @@ export {
99
100
  EntryFlag,
100
101
  Action
101
102
  };
102
- //# sourceMappingURL=chunk-C2QLJBAC.mjs.map
103
+ //# sourceMappingURL=chunk-3EHQPPLU.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/proto/constants.ts"],"sourcesContent":["// V2 wire protocol constants — must match arbitro-proto/src/action.rs exactly.\n\n// ── Hello handshake (only frame without a Header) ───────────────────────\nexport const MAGIC_V2 = 0x32425241 // \"ARB2\" as u32 LE\nexport const HELLO_SIZE = 8\nexport const CURRENT_VERSION = 2\n\nexport const enum Role {\n Client = 0,\n Server = 1,\n}\n\nexport const enum Cap {\n Headers = 1 << 0,\n Reply = 1 << 1,\n BatchHeaders = 1 << 2,\n CompressedPayload = 1 << 3,\n}\n\n// ── Header (16 bytes, every frame after Hello) ──────────────────────────\nexport const HEADER_SIZE = 16\n\n// Byte offsets within the 16-byte header\nexport const OFF_ACTION = 0 // u16 LE\nexport const OFF_FLAGS = 2 // u8\nexport const OFF_ENTRY_FLAGS = 3 // u8\nexport const OFF_MSG_LEN = 4 // u32 LE\nexport const OFF_SEQ = 8 // u64 LE\n\n// ── Transport flags (header.flags, offset 2) ────────────────────────────\nexport const enum Flag {\n None = 0x00,\n AckReq = 0x01,\n Dup = 0x02,\n PriorityHigh = 0x04,\n}\n\n// ── Per-message flags (header.entry_flags, offset 3) ────────────────────\nexport const enum EntryFlag {\n None = 0x00,\n Retain = 0x01,\n Compressed = 0x02,\n NoBackpressure = 0x04,\n}\n\n// ── Action codes (0xFFGG: FF=family, GG=variant) ────────────────────────\nexport const enum Action {\n // 0x00xx — Handshake / control\n Hello = 0x0001,\n Auth = 0x0002,\n\n // 0x01xx — Publish family\n Publish = 0x0101,\n PublishAccumulate = 0x0102,\n PublishBatch = 0x0103,\n PublishWithReply = 0x0104,\n PublishWithHeaders = 0x0105,\n PublishBatchWithHeaders = 0x0106,\n\n // 0x02xx — Delivery / Ack\n Deliver = 0x0200,\n Ack = 0x0201,\n Nack = 0x0202,\n RepOk = 0x0203,\n RepError = 0x0204,\n RepBatch = 0x0205,\n BatchAck = 0x0206,\n FanoutBatch = 0x0207,\n AckSync = 0x0208,\n BatchAckSync = 0x0209,\n BatchNack = 0x020A,\n\n // 0x03xx — Subscription\n Subscribe = 0x0301,\n Unsubscribe = 0x0302,\n\n // 0x04xx — Stream management\n CreateStream = 0x0401,\n DeleteStream = 0x0402,\n GetStream = 0x0403,\n ListStreams = 0x0404,\n PurgeStream = 0x0405,\n DrainSubject = 0x0406,\n DeleteMessage = 0x0407,\n\n // 0x05xx — Consumer management\n CreateConsumer = 0x0501,\n DeleteConsumer = 0x0502,\n GetConsumer = 0x0503,\n ListConsumers = 0x0504,\n ConsumerStats = 0x0505,\n PauseConsumer = 0x0506,\n ResumeConsumer = 0x0507,\n\n // 0x08xx — Delayed publish\n PublishDelayed = 0x0801,\n\n // 0x06xx — System\n Ping = 0x0601,\n Pong = 0x0602,\n Connect = 0x0603,\n Connected = 0x0604,\n Disconnect = 0x0605,\n\n // 0x07xx — Cron scheduling\n CreateCron = 0x0701,\n DeleteCron = 0x0702,\n ListCrons = 0x0703,\n CronFire = 0x0704,\n CronAck = 0x0705,\n\n // 0x09xx reserved (workflow removed — now a client-side library).\n}\n"],"mappings":"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","names":["Role","Cap","Flag","EntryFlag","Action"]}
@@ -8,7 +8,7 @@ import {
8
8
  OFF_FLAGS,
9
9
  OFF_MSG_LEN,
10
10
  OFF_SEQ
11
- } from "./chunk-C2QLJBAC.mjs";
11
+ } from "./chunk-3EHQPPLU.mjs";
12
12
 
13
13
  // src/proto/frame.ts
14
14
  function frame(action, seq, bodyLen, flags = 0, entryFlags = 0) {
@@ -121,4 +121,4 @@ export {
121
121
  packPublishDelayed,
122
122
  packPublishBatch
123
123
  };
124
- //# sourceMappingURL=chunk-GW36GP2C.mjs.map
124
+ //# sourceMappingURL=chunk-BSPQZJHF.mjs.map
@@ -13,7 +13,7 @@ import {
13
13
  OFF_MSG_LEN,
14
14
  OFF_SEQ,
15
15
  Role
16
- } from "./chunk-C2QLJBAC.mjs";
16
+ } from "./chunk-3EHQPPLU.mjs";
17
17
  export {
18
18
  Action,
19
19
  CURRENT_VERSION,
@@ -30,4 +30,4 @@ export {
30
30
  OFF_SEQ,
31
31
  Role
32
32
  };
33
- //# sourceMappingURL=constants-57DO6N3H.mjs.map
33
+ //# sourceMappingURL=constants-LWTWKOBZ.mjs.map
package/dist/index.d.mts CHANGED
@@ -463,6 +463,8 @@ declare class Consumer {
463
463
  * from `streamName + name`. Equivalent of NATS `num_ack_pending`.
464
464
  */
465
465
  getPendings(): Promise<number>;
466
+ /** Tombstone a single message by seq. Returns true if found. */
467
+ deleteMessage(seq: bigint): Promise<boolean>;
466
468
  subscribe(cb?: RawCallback, opts?: SubscribeOptions): Promise<Subscription>;
467
469
  subscribe<T extends Record<string, unknown>>(codec: Encoding<T>, cb: (msg: LazyMessage<T>) => void, opts?: SubscribeOptions): Promise<Subscription>;
468
470
  }
@@ -574,6 +576,14 @@ declare class ArbitroClient {
574
576
  streamExists(name: string): Promise<boolean>;
575
577
  purgeStream(name: string): Promise<number>;
576
578
  drainSubject(streamName: string, subject: string): Promise<number>;
579
+ /**
580
+ * Tombstone a single message by sequence number.
581
+ *
582
+ * The broker marks the entry as deleted — it will never be delivered
583
+ * to any consumer. Returns `true` if the message was found and
584
+ * tombstoned, `false` if not found or already tombstoned.
585
+ */
586
+ deleteMessage(streamName: string, seq: bigint): Promise<boolean>;
577
587
  createConsumer(streamName: string, config: ConsumerConfig): Promise<Consumer>;
578
588
  private createConsumerRaw;
579
589
  upsertConsumer(streamName: string, config: ConsumerConfig): Promise<Consumer>;
@@ -638,6 +648,8 @@ declare class Stream {
638
648
  * with the broker; the caller decides whether to wait via `await`. */
639
649
  publishBatch(messages: BatchPublishEntry[]): Promise<bigint>;
640
650
  request(subject: string, data: Buffer, timeoutMs?: number): Promise<Buffer>;
651
+ /** Tombstone a single message by seq. Returns true if found. */
652
+ deleteMessage(seq: bigint): Promise<boolean>;
641
653
  consumer(overrides?: Partial<ConsumerConfig>): Consumer;
642
654
  topic<T extends Record<string, unknown>>(subject: string, codec: Encoding<T>): Topic<T>;
643
655
  }
package/dist/index.d.ts CHANGED
@@ -463,6 +463,8 @@ declare class Consumer {
463
463
  * from `streamName + name`. Equivalent of NATS `num_ack_pending`.
464
464
  */
465
465
  getPendings(): Promise<number>;
466
+ /** Tombstone a single message by seq. Returns true if found. */
467
+ deleteMessage(seq: bigint): Promise<boolean>;
466
468
  subscribe(cb?: RawCallback, opts?: SubscribeOptions): Promise<Subscription>;
467
469
  subscribe<T extends Record<string, unknown>>(codec: Encoding<T>, cb: (msg: LazyMessage<T>) => void, opts?: SubscribeOptions): Promise<Subscription>;
468
470
  }
@@ -574,6 +576,14 @@ declare class ArbitroClient {
574
576
  streamExists(name: string): Promise<boolean>;
575
577
  purgeStream(name: string): Promise<number>;
576
578
  drainSubject(streamName: string, subject: string): Promise<number>;
579
+ /**
580
+ * Tombstone a single message by sequence number.
581
+ *
582
+ * The broker marks the entry as deleted — it will never be delivered
583
+ * to any consumer. Returns `true` if the message was found and
584
+ * tombstoned, `false` if not found or already tombstoned.
585
+ */
586
+ deleteMessage(streamName: string, seq: bigint): Promise<boolean>;
577
587
  createConsumer(streamName: string, config: ConsumerConfig): Promise<Consumer>;
578
588
  private createConsumerRaw;
579
589
  upsertConsumer(streamName: string, config: ConsumerConfig): Promise<Consumer>;
@@ -638,6 +648,8 @@ declare class Stream {
638
648
  * with the broker; the caller decides whether to wait via `await`. */
639
649
  publishBatch(messages: BatchPublishEntry[]): Promise<bigint>;
640
650
  request(subject: string, data: Buffer, timeoutMs?: number): Promise<Buffer>;
651
+ /** Tombstone a single message by seq. Returns true if found. */
652
+ deleteMessage(seq: bigint): Promise<boolean>;
641
653
  consumer(overrides?: Partial<ConsumerConfig>): Consumer;
642
654
  topic<T extends Record<string, unknown>>(subject: string, codec: Encoding<T>): Topic<T>;
643
655
  }
package/dist/index.js CHANGED
@@ -115,6 +115,7 @@ var init_constants = __esm({
115
115
  Action2[Action2["ListStreams"] = 1028] = "ListStreams";
116
116
  Action2[Action2["PurgeStream"] = 1029] = "PurgeStream";
117
117
  Action2[Action2["DrainSubject"] = 1030] = "DrainSubject";
118
+ Action2[Action2["DeleteMessage"] = 1031] = "DeleteMessage";
118
119
  Action2[Action2["CreateConsumer"] = 1281] = "CreateConsumer";
119
120
  Action2[Action2["DeleteConsumer"] = 1282] = "DeleteConsumer";
120
121
  Action2[Action2["GetConsumer"] = 1283] = "GetConsumer";
@@ -562,6 +563,7 @@ var packDrainSubject = (seq, name, subject) => packCold2(1030 /* DrainSubject */
562
563
  name: bytesArr(name),
563
564
  subject: bytesArr(subject)
564
565
  });
566
+ var packDeleteMessage = (seq, name, msgSeq) => packCold2(1031 /* DeleteMessage */, seq, { name: bytesArr(name), seq: Number(msgSeq) });
565
567
  var packListStreams = (seq, offset = 0, limit = 1e3) => packCold2(1028 /* ListStreams */, seq, { offset: offset >>> 0, limit: limit >>> 0 });
566
568
  function packCreateConsumer(seq, opts) {
567
569
  const limits = (opts.subjectLimits ?? []).map((l) => ({
@@ -1225,6 +1227,10 @@ var Consumer = class {
1225
1227
  }
1226
1228
  return this.client.getPending(this.streamName, this.name);
1227
1229
  }
1230
+ /** Tombstone a single message by seq. Returns true if found. */
1231
+ deleteMessage(seq) {
1232
+ return this.client.deleteMessage(this.streamName, seq);
1233
+ }
1228
1234
  subscribe(codecOrCb, cbOrOpts, opts) {
1229
1235
  if (!codecOrCb || typeof codecOrCb === "function") {
1230
1236
  return this.client.subscribe(
@@ -1314,6 +1320,10 @@ var Stream = class {
1314
1320
  request(subject, data, timeoutMs) {
1315
1321
  return this.client.request(this.name, subject, data, timeoutMs);
1316
1322
  }
1323
+ /** Tombstone a single message by seq. Returns true if found. */
1324
+ deleteMessage(seq) {
1325
+ return this.client.deleteMessage(this.name, seq);
1326
+ }
1317
1327
  // ── Context factories ───────────────────────────────────────────────────
1318
1328
  consumer(overrides) {
1319
1329
  const config = {
@@ -1701,6 +1711,19 @@ var ArbitroClient = class {
1701
1711
  );
1702
1712
  return Number(refSeq);
1703
1713
  }
1714
+ /**
1715
+ * Tombstone a single message by sequence number.
1716
+ *
1717
+ * The broker marks the entry as deleted — it will never be delivered
1718
+ * to any consumer. Returns `true` if the message was found and
1719
+ * tombstoned, `false` if not found or already tombstoned.
1720
+ */
1721
+ async deleteMessage(streamName, seq) {
1722
+ const refSeq = await this.conn.sendExpectReply(
1723
+ packDeleteMessage(this.conn.nextSeq(), Buffer.from(streamName), seq)
1724
+ );
1725
+ return refSeq > 0n;
1726
+ }
1704
1727
  // ── Consumer management ───────────────────────────────────────────────────
1705
1728
  async createConsumer(streamName, config) {
1706
1729
  const consumerId = await this.createConsumerRaw(streamName, config);