@pikku/kysely 0.12.17 → 0.12.18

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/CHANGELOG.md CHANGED
@@ -1,3 +1,28 @@
1
+ ## 0.12.18
2
+
3
+ ### Patch Changes
4
+
5
+ - 5c0ff0f: Fix `getRunHistory` dropping step provenance (`fromStepName`). The value was persisted on the step row and used by the graph planner, but `getRunHistory` built its rows from the per-attempt history and never carried `fromStepName` through — so run history (and any timeline reconstructed from it) reported no predecessors. Redis and Kysely `getRunHistory` now return `fromStepName`. Also adds the missing `from_step_name` column (+ backfill) to the Kysely workflow mirror's `workflow_step` schema and persists it on mirror inserts, so a mirror-side history has identical provenance.
6
+ - 72694f6: feat(workflow): expose per-step attempt count + record running/succeeded/failed timestamps
7
+
8
+ `getRunStatus` now returns `attempts` (the latest attempt count) per step, so
9
+ consumers can show retry counts without a second history query. It already
10
+ computed `duration` from `runningAt`/`succeededAt`, but the kysely and mongodb
11
+ workflow stores only stamped those timestamps on the _insert_ path — the
12
+ `running` / `succeeded` / `failed` status transitions updated the history row's
13
+ status without setting `runningAt` / `succeededAt` / `failedAt`, so `duration`
14
+ was always undefined. The transitions now stamp the matching timestamp, so step
15
+ duration is populated for kysely- and mongodb-backed runs. (Redis already
16
+ stamped on transition.) A shared service-suite test guards both behaviours.
17
+
18
+ - Updated dependencies [4be205f]
19
+ - Updated dependencies [061c717]
20
+ - Updated dependencies [2c55e13]
21
+ - Updated dependencies [c745c26]
22
+ - Updated dependencies [57900b5]
23
+ - Updated dependencies [72694f6]
24
+ - @pikku/core@0.12.39
25
+
1
26
  ## 0.12.17
2
27
 
3
28
  ### Patch Changes
@@ -61,6 +61,7 @@ export class KyselyWorkflowMirror {
61
61
  .addColumn('branch_taken', 'text')
62
62
  .addColumn('retries', 'integer')
63
63
  .addColumn('retry_delay', 'text')
64
+ .addColumn('from_step_name', 'text')
64
65
  .addColumn('created_at', 'timestamp', (col) => col.defaultTo(sql `CURRENT_TIMESTAMP`).notNull())
65
66
  .addColumn('updated_at', 'timestamp', (col) => col.defaultTo(sql `CURRENT_TIMESTAMP`).notNull())
66
67
  .addUniqueConstraint('workflow_step_run_name_unique', [
@@ -68,6 +69,14 @@ export class KyselyWorkflowMirror {
68
69
  'step_name',
69
70
  ])
70
71
  .execute();
72
+ // Additive: backfill the column on mirror tables created before from_step_name.
73
+ await this.db.schema
74
+ .alterTable('workflow_step')
75
+ .addColumn('from_step_name', 'text')
76
+ .execute()
77
+ .catch(() => {
78
+ // Column already exists (fresh tables include it above) — nothing to do.
79
+ });
71
80
  await this.db.schema
72
81
  .createTable('workflow_step_history')
73
82
  .ifNotExists()
@@ -144,6 +153,7 @@ export class KyselyWorkflowMirror {
144
153
  status: step.status,
145
154
  retries: step.retries ?? null,
146
155
  retryDelay: step.retryDelay?.toString() ?? null,
156
+ fromStepName: step.fromStepName ?? null,
147
157
  createdAt: now,
148
158
  updatedAt: now,
149
159
  })
@@ -137,6 +137,7 @@ export class KyselyWorkflowRunService {
137
137
  .select([
138
138
  's.workflowStepId',
139
139
  's.stepName',
140
+ 's.fromStepName',
140
141
  's.retries',
141
142
  's.retryDelay',
142
143
  'h.status',
@@ -158,6 +159,7 @@ export class KyselyWorkflowRunService {
158
159
  return {
159
160
  stepId,
160
161
  stepName: row.stepName,
162
+ fromStepName: row.fromStepName ?? undefined,
161
163
  status: row.status,
162
164
  result: parseJson(row.result),
163
165
  error: parseJson(row.error),
@@ -231,7 +231,7 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
231
231
  if (latestHistory) {
232
232
  await this.db
233
233
  .updateTable('workflowStepHistory')
234
- .set({ status: 'running' })
234
+ .set({ status: 'running', runningAt: new Date() })
235
235
  .where('historyId', '=', latestHistory.historyId)
236
236
  .execute();
237
237
  }
@@ -308,7 +308,7 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
308
308
  if (latestHistory) {
309
309
  await this.db
310
310
  .updateTable('workflowStepHistory')
311
- .set({ status: 'succeeded', result: resultJson })
311
+ .set({ status: 'succeeded', result: resultJson, succeededAt: new Date() })
312
312
  .where('historyId', '=', latestHistory.historyId)
313
313
  .execute();
314
314
  }
@@ -340,7 +340,7 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
340
340
  if (latestHistory) {
341
341
  await this.db
342
342
  .updateTable('workflowStepHistory')
343
- .set({ status: 'failed', error: errorJson })
343
+ .set({ status: 'failed', error: errorJson, failedAt: new Date() })
344
344
  .where('historyId', '=', latestHistory.historyId)
345
345
  .execute();
346
346
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/kysely",
3
- "version": "0.12.17",
3
+ "version": "0.12.18",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "MIT",
6
6
  "module": "dist/src/index.js",
@@ -20,7 +20,7 @@
20
20
  "prepublishOnly": "yarn build"
21
21
  },
22
22
  "peerDependencies": {
23
- "@pikku/core": "^0.12.38"
23
+ "@pikku/core": "^0.12.39"
24
24
  },
25
25
  "dependencies": {
26
26
  "kysely": "^0.29.0"
@@ -103,6 +103,29 @@ describe('KyselyWorkflowMirror — step lifecycle', () => {
103
103
  assert.equal(history[0]!.status, 'pending')
104
104
  })
105
105
 
106
+ test('getRunHistory surfaces step provenance (fromStepName)', async () => {
107
+ // The mirror persists fromStepName and getRunHistory reads it back, so the
108
+ // run history reconstructs the walked path (entry has none, the next step
109
+ // records its predecessor).
110
+ await seedStep(STEP_ID, 'begin')
111
+ await mirror.insertStepState(RUN_ID, {
112
+ stepId: `${STEP_ID}-2`,
113
+ stepName: 'next',
114
+ rpcName: 'rpc.fn',
115
+ data: { x: 2 },
116
+ status: 'pending',
117
+ attemptCount: 1,
118
+ fromStepName: 'begin',
119
+ createdAt: new Date(),
120
+ updatedAt: new Date(),
121
+ } as any)
122
+
123
+ const history = await runService.getRunHistory(RUN_ID)
124
+ const from = new Map(history.map((h) => [h.stepName, h.fromStepName]))
125
+ assert.equal(from.get('begin'), undefined, 'entry step has no predecessor')
126
+ assert.equal(from.get('next'), 'begin', 'next records its predecessor')
127
+ })
128
+
106
129
  test('running → succeeded updates the in-place history row', async () => {
107
130
  await seedStep()
108
131
  await mirror.setStepRunning(STEP_ID)
@@ -79,6 +79,7 @@ export class KyselyWorkflowMirror implements WorkflowRunMirror {
79
79
  .addColumn('branch_taken', 'text')
80
80
  .addColumn('retries', 'integer')
81
81
  .addColumn('retry_delay', 'text')
82
+ .addColumn('from_step_name', 'text')
82
83
  .addColumn('created_at', 'timestamp', (col) =>
83
84
  col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
84
85
  )
@@ -91,6 +92,15 @@ export class KyselyWorkflowMirror implements WorkflowRunMirror {
91
92
  ])
92
93
  .execute()
93
94
 
95
+ // Additive: backfill the column on mirror tables created before from_step_name.
96
+ await this.db.schema
97
+ .alterTable('workflow_step')
98
+ .addColumn('from_step_name', 'text')
99
+ .execute()
100
+ .catch(() => {
101
+ // Column already exists (fresh tables include it above) — nothing to do.
102
+ })
103
+
94
104
  await this.db.schema
95
105
  .createTable('workflow_step_history')
96
106
  .ifNotExists()
@@ -202,6 +212,7 @@ export class KyselyWorkflowMirror implements WorkflowRunMirror {
202
212
  status: step.status,
203
213
  retries: step.retries ?? null,
204
214
  retryDelay: step.retryDelay?.toString() ?? null,
215
+ fromStepName: step.fromStepName ?? null,
205
216
  createdAt: now,
206
217
  updatedAt: now,
207
218
  })
@@ -192,6 +192,7 @@ export class KyselyWorkflowRunService implements WorkflowRunService {
192
192
  .select([
193
193
  's.workflowStepId',
194
194
  's.stepName',
195
+ 's.fromStepName',
195
196
  's.retries',
196
197
  's.retryDelay',
197
198
  'h.status',
@@ -215,6 +216,7 @@ export class KyselyWorkflowRunService implements WorkflowRunService {
215
216
  return {
216
217
  stepId,
217
218
  stepName: row.stepName,
219
+ fromStepName: row.fromStepName ?? undefined,
218
220
  status: row.status as StepState['status'],
219
221
  result: parseJson(row.result),
220
222
  error: parseJson(row.error),
@@ -319,7 +319,7 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
319
319
  if (latestHistory) {
320
320
  await this.db
321
321
  .updateTable('workflowStepHistory')
322
- .set({ status: 'running' })
322
+ .set({ status: 'running', runningAt: new Date() })
323
323
  .where('historyId', '=', latestHistory.historyId)
324
324
  .execute()
325
325
  }
@@ -417,7 +417,7 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
417
417
  if (latestHistory) {
418
418
  await this.db
419
419
  .updateTable('workflowStepHistory')
420
- .set({ status: 'succeeded', result: resultJson })
420
+ .set({ status: 'succeeded', result: resultJson, succeededAt: new Date() })
421
421
  .where('historyId', '=', latestHistory.historyId)
422
422
  .execute()
423
423
  }
@@ -456,7 +456,7 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
456
456
  if (latestHistory) {
457
457
  await this.db
458
458
  .updateTable('workflowStepHistory')
459
- .set({ status: 'failed', error: errorJson })
459
+ .set({ status: 'failed', error: errorJson, failedAt: new Date() })
460
460
  .where('historyId', '=', latestHistory.historyId)
461
461
  .execute()
462
462
  }