@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 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 { AdaptivePollingSubscriberStrategy, ApiClient, Client, ClientParams, Logger, PollingSubscriberStrategy, RedisConfig, RedisStreamsSubscriberStrategy, ResolvedSubscriberStrategy, SubscriberMessageMeta, SubscriberStrategy, WorkflowRunBatch } from '@aikirun/types/client';
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) => fetchRedisStreamMessages(
276
- redis,
277
- logger.child({ "aiki.workerId": workerId }),
278
- streams,
279
- streamConsumerGroupMap,
280
- workerId,
281
- size,
282
- blockTimeMs,
283
- claimMinIdleTimeMs
284
- ),
285
- heartbeat: getHeartbeat(workerId),
286
- acknowledge
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.type;
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[AIKI_API_KEY_ENV_NAME];
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 ${AIKI_API_KEY_ENV_NAME} env variable`);
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.17.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.17.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"