@kubun/store-workflow 0.13.0 → 0.14.1
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/lib/api.d.ts +23 -1
- package/lib/api.js +52 -0
- package/lib/index.d.ts +1 -1
- package/lib/migrations.js +2 -1
- package/lib/tables.d.ts +4 -0
- package/package.json +7 -7
package/lib/api.d.ts
CHANGED
|
@@ -51,8 +51,9 @@ export type ListFilter = {
|
|
|
51
51
|
queue?: string;
|
|
52
52
|
status?: WorkflowStatus;
|
|
53
53
|
};
|
|
54
|
-
export type WorkflowScheduleRow = Omit<WorkflowSchedule, 'params' | 'enabled'> & {
|
|
54
|
+
export type WorkflowScheduleRow = Omit<WorkflowSchedule, 'params' | 'policy' | 'enabled'> & {
|
|
55
55
|
params: unknown;
|
|
56
|
+
policy: unknown;
|
|
56
57
|
enabled: boolean;
|
|
57
58
|
};
|
|
58
59
|
export type InsertScheduleInput = {
|
|
@@ -63,6 +64,24 @@ export type InsertScheduleInput = {
|
|
|
63
64
|
every?: string | null;
|
|
64
65
|
nextRun: number;
|
|
65
66
|
enabled: boolean;
|
|
67
|
+
kind?: 'interval' | 'adaptive';
|
|
68
|
+
policy?: unknown | null;
|
|
69
|
+
lastOutcome?: string | null;
|
|
70
|
+
consecutiveCount?: number;
|
|
71
|
+
};
|
|
72
|
+
export type AdvanceScheduleAdaptiveInput = {
|
|
73
|
+
nextRun: number;
|
|
74
|
+
lastOutcome: string | null;
|
|
75
|
+
consecutiveCount: number;
|
|
76
|
+
};
|
|
77
|
+
export type UpsertAdaptiveScheduleInput = {
|
|
78
|
+
id: string;
|
|
79
|
+
workflowName: string;
|
|
80
|
+
params: unknown;
|
|
81
|
+
policy: unknown;
|
|
82
|
+
nextRun: number;
|
|
83
|
+
enabled: boolean;
|
|
84
|
+
reactivate?: boolean;
|
|
66
85
|
};
|
|
67
86
|
export type WorkflowStoreAPI = {
|
|
68
87
|
insertInstance(input: InsertInstanceInput): Promise<InsertInstanceResult>;
|
|
@@ -78,10 +97,13 @@ export type WorkflowStoreAPI = {
|
|
|
78
97
|
get(instanceID: string): Promise<WorkflowInstanceRow | null>;
|
|
79
98
|
list(filter?: ListFilter): Promise<Array<WorkflowInstanceRow>>;
|
|
80
99
|
insertSchedule(input: InsertScheduleInput, now: number): Promise<void>;
|
|
100
|
+
upsertAdaptiveSchedule(input: UpsertAdaptiveScheduleInput, now: number): Promise<WorkflowScheduleRow>;
|
|
81
101
|
getSchedule(id: string): Promise<WorkflowScheduleRow | null>;
|
|
82
102
|
listSchedules(): Promise<Array<WorkflowScheduleRow>>;
|
|
83
103
|
listDueSchedules(now: number): Promise<Array<WorkflowScheduleRow>>;
|
|
84
104
|
advanceSchedule(id: string, nextRun: number, now: number): Promise<void>;
|
|
105
|
+
advanceScheduleAdaptive(id: string, input: AdvanceScheduleAdaptiveInput, now: number): Promise<void>;
|
|
85
106
|
setScheduleEnabled(id: string, enabled: boolean, now: number): Promise<void>;
|
|
107
|
+
setSchedulePolicy(id: string, policy: unknown, now: number): Promise<void>;
|
|
86
108
|
};
|
|
87
109
|
export declare function createWorkflowStore(db: Kysely<WorkflowTables>, adapter: Adapter): WorkflowStoreAPI;
|
package/lib/api.js
CHANGED
|
@@ -16,6 +16,7 @@ function rowToSchedule(row) {
|
|
|
16
16
|
return {
|
|
17
17
|
...row,
|
|
18
18
|
params: decodeJSON(row.params),
|
|
19
|
+
policy: decodeJSON(row.policy),
|
|
19
20
|
// Stored as integer 0/1 across adapters.
|
|
20
21
|
enabled: Number(row.enabled) === 1
|
|
21
22
|
};
|
|
@@ -25,6 +26,7 @@ export function createWorkflowStore(db, adapter) {
|
|
|
25
26
|
const encodeJSON = (value)=>adapter.encodeJSON(value);
|
|
26
27
|
const encodeNextAction = (value)=>value != null ? adapter.encodeJSON(value) : null;
|
|
27
28
|
const encodeParams = (value)=>adapter.encodeJSON(value);
|
|
29
|
+
const encodePolicy = (value)=>value != null ? adapter.encodeJSON(value) : null;
|
|
28
30
|
async function readRow(tx, instanceID) {
|
|
29
31
|
return await tx.selectFrom('kubun_workflow_instances').selectAll().where('instance_id', '=', instanceID).executeTakeFirst();
|
|
30
32
|
}
|
|
@@ -219,12 +221,48 @@ export function createWorkflowStore(db, adapter) {
|
|
|
219
221
|
params: encodeParams(input.params),
|
|
220
222
|
cron: input.cron ?? null,
|
|
221
223
|
every: input.every ?? null,
|
|
224
|
+
kind: input.kind ?? 'interval',
|
|
225
|
+
policy: encodePolicy(input.policy ?? null),
|
|
226
|
+
last_outcome: input.lastOutcome ?? null,
|
|
227
|
+
consecutive_count: input.consecutiveCount ?? 0,
|
|
222
228
|
next_run: input.nextRun,
|
|
223
229
|
enabled: input.enabled ? 1 : 0,
|
|
224
230
|
created_at: now,
|
|
225
231
|
updated_at: now
|
|
226
232
|
}).execute();
|
|
227
233
|
},
|
|
234
|
+
async upsertAdaptiveSchedule (input, now) {
|
|
235
|
+
const reactivate = input.reactivate ?? true;
|
|
236
|
+
// One statement, no preceding read: ON CONFLICT DO UPDATE lets two
|
|
237
|
+
// concurrent enables of the same id both commit — one inserts, the other
|
|
238
|
+
// updates — instead of the loser hitting the PK constraint. RETURNING
|
|
239
|
+
// hands back the row as committed by this statement. The update touches
|
|
240
|
+
// only next_run + enabled, so an existing row's params/policy and its
|
|
241
|
+
// last_outcome/consecutive_count streak survive a re-arm. Under
|
|
242
|
+
// reactivate:false the update keeps the stored `enabled` flag (a
|
|
243
|
+
// qualified self-reference, portable across SQLite and Postgres) so a
|
|
244
|
+
// user-paused row is not silently re-enabled; next_run still advances.
|
|
245
|
+
const row = await db.insertInto('kubun_workflow_schedules').values({
|
|
246
|
+
id: input.id,
|
|
247
|
+
workflow_name: input.workflowName,
|
|
248
|
+
params: encodeParams(input.params),
|
|
249
|
+
cron: null,
|
|
250
|
+
every: null,
|
|
251
|
+
kind: 'adaptive',
|
|
252
|
+
policy: encodePolicy(input.policy ?? null),
|
|
253
|
+
last_outcome: null,
|
|
254
|
+
consecutive_count: 0,
|
|
255
|
+
next_run: input.nextRun,
|
|
256
|
+
enabled: input.enabled ? 1 : 0,
|
|
257
|
+
created_at: now,
|
|
258
|
+
updated_at: now
|
|
259
|
+
}).onConflict((oc)=>oc.column('id').doUpdateSet((eb)=>({
|
|
260
|
+
next_run: input.nextRun,
|
|
261
|
+
enabled: reactivate ? input.enabled ? 1 : 0 : eb.ref('kubun_workflow_schedules.enabled'),
|
|
262
|
+
updated_at: now
|
|
263
|
+
}))).returningAll().executeTakeFirstOrThrow();
|
|
264
|
+
return rowToSchedule(row);
|
|
265
|
+
},
|
|
228
266
|
async getSchedule (id) {
|
|
229
267
|
const row = await db.selectFrom('kubun_workflow_schedules').selectAll().where('id', '=', id).executeTakeFirst();
|
|
230
268
|
return row != null ? rowToSchedule(row) : null;
|
|
@@ -243,11 +281,25 @@ export function createWorkflowStore(db, adapter) {
|
|
|
243
281
|
updated_at: now
|
|
244
282
|
}).where('id', '=', id).execute();
|
|
245
283
|
},
|
|
284
|
+
async advanceScheduleAdaptive (id, input, now) {
|
|
285
|
+
await db.updateTable('kubun_workflow_schedules').set({
|
|
286
|
+
next_run: input.nextRun,
|
|
287
|
+
last_outcome: input.lastOutcome,
|
|
288
|
+
consecutive_count: input.consecutiveCount,
|
|
289
|
+
updated_at: now
|
|
290
|
+
}).where('id', '=', id).execute();
|
|
291
|
+
},
|
|
246
292
|
async setScheduleEnabled (id, enabled, now) {
|
|
247
293
|
await db.updateTable('kubun_workflow_schedules').set({
|
|
248
294
|
enabled: enabled ? 1 : 0,
|
|
249
295
|
updated_at: now
|
|
250
296
|
}).where('id', '=', id).execute();
|
|
297
|
+
},
|
|
298
|
+
async setSchedulePolicy (id, policy, now) {
|
|
299
|
+
await db.updateTable('kubun_workflow_schedules').set({
|
|
300
|
+
policy: encodePolicy(policy),
|
|
301
|
+
updated_at: now
|
|
302
|
+
}).where('id', '=', id).execute();
|
|
251
303
|
}
|
|
252
304
|
};
|
|
253
305
|
}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { StoreProvider } from '@kubun/db';
|
|
2
2
|
import type { WorkflowStoreAPI } from './api.js';
|
|
3
|
-
export type { CheckpointInput, FailInput, FinishInput, InsertInstanceInput, InsertInstanceResult, InsertReason, InsertScheduleInput, ListFilter, WorkflowInstanceRow, WorkflowNextAction, WorkflowScheduleRow, WorkflowStatus, WorkflowStoreAPI, } from './api.js';
|
|
3
|
+
export type { AdvanceScheduleAdaptiveInput, CheckpointInput, FailInput, FinishInput, InsertInstanceInput, InsertInstanceResult, InsertReason, InsertScheduleInput, ListFilter, WorkflowInstanceRow, WorkflowNextAction, WorkflowScheduleRow, WorkflowStatus, WorkflowStoreAPI, } from './api.js';
|
|
4
4
|
export { createWorkflowStore } from './api.js';
|
|
5
5
|
export { workflowStoreDefinition } from './definition.js';
|
|
6
6
|
export { WORKFLOW_SINGLETON_UNIQUE_INDEX } from './migrations.js';
|
package/lib/migrations.js
CHANGED
|
@@ -40,7 +40,8 @@ export function getWorkflowMigrations(ctx) {
|
|
|
40
40
|
]).execute();
|
|
41
41
|
await db.schema.createTable('kubun_workflow_schedules').ifNotExists().addColumn('id', t.text, (col)=>col.notNull().primaryKey()).addColumn('workflow_name', t.text, (col)=>col.notNull()).addColumn('params', t.json, (col)=>col.notNull())// Exactly one of cron / every is set; both nullable, the store persists
|
|
42
42
|
// them without parsing.
|
|
43
|
-
.addColumn('cron', t.text).addColumn('every', t.text)//
|
|
43
|
+
.addColumn('cron', t.text).addColumn('every', t.text)// Default keeps existing interval inserts working unchanged.
|
|
44
|
+
.addColumn('kind', t.text, (col)=>col.notNull().defaultTo('interval')).addColumn('policy', t.json).addColumn('last_outcome', t.text).addColumn('consecutive_count', 'integer', (col)=>col.notNull().defaultTo(0))// ms-epoch as bigint (not the `timestamp` column type), matching the
|
|
44
45
|
// instance time columns and compared against an explicit numeric `now`.
|
|
45
46
|
.addColumn('next_run', t.bigint, (col)=>col.notNull())// 0/1: no portable boolean column type across the adapters.
|
|
46
47
|
.addColumn('enabled', 'integer', (col)=>col.notNull().defaultTo(1)).addColumn('created_at', t.bigint, (col)=>col.notNull()).addColumn('updated_at', t.bigint, (col)=>col.notNull()).execute();
|
package/lib/tables.d.ts
CHANGED
|
@@ -24,6 +24,10 @@ export type WorkflowScheduleTable = {
|
|
|
24
24
|
params: unknown;
|
|
25
25
|
cron: string | null;
|
|
26
26
|
every: string | null;
|
|
27
|
+
kind: string;
|
|
28
|
+
policy: unknown | null;
|
|
29
|
+
last_outcome: string | null;
|
|
30
|
+
consecutive_count: number;
|
|
27
31
|
next_run: number;
|
|
28
32
|
enabled: number;
|
|
29
33
|
created_at: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubun/store-workflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"keywords": [],
|
|
5
5
|
"license": "see LICENSE.md",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -15,14 +15,14 @@
|
|
|
15
15
|
"LICENSE.md"
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"
|
|
19
|
-
"@kubun/db": "^0.
|
|
20
|
-
"
|
|
18
|
+
"@kubun/db": "^0.14.0",
|
|
19
|
+
"@kubun/db-adapter": "^0.14.1",
|
|
20
|
+
"kysely": "^0.29.5"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@
|
|
24
|
-
"@kubun/db-
|
|
25
|
-
"@
|
|
23
|
+
"@kubun/db-better-sqlite": "^0.14.0",
|
|
24
|
+
"@kubun/db-postgres": "^0.14.0",
|
|
25
|
+
"@testcontainers/postgresql": "^12.1.0"
|
|
26
26
|
},
|
|
27
27
|
"publishConfig": {
|
|
28
28
|
"access": "public"
|