@pikku/kysely 0.13.40 → 0.13.41

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.
@@ -7,7 +7,7 @@ export declare class KyselyChannelStore extends ChannelStore {
7
7
  private initialized;
8
8
  constructor(db: Kysely<KyselyPikkuDB>);
9
9
  init(): Promise<void>;
10
- addChannel({ channelId, channelName, openingData, }: Channel): Promise<void>;
10
+ addChannel({ channelId, channelName, openingData, traceparent, }: Channel): Promise<void>;
11
11
  removeChannels(channelIds: string[]): Promise<void>;
12
12
  setPikkuUserId(channelId: string, pikkuUserId: string | null): Promise<void>;
13
13
  getChannel(channelId: string): Promise<Channel & {
@@ -15,13 +15,14 @@ export class KyselyChannelStore extends ChannelStore {
15
15
  await requirePikkuSchema(this.db, channelSchema);
16
16
  this.initialized = true;
17
17
  }
18
- async addChannel({ channelId, channelName, openingData, }) {
18
+ async addChannel({ channelId, channelName, openingData, traceparent, }) {
19
19
  await this.db
20
20
  .insertInto('channels')
21
21
  .values({
22
22
  channelId: channelId,
23
23
  channelName: channelName,
24
24
  openingData: JSON.stringify(openingData || {}),
25
+ traceparent: traceparent ?? null,
25
26
  })
26
27
  .execute();
27
28
  }
@@ -44,7 +45,13 @@ export class KyselyChannelStore extends ChannelStore {
44
45
  async getChannel(channelId) {
45
46
  const row = await this.db
46
47
  .selectFrom('channels')
47
- .select(['channelId', 'channelName', 'openingData', 'pikkuUserId'])
48
+ .select([
49
+ 'channelId',
50
+ 'channelName',
51
+ 'openingData',
52
+ 'traceparent',
53
+ 'pikkuUserId',
54
+ ])
48
55
  .where('channelId', '=', channelId)
49
56
  .executeTakeFirst();
50
57
  if (!row) {
@@ -54,6 +61,7 @@ export class KyselyChannelStore extends ChannelStore {
54
61
  channelId: row.channelId,
55
62
  channelName: row.channelName,
56
63
  openingData: parseJson(row.openingData) ?? {},
64
+ traceparent: row.traceparent ?? undefined,
57
65
  pikkuUserId: row.pikkuUserId ?? undefined,
58
66
  };
59
67
  }
@@ -16,6 +16,6 @@ export declare class KyselyDeploymentService implements DeploymentService {
16
16
  init(): Promise<void>;
17
17
  start(config: DeploymentConfig): Promise<void>;
18
18
  stop(): Promise<void>;
19
- invoke(funcName: string, data: unknown, session?: unknown, traceId?: string): Promise<unknown>;
19
+ invoke(funcName: string, data: unknown, session?: unknown, traceparent?: string): Promise<unknown>;
20
20
  private sendHeartbeat;
21
21
  }
@@ -65,8 +65,8 @@ export class KyselyDeploymentService {
65
65
  .execute();
66
66
  }
67
67
  }
68
- async invoke(funcName, data, session, traceId) {
69
- const headers = await buildMeshHeaders(this.jwt, this.secrets, funcName, session, traceId);
68
+ async invoke(funcName, data, session, traceparent) {
69
+ const headers = await buildMeshHeaders(this.jwt, this.secrets, funcName, session, traceparent);
70
70
  const ttlMs = this.heartbeatTtl;
71
71
  const cutoff = new Date(Date.now() - ttlMs);
72
72
  const result = await this.db
@@ -14,10 +14,11 @@ export declare class KyselyIncomingWebhookService extends IncomingWebhookService
14
14
  private initialized;
15
15
  constructor(queueService: QueueService, db: Kysely<KyselyPikkuDB>, retries?: number);
16
16
  init(): Promise<void>;
17
- accept({ source, events, }: {
17
+ accept({ source, events, traceparent, }: {
18
18
  source: string;
19
19
  request: WebhookRequest;
20
20
  events: TriggerEvent[];
21
+ traceparent?: string;
21
22
  }): Promise<number>;
22
23
  /** The new receipt's id, or undefined when this provider event id was already received. */
23
24
  private record;
@@ -20,14 +20,14 @@ export class KyselyIncomingWebhookService extends IncomingWebhookService {
20
20
  await requirePikkuSchema(this.db, incomingWebhookSchema);
21
21
  this.initialized = true;
22
22
  }
23
- async accept({ source, events, }) {
23
+ async accept({ source, events, traceparent, }) {
24
24
  let accepted = 0;
25
25
  for (const event of events) {
26
26
  const receiptId = await this.record(source, event);
27
27
  if (!receiptId)
28
28
  continue;
29
29
  try {
30
- await this.enqueue({ source, event, receiptId });
30
+ await this.enqueue({ source, event, receiptId }, traceparent);
31
31
  }
32
32
  catch (error) {
33
33
  // Without the receipt the provider's retry is accepted again rather
@@ -5,6 +5,8 @@ export interface ChannelsTable {
5
5
  channelName: string;
6
6
  createdAt: Generated<Date>;
7
7
  openingData: string;
8
+ /** The connection's W3C trace. */
9
+ traceparent: string | null;
8
10
  pikkuUserId: string | null;
9
11
  /** JSON-serialized per-socket channel state, keyed by channelId. */
10
12
  state: string | null;
@@ -1,6 +1,6 @@
1
1
  import type { SerializedError } from '@pikku/core/errors';
2
2
  import { PikkuWorkflowService } from '@pikku/core/workflow';
3
- import type { WorkflowPlannedStep, WorkflowServiceOptions, WorkflowRun, WorkflowRunWire, StepState, StepStatus, WorkflowStatus, WorkflowVersionStatus } from '@pikku/core/workflow';
3
+ import type { WorkflowPlannedStep, WorkflowServiceOptions, ScheduledStep, WorkflowRun, WorkflowRunWire, StepState, StepStatus, WorkflowStatus, WorkflowVersionStatus } from '@pikku/core/workflow';
4
4
  import { type Kysely } from 'kysely';
5
5
  import type { KyselyPikkuDB } from './kysely-tables.js';
6
6
  export declare class KyselyWorkflowService extends PikkuWorkflowService {
@@ -142,6 +142,7 @@ export declare class KyselyWorkflowService extends PikkuWorkflowService {
142
142
  stepId: string;
143
143
  }>>;
144
144
  protected findStalledRunIds(before: Date, limit: number): Promise<string[]>;
145
+ protected findScheduledSteps(limit: number, offset: number): Promise<ScheduledStep[]>;
145
146
  getRunState(runId: string): Promise<Record<string, unknown>>;
146
147
  protected upsertWorkflowVersionImpl(name: string, graphHash: string, graph: any, source: string, status?: WorkflowVersionStatus): Promise<void>;
147
148
  protected updateWorkflowVersionStatusImpl(name: string, graphHash: string, status: WorkflowVersionStatus): Promise<void>;
@@ -418,6 +418,7 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
418
418
  return null;
419
419
  }
420
420
  await this.setStepError(stepState.stepId, new WorkflowStepLeaseExpiredError(runId, stepName, stepState.attemptCount), stepState.attemptCount);
421
+ await this.logStepAttemptsExhausted(runId, stepName, stepState.attemptCount);
421
422
  // `executeWorkflowStepInner` reads a null claim as "another dispatch
422
423
  // owns it" and returns without resuming, so the run would stay
423
424
  // `running` until a stalled sweep. Requeue the orchestrator here.
@@ -432,7 +433,7 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
432
433
  }
433
434
  if (stepState.status === 'failed' || stepState.status === 'running') {
434
435
  const attempt = stepState.attemptCount + 1;
435
- return this.mirrored(() => this.reclaimStepAttempt(stepState, attempt, leaseMs), async (mirror, newStep) => {
436
+ const reclaimed = await this.mirrored(() => this.reclaimStepAttempt(stepState, attempt, leaseMs), async (mirror, newStep) => {
436
437
  if (newStep) {
437
438
  await mirror.createRetryAttempt(stepState.stepId, {
438
439
  ...newStep,
@@ -440,6 +441,11 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
440
441
  });
441
442
  }
442
443
  });
444
+ // A `failed` step threw, and this is its ordinary retry.
445
+ if (reclaimed && stepState.status === 'running') {
446
+ await this.logStepRerun(runId, stepName, reclaimed.attemptCount);
447
+ }
448
+ return reclaimed;
443
449
  }
444
450
  if (stepState.status === 'pending' || stepState.status === 'scheduled') {
445
451
  if (!(await this.claimStepStatus(stepState.stepId, ['pending', 'scheduled'], leaseMs))) {
@@ -660,12 +666,13 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
660
666
  eb('s.status', 'in', ['scheduled', 'suspended']),
661
667
  // A `running` step counts as in flight only while someone
662
668
  // still holds it. A lapsed lease is a dispatch that died, and
663
- // a null one a step nothing was expected to hand back.
669
+ // a null one with no child run is a step worked inside a pass,
670
+ // which the run's lease answers for, not this query.
664
671
  eb.and([
665
672
  eb('s.status', '=', 'running'),
666
673
  eb.or([
667
- eb('s.leaseExpiresAt', 'is', null),
668
674
  eb('s.leaseExpiresAt', '>', this.nowMs()),
675
+ eb('s.childRunId', 'is not', null),
669
676
  ]),
670
677
  ]),
671
678
  ])))))
@@ -682,6 +689,40 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
682
689
  .execute();
683
690
  return rows.map((row) => row.workflowRunId);
684
691
  }
692
+ async findScheduledSteps(limit, offset) {
693
+ const rows = await this.db
694
+ .selectFrom('workflowStep as s')
695
+ .innerJoin('workflowRuns as r', 'r.workflowRunId', 's.workflowRunId')
696
+ .select([
697
+ 's.workflowRunId',
698
+ 's.workflowStepId',
699
+ 's.stepName',
700
+ 's.rpcName',
701
+ 's.data',
702
+ 's.fromStepName',
703
+ 's.retries',
704
+ 's.retryDelay',
705
+ 's.updatedAt',
706
+ ])
707
+ .where('s.status', '=', 'scheduled')
708
+ .where('r.status', '=', 'running')
709
+ .orderBy('s.updatedAt', 'asc')
710
+ .orderBy('s.workflowStepId', 'asc')
711
+ .limit(limit)
712
+ .offset(offset)
713
+ .execute();
714
+ return rows.map((row) => ({
715
+ runId: row.workflowRunId,
716
+ stepId: row.workflowStepId,
717
+ stepName: row.stepName,
718
+ rpcName: row.rpcName ?? null,
719
+ data: parseJson(row.data),
720
+ fromStepName: row.fromStepName ?? undefined,
721
+ retries: row.retries != null ? Number(row.retries) : undefined,
722
+ retryDelay: row.retryDelay ?? undefined,
723
+ scheduledAt: new Date(row.updatedAt),
724
+ }));
725
+ }
685
726
  async getRunState(runId) {
686
727
  const row = await this.db
687
728
  .selectFrom('workflowRuns')
@@ -10,6 +10,7 @@ export const channelSchema = {
10
10
  .addColumn('channelName', ctx.text, (col) => col.notNull())
11
11
  .addColumn('createdAt', 'timestamp', (col) => col.defaultTo(sql `CURRENT_TIMESTAMP`).notNull())
12
12
  .addColumn('openingData', ctx.text, (col) => col.notNull().defaultTo(ctx.defaultText('{}')))
13
+ .addColumn('traceparent', ctx.text)
13
14
  .addColumn('pikkuUserId', ctx.text)
14
15
  .addColumn('state', ctx.text)
15
16
  .addColumn('lastWire', 'timestamp', (col) => col.defaultTo(sql `CURRENT_TIMESTAMP`).notNull()),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/kysely",
3
- "version": "0.13.40",
3
+ "version": "0.13.41",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/pikkujs/pikku.git",
@@ -28,7 +28,7 @@
28
28
  "clean": "rm -rf dist tsconfig.tsbuildinfo"
29
29
  },
30
30
  "peerDependencies": {
31
- "@pikku/core": "^0.12.146"
31
+ "@pikku/core": "^0.12.151"
32
32
  },
33
33
  "dependencies": {
34
34
  "kysely": "^0.29.6"