@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 +77 -0
- package/dist/{chunk-6BCX2E2R.mjs → chunk-3EHQPPLU.mjs} +2 -1
- package/dist/chunk-3EHQPPLU.mjs.map +1 -0
- package/dist/{chunk-SKCXQO7R.mjs → chunk-BSPQZJHF.mjs} +2 -2
- package/dist/{constants-KF57DJ2L.mjs → constants-LWTWKOBZ.mjs} +2 -2
- package/dist/index.d.mts +73 -1
- package/dist/index.d.ts +73 -1
- package/dist/index.js +281 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +281 -5
- package/dist/index.mjs.map +1 -1
- package/dist/{publish-UA3YZGMK.mjs → publish-NKAK5BOA.mjs} +3 -3
- package/package.json +1 -1
- package/dist/chunk-6BCX2E2R.mjs.map +0 -1
- /package/dist/{chunk-SKCXQO7R.mjs.map → chunk-BSPQZJHF.mjs.map} +0 -0
- /package/dist/{constants-KF57DJ2L.mjs.map → constants-LWTWKOBZ.mjs.map} +0 -0
- /package/dist/{publish-UA3YZGMK.mjs.map → publish-NKAK5BOA.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-BSPQZJHF.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-3EHQPPLU.mjs";
|
|
15
15
|
|
|
16
16
|
// src/types/config.ts
|
|
17
17
|
var DeliverPolicy = /* @__PURE__ */ ((DeliverPolicy2) => {
|
|
@@ -270,6 +270,7 @@ var packDrainSubject = (seq, name, subject) => packCold2(1030 /* DrainSubject */
|
|
|
270
270
|
name: bytesArr(name),
|
|
271
271
|
subject: bytesArr(subject)
|
|
272
272
|
});
|
|
273
|
+
var packDeleteMessage = (seq, name, msgSeq) => packCold2(1031 /* DeleteMessage */, seq, { name: bytesArr(name), seq: Number(msgSeq) });
|
|
273
274
|
var packListStreams = (seq, offset = 0, limit = 1e3) => packCold2(1028 /* ListStreams */, seq, { offset: offset >>> 0, limit: limit >>> 0 });
|
|
274
275
|
function packCreateConsumer(seq, opts) {
|
|
275
276
|
const limits = (opts.subjectLimits ?? []).map((l) => ({
|
|
@@ -927,6 +928,10 @@ var Consumer = class {
|
|
|
927
928
|
}
|
|
928
929
|
return this.client.getPending(this.streamName, this.name);
|
|
929
930
|
}
|
|
931
|
+
/** Tombstone a single message by seq. Returns true if found. */
|
|
932
|
+
deleteMessage(seq) {
|
|
933
|
+
return this.client.deleteMessage(this.streamName, seq);
|
|
934
|
+
}
|
|
930
935
|
subscribe(codecOrCb, cbOrOpts, opts) {
|
|
931
936
|
if (!codecOrCb || typeof codecOrCb === "function") {
|
|
932
937
|
return this.client.subscribe(
|
|
@@ -1016,6 +1021,10 @@ var Stream = class {
|
|
|
1016
1021
|
request(subject, data, timeoutMs) {
|
|
1017
1022
|
return this.client.request(this.name, subject, data, timeoutMs);
|
|
1018
1023
|
}
|
|
1024
|
+
/** Tombstone a single message by seq. Returns true if found. */
|
|
1025
|
+
deleteMessage(seq) {
|
|
1026
|
+
return this.client.deleteMessage(this.name, seq);
|
|
1027
|
+
}
|
|
1019
1028
|
// ── Context factories ───────────────────────────────────────────────────
|
|
1020
1029
|
consumer(overrides) {
|
|
1021
1030
|
const config = {
|
|
@@ -1291,8 +1300,8 @@ var ArbitroClient = class {
|
|
|
1291
1300
|
*/
|
|
1292
1301
|
async publishDelayed(streamName, subject, data, delayMs) {
|
|
1293
1302
|
const sid = await this.resolveStreamId(streamName);
|
|
1294
|
-
const { packPublishDelayed: packPublishDelayed2 } = await import("./publish-
|
|
1295
|
-
const { Flag: Flag3 } = await import("./constants-
|
|
1303
|
+
const { packPublishDelayed: packPublishDelayed2 } = await import("./publish-NKAK5BOA.mjs");
|
|
1304
|
+
const { Flag: Flag3 } = await import("./constants-LWTWKOBZ.mjs");
|
|
1296
1305
|
const subj = Buffer.from(this.prefixed(subject));
|
|
1297
1306
|
await this.conn.sendExpectReply(
|
|
1298
1307
|
packPublishDelayed2(this.conn.nextSeq(), sid, subj, data, BigInt(delayMs), Flag3.AckReq)
|
|
@@ -1399,6 +1408,19 @@ var ArbitroClient = class {
|
|
|
1399
1408
|
);
|
|
1400
1409
|
return Number(refSeq);
|
|
1401
1410
|
}
|
|
1411
|
+
/**
|
|
1412
|
+
* Tombstone a single message by sequence number.
|
|
1413
|
+
*
|
|
1414
|
+
* The broker marks the entry as deleted — it will never be delivered
|
|
1415
|
+
* to any consumer. Returns `true` if the message was found and
|
|
1416
|
+
* tombstoned, `false` if not found or already tombstoned.
|
|
1417
|
+
*/
|
|
1418
|
+
async deleteMessage(streamName, seq) {
|
|
1419
|
+
const refSeq = await this.conn.sendExpectReply(
|
|
1420
|
+
packDeleteMessage(this.conn.nextSeq(), Buffer.from(streamName), seq)
|
|
1421
|
+
);
|
|
1422
|
+
return refSeq > 0n;
|
|
1423
|
+
}
|
|
1402
1424
|
// ── Consumer management ───────────────────────────────────────────────────
|
|
1403
1425
|
async createConsumer(streamName, config) {
|
|
1404
1426
|
const consumerId = await this.createConsumerRaw(streamName, config);
|
|
@@ -1407,7 +1429,7 @@ var ArbitroClient = class {
|
|
|
1407
1429
|
async createConsumerRaw(streamName, config) {
|
|
1408
1430
|
const sid = await this.resolveStreamId(streamName);
|
|
1409
1431
|
const name = Buffer.from(config.name ?? streamName);
|
|
1410
|
-
const group = Buffer.from(config.name ?? streamName);
|
|
1432
|
+
const group = Buffer.from(config.group ?? config.name ?? streamName);
|
|
1411
1433
|
const filter = Buffer.from(config.filter ?? "");
|
|
1412
1434
|
const ackPolicyByte = config.ackPolicy === "none" /* None */ ? 0 : 1;
|
|
1413
1435
|
const opts = {
|
|
@@ -1617,6 +1639,257 @@ function streamId(name) {
|
|
|
1617
1639
|
return h >>> 0;
|
|
1618
1640
|
}
|
|
1619
1641
|
|
|
1642
|
+
// src/workflow/task.ts
|
|
1643
|
+
var TASK_HEADER = 7;
|
|
1644
|
+
var COMPENSATION_BIT = 32768;
|
|
1645
|
+
function encodeTask(instanceId, stepIndex, attempt, context) {
|
|
1646
|
+
const buf = Buffer.allocUnsafe(TASK_HEADER + context.length);
|
|
1647
|
+
buf.writeUInt32LE(instanceId, 0);
|
|
1648
|
+
buf.writeUInt16LE(stepIndex, 4);
|
|
1649
|
+
buf[6] = attempt;
|
|
1650
|
+
context.copy(buf, TASK_HEADER);
|
|
1651
|
+
return buf;
|
|
1652
|
+
}
|
|
1653
|
+
function decodeTask(payload) {
|
|
1654
|
+
if (payload.length < TASK_HEADER) return void 0;
|
|
1655
|
+
return {
|
|
1656
|
+
instanceId: payload.readUInt32LE(0),
|
|
1657
|
+
stepIndex: payload.readUInt16LE(4),
|
|
1658
|
+
attempt: payload[6],
|
|
1659
|
+
context: payload.subarray(TASK_HEADER)
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
// src/workflow/handle.ts
|
|
1664
|
+
var nextInstanceId = 1;
|
|
1665
|
+
function allocInstanceId() {
|
|
1666
|
+
return nextInstanceId++;
|
|
1667
|
+
}
|
|
1668
|
+
var WorkflowHandle = class {
|
|
1669
|
+
constructor(workflowName, taskStreamName, dlqStreamName, sub, triggerSub) {
|
|
1670
|
+
this.workflowName = workflowName;
|
|
1671
|
+
this.taskStreamName = taskStreamName;
|
|
1672
|
+
this.dlqStreamName = dlqStreamName;
|
|
1673
|
+
this.sub = sub;
|
|
1674
|
+
this.triggerSub = triggerSub;
|
|
1675
|
+
}
|
|
1676
|
+
get name() {
|
|
1677
|
+
return this.workflowName;
|
|
1678
|
+
}
|
|
1679
|
+
get taskStream() {
|
|
1680
|
+
return this.taskStreamName;
|
|
1681
|
+
}
|
|
1682
|
+
get dlqStream() {
|
|
1683
|
+
return this.dlqStreamName;
|
|
1684
|
+
}
|
|
1685
|
+
async trigger(client, context) {
|
|
1686
|
+
const instanceId = allocInstanceId();
|
|
1687
|
+
const msgId = `wf:${instanceId}:0:0`;
|
|
1688
|
+
const subject = `_wf.${this.workflowName}.step.0`;
|
|
1689
|
+
const task = encodeTask(instanceId, 0, 0, context);
|
|
1690
|
+
await client.publish(this.taskStreamName, subject, task, { msgId });
|
|
1691
|
+
return instanceId;
|
|
1692
|
+
}
|
|
1693
|
+
};
|
|
1694
|
+
|
|
1695
|
+
// src/workflow/processor.ts
|
|
1696
|
+
async function processMessage(cfg, msg) {
|
|
1697
|
+
const task = decodeTask(msg.data());
|
|
1698
|
+
if (!task) {
|
|
1699
|
+
msg.ack();
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
if (task.context.length > cfg.maxContextSize) {
|
|
1703
|
+
msg.ack();
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
const isCompensation = (task.stepIndex & COMPENSATION_BIT) !== 0;
|
|
1707
|
+
if (isCompensation) {
|
|
1708
|
+
await runCompensation(cfg, msg, task);
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
if (task.stepIndex >= cfg.steps.length) {
|
|
1712
|
+
msg.ack();
|
|
1713
|
+
return;
|
|
1714
|
+
}
|
|
1715
|
+
await runStep(cfg, msg, task);
|
|
1716
|
+
}
|
|
1717
|
+
async function runCompensation(cfg, msg, task) {
|
|
1718
|
+
const idx = task.stepIndex & ~COMPENSATION_BIT;
|
|
1719
|
+
const comp = cfg.steps[idx]?.compensation;
|
|
1720
|
+
if (comp) {
|
|
1721
|
+
try {
|
|
1722
|
+
await comp({ name: cfg.name, instanceId: task.instanceId, stepIndex: idx, attempt: task.attempt, context: task.context });
|
|
1723
|
+
} catch {
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
msg.ack();
|
|
1727
|
+
}
|
|
1728
|
+
async function runStep(cfg, msg, task) {
|
|
1729
|
+
const handler = cfg.steps[task.stepIndex].handler;
|
|
1730
|
+
try {
|
|
1731
|
+
const result = await handler({
|
|
1732
|
+
name: cfg.name,
|
|
1733
|
+
instanceId: task.instanceId,
|
|
1734
|
+
stepIndex: task.stepIndex,
|
|
1735
|
+
attempt: task.attempt,
|
|
1736
|
+
context: task.context
|
|
1737
|
+
});
|
|
1738
|
+
if (result.context.length > cfg.maxContextSize) {
|
|
1739
|
+
msg.nack();
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
await advance(cfg, msg, task, result);
|
|
1743
|
+
} catch (err) {
|
|
1744
|
+
await onFailure(cfg, msg, task, err);
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
async function advance(cfg, msg, task, result) {
|
|
1748
|
+
const nextStep = task.stepIndex + 1;
|
|
1749
|
+
if (nextStep < cfg.steps.length) {
|
|
1750
|
+
const msgId = `wf:${task.instanceId}:${nextStep}:0`;
|
|
1751
|
+
const subject = `_wf.${cfg.name}.step.${nextStep}`;
|
|
1752
|
+
const buf = encodeTask(task.instanceId, nextStep, 0, result.context);
|
|
1753
|
+
await cfg.client.publish(cfg.taskStreamName, subject, buf, { msgId });
|
|
1754
|
+
}
|
|
1755
|
+
msg.ack();
|
|
1756
|
+
}
|
|
1757
|
+
async function onFailure(cfg, msg, task, err) {
|
|
1758
|
+
if (task.attempt >= cfg.maxRetries) {
|
|
1759
|
+
await publishDlq(cfg, task, err);
|
|
1760
|
+
await publishCompensations(cfg, task);
|
|
1761
|
+
msg.ack();
|
|
1762
|
+
} else {
|
|
1763
|
+
msg.nack();
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
async function publishDlq(cfg, task, err) {
|
|
1767
|
+
const dlqSubject = `_wf.${cfg.name}.dlq.${task.stepIndex}`;
|
|
1768
|
+
const errBytes = Buffer.from(String(err));
|
|
1769
|
+
const buf = Buffer.allocUnsafe(7 + 4 + errBytes.length + task.context.length);
|
|
1770
|
+
buf.writeUInt32LE(task.instanceId, 0);
|
|
1771
|
+
buf.writeUInt16LE(task.stepIndex, 4);
|
|
1772
|
+
buf[6] = task.attempt;
|
|
1773
|
+
buf.writeUInt32LE(errBytes.length, 7);
|
|
1774
|
+
errBytes.copy(buf, 11);
|
|
1775
|
+
task.context.copy(buf, 11 + errBytes.length);
|
|
1776
|
+
const msgId = `wf:${task.instanceId}:dlq:${task.stepIndex}`;
|
|
1777
|
+
await cfg.client.publish(cfg.dlqStreamName, dlqSubject, buf, { msgId }).catch(() => {
|
|
1778
|
+
});
|
|
1779
|
+
}
|
|
1780
|
+
async function publishCompensations(cfg, task) {
|
|
1781
|
+
for (let i = task.stepIndex - 1; i >= 0; i--) {
|
|
1782
|
+
const compStep = COMPENSATION_BIT | i;
|
|
1783
|
+
const subject = `_wf.${cfg.name}.compensate.${i}`;
|
|
1784
|
+
const buf = encodeTask(task.instanceId, compStep, 0, task.context);
|
|
1785
|
+
const msgId = `wf:${task.instanceId}:comp:${i}`;
|
|
1786
|
+
await cfg.client.publish(cfg.taskStreamName, subject, buf, { msgId }).catch(() => {
|
|
1787
|
+
});
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
// src/workflow/workflow.ts
|
|
1792
|
+
var nextWorkerUid = 1;
|
|
1793
|
+
var WorkflowBuilder = class {
|
|
1794
|
+
constructor(client, workflowName) {
|
|
1795
|
+
this.client = client;
|
|
1796
|
+
this.workflowName = workflowName;
|
|
1797
|
+
}
|
|
1798
|
+
triggerSubject;
|
|
1799
|
+
triggerStreamName;
|
|
1800
|
+
steps = [];
|
|
1801
|
+
ackWaitMs = 3e4;
|
|
1802
|
+
maxInflightVal = 10;
|
|
1803
|
+
maxRetriesVal = 3;
|
|
1804
|
+
maxContextSizeVal = 256 * 1024;
|
|
1805
|
+
trigger(subject) {
|
|
1806
|
+
this.triggerSubject = subject;
|
|
1807
|
+
return this;
|
|
1808
|
+
}
|
|
1809
|
+
triggerStream(streamName) {
|
|
1810
|
+
this.triggerStreamName = streamName;
|
|
1811
|
+
return this;
|
|
1812
|
+
}
|
|
1813
|
+
step(name, handler) {
|
|
1814
|
+
this.steps.push({ name, handler, compensation: void 0 });
|
|
1815
|
+
return this;
|
|
1816
|
+
}
|
|
1817
|
+
/** Compensation handler for the most recently added step. */
|
|
1818
|
+
compensate(_stepName, handler) {
|
|
1819
|
+
const last = this.steps[this.steps.length - 1];
|
|
1820
|
+
if (last) last.compensation = handler;
|
|
1821
|
+
return this;
|
|
1822
|
+
}
|
|
1823
|
+
ackWait(ms) {
|
|
1824
|
+
this.ackWaitMs = ms;
|
|
1825
|
+
return this;
|
|
1826
|
+
}
|
|
1827
|
+
inflight(n) {
|
|
1828
|
+
this.maxInflightVal = n;
|
|
1829
|
+
return this;
|
|
1830
|
+
}
|
|
1831
|
+
maxRetries(n) {
|
|
1832
|
+
this.maxRetriesVal = n;
|
|
1833
|
+
return this;
|
|
1834
|
+
}
|
|
1835
|
+
maxContextSize(bytes) {
|
|
1836
|
+
this.maxContextSizeVal = bytes;
|
|
1837
|
+
return this;
|
|
1838
|
+
}
|
|
1839
|
+
async start() {
|
|
1840
|
+
if (!this.triggerSubject) throw new Error("trigger subject required");
|
|
1841
|
+
if (this.steps.length === 0) throw new Error("at least one step required");
|
|
1842
|
+
const name = this.workflowName;
|
|
1843
|
+
const taskStream = `_wf.${name}.tasks`;
|
|
1844
|
+
const taskSubject = `_wf.${name}.>`;
|
|
1845
|
+
const dlqStream = `_wf.${name}.dlq`;
|
|
1846
|
+
const dlqSubject = `_wf.${name}.dlq.>`;
|
|
1847
|
+
await this.client.upsertStream(taskStream, { subjectFilter: taskSubject, idempotencyWindowMs: 3e5 });
|
|
1848
|
+
await this.client.upsertStream(dlqStream, { subjectFilter: dlqSubject });
|
|
1849
|
+
const cfg = {
|
|
1850
|
+
client: this.client,
|
|
1851
|
+
name,
|
|
1852
|
+
taskStreamName: taskStream,
|
|
1853
|
+
dlqStreamName: dlqStream,
|
|
1854
|
+
steps: this.steps,
|
|
1855
|
+
maxContextSize: this.maxContextSizeVal,
|
|
1856
|
+
maxRetries: this.maxRetriesVal
|
|
1857
|
+
};
|
|
1858
|
+
const sub = await this.subscribeWorker(cfg, taskStream, taskSubject);
|
|
1859
|
+
const triggerSub = await this.subscribeTrigger(taskStream, name);
|
|
1860
|
+
return new WorkflowHandle(name, taskStream, dlqStream, sub, triggerSub);
|
|
1861
|
+
}
|
|
1862
|
+
async subscribeWorker(cfg, taskStream, taskSubject) {
|
|
1863
|
+
const uid = nextWorkerUid++;
|
|
1864
|
+
return this.client.subscribe(taskStream, {
|
|
1865
|
+
name: `_wf_${cfg.name}_w${uid}`,
|
|
1866
|
+
group: `_wf_${cfg.name}_workers`,
|
|
1867
|
+
filter: taskSubject,
|
|
1868
|
+
ackPolicy: "explicit" /* Explicit */,
|
|
1869
|
+
ackWaitMs: this.ackWaitMs,
|
|
1870
|
+
maxAckPending: this.maxInflightVal
|
|
1871
|
+
}, (msg) => {
|
|
1872
|
+
void processMessage(cfg, msg);
|
|
1873
|
+
});
|
|
1874
|
+
}
|
|
1875
|
+
async subscribeTrigger(taskStream, name) {
|
|
1876
|
+
if (!this.triggerSubject || !this.triggerStreamName) return void 0;
|
|
1877
|
+
const subject = this.triggerSubject;
|
|
1878
|
+
return this.client.subscribe(this.triggerStreamName, {
|
|
1879
|
+
name: `_wf_${name}_trigger`,
|
|
1880
|
+
filter: subject,
|
|
1881
|
+
ackPolicy: "explicit" /* Explicit */,
|
|
1882
|
+
ackWaitMs: this.ackWaitMs,
|
|
1883
|
+
maxAckPending: 1
|
|
1884
|
+
}, async (msg) => {
|
|
1885
|
+
const id = allocInstanceId();
|
|
1886
|
+
const taskBuf = encodeTask(id, 0, 0, msg.data());
|
|
1887
|
+
await this.client.publish(taskStream, `_wf.${name}.step.0`, taskBuf, { msgId: `wf:${id}:0:0` });
|
|
1888
|
+
msg.ack();
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
};
|
|
1892
|
+
|
|
1620
1893
|
// src/utils/zod.ts
|
|
1621
1894
|
import { Packr as Packr2, Unpackr as Unpackr2 } from "msgpackr";
|
|
1622
1895
|
var packr = new Packr2({ structuredClone: false, useRecords: false });
|
|
@@ -1637,6 +1910,7 @@ export {
|
|
|
1637
1910
|
AckPolicy,
|
|
1638
1911
|
ArbitroClient,
|
|
1639
1912
|
ArbitroError,
|
|
1913
|
+
COMPENSATION_BIT,
|
|
1640
1914
|
Codec,
|
|
1641
1915
|
Consumer,
|
|
1642
1916
|
CronBuilder,
|
|
@@ -1650,6 +1924,8 @@ export {
|
|
|
1650
1924
|
StringCodec,
|
|
1651
1925
|
Subscription,
|
|
1652
1926
|
Topic,
|
|
1927
|
+
WorkflowBuilder,
|
|
1928
|
+
WorkflowHandle,
|
|
1653
1929
|
decodeJson,
|
|
1654
1930
|
decodeString,
|
|
1655
1931
|
encodeJson,
|