@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/dist/index.mjs
CHANGED
|
@@ -5,13 +5,13 @@ import {
|
|
|
5
5
|
packPublish,
|
|
6
6
|
packPublishBatch,
|
|
7
7
|
packPublishWithReply
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-GW36GP2C.mjs";
|
|
9
9
|
import {
|
|
10
10
|
HEADER_SIZE,
|
|
11
11
|
OFF_ACTION,
|
|
12
12
|
OFF_MSG_LEN,
|
|
13
13
|
OFF_SEQ
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-C2QLJBAC.mjs";
|
|
15
15
|
|
|
16
16
|
// src/types/config.ts
|
|
17
17
|
var DeliverPolicy = /* @__PURE__ */ ((DeliverPolicy2) => {
|
|
@@ -1291,8 +1291,8 @@ var ArbitroClient = class {
|
|
|
1291
1291
|
*/
|
|
1292
1292
|
async publishDelayed(streamName, subject, data, delayMs) {
|
|
1293
1293
|
const sid = await this.resolveStreamId(streamName);
|
|
1294
|
-
const { packPublishDelayed: packPublishDelayed2 } = await import("./publish-
|
|
1295
|
-
const { Flag: Flag3 } = await import("./constants-
|
|
1294
|
+
const { packPublishDelayed: packPublishDelayed2 } = await import("./publish-BSVUMN7T.mjs");
|
|
1295
|
+
const { Flag: Flag3 } = await import("./constants-57DO6N3H.mjs");
|
|
1296
1296
|
const subj = Buffer.from(this.prefixed(subject));
|
|
1297
1297
|
await this.conn.sendExpectReply(
|
|
1298
1298
|
packPublishDelayed2(this.conn.nextSeq(), sid, subj, data, BigInt(delayMs), Flag3.AckReq)
|
|
@@ -1407,7 +1407,7 @@ var ArbitroClient = class {
|
|
|
1407
1407
|
async createConsumerRaw(streamName, config) {
|
|
1408
1408
|
const sid = await this.resolveStreamId(streamName);
|
|
1409
1409
|
const name = Buffer.from(config.name ?? streamName);
|
|
1410
|
-
const group = Buffer.from(config.name ?? streamName);
|
|
1410
|
+
const group = Buffer.from(config.group ?? config.name ?? streamName);
|
|
1411
1411
|
const filter = Buffer.from(config.filter ?? "");
|
|
1412
1412
|
const ackPolicyByte = config.ackPolicy === "none" /* None */ ? 0 : 1;
|
|
1413
1413
|
const opts = {
|
|
@@ -1617,6 +1617,257 @@ function streamId(name) {
|
|
|
1617
1617
|
return h >>> 0;
|
|
1618
1618
|
}
|
|
1619
1619
|
|
|
1620
|
+
// src/workflow/task.ts
|
|
1621
|
+
var TASK_HEADER = 7;
|
|
1622
|
+
var COMPENSATION_BIT = 32768;
|
|
1623
|
+
function encodeTask(instanceId, stepIndex, attempt, context) {
|
|
1624
|
+
const buf = Buffer.allocUnsafe(TASK_HEADER + context.length);
|
|
1625
|
+
buf.writeUInt32LE(instanceId, 0);
|
|
1626
|
+
buf.writeUInt16LE(stepIndex, 4);
|
|
1627
|
+
buf[6] = attempt;
|
|
1628
|
+
context.copy(buf, TASK_HEADER);
|
|
1629
|
+
return buf;
|
|
1630
|
+
}
|
|
1631
|
+
function decodeTask(payload) {
|
|
1632
|
+
if (payload.length < TASK_HEADER) return void 0;
|
|
1633
|
+
return {
|
|
1634
|
+
instanceId: payload.readUInt32LE(0),
|
|
1635
|
+
stepIndex: payload.readUInt16LE(4),
|
|
1636
|
+
attempt: payload[6],
|
|
1637
|
+
context: payload.subarray(TASK_HEADER)
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
// src/workflow/handle.ts
|
|
1642
|
+
var nextInstanceId = 1;
|
|
1643
|
+
function allocInstanceId() {
|
|
1644
|
+
return nextInstanceId++;
|
|
1645
|
+
}
|
|
1646
|
+
var WorkflowHandle = class {
|
|
1647
|
+
constructor(workflowName, taskStreamName, dlqStreamName, sub, triggerSub) {
|
|
1648
|
+
this.workflowName = workflowName;
|
|
1649
|
+
this.taskStreamName = taskStreamName;
|
|
1650
|
+
this.dlqStreamName = dlqStreamName;
|
|
1651
|
+
this.sub = sub;
|
|
1652
|
+
this.triggerSub = triggerSub;
|
|
1653
|
+
}
|
|
1654
|
+
get name() {
|
|
1655
|
+
return this.workflowName;
|
|
1656
|
+
}
|
|
1657
|
+
get taskStream() {
|
|
1658
|
+
return this.taskStreamName;
|
|
1659
|
+
}
|
|
1660
|
+
get dlqStream() {
|
|
1661
|
+
return this.dlqStreamName;
|
|
1662
|
+
}
|
|
1663
|
+
async trigger(client, context) {
|
|
1664
|
+
const instanceId = allocInstanceId();
|
|
1665
|
+
const msgId = `wf:${instanceId}:0:0`;
|
|
1666
|
+
const subject = `_wf.${this.workflowName}.step.0`;
|
|
1667
|
+
const task = encodeTask(instanceId, 0, 0, context);
|
|
1668
|
+
await client.publish(this.taskStreamName, subject, task, { msgId });
|
|
1669
|
+
return instanceId;
|
|
1670
|
+
}
|
|
1671
|
+
};
|
|
1672
|
+
|
|
1673
|
+
// src/workflow/processor.ts
|
|
1674
|
+
async function processMessage(cfg, msg) {
|
|
1675
|
+
const task = decodeTask(msg.data());
|
|
1676
|
+
if (!task) {
|
|
1677
|
+
msg.ack();
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
if (task.context.length > cfg.maxContextSize) {
|
|
1681
|
+
msg.ack();
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
const isCompensation = (task.stepIndex & COMPENSATION_BIT) !== 0;
|
|
1685
|
+
if (isCompensation) {
|
|
1686
|
+
await runCompensation(cfg, msg, task);
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
if (task.stepIndex >= cfg.steps.length) {
|
|
1690
|
+
msg.ack();
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
await runStep(cfg, msg, task);
|
|
1694
|
+
}
|
|
1695
|
+
async function runCompensation(cfg, msg, task) {
|
|
1696
|
+
const idx = task.stepIndex & ~COMPENSATION_BIT;
|
|
1697
|
+
const comp = cfg.steps[idx]?.compensation;
|
|
1698
|
+
if (comp) {
|
|
1699
|
+
try {
|
|
1700
|
+
await comp({ name: cfg.name, instanceId: task.instanceId, stepIndex: idx, attempt: task.attempt, context: task.context });
|
|
1701
|
+
} catch {
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
msg.ack();
|
|
1705
|
+
}
|
|
1706
|
+
async function runStep(cfg, msg, task) {
|
|
1707
|
+
const handler = cfg.steps[task.stepIndex].handler;
|
|
1708
|
+
try {
|
|
1709
|
+
const result = await handler({
|
|
1710
|
+
name: cfg.name,
|
|
1711
|
+
instanceId: task.instanceId,
|
|
1712
|
+
stepIndex: task.stepIndex,
|
|
1713
|
+
attempt: task.attempt,
|
|
1714
|
+
context: task.context
|
|
1715
|
+
});
|
|
1716
|
+
if (result.context.length > cfg.maxContextSize) {
|
|
1717
|
+
msg.nack();
|
|
1718
|
+
return;
|
|
1719
|
+
}
|
|
1720
|
+
await advance(cfg, msg, task, result);
|
|
1721
|
+
} catch (err) {
|
|
1722
|
+
await onFailure(cfg, msg, task, err);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
async function advance(cfg, msg, task, result) {
|
|
1726
|
+
const nextStep = task.stepIndex + 1;
|
|
1727
|
+
if (nextStep < cfg.steps.length) {
|
|
1728
|
+
const msgId = `wf:${task.instanceId}:${nextStep}:0`;
|
|
1729
|
+
const subject = `_wf.${cfg.name}.step.${nextStep}`;
|
|
1730
|
+
const buf = encodeTask(task.instanceId, nextStep, 0, result.context);
|
|
1731
|
+
await cfg.client.publish(cfg.taskStreamName, subject, buf, { msgId });
|
|
1732
|
+
}
|
|
1733
|
+
msg.ack();
|
|
1734
|
+
}
|
|
1735
|
+
async function onFailure(cfg, msg, task, err) {
|
|
1736
|
+
if (task.attempt >= cfg.maxRetries) {
|
|
1737
|
+
await publishDlq(cfg, task, err);
|
|
1738
|
+
await publishCompensations(cfg, task);
|
|
1739
|
+
msg.ack();
|
|
1740
|
+
} else {
|
|
1741
|
+
msg.nack();
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
async function publishDlq(cfg, task, err) {
|
|
1745
|
+
const dlqSubject = `_wf.${cfg.name}.dlq.${task.stepIndex}`;
|
|
1746
|
+
const errBytes = Buffer.from(String(err));
|
|
1747
|
+
const buf = Buffer.allocUnsafe(7 + 4 + errBytes.length + task.context.length);
|
|
1748
|
+
buf.writeUInt32LE(task.instanceId, 0);
|
|
1749
|
+
buf.writeUInt16LE(task.stepIndex, 4);
|
|
1750
|
+
buf[6] = task.attempt;
|
|
1751
|
+
buf.writeUInt32LE(errBytes.length, 7);
|
|
1752
|
+
errBytes.copy(buf, 11);
|
|
1753
|
+
task.context.copy(buf, 11 + errBytes.length);
|
|
1754
|
+
const msgId = `wf:${task.instanceId}:dlq:${task.stepIndex}`;
|
|
1755
|
+
await cfg.client.publish(cfg.dlqStreamName, dlqSubject, buf, { msgId }).catch(() => {
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1758
|
+
async function publishCompensations(cfg, task) {
|
|
1759
|
+
for (let i = task.stepIndex - 1; i >= 0; i--) {
|
|
1760
|
+
const compStep = COMPENSATION_BIT | i;
|
|
1761
|
+
const subject = `_wf.${cfg.name}.compensate.${i}`;
|
|
1762
|
+
const buf = encodeTask(task.instanceId, compStep, 0, task.context);
|
|
1763
|
+
const msgId = `wf:${task.instanceId}:comp:${i}`;
|
|
1764
|
+
await cfg.client.publish(cfg.taskStreamName, subject, buf, { msgId }).catch(() => {
|
|
1765
|
+
});
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
// src/workflow/workflow.ts
|
|
1770
|
+
var nextWorkerUid = 1;
|
|
1771
|
+
var WorkflowBuilder = class {
|
|
1772
|
+
constructor(client, workflowName) {
|
|
1773
|
+
this.client = client;
|
|
1774
|
+
this.workflowName = workflowName;
|
|
1775
|
+
}
|
|
1776
|
+
triggerSubject;
|
|
1777
|
+
triggerStreamName;
|
|
1778
|
+
steps = [];
|
|
1779
|
+
ackWaitMs = 3e4;
|
|
1780
|
+
maxInflightVal = 10;
|
|
1781
|
+
maxRetriesVal = 3;
|
|
1782
|
+
maxContextSizeVal = 256 * 1024;
|
|
1783
|
+
trigger(subject) {
|
|
1784
|
+
this.triggerSubject = subject;
|
|
1785
|
+
return this;
|
|
1786
|
+
}
|
|
1787
|
+
triggerStream(streamName) {
|
|
1788
|
+
this.triggerStreamName = streamName;
|
|
1789
|
+
return this;
|
|
1790
|
+
}
|
|
1791
|
+
step(name, handler) {
|
|
1792
|
+
this.steps.push({ name, handler, compensation: void 0 });
|
|
1793
|
+
return this;
|
|
1794
|
+
}
|
|
1795
|
+
/** Compensation handler for the most recently added step. */
|
|
1796
|
+
compensate(_stepName, handler) {
|
|
1797
|
+
const last = this.steps[this.steps.length - 1];
|
|
1798
|
+
if (last) last.compensation = handler;
|
|
1799
|
+
return this;
|
|
1800
|
+
}
|
|
1801
|
+
ackWait(ms) {
|
|
1802
|
+
this.ackWaitMs = ms;
|
|
1803
|
+
return this;
|
|
1804
|
+
}
|
|
1805
|
+
inflight(n) {
|
|
1806
|
+
this.maxInflightVal = n;
|
|
1807
|
+
return this;
|
|
1808
|
+
}
|
|
1809
|
+
maxRetries(n) {
|
|
1810
|
+
this.maxRetriesVal = n;
|
|
1811
|
+
return this;
|
|
1812
|
+
}
|
|
1813
|
+
maxContextSize(bytes) {
|
|
1814
|
+
this.maxContextSizeVal = bytes;
|
|
1815
|
+
return this;
|
|
1816
|
+
}
|
|
1817
|
+
async start() {
|
|
1818
|
+
if (!this.triggerSubject) throw new Error("trigger subject required");
|
|
1819
|
+
if (this.steps.length === 0) throw new Error("at least one step required");
|
|
1820
|
+
const name = this.workflowName;
|
|
1821
|
+
const taskStream = `_wf.${name}.tasks`;
|
|
1822
|
+
const taskSubject = `_wf.${name}.>`;
|
|
1823
|
+
const dlqStream = `_wf.${name}.dlq`;
|
|
1824
|
+
const dlqSubject = `_wf.${name}.dlq.>`;
|
|
1825
|
+
await this.client.upsertStream(taskStream, { subjectFilter: taskSubject, idempotencyWindowMs: 3e5 });
|
|
1826
|
+
await this.client.upsertStream(dlqStream, { subjectFilter: dlqSubject });
|
|
1827
|
+
const cfg = {
|
|
1828
|
+
client: this.client,
|
|
1829
|
+
name,
|
|
1830
|
+
taskStreamName: taskStream,
|
|
1831
|
+
dlqStreamName: dlqStream,
|
|
1832
|
+
steps: this.steps,
|
|
1833
|
+
maxContextSize: this.maxContextSizeVal,
|
|
1834
|
+
maxRetries: this.maxRetriesVal
|
|
1835
|
+
};
|
|
1836
|
+
const sub = await this.subscribeWorker(cfg, taskStream, taskSubject);
|
|
1837
|
+
const triggerSub = await this.subscribeTrigger(taskStream, name);
|
|
1838
|
+
return new WorkflowHandle(name, taskStream, dlqStream, sub, triggerSub);
|
|
1839
|
+
}
|
|
1840
|
+
async subscribeWorker(cfg, taskStream, taskSubject) {
|
|
1841
|
+
const uid = nextWorkerUid++;
|
|
1842
|
+
return this.client.subscribe(taskStream, {
|
|
1843
|
+
name: `_wf_${cfg.name}_w${uid}`,
|
|
1844
|
+
group: `_wf_${cfg.name}_workers`,
|
|
1845
|
+
filter: taskSubject,
|
|
1846
|
+
ackPolicy: "explicit" /* Explicit */,
|
|
1847
|
+
ackWaitMs: this.ackWaitMs,
|
|
1848
|
+
maxAckPending: this.maxInflightVal
|
|
1849
|
+
}, (msg) => {
|
|
1850
|
+
void processMessage(cfg, msg);
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
async subscribeTrigger(taskStream, name) {
|
|
1854
|
+
if (!this.triggerSubject || !this.triggerStreamName) return void 0;
|
|
1855
|
+
const subject = this.triggerSubject;
|
|
1856
|
+
return this.client.subscribe(this.triggerStreamName, {
|
|
1857
|
+
name: `_wf_${name}_trigger`,
|
|
1858
|
+
filter: subject,
|
|
1859
|
+
ackPolicy: "explicit" /* Explicit */,
|
|
1860
|
+
ackWaitMs: this.ackWaitMs,
|
|
1861
|
+
maxAckPending: 1
|
|
1862
|
+
}, async (msg) => {
|
|
1863
|
+
const id = allocInstanceId();
|
|
1864
|
+
const taskBuf = encodeTask(id, 0, 0, msg.data());
|
|
1865
|
+
await this.client.publish(taskStream, `_wf.${name}.step.0`, taskBuf, { msgId: `wf:${id}:0:0` });
|
|
1866
|
+
msg.ack();
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
};
|
|
1870
|
+
|
|
1620
1871
|
// src/utils/zod.ts
|
|
1621
1872
|
import { Packr as Packr2, Unpackr as Unpackr2 } from "msgpackr";
|
|
1622
1873
|
var packr = new Packr2({ structuredClone: false, useRecords: false });
|
|
@@ -1637,6 +1888,7 @@ export {
|
|
|
1637
1888
|
AckPolicy,
|
|
1638
1889
|
ArbitroClient,
|
|
1639
1890
|
ArbitroError,
|
|
1891
|
+
COMPENSATION_BIT,
|
|
1640
1892
|
Codec,
|
|
1641
1893
|
Consumer,
|
|
1642
1894
|
CronBuilder,
|
|
@@ -1650,6 +1902,8 @@ export {
|
|
|
1650
1902
|
StringCodec,
|
|
1651
1903
|
Subscription,
|
|
1652
1904
|
Topic,
|
|
1905
|
+
WorkflowBuilder,
|
|
1906
|
+
WorkflowHandle,
|
|
1653
1907
|
decodeJson,
|
|
1654
1908
|
decodeString,
|
|
1655
1909
|
encodeJson,
|