@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.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";
|
|
@@ -267,6 +268,7 @@ __export(index_exports, {
|
|
|
267
268
|
AckPolicy: () => AckPolicy,
|
|
268
269
|
ArbitroClient: () => ArbitroClient,
|
|
269
270
|
ArbitroError: () => ArbitroError,
|
|
271
|
+
COMPENSATION_BIT: () => COMPENSATION_BIT,
|
|
270
272
|
Codec: () => Codec,
|
|
271
273
|
Consumer: () => Consumer,
|
|
272
274
|
CronBuilder: () => CronBuilder,
|
|
@@ -280,6 +282,8 @@ __export(index_exports, {
|
|
|
280
282
|
StringCodec: () => StringCodec,
|
|
281
283
|
Subscription: () => Subscription,
|
|
282
284
|
Topic: () => Topic,
|
|
285
|
+
WorkflowBuilder: () => WorkflowBuilder,
|
|
286
|
+
WorkflowHandle: () => WorkflowHandle,
|
|
283
287
|
decodeJson: () => decodeJson,
|
|
284
288
|
decodeString: () => decodeString,
|
|
285
289
|
encodeJson: () => encodeJson,
|
|
@@ -559,6 +563,7 @@ var packDrainSubject = (seq, name, subject) => packCold2(1030 /* DrainSubject */
|
|
|
559
563
|
name: bytesArr(name),
|
|
560
564
|
subject: bytesArr(subject)
|
|
561
565
|
});
|
|
566
|
+
var packDeleteMessage = (seq, name, msgSeq) => packCold2(1031 /* DeleteMessage */, seq, { name: bytesArr(name), seq: Number(msgSeq) });
|
|
562
567
|
var packListStreams = (seq, offset = 0, limit = 1e3) => packCold2(1028 /* ListStreams */, seq, { offset: offset >>> 0, limit: limit >>> 0 });
|
|
563
568
|
function packCreateConsumer(seq, opts) {
|
|
564
569
|
const limits = (opts.subjectLimits ?? []).map((l) => ({
|
|
@@ -1222,6 +1227,10 @@ var Consumer = class {
|
|
|
1222
1227
|
}
|
|
1223
1228
|
return this.client.getPending(this.streamName, this.name);
|
|
1224
1229
|
}
|
|
1230
|
+
/** Tombstone a single message by seq. Returns true if found. */
|
|
1231
|
+
deleteMessage(seq) {
|
|
1232
|
+
return this.client.deleteMessage(this.streamName, seq);
|
|
1233
|
+
}
|
|
1225
1234
|
subscribe(codecOrCb, cbOrOpts, opts) {
|
|
1226
1235
|
if (!codecOrCb || typeof codecOrCb === "function") {
|
|
1227
1236
|
return this.client.subscribe(
|
|
@@ -1311,6 +1320,10 @@ var Stream = class {
|
|
|
1311
1320
|
request(subject, data, timeoutMs) {
|
|
1312
1321
|
return this.client.request(this.name, subject, data, timeoutMs);
|
|
1313
1322
|
}
|
|
1323
|
+
/** Tombstone a single message by seq. Returns true if found. */
|
|
1324
|
+
deleteMessage(seq) {
|
|
1325
|
+
return this.client.deleteMessage(this.name, seq);
|
|
1326
|
+
}
|
|
1314
1327
|
// ── Context factories ───────────────────────────────────────────────────
|
|
1315
1328
|
consumer(overrides) {
|
|
1316
1329
|
const config = {
|
|
@@ -1698,6 +1711,19 @@ var ArbitroClient = class {
|
|
|
1698
1711
|
);
|
|
1699
1712
|
return Number(refSeq);
|
|
1700
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
|
+
}
|
|
1701
1727
|
// ── Consumer management ───────────────────────────────────────────────────
|
|
1702
1728
|
async createConsumer(streamName, config) {
|
|
1703
1729
|
const consumerId = await this.createConsumerRaw(streamName, config);
|
|
@@ -1706,7 +1732,7 @@ var ArbitroClient = class {
|
|
|
1706
1732
|
async createConsumerRaw(streamName, config) {
|
|
1707
1733
|
const sid = await this.resolveStreamId(streamName);
|
|
1708
1734
|
const name = Buffer.from(config.name ?? streamName);
|
|
1709
|
-
const group = Buffer.from(config.name ?? streamName);
|
|
1735
|
+
const group = Buffer.from(config.group ?? config.name ?? streamName);
|
|
1710
1736
|
const filter = Buffer.from(config.filter ?? "");
|
|
1711
1737
|
const ackPolicyByte = config.ackPolicy === "none" /* None */ ? 0 : 1;
|
|
1712
1738
|
const opts = {
|
|
@@ -1916,6 +1942,257 @@ function streamId(name) {
|
|
|
1916
1942
|
return h >>> 0;
|
|
1917
1943
|
}
|
|
1918
1944
|
|
|
1945
|
+
// src/workflow/task.ts
|
|
1946
|
+
var TASK_HEADER = 7;
|
|
1947
|
+
var COMPENSATION_BIT = 32768;
|
|
1948
|
+
function encodeTask(instanceId, stepIndex, attempt, context) {
|
|
1949
|
+
const buf = Buffer.allocUnsafe(TASK_HEADER + context.length);
|
|
1950
|
+
buf.writeUInt32LE(instanceId, 0);
|
|
1951
|
+
buf.writeUInt16LE(stepIndex, 4);
|
|
1952
|
+
buf[6] = attempt;
|
|
1953
|
+
context.copy(buf, TASK_HEADER);
|
|
1954
|
+
return buf;
|
|
1955
|
+
}
|
|
1956
|
+
function decodeTask(payload) {
|
|
1957
|
+
if (payload.length < TASK_HEADER) return void 0;
|
|
1958
|
+
return {
|
|
1959
|
+
instanceId: payload.readUInt32LE(0),
|
|
1960
|
+
stepIndex: payload.readUInt16LE(4),
|
|
1961
|
+
attempt: payload[6],
|
|
1962
|
+
context: payload.subarray(TASK_HEADER)
|
|
1963
|
+
};
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
// src/workflow/handle.ts
|
|
1967
|
+
var nextInstanceId = 1;
|
|
1968
|
+
function allocInstanceId() {
|
|
1969
|
+
return nextInstanceId++;
|
|
1970
|
+
}
|
|
1971
|
+
var WorkflowHandle = class {
|
|
1972
|
+
constructor(workflowName, taskStreamName, dlqStreamName, sub, triggerSub) {
|
|
1973
|
+
this.workflowName = workflowName;
|
|
1974
|
+
this.taskStreamName = taskStreamName;
|
|
1975
|
+
this.dlqStreamName = dlqStreamName;
|
|
1976
|
+
this.sub = sub;
|
|
1977
|
+
this.triggerSub = triggerSub;
|
|
1978
|
+
}
|
|
1979
|
+
get name() {
|
|
1980
|
+
return this.workflowName;
|
|
1981
|
+
}
|
|
1982
|
+
get taskStream() {
|
|
1983
|
+
return this.taskStreamName;
|
|
1984
|
+
}
|
|
1985
|
+
get dlqStream() {
|
|
1986
|
+
return this.dlqStreamName;
|
|
1987
|
+
}
|
|
1988
|
+
async trigger(client, context) {
|
|
1989
|
+
const instanceId = allocInstanceId();
|
|
1990
|
+
const msgId = `wf:${instanceId}:0:0`;
|
|
1991
|
+
const subject = `_wf.${this.workflowName}.step.0`;
|
|
1992
|
+
const task = encodeTask(instanceId, 0, 0, context);
|
|
1993
|
+
await client.publish(this.taskStreamName, subject, task, { msgId });
|
|
1994
|
+
return instanceId;
|
|
1995
|
+
}
|
|
1996
|
+
};
|
|
1997
|
+
|
|
1998
|
+
// src/workflow/processor.ts
|
|
1999
|
+
async function processMessage(cfg, msg) {
|
|
2000
|
+
const task = decodeTask(msg.data());
|
|
2001
|
+
if (!task) {
|
|
2002
|
+
msg.ack();
|
|
2003
|
+
return;
|
|
2004
|
+
}
|
|
2005
|
+
if (task.context.length > cfg.maxContextSize) {
|
|
2006
|
+
msg.ack();
|
|
2007
|
+
return;
|
|
2008
|
+
}
|
|
2009
|
+
const isCompensation = (task.stepIndex & COMPENSATION_BIT) !== 0;
|
|
2010
|
+
if (isCompensation) {
|
|
2011
|
+
await runCompensation(cfg, msg, task);
|
|
2012
|
+
return;
|
|
2013
|
+
}
|
|
2014
|
+
if (task.stepIndex >= cfg.steps.length) {
|
|
2015
|
+
msg.ack();
|
|
2016
|
+
return;
|
|
2017
|
+
}
|
|
2018
|
+
await runStep(cfg, msg, task);
|
|
2019
|
+
}
|
|
2020
|
+
async function runCompensation(cfg, msg, task) {
|
|
2021
|
+
const idx = task.stepIndex & ~COMPENSATION_BIT;
|
|
2022
|
+
const comp = cfg.steps[idx]?.compensation;
|
|
2023
|
+
if (comp) {
|
|
2024
|
+
try {
|
|
2025
|
+
await comp({ name: cfg.name, instanceId: task.instanceId, stepIndex: idx, attempt: task.attempt, context: task.context });
|
|
2026
|
+
} catch {
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
msg.ack();
|
|
2030
|
+
}
|
|
2031
|
+
async function runStep(cfg, msg, task) {
|
|
2032
|
+
const handler = cfg.steps[task.stepIndex].handler;
|
|
2033
|
+
try {
|
|
2034
|
+
const result = await handler({
|
|
2035
|
+
name: cfg.name,
|
|
2036
|
+
instanceId: task.instanceId,
|
|
2037
|
+
stepIndex: task.stepIndex,
|
|
2038
|
+
attempt: task.attempt,
|
|
2039
|
+
context: task.context
|
|
2040
|
+
});
|
|
2041
|
+
if (result.context.length > cfg.maxContextSize) {
|
|
2042
|
+
msg.nack();
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
await advance(cfg, msg, task, result);
|
|
2046
|
+
} catch (err) {
|
|
2047
|
+
await onFailure(cfg, msg, task, err);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
async function advance(cfg, msg, task, result) {
|
|
2051
|
+
const nextStep = task.stepIndex + 1;
|
|
2052
|
+
if (nextStep < cfg.steps.length) {
|
|
2053
|
+
const msgId = `wf:${task.instanceId}:${nextStep}:0`;
|
|
2054
|
+
const subject = `_wf.${cfg.name}.step.${nextStep}`;
|
|
2055
|
+
const buf = encodeTask(task.instanceId, nextStep, 0, result.context);
|
|
2056
|
+
await cfg.client.publish(cfg.taskStreamName, subject, buf, { msgId });
|
|
2057
|
+
}
|
|
2058
|
+
msg.ack();
|
|
2059
|
+
}
|
|
2060
|
+
async function onFailure(cfg, msg, task, err) {
|
|
2061
|
+
if (task.attempt >= cfg.maxRetries) {
|
|
2062
|
+
await publishDlq(cfg, task, err);
|
|
2063
|
+
await publishCompensations(cfg, task);
|
|
2064
|
+
msg.ack();
|
|
2065
|
+
} else {
|
|
2066
|
+
msg.nack();
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
async function publishDlq(cfg, task, err) {
|
|
2070
|
+
const dlqSubject = `_wf.${cfg.name}.dlq.${task.stepIndex}`;
|
|
2071
|
+
const errBytes = Buffer.from(String(err));
|
|
2072
|
+
const buf = Buffer.allocUnsafe(7 + 4 + errBytes.length + task.context.length);
|
|
2073
|
+
buf.writeUInt32LE(task.instanceId, 0);
|
|
2074
|
+
buf.writeUInt16LE(task.stepIndex, 4);
|
|
2075
|
+
buf[6] = task.attempt;
|
|
2076
|
+
buf.writeUInt32LE(errBytes.length, 7);
|
|
2077
|
+
errBytes.copy(buf, 11);
|
|
2078
|
+
task.context.copy(buf, 11 + errBytes.length);
|
|
2079
|
+
const msgId = `wf:${task.instanceId}:dlq:${task.stepIndex}`;
|
|
2080
|
+
await cfg.client.publish(cfg.dlqStreamName, dlqSubject, buf, { msgId }).catch(() => {
|
|
2081
|
+
});
|
|
2082
|
+
}
|
|
2083
|
+
async function publishCompensations(cfg, task) {
|
|
2084
|
+
for (let i = task.stepIndex - 1; i >= 0; i--) {
|
|
2085
|
+
const compStep = COMPENSATION_BIT | i;
|
|
2086
|
+
const subject = `_wf.${cfg.name}.compensate.${i}`;
|
|
2087
|
+
const buf = encodeTask(task.instanceId, compStep, 0, task.context);
|
|
2088
|
+
const msgId = `wf:${task.instanceId}:comp:${i}`;
|
|
2089
|
+
await cfg.client.publish(cfg.taskStreamName, subject, buf, { msgId }).catch(() => {
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
// src/workflow/workflow.ts
|
|
2095
|
+
var nextWorkerUid = 1;
|
|
2096
|
+
var WorkflowBuilder = class {
|
|
2097
|
+
constructor(client, workflowName) {
|
|
2098
|
+
this.client = client;
|
|
2099
|
+
this.workflowName = workflowName;
|
|
2100
|
+
}
|
|
2101
|
+
triggerSubject;
|
|
2102
|
+
triggerStreamName;
|
|
2103
|
+
steps = [];
|
|
2104
|
+
ackWaitMs = 3e4;
|
|
2105
|
+
maxInflightVal = 10;
|
|
2106
|
+
maxRetriesVal = 3;
|
|
2107
|
+
maxContextSizeVal = 256 * 1024;
|
|
2108
|
+
trigger(subject) {
|
|
2109
|
+
this.triggerSubject = subject;
|
|
2110
|
+
return this;
|
|
2111
|
+
}
|
|
2112
|
+
triggerStream(streamName) {
|
|
2113
|
+
this.triggerStreamName = streamName;
|
|
2114
|
+
return this;
|
|
2115
|
+
}
|
|
2116
|
+
step(name, handler) {
|
|
2117
|
+
this.steps.push({ name, handler, compensation: void 0 });
|
|
2118
|
+
return this;
|
|
2119
|
+
}
|
|
2120
|
+
/** Compensation handler for the most recently added step. */
|
|
2121
|
+
compensate(_stepName, handler) {
|
|
2122
|
+
const last = this.steps[this.steps.length - 1];
|
|
2123
|
+
if (last) last.compensation = handler;
|
|
2124
|
+
return this;
|
|
2125
|
+
}
|
|
2126
|
+
ackWait(ms) {
|
|
2127
|
+
this.ackWaitMs = ms;
|
|
2128
|
+
return this;
|
|
2129
|
+
}
|
|
2130
|
+
inflight(n) {
|
|
2131
|
+
this.maxInflightVal = n;
|
|
2132
|
+
return this;
|
|
2133
|
+
}
|
|
2134
|
+
maxRetries(n) {
|
|
2135
|
+
this.maxRetriesVal = n;
|
|
2136
|
+
return this;
|
|
2137
|
+
}
|
|
2138
|
+
maxContextSize(bytes) {
|
|
2139
|
+
this.maxContextSizeVal = bytes;
|
|
2140
|
+
return this;
|
|
2141
|
+
}
|
|
2142
|
+
async start() {
|
|
2143
|
+
if (!this.triggerSubject) throw new Error("trigger subject required");
|
|
2144
|
+
if (this.steps.length === 0) throw new Error("at least one step required");
|
|
2145
|
+
const name = this.workflowName;
|
|
2146
|
+
const taskStream = `_wf.${name}.tasks`;
|
|
2147
|
+
const taskSubject = `_wf.${name}.>`;
|
|
2148
|
+
const dlqStream = `_wf.${name}.dlq`;
|
|
2149
|
+
const dlqSubject = `_wf.${name}.dlq.>`;
|
|
2150
|
+
await this.client.upsertStream(taskStream, { subjectFilter: taskSubject, idempotencyWindowMs: 3e5 });
|
|
2151
|
+
await this.client.upsertStream(dlqStream, { subjectFilter: dlqSubject });
|
|
2152
|
+
const cfg = {
|
|
2153
|
+
client: this.client,
|
|
2154
|
+
name,
|
|
2155
|
+
taskStreamName: taskStream,
|
|
2156
|
+
dlqStreamName: dlqStream,
|
|
2157
|
+
steps: this.steps,
|
|
2158
|
+
maxContextSize: this.maxContextSizeVal,
|
|
2159
|
+
maxRetries: this.maxRetriesVal
|
|
2160
|
+
};
|
|
2161
|
+
const sub = await this.subscribeWorker(cfg, taskStream, taskSubject);
|
|
2162
|
+
const triggerSub = await this.subscribeTrigger(taskStream, name);
|
|
2163
|
+
return new WorkflowHandle(name, taskStream, dlqStream, sub, triggerSub);
|
|
2164
|
+
}
|
|
2165
|
+
async subscribeWorker(cfg, taskStream, taskSubject) {
|
|
2166
|
+
const uid = nextWorkerUid++;
|
|
2167
|
+
return this.client.subscribe(taskStream, {
|
|
2168
|
+
name: `_wf_${cfg.name}_w${uid}`,
|
|
2169
|
+
group: `_wf_${cfg.name}_workers`,
|
|
2170
|
+
filter: taskSubject,
|
|
2171
|
+
ackPolicy: "explicit" /* Explicit */,
|
|
2172
|
+
ackWaitMs: this.ackWaitMs,
|
|
2173
|
+
maxAckPending: this.maxInflightVal
|
|
2174
|
+
}, (msg) => {
|
|
2175
|
+
void processMessage(cfg, msg);
|
|
2176
|
+
});
|
|
2177
|
+
}
|
|
2178
|
+
async subscribeTrigger(taskStream, name) {
|
|
2179
|
+
if (!this.triggerSubject || !this.triggerStreamName) return void 0;
|
|
2180
|
+
const subject = this.triggerSubject;
|
|
2181
|
+
return this.client.subscribe(this.triggerStreamName, {
|
|
2182
|
+
name: `_wf_${name}_trigger`,
|
|
2183
|
+
filter: subject,
|
|
2184
|
+
ackPolicy: "explicit" /* Explicit */,
|
|
2185
|
+
ackWaitMs: this.ackWaitMs,
|
|
2186
|
+
maxAckPending: 1
|
|
2187
|
+
}, async (msg) => {
|
|
2188
|
+
const id = allocInstanceId();
|
|
2189
|
+
const taskBuf = encodeTask(id, 0, 0, msg.data());
|
|
2190
|
+
await this.client.publish(taskStream, `_wf.${name}.step.0`, taskBuf, { msgId: `wf:${id}:0:0` });
|
|
2191
|
+
msg.ack();
|
|
2192
|
+
});
|
|
2193
|
+
}
|
|
2194
|
+
};
|
|
2195
|
+
|
|
1919
2196
|
// src/utils/zod.ts
|
|
1920
2197
|
var import_msgpackr2 = require("msgpackr");
|
|
1921
2198
|
var packr = new import_msgpackr2.Packr({ structuredClone: false, useRecords: false });
|
|
@@ -1937,6 +2214,7 @@ function zodCodec(zodSchema) {
|
|
|
1937
2214
|
AckPolicy,
|
|
1938
2215
|
ArbitroClient,
|
|
1939
2216
|
ArbitroError,
|
|
2217
|
+
COMPENSATION_BIT,
|
|
1940
2218
|
Codec,
|
|
1941
2219
|
Consumer,
|
|
1942
2220
|
CronBuilder,
|
|
@@ -1950,6 +2228,8 @@ function zodCodec(zodSchema) {
|
|
|
1950
2228
|
StringCodec,
|
|
1951
2229
|
Subscription,
|
|
1952
2230
|
Topic,
|
|
2231
|
+
WorkflowBuilder,
|
|
2232
|
+
WorkflowHandle,
|
|
1953
2233
|
decodeJson,
|
|
1954
2234
|
decodeString,
|
|
1955
2235
|
encodeJson,
|