@headless-lms/adapter-workflow-hatchet 0.1.0 → 0.2.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/README.md +30 -5
- package/dist/index.d.ts +101 -12
- package/dist/index.js +318 -28
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# @headless-lms/adapter-workflow-hatchet
|
|
2
2
|
|
|
3
3
|
Hatchet ([hatchet.run](https://hatchet.run)) implementation of the `AutomationEngine`
|
|
4
|
-
port from `@headless-lms/core/types`
|
|
4
|
+
port from `@headless-lms/core/types` and the `Scheduler` port from
|
|
5
|
+
`@headless-lms/core/shared/ports` — durable execution for
|
|
6
|
+
dispatched automations, notification deliveries and scheduled jobs. One object
|
|
7
|
+
implements both ports; the container exposes it as `automationEngine` and `scheduler`.
|
|
5
8
|
|
|
6
9
|
The adapter declares one workflow, `automation-run`: a durable parent task that
|
|
7
10
|
iterates the dispatched actions, spawning one child task per action
|
|
@@ -11,11 +14,33 @@ child that exhausts its retries stops the sequence; the parent then calls
|
|
|
11
14
|
spawned via the durable event log, a crash mid-run resumes after the last
|
|
12
15
|
completed action instead of restarting the sequence from scratch.
|
|
13
16
|
|
|
17
|
+
Webhook deliveries run through the same workflow: a webhook is an automation whose
|
|
18
|
+
one action delivers the event, so a failed delivery is retried like any other action.
|
|
19
|
+
|
|
20
|
+
Two more tasks run on the same worker:
|
|
21
|
+
|
|
22
|
+
- `notification-delivery` sends one notification delivery (an email or a push) on its
|
|
23
|
+
channel, with `retries: 3` and `backoff: { factor: 5, maxSeconds: 300 }` (retries after
|
|
24
|
+
5 s, 25 s and 125 s). Its concurrency key is the delivery id with
|
|
25
|
+
`maxRuns: 1` and `GROUP_ROUND_ROBIN`, so a second run for the same delivery queues
|
|
26
|
+
instead of overlapping; on the last attempt a failing delivery is marked `failed`.
|
|
27
|
+
- `scheduled-job` runs the job a context registered under the input's `job` name with
|
|
28
|
+
its payload, with the same retries and backoff and a concurrency key of `job:key`. A one-off job
|
|
29
|
+
(`Scheduler.at`) is a Hatchet scheduled run tagged with metadata `job` and `key`;
|
|
30
|
+
a second `at` with the same key moves it, and `cancel` deletes it. A recurring
|
|
31
|
+
product job is a cron named `hlms-job:{job}`, created, replaced or removed by
|
|
32
|
+
`start()` to match the registered jobs. A firing whose job has no handler is
|
|
33
|
+
logged and dropped.
|
|
34
|
+
|
|
35
|
+
`start()` registers every task on one worker, reconciles the product job crons and
|
|
36
|
+
starts the worker; call it after the server listens. The `automation-schedule` crons
|
|
37
|
+
for an org's own `schedule:` triggers are unchanged.
|
|
38
|
+
|
|
14
39
|
```ts
|
|
15
40
|
import { HatchetAutomationEngine } from "@headless-lms/adapter-workflow-hatchet";
|
|
16
41
|
|
|
17
42
|
const container = await createContainer(config, {
|
|
18
|
-
adapters: {
|
|
43
|
+
adapters: { workflows: (logger) => new HatchetAutomationEngine(undefined, logger) },
|
|
19
44
|
});
|
|
20
45
|
```
|
|
21
46
|
|
|
@@ -27,9 +52,9 @@ setups.
|
|
|
27
52
|
|
|
28
53
|
## Environment variables (Hatchet client, read by the SDK)
|
|
29
54
|
|
|
30
|
-
| Variable
|
|
31
|
-
|
|
|
32
|
-
| `HATCHET_CLIENT_TOKEN` | yes
|
|
55
|
+
| Variable | Required | Notes |
|
|
56
|
+
| ---------------------- | -------- | ---------------------------------------- |
|
|
57
|
+
| `HATCHET_CLIENT_TOKEN` | yes | Hatchet API token for the target tenant. |
|
|
33
58
|
|
|
34
59
|
See the Hatchet TypeScript SDK docs for the full set of `HATCHET_CLIENT_*` variables
|
|
35
60
|
(host/port, TLS, namespace, …).
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { APIContracts } from "@hatchet-dev/typescript-sdk/clients/rest/index.js";
|
|
2
|
+
import { ConcurrencyLimitStrategy } from "@hatchet-dev/typescript-sdk/v1/index.js";
|
|
3
|
+
import { AutomationActionResult, AutomationDispatch, AutomationEngine, AutomationExecutor, DeliveryDispatch, JsonValue, Logger, ScheduledAutomation } from "@headless-lms/core/types";
|
|
4
|
+
import { RecurringJob, ScheduledJob, Scheduler } from "@headless-lms/core/shared/ports";
|
|
2
5
|
//#region src/index.d.ts
|
|
3
6
|
interface ActionTaskInput {
|
|
4
7
|
dispatch: AutomationDispatch;
|
|
5
8
|
index: number;
|
|
6
9
|
}
|
|
7
|
-
/** Subset of `DurableContext` the parent task uses; hand-rolled so tests can fake it without a real Hatchet client. */
|
|
8
10
|
interface DurableCtxLike {
|
|
9
11
|
spawnChild(task: unknown, input: ActionTaskInput, options?: Record<string, unknown>): Promise<AutomationActionResult>;
|
|
10
12
|
}
|
|
@@ -15,38 +17,125 @@ interface HatchetWorkerLike {
|
|
|
15
17
|
start(): Promise<void>;
|
|
16
18
|
stop(): Promise<void>;
|
|
17
19
|
}
|
|
18
|
-
|
|
20
|
+
interface HatchetCronLike {
|
|
21
|
+
metadata: {
|
|
22
|
+
id: string;
|
|
23
|
+
};
|
|
24
|
+
name?: string;
|
|
25
|
+
cron: string;
|
|
26
|
+
}
|
|
27
|
+
interface HatchetCronClientLike {
|
|
28
|
+
create(workflow: unknown, cron: {
|
|
29
|
+
name: string;
|
|
30
|
+
expression: string;
|
|
31
|
+
input?: Record<string, unknown>;
|
|
32
|
+
}): Promise<HatchetCronLike>;
|
|
33
|
+
delete(cron: string | HatchetCronLike): Promise<void>;
|
|
34
|
+
list(query: {
|
|
35
|
+
workflow?: unknown;
|
|
36
|
+
limit?: number;
|
|
37
|
+
offset?: number;
|
|
38
|
+
}): Promise<{
|
|
39
|
+
rows?: HatchetCronLike[];
|
|
40
|
+
}>;
|
|
41
|
+
}
|
|
42
|
+
interface HatchetScheduledRunLike {
|
|
43
|
+
metadata: {
|
|
44
|
+
id: string;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
interface HatchetScheduledClientLike {
|
|
48
|
+
list(query: {
|
|
49
|
+
workflow?: unknown;
|
|
50
|
+
additionalMetadata?: string[];
|
|
51
|
+
statuses?: APIContracts.ScheduledRunStatus[];
|
|
52
|
+
}): Promise<{
|
|
53
|
+
rows?: HatchetScheduledRunLike[];
|
|
54
|
+
}>;
|
|
55
|
+
update(scheduledRun: string, update: {
|
|
56
|
+
triggerAt: Date;
|
|
57
|
+
}): Promise<unknown>;
|
|
58
|
+
bulkDelete(opts: {
|
|
59
|
+
scheduledRuns?: string[];
|
|
60
|
+
filter?: {
|
|
61
|
+
additionalMetadata?: string[];
|
|
62
|
+
};
|
|
63
|
+
}): Promise<unknown>;
|
|
64
|
+
}
|
|
65
|
+
interface HatchetTaskLike<I> {
|
|
66
|
+
runNoWait(input: I): Promise<unknown>;
|
|
67
|
+
schedule(enqueueAt: Date, input: I, options?: {
|
|
68
|
+
additionalMetadata?: Record<string, string>;
|
|
69
|
+
}): Promise<unknown>;
|
|
70
|
+
}
|
|
19
71
|
interface HatchetClientLike {
|
|
20
|
-
task(opts: {
|
|
72
|
+
task<I>(opts: {
|
|
21
73
|
name: string;
|
|
22
74
|
retries?: number;
|
|
23
|
-
|
|
24
|
-
|
|
75
|
+
backoff?: {
|
|
76
|
+
factor?: number;
|
|
77
|
+
maxSeconds?: number;
|
|
78
|
+
};
|
|
79
|
+
concurrency?: {
|
|
80
|
+
expression: string;
|
|
81
|
+
maxRuns?: number;
|
|
82
|
+
limitStrategy?: ConcurrencyLimitStrategy;
|
|
83
|
+
};
|
|
84
|
+
fn: (input: I, ctx: unknown) => Promise<unknown> | unknown;
|
|
85
|
+
}): HatchetTaskLike<I>;
|
|
25
86
|
durableTask(opts: {
|
|
26
87
|
name: string;
|
|
27
88
|
fn: (input: AutomationDispatch, ctx: DurableCtxLike) => Promise<void>;
|
|
28
89
|
}): RunnableTask<AutomationDispatch>;
|
|
29
90
|
worker(name: string, opts: {
|
|
30
91
|
workflows: any[];
|
|
92
|
+
handleKill?: boolean;
|
|
31
93
|
}): Promise<HatchetWorkerLike>;
|
|
94
|
+
crons: HatchetCronClientLike;
|
|
95
|
+
scheduled: HatchetScheduledClientLike;
|
|
32
96
|
}
|
|
33
|
-
declare
|
|
34
|
-
|
|
97
|
+
declare function cronName(schedule: ScheduledAutomation): string;
|
|
98
|
+
declare class HatchetAutomationEngine implements AutomationEngine, Scheduler {
|
|
35
99
|
private readonly logger?;
|
|
100
|
+
private readonly reconcileEveryMs;
|
|
36
101
|
private executor;
|
|
102
|
+
private jobs;
|
|
103
|
+
private recurring;
|
|
37
104
|
private worker;
|
|
105
|
+
private syncing;
|
|
106
|
+
private syncingJobs;
|
|
107
|
+
private reconcileTimer;
|
|
38
108
|
private readonly actionTask;
|
|
109
|
+
private readonly scheduleTask;
|
|
110
|
+
private readonly jobTask;
|
|
111
|
+
private readonly deliveryTask;
|
|
39
112
|
private readonly automationRunTask;
|
|
40
|
-
|
|
41
|
-
constructor(client?: HatchetClientLike, logger?: Logger | undefined);
|
|
113
|
+
private readonly client;
|
|
114
|
+
constructor(client?: HatchetClientLike, logger?: Logger | undefined, reconcileEveryMs?: number);
|
|
42
115
|
register(executor: AutomationExecutor): void;
|
|
116
|
+
registerJobs(handlers: ScheduledJob[], recurring: RecurringJob[]): void;
|
|
117
|
+
at(job: string, runAt: Date, key: string, payload: JsonValue): Promise<void>;
|
|
118
|
+
private pendingRuns;
|
|
119
|
+
private scheduleJob;
|
|
120
|
+
cancel(job: string, key: string): Promise<void>;
|
|
43
121
|
dispatch(d: AutomationDispatch): Promise<void>;
|
|
122
|
+
deliver(d: DeliveryDispatch): Promise<void>;
|
|
44
123
|
start(): Promise<void>;
|
|
45
124
|
stop(): Promise<void>;
|
|
46
|
-
|
|
125
|
+
private reconcileAll;
|
|
126
|
+
private reconcileJobsLogged;
|
|
127
|
+
private syncLogged;
|
|
128
|
+
syncSchedules(): Promise<void>;
|
|
129
|
+
private reconcile;
|
|
130
|
+
private reconcileJobs;
|
|
131
|
+
private runJob;
|
|
132
|
+
private listCrons;
|
|
133
|
+
private runDelivery;
|
|
134
|
+
private runSchedule;
|
|
47
135
|
private runAction;
|
|
136
|
+
private logActionError;
|
|
48
137
|
private runDispatch;
|
|
49
138
|
}
|
|
50
139
|
//#endregion
|
|
51
|
-
export { HatchetAutomationEngine, HatchetClientLike };
|
|
140
|
+
export { HatchetAutomationEngine, HatchetClientLike, HatchetCronClientLike, HatchetCronLike, HatchetScheduledClientLike, HatchetScheduledRunLike, HatchetTaskLike, cronName };
|
|
52
141
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,23 +1,110 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { APIContracts } from "@hatchet-dev/typescript-sdk/clients/rest/index.js";
|
|
2
|
+
import { ConcurrencyLimitStrategy, HatchetClient } from "@hatchet-dev/typescript-sdk/v1/index.js";
|
|
3
|
+
import { ACTION_FAILED, ActionFailedError, actionFailureMessage } from "@headless-lms/core/automations";
|
|
2
4
|
//#region src/index.ts
|
|
3
5
|
const WORKFLOW_NAME = "automation-run";
|
|
4
6
|
const ACTION_TASK_NAME = "automation-run-action";
|
|
7
|
+
const SCHEDULE_TASK_NAME = "automation-schedule";
|
|
8
|
+
const JOB_TASK_NAME = "scheduled-job";
|
|
9
|
+
const DELIVERY_TASK_NAME = "notification-delivery";
|
|
5
10
|
const WORKER_NAME = "automation-run-worker";
|
|
11
|
+
const CRON_PREFIX = "hlms-schedule:";
|
|
12
|
+
const JOB_CRON_PREFIX = "hlms-job:";
|
|
13
|
+
const RECONCILE_EVERY_MS = 5 * 6e4;
|
|
14
|
+
const ACTION_RETRIES = 3;
|
|
15
|
+
const JOB_RETRIES = 3;
|
|
16
|
+
const RETRY_BACKOFF = {
|
|
17
|
+
factor: 5,
|
|
18
|
+
maxSeconds: 300
|
|
19
|
+
};
|
|
20
|
+
function floorMinute(at) {
|
|
21
|
+
const copy = new Date(at);
|
|
22
|
+
copy.setUTCSeconds(0, 0);
|
|
23
|
+
return copy;
|
|
24
|
+
}
|
|
25
|
+
function cronName(schedule) {
|
|
26
|
+
return `${CRON_PREFIX}${schedule.orgId}:${schedule.automationId}:${schedule.cron}`;
|
|
27
|
+
}
|
|
28
|
+
function jobMetadata(job, key) {
|
|
29
|
+
return {
|
|
30
|
+
job: encodeURIComponent(job),
|
|
31
|
+
key: encodeURIComponent(key)
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function jobMetadataFilter(job, key) {
|
|
35
|
+
const metadata = jobMetadata(job, key);
|
|
36
|
+
return [`job:${metadata.job}`, `key:${metadata.key}`];
|
|
37
|
+
}
|
|
38
|
+
function sdkLogger(logger) {
|
|
39
|
+
return (context) => {
|
|
40
|
+
const log = logger.child({ hatchet: context });
|
|
41
|
+
const failure = (error, extra) => ({
|
|
42
|
+
...extra,
|
|
43
|
+
...error && { err: error }
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
debug: (message, extra) => log.debug(message, extra),
|
|
47
|
+
info: (message, extra) => log.info(message, extra),
|
|
48
|
+
green: (message, extra) => log.info(message, extra),
|
|
49
|
+
warn: (message, error, extra) => log.warn(message, failure(error, extra)),
|
|
50
|
+
error: (message, error, extra) => log.error(message, failure(error, extra)),
|
|
51
|
+
util: (key, message, extra) => log.debug(message, {
|
|
52
|
+
...extra,
|
|
53
|
+
key
|
|
54
|
+
})
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
}
|
|
6
58
|
var HatchetAutomationEngine = class {
|
|
7
|
-
client;
|
|
8
59
|
logger;
|
|
60
|
+
reconcileEveryMs;
|
|
9
61
|
executor;
|
|
62
|
+
jobs = /* @__PURE__ */ new Map();
|
|
63
|
+
recurring = [];
|
|
10
64
|
worker;
|
|
65
|
+
syncing;
|
|
66
|
+
syncingJobs;
|
|
67
|
+
reconcileTimer;
|
|
11
68
|
actionTask;
|
|
69
|
+
scheduleTask;
|
|
70
|
+
jobTask;
|
|
71
|
+
deliveryTask;
|
|
12
72
|
automationRunTask;
|
|
13
|
-
|
|
14
|
-
constructor(client
|
|
15
|
-
this.client = client;
|
|
73
|
+
client;
|
|
74
|
+
constructor(client, logger, reconcileEveryMs = RECONCILE_EVERY_MS) {
|
|
16
75
|
this.logger = logger;
|
|
76
|
+
this.reconcileEveryMs = reconcileEveryMs;
|
|
77
|
+
this.client = client ?? new HatchetClient(logger ? { logger: sdkLogger(logger) } : {});
|
|
17
78
|
this.actionTask = this.client.task({
|
|
18
79
|
name: ACTION_TASK_NAME,
|
|
19
|
-
retries:
|
|
20
|
-
fn: async (input) => this.runAction(input)
|
|
80
|
+
retries: ACTION_RETRIES,
|
|
81
|
+
fn: async (input, ctx) => this.runAction(input, ctx)
|
|
82
|
+
});
|
|
83
|
+
this.scheduleTask = this.client.task({
|
|
84
|
+
name: SCHEDULE_TASK_NAME,
|
|
85
|
+
fn: async (input) => this.runSchedule(input)
|
|
86
|
+
});
|
|
87
|
+
this.jobTask = this.client.task({
|
|
88
|
+
name: JOB_TASK_NAME,
|
|
89
|
+
retries: JOB_RETRIES,
|
|
90
|
+
backoff: RETRY_BACKOFF,
|
|
91
|
+
concurrency: {
|
|
92
|
+
expression: "input.job + ':' + input.key",
|
|
93
|
+
maxRuns: 1,
|
|
94
|
+
limitStrategy: ConcurrencyLimitStrategy.GROUP_ROUND_ROBIN
|
|
95
|
+
},
|
|
96
|
+
fn: async (input, ctx) => this.runJob(input, ctx)
|
|
97
|
+
});
|
|
98
|
+
this.deliveryTask = this.client.task({
|
|
99
|
+
name: DELIVERY_TASK_NAME,
|
|
100
|
+
retries: ACTION_RETRIES,
|
|
101
|
+
backoff: RETRY_BACKOFF,
|
|
102
|
+
concurrency: {
|
|
103
|
+
expression: "input.deliveryId",
|
|
104
|
+
maxRuns: 1,
|
|
105
|
+
limitStrategy: ConcurrencyLimitStrategy.GROUP_ROUND_ROBIN
|
|
106
|
+
},
|
|
107
|
+
fn: async (input, ctx) => this.runDelivery(input, ctx)
|
|
21
108
|
});
|
|
22
109
|
this.automationRunTask = this.client.durableTask({
|
|
23
110
|
name: WORKFLOW_NAME,
|
|
@@ -27,49 +114,252 @@ var HatchetAutomationEngine = class {
|
|
|
27
114
|
register(executor) {
|
|
28
115
|
this.executor = executor;
|
|
29
116
|
}
|
|
117
|
+
registerJobs(handlers, recurring) {
|
|
118
|
+
this.jobs = new Map(handlers.map((handler) => [handler.job, handler]));
|
|
119
|
+
this.recurring = [...recurring];
|
|
120
|
+
}
|
|
121
|
+
async at(job, runAt, key, payload) {
|
|
122
|
+
const [found, ...extra] = await this.pendingRuns(job, key);
|
|
123
|
+
if (!found) {
|
|
124
|
+
await this.scheduleJob(job, runAt, key, payload);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
await this.client.scheduled.update(found.metadata.id, { triggerAt: runAt });
|
|
129
|
+
} catch (err) {
|
|
130
|
+
if ((await this.pendingRuns(job, key)).some((run) => run.metadata.id === found.metadata.id)) throw err;
|
|
131
|
+
await this.scheduleJob(job, runAt, key, payload);
|
|
132
|
+
}
|
|
133
|
+
if (extra.length > 0) await this.client.scheduled.bulkDelete({ scheduledRuns: extra.map((run) => run.metadata.id) });
|
|
134
|
+
}
|
|
135
|
+
async pendingRuns(job, key) {
|
|
136
|
+
return (await this.client.scheduled.list({
|
|
137
|
+
workflow: this.jobTask,
|
|
138
|
+
additionalMetadata: jobMetadataFilter(job, key),
|
|
139
|
+
statuses: [APIContracts.ScheduledRunStatus.SCHEDULED]
|
|
140
|
+
})).rows ?? [];
|
|
141
|
+
}
|
|
142
|
+
async scheduleJob(job, runAt, key, payload) {
|
|
143
|
+
await this.jobTask.schedule(runAt, {
|
|
144
|
+
job,
|
|
145
|
+
key,
|
|
146
|
+
payload
|
|
147
|
+
}, { additionalMetadata: jobMetadata(job, key) });
|
|
148
|
+
}
|
|
149
|
+
async cancel(job, key) {
|
|
150
|
+
await this.client.scheduled.bulkDelete({ filter: { additionalMetadata: jobMetadataFilter(job, key) } });
|
|
151
|
+
}
|
|
30
152
|
async dispatch(d) {
|
|
31
153
|
if (!this.executor) throw new Error("HatchetAutomationEngine.dispatch called before register()");
|
|
32
154
|
await this.automationRunTask.runNoWait(d);
|
|
33
155
|
}
|
|
156
|
+
async deliver(d) {
|
|
157
|
+
if (!this.executor) throw new Error("HatchetAutomationEngine.deliver called before register()");
|
|
158
|
+
await this.deliveryTask.runNoWait(d);
|
|
159
|
+
}
|
|
34
160
|
async start() {
|
|
35
161
|
if (!this.executor) throw new Error("HatchetAutomationEngine.start called before register()");
|
|
36
|
-
this.worker = await this.client.worker(WORKER_NAME, {
|
|
162
|
+
this.worker = await this.client.worker(WORKER_NAME, {
|
|
163
|
+
handleKill: false,
|
|
164
|
+
workflows: [
|
|
165
|
+
this.actionTask,
|
|
166
|
+
this.automationRunTask,
|
|
167
|
+
this.scheduleTask,
|
|
168
|
+
this.jobTask,
|
|
169
|
+
this.deliveryTask
|
|
170
|
+
]
|
|
171
|
+
});
|
|
172
|
+
await this.reconcileJobsLogged();
|
|
37
173
|
this.worker.start().catch((err) => {
|
|
38
|
-
this.logger?.error("hatchet worker stopped unexpectedly", {
|
|
174
|
+
this.logger?.error("hatchet worker stopped unexpectedly", { err });
|
|
39
175
|
});
|
|
176
|
+
if (this.reconcileTimer) clearInterval(this.reconcileTimer);
|
|
177
|
+
this.reconcileTimer = setInterval(() => void this.reconcileAll(), this.reconcileEveryMs);
|
|
178
|
+
this.reconcileTimer.unref?.();
|
|
179
|
+
await this.syncLogged();
|
|
40
180
|
}
|
|
41
181
|
async stop() {
|
|
42
|
-
|
|
182
|
+
if (this.reconcileTimer) clearInterval(this.reconcileTimer);
|
|
183
|
+
this.reconcileTimer = void 0;
|
|
184
|
+
if (!this.worker) return;
|
|
185
|
+
await this.worker.stop();
|
|
43
186
|
this.worker = void 0;
|
|
187
|
+
this.logger?.info("hatchet worker stopped");
|
|
188
|
+
}
|
|
189
|
+
async reconcileAll() {
|
|
190
|
+
await this.reconcileJobsLogged();
|
|
191
|
+
await this.syncLogged();
|
|
192
|
+
}
|
|
193
|
+
async reconcileJobsLogged() {
|
|
194
|
+
if (this.syncingJobs) return this.syncingJobs;
|
|
195
|
+
this.syncingJobs = this.reconcileJobs().catch((err) => {
|
|
196
|
+
this.logger?.error("hatchet job crons not reconciled", { err });
|
|
197
|
+
}).finally(() => {
|
|
198
|
+
this.syncingJobs = void 0;
|
|
199
|
+
});
|
|
200
|
+
return this.syncingJobs;
|
|
201
|
+
}
|
|
202
|
+
async syncLogged() {
|
|
203
|
+
try {
|
|
204
|
+
await this.syncSchedules();
|
|
205
|
+
} catch (err) {
|
|
206
|
+
this.logger?.error("hatchet schedules not reconciled", { err });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async syncSchedules() {
|
|
210
|
+
if (this.syncing) {
|
|
211
|
+
await this.syncing;
|
|
212
|
+
return this.syncSchedules();
|
|
213
|
+
}
|
|
214
|
+
this.syncing = this.reconcile().finally(() => {
|
|
215
|
+
this.syncing = void 0;
|
|
216
|
+
});
|
|
217
|
+
await this.syncing;
|
|
218
|
+
}
|
|
219
|
+
async reconcile() {
|
|
220
|
+
if (!this.executor?.listSchedules) return;
|
|
221
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
222
|
+
for (const schedule of await this.executor.listSchedules()) wanted.set(cronName(schedule), schedule);
|
|
223
|
+
const existing = await this.listCrons(this.scheduleTask);
|
|
224
|
+
const seen = /* @__PURE__ */ new Set();
|
|
225
|
+
for (const cron of existing) {
|
|
226
|
+
if (!cron.name?.startsWith(CRON_PREFIX)) continue;
|
|
227
|
+
const schedule = wanted.get(cron.name);
|
|
228
|
+
if (schedule && schedule.cron === cron.cron && !seen.has(cron.name)) {
|
|
229
|
+
seen.add(cron.name);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
await this.client.crons.delete(cron);
|
|
233
|
+
}
|
|
234
|
+
for (const [name, schedule] of wanted) {
|
|
235
|
+
if (seen.has(name)) continue;
|
|
236
|
+
await this.client.crons.create(this.scheduleTask, {
|
|
237
|
+
name,
|
|
238
|
+
expression: schedule.cron,
|
|
239
|
+
input: {
|
|
240
|
+
orgId: schedule.orgId,
|
|
241
|
+
automationId: schedule.automationId
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
this.logger?.info("hatchet schedules synced", { count: wanted.size });
|
|
246
|
+
}
|
|
247
|
+
async reconcileJobs() {
|
|
248
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
249
|
+
for (const recurring of this.recurring) wanted.set(`${JOB_CRON_PREFIX}${recurring.job}`, recurring);
|
|
250
|
+
const existing = await this.listCrons(this.jobTask);
|
|
251
|
+
const seen = /* @__PURE__ */ new Set();
|
|
252
|
+
for (const cron of existing) {
|
|
253
|
+
if (!cron.name?.startsWith(JOB_CRON_PREFIX)) continue;
|
|
254
|
+
const recurring = wanted.get(cron.name);
|
|
255
|
+
if (recurring && recurring.cron === cron.cron && !seen.has(cron.name)) {
|
|
256
|
+
seen.add(cron.name);
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
await this.client.crons.delete(cron);
|
|
260
|
+
}
|
|
261
|
+
for (const [name, recurring] of wanted) {
|
|
262
|
+
if (seen.has(name)) continue;
|
|
263
|
+
await this.client.crons.create(this.jobTask, {
|
|
264
|
+
name,
|
|
265
|
+
expression: recurring.cron,
|
|
266
|
+
input: {
|
|
267
|
+
job: recurring.job,
|
|
268
|
+
key: recurring.job,
|
|
269
|
+
payload: null
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
this.logger?.info("hatchet job crons synced", { count: wanted.size });
|
|
274
|
+
}
|
|
275
|
+
async runJob(input, ctx) {
|
|
276
|
+
const handler = this.jobs.get(input.job);
|
|
277
|
+
if (!handler) {
|
|
278
|
+
this.logger?.error("scheduled job has no handler", {
|
|
279
|
+
job: input.job,
|
|
280
|
+
key: input.key
|
|
281
|
+
});
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
await handler.run(input.payload, {
|
|
285
|
+
id: ctx.workflowRunId(),
|
|
286
|
+
startedAt: /* @__PURE__ */ new Date()
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
async listCrons(workflow) {
|
|
290
|
+
const rows = [];
|
|
291
|
+
const limit = 100;
|
|
292
|
+
for (let offset = 0;; offset += limit) {
|
|
293
|
+
const batch = (await this.client.crons.list({
|
|
294
|
+
workflow,
|
|
295
|
+
limit,
|
|
296
|
+
offset
|
|
297
|
+
})).rows ?? [];
|
|
298
|
+
rows.push(...batch);
|
|
299
|
+
if (batch.length < limit) return rows;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
async runDelivery(input, ctx) {
|
|
303
|
+
if (!this.executor) throw new Error("HatchetAutomationEngine: notification-delivery invoked before register()");
|
|
304
|
+
const lastAttempt = (ctx?.retryCount?.() ?? 0) >= ACTION_RETRIES;
|
|
305
|
+
await this.executor.runDelivery(input, lastAttempt);
|
|
44
306
|
}
|
|
45
|
-
|
|
46
|
-
|
|
307
|
+
async runSchedule(input) {
|
|
308
|
+
if (!this.executor?.fireSchedule) throw new Error("HatchetAutomationEngine: automation-schedule invoked before register()");
|
|
309
|
+
await this.executor.fireSchedule(input.orgId, input.automationId, floorMinute(/* @__PURE__ */ new Date()));
|
|
310
|
+
}
|
|
311
|
+
async runAction(input, ctx) {
|
|
47
312
|
if (!this.executor) throw new Error("HatchetAutomationEngine: automation-run-action invoked before register()");
|
|
48
|
-
|
|
313
|
+
const { dispatch, index } = input;
|
|
314
|
+
try {
|
|
315
|
+
return await this.executor.runAction(dispatch, index);
|
|
316
|
+
} catch (err) {
|
|
317
|
+
if ((ctx?.retryCount?.() ?? ACTION_RETRIES) < ACTION_RETRIES) throw err;
|
|
318
|
+
if (!(err instanceof ActionFailedError)) this.logActionError(dispatch, index, err);
|
|
319
|
+
return {
|
|
320
|
+
index,
|
|
321
|
+
type: dispatch.actions[index].type,
|
|
322
|
+
status: "failed",
|
|
323
|
+
error: actionFailureMessage(err)
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
logActionError(dispatch, index, err) {
|
|
328
|
+
this.logger?.error("automation action failed", {
|
|
329
|
+
orgId: dispatch.orgId,
|
|
330
|
+
automationId: dispatch.automationId,
|
|
331
|
+
runId: dispatch.runId,
|
|
332
|
+
index,
|
|
333
|
+
err
|
|
334
|
+
});
|
|
49
335
|
}
|
|
50
336
|
async runDispatch(dispatch, ctx) {
|
|
51
337
|
if (!this.executor) throw new Error("HatchetAutomationEngine: automation-run invoked before register()");
|
|
52
338
|
const executor = this.executor;
|
|
53
339
|
const results = [];
|
|
54
|
-
for (let index = 0; index < dispatch.actions.length; index++)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
index,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
340
|
+
for (let index = 0; index < dispatch.actions.length; index++) {
|
|
341
|
+
let result;
|
|
342
|
+
try {
|
|
343
|
+
result = await ctx.spawnChild(this.actionTask, {
|
|
344
|
+
dispatch,
|
|
345
|
+
index
|
|
346
|
+
});
|
|
347
|
+
} catch (err) {
|
|
348
|
+
this.logActionError(dispatch, index, err);
|
|
349
|
+
result = {
|
|
350
|
+
index,
|
|
351
|
+
type: dispatch.actions[index].type,
|
|
352
|
+
status: "failed",
|
|
353
|
+
error: ACTION_FAILED
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
results.push(result);
|
|
357
|
+
if (result.status === "failed") break;
|
|
68
358
|
}
|
|
69
359
|
await executor.finalize(dispatch, results);
|
|
70
360
|
}
|
|
71
361
|
};
|
|
72
362
|
//#endregion
|
|
73
|
-
export { HatchetAutomationEngine };
|
|
363
|
+
export { HatchetAutomationEngine, cronName };
|
|
74
364
|
|
|
75
365
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["// Hatchet implementation of the AutomationEngine port — durable execution via\n// hatchet.run. The \"automation-run\" durable parent task spawns one child task\n// (\"automation-run-action\", retries: 3) per action through the durable event\n// log, stopping on the first exhausted child; the parent then finalizes once\n// with the results collected so far.\nimport type { Logger, AutomationDispatch, AutomationExecutor, AutomationEngine, AutomationActionResult } from \"@headless-lms/core/types\";\nimport { Hatchet } from \"@hatchet-dev/typescript-sdk\";\n\nconst WORKFLOW_NAME = \"automation-run\";\nconst ACTION_TASK_NAME = \"automation-run-action\";\nconst WORKER_NAME = \"automation-run-worker\";\n\ninterface ActionTaskInput {\n dispatch: AutomationDispatch;\n index: number;\n}\n\n/** Subset of `DurableContext` the parent task uses; hand-rolled so tests can fake it without a real Hatchet client. */\ninterface DurableCtxLike {\n spawnChild(task: unknown, input: ActionTaskInput, options?: Record<string, unknown>): Promise<AutomationActionResult>;\n}\n\ninterface RunnableTask<I> {\n runNoWait(input: I): Promise<unknown>;\n}\n\ninterface HatchetWorkerLike {\n start(): Promise<void>;\n stop(): Promise<void>;\n}\n\n/** Subset of the Hatchet v1 client this adapter calls; hand-rolled so tests can inject a fake client. */\nexport interface HatchetClientLike {\n task(opts: {\n name: string;\n retries?: number;\n fn: (input: ActionTaskInput, ctx: unknown) => Promise<AutomationActionResult> | AutomationActionResult;\n }): unknown;\n durableTask(opts: {\n name: string;\n fn: (input: AutomationDispatch, ctx: DurableCtxLike) => Promise<void>;\n }): RunnableTask<AutomationDispatch>;\n worker(name: string, opts: { workflows: any[] }): Promise<HatchetWorkerLike>; // eslint-disable-line @typescript-eslint/no-explicit-any -- bridges to the SDK's own workflow union type without importing it\n}\n\nexport class HatchetAutomationEngine implements AutomationEngine {\n private executor: AutomationExecutor | undefined;\n private worker: HatchetWorkerLike | undefined;\n private readonly actionTask: unknown;\n private readonly automationRunTask: RunnableTask<AutomationDispatch>;\n\n /** `client` defaults to a Hatchet client built from env (`HATCHET_CLIENT_TOKEN`); pass a fake for tests. */\n constructor(\n private readonly client: HatchetClientLike = new Hatchet(),\n private readonly logger?: Logger,\n ) {\n this.actionTask = this.client.task({\n name: ACTION_TASK_NAME,\n retries: 3,\n fn: async (input) => this.runAction(input),\n });\n\n this.automationRunTask = this.client.durableTask({\n name: WORKFLOW_NAME,\n fn: async (dispatch, ctx) => this.runDispatch(dispatch, ctx),\n });\n }\n\n register(executor: AutomationExecutor): void {\n this.executor = executor;\n }\n\n async dispatch(d: AutomationDispatch): Promise<void> {\n if (!this.executor) {\n throw new Error(\"HatchetAutomationEngine.dispatch called before register()\");\n }\n await this.automationRunTask.runNoWait(d);\n }\n\n async start(): Promise<void> {\n if (!this.executor) {\n throw new Error(\"HatchetAutomationEngine.start called before register()\");\n }\n this.worker = await this.client.worker(WORKER_NAME, {\n workflows: [this.actionTask, this.automationRunTask],\n });\n // worker.start() resolves only once the worker stops — run it in the background.\n void this.worker.start().catch((err: unknown) => {\n this.logger?.error(\"hatchet worker stopped unexpectedly\", { error: String(err) });\n });\n }\n\n async stop(): Promise<void> {\n await this.worker?.stop();\n this.worker = undefined;\n }\n\n /** Lets failures throw so Hatchet's per-task retry (retries: 3) applies before the parent gives up. */\n private async runAction(input: ActionTaskInput): Promise<AutomationActionResult> {\n if (!this.executor) {\n throw new Error(\"HatchetAutomationEngine: automation-run-action invoked before register()\");\n }\n return this.executor.runAction(input.dispatch, input.index);\n }\n\n private async runDispatch(dispatch: AutomationDispatch, ctx: DurableCtxLike): Promise<void> {\n if (!this.executor) {\n throw new Error(\"HatchetAutomationEngine: automation-run invoked before register()\");\n }\n const executor = this.executor;\n\n const results: AutomationActionResult[] = [];\n for (let index = 0; index < dispatch.actions.length; index++) {\n try {\n results.push(await ctx.spawnChild(this.actionTask, { dispatch, index }));\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n results.push({ index, type: dispatch.actions[index]!.type, status: \"failed\", error: message });\n break;\n }\n }\n\n // A crash before this task completes replays finalize on resume — finalize and its downstream events must tolerate duplicates.\n await executor.finalize(dispatch, results);\n }\n}\n"],"mappings":";;AAQA,MAAM,gBAAgB;AACtB,MAAM,mBAAmB;AACzB,MAAM,cAAc;AAmCpB,IAAa,0BAAb,MAAiE;CAQ5C;CACA;CARnB;CACA;CACA;CACA;;CAGA,YACE,SAA6C,IAAI,QAAQ,GACzD,QACA;EAFiB,KAAA,SAAA;EACA,KAAA,SAAA;EAEjB,KAAK,aAAa,KAAK,OAAO,KAAK;GACjC,MAAM;GACN,SAAS;GACT,IAAI,OAAO,UAAU,KAAK,UAAU,KAAK;EAC3C,CAAC;EAED,KAAK,oBAAoB,KAAK,OAAO,YAAY;GAC/C,MAAM;GACN,IAAI,OAAO,UAAU,QAAQ,KAAK,YAAY,UAAU,GAAG;EAC7D,CAAC;CACH;CAEA,SAAS,UAAoC;EAC3C,KAAK,WAAW;CAClB;CAEA,MAAM,SAAS,GAAsC;EACnD,IAAI,CAAC,KAAK,UACR,MAAM,IAAI,MAAM,2DAA2D;EAE7E,MAAM,KAAK,kBAAkB,UAAU,CAAC;CAC1C;CAEA,MAAM,QAAuB;EAC3B,IAAI,CAAC,KAAK,UACR,MAAM,IAAI,MAAM,wDAAwD;EAE1E,KAAK,SAAS,MAAM,KAAK,OAAO,OAAO,aAAa,EAClD,WAAW,CAAC,KAAK,YAAY,KAAK,iBAAiB,EACrD,CAAC;EAED,KAAU,OAAO,MAAM,CAAC,CAAC,OAAO,QAAiB;GAC/C,KAAK,QAAQ,MAAM,uCAAuC,EAAE,OAAO,OAAO,GAAG,EAAE,CAAC;EAClF,CAAC;CACH;CAEA,MAAM,OAAsB;EAC1B,MAAM,KAAK,QAAQ,KAAK;EACxB,KAAK,SAAS,KAAA;CAChB;;CAGA,MAAc,UAAU,OAAyD;EAC/E,IAAI,CAAC,KAAK,UACR,MAAM,IAAI,MAAM,0EAA0E;EAE5F,OAAO,KAAK,SAAS,UAAU,MAAM,UAAU,MAAM,KAAK;CAC5D;CAEA,MAAc,YAAY,UAA8B,KAAoC;EAC1F,IAAI,CAAC,KAAK,UACR,MAAM,IAAI,MAAM,mEAAmE;EAErF,MAAM,WAAW,KAAK;EAEtB,MAAM,UAAoC,CAAC;EAC3C,KAAK,IAAI,QAAQ,GAAG,QAAQ,SAAS,QAAQ,QAAQ,SACnD,IAAI;GACF,QAAQ,KAAK,MAAM,IAAI,WAAW,KAAK,YAAY;IAAE;IAAU;GAAM,CAAC,CAAC;EACzE,SAAS,KAAK;GACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;GAC/D,QAAQ,KAAK;IAAE;IAAO,MAAM,SAAS,QAAQ,MAAM,CAAE;IAAM,QAAQ;IAAU,OAAO;GAAQ,CAAC;GAC7F;EACF;EAIF,MAAM,SAAS,SAAS,UAAU,OAAO;CAC3C;AACF"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["Hatchet"],"sources":["../src/index.ts"],"sourcesContent":["import type {\n Logger,\n AutomationDispatch,\n AutomationExecutor,\n AutomationEngine,\n AutomationActionResult,\n DeliveryDispatch,\n ScheduledAutomation,\n JsonValue,\n} from \"@headless-lms/core/types\";\nimport type { RecurringJob, ScheduledJob, Scheduler } from \"@headless-lms/core/shared/ports\";\nimport { APIContracts } from \"@hatchet-dev/typescript-sdk/clients/rest/index.js\";\nimport {\n ConcurrencyLimitStrategy,\n HatchetClient as Hatchet,\n} from \"@hatchet-dev/typescript-sdk/v1/index.js\";\nimport type { LogConstructor } from \"@hatchet-dev/typescript-sdk/clients/hatchet-client/client-config.js\";\nimport {\n ACTION_FAILED,\n ActionFailedError,\n actionFailureMessage,\n} from \"@headless-lms/core/automations\";\n\nconst WORKFLOW_NAME = \"automation-run\";\nconst ACTION_TASK_NAME = \"automation-run-action\";\nconst SCHEDULE_TASK_NAME = \"automation-schedule\";\nconst JOB_TASK_NAME = \"scheduled-job\";\nconst DELIVERY_TASK_NAME = \"notification-delivery\";\nconst WORKER_NAME = \"automation-run-worker\";\nconst CRON_PREFIX = \"hlms-schedule:\";\nconst JOB_CRON_PREFIX = \"hlms-job:\";\nconst RECONCILE_EVERY_MS = 5 * 60_000;\nconst ACTION_RETRIES = 3;\nconst JOB_RETRIES = 3;\nconst RETRY_BACKOFF = { factor: 5, maxSeconds: 300 };\n\ninterface ActionTaskInput {\n dispatch: AutomationDispatch;\n index: number;\n}\n\ninterface ScheduleTaskInput {\n orgId: string;\n automationId: string;\n}\n\ninterface JobTaskInput {\n job: string;\n key: string;\n payload: JsonValue;\n}\n\ninterface TaskCtxLike {\n retryCount?(): number;\n}\n\ninterface JobCtxLike {\n workflowRunId(): string;\n}\n\ninterface DurableCtxLike {\n spawnChild(\n task: unknown,\n input: ActionTaskInput,\n options?: Record<string, unknown>,\n ): Promise<AutomationActionResult>;\n}\n\ninterface RunnableTask<I> {\n runNoWait(input: I): Promise<unknown>;\n}\n\ninterface HatchetWorkerLike {\n start(): Promise<void>;\n stop(): Promise<void>;\n}\n\nexport interface HatchetCronLike {\n metadata: { id: string };\n name?: string;\n cron: string;\n}\n\nexport interface HatchetCronClientLike {\n create(\n workflow: unknown,\n cron: { name: string; expression: string; input?: Record<string, unknown> },\n ): Promise<HatchetCronLike>;\n delete(cron: string | HatchetCronLike): Promise<void>;\n list(query: { workflow?: unknown; limit?: number; offset?: number }): Promise<{\n rows?: HatchetCronLike[];\n }>;\n}\n\nexport interface HatchetScheduledRunLike {\n metadata: { id: string };\n}\n\nexport interface HatchetScheduledClientLike {\n list(query: {\n workflow?: unknown;\n additionalMetadata?: string[];\n statuses?: APIContracts.ScheduledRunStatus[];\n }): Promise<{ rows?: HatchetScheduledRunLike[] }>;\n update(scheduledRun: string, update: { triggerAt: Date }): Promise<unknown>;\n bulkDelete(opts: {\n scheduledRuns?: string[];\n filter?: { additionalMetadata?: string[] };\n }): Promise<unknown>;\n}\n\nexport interface HatchetTaskLike<I> {\n runNoWait(input: I): Promise<unknown>;\n schedule(\n enqueueAt: Date,\n input: I,\n options?: { additionalMetadata?: Record<string, string> },\n ): Promise<unknown>;\n}\n\nexport interface HatchetClientLike {\n task<I>(opts: {\n name: string;\n retries?: number;\n backoff?: { factor?: number; maxSeconds?: number };\n concurrency?: {\n expression: string;\n maxRuns?: number;\n limitStrategy?: ConcurrencyLimitStrategy;\n };\n fn: (input: I, ctx: unknown) => Promise<unknown> | unknown;\n }): HatchetTaskLike<I>;\n durableTask(opts: {\n name: string;\n fn: (input: AutomationDispatch, ctx: DurableCtxLike) => Promise<void>;\n }): RunnableTask<AutomationDispatch>;\n worker(\n name: string,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- bridges to the SDK's own workflow union type without importing it\n opts: { workflows: any[]; handleKill?: boolean },\n ): Promise<HatchetWorkerLike>;\n crons: HatchetCronClientLike;\n scheduled: HatchetScheduledClientLike;\n}\n\nfunction floorMinute(at: Date): Date {\n const copy = new Date(at);\n copy.setUTCSeconds(0, 0);\n return copy;\n}\n\nexport function cronName(schedule: ScheduledAutomation): string {\n return `${CRON_PREFIX}${schedule.orgId}:${schedule.automationId}:${schedule.cron}`;\n}\n\nfunction jobMetadata(job: string, key: string): { job: string; key: string } {\n return { job: encodeURIComponent(job), key: encodeURIComponent(key) };\n}\n\nfunction jobMetadataFilter(job: string, key: string): string[] {\n const metadata = jobMetadata(job, key);\n return [`job:${metadata.job}`, `key:${metadata.key}`];\n}\n\nfunction sdkLogger(logger: Logger): LogConstructor {\n return (context) => {\n const log = logger.child({ hatchet: context });\n const failure = (error?: Error, extra?: object) => ({ ...extra, ...(error && { err: error }) });\n return {\n debug: (message, extra) => log.debug(message, extra),\n info: (message, extra) => log.info(message, extra),\n green: (message, extra) => log.info(message, extra),\n warn: (message, error, extra) => log.warn(message, failure(error, extra)),\n error: (message, error, extra) => log.error(message, failure(error, extra)),\n util: (key, message, extra) => log.debug(message, { ...extra, key }),\n };\n };\n}\n\nexport class HatchetAutomationEngine implements AutomationEngine, Scheduler {\n private executor: AutomationExecutor | undefined;\n private jobs = new Map<string, ScheduledJob>();\n private recurring: RecurringJob[] = [];\n private worker: HatchetWorkerLike | undefined;\n private syncing: Promise<void> | undefined;\n private syncingJobs: Promise<void> | undefined;\n private reconcileTimer: ReturnType<typeof setInterval> | undefined;\n private readonly actionTask: unknown;\n private readonly scheduleTask: unknown;\n private readonly jobTask: HatchetTaskLike<JobTaskInput>;\n private readonly deliveryTask: HatchetTaskLike<DeliveryDispatch>;\n private readonly automationRunTask: RunnableTask<AutomationDispatch>;\n\n private readonly client: HatchetClientLike;\n\n constructor(\n client?: HatchetClientLike,\n private readonly logger?: Logger,\n private readonly reconcileEveryMs: number = RECONCILE_EVERY_MS,\n ) {\n this.client =\n client ??\n (new Hatchet(logger ? { logger: sdkLogger(logger) } : {}) as unknown as HatchetClientLike);\n this.actionTask = this.client.task<ActionTaskInput>({\n name: ACTION_TASK_NAME,\n retries: ACTION_RETRIES,\n fn: async (input, ctx) => this.runAction(input, ctx as TaskCtxLike | undefined),\n });\n\n this.scheduleTask = this.client.task<ScheduleTaskInput>({\n name: SCHEDULE_TASK_NAME,\n fn: async (input) => this.runSchedule(input),\n });\n\n this.jobTask = this.client.task<JobTaskInput>({\n name: JOB_TASK_NAME,\n retries: JOB_RETRIES,\n backoff: RETRY_BACKOFF,\n concurrency: {\n expression: \"input.job + ':' + input.key\",\n maxRuns: 1,\n limitStrategy: ConcurrencyLimitStrategy.GROUP_ROUND_ROBIN,\n },\n fn: async (input, ctx) => this.runJob(input, ctx as JobCtxLike),\n });\n\n this.deliveryTask = this.client.task<DeliveryDispatch>({\n name: DELIVERY_TASK_NAME,\n retries: ACTION_RETRIES,\n backoff: RETRY_BACKOFF,\n concurrency: {\n expression: \"input.deliveryId\",\n maxRuns: 1,\n limitStrategy: ConcurrencyLimitStrategy.GROUP_ROUND_ROBIN,\n },\n fn: async (input, ctx) => this.runDelivery(input, ctx as TaskCtxLike | undefined),\n });\n\n this.automationRunTask = this.client.durableTask({\n name: WORKFLOW_NAME,\n fn: async (dispatch, ctx) => this.runDispatch(dispatch, ctx),\n });\n }\n\n register(executor: AutomationExecutor): void {\n this.executor = executor;\n }\n\n registerJobs(handlers: ScheduledJob[], recurring: RecurringJob[]): void {\n this.jobs = new Map(handlers.map((handler) => [handler.job, handler]));\n this.recurring = [...recurring];\n }\n\n async at(job: string, runAt: Date, key: string, payload: JsonValue): Promise<void> {\n const [found, ...extra] = await this.pendingRuns(job, key);\n if (!found) {\n await this.scheduleJob(job, runAt, key, payload);\n return;\n }\n try {\n await this.client.scheduled.update(found.metadata.id, { triggerAt: runAt });\n } catch (err) {\n const pending = await this.pendingRuns(job, key);\n if (pending.some((run) => run.metadata.id === found.metadata.id)) {\n throw err;\n }\n await this.scheduleJob(job, runAt, key, payload);\n }\n if (extra.length > 0) {\n await this.client.scheduled.bulkDelete({\n scheduledRuns: extra.map((run) => run.metadata.id),\n });\n }\n }\n\n private async pendingRuns(job: string, key: string): Promise<HatchetScheduledRunLike[]> {\n const page = await this.client.scheduled.list({\n workflow: this.jobTask,\n additionalMetadata: jobMetadataFilter(job, key),\n statuses: [APIContracts.ScheduledRunStatus.SCHEDULED],\n });\n return page.rows ?? [];\n }\n\n private async scheduleJob(\n job: string,\n runAt: Date,\n key: string,\n payload: JsonValue,\n ): Promise<void> {\n await this.jobTask.schedule(\n runAt,\n { job, key, payload },\n { additionalMetadata: jobMetadata(job, key) },\n );\n }\n\n async cancel(job: string, key: string): Promise<void> {\n await this.client.scheduled.bulkDelete({\n filter: { additionalMetadata: jobMetadataFilter(job, key) },\n });\n }\n\n async dispatch(d: AutomationDispatch): Promise<void> {\n if (!this.executor) {\n throw new Error(\"HatchetAutomationEngine.dispatch called before register()\");\n }\n await this.automationRunTask.runNoWait(d);\n }\n\n async deliver(d: DeliveryDispatch): Promise<void> {\n if (!this.executor) {\n throw new Error(\"HatchetAutomationEngine.deliver called before register()\");\n }\n await this.deliveryTask.runNoWait(d);\n }\n\n async start(): Promise<void> {\n if (!this.executor) {\n throw new Error(\"HatchetAutomationEngine.start called before register()\");\n }\n this.worker = await this.client.worker(WORKER_NAME, {\n handleKill: false,\n workflows: [\n this.actionTask,\n this.automationRunTask,\n this.scheduleTask,\n this.jobTask,\n this.deliveryTask,\n ],\n });\n await this.reconcileJobsLogged();\n void this.worker.start().catch((err: unknown) => {\n this.logger?.error(\"hatchet worker stopped unexpectedly\", { err });\n });\n if (this.reconcileTimer) {\n clearInterval(this.reconcileTimer);\n }\n this.reconcileTimer = setInterval(() => void this.reconcileAll(), this.reconcileEveryMs);\n this.reconcileTimer.unref?.();\n await this.syncLogged();\n }\n\n async stop(): Promise<void> {\n if (this.reconcileTimer) {\n clearInterval(this.reconcileTimer);\n }\n this.reconcileTimer = undefined;\n if (!this.worker) {\n return;\n }\n await this.worker.stop();\n this.worker = undefined;\n this.logger?.info(\"hatchet worker stopped\");\n }\n\n private async reconcileAll(): Promise<void> {\n await this.reconcileJobsLogged();\n await this.syncLogged();\n }\n\n private async reconcileJobsLogged(): Promise<void> {\n if (this.syncingJobs) {\n return this.syncingJobs;\n }\n this.syncingJobs = this.reconcileJobs()\n .catch((err: unknown) => {\n this.logger?.error(\"hatchet job crons not reconciled\", { err });\n })\n .finally(() => {\n this.syncingJobs = undefined;\n });\n return this.syncingJobs;\n }\n\n private async syncLogged(): Promise<void> {\n try {\n await this.syncSchedules();\n } catch (err) {\n this.logger?.error(\"hatchet schedules not reconciled\", { err });\n }\n }\n\n async syncSchedules(): Promise<void> {\n if (this.syncing) {\n await this.syncing;\n return this.syncSchedules();\n }\n this.syncing = this.reconcile().finally(() => {\n this.syncing = undefined;\n });\n await this.syncing;\n }\n\n private async reconcile(): Promise<void> {\n if (!this.executor?.listSchedules) {\n return;\n }\n const wanted = new Map<string, ScheduledAutomation>();\n for (const schedule of await this.executor.listSchedules()) {\n wanted.set(cronName(schedule), schedule);\n }\n const existing = await this.listCrons(this.scheduleTask);\n const seen = new Set<string>();\n for (const cron of existing) {\n if (!cron.name?.startsWith(CRON_PREFIX)) {\n continue;\n }\n const schedule = wanted.get(cron.name);\n if (schedule && schedule.cron === cron.cron && !seen.has(cron.name)) {\n seen.add(cron.name);\n continue;\n }\n await this.client.crons.delete(cron);\n }\n for (const [name, schedule] of wanted) {\n if (seen.has(name)) {\n continue;\n }\n await this.client.crons.create(this.scheduleTask, {\n name,\n expression: schedule.cron,\n input: { orgId: schedule.orgId, automationId: schedule.automationId },\n });\n }\n this.logger?.info(\"hatchet schedules synced\", { count: wanted.size });\n }\n\n private async reconcileJobs(): Promise<void> {\n const wanted = new Map<string, RecurringJob>();\n for (const recurring of this.recurring) {\n wanted.set(`${JOB_CRON_PREFIX}${recurring.job}`, recurring);\n }\n const existing = await this.listCrons(this.jobTask);\n const seen = new Set<string>();\n for (const cron of existing) {\n if (!cron.name?.startsWith(JOB_CRON_PREFIX)) {\n continue;\n }\n const recurring = wanted.get(cron.name);\n if (recurring && recurring.cron === cron.cron && !seen.has(cron.name)) {\n seen.add(cron.name);\n continue;\n }\n await this.client.crons.delete(cron);\n }\n for (const [name, recurring] of wanted) {\n if (seen.has(name)) {\n continue;\n }\n await this.client.crons.create(this.jobTask, {\n name,\n expression: recurring.cron,\n input: { job: recurring.job, key: recurring.job, payload: null },\n });\n }\n this.logger?.info(\"hatchet job crons synced\", { count: wanted.size });\n }\n\n private async runJob(input: JobTaskInput, ctx: JobCtxLike): Promise<void> {\n const handler = this.jobs.get(input.job);\n if (!handler) {\n this.logger?.error(\"scheduled job has no handler\", { job: input.job, key: input.key });\n return;\n }\n await handler.run(input.payload, { id: ctx.workflowRunId(), startedAt: new Date() });\n }\n\n private async listCrons(workflow: unknown): Promise<HatchetCronLike[]> {\n const rows: HatchetCronLike[] = [];\n const limit = 100;\n for (let offset = 0; ; offset += limit) {\n const page = await this.client.crons.list({ workflow, limit, offset });\n const batch = page.rows ?? [];\n rows.push(...batch);\n if (batch.length < limit) {\n return rows;\n }\n }\n }\n\n private async runDelivery(input: DeliveryDispatch, ctx: TaskCtxLike | undefined): Promise<void> {\n if (!this.executor) {\n throw new Error(\"HatchetAutomationEngine: notification-delivery invoked before register()\");\n }\n const lastAttempt = (ctx?.retryCount?.() ?? 0) >= ACTION_RETRIES;\n await this.executor.runDelivery(input, lastAttempt);\n }\n\n private async runSchedule(input: ScheduleTaskInput): Promise<void> {\n if (!this.executor?.fireSchedule) {\n throw new Error(\"HatchetAutomationEngine: automation-schedule invoked before register()\");\n }\n await this.executor.fireSchedule(input.orgId, input.automationId, floorMinute(new Date()));\n }\n\n private async runAction(\n input: ActionTaskInput,\n ctx: TaskCtxLike | undefined,\n ): Promise<AutomationActionResult> {\n if (!this.executor) {\n throw new Error(\"HatchetAutomationEngine: automation-run-action invoked before register()\");\n }\n const { dispatch, index } = input;\n try {\n return await this.executor.runAction(dispatch, index);\n } catch (err) {\n if ((ctx?.retryCount?.() ?? ACTION_RETRIES) < ACTION_RETRIES) {\n throw err;\n }\n if (!(err instanceof ActionFailedError)) {\n this.logActionError(dispatch, index, err);\n }\n return {\n index,\n type: dispatch.actions[index]!.type,\n status: \"failed\",\n error: actionFailureMessage(err),\n };\n }\n }\n\n private logActionError(dispatch: AutomationDispatch, index: number, err: unknown): void {\n this.logger?.error(\"automation action failed\", {\n orgId: dispatch.orgId,\n automationId: dispatch.automationId,\n runId: dispatch.runId,\n index,\n err,\n });\n }\n\n private async runDispatch(dispatch: AutomationDispatch, ctx: DurableCtxLike): Promise<void> {\n if (!this.executor) {\n throw new Error(\"HatchetAutomationEngine: automation-run invoked before register()\");\n }\n const executor = this.executor;\n\n const results: AutomationActionResult[] = [];\n for (let index = 0; index < dispatch.actions.length; index++) {\n let result: AutomationActionResult;\n try {\n result = await ctx.spawnChild(this.actionTask, { dispatch, index });\n } catch (err) {\n this.logActionError(dispatch, index, err);\n result = {\n index,\n type: dispatch.actions[index]!.type,\n status: \"failed\",\n error: ACTION_FAILED,\n };\n }\n results.push(result);\n if (result.status === \"failed\") {\n break;\n }\n }\n\n await executor.finalize(dispatch, results);\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@headless-lms/adapter-workflow-hatchet",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@hatchet-dev/typescript-sdk": "^1.28.0",
|
|
15
|
-
"@headless-lms/
|
|
15
|
+
"@headless-lms/adapter-defaults": "0.5.1",
|
|
16
|
+
"@headless-lms/core": "0.6.0"
|
|
16
17
|
},
|
|
17
18
|
"devDependencies": {
|
|
18
19
|
"@types/node": "^22.10.2",
|