@aikirun/client 0.9.2 → 0.10.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/dist/index.d.ts +7 -1
- package/dist/index.js +146 -104
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -44,9 +44,15 @@ export { AdaptivePollingSubscriberStrategy, ApiClient, Client, ClientParams, Log
|
|
|
44
44
|
*/
|
|
45
45
|
declare function client<AppContext = null>(params: ClientParams<AppContext>): Promise<Client<AppContext>>;
|
|
46
46
|
|
|
47
|
+
type LogLevel = "TRACE" | "DEBUG" | "INFO" | "WARN" | "ERROR";
|
|
48
|
+
interface ConsoleLoggerOptions {
|
|
49
|
+
level?: LogLevel;
|
|
50
|
+
context?: Record<string, unknown>;
|
|
51
|
+
}
|
|
47
52
|
declare class ConsoleLogger implements Logger {
|
|
53
|
+
private readonly level;
|
|
48
54
|
private readonly context;
|
|
49
|
-
constructor(
|
|
55
|
+
constructor(options?: ConsoleLoggerOptions);
|
|
50
56
|
trace(message: string, metadata?: Record<string, unknown>): void;
|
|
51
57
|
debug(message: string, metadata?: Record<string, unknown>): void;
|
|
52
58
|
info(message: string, metadata?: Record<string, unknown>): void;
|
package/dist/index.js
CHANGED
|
@@ -5,50 +5,84 @@ import { RPCLink } from "@orpc/client/fetch";
|
|
|
5
5
|
import { Redis } from "ioredis";
|
|
6
6
|
|
|
7
7
|
// logger/console-logger.ts
|
|
8
|
+
var colors = {
|
|
9
|
+
reset: "\x1B[0m",
|
|
10
|
+
dim: "\x1B[2m",
|
|
11
|
+
bold: "\x1B[1m",
|
|
12
|
+
gray: "\x1B[90m",
|
|
13
|
+
blue: "\x1B[94m",
|
|
14
|
+
cyan: "\x1B[36m",
|
|
15
|
+
green: "\x1B[32m",
|
|
16
|
+
yellow: "\x1B[33m",
|
|
17
|
+
red: "\x1B[31m",
|
|
18
|
+
magenta: "\x1B[35m"
|
|
19
|
+
};
|
|
20
|
+
var logLevelConfig = {
|
|
21
|
+
TRACE: { level: 10, color: colors.gray },
|
|
22
|
+
DEBUG: { level: 20, color: colors.blue },
|
|
23
|
+
INFO: { level: 30, color: colors.green },
|
|
24
|
+
WARN: { level: 40, color: colors.yellow },
|
|
25
|
+
ERROR: { level: 50, color: colors.red }
|
|
26
|
+
};
|
|
8
27
|
var ConsoleLogger = class _ConsoleLogger {
|
|
9
|
-
|
|
10
|
-
|
|
28
|
+
level;
|
|
29
|
+
context;
|
|
30
|
+
constructor(options = {}) {
|
|
31
|
+
this.level = logLevelConfig[options.level ?? "INFO"].level;
|
|
32
|
+
this.context = options.context ?? {};
|
|
11
33
|
}
|
|
12
34
|
trace(message, metadata) {
|
|
13
|
-
|
|
35
|
+
if (this.level <= logLevelConfig.TRACE.level) {
|
|
36
|
+
console.debug(this.format("TRACE", message, metadata));
|
|
37
|
+
}
|
|
14
38
|
}
|
|
15
39
|
debug(message, metadata) {
|
|
16
|
-
|
|
40
|
+
if (this.level <= logLevelConfig.DEBUG.level) {
|
|
41
|
+
console.debug(this.format("DEBUG", message, metadata));
|
|
42
|
+
}
|
|
17
43
|
}
|
|
18
44
|
info(message, metadata) {
|
|
19
|
-
|
|
45
|
+
if (this.level <= logLevelConfig.INFO.level) {
|
|
46
|
+
console.info(this.format("INFO", message, metadata));
|
|
47
|
+
}
|
|
20
48
|
}
|
|
21
49
|
warn(message, metadata) {
|
|
22
|
-
|
|
50
|
+
if (this.level <= logLevelConfig.WARN.level) {
|
|
51
|
+
console.warn(this.format("WARN", message, metadata));
|
|
52
|
+
}
|
|
23
53
|
}
|
|
24
54
|
error(message, metadata) {
|
|
25
|
-
|
|
55
|
+
if (this.level <= logLevelConfig.ERROR.level) {
|
|
56
|
+
console.error(this.format("ERROR", message, metadata));
|
|
57
|
+
}
|
|
26
58
|
}
|
|
27
59
|
child(bindings) {
|
|
28
|
-
return new _ConsoleLogger({
|
|
60
|
+
return new _ConsoleLogger({
|
|
61
|
+
level: Object.entries(logLevelConfig).find(([, v]) => v.level === this.level)?.[0],
|
|
62
|
+
context: { ...this.context, ...bindings }
|
|
63
|
+
});
|
|
29
64
|
}
|
|
30
65
|
format(level, message, metadata) {
|
|
31
66
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
32
67
|
const mergedContext = { ...this.context, ...metadata };
|
|
33
|
-
const
|
|
34
|
-
|
|
68
|
+
const levelColor = logLevelConfig[level].color ?? colors.reset;
|
|
69
|
+
const timestampStr = `${colors.dim}${timestamp}${colors.reset}`;
|
|
70
|
+
const levelStr = `${levelColor}${colors.bold}${level.padEnd(5)}${colors.reset}`;
|
|
71
|
+
const messageStr = `${colors.cyan}${message}${colors.reset}`;
|
|
72
|
+
let output = `${timestampStr} ${levelStr} ${messageStr}`;
|
|
73
|
+
if (Object.keys(mergedContext).length > 0) {
|
|
74
|
+
const entries = Object.entries(mergedContext).map(([key, value]) => {
|
|
75
|
+
const valueStr = typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
76
|
+
return `${colors.magenta}${key}:${colors.reset} ${valueStr}`;
|
|
77
|
+
}).join("\n ");
|
|
78
|
+
output += `
|
|
79
|
+
${entries}`;
|
|
80
|
+
}
|
|
81
|
+
return output;
|
|
35
82
|
}
|
|
36
83
|
};
|
|
37
84
|
|
|
38
85
|
// ../../lib/array/utils.ts
|
|
39
|
-
function groupBy(items, unwrap) {
|
|
40
|
-
const result = /* @__PURE__ */ new Map();
|
|
41
|
-
for (const item of items) {
|
|
42
|
-
const [key, value] = unwrap(item);
|
|
43
|
-
const valuesWithSameKey = result.get(key);
|
|
44
|
-
if (valuesWithSameKey === void 0) {
|
|
45
|
-
result.set(key, [value]);
|
|
46
|
-
} else {
|
|
47
|
-
valuesWithSameKey.push(value);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return result;
|
|
51
|
-
}
|
|
52
86
|
function isNonEmptyArray(value) {
|
|
53
87
|
return value.length > 0;
|
|
54
88
|
}
|
|
@@ -127,13 +161,9 @@ function getRetryParams(attempts, strategy) {
|
|
|
127
161
|
|
|
128
162
|
// subscribers/redis-streams.ts
|
|
129
163
|
import { INTERNAL } from "@aikirun/types/symbols";
|
|
130
|
-
import {
|
|
131
|
-
var
|
|
132
|
-
|
|
133
|
-
workflowRunId: z.string().transform((id) => id)
|
|
134
|
-
});
|
|
135
|
-
var RedisMessageDataSchema = z.discriminatedUnion("type", [WorkflowRunReadyMessageDataSchema]);
|
|
136
|
-
var RedisMessageRawDataSchema = z.array(z.unknown()).transform((rawFields) => {
|
|
164
|
+
import { type } from "arktype";
|
|
165
|
+
var streamEntriesSchema = type(["string", type(["string", "unknown[]"]).array()]).array();
|
|
166
|
+
var rawStreamMessageFieldsToRecord = (rawFields) => {
|
|
137
167
|
const data = {};
|
|
138
168
|
for (let i = 0; i < rawFields.length; i += 2) {
|
|
139
169
|
if (i + 1 < rawFields.length) {
|
|
@@ -144,20 +174,12 @@ var RedisMessageRawDataSchema = z.array(z.unknown()).transform((rawFields) => {
|
|
|
144
174
|
}
|
|
145
175
|
}
|
|
146
176
|
return data;
|
|
177
|
+
};
|
|
178
|
+
var streamMessageDataSchema = type({
|
|
179
|
+
type: "'workflow_run_ready'",
|
|
180
|
+
workflowRunId: "string > 0"
|
|
147
181
|
});
|
|
148
|
-
var
|
|
149
|
-
z.string(),
|
|
150
|
-
// message-id
|
|
151
|
-
RedisMessageRawDataSchema
|
|
152
|
-
]);
|
|
153
|
-
var RedisStreamEntrySchema = z.tuple([
|
|
154
|
-
z.string(),
|
|
155
|
-
// stream
|
|
156
|
-
z.array(RedisStreamMessageSchema)
|
|
157
|
-
]);
|
|
158
|
-
var RedisStreamEntriesSchema = z.array(RedisStreamEntrySchema);
|
|
159
|
-
var RedisStreamPendingMessageSchema = z.tuple([z.string(), z.string(), z.number(), z.number()]);
|
|
160
|
-
var RedisStreamPendingMessagesSchema = z.array(RedisStreamPendingMessageSchema);
|
|
182
|
+
var streamPendingMessagesSchema = type(["string", "string", "number", "number"]).array();
|
|
161
183
|
function createRedisStreamsStrategy(client2, strategy, workflows, workerShards) {
|
|
162
184
|
const redis = client2[INTERNAL].redis.getConnection();
|
|
163
185
|
const logger = client2.logger.child({
|
|
@@ -169,7 +191,7 @@ function createRedisStreamsStrategy(client2, strategy, workflows, workerShards)
|
|
|
169
191
|
const maxRetryIntervalMs = strategy.maxRetryIntervalMs ?? 3e4;
|
|
170
192
|
const atCapacityIntervalMs = strategy.atCapacityIntervalMs ?? 50;
|
|
171
193
|
const blockTimeMs = strategy.blockTimeMs ?? 1e3;
|
|
172
|
-
const claimMinIdleTimeMs = strategy.claimMinIdleTimeMs ??
|
|
194
|
+
const claimMinIdleTimeMs = strategy.claimMinIdleTimeMs ?? 9e4;
|
|
173
195
|
const getNextDelay = (params) => {
|
|
174
196
|
switch (params.type) {
|
|
175
197
|
case "polled":
|
|
@@ -197,31 +219,40 @@ function createRedisStreamsStrategy(client2, strategy, workflows, workerShards)
|
|
|
197
219
|
try {
|
|
198
220
|
await redis.xclaim(meta.stream, meta.consumerGroup, workerId, 0, meta.messageId, "JUSTID");
|
|
199
221
|
logger.debug("Heartbeat sent", {
|
|
222
|
+
"aiki.workerId": workerId,
|
|
200
223
|
"aiki.workflowRunId": workflowRunId,
|
|
201
224
|
"aiki.messageId": meta.messageId
|
|
202
225
|
});
|
|
203
226
|
} catch (error) {
|
|
204
227
|
logger.warn("Heartbeat failed", {
|
|
228
|
+
"aiki.workerId": workerId,
|
|
205
229
|
"aiki.workflowRunId": workflowRunId,
|
|
206
230
|
"aiki.error": error instanceof Error ? error.message : String(error)
|
|
207
231
|
});
|
|
208
232
|
}
|
|
209
233
|
};
|
|
210
|
-
const acknowledge = async (workflowRunId, meta) => {
|
|
234
|
+
const acknowledge = async (workerId, workflowRunId, meta) => {
|
|
211
235
|
try {
|
|
212
236
|
const result = await redis.xack(meta.stream, meta.consumerGroup, meta.messageId);
|
|
213
237
|
if (result === 0) {
|
|
214
238
|
logger.warn("Message already acknowledged", {
|
|
239
|
+
"aiki.workerId": workerId,
|
|
215
240
|
"aiki.workflowRunId": workflowRunId,
|
|
216
241
|
"aiki.messageId": meta.messageId
|
|
217
242
|
});
|
|
218
243
|
} else {
|
|
244
|
+
logger.debug("Message acknowledged", {
|
|
245
|
+
"aiki.workerId": workerId,
|
|
246
|
+
"aiki.workflowRunId": workflowRunId,
|
|
247
|
+
"aiki.messageId": meta.messageId
|
|
248
|
+
});
|
|
219
249
|
}
|
|
220
250
|
} catch (error) {
|
|
221
251
|
logger.error("Failed to acknowledge message", {
|
|
222
|
-
"aiki.
|
|
252
|
+
"aiki.workerId": workerId,
|
|
223
253
|
"aiki.workflowRunId": workflowRunId,
|
|
224
|
-
"aiki.messageId": meta.messageId
|
|
254
|
+
"aiki.messageId": meta.messageId,
|
|
255
|
+
"aiki.error": error instanceof Error ? error.message : String(error)
|
|
225
256
|
});
|
|
226
257
|
throw error;
|
|
227
258
|
}
|
|
@@ -242,7 +273,7 @@ function createRedisStreamsStrategy(client2, strategy, workflows, workerShards)
|
|
|
242
273
|
getNextDelay,
|
|
243
274
|
getNextBatch: (size) => fetchRedisStreamMessages(
|
|
244
275
|
redis,
|
|
245
|
-
logger,
|
|
276
|
+
logger.child({ "aiki.workerId": workerId }),
|
|
246
277
|
streams,
|
|
247
278
|
streamConsumerGroupMap,
|
|
248
279
|
workerId,
|
|
@@ -278,9 +309,10 @@ async function fetchRedisStreamMessages(redis, logger, streams, streamConsumerGr
|
|
|
278
309
|
if (!isNonEmptyArray(streams)) {
|
|
279
310
|
return [];
|
|
280
311
|
}
|
|
312
|
+
const perStreamBlockTimeMs = Math.max(50, Math.floor(blockTimeMs / streams.length));
|
|
281
313
|
const batchSizePerStream = distributeRoundRobin(size, streams.length);
|
|
282
314
|
const shuffledStreams = shuffleArray(streams);
|
|
283
|
-
const
|
|
315
|
+
const streamEntries = [];
|
|
284
316
|
for (let i = 0; i < shuffledStreams.length; i++) {
|
|
285
317
|
const stream = shuffledStreams[i];
|
|
286
318
|
if (!stream) {
|
|
@@ -294,25 +326,27 @@ async function fetchRedisStreamMessages(redis, logger, streams, streamConsumerGr
|
|
|
294
326
|
if (!consumerGroup) {
|
|
295
327
|
continue;
|
|
296
328
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
329
|
+
try {
|
|
330
|
+
const result = await redis.xreadgroup(
|
|
331
|
+
"GROUP",
|
|
332
|
+
consumerGroup,
|
|
333
|
+
workerId,
|
|
334
|
+
"COUNT",
|
|
335
|
+
streamBatchSize,
|
|
336
|
+
"BLOCK",
|
|
337
|
+
perStreamBlockTimeMs,
|
|
338
|
+
"STREAMS",
|
|
339
|
+
stream,
|
|
340
|
+
">"
|
|
341
|
+
);
|
|
342
|
+
if (result) {
|
|
343
|
+
streamEntries.push(result);
|
|
344
|
+
}
|
|
345
|
+
} catch (error) {
|
|
346
|
+
logger.error("XREADGROUP failed", {
|
|
347
|
+
"aiki.stream": stream,
|
|
348
|
+
"aiki.error": error instanceof Error ? error.message : String(error)
|
|
349
|
+
});
|
|
316
350
|
}
|
|
317
351
|
}
|
|
318
352
|
const workflowRuns = isNonEmptyArray(streamEntries) ? await processRedisStreamMessages(redis, logger, streamConsumerGroupMap, streamEntries) : [];
|
|
@@ -336,14 +370,15 @@ async function fetchRedisStreamMessages(redis, logger, streams, streamConsumerGr
|
|
|
336
370
|
async function processRedisStreamMessages(redis, logger, streamConsumerGroupMap, streamsEntries) {
|
|
337
371
|
const workflowRuns = [];
|
|
338
372
|
for (const streamEntriesRaw of streamsEntries) {
|
|
339
|
-
|
|
340
|
-
|
|
373
|
+
logger.debug("Raw stream entries", { "aiki.entries": streamEntriesRaw });
|
|
374
|
+
const streamEntriesResult = streamEntriesSchema(streamEntriesRaw);
|
|
375
|
+
if (streamEntriesResult instanceof type.errors) {
|
|
341
376
|
logger.error("Invalid stream entries format", {
|
|
342
|
-
"aiki.error":
|
|
377
|
+
"aiki.error": streamEntriesResult.summary
|
|
343
378
|
});
|
|
344
379
|
continue;
|
|
345
380
|
}
|
|
346
|
-
for (const streamEntry of streamEntriesResult
|
|
381
|
+
for (const streamEntry of streamEntriesResult) {
|
|
347
382
|
const [stream, messages] = streamEntry;
|
|
348
383
|
const consumerGroup = streamConsumerGroupMap.get(stream);
|
|
349
384
|
if (!consumerGroup) {
|
|
@@ -352,20 +387,21 @@ async function processRedisStreamMessages(redis, logger, streamConsumerGroupMap,
|
|
|
352
387
|
});
|
|
353
388
|
continue;
|
|
354
389
|
}
|
|
355
|
-
for (const [messageId,
|
|
356
|
-
const
|
|
357
|
-
|
|
390
|
+
for (const [messageId, rawFields] of messages) {
|
|
391
|
+
const rawMessageData = rawStreamMessageFieldsToRecord(rawFields);
|
|
392
|
+
const messageData = streamMessageDataSchema(rawMessageData);
|
|
393
|
+
if (messageData instanceof type.errors) {
|
|
358
394
|
logger.warn("Invalid message structure", {
|
|
359
395
|
"aiki.stream": stream,
|
|
360
396
|
"aiki.messageId": messageId,
|
|
361
|
-
"aiki.error":
|
|
397
|
+
"aiki.error": messageData.summary
|
|
362
398
|
});
|
|
363
399
|
await redis.xack(stream, consumerGroup, messageId);
|
|
364
400
|
continue;
|
|
365
401
|
}
|
|
366
|
-
switch (messageData.
|
|
402
|
+
switch (messageData.type) {
|
|
367
403
|
case "workflow_run_ready": {
|
|
368
|
-
const
|
|
404
|
+
const workflowRunId = messageData.workflowRunId;
|
|
369
405
|
workflowRuns.push({
|
|
370
406
|
data: { workflowRunId },
|
|
371
407
|
meta: {
|
|
@@ -377,7 +413,7 @@ async function processRedisStreamMessages(redis, logger, streamConsumerGroupMap,
|
|
|
377
413
|
break;
|
|
378
414
|
}
|
|
379
415
|
default:
|
|
380
|
-
messageData.
|
|
416
|
+
messageData.type;
|
|
381
417
|
continue;
|
|
382
418
|
}
|
|
383
419
|
}
|
|
@@ -389,7 +425,7 @@ async function claimStuckRedisStreamMessages(redis, logger, shuffledStreams, str
|
|
|
389
425
|
if (maxClaim <= 0 || minIdleMs <= 0) {
|
|
390
426
|
return [];
|
|
391
427
|
}
|
|
392
|
-
const
|
|
428
|
+
const claimaibleMessagesByStream = await findClaimableMessagesByStream(
|
|
393
429
|
redis,
|
|
394
430
|
logger,
|
|
395
431
|
shuffledStreams,
|
|
@@ -398,16 +434,17 @@ async function claimStuckRedisStreamMessages(redis, logger, shuffledStreams, str
|
|
|
398
434
|
maxClaim,
|
|
399
435
|
minIdleMs
|
|
400
436
|
);
|
|
401
|
-
if (!
|
|
437
|
+
if (!claimaibleMessagesByStream.size) {
|
|
402
438
|
return [];
|
|
403
439
|
}
|
|
404
|
-
const
|
|
405
|
-
|
|
440
|
+
const claimPromises = Array.from(claimaibleMessagesByStream.entries()).map(async ([stream, messageIds]) => {
|
|
441
|
+
if (!messageIds.length) {
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
406
444
|
const consumerGroup = streamConsumerGroupMap.get(stream);
|
|
407
445
|
if (!consumerGroup) {
|
|
408
446
|
return null;
|
|
409
447
|
}
|
|
410
|
-
const messageIds = messages.map((message) => message.messageId);
|
|
411
448
|
try {
|
|
412
449
|
const claimedMessages = await redis.xclaim(stream, consumerGroup, workerId, minIdleMs, ...messageIds);
|
|
413
450
|
return { stream, claimedMessages };
|
|
@@ -415,23 +452,25 @@ async function claimStuckRedisStreamMessages(redis, logger, shuffledStreams, str
|
|
|
415
452
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
416
453
|
if (errorMessage.includes("NOGROUP")) {
|
|
417
454
|
logger.warn("Consumer group does not exist for stream, skipping claim operation", {
|
|
418
|
-
"aiki.stream": stream
|
|
455
|
+
"aiki.stream": stream,
|
|
456
|
+
"aiki.consumerGroup": consumerGroup
|
|
419
457
|
});
|
|
420
458
|
} else if (errorMessage.includes("BUSYGROUP")) {
|
|
421
459
|
logger.warn("Consumer group busy for stream, skipping claim operation", {
|
|
422
|
-
"aiki.stream": stream
|
|
460
|
+
"aiki.stream": stream,
|
|
461
|
+
"aiki.consumerGroup": consumerGroup
|
|
423
462
|
});
|
|
424
463
|
} else if (errorMessage.includes("NOSCRIPT")) {
|
|
425
464
|
logger.warn("Redis script not loaded for stream, skipping claim operation", {
|
|
426
|
-
"aiki.stream": stream
|
|
465
|
+
"aiki.stream": stream,
|
|
466
|
+
"aiki.consumerGroup": consumerGroup
|
|
427
467
|
});
|
|
428
468
|
} else {
|
|
429
469
|
logger.error("Failed to claim messages from stream", {
|
|
430
|
-
"aiki.
|
|
431
|
-
"aiki.messageIds": messageIds.length,
|
|
432
|
-
"aiki.workerId": workerId,
|
|
470
|
+
"aiki.stream": stream,
|
|
433
471
|
"aiki.consumerGroup": consumerGroup,
|
|
434
|
-
"aiki.
|
|
472
|
+
"aiki.messageIds": messageIds.length,
|
|
473
|
+
"aiki.error": errorMessage
|
|
435
474
|
});
|
|
436
475
|
}
|
|
437
476
|
return null;
|
|
@@ -448,10 +487,9 @@ async function claimStuckRedisStreamMessages(redis, logger, shuffledStreams, str
|
|
|
448
487
|
if (!isNonEmptyArray(claimedStreamEntries)) {
|
|
449
488
|
return [];
|
|
450
489
|
}
|
|
451
|
-
return processRedisStreamMessages(redis, logger, streamConsumerGroupMap, claimedStreamEntries);
|
|
490
|
+
return processRedisStreamMessages(redis, logger, streamConsumerGroupMap, [claimedStreamEntries]);
|
|
452
491
|
}
|
|
453
|
-
async function
|
|
454
|
-
const claimableMessages = [];
|
|
492
|
+
async function findClaimableMessagesByStream(redis, logger, shuffledStreams, streamConsumerGroupMap, workerId, maxClaim, minIdleMs) {
|
|
455
493
|
const claimSizePerStream = distributeRoundRobin(maxClaim, shuffledStreams.length);
|
|
456
494
|
const pendingPromises = [];
|
|
457
495
|
for (let i = 0; i < shuffledStreams.length; i++) {
|
|
@@ -467,31 +505,35 @@ async function findClaimableRedisStreamMessages(redis, logger, shuffledStreams,
|
|
|
467
505
|
if (!consumerGroup) {
|
|
468
506
|
continue;
|
|
469
507
|
}
|
|
470
|
-
const
|
|
471
|
-
pendingPromises.push(
|
|
508
|
+
const readPromise = redis.xpending(stream, consumerGroup, "IDLE", minIdleMs, "-", "+", claimSize).then((result) => ({ stream, result }));
|
|
509
|
+
pendingPromises.push(readPromise);
|
|
472
510
|
}
|
|
473
511
|
const pendingResults = await Promise.allSettled(pendingPromises);
|
|
512
|
+
const claimableMessagesByStream = /* @__PURE__ */ new Map();
|
|
474
513
|
for (const pendingResult of pendingResults) {
|
|
475
514
|
if (pendingResult.status !== "fulfilled") {
|
|
476
515
|
continue;
|
|
477
516
|
}
|
|
478
517
|
const { stream, result } = pendingResult.value;
|
|
479
|
-
const parsedResult =
|
|
480
|
-
if (
|
|
518
|
+
const parsedResult = streamPendingMessagesSchema(result);
|
|
519
|
+
if (parsedResult instanceof type.errors) {
|
|
481
520
|
logger.error("Invalid XPENDING response", {
|
|
482
521
|
"aiki.stream": stream,
|
|
483
|
-
"aiki.error":
|
|
522
|
+
"aiki.error": parsedResult.summary
|
|
484
523
|
});
|
|
485
524
|
continue;
|
|
486
525
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
526
|
+
const claimableStreamMessages = claimableMessagesByStream.get(stream) ?? [];
|
|
527
|
+
for (const [messageId, consumerName, _idleTimeMs, _deliveryCount] of parsedResult) {
|
|
528
|
+
if (consumerName !== workerId) {
|
|
529
|
+
claimableStreamMessages.push(messageId);
|
|
490
530
|
}
|
|
491
|
-
|
|
531
|
+
}
|
|
532
|
+
if (claimableStreamMessages.length) {
|
|
533
|
+
claimableMessagesByStream.set(stream, claimableStreamMessages);
|
|
492
534
|
}
|
|
493
535
|
}
|
|
494
|
-
return
|
|
536
|
+
return claimableMessagesByStream;
|
|
495
537
|
}
|
|
496
538
|
|
|
497
539
|
// subscribers/strategy-resolver.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikirun/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Client SDK for Aiki - connect to the server, start workflows, and manage execution",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
"build": "tsup"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@aikirun/types": "0.
|
|
21
|
+
"@aikirun/types": "0.10.0",
|
|
22
22
|
"@orpc/client": "^1.9.3",
|
|
23
23
|
"ioredis": "^5.4.1",
|
|
24
|
-
"
|
|
24
|
+
"arktype": "^2.1.29"
|
|
25
25
|
},
|
|
26
26
|
"publishConfig": {
|
|
27
27
|
"access": "public"
|