@arbitro/client 0.4.1 → 0.5.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/README.md +68 -0
- package/dist/{chunk-6BCX2E2R.mjs → chunk-C2QLJBAC.mjs} +1 -1
- package/dist/{chunk-6BCX2E2R.mjs.map → chunk-C2QLJBAC.mjs.map} +1 -1
- package/dist/{chunk-SKCXQO7R.mjs → chunk-GW36GP2C.mjs} +2 -2
- package/dist/{constants-KF57DJ2L.mjs → constants-57DO6N3H.mjs} +2 -2
- package/dist/index.d.mts +61 -1
- package/dist/index.d.ts +61 -1
- package/dist/index.js +258 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +259 -5
- package/dist/index.mjs.map +1 -1
- package/dist/{publish-UA3YZGMK.mjs → publish-BSVUMN7T.mjs} +3 -3
- package/package.json +1 -1
- /package/dist/{chunk-SKCXQO7R.mjs.map → chunk-GW36GP2C.mjs.map} +0 -0
- /package/dist/{constants-KF57DJ2L.mjs.map → constants-57DO6N3H.mjs.map} +0 -0
- /package/dist/{publish-UA3YZGMK.mjs.map → publish-BSVUMN7T.mjs.map} +0 -0
package/README.md
CHANGED
|
@@ -248,6 +248,74 @@ await client.publishDelayed("ORDERS", "orders.reminder", payload, 5000); // 5s d
|
|
|
248
248
|
|
|
249
249
|
Messages are persisted immediately — survives broker restart.
|
|
250
250
|
|
|
251
|
+
## Workflow Orchestration
|
|
252
|
+
|
|
253
|
+
Client-side linear pipelines over Arbitro streams. The broker has no workflow-specific code -- everything uses streams, consumer groups, and idempotent publish.
|
|
254
|
+
|
|
255
|
+
### WorkflowBuilder API
|
|
256
|
+
|
|
257
|
+
| Method | Signature | Description |
|
|
258
|
+
|--------|-----------|-------------|
|
|
259
|
+
| `trigger` | `(subject: string) => this` | Subject pattern that triggers new instances. Required. |
|
|
260
|
+
| `triggerStream` | *(not yet implemented)* | Planned: auto-subscribe to an external stream for trigger. |
|
|
261
|
+
| `step` | `(name: string, handler: StepHandler) => this` | Append a processing step. |
|
|
262
|
+
| `compensate` | *(not yet implemented)* | Planned: rollback handler per step (saga pattern). |
|
|
263
|
+
| `maxRetries` | *(not yet implemented)* | Planned: attempts before DLQ. |
|
|
264
|
+
| `maxContextSize` | *(not yet implemented)* | Planned: max context payload in bytes. |
|
|
265
|
+
| `ackWait` | `(ms: number) => this` | Ack timeout for failover (default: 30000). |
|
|
266
|
+
| `inflight` | `(n: number) => this` | Concurrent tasks per worker (default: 10). |
|
|
267
|
+
| `start` | `() => Promise<WorkflowHandle>` | Register streams, consumer, and start processing. |
|
|
268
|
+
|
|
269
|
+
### WorkflowHandle API
|
|
270
|
+
|
|
271
|
+
| Method | Signature | Description |
|
|
272
|
+
|--------|-----------|-------------|
|
|
273
|
+
| `trigger` | `(client: ArbitroClient, context: Buffer) => Promise<number>` | Trigger a new workflow instance. Returns the instance ID. |
|
|
274
|
+
| `name` | `string` (getter) | Workflow name. |
|
|
275
|
+
|
|
276
|
+
### Complete Example
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
import { ArbitroClient, WorkflowBuilder } from '@arbitro/client'
|
|
280
|
+
import type { StepContext, StepResult } from '@arbitro/client'
|
|
281
|
+
|
|
282
|
+
const client = new ArbitroClient({ servers: ['127.0.0.1:9898'] })
|
|
283
|
+
await client.connect()
|
|
284
|
+
|
|
285
|
+
const wf = await new WorkflowBuilder(client, 'order-process')
|
|
286
|
+
.trigger('orders.created')
|
|
287
|
+
// Step 1: validate
|
|
288
|
+
.step('validate', async (ctx: StepContext): Promise<StepResult> => {
|
|
289
|
+
const validated = await validateOrder(ctx.context)
|
|
290
|
+
return { context: validated }
|
|
291
|
+
})
|
|
292
|
+
// Step 2: charge
|
|
293
|
+
.step('charge', async (ctx: StepContext): Promise<StepResult> => {
|
|
294
|
+
const receipt = await chargePayment(ctx.context)
|
|
295
|
+
return { context: receipt }
|
|
296
|
+
})
|
|
297
|
+
// Step 3: ship
|
|
298
|
+
.step('ship', async (ctx: StepContext): Promise<StepResult> => {
|
|
299
|
+
const tracking = await createShipment(ctx.context)
|
|
300
|
+
return { context: tracking }
|
|
301
|
+
})
|
|
302
|
+
.ackWait(30_000)
|
|
303
|
+
.inflight(10)
|
|
304
|
+
.start()
|
|
305
|
+
|
|
306
|
+
// Manual trigger
|
|
307
|
+
const instanceId = await wf.trigger(client, Buffer.from('order-123-payload'))
|
|
308
|
+
console.log(`started instance ${instanceId}`)
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Internals
|
|
312
|
+
|
|
313
|
+
- Tasks flow through `_wf.{name}.tasks` stream with a consumer `_wf.{name}.workers`.
|
|
314
|
+
- Each step transition publishes with `msgId` format `wf:{instance}:{step}:{attempt}` for deduplication.
|
|
315
|
+
- `ackWait` enables failover: if a worker dies, the broker redelivers to another subscriber.
|
|
316
|
+
|
|
317
|
+
> **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.
|
|
318
|
+
|
|
251
319
|
## Reconnect behavior
|
|
252
320
|
|
|
253
321
|
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:
|
|
@@ -1 +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\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"],"mappings":"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","names":["Role","Cap","Flag","EntryFlag","Action"]}
|
|
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\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-
|
|
11
|
+
} from "./chunk-C2QLJBAC.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-
|
|
124
|
+
//# sourceMappingURL=chunk-GW36GP2C.mjs.map
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
OFF_MSG_LEN,
|
|
14
14
|
OFF_SEQ,
|
|
15
15
|
Role
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-C2QLJBAC.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-
|
|
33
|
+
//# sourceMappingURL=constants-57DO6N3H.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. */
|
|
@@ -654,6 +656,64 @@ declare class Topic<T extends Record<string, unknown>> {
|
|
|
654
656
|
|
|
655
657
|
declare function streamId(name: Buffer | string): number;
|
|
656
658
|
|
|
659
|
+
declare class WorkflowHandle {
|
|
660
|
+
private readonly workflowName;
|
|
661
|
+
private readonly taskStreamName;
|
|
662
|
+
private readonly dlqStreamName;
|
|
663
|
+
private readonly sub;
|
|
664
|
+
private readonly triggerSub;
|
|
665
|
+
constructor(workflowName: string, taskStreamName: string, dlqStreamName: string, sub: unknown, triggerSub: unknown | undefined);
|
|
666
|
+
get name(): string;
|
|
667
|
+
get taskStream(): string;
|
|
668
|
+
get dlqStream(): string;
|
|
669
|
+
trigger(client: ArbitroClient, context: Buffer): Promise<number>;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
interface StepContext {
|
|
673
|
+
readonly name: string;
|
|
674
|
+
readonly instanceId: number;
|
|
675
|
+
readonly stepIndex: number;
|
|
676
|
+
readonly attempt: number;
|
|
677
|
+
readonly context: Buffer;
|
|
678
|
+
}
|
|
679
|
+
interface StepResult {
|
|
680
|
+
readonly context: Buffer;
|
|
681
|
+
}
|
|
682
|
+
type StepHandler = (ctx: StepContext) => Promise<StepResult>;
|
|
683
|
+
declare class WorkflowBuilder {
|
|
684
|
+
private readonly client;
|
|
685
|
+
private readonly workflowName;
|
|
686
|
+
private triggerSubject;
|
|
687
|
+
private triggerStreamName;
|
|
688
|
+
private readonly steps;
|
|
689
|
+
private ackWaitMs;
|
|
690
|
+
private maxInflightVal;
|
|
691
|
+
private maxRetriesVal;
|
|
692
|
+
private maxContextSizeVal;
|
|
693
|
+
constructor(client: ArbitroClient, workflowName: string);
|
|
694
|
+
trigger(subject: string): this;
|
|
695
|
+
triggerStream(streamName: string): this;
|
|
696
|
+
step(name: string, handler: StepHandler): this;
|
|
697
|
+
/** Compensation handler for the most recently added step. */
|
|
698
|
+
compensate(_stepName: string, handler: StepHandler): this;
|
|
699
|
+
ackWait(ms: number): this;
|
|
700
|
+
inflight(n: number): this;
|
|
701
|
+
maxRetries(n: number): this;
|
|
702
|
+
maxContextSize(bytes: number): this;
|
|
703
|
+
start(): Promise<WorkflowHandle>;
|
|
704
|
+
private subscribeWorker;
|
|
705
|
+
private subscribeTrigger;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/** Bit flag set on stepIndex to mark compensation tasks. */
|
|
709
|
+
declare const COMPENSATION_BIT = 32768;
|
|
710
|
+
interface DecodedTask {
|
|
711
|
+
readonly instanceId: number;
|
|
712
|
+
readonly stepIndex: number;
|
|
713
|
+
readonly attempt: number;
|
|
714
|
+
readonly context: Buffer;
|
|
715
|
+
}
|
|
716
|
+
|
|
657
717
|
/** Wraps a ZodObject schema as an Encoding<T>.
|
|
658
718
|
*
|
|
659
719
|
* - encode: msgpack (no Zod overhead — bytes out)
|
|
@@ -667,4 +727,4 @@ declare function zodCodec<S extends ZodRawShape>(zodSchema: ZodObject<S>): Encod
|
|
|
667
727
|
readonly fields: string[];
|
|
668
728
|
};
|
|
669
729
|
|
|
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 };
|
|
730
|
+
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. */
|
|
@@ -654,6 +656,64 @@ declare class Topic<T extends Record<string, unknown>> {
|
|
|
654
656
|
|
|
655
657
|
declare function streamId(name: Buffer | string): number;
|
|
656
658
|
|
|
659
|
+
declare class WorkflowHandle {
|
|
660
|
+
private readonly workflowName;
|
|
661
|
+
private readonly taskStreamName;
|
|
662
|
+
private readonly dlqStreamName;
|
|
663
|
+
private readonly sub;
|
|
664
|
+
private readonly triggerSub;
|
|
665
|
+
constructor(workflowName: string, taskStreamName: string, dlqStreamName: string, sub: unknown, triggerSub: unknown | undefined);
|
|
666
|
+
get name(): string;
|
|
667
|
+
get taskStream(): string;
|
|
668
|
+
get dlqStream(): string;
|
|
669
|
+
trigger(client: ArbitroClient, context: Buffer): Promise<number>;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
interface StepContext {
|
|
673
|
+
readonly name: string;
|
|
674
|
+
readonly instanceId: number;
|
|
675
|
+
readonly stepIndex: number;
|
|
676
|
+
readonly attempt: number;
|
|
677
|
+
readonly context: Buffer;
|
|
678
|
+
}
|
|
679
|
+
interface StepResult {
|
|
680
|
+
readonly context: Buffer;
|
|
681
|
+
}
|
|
682
|
+
type StepHandler = (ctx: StepContext) => Promise<StepResult>;
|
|
683
|
+
declare class WorkflowBuilder {
|
|
684
|
+
private readonly client;
|
|
685
|
+
private readonly workflowName;
|
|
686
|
+
private triggerSubject;
|
|
687
|
+
private triggerStreamName;
|
|
688
|
+
private readonly steps;
|
|
689
|
+
private ackWaitMs;
|
|
690
|
+
private maxInflightVal;
|
|
691
|
+
private maxRetriesVal;
|
|
692
|
+
private maxContextSizeVal;
|
|
693
|
+
constructor(client: ArbitroClient, workflowName: string);
|
|
694
|
+
trigger(subject: string): this;
|
|
695
|
+
triggerStream(streamName: string): this;
|
|
696
|
+
step(name: string, handler: StepHandler): this;
|
|
697
|
+
/** Compensation handler for the most recently added step. */
|
|
698
|
+
compensate(_stepName: string, handler: StepHandler): this;
|
|
699
|
+
ackWait(ms: number): this;
|
|
700
|
+
inflight(n: number): this;
|
|
701
|
+
maxRetries(n: number): this;
|
|
702
|
+
maxContextSize(bytes: number): this;
|
|
703
|
+
start(): Promise<WorkflowHandle>;
|
|
704
|
+
private subscribeWorker;
|
|
705
|
+
private subscribeTrigger;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/** Bit flag set on stepIndex to mark compensation tasks. */
|
|
709
|
+
declare const COMPENSATION_BIT = 32768;
|
|
710
|
+
interface DecodedTask {
|
|
711
|
+
readonly instanceId: number;
|
|
712
|
+
readonly stepIndex: number;
|
|
713
|
+
readonly attempt: number;
|
|
714
|
+
readonly context: Buffer;
|
|
715
|
+
}
|
|
716
|
+
|
|
657
717
|
/** Wraps a ZodObject schema as an Encoding<T>.
|
|
658
718
|
*
|
|
659
719
|
* - encode: msgpack (no Zod overhead — bytes out)
|
|
@@ -667,4 +727,4 @@ declare function zodCodec<S extends ZodRawShape>(zodSchema: ZodObject<S>): Encod
|
|
|
667
727
|
readonly fields: string[];
|
|
668
728
|
};
|
|
669
729
|
|
|
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 };
|
|
730
|
+
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.js
CHANGED
|
@@ -267,6 +267,7 @@ __export(index_exports, {
|
|
|
267
267
|
AckPolicy: () => AckPolicy,
|
|
268
268
|
ArbitroClient: () => ArbitroClient,
|
|
269
269
|
ArbitroError: () => ArbitroError,
|
|
270
|
+
COMPENSATION_BIT: () => COMPENSATION_BIT,
|
|
270
271
|
Codec: () => Codec,
|
|
271
272
|
Consumer: () => Consumer,
|
|
272
273
|
CronBuilder: () => CronBuilder,
|
|
@@ -280,6 +281,8 @@ __export(index_exports, {
|
|
|
280
281
|
StringCodec: () => StringCodec,
|
|
281
282
|
Subscription: () => Subscription,
|
|
282
283
|
Topic: () => Topic,
|
|
284
|
+
WorkflowBuilder: () => WorkflowBuilder,
|
|
285
|
+
WorkflowHandle: () => WorkflowHandle,
|
|
283
286
|
decodeJson: () => decodeJson,
|
|
284
287
|
decodeString: () => decodeString,
|
|
285
288
|
encodeJson: () => encodeJson,
|
|
@@ -1706,7 +1709,7 @@ var ArbitroClient = class {
|
|
|
1706
1709
|
async createConsumerRaw(streamName, config) {
|
|
1707
1710
|
const sid = await this.resolveStreamId(streamName);
|
|
1708
1711
|
const name = Buffer.from(config.name ?? streamName);
|
|
1709
|
-
const group = Buffer.from(config.name ?? streamName);
|
|
1712
|
+
const group = Buffer.from(config.group ?? config.name ?? streamName);
|
|
1710
1713
|
const filter = Buffer.from(config.filter ?? "");
|
|
1711
1714
|
const ackPolicyByte = config.ackPolicy === "none" /* None */ ? 0 : 1;
|
|
1712
1715
|
const opts = {
|
|
@@ -1916,6 +1919,257 @@ function streamId(name) {
|
|
|
1916
1919
|
return h >>> 0;
|
|
1917
1920
|
}
|
|
1918
1921
|
|
|
1922
|
+
// src/workflow/task.ts
|
|
1923
|
+
var TASK_HEADER = 7;
|
|
1924
|
+
var COMPENSATION_BIT = 32768;
|
|
1925
|
+
function encodeTask(instanceId, stepIndex, attempt, context) {
|
|
1926
|
+
const buf = Buffer.allocUnsafe(TASK_HEADER + context.length);
|
|
1927
|
+
buf.writeUInt32LE(instanceId, 0);
|
|
1928
|
+
buf.writeUInt16LE(stepIndex, 4);
|
|
1929
|
+
buf[6] = attempt;
|
|
1930
|
+
context.copy(buf, TASK_HEADER);
|
|
1931
|
+
return buf;
|
|
1932
|
+
}
|
|
1933
|
+
function decodeTask(payload) {
|
|
1934
|
+
if (payload.length < TASK_HEADER) return void 0;
|
|
1935
|
+
return {
|
|
1936
|
+
instanceId: payload.readUInt32LE(0),
|
|
1937
|
+
stepIndex: payload.readUInt16LE(4),
|
|
1938
|
+
attempt: payload[6],
|
|
1939
|
+
context: payload.subarray(TASK_HEADER)
|
|
1940
|
+
};
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
// src/workflow/handle.ts
|
|
1944
|
+
var nextInstanceId = 1;
|
|
1945
|
+
function allocInstanceId() {
|
|
1946
|
+
return nextInstanceId++;
|
|
1947
|
+
}
|
|
1948
|
+
var WorkflowHandle = class {
|
|
1949
|
+
constructor(workflowName, taskStreamName, dlqStreamName, sub, triggerSub) {
|
|
1950
|
+
this.workflowName = workflowName;
|
|
1951
|
+
this.taskStreamName = taskStreamName;
|
|
1952
|
+
this.dlqStreamName = dlqStreamName;
|
|
1953
|
+
this.sub = sub;
|
|
1954
|
+
this.triggerSub = triggerSub;
|
|
1955
|
+
}
|
|
1956
|
+
get name() {
|
|
1957
|
+
return this.workflowName;
|
|
1958
|
+
}
|
|
1959
|
+
get taskStream() {
|
|
1960
|
+
return this.taskStreamName;
|
|
1961
|
+
}
|
|
1962
|
+
get dlqStream() {
|
|
1963
|
+
return this.dlqStreamName;
|
|
1964
|
+
}
|
|
1965
|
+
async trigger(client, context) {
|
|
1966
|
+
const instanceId = allocInstanceId();
|
|
1967
|
+
const msgId = `wf:${instanceId}:0:0`;
|
|
1968
|
+
const subject = `_wf.${this.workflowName}.step.0`;
|
|
1969
|
+
const task = encodeTask(instanceId, 0, 0, context);
|
|
1970
|
+
await client.publish(this.taskStreamName, subject, task, { msgId });
|
|
1971
|
+
return instanceId;
|
|
1972
|
+
}
|
|
1973
|
+
};
|
|
1974
|
+
|
|
1975
|
+
// src/workflow/processor.ts
|
|
1976
|
+
async function processMessage(cfg, msg) {
|
|
1977
|
+
const task = decodeTask(msg.data());
|
|
1978
|
+
if (!task) {
|
|
1979
|
+
msg.ack();
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
if (task.context.length > cfg.maxContextSize) {
|
|
1983
|
+
msg.ack();
|
|
1984
|
+
return;
|
|
1985
|
+
}
|
|
1986
|
+
const isCompensation = (task.stepIndex & COMPENSATION_BIT) !== 0;
|
|
1987
|
+
if (isCompensation) {
|
|
1988
|
+
await runCompensation(cfg, msg, task);
|
|
1989
|
+
return;
|
|
1990
|
+
}
|
|
1991
|
+
if (task.stepIndex >= cfg.steps.length) {
|
|
1992
|
+
msg.ack();
|
|
1993
|
+
return;
|
|
1994
|
+
}
|
|
1995
|
+
await runStep(cfg, msg, task);
|
|
1996
|
+
}
|
|
1997
|
+
async function runCompensation(cfg, msg, task) {
|
|
1998
|
+
const idx = task.stepIndex & ~COMPENSATION_BIT;
|
|
1999
|
+
const comp = cfg.steps[idx]?.compensation;
|
|
2000
|
+
if (comp) {
|
|
2001
|
+
try {
|
|
2002
|
+
await comp({ name: cfg.name, instanceId: task.instanceId, stepIndex: idx, attempt: task.attempt, context: task.context });
|
|
2003
|
+
} catch {
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
msg.ack();
|
|
2007
|
+
}
|
|
2008
|
+
async function runStep(cfg, msg, task) {
|
|
2009
|
+
const handler = cfg.steps[task.stepIndex].handler;
|
|
2010
|
+
try {
|
|
2011
|
+
const result = await handler({
|
|
2012
|
+
name: cfg.name,
|
|
2013
|
+
instanceId: task.instanceId,
|
|
2014
|
+
stepIndex: task.stepIndex,
|
|
2015
|
+
attempt: task.attempt,
|
|
2016
|
+
context: task.context
|
|
2017
|
+
});
|
|
2018
|
+
if (result.context.length > cfg.maxContextSize) {
|
|
2019
|
+
msg.nack();
|
|
2020
|
+
return;
|
|
2021
|
+
}
|
|
2022
|
+
await advance(cfg, msg, task, result);
|
|
2023
|
+
} catch (err) {
|
|
2024
|
+
await onFailure(cfg, msg, task, err);
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
async function advance(cfg, msg, task, result) {
|
|
2028
|
+
const nextStep = task.stepIndex + 1;
|
|
2029
|
+
if (nextStep < cfg.steps.length) {
|
|
2030
|
+
const msgId = `wf:${task.instanceId}:${nextStep}:0`;
|
|
2031
|
+
const subject = `_wf.${cfg.name}.step.${nextStep}`;
|
|
2032
|
+
const buf = encodeTask(task.instanceId, nextStep, 0, result.context);
|
|
2033
|
+
await cfg.client.publish(cfg.taskStreamName, subject, buf, { msgId });
|
|
2034
|
+
}
|
|
2035
|
+
msg.ack();
|
|
2036
|
+
}
|
|
2037
|
+
async function onFailure(cfg, msg, task, err) {
|
|
2038
|
+
if (task.attempt >= cfg.maxRetries) {
|
|
2039
|
+
await publishDlq(cfg, task, err);
|
|
2040
|
+
await publishCompensations(cfg, task);
|
|
2041
|
+
msg.ack();
|
|
2042
|
+
} else {
|
|
2043
|
+
msg.nack();
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
async function publishDlq(cfg, task, err) {
|
|
2047
|
+
const dlqSubject = `_wf.${cfg.name}.dlq.${task.stepIndex}`;
|
|
2048
|
+
const errBytes = Buffer.from(String(err));
|
|
2049
|
+
const buf = Buffer.allocUnsafe(7 + 4 + errBytes.length + task.context.length);
|
|
2050
|
+
buf.writeUInt32LE(task.instanceId, 0);
|
|
2051
|
+
buf.writeUInt16LE(task.stepIndex, 4);
|
|
2052
|
+
buf[6] = task.attempt;
|
|
2053
|
+
buf.writeUInt32LE(errBytes.length, 7);
|
|
2054
|
+
errBytes.copy(buf, 11);
|
|
2055
|
+
task.context.copy(buf, 11 + errBytes.length);
|
|
2056
|
+
const msgId = `wf:${task.instanceId}:dlq:${task.stepIndex}`;
|
|
2057
|
+
await cfg.client.publish(cfg.dlqStreamName, dlqSubject, buf, { msgId }).catch(() => {
|
|
2058
|
+
});
|
|
2059
|
+
}
|
|
2060
|
+
async function publishCompensations(cfg, task) {
|
|
2061
|
+
for (let i = task.stepIndex - 1; i >= 0; i--) {
|
|
2062
|
+
const compStep = COMPENSATION_BIT | i;
|
|
2063
|
+
const subject = `_wf.${cfg.name}.compensate.${i}`;
|
|
2064
|
+
const buf = encodeTask(task.instanceId, compStep, 0, task.context);
|
|
2065
|
+
const msgId = `wf:${task.instanceId}:comp:${i}`;
|
|
2066
|
+
await cfg.client.publish(cfg.taskStreamName, subject, buf, { msgId }).catch(() => {
|
|
2067
|
+
});
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
// src/workflow/workflow.ts
|
|
2072
|
+
var nextWorkerUid = 1;
|
|
2073
|
+
var WorkflowBuilder = class {
|
|
2074
|
+
constructor(client, workflowName) {
|
|
2075
|
+
this.client = client;
|
|
2076
|
+
this.workflowName = workflowName;
|
|
2077
|
+
}
|
|
2078
|
+
triggerSubject;
|
|
2079
|
+
triggerStreamName;
|
|
2080
|
+
steps = [];
|
|
2081
|
+
ackWaitMs = 3e4;
|
|
2082
|
+
maxInflightVal = 10;
|
|
2083
|
+
maxRetriesVal = 3;
|
|
2084
|
+
maxContextSizeVal = 256 * 1024;
|
|
2085
|
+
trigger(subject) {
|
|
2086
|
+
this.triggerSubject = subject;
|
|
2087
|
+
return this;
|
|
2088
|
+
}
|
|
2089
|
+
triggerStream(streamName) {
|
|
2090
|
+
this.triggerStreamName = streamName;
|
|
2091
|
+
return this;
|
|
2092
|
+
}
|
|
2093
|
+
step(name, handler) {
|
|
2094
|
+
this.steps.push({ name, handler, compensation: void 0 });
|
|
2095
|
+
return this;
|
|
2096
|
+
}
|
|
2097
|
+
/** Compensation handler for the most recently added step. */
|
|
2098
|
+
compensate(_stepName, handler) {
|
|
2099
|
+
const last = this.steps[this.steps.length - 1];
|
|
2100
|
+
if (last) last.compensation = handler;
|
|
2101
|
+
return this;
|
|
2102
|
+
}
|
|
2103
|
+
ackWait(ms) {
|
|
2104
|
+
this.ackWaitMs = ms;
|
|
2105
|
+
return this;
|
|
2106
|
+
}
|
|
2107
|
+
inflight(n) {
|
|
2108
|
+
this.maxInflightVal = n;
|
|
2109
|
+
return this;
|
|
2110
|
+
}
|
|
2111
|
+
maxRetries(n) {
|
|
2112
|
+
this.maxRetriesVal = n;
|
|
2113
|
+
return this;
|
|
2114
|
+
}
|
|
2115
|
+
maxContextSize(bytes) {
|
|
2116
|
+
this.maxContextSizeVal = bytes;
|
|
2117
|
+
return this;
|
|
2118
|
+
}
|
|
2119
|
+
async start() {
|
|
2120
|
+
if (!this.triggerSubject) throw new Error("trigger subject required");
|
|
2121
|
+
if (this.steps.length === 0) throw new Error("at least one step required");
|
|
2122
|
+
const name = this.workflowName;
|
|
2123
|
+
const taskStream = `_wf.${name}.tasks`;
|
|
2124
|
+
const taskSubject = `_wf.${name}.>`;
|
|
2125
|
+
const dlqStream = `_wf.${name}.dlq`;
|
|
2126
|
+
const dlqSubject = `_wf.${name}.dlq.>`;
|
|
2127
|
+
await this.client.upsertStream(taskStream, { subjectFilter: taskSubject, idempotencyWindowMs: 3e5 });
|
|
2128
|
+
await this.client.upsertStream(dlqStream, { subjectFilter: dlqSubject });
|
|
2129
|
+
const cfg = {
|
|
2130
|
+
client: this.client,
|
|
2131
|
+
name,
|
|
2132
|
+
taskStreamName: taskStream,
|
|
2133
|
+
dlqStreamName: dlqStream,
|
|
2134
|
+
steps: this.steps,
|
|
2135
|
+
maxContextSize: this.maxContextSizeVal,
|
|
2136
|
+
maxRetries: this.maxRetriesVal
|
|
2137
|
+
};
|
|
2138
|
+
const sub = await this.subscribeWorker(cfg, taskStream, taskSubject);
|
|
2139
|
+
const triggerSub = await this.subscribeTrigger(taskStream, name);
|
|
2140
|
+
return new WorkflowHandle(name, taskStream, dlqStream, sub, triggerSub);
|
|
2141
|
+
}
|
|
2142
|
+
async subscribeWorker(cfg, taskStream, taskSubject) {
|
|
2143
|
+
const uid = nextWorkerUid++;
|
|
2144
|
+
return this.client.subscribe(taskStream, {
|
|
2145
|
+
name: `_wf_${cfg.name}_w${uid}`,
|
|
2146
|
+
group: `_wf_${cfg.name}_workers`,
|
|
2147
|
+
filter: taskSubject,
|
|
2148
|
+
ackPolicy: "explicit" /* Explicit */,
|
|
2149
|
+
ackWaitMs: this.ackWaitMs,
|
|
2150
|
+
maxAckPending: this.maxInflightVal
|
|
2151
|
+
}, (msg) => {
|
|
2152
|
+
void processMessage(cfg, msg);
|
|
2153
|
+
});
|
|
2154
|
+
}
|
|
2155
|
+
async subscribeTrigger(taskStream, name) {
|
|
2156
|
+
if (!this.triggerSubject || !this.triggerStreamName) return void 0;
|
|
2157
|
+
const subject = this.triggerSubject;
|
|
2158
|
+
return this.client.subscribe(this.triggerStreamName, {
|
|
2159
|
+
name: `_wf_${name}_trigger`,
|
|
2160
|
+
filter: subject,
|
|
2161
|
+
ackPolicy: "explicit" /* Explicit */,
|
|
2162
|
+
ackWaitMs: this.ackWaitMs,
|
|
2163
|
+
maxAckPending: 1
|
|
2164
|
+
}, async (msg) => {
|
|
2165
|
+
const id = allocInstanceId();
|
|
2166
|
+
const taskBuf = encodeTask(id, 0, 0, msg.data());
|
|
2167
|
+
await this.client.publish(taskStream, `_wf.${name}.step.0`, taskBuf, { msgId: `wf:${id}:0:0` });
|
|
2168
|
+
msg.ack();
|
|
2169
|
+
});
|
|
2170
|
+
}
|
|
2171
|
+
};
|
|
2172
|
+
|
|
1919
2173
|
// src/utils/zod.ts
|
|
1920
2174
|
var import_msgpackr2 = require("msgpackr");
|
|
1921
2175
|
var packr = new import_msgpackr2.Packr({ structuredClone: false, useRecords: false });
|
|
@@ -1937,6 +2191,7 @@ function zodCodec(zodSchema) {
|
|
|
1937
2191
|
AckPolicy,
|
|
1938
2192
|
ArbitroClient,
|
|
1939
2193
|
ArbitroError,
|
|
2194
|
+
COMPENSATION_BIT,
|
|
1940
2195
|
Codec,
|
|
1941
2196
|
Consumer,
|
|
1942
2197
|
CronBuilder,
|
|
@@ -1950,6 +2205,8 @@ function zodCodec(zodSchema) {
|
|
|
1950
2205
|
StringCodec,
|
|
1951
2206
|
Subscription,
|
|
1952
2207
|
Topic,
|
|
2208
|
+
WorkflowBuilder,
|
|
2209
|
+
WorkflowHandle,
|
|
1953
2210
|
decodeJson,
|
|
1954
2211
|
decodeString,
|
|
1955
2212
|
encodeJson,
|