@pikku/kysely 0.13.39 → 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.
- package/dist/src/kysely-channel-store.d.ts +1 -1
- package/dist/src/kysely-channel-store.js +10 -2
- package/dist/src/kysely-deployment-service.d.ts +1 -1
- package/dist/src/kysely-deployment-service.js +5 -5
- package/dist/src/kysely-incoming-webhook-service.d.ts +2 -1
- package/dist/src/kysely-incoming-webhook-service.js +2 -2
- package/dist/src/kysely-tables.d.ts +2 -0
- package/dist/src/kysely-workflow-service.d.ts +2 -1
- package/dist/src/kysely-workflow-service.js +44 -3
- package/dist/src/schema/channel.schema.js +1 -0
- package/package.json +2 -2
|
@@ -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([
|
|
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,
|
|
19
|
+
invoke(funcName: string, data: unknown, session?: unknown, traceparent?: string): Promise<unknown>;
|
|
20
20
|
private sendHeartbeat;
|
|
21
21
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { buildMeshHeaders } from '@pikku/core/mesh';
|
|
2
2
|
import { getAllFunctionNames } from '@pikku/core/function';
|
|
3
3
|
import { requirePikkuSchema } from './schema/index.js';
|
|
4
4
|
import { upsert } from './kysely-upsert.js';
|
|
@@ -65,8 +65,8 @@ export class KyselyDeploymentService {
|
|
|
65
65
|
.execute();
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
-
async invoke(funcName, data, session,
|
|
69
|
-
const headers = await
|
|
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
|
|
@@ -82,7 +82,7 @@ export class KyselyDeploymentService {
|
|
|
82
82
|
throw new Error(`No deployment found for function '${funcName}'`);
|
|
83
83
|
}
|
|
84
84
|
const endpoint = result[0].endpoint;
|
|
85
|
-
const url = `${endpoint}/
|
|
85
|
+
const url = `${endpoint}/mesh/rpc/${encodeURIComponent(funcName)}`;
|
|
86
86
|
const response = await fetch(url, {
|
|
87
87
|
method: 'POST',
|
|
88
88
|
headers: {
|
|
@@ -92,7 +92,7 @@ export class KyselyDeploymentService {
|
|
|
92
92
|
body: JSON.stringify({ data }),
|
|
93
93
|
});
|
|
94
94
|
if (!response.ok) {
|
|
95
|
-
throw new Error(`
|
|
95
|
+
throw new Error(`Mesh RPC call to '${funcName}' failed: ${response.status}`);
|
|
96
96
|
}
|
|
97
97
|
return response.json();
|
|
98
98
|
}
|
|
@@ -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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
31
|
+
"@pikku/core": "^0.12.151"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"kysely": "^0.29.6"
|