@aikirun/client 0.18.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/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,14 +83,6 @@ 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
88
  return value !== void 0 && value.length > 0;
@@ -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,14 +639,12 @@ 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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aikirun/client",
3
- "version": "0.18.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.18.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"