@aikirun/client 0.17.0 → 0.19.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 +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +167 -71
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ npm install @aikirun/client
|
|
|
14
14
|
import { client } from "@aikirun/client";
|
|
15
15
|
import { orderWorkflowV1 } from "./workflows.ts";
|
|
16
16
|
|
|
17
|
+
// Set AIKI_API_KEY env variable or pass apiKey option
|
|
17
18
|
const aikiClient = client({
|
|
18
19
|
url: "http://localhost:9850",
|
|
19
20
|
redis: { host: "localhost", port: 6379 },
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ClientParams, Client, Logger } from '@aikirun/types/client';
|
|
2
|
-
export {
|
|
2
|
+
export { ApiClient, Client, ClientParams, DbSubscriberStrategy, Logger, RedisConfig, RedisStreamsSubscriberStrategy, ResolvedSubscriberStrategy, SubscriberStrategy, WorkflowRunBatch } from '@aikirun/types/client';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Creates an Aiki client for starting and managing workflows.
|
package/dist/index.js
CHANGED
|
@@ -83,17 +83,9 @@ var ConsoleLogger = class _ConsoleLogger {
|
|
|
83
83
|
}
|
|
84
84
|
};
|
|
85
85
|
|
|
86
|
-
// ../../lib/address/index.ts
|
|
87
|
-
function getWorkflowStreamName(name, versionId, shard) {
|
|
88
|
-
return shard ? `workflow:${name}:${versionId}:${shard}` : `workflow:${name}:${versionId}`;
|
|
89
|
-
}
|
|
90
|
-
function getWorkerConsumerGroupName(workflowName, workflowVersionId, shard) {
|
|
91
|
-
return shard ? `worker:${workflowName}:${workflowVersionId}:${shard}` : `worker:${workflowName}:${workflowVersionId}`;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
86
|
// ../../lib/array/utils.ts
|
|
95
87
|
function isNonEmptyArray(value) {
|
|
96
|
-
return value.length > 0;
|
|
88
|
+
return value !== void 0 && value.length > 0;
|
|
97
89
|
}
|
|
98
90
|
function shuffleArray(array) {
|
|
99
91
|
const shuffledArray = Array.from(array);
|
|
@@ -112,6 +104,15 @@ function distributeRoundRobin(totalSize, itemCount) {
|
|
|
112
104
|
return distribution;
|
|
113
105
|
}
|
|
114
106
|
|
|
107
|
+
// ../../lib/crypto/hash.ts
|
|
108
|
+
import { createHash } from "crypto";
|
|
109
|
+
|
|
110
|
+
// ../../lib/duration/convert.ts
|
|
111
|
+
var MS_PER_SECOND = 1e3;
|
|
112
|
+
var MS_PER_MINUTE = 60 * MS_PER_SECOND;
|
|
113
|
+
var MS_PER_HOUR = 60 * MS_PER_MINUTE;
|
|
114
|
+
var MS_PER_DAY = 24 * MS_PER_HOUR;
|
|
115
|
+
|
|
115
116
|
// ../../lib/retry/strategy.ts
|
|
116
117
|
function getRetryParams(attempts, strategy) {
|
|
117
118
|
const strategyType = strategy.type;
|
|
@@ -160,6 +161,87 @@ function getRetryParams(attempts, strategy) {
|
|
|
160
161
|
}
|
|
161
162
|
}
|
|
162
163
|
|
|
164
|
+
// subscribers/db.ts
|
|
165
|
+
function createDbStrategy(client2, strategy, workflows, workerShards) {
|
|
166
|
+
const logger = client2.logger.child({
|
|
167
|
+
"aiki.component": "db-subscriber"
|
|
168
|
+
});
|
|
169
|
+
const intervalMs = strategy.intervalMs ?? 1e3;
|
|
170
|
+
const maxRetryIntervalMs = strategy.maxRetryIntervalMs ?? 3e4;
|
|
171
|
+
const atCapacityIntervalMs = strategy.atCapacityIntervalMs ?? 500;
|
|
172
|
+
const claimMinIdleTimeMs = strategy.claimMinIdleTimeMs ?? 9e4;
|
|
173
|
+
const workflowFilters = !isNonEmptyArray(workerShards) ? workflows.map((workflow) => ({ name: workflow.name, versionId: workflow.versionId })) : workflows.flatMap(
|
|
174
|
+
(workflow) => workerShards.map((shard) => ({ name: workflow.name, versionId: workflow.versionId, shard }))
|
|
175
|
+
);
|
|
176
|
+
const getNextDelay = (params) => {
|
|
177
|
+
switch (params.type) {
|
|
178
|
+
case "polled":
|
|
179
|
+
case "heartbeat":
|
|
180
|
+
return intervalMs;
|
|
181
|
+
case "at_capacity":
|
|
182
|
+
return atCapacityIntervalMs;
|
|
183
|
+
case "retry": {
|
|
184
|
+
const retryParams = getRetryParams(params.attemptNumber, {
|
|
185
|
+
type: "jittered",
|
|
186
|
+
maxAttempts: Number.POSITIVE_INFINITY,
|
|
187
|
+
baseDelayMs: intervalMs,
|
|
188
|
+
maxDelayMs: maxRetryIntervalMs
|
|
189
|
+
});
|
|
190
|
+
if (!retryParams.retriesLeft) {
|
|
191
|
+
return maxRetryIntervalMs;
|
|
192
|
+
}
|
|
193
|
+
return retryParams.delayMs;
|
|
194
|
+
}
|
|
195
|
+
default:
|
|
196
|
+
return params;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
return {
|
|
200
|
+
async init(workerId, _callbacks) {
|
|
201
|
+
return {
|
|
202
|
+
type: strategy.type,
|
|
203
|
+
getNextDelay,
|
|
204
|
+
getNextBatch: async (size) => {
|
|
205
|
+
const response = await client2.api.workflowRun.claimReadyV1({
|
|
206
|
+
workerId,
|
|
207
|
+
workflows: workflowFilters,
|
|
208
|
+
limit: size,
|
|
209
|
+
claimMinIdleTimeMs
|
|
210
|
+
});
|
|
211
|
+
return response.runs.map((run) => ({
|
|
212
|
+
data: { workflowRunId: run.id }
|
|
213
|
+
}));
|
|
214
|
+
},
|
|
215
|
+
heartbeat: async (workflowRunId) => {
|
|
216
|
+
try {
|
|
217
|
+
await client2.api.workflowRun.heartbeatV1({ id: workflowRunId });
|
|
218
|
+
logger.debug("Heartbeat sent", {
|
|
219
|
+
"aiki.workerId": workerId,
|
|
220
|
+
"aiki.workflowRunId": workflowRunId
|
|
221
|
+
});
|
|
222
|
+
} catch (error) {
|
|
223
|
+
logger.warn("Heartbeat failed", {
|
|
224
|
+
"aiki.workerId": workerId,
|
|
225
|
+
"aiki.workflowRunId": workflowRunId,
|
|
226
|
+
"aiki.error": error instanceof Error ? error.message : String(error)
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
acknowledge: async () => {
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ../../lib/address/index.ts
|
|
238
|
+
function getWorkflowStreamName(name, versionId, shard) {
|
|
239
|
+
return shard ? `workflow:${name}:${versionId}:${shard}` : `workflow:${name}:${versionId}`;
|
|
240
|
+
}
|
|
241
|
+
function getWorkerConsumerGroupName(workflowName, workflowVersionId, shard) {
|
|
242
|
+
return shard ? `worker:${workflowName}:${workflowVersionId}:${shard}` : `worker:${workflowName}:${workflowVersionId}`;
|
|
243
|
+
}
|
|
244
|
+
|
|
163
245
|
// subscribers/redis-streams.ts
|
|
164
246
|
import { INTERNAL } from "@aikirun/types/symbols";
|
|
165
247
|
import { type } from "arktype";
|
|
@@ -216,48 +298,6 @@ function createRedisStreamsStrategy(client2, strategy, workflows, workerShards)
|
|
|
216
298
|
return params;
|
|
217
299
|
}
|
|
218
300
|
};
|
|
219
|
-
const getHeartbeat = (workerId) => async (workflowRunId, meta) => {
|
|
220
|
-
try {
|
|
221
|
-
await redis.xclaim(meta.stream, meta.consumerGroup, workerId, 0, meta.messageId, "JUSTID");
|
|
222
|
-
logger.debug("Heartbeat sent", {
|
|
223
|
-
"aiki.workerId": workerId,
|
|
224
|
-
"aiki.workflowRunId": workflowRunId,
|
|
225
|
-
"aiki.messageId": meta.messageId
|
|
226
|
-
});
|
|
227
|
-
} catch (error) {
|
|
228
|
-
logger.warn("Heartbeat failed", {
|
|
229
|
-
"aiki.workerId": workerId,
|
|
230
|
-
"aiki.workflowRunId": workflowRunId,
|
|
231
|
-
"aiki.error": error instanceof Error ? error.message : String(error)
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
};
|
|
235
|
-
const acknowledge = async (workerId, workflowRunId, meta) => {
|
|
236
|
-
try {
|
|
237
|
-
const result = await redis.xack(meta.stream, meta.consumerGroup, meta.messageId);
|
|
238
|
-
if (result === 0) {
|
|
239
|
-
logger.warn("Message already acknowledged", {
|
|
240
|
-
"aiki.workerId": workerId,
|
|
241
|
-
"aiki.workflowRunId": workflowRunId,
|
|
242
|
-
"aiki.messageId": meta.messageId
|
|
243
|
-
});
|
|
244
|
-
} else {
|
|
245
|
-
logger.debug("Message acknowledged", {
|
|
246
|
-
"aiki.workerId": workerId,
|
|
247
|
-
"aiki.workflowRunId": workflowRunId,
|
|
248
|
-
"aiki.messageId": meta.messageId
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
} catch (error) {
|
|
252
|
-
logger.error("Failed to acknowledge message", {
|
|
253
|
-
"aiki.workerId": workerId,
|
|
254
|
-
"aiki.workflowRunId": workflowRunId,
|
|
255
|
-
"aiki.messageId": meta.messageId,
|
|
256
|
-
"aiki.error": error instanceof Error ? error.message : String(error)
|
|
257
|
-
});
|
|
258
|
-
throw error;
|
|
259
|
-
}
|
|
260
|
-
};
|
|
261
301
|
return {
|
|
262
302
|
async init(workerId, _callbacks) {
|
|
263
303
|
for (const [stream, consumerGroup] of streamConsumerGroupMap) {
|
|
@@ -269,21 +309,80 @@ function createRedisStreamsStrategy(client2, strategy, workflows, workerShards)
|
|
|
269
309
|
}
|
|
270
310
|
}
|
|
271
311
|
}
|
|
312
|
+
const pendingMessageMetaByWorkflowRunId = /* @__PURE__ */ new Map();
|
|
272
313
|
return {
|
|
273
314
|
type: strategy.type,
|
|
274
315
|
getNextDelay,
|
|
275
|
-
getNextBatch: (size) =>
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
316
|
+
getNextBatch: async (size) => {
|
|
317
|
+
const messages = await fetchRedisStreamMessages(
|
|
318
|
+
redis,
|
|
319
|
+
logger.child({ "aiki.workerId": workerId }),
|
|
320
|
+
streams,
|
|
321
|
+
streamConsumerGroupMap,
|
|
322
|
+
workerId,
|
|
323
|
+
size,
|
|
324
|
+
blockTimeMs,
|
|
325
|
+
claimMinIdleTimeMs
|
|
326
|
+
);
|
|
327
|
+
const batches = [];
|
|
328
|
+
for (const message of messages) {
|
|
329
|
+
pendingMessageMetaByWorkflowRunId.set(message.data.workflowRunId, message.meta);
|
|
330
|
+
batches.push({ data: message.data });
|
|
331
|
+
}
|
|
332
|
+
return batches;
|
|
333
|
+
},
|
|
334
|
+
heartbeat: async (workflowRunId) => {
|
|
335
|
+
const meta = pendingMessageMetaByWorkflowRunId.get(workflowRunId);
|
|
336
|
+
if (!meta) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
try {
|
|
340
|
+
await redis.xclaim(meta.stream, meta.consumerGroup, workerId, 0, meta.messageId, "JUSTID");
|
|
341
|
+
logger.debug("Heartbeat sent", {
|
|
342
|
+
"aiki.workerId": workerId,
|
|
343
|
+
"aiki.workflowRunId": workflowRunId,
|
|
344
|
+
"aiki.messageId": meta.messageId
|
|
345
|
+
});
|
|
346
|
+
} catch (error) {
|
|
347
|
+
logger.warn("Heartbeat failed", {
|
|
348
|
+
"aiki.workerId": workerId,
|
|
349
|
+
"aiki.workflowRunId": workflowRunId,
|
|
350
|
+
"aiki.error": error instanceof Error ? error.message : String(error)
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
acknowledge: async (workflowRunId) => {
|
|
355
|
+
const meta = pendingMessageMetaByWorkflowRunId.get(workflowRunId);
|
|
356
|
+
if (!meta) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
try {
|
|
360
|
+
const result = await redis.xack(meta.stream, meta.consumerGroup, meta.messageId);
|
|
361
|
+
if (result === 0) {
|
|
362
|
+
logger.warn("Message already acknowledged", {
|
|
363
|
+
"aiki.workerId": workerId,
|
|
364
|
+
"aiki.workflowRunId": workflowRunId,
|
|
365
|
+
"aiki.messageId": meta.messageId
|
|
366
|
+
});
|
|
367
|
+
} else {
|
|
368
|
+
logger.debug("Message acknowledged", {
|
|
369
|
+
"aiki.workerId": workerId,
|
|
370
|
+
"aiki.workflowRunId": workflowRunId,
|
|
371
|
+
"aiki.messageId": meta.messageId
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
} catch (error) {
|
|
375
|
+
logger.error("Failed to acknowledge message", {
|
|
376
|
+
"aiki.workerId": workerId,
|
|
377
|
+
"aiki.workflowRunId": workflowRunId,
|
|
378
|
+
"aiki.messageId": meta.messageId,
|
|
379
|
+
"aiki.error": error instanceof Error ? error.message : String(error)
|
|
380
|
+
});
|
|
381
|
+
throw error;
|
|
382
|
+
} finally {
|
|
383
|
+
pendingMessageMetaByWorkflowRunId.delete(workflowRunId);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
287
386
|
};
|
|
288
387
|
}
|
|
289
388
|
};
|
|
@@ -540,19 +639,16 @@ async function findClaimableMessagesByStream(redis, logger, shuffledStreams, str
|
|
|
540
639
|
// subscribers/strategy-resolver.ts
|
|
541
640
|
function resolveSubscriberStrategy(client2, strategy, workflows, workerShards) {
|
|
542
641
|
switch (strategy.type) {
|
|
543
|
-
// case "polling":
|
|
544
|
-
// return createPollingStrategy(client, strategy);
|
|
545
|
-
// case "adaptive_polling":
|
|
546
|
-
// return createAdaptivePollingStrategy(client, strategy);
|
|
547
642
|
case "redis":
|
|
548
643
|
return createRedisStreamsStrategy(client2, strategy, workflows, workerShards);
|
|
644
|
+
case "db":
|
|
645
|
+
return createDbStrategy(client2, strategy, workflows, workerShards);
|
|
549
646
|
default:
|
|
550
|
-
return strategy
|
|
647
|
+
return strategy;
|
|
551
648
|
}
|
|
552
649
|
}
|
|
553
650
|
|
|
554
651
|
// client.ts
|
|
555
|
-
var AIKI_API_KEY_ENV_NAME = "AIKI_API_KEY";
|
|
556
652
|
function client(params) {
|
|
557
653
|
return new ClientImpl(params);
|
|
558
654
|
}
|
|
@@ -560,9 +656,9 @@ var ClientImpl = class {
|
|
|
560
656
|
constructor(params) {
|
|
561
657
|
this.params = params;
|
|
562
658
|
this.logger = params.logger ?? new ConsoleLogger();
|
|
563
|
-
const apiKey = params.apiKey ?? process.env
|
|
659
|
+
const apiKey = params.apiKey ?? process.env.AIKI_API_KEY;
|
|
564
660
|
if (!apiKey) {
|
|
565
|
-
throw new Error(`API key is required. Provide it via 'apiKey' param or
|
|
661
|
+
throw new Error(`API key is required. Provide it via 'apiKey' param or AIKI_API_KEY env variable`);
|
|
566
662
|
}
|
|
567
663
|
const rpcLink = new RPCLink({
|
|
568
664
|
url: `${params.url}/api`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikirun/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.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,7 +18,7 @@
|
|
|
18
18
|
"build": "tsup"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@aikirun/types": "0.
|
|
21
|
+
"@aikirun/types": "0.19.0",
|
|
22
22
|
"@orpc/client": "^1.9.3",
|
|
23
23
|
"ioredis": "^5.4.1",
|
|
24
24
|
"arktype": "^2.1.29"
|