@kubun/plugin-workflow 0.13.0 → 0.14.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.
@@ -0,0 +1,9 @@
1
+ import type { AdaptivePolicy, WorkflowOutcome } from '@kubun/plugin-workflow-api';
2
+ export declare function validateAdaptivePolicy(policy: AdaptivePolicy): void;
3
+ export declare function computeNextDelay(policy: AdaptivePolicy, outcome: WorkflowOutcome, streak: {
4
+ lastOutcome: WorkflowOutcome | null;
5
+ count: number;
6
+ }): {
7
+ delayMs: number;
8
+ count: number;
9
+ };
@@ -0,0 +1,44 @@
1
+ // Adaptive schedule policy: the pure core that maps a recurring sync's last
2
+ // outcome to the delay before its next run. No DB, no engine state — the store
3
+ // persists the streak, the engine calls this to advance it. The policy types
4
+ // are the cross-plugin contract; this module keeps only the pure math.
5
+ const DEFAULT_BACKOFF = {
6
+ idle: 'fixed',
7
+ error: 'exponential',
8
+ offline: 'exponential'
9
+ };
10
+ function assertPositive(value, label) {
11
+ if (!(value > 0)) throw new Error(`adaptive policy: ${label} must be > 0, got ${value}`);
12
+ }
13
+ function validateBranch(branch, label) {
14
+ assertPositive(branch.base, `${label}.base`);
15
+ assertPositive(branch.max, `${label}.max`);
16
+ if (branch.base > branch.max) {
17
+ throw new Error(`adaptive policy: ${label}.base (${branch.base}) exceeds max (${branch.max})`);
18
+ }
19
+ }
20
+ export function validateAdaptivePolicy(policy) {
21
+ assertPositive(policy.changed, 'changed');
22
+ validateBranch(policy.idle, 'idle');
23
+ validateBranch(policy.error, 'error');
24
+ validateBranch(policy.offline, 'offline');
25
+ }
26
+ export function computeNextDelay(policy, outcome, streak) {
27
+ if (outcome === 'changed') {
28
+ return {
29
+ delayMs: policy.changed,
30
+ count: 0
31
+ };
32
+ }
33
+ // A different outcome class (a productive run's stored count of 0 included)
34
+ // starts a fresh streak at 1; same class grows it.
35
+ const count = streak.lastOutcome === outcome ? streak.count + 1 : 1;
36
+ const branch = policy[outcome];
37
+ const kind = branch.backoff ?? DEFAULT_BACKOFF[outcome];
38
+ const raw = kind === 'fixed' ? branch.base : branch.base * 2 ** (count - 1);
39
+ // Clamp is the runtime backstop even if validation was bypassed.
40
+ return {
41
+ delayMs: Math.min(branch.max, raw),
42
+ count
43
+ };
44
+ }
@@ -1,25 +1,3 @@
1
- import type { FlowAction, HandlersRecord } from '@sozai/flow';
2
- import type { Validator } from '@sozai/schema';
3
- export type RetryPolicy = {
4
- maxAttempts?: number;
5
- backoff?: 'exponential' | 'fixed';
6
- baseMs?: number;
7
- maxMs?: number;
8
- };
9
- export type TerminalStatus = 'completed' | 'failed' | 'cancelled';
10
- export type TerminalInfo = {
11
- instanceID: string;
12
- name: string;
13
- status: TerminalStatus;
14
- error?: string;
15
- };
16
- export type WorkflowDefinition<State extends Record<string, unknown> = Record<string, unknown>, Handlers extends HandlersRecord<State> = HandlersRecord<State>> = {
17
- name: string;
18
- handlers: Handlers;
19
- initialAction: FlowAction<State, Handlers>;
20
- stateValidator?: Validator<State>;
21
- queue?: string;
22
- retry?: RetryPolicy;
23
- onTerminal?: (info: TerminalInfo) => void;
24
- };
1
+ import type { WorkflowDefinition } from '@kubun/plugin-workflow-api';
2
+ import type { HandlersRecord } from '@sozai/flow';
25
3
  export declare function defineWorkflow<State extends Record<string, unknown>, Handlers extends HandlersRecord<State> = HandlersRecord<State>>(def: WorkflowDefinition<State, Handlers>): WorkflowDefinition<State, Handlers>;
package/lib/engine.d.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import type { Logger } from '@kubun/logger';
2
+ import type { RetryPolicy, WorkflowAPI } from '@kubun/plugin-workflow-api';
2
3
  import type { WorkflowStoreAPI } from '@kubun/store-workflow';
3
4
  import type { GetRandomID } from '@sozai/runtime';
4
- import type { RetryPolicy } from './definition.js';
5
- import type { WorkflowAPI } from './types.js';
6
5
  export declare function backoff(attempts: number, policy?: RetryPolicy): number;
7
6
  export declare function parseEvery(spec: string): number;
8
7
  export type WorkflowEngineDeps = {
package/lib/engine.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { EventEmitter } from '@sozai/event';
2
2
  import { createFlow } from '@sozai/flow';
3
+ import { computeNextDelay, validateAdaptivePolicy } from './adaptive.js';
3
4
  // One runner may hold an instance's lease for this long before another boot may
4
5
  // reclaim it as stranded.
5
6
  const LEASE_MS = 30_000;
@@ -43,6 +44,35 @@ function toStatus(row) {
43
44
  updatedAt: row.updated_at
44
45
  };
45
46
  }
47
+ // The active (pending/running) instance for (name, singletonKey) if one exists,
48
+ // else the most recent terminal by updated_at, else null. Instances are promoted
49
+ // with singleton_key = subject, so the subject keys the lookup.
50
+ async function currentInstanceRow(s, name, singletonKey) {
51
+ const rows = await s.list({
52
+ name
53
+ });
54
+ const forSubject = rows.filter((r)=>r.singleton_key === singletonKey);
55
+ const active = forSubject.find((r)=>r.status === 'pending' || r.status === 'running');
56
+ return active ?? forSubject.reduce((best, r)=>best == null || r.updated_at > best.updated_at ? r : best, null);
57
+ }
58
+ // Project an adaptive schedule row as a PeriodicSync. `currentStatus` is the
59
+ // schedule subject's current instance (see `currentInstanceRow`), null if never
60
+ // run.
61
+ async function buildPeriodicSync(s, sched) {
62
+ const subject = sched.id.slice(sched.workflow_name.length + 1);
63
+ const chosen = await currentInstanceRow(s, sched.workflow_name, subject);
64
+ return {
65
+ scheduleID: sched.id,
66
+ subject,
67
+ workflowName: sched.workflow_name,
68
+ enabled: sched.enabled,
69
+ nextRun: sched.next_run,
70
+ lastOutcome: sched.last_outcome,
71
+ consecutiveCount: sched.consecutive_count,
72
+ policy: sched.policy,
73
+ currentStatus: chosen != null ? toStatus(chosen) : null
74
+ };
75
+ }
46
76
  export function createWorkflowEngine(deps) {
47
77
  const now = deps.now ?? Date.now;
48
78
  const logger = deps.logger;
@@ -65,7 +95,7 @@ export function createWorkflowEngine(deps) {
65
95
  inflight.add(promise);
66
96
  void promise.finally(()=>inflight.delete(promise));
67
97
  }
68
- function fireTerminal(def, instanceID, status, error) {
98
+ function fireTerminal(def, instanceID, status, error, outcome) {
69
99
  events.fire(status, {
70
100
  instanceID,
71
101
  name: def.name
@@ -76,7 +106,8 @@ export function createWorkflowEngine(deps) {
76
106
  instanceID,
77
107
  name: def.name,
78
108
  status,
79
- error
109
+ error,
110
+ outcome
80
111
  });
81
112
  } catch (cause) {
82
113
  logger.warn('workflow onTerminal threw', {
@@ -87,6 +118,24 @@ export function createWorkflowEngine(deps) {
87
118
  }
88
119
  }
89
120
  }
121
+ // Advance the adaptive schedule that owns this instance, if any. Keyed by the
122
+ // instance's singleton_key: an interval-promoted or one-shot run has none and
123
+ // is a no-op. Called only after a fenced terminal write succeeded.
124
+ async function advanceAdaptive(row, outcome, at) {
125
+ if (row.singleton_key == null) return;
126
+ const s = await store();
127
+ const sched = await s.getSchedule(`${row.name}:${row.singleton_key}`);
128
+ if (sched == null || sched.kind !== 'adaptive') return;
129
+ const { delayMs, count } = computeNextDelay(sched.policy, outcome, {
130
+ lastOutcome: sched.last_outcome,
131
+ count: sched.consecutive_count
132
+ });
133
+ await s.advanceScheduleAdaptive(sched.id, {
134
+ nextRun: at + delayMs,
135
+ lastOutcome: outcome,
136
+ consecutiveCount: count
137
+ }, at);
138
+ }
90
139
  // Runs an already-claimed, already-leased row through the flow loop. The tick's
91
140
  // `claimDue` does the claiming, so this path never re-claims.
92
141
  async function run(row) {
@@ -153,7 +202,10 @@ export function createWorkflowEngine(deps) {
153
202
  state: flow.getState()
154
203
  }, now(), bootID);
155
204
  if (!wrote) return;
156
- fireTerminal(def, instanceID, 'completed');
205
+ // A run reports its outcome via the flow `end` value; absent = idle.
206
+ const outcome = value.outcome ?? 'idle';
207
+ fireTerminal(def, instanceID, 'completed', undefined, outcome);
208
+ await advanceAdaptive(row, outcome, now());
157
209
  return;
158
210
  }
159
211
  if (value.status === 'aborted') {
@@ -191,7 +243,8 @@ export function createWorkflowEngine(deps) {
191
243
  status: 'failed'
192
244
  }, now(), bootID);
193
245
  if (!wrote) return;
194
- fireTerminal(def, instanceID, 'failed', error);
246
+ fireTerminal(def, instanceID, 'failed', error, 'error');
247
+ await advanceAdaptive(row, 'error', now());
195
248
  return;
196
249
  }
197
250
  } catch (cause) {
@@ -255,8 +308,21 @@ export function createWorkflowEngine(deps) {
255
308
  const due = await s.listDueSchedules(at);
256
309
  for (const sched of due){
257
310
  try {
311
+ if (sched.kind === 'adaptive') {
312
+ // subjectKey may itself contain ':', so slice by the known prefix
313
+ // rather than splitting.
314
+ const subjectKey = sched.id.slice(sched.workflow_name.length + 1);
315
+ // next_run is NOT advanced here: it stays stable until the instance
316
+ // terminates, so the fire-time idempotency key blocks re-enqueue and
317
+ // the singleton key interlocks any manual sync of the same subject.
318
+ await api.enqueue(sched.workflow_name, sched.params, {
319
+ idempotencyKey: `schedule:${sched.id}:${sched.next_run}`,
320
+ singletonKey: subjectKey
321
+ });
322
+ continue;
323
+ }
258
324
  if (sched.every == null) {
259
- // Only interval schedules exist today; a cron-only row can't be armed.
325
+ // A cron-only interval row can't be armed (cron is deferred).
260
326
  logger.warn('workflow schedule "{id}" has no interval, skipping', {
261
327
  id: sched.id
262
328
  });
@@ -369,6 +435,57 @@ export function createWorkflowEngine(deps) {
369
435
  };
370
436
  return handle;
371
437
  },
438
+ async scheduleAdaptive (name, params, opts) {
439
+ validateAdaptivePolicy(opts.policy);
440
+ const s = await store();
441
+ // Deterministic id so a re-arm across restarts targets the same row.
442
+ const id = `${name}:${opts.subjectKey}`;
443
+ const at = now();
444
+ const existing = await s.getSchedule(id);
445
+ if (existing == null) {
446
+ await s.insertSchedule({
447
+ id,
448
+ workflowName: name,
449
+ params,
450
+ cron: null,
451
+ every: null,
452
+ kind: 'adaptive',
453
+ policy: opts.policy,
454
+ nextRun: at + opts.policy.changed,
455
+ enabled: true
456
+ }, at);
457
+ } else {
458
+ // Reactivation: re-arm eagerly, preserving the stored streak.
459
+ await s.advanceScheduleAdaptive(id, {
460
+ nextRun: at + opts.policy.changed,
461
+ lastOutcome: existing.last_outcome,
462
+ consecutiveCount: existing.consecutive_count
463
+ }, at);
464
+ await s.setScheduleEnabled(id, true, at);
465
+ }
466
+ const handle = {
467
+ id,
468
+ async disable () {
469
+ await (await store()).setScheduleEnabled(id, false, now());
470
+ },
471
+ async enable () {
472
+ // Re-arm eagerly (now + changed), mirroring reactivation above.
473
+ const s2 = await store();
474
+ const reAt = now();
475
+ const cur = await s2.getSchedule(id);
476
+ await s2.advanceScheduleAdaptive(id, {
477
+ nextRun: reAt + opts.policy.changed,
478
+ lastOutcome: cur?.last_outcome ?? null,
479
+ consecutiveCount: cur?.consecutive_count ?? 0
480
+ }, reAt);
481
+ await s2.setScheduleEnabled(id, true, reAt);
482
+ },
483
+ async cancel () {
484
+ await (await store()).setScheduleEnabled(id, false, now());
485
+ }
486
+ };
487
+ return handle;
488
+ },
372
489
  async enqueue (name, params, opts) {
373
490
  const def = registry.get(name);
374
491
  if (def == null) {
@@ -453,6 +570,39 @@ export function createWorkflowEngine(deps) {
453
570
  const rows = await s.list(filter);
454
571
  return rows.map(toStatus);
455
572
  },
573
+ async getCurrentInstance (name, singletonKey) {
574
+ const s = await store();
575
+ const row = await currentInstanceRow(s, name, singletonKey);
576
+ return row != null ? toStatus(row) : null;
577
+ },
578
+ async getPeriodicSync (scheduleID) {
579
+ const s = await store();
580
+ const sched = await s.getSchedule(scheduleID);
581
+ if (sched == null || sched.kind !== 'adaptive') return null;
582
+ return buildPeriodicSync(s, sched);
583
+ },
584
+ async listPeriodicSyncs (workflowName) {
585
+ const s = await store();
586
+ const scheds = (await s.listSchedules()).filter((x)=>x.kind === 'adaptive' && (workflowName == null || x.workflow_name === workflowName));
587
+ return Promise.all(scheds.map((x)=>buildPeriodicSync(s, x)));
588
+ },
589
+ async setPeriodicSyncEnabled (scheduleID, enabled) {
590
+ const s = await store();
591
+ const sched = await s.getSchedule(scheduleID);
592
+ if (sched == null || sched.kind !== 'adaptive') return null;
593
+ await s.setScheduleEnabled(scheduleID, enabled, now());
594
+ return buildPeriodicSync(s, await s.getSchedule(scheduleID) ?? sched);
595
+ },
596
+ async setPeriodicSyncPolicy (scheduleID, policy) {
597
+ // Validate before touching the store: a bad policy throws loudly and
598
+ // nothing is written.
599
+ validateAdaptivePolicy(policy);
600
+ const s = await store();
601
+ const sched = await s.getSchedule(scheduleID);
602
+ if (sched == null || sched.kind !== 'adaptive') return null;
603
+ await s.setSchedulePolicy(scheduleID, policy, now());
604
+ return buildPeriodicSync(s, await s.getSchedule(scheduleID) ?? sched);
605
+ },
456
606
  events
457
607
  };
458
608
  return {
package/lib/index.d.ts CHANGED
@@ -1,10 +1,9 @@
1
1
  import type { KubunPlugin, PluginFactoryParams } from '@kubun/engine';
2
2
  export type WorkflowPluginOptions = Record<string, never>;
3
3
  export declare function createWorkflowPlugin(_options?: WorkflowPluginOptions): (params: PluginFactoryParams) => KubunPlugin;
4
- export type { RetryPolicy, TerminalInfo, TerminalStatus, WorkflowDefinition, } from './definition.js';
4
+ export { computeNextDelay, validateAdaptivePolicy } from './adaptive.js';
5
5
  export { defineWorkflow } from './definition.js';
6
6
  export type { WorkflowEngine, WorkflowEngineDeps } from './engine.js';
7
7
  export { createWorkflowEngine, parseEvery } from './engine.js';
8
8
  export type { WorkflowContext, WorkflowEventResult } from './schema.js';
9
9
  export { createWorkflowContextFactory, createWorkflowSchemaExtension, subscribeToWorkflowEvents, } from './schema.js';
10
- export type { EnqueueOptions, InstanceHandle, InstanceStatus, ListFilter, QueueOptions, ScheduleHandle, ScheduleSpec, WorkflowAPI, WorkflowEvents, } from './types.js';
package/lib/index.js CHANGED
@@ -25,6 +25,7 @@ export function createWorkflowPlugin(_options = {}) {
25
25
  };
26
26
  };
27
27
  }
28
+ export { computeNextDelay, validateAdaptivePolicy } from './adaptive.js';
28
29
  export { defineWorkflow } from './definition.js';
29
30
  export { createWorkflowEngine, parseEvery } from './engine.js';
30
31
  export { createWorkflowContextFactory, createWorkflowSchemaExtension, subscribeToWorkflowEvents } from './schema.js';
package/lib/schema.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { ContextFactory, SchemaExtension } from '@kubun/engine';
2
+ import type { AdaptivePolicy, InstanceStatus, ListFilter, PeriodicSync, WorkflowAPI, WorkflowEvents } from '@kubun/plugin-workflow-api';
2
3
  import type { EventsSource } from '@sozai/event';
3
- import type { InstanceStatus, ListFilter, WorkflowAPI, WorkflowEvents } from './types.js';
4
4
  export type WorkflowEventResult = {
5
5
  type: keyof WorkflowEvents;
6
6
  instanceID: string;
@@ -31,6 +31,10 @@ export type WorkflowContext = {
31
31
  get(instanceID: string): Promise<InstanceStatus | null>;
32
32
  list(filter: ListFilter): Promise<Array<InstanceStatus>>;
33
33
  subscribeToWorkflowEvents(instanceID?: string | null): AsyncIterableIterator<WorkflowEventResult>;
34
+ getPeriodicSync(scheduleID: string): Promise<PeriodicSync | null>;
35
+ listPeriodicSyncs(workflowName?: string): Promise<Array<PeriodicSync>>;
36
+ setPeriodicSyncEnabled(scheduleID: string, enabled: boolean): Promise<PeriodicSync | null>;
37
+ setPeriodicSyncPolicy(scheduleID: string, policy: AdaptivePolicy): Promise<PeriodicSync | null>;
34
38
  };
35
39
  declare module '@kubun/graphql' {
36
40
  interface PluginContextMap {
package/lib/schema.js CHANGED
@@ -1,3 +1,7 @@
1
+ import { PluginNodeID } from '@kubun/id';
2
+ // The plugin identity and Node type name in every PeriodicSync global id.
3
+ const PLUGIN_NAME = 'workflow';
4
+ const PERIODIC_SYNC_TYPE = 'PeriodicSync';
1
5
  // Every lifecycle key. The source must listen to all of them and tag each event
2
6
  // with its `type`, since one subscription surfaces the whole lifecycle.
3
7
  const EVENT_TYPES = [
@@ -95,11 +99,21 @@ function requireWorkflow(ctx) {
95
99
  }
96
100
  return ctx.workflow;
97
101
  }
102
+ // A PeriodicSync's GraphQL `id` is a global PluginNodeID; the schedule id is its
103
+ // localID. `node(id)` decodes centrally, but the typed fields receive the global
104
+ // id and must decode it here.
105
+ function decodeScheduleID(id) {
106
+ return PluginNodeID.fromString(id).localID;
107
+ }
98
108
  export function createWorkflowContextFactory(api) {
99
109
  return ()=>({
100
110
  get: (instanceID)=>api.get(instanceID),
101
111
  list: (filter)=>api.list(filter),
102
- subscribeToWorkflowEvents: (instanceID)=>subscribeToWorkflowEvents(api.events, instanceID)
112
+ subscribeToWorkflowEvents: (instanceID)=>subscribeToWorkflowEvents(api.events, instanceID),
113
+ getPeriodicSync: (scheduleID)=>api.getPeriodicSync(scheduleID),
114
+ listPeriodicSyncs: (workflowName)=>api.listPeriodicSyncs(workflowName),
115
+ setPeriodicSyncEnabled: (scheduleID, enabled)=>api.setPeriodicSyncEnabled(scheduleID, enabled),
116
+ setPeriodicSyncPolicy: (scheduleID, policy)=>api.setPeriodicSyncPolicy(scheduleID, policy)
103
117
  });
104
118
  }
105
119
  // ms-epoch timestamps are exposed as `Float` (JS number), matching how the
@@ -124,9 +138,28 @@ type WorkflowEvent {
124
138
  name: String!
125
139
  }
126
140
 
141
+ type PeriodicSync implements Node {
142
+ id: ID!
143
+ subject: String!
144
+ workflowName: String!
145
+ enabled: Boolean!
146
+ nextRun: Float!
147
+ lastOutcome: String
148
+ consecutiveCount: Int!
149
+ policy: JSON!
150
+ currentStatus: WorkflowInstanceStatus
151
+ }
152
+
127
153
  extend type Query {
128
154
  workflowInstance(instanceID: ID!): WorkflowInstanceStatus
129
155
  workflowInstances(name: String, queue: String, status: String): [WorkflowInstanceStatus!]!
156
+ periodicSync(id: ID!): PeriodicSync
157
+ periodicSyncs(workflowName: String): [PeriodicSync!]!
158
+ }
159
+
160
+ extend type Mutation {
161
+ setPeriodicSyncEnabled(id: ID!, enabled: Boolean!): PeriodicSync!
162
+ setPeriodicSyncPolicy(id: ID!, policy: JSON!): PeriodicSync!
130
163
  }
131
164
 
132
165
  extend type Subscription {
@@ -143,7 +176,23 @@ export function createWorkflowSchemaExtension() {
143
176
  name: args.name,
144
177
  queue: args.queue,
145
178
  status: args.status
146
- })
179
+ }),
180
+ periodicSync: (_source, args, context)=>requireWorkflow(context).getPeriodicSync(decodeScheduleID(args.id)),
181
+ periodicSyncs: (_source, args, context)=>requireWorkflow(context).listPeriodicSyncs(args.workflowName)
182
+ },
183
+ mutationFields: {
184
+ setPeriodicSyncEnabled: (_source, args, context)=>requireWorkflow(context).setPeriodicSyncEnabled(decodeScheduleID(args.id), args.enabled),
185
+ setPeriodicSyncPolicy: (_source, args, context)=>requireWorkflow(context).setPeriodicSyncPolicy(decodeScheduleID(args.id), args.policy)
186
+ },
187
+ typeFields: {
188
+ PeriodicSync: {
189
+ id: (source)=>PluginNodeID.create(PLUGIN_NAME, PERIODIC_SYNC_TYPE, source.scheduleID).toString()
190
+ }
191
+ },
192
+ nodeResolvers: {
193
+ PeriodicSync: {
194
+ resolve: (localID, ctx)=>requireWorkflow(ctx).getPeriodicSync(localID)
195
+ }
147
196
  },
148
197
  subscriptionFields: {
149
198
  workflowEvents: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubun/plugin-workflow",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "keywords": [],
5
5
  "license": "see LICENSE.md",
6
6
  "sideEffects": false,
@@ -15,20 +15,22 @@
15
15
  "LICENSE.md"
16
16
  ],
17
17
  "dependencies": {
18
+ "@kubun/db": "^0.14.0",
19
+ "@kubun/engine": "^0.14.0",
20
+ "@kubun/graphql": "^0.14.0",
21
+ "@kubun/id": "^0.14.0",
22
+ "@kubun/logger": "^0.14.0",
23
+ "@kubun/plugin-workflow-api": "^0.14.0",
24
+ "@kubun/store-workflow": "^0.14.0",
18
25
  "@sozai/event": "^0.1.3",
19
- "@sozai/flow": "^0.3.0",
26
+ "@sozai/flow": "^0.3.1",
20
27
  "@sozai/runtime": "^0.1.0",
21
- "@sozai/schema": "^0.1.1",
22
- "@kubun/engine": "^0.13.0",
23
- "@kubun/logger": "^0.13.0",
24
- "@kubun/store-workflow": "^0.13.0",
25
- "@kubun/graphql": "^0.13.0",
26
- "@kubun/db": "^0.13.0"
28
+ "@sozai/schema": "^0.1.1"
27
29
  },
28
30
  "devDependencies": {
29
- "@testcontainers/postgresql": "^12.1.0",
30
- "@kubun/db-postgres": "^0.13.0",
31
- "@kubun/db-better-sqlite": "^0.13.0"
31
+ "@kubun/db-better-sqlite": "^0.14.0",
32
+ "@kubun/db-postgres": "^0.14.0",
33
+ "@testcontainers/postgresql": "^12.1.0"
32
34
  },
33
35
  "publishConfig": {
34
36
  "access": "public"
package/lib/types.d.ts DELETED
@@ -1,73 +0,0 @@
1
- import type { WorkflowStatus } from '@kubun/store-workflow';
2
- import type { EventsSource } from '@sozai/event';
3
- import type { HandlersRecord } from '@sozai/flow';
4
- import type { WorkflowDefinition } from './definition.js';
5
- export type WorkflowEvents = {
6
- started: {
7
- instanceID: string;
8
- name: string;
9
- };
10
- checkpoint: {
11
- instanceID: string;
12
- name: string;
13
- };
14
- failed: {
15
- instanceID: string;
16
- name: string;
17
- };
18
- completed: {
19
- instanceID: string;
20
- name: string;
21
- };
22
- cancelled: {
23
- instanceID: string;
24
- name: string;
25
- };
26
- };
27
- export type InstanceStatus = {
28
- instanceID: string;
29
- name: string;
30
- queue: string;
31
- status: WorkflowStatus;
32
- attempts: number;
33
- lastError: string | null;
34
- createdAt: number;
35
- updatedAt: number;
36
- };
37
- export type InstanceHandle = {
38
- instanceID: string;
39
- };
40
- export type EnqueueOptions = {
41
- queue?: string;
42
- priority?: number;
43
- idempotencyKey?: string;
44
- singletonKey?: string;
45
- };
46
- export type ListFilter = {
47
- name?: string;
48
- queue?: string;
49
- status?: WorkflowStatus;
50
- };
51
- export type QueueOptions = {
52
- concurrency: number;
53
- ratePerMinute?: number;
54
- };
55
- export type ScheduleSpec = {
56
- every: string;
57
- };
58
- export type ScheduleHandle = {
59
- id: string;
60
- disable(): Promise<void>;
61
- enable(): Promise<void>;
62
- cancel(): Promise<void>;
63
- };
64
- export type WorkflowAPI = {
65
- register<State extends Record<string, unknown>, Handlers extends HandlersRecord<State> = HandlersRecord<State>>(def: WorkflowDefinition<State, Handlers>): void;
66
- defineQueue(name: string, opts: QueueOptions): void;
67
- enqueue(name: string, params?: unknown, opts?: EnqueueOptions): Promise<InstanceHandle>;
68
- schedule(name: string, params: unknown, when: ScheduleSpec): Promise<ScheduleHandle>;
69
- cancel(instanceID: string): Promise<void>;
70
- get(instanceID: string): Promise<InstanceStatus | null>;
71
- list(filter?: ListFilter): Promise<Array<InstanceStatus>>;
72
- events: EventsSource<WorkflowEvents>;
73
- };
package/lib/types.js DELETED
@@ -1 +0,0 @@
1
- export { };