@arbitro/client 0.4.1 → 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
@@ -248,6 +253,74 @@ await client.publishDelayed("ORDERS", "orders.reminder", payload, 5000); // 5s d
248
253
 
249
254
  Messages are persisted immediately — survives broker restart.
250
255
 
256
+ ## Workflow Orchestration
257
+
258
+ Client-side linear pipelines over Arbitro streams. The broker has no workflow-specific code -- everything uses streams, consumer groups, and idempotent publish.
259
+
260
+ ### WorkflowBuilder API
261
+
262
+ | Method | Signature | Description |
263
+ |--------|-----------|-------------|
264
+ | `trigger` | `(subject: string) => this` | Subject pattern that triggers new instances. Required. |
265
+ | `triggerStream` | *(not yet implemented)* | Planned: auto-subscribe to an external stream for trigger. |
266
+ | `step` | `(name: string, handler: StepHandler) => this` | Append a processing step. |
267
+ | `compensate` | *(not yet implemented)* | Planned: rollback handler per step (saga pattern). |
268
+ | `maxRetries` | *(not yet implemented)* | Planned: attempts before DLQ. |
269
+ | `maxContextSize` | *(not yet implemented)* | Planned: max context payload in bytes. |
270
+ | `ackWait` | `(ms: number) => this` | Ack timeout for failover (default: 30000). |
271
+ | `inflight` | `(n: number) => this` | Concurrent tasks per worker (default: 10). |
272
+ | `start` | `() => Promise<WorkflowHandle>` | Register streams, consumer, and start processing. |
273
+
274
+ ### WorkflowHandle API
275
+
276
+ | Method | Signature | Description |
277
+ |--------|-----------|-------------|
278
+ | `trigger` | `(client: ArbitroClient, context: Buffer) => Promise<number>` | Trigger a new workflow instance. Returns the instance ID. |
279
+ | `name` | `string` (getter) | Workflow name. |
280
+
281
+ ### Complete Example
282
+
283
+ ```typescript
284
+ import { ArbitroClient, WorkflowBuilder } from '@arbitro/client'
285
+ import type { StepContext, StepResult } from '@arbitro/client'
286
+
287
+ const client = new ArbitroClient({ servers: ['127.0.0.1:9898'] })
288
+ await client.connect()
289
+
290
+ const wf = await new WorkflowBuilder(client, 'order-process')
291
+ .trigger('orders.created')
292
+ // Step 1: validate
293
+ .step('validate', async (ctx: StepContext): Promise<StepResult> => {
294
+ const validated = await validateOrder(ctx.context)
295
+ return { context: validated }
296
+ })
297
+ // Step 2: charge
298
+ .step('charge', async (ctx: StepContext): Promise<StepResult> => {
299
+ const receipt = await chargePayment(ctx.context)
300
+ return { context: receipt }
301
+ })
302
+ // Step 3: ship
303
+ .step('ship', async (ctx: StepContext): Promise<StepResult> => {
304
+ const tracking = await createShipment(ctx.context)
305
+ return { context: tracking }
306
+ })
307
+ .ackWait(30_000)
308
+ .inflight(10)
309
+ .start()
310
+
311
+ // Manual trigger
312
+ const instanceId = await wf.trigger(client, Buffer.from('order-123-payload'))
313
+ console.log(`started instance ${instanceId}`)
314
+ ```
315
+
316
+ ### Internals
317
+
318
+ - Tasks flow through `_wf.{name}.tasks` stream with a consumer `_wf.{name}.workers`.
319
+ - Each step transition publishes with `msgId` format `wf:{instance}:{step}:{attempt}` for deduplication.
320
+ - `ackWait` enables failover: if a worker dies, the broker redelivers to another subscriber.
321
+
322
+ > **Note:** The TypeScript workflow module currently implements the core step pipeline. Compensation (saga), DLQ, `triggerStream`, `maxRetries`, and `maxContextSize` are available in the Rust client and planned for the TS client.
323
+
251
324
  ## Reconnect behavior
252
325
 
253
326
  The TS client reconnects transport automatically and reattaches active subscriptions and cron jobs after reconnect. That behavior lives in the client, not in the benchmarks. This matters for:
@@ -271,6 +344,10 @@ npm run test:integration # requires Docker
271
344
  - `.agent/rules/*.md` — internal coding rules (hot-path discipline, wire protocol, etc.).
272
345
  - `CLAUDE.md` — index pointing at the rule files.
273
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
+
274
351
  ## License
275
352
 
276
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-6BCX2E2R.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-6BCX2E2R.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-SKCXQO7R.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-6BCX2E2R.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-KF57DJ2L.mjs.map
33
+ //# sourceMappingURL=constants-LWTWKOBZ.mjs.map
package/dist/index.d.mts CHANGED
@@ -86,6 +86,8 @@ interface SubjectInflightLimit {
86
86
  }
87
87
  interface ConsumerConfig {
88
88
  name?: string;
89
+ /** Shared consumer group name for round-robin delivery. Defaults to `name`. */
90
+ group?: string;
89
91
  filter?: string;
90
92
  fanout?: boolean;
91
93
  /** Consumer-side ACK policy. None = fire-and-forget delivery, Explicit = consumer must ACK. */
@@ -461,6 +463,8 @@ declare class Consumer {
461
463
  * from `streamName + name`. Equivalent of NATS `num_ack_pending`.
462
464
  */
463
465
  getPendings(): Promise<number>;
466
+ /** Tombstone a single message by seq. Returns true if found. */
467
+ deleteMessage(seq: bigint): Promise<boolean>;
464
468
  subscribe(cb?: RawCallback, opts?: SubscribeOptions): Promise<Subscription>;
465
469
  subscribe<T extends Record<string, unknown>>(codec: Encoding<T>, cb: (msg: LazyMessage<T>) => void, opts?: SubscribeOptions): Promise<Subscription>;
466
470
  }
@@ -572,6 +576,14 @@ declare class ArbitroClient {
572
576
  streamExists(name: string): Promise<boolean>;
573
577
  purgeStream(name: string): Promise<number>;
574
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>;
575
587
  createConsumer(streamName: string, config: ConsumerConfig): Promise<Consumer>;
576
588
  private createConsumerRaw;
577
589
  upsertConsumer(streamName: string, config: ConsumerConfig): Promise<Consumer>;
@@ -636,6 +648,8 @@ declare class Stream {
636
648
  * with the broker; the caller decides whether to wait via `await`. */
637
649
  publishBatch(messages: BatchPublishEntry[]): Promise<bigint>;
638
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>;
639
653
  consumer(overrides?: Partial<ConsumerConfig>): Consumer;
640
654
  topic<T extends Record<string, unknown>>(subject: string, codec: Encoding<T>): Topic<T>;
641
655
  }
@@ -654,6 +668,64 @@ declare class Topic<T extends Record<string, unknown>> {
654
668
 
655
669
  declare function streamId(name: Buffer | string): number;
656
670
 
671
+ declare class WorkflowHandle {
672
+ private readonly workflowName;
673
+ private readonly taskStreamName;
674
+ private readonly dlqStreamName;
675
+ private readonly sub;
676
+ private readonly triggerSub;
677
+ constructor(workflowName: string, taskStreamName: string, dlqStreamName: string, sub: unknown, triggerSub: unknown | undefined);
678
+ get name(): string;
679
+ get taskStream(): string;
680
+ get dlqStream(): string;
681
+ trigger(client: ArbitroClient, context: Buffer): Promise<number>;
682
+ }
683
+
684
+ interface StepContext {
685
+ readonly name: string;
686
+ readonly instanceId: number;
687
+ readonly stepIndex: number;
688
+ readonly attempt: number;
689
+ readonly context: Buffer;
690
+ }
691
+ interface StepResult {
692
+ readonly context: Buffer;
693
+ }
694
+ type StepHandler = (ctx: StepContext) => Promise<StepResult>;
695
+ declare class WorkflowBuilder {
696
+ private readonly client;
697
+ private readonly workflowName;
698
+ private triggerSubject;
699
+ private triggerStreamName;
700
+ private readonly steps;
701
+ private ackWaitMs;
702
+ private maxInflightVal;
703
+ private maxRetriesVal;
704
+ private maxContextSizeVal;
705
+ constructor(client: ArbitroClient, workflowName: string);
706
+ trigger(subject: string): this;
707
+ triggerStream(streamName: string): this;
708
+ step(name: string, handler: StepHandler): this;
709
+ /** Compensation handler for the most recently added step. */
710
+ compensate(_stepName: string, handler: StepHandler): this;
711
+ ackWait(ms: number): this;
712
+ inflight(n: number): this;
713
+ maxRetries(n: number): this;
714
+ maxContextSize(bytes: number): this;
715
+ start(): Promise<WorkflowHandle>;
716
+ private subscribeWorker;
717
+ private subscribeTrigger;
718
+ }
719
+
720
+ /** Bit flag set on stepIndex to mark compensation tasks. */
721
+ declare const COMPENSATION_BIT = 32768;
722
+ interface DecodedTask {
723
+ readonly instanceId: number;
724
+ readonly stepIndex: number;
725
+ readonly attempt: number;
726
+ readonly context: Buffer;
727
+ }
728
+
657
729
  /** Wraps a ZodObject schema as an Encoding<T>.
658
730
  *
659
731
  * - encode: msgpack (no Zod overhead — bytes out)
@@ -667,4 +739,4 @@ declare function zodCodec<S extends ZodRawShape>(zodSchema: ZodObject<S>): Encod
667
739
  readonly fields: string[];
668
740
  };
669
741
 
670
- export { AckPolicy, ArbitroClient, ArbitroError, type BatchPublishEntry, type ClientConfig, type ClientMetricsSnapshot, Codec, Consumer, type ConsumerConfig, type ConsumerInfo, CronBuilder, type CronContext, CronHandle, type CronHandler, type DeleteStreamOpts, DeliverPolicy, type Encoding, ErrorCode, type ErrorCodeValue, type FieldType, type FieldTypeMap, type FlushConfig, type InferSchema, type JournalConfig, JournalType, JsonCodec, type LazyMessage, type LogFn, type Logger, Message, type PublishOpts, type ReconnectConfig, type Schema, Stream, type StreamConfig, type StreamInfo, StringCodec, type SubjectInflightLimit, type SubscribeOptions, Subscription, type TlsConfig, Topic, decodeJson, decodeString, encodeJson, encodeString, makeLazyMessage, schema, streamId, zodCodec };
742
+ export { AckPolicy, ArbitroClient, ArbitroError, type BatchPublishEntry, COMPENSATION_BIT, type ClientConfig, type ClientMetricsSnapshot, Codec, Consumer, type ConsumerConfig, type ConsumerInfo, CronBuilder, type CronContext, CronHandle, type CronHandler, type DecodedTask, type DeleteStreamOpts, DeliverPolicy, type Encoding, ErrorCode, type ErrorCodeValue, type FieldType, type FieldTypeMap, type FlushConfig, type InferSchema, type JournalConfig, JournalType, JsonCodec, type LazyMessage, type LogFn, type Logger, Message, type PublishOpts, type ReconnectConfig, type Schema, type StepContext, type StepHandler, type StepResult, Stream, type StreamConfig, type StreamInfo, StringCodec, type SubjectInflightLimit, type SubscribeOptions, Subscription, type TlsConfig, Topic, WorkflowBuilder, WorkflowHandle, decodeJson, decodeString, encodeJson, encodeString, makeLazyMessage, schema, streamId, zodCodec };
package/dist/index.d.ts CHANGED
@@ -86,6 +86,8 @@ interface SubjectInflightLimit {
86
86
  }
87
87
  interface ConsumerConfig {
88
88
  name?: string;
89
+ /** Shared consumer group name for round-robin delivery. Defaults to `name`. */
90
+ group?: string;
89
91
  filter?: string;
90
92
  fanout?: boolean;
91
93
  /** Consumer-side ACK policy. None = fire-and-forget delivery, Explicit = consumer must ACK. */
@@ -461,6 +463,8 @@ declare class Consumer {
461
463
  * from `streamName + name`. Equivalent of NATS `num_ack_pending`.
462
464
  */
463
465
  getPendings(): Promise<number>;
466
+ /** Tombstone a single message by seq. Returns true if found. */
467
+ deleteMessage(seq: bigint): Promise<boolean>;
464
468
  subscribe(cb?: RawCallback, opts?: SubscribeOptions): Promise<Subscription>;
465
469
  subscribe<T extends Record<string, unknown>>(codec: Encoding<T>, cb: (msg: LazyMessage<T>) => void, opts?: SubscribeOptions): Promise<Subscription>;
466
470
  }
@@ -572,6 +576,14 @@ declare class ArbitroClient {
572
576
  streamExists(name: string): Promise<boolean>;
573
577
  purgeStream(name: string): Promise<number>;
574
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>;
575
587
  createConsumer(streamName: string, config: ConsumerConfig): Promise<Consumer>;
576
588
  private createConsumerRaw;
577
589
  upsertConsumer(streamName: string, config: ConsumerConfig): Promise<Consumer>;
@@ -636,6 +648,8 @@ declare class Stream {
636
648
  * with the broker; the caller decides whether to wait via `await`. */
637
649
  publishBatch(messages: BatchPublishEntry[]): Promise<bigint>;
638
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>;
639
653
  consumer(overrides?: Partial<ConsumerConfig>): Consumer;
640
654
  topic<T extends Record<string, unknown>>(subject: string, codec: Encoding<T>): Topic<T>;
641
655
  }
@@ -654,6 +668,64 @@ declare class Topic<T extends Record<string, unknown>> {
654
668
 
655
669
  declare function streamId(name: Buffer | string): number;
656
670
 
671
+ declare class WorkflowHandle {
672
+ private readonly workflowName;
673
+ private readonly taskStreamName;
674
+ private readonly dlqStreamName;
675
+ private readonly sub;
676
+ private readonly triggerSub;
677
+ constructor(workflowName: string, taskStreamName: string, dlqStreamName: string, sub: unknown, triggerSub: unknown | undefined);
678
+ get name(): string;
679
+ get taskStream(): string;
680
+ get dlqStream(): string;
681
+ trigger(client: ArbitroClient, context: Buffer): Promise<number>;
682
+ }
683
+
684
+ interface StepContext {
685
+ readonly name: string;
686
+ readonly instanceId: number;
687
+ readonly stepIndex: number;
688
+ readonly attempt: number;
689
+ readonly context: Buffer;
690
+ }
691
+ interface StepResult {
692
+ readonly context: Buffer;
693
+ }
694
+ type StepHandler = (ctx: StepContext) => Promise<StepResult>;
695
+ declare class WorkflowBuilder {
696
+ private readonly client;
697
+ private readonly workflowName;
698
+ private triggerSubject;
699
+ private triggerStreamName;
700
+ private readonly steps;
701
+ private ackWaitMs;
702
+ private maxInflightVal;
703
+ private maxRetriesVal;
704
+ private maxContextSizeVal;
705
+ constructor(client: ArbitroClient, workflowName: string);
706
+ trigger(subject: string): this;
707
+ triggerStream(streamName: string): this;
708
+ step(name: string, handler: StepHandler): this;
709
+ /** Compensation handler for the most recently added step. */
710
+ compensate(_stepName: string, handler: StepHandler): this;
711
+ ackWait(ms: number): this;
712
+ inflight(n: number): this;
713
+ maxRetries(n: number): this;
714
+ maxContextSize(bytes: number): this;
715
+ start(): Promise<WorkflowHandle>;
716
+ private subscribeWorker;
717
+ private subscribeTrigger;
718
+ }
719
+
720
+ /** Bit flag set on stepIndex to mark compensation tasks. */
721
+ declare const COMPENSATION_BIT = 32768;
722
+ interface DecodedTask {
723
+ readonly instanceId: number;
724
+ readonly stepIndex: number;
725
+ readonly attempt: number;
726
+ readonly context: Buffer;
727
+ }
728
+
657
729
  /** Wraps a ZodObject schema as an Encoding<T>.
658
730
  *
659
731
  * - encode: msgpack (no Zod overhead — bytes out)
@@ -667,4 +739,4 @@ declare function zodCodec<S extends ZodRawShape>(zodSchema: ZodObject<S>): Encod
667
739
  readonly fields: string[];
668
740
  };
669
741
 
670
- export { AckPolicy, ArbitroClient, ArbitroError, type BatchPublishEntry, type ClientConfig, type ClientMetricsSnapshot, Codec, Consumer, type ConsumerConfig, type ConsumerInfo, CronBuilder, type CronContext, CronHandle, type CronHandler, type DeleteStreamOpts, DeliverPolicy, type Encoding, ErrorCode, type ErrorCodeValue, type FieldType, type FieldTypeMap, type FlushConfig, type InferSchema, type JournalConfig, JournalType, JsonCodec, type LazyMessage, type LogFn, type Logger, Message, type PublishOpts, type ReconnectConfig, type Schema, Stream, type StreamConfig, type StreamInfo, StringCodec, type SubjectInflightLimit, type SubscribeOptions, Subscription, type TlsConfig, Topic, decodeJson, decodeString, encodeJson, encodeString, makeLazyMessage, schema, streamId, zodCodec };
742
+ export { AckPolicy, ArbitroClient, ArbitroError, type BatchPublishEntry, COMPENSATION_BIT, type ClientConfig, type ClientMetricsSnapshot, Codec, Consumer, type ConsumerConfig, type ConsumerInfo, CronBuilder, type CronContext, CronHandle, type CronHandler, type DecodedTask, type DeleteStreamOpts, DeliverPolicy, type Encoding, ErrorCode, type ErrorCodeValue, type FieldType, type FieldTypeMap, type FlushConfig, type InferSchema, type JournalConfig, JournalType, JsonCodec, type LazyMessage, type LogFn, type Logger, Message, type PublishOpts, type ReconnectConfig, type Schema, type StepContext, type StepHandler, type StepResult, Stream, type StreamConfig, type StreamInfo, StringCodec, type SubjectInflightLimit, type SubscribeOptions, Subscription, type TlsConfig, Topic, WorkflowBuilder, WorkflowHandle, decodeJson, decodeString, encodeJson, encodeString, makeLazyMessage, schema, streamId, zodCodec };