@amalgm/automations 0.2.1 → 0.2.3
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/AXIOMS.md +39 -17
- package/PURPOSE.md +26 -11
- package/README.md +33 -4
- package/dist/host/config.d.ts +2 -0
- package/dist/host/config.js +8 -0
- package/dist/host/main.js +10 -5
- package/dist/host/server.d.ts +2 -0
- package/dist/host/server.js +25 -7
- package/dist/src/automations.d.ts +15 -9
- package/dist/src/automations.js +33 -76
- package/dist/src/cli.d.ts +1 -1
- package/dist/src/cli.js +5 -2
- package/dist/src/client.d.ts +1 -0
- package/dist/src/client.js +4 -2
- package/dist/src/contract.d.ts +11 -31
- package/dist/src/crud/context.d.ts +3 -1
- package/dist/src/crud/context.js +2 -1
- package/dist/src/crud/repository.d.ts +17 -6
- package/dist/src/crud/runs.d.ts +1 -1
- package/dist/src/crud/runs.js +24 -1
- package/dist/src/crud/triggers.js +18 -9
- package/dist/src/crud.d.ts +4 -2
- package/dist/src/crud.js +5 -2
- package/dist/src/events-http.d.ts +3 -5
- package/dist/src/events-http.js +22 -26
- package/dist/src/executor.d.ts +12 -3
- package/dist/src/executor.js +163 -44
- package/dist/src/http.js +8 -1
- package/dist/src/index.d.ts +6 -4
- package/dist/src/index.js +6 -4
- package/dist/src/machine-client.d.ts +1 -0
- package/dist/src/machine-client.js +2 -0
- package/dist/src/machine.d.ts +2 -1
- package/dist/src/machine.js +6 -1
- package/dist/src/mcp.d.ts +3 -0
- package/dist/src/mcp.js +61 -50
- package/dist/src/plan.d.ts +3 -0
- package/dist/src/plan.js +48 -0
- package/dist/src/run-contract.d.ts +52 -0
- package/dist/src/run-contract.js +1 -0
- package/dist/src/run-journal.d.ts +8 -0
- package/dist/src/run-journal.js +56 -0
- package/dist/src/runner.d.ts +14 -0
- package/dist/src/runner.js +70 -0
- package/dist/src/schema.d.ts +31 -15
- package/dist/src/schema.js +27 -4
- package/dist/src/supabase-crud/mappers.d.ts +3 -2
- package/dist/src/supabase-crud/mappers.js +5 -0
- package/dist/src/supabase-crud/rows.d.ts +3 -0
- package/dist/src/supabase-crud/workflow-runs.d.ts +1 -1
- package/dist/src/supabase-crud/workflow-runs.js +10 -0
- package/dist/src/supabase-machine.js +2 -0
- package/dist/src/supabase-store.d.ts +3 -6
- package/dist/src/supabase-store.js +13 -34
- package/dist/src/tool-surface.d.ts +47 -0
- package/dist/src/tool-surface.js +126 -0
- package/dist/src/types.d.ts +8 -24
- package/dist/src/webhook.d.ts +15 -1
- package/dist/src/webhook.js +86 -0
- package/package.json +3 -3
- package/skills/automations/SKILL.md +29 -16
- package/supabase/migrations/20260830010000_durable_step_retries.sql +332 -0
- package/supabase/migrations/20260831010000_public_webhook_endpoints.sql +287 -0
- package/supabase/migrations/20260831020000_manual_run_admission.sql +68 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AutomationRunJournal, AutomationStepRun, Json, ToolActionStep } from './contract.js';
|
|
2
|
+
export declare function emptyJournal(): AutomationRunJournal;
|
|
3
|
+
export declare function runJournal(value: Json | undefined, plan: readonly ToolActionStep[]): AutomationRunJournal;
|
|
4
|
+
export declare function completed(journal: AutomationRunJournal, stepId: string): boolean;
|
|
5
|
+
export declare function stepAttempt(journal: AutomationRunJournal, stepId: string): number;
|
|
6
|
+
export declare function putStep(journal: AutomationRunJournal, step: AutomationStepRun): AutomationRunJournal;
|
|
7
|
+
/** Journals are constructed from JSON fields only; this is the persistence boundary. */
|
|
8
|
+
export declare function journalJson(journal: AutomationRunJournal): Json;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export function emptyJournal() {
|
|
2
|
+
return { version: 1, steps: [] };
|
|
3
|
+
}
|
|
4
|
+
export function runJournal(value, plan) {
|
|
5
|
+
if (value === undefined)
|
|
6
|
+
return emptyJournal();
|
|
7
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
8
|
+
throw new Error('Run step journal is invalid');
|
|
9
|
+
const source = value;
|
|
10
|
+
if (source.version !== 1 || !Array.isArray(source.steps))
|
|
11
|
+
throw new Error('Run step journal is invalid');
|
|
12
|
+
const planIds = new Set(plan.map(({ id }) => id));
|
|
13
|
+
const steps = source.steps.map(runStep);
|
|
14
|
+
const ids = new Set();
|
|
15
|
+
for (const step of steps) {
|
|
16
|
+
if (!planIds.has(step.id))
|
|
17
|
+
throw new Error(`Run step is not in the immutable plan: ${step.id}`);
|
|
18
|
+
if (ids.has(step.id))
|
|
19
|
+
throw new Error(`Run step journal has a duplicate id: ${step.id}`);
|
|
20
|
+
ids.add(step.id);
|
|
21
|
+
}
|
|
22
|
+
return { version: 1, steps };
|
|
23
|
+
}
|
|
24
|
+
export function completed(journal, stepId) {
|
|
25
|
+
return journal.steps.some((step) => step.id === stepId && step.status === 'completed');
|
|
26
|
+
}
|
|
27
|
+
export function stepAttempt(journal, stepId) {
|
|
28
|
+
return journal.steps.find((step) => step.id === stepId)?.attempts ?? 0;
|
|
29
|
+
}
|
|
30
|
+
export function putStep(journal, step) {
|
|
31
|
+
const index = journal.steps.findIndex(({ id }) => id === step.id);
|
|
32
|
+
const steps = [...journal.steps];
|
|
33
|
+
if (index === -1)
|
|
34
|
+
steps.push(step);
|
|
35
|
+
else
|
|
36
|
+
steps[index] = step;
|
|
37
|
+
return { version: 1, steps };
|
|
38
|
+
}
|
|
39
|
+
/** Journals are constructed from JSON fields only; this is the persistence boundary. */
|
|
40
|
+
export function journalJson(journal) {
|
|
41
|
+
return journal;
|
|
42
|
+
}
|
|
43
|
+
function runStep(value) {
|
|
44
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
45
|
+
throw new Error('Run step is invalid');
|
|
46
|
+
const item = value;
|
|
47
|
+
if (typeof item.id !== 'string' || !item.id)
|
|
48
|
+
throw new Error('Run step id is invalid');
|
|
49
|
+
if (item.status !== 'running' && item.status !== 'completed' && item.status !== 'failed') {
|
|
50
|
+
throw new Error(`Run step status is invalid: ${item.id}`);
|
|
51
|
+
}
|
|
52
|
+
if (!Number.isInteger(item.attempts) || Number(item.attempts) < 1 || typeof item.startedAt !== 'string') {
|
|
53
|
+
throw new Error(`Run step attempt is invalid: ${item.id}`);
|
|
54
|
+
}
|
|
55
|
+
return item;
|
|
56
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ClaimedAutomationRun, MachineRuns } from './machine.js';
|
|
2
|
+
export interface AutomationMachineRunnerOptions {
|
|
3
|
+
readonly runs: MachineRuns;
|
|
4
|
+
readonly execute: (run: ClaimedAutomationRun) => Promise<void>;
|
|
5
|
+
readonly batchSize?: number;
|
|
6
|
+
readonly leaseSeconds?: number;
|
|
7
|
+
readonly pollIntervalMs?: number;
|
|
8
|
+
readonly maxBackoffMs?: number;
|
|
9
|
+
readonly log?: (event: string, details?: Readonly<Record<string, unknown>>) => void;
|
|
10
|
+
}
|
|
11
|
+
export declare function createAutomationMachineRunner(options: AutomationMachineRunnerOptions): Readonly<{
|
|
12
|
+
start(): void;
|
|
13
|
+
close(): Promise<void>;
|
|
14
|
+
}>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export function createAutomationMachineRunner(options) {
|
|
2
|
+
const batchSize = integer(options.batchSize ?? 8, 1, 20, 'batchSize');
|
|
3
|
+
const leaseSeconds = integer(options.leaseSeconds ?? 60, 30, 900, 'leaseSeconds');
|
|
4
|
+
const pollIntervalMs = integer(options.pollIntervalMs ?? 1_000, 1, 60_000, 'pollIntervalMs');
|
|
5
|
+
const maxBackoffMs = integer(options.maxBackoffMs ?? Math.max(30_000, pollIntervalMs), pollIntervalMs, 300_000, 'maxBackoffMs');
|
|
6
|
+
let active = null;
|
|
7
|
+
let timer = null;
|
|
8
|
+
let stopped = true;
|
|
9
|
+
let failures = 0;
|
|
10
|
+
let nextDelay = pollIntervalMs;
|
|
11
|
+
const schedule = (delay) => {
|
|
12
|
+
if (stopped)
|
|
13
|
+
return;
|
|
14
|
+
timer = setTimeout(drain, delay);
|
|
15
|
+
timer.unref?.();
|
|
16
|
+
};
|
|
17
|
+
const drain = () => {
|
|
18
|
+
if (stopped || active)
|
|
19
|
+
return active;
|
|
20
|
+
active = options.runs.claim({ limit: batchSize, leaseSeconds })
|
|
21
|
+
.then(async (runs) => {
|
|
22
|
+
failures = 0;
|
|
23
|
+
if (runs.length)
|
|
24
|
+
options.log?.('automations.claimed', { count: runs.length });
|
|
25
|
+
const settled = await Promise.allSettled(runs.map(options.execute));
|
|
26
|
+
settled.forEach((result) => {
|
|
27
|
+
if (result.status === 'rejected') {
|
|
28
|
+
options.log?.('automations.run.failed', { error: safe(result.reason) });
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
nextDelay = runs.length === batchSize ? 0 : pollIntervalMs;
|
|
32
|
+
})
|
|
33
|
+
.catch((error) => {
|
|
34
|
+
failures += 1;
|
|
35
|
+
const delayMs = Math.min(maxBackoffMs, pollIntervalMs * (2 ** Math.min(failures - 1, 10)));
|
|
36
|
+
options.log?.('automations.poll.failed', { error: safe(error), retryInMs: delayMs });
|
|
37
|
+
nextDelay = delayMs;
|
|
38
|
+
})
|
|
39
|
+
.finally(() => {
|
|
40
|
+
active = null;
|
|
41
|
+
schedule(nextDelay);
|
|
42
|
+
});
|
|
43
|
+
return active;
|
|
44
|
+
};
|
|
45
|
+
return Object.freeze({
|
|
46
|
+
start() {
|
|
47
|
+
if (!stopped)
|
|
48
|
+
return;
|
|
49
|
+
stopped = false;
|
|
50
|
+
failures = 0;
|
|
51
|
+
void drain();
|
|
52
|
+
},
|
|
53
|
+
async close() {
|
|
54
|
+
stopped = true;
|
|
55
|
+
if (timer)
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
timer = null;
|
|
58
|
+
await active;
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function integer(value, minimum, maximum, name) {
|
|
63
|
+
if (!Number.isInteger(value) || value < minimum || value > maximum) {
|
|
64
|
+
throw new Error(`${name} must be an integer from ${minimum} to ${maximum}`);
|
|
65
|
+
}
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
function safe(error) {
|
|
69
|
+
return (error instanceof Error ? error.message : String(error)).replace(/[\r\n]+/g, ' ').slice(0, 500);
|
|
70
|
+
}
|
package/dist/src/schema.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod/v3';
|
|
2
|
-
import type { CreateAutomation, CreateScheduleTrigger, CreateWebhookTrigger, CreateWorkflow, Json, ListAutomations, ListRuns, PageQuery, UpdateAutomation, UpdateScheduleTrigger, UpdateWebhookTrigger, UpdateWorkflow } from './contract.js';
|
|
2
|
+
import type { CreateAutomation, CreateScheduleTrigger, CreateWebhookTrigger, CreateWorkflow, Json, ListAutomations, ListRuns, PageQuery, RunAutomationNow, UpdateAutomation, UpdateScheduleTrigger, UpdateWebhookTrigger, UpdateWorkflow } from './contract.js';
|
|
3
3
|
export declare const identifierSchema: z.ZodString;
|
|
4
4
|
export declare const pageQuerySchema: z.ZodObject<{
|
|
5
5
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
@@ -122,46 +122,51 @@ export declare const createWebhookTriggerSchema: z.ZodObject<{
|
|
|
122
122
|
id: z.ZodOptional<z.ZodString>;
|
|
123
123
|
source: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
124
124
|
event: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
125
|
-
secret: z.ZodString
|
|
125
|
+
secret: z.ZodOptional<z.ZodString>;
|
|
126
126
|
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
127
127
|
}, "strict", z.ZodTypeAny, {
|
|
128
|
-
secret: string;
|
|
129
128
|
enabled?: boolean | undefined;
|
|
130
129
|
event?: string | undefined;
|
|
131
|
-
|
|
130
|
+
secret?: string | undefined;
|
|
132
131
|
id?: string | undefined;
|
|
132
|
+
source?: string | undefined;
|
|
133
133
|
}, {
|
|
134
|
-
secret: string;
|
|
135
134
|
enabled?: boolean | undefined;
|
|
136
135
|
event?: string | undefined;
|
|
137
|
-
|
|
136
|
+
secret?: string | undefined;
|
|
138
137
|
id?: string | undefined;
|
|
138
|
+
source?: string | undefined;
|
|
139
139
|
}>;
|
|
140
140
|
export declare const updateWebhookTriggerSchema: z.ZodEffects<z.ZodObject<{
|
|
141
141
|
source: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
142
142
|
event: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
143
|
-
secret: z.ZodOptional<z.ZodString
|
|
143
|
+
secret: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
144
|
+
rotateUrl: z.ZodOptional<z.ZodBoolean>;
|
|
144
145
|
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
145
146
|
}, "strict", z.ZodTypeAny, {
|
|
146
147
|
enabled?: boolean | undefined;
|
|
147
148
|
event?: string | undefined;
|
|
148
|
-
secret?: string | undefined;
|
|
149
|
+
secret?: string | null | undefined;
|
|
149
150
|
source?: string | undefined;
|
|
151
|
+
rotateUrl?: boolean | undefined;
|
|
150
152
|
}, {
|
|
151
153
|
enabled?: boolean | undefined;
|
|
152
154
|
event?: string | undefined;
|
|
153
|
-
secret?: string | undefined;
|
|
155
|
+
secret?: string | null | undefined;
|
|
154
156
|
source?: string | undefined;
|
|
157
|
+
rotateUrl?: boolean | undefined;
|
|
155
158
|
}>, {
|
|
156
159
|
enabled?: boolean | undefined;
|
|
157
160
|
event?: string | undefined;
|
|
158
|
-
secret?: string | undefined;
|
|
161
|
+
secret?: string | null | undefined;
|
|
159
162
|
source?: string | undefined;
|
|
163
|
+
rotateUrl?: boolean | undefined;
|
|
160
164
|
}, {
|
|
161
165
|
enabled?: boolean | undefined;
|
|
162
166
|
event?: string | undefined;
|
|
163
|
-
secret?: string | undefined;
|
|
167
|
+
secret?: string | null | undefined;
|
|
164
168
|
source?: string | undefined;
|
|
169
|
+
rotateUrl?: boolean | undefined;
|
|
165
170
|
}>;
|
|
166
171
|
export declare const createWorkflowSchema: z.ZodObject<{
|
|
167
172
|
id: z.ZodOptional<z.ZodString>;
|
|
@@ -193,26 +198,26 @@ export declare const updateWorkflowSchema: z.ZodEffects<z.ZodObject<{
|
|
|
193
198
|
limits: z.ZodOptional<z.ZodNullable<z.ZodType<Json, z.ZodTypeDef, Json>>>;
|
|
194
199
|
}, "strict", z.ZodTypeAny, {
|
|
195
200
|
name?: string | null | undefined;
|
|
196
|
-
script?: string | undefined;
|
|
197
201
|
compiled?: Json | undefined;
|
|
202
|
+
script?: string | undefined;
|
|
198
203
|
allowlist?: Json | undefined;
|
|
199
204
|
limits?: Json | undefined;
|
|
200
205
|
}, {
|
|
201
206
|
name?: string | null | undefined;
|
|
202
|
-
script?: string | undefined;
|
|
203
207
|
compiled?: Json | undefined;
|
|
208
|
+
script?: string | undefined;
|
|
204
209
|
allowlist?: Json | undefined;
|
|
205
210
|
limits?: Json | undefined;
|
|
206
211
|
}>, {
|
|
207
212
|
name?: string | null | undefined;
|
|
208
|
-
script?: string | undefined;
|
|
209
213
|
compiled?: Json | undefined;
|
|
214
|
+
script?: string | undefined;
|
|
210
215
|
allowlist?: Json | undefined;
|
|
211
216
|
limits?: Json | undefined;
|
|
212
217
|
}, {
|
|
213
218
|
name?: string | null | undefined;
|
|
214
|
-
script?: string | undefined;
|
|
215
219
|
compiled?: Json | undefined;
|
|
220
|
+
script?: string | undefined;
|
|
216
221
|
allowlist?: Json | undefined;
|
|
217
222
|
limits?: Json | undefined;
|
|
218
223
|
}>;
|
|
@@ -230,6 +235,16 @@ export declare const listRunsSchema: z.ZodObject<{
|
|
|
230
235
|
limit?: number | undefined;
|
|
231
236
|
offset?: number | undefined;
|
|
232
237
|
}>;
|
|
238
|
+
export declare const runAutomationNowSchema: z.ZodObject<{
|
|
239
|
+
input: z.ZodOptional<z.ZodType<Json, z.ZodTypeDef, Json>>;
|
|
240
|
+
idempotencyKey: z.ZodOptional<z.ZodString>;
|
|
241
|
+
}, "strict", z.ZodTypeAny, {
|
|
242
|
+
input?: Json | undefined;
|
|
243
|
+
idempotencyKey?: string | undefined;
|
|
244
|
+
}, {
|
|
245
|
+
input?: Json | undefined;
|
|
246
|
+
idempotencyKey?: string | undefined;
|
|
247
|
+
}>;
|
|
233
248
|
export declare function parseCreateAutomation(value: unknown): CreateAutomation;
|
|
234
249
|
export declare function parseUpdateAutomation(value: unknown): UpdateAutomation;
|
|
235
250
|
export declare function parseListAutomations(value: unknown): ListAutomations;
|
|
@@ -241,4 +256,5 @@ export declare function parseCreateWorkflow(value: unknown): CreateWorkflow;
|
|
|
241
256
|
export declare function parseUpdateWorkflow(value: unknown): UpdateWorkflow;
|
|
242
257
|
export declare function parsePageQuery(value: unknown): PageQuery;
|
|
243
258
|
export declare function parseListRuns(value: unknown): ListRuns;
|
|
259
|
+
export declare function parseRunAutomationNow(value: unknown): RunAutomationNow;
|
|
244
260
|
export declare function parse<T>(schema: z.ZodType<T>, value: unknown): T;
|
package/dist/src/schema.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod/v3';
|
|
2
2
|
import { ValidationError } from './errors.js';
|
|
3
|
+
import { compiledAutomationPlan } from './plan.js';
|
|
3
4
|
import { IDENTIFIER } from './validation.js';
|
|
4
5
|
const maxPageSize = 100;
|
|
5
6
|
const identifierMessage = 'Identifier is invalid';
|
|
@@ -53,13 +54,14 @@ export const createWebhookTriggerSchema = z.object({
|
|
|
53
54
|
id: identifierSchema.optional(),
|
|
54
55
|
source: text(200, 'Webhook source').optional(),
|
|
55
56
|
event: text(200, 'Webhook event').optional(),
|
|
56
|
-
secret: z.string().min(16, 'Webhook secret must be at least 16 characters').max(10_000, 'Webhook secret is too long'),
|
|
57
|
+
secret: z.string().min(16, 'Webhook secret must be at least 16 characters').max(10_000, 'Webhook secret is too long').optional(),
|
|
57
58
|
enabled: z.boolean().optional(),
|
|
58
59
|
}).strict();
|
|
59
60
|
export const updateWebhookTriggerSchema = z.object({
|
|
60
61
|
source: text(200, 'Webhook source').optional(),
|
|
61
62
|
event: text(200, 'Webhook event').optional(),
|
|
62
|
-
secret: z.string().min(16, 'Webhook secret must be at least 16 characters').max(10_000, 'Webhook secret is too long').optional(),
|
|
63
|
+
secret: z.string().min(16, 'Webhook secret must be at least 16 characters').max(10_000, 'Webhook secret is too long').nullable().optional(),
|
|
64
|
+
rotateUrl: z.boolean().optional(),
|
|
63
65
|
enabled: z.boolean().optional(),
|
|
64
66
|
}).strict().refine((patch) => Object.keys(patch).length > 0, 'Webhook trigger update must include a change');
|
|
65
67
|
export const createWorkflowSchema = z.object({
|
|
@@ -80,6 +82,10 @@ export const updateWorkflowSchema = z.object({
|
|
|
80
82
|
export const listRunsSchema = pageQuerySchema.extend({
|
|
81
83
|
status: z.enum(['pending', 'sent', 'running', 'completed', 'failed']).optional(),
|
|
82
84
|
}).strict();
|
|
85
|
+
export const runAutomationNowSchema = z.object({
|
|
86
|
+
input: jsonSchema.optional(),
|
|
87
|
+
idempotencyKey: z.string().min(1).max(500).optional(),
|
|
88
|
+
}).strict();
|
|
83
89
|
export function parseCreateAutomation(value) {
|
|
84
90
|
return parse(createAutomationSchema, value);
|
|
85
91
|
}
|
|
@@ -102,10 +108,16 @@ export function parseUpdateWebhookTrigger(value) {
|
|
|
102
108
|
return parse(updateWebhookTriggerSchema, value);
|
|
103
109
|
}
|
|
104
110
|
export function parseCreateWorkflow(value) {
|
|
105
|
-
|
|
111
|
+
const workflow = parse(createWorkflowSchema, value);
|
|
112
|
+
if (workflow.compiled !== undefined)
|
|
113
|
+
validateCompiledPlan(workflow.compiled);
|
|
114
|
+
return workflow;
|
|
106
115
|
}
|
|
107
116
|
export function parseUpdateWorkflow(value) {
|
|
108
|
-
|
|
117
|
+
const workflow = parse(updateWorkflowSchema, value);
|
|
118
|
+
if (workflow.compiled !== undefined && workflow.compiled !== null)
|
|
119
|
+
validateCompiledPlan(workflow.compiled);
|
|
120
|
+
return workflow;
|
|
109
121
|
}
|
|
110
122
|
export function parsePageQuery(value) {
|
|
111
123
|
return parse(pageQuerySchema, value);
|
|
@@ -113,9 +125,20 @@ export function parsePageQuery(value) {
|
|
|
113
125
|
export function parseListRuns(value) {
|
|
114
126
|
return parse(listRunsSchema, value);
|
|
115
127
|
}
|
|
128
|
+
export function parseRunAutomationNow(value) {
|
|
129
|
+
return parse(runAutomationNowSchema, value);
|
|
130
|
+
}
|
|
116
131
|
export function parse(schema, value) {
|
|
117
132
|
const result = schema.safeParse(value);
|
|
118
133
|
if (result.success)
|
|
119
134
|
return result.data;
|
|
120
135
|
throw new ValidationError(result.error.issues[0]?.message || 'Input is invalid');
|
|
121
136
|
}
|
|
137
|
+
function validateCompiledPlan(value) {
|
|
138
|
+
try {
|
|
139
|
+
compiledAutomationPlan(value);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
throw new ValidationError(error instanceof Error ? error.message : String(error));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { Automation, AutomationRun, Page, PageQuery, ScheduleTrigger,
|
|
1
|
+
import type { Automation, AutomationRun, Page, PageQuery, ScheduleTrigger, Workflow } from '../contract.js';
|
|
2
|
+
import type { WebhookTriggerRecord } from '../crud/repository.js';
|
|
2
3
|
import type { AutomationRow, PageResult, RunRow, TriggerRow, WorkflowRow } from './rows.js';
|
|
3
4
|
export declare function automation(row: AutomationRow): Automation;
|
|
4
5
|
export declare function schedule(row: TriggerRow): ScheduleTrigger;
|
|
5
|
-
export declare function webhook(row: TriggerRow):
|
|
6
|
+
export declare function webhook(row: TriggerRow): WebhookTriggerRecord;
|
|
6
7
|
export declare function workflow(row: WorkflowRow): Workflow;
|
|
7
8
|
export declare function run(row: RunRow): AutomationRun;
|
|
8
9
|
export declare function paged<T, U>(result: PageResult<T>, mapper: (row: T) => U, query: Required<PageQuery>): Page<U>;
|
|
@@ -25,6 +25,8 @@ export function schedule(row) {
|
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
27
|
export function webhook(row) {
|
|
28
|
+
if (!row.endpoint_id)
|
|
29
|
+
throw new Error('Webhook trigger is missing its endpoint id');
|
|
28
30
|
return {
|
|
29
31
|
id: row.id,
|
|
30
32
|
automationId: row.automation_id,
|
|
@@ -32,6 +34,7 @@ export function webhook(row) {
|
|
|
32
34
|
enabled: row.enabled,
|
|
33
35
|
source: row.source,
|
|
34
36
|
event: row.event,
|
|
37
|
+
endpointId: row.endpoint_id,
|
|
35
38
|
secretConfigured: row.secret_configured,
|
|
36
39
|
createdAt: timestamp(row.created_at),
|
|
37
40
|
updatedAt: timestamp(row.updated_at),
|
|
@@ -58,6 +61,8 @@ export function run(row) {
|
|
|
58
61
|
workflowId: row.workflow_id,
|
|
59
62
|
targetId: row.target_id,
|
|
60
63
|
status: row.status,
|
|
64
|
+
attempts: row.attempts,
|
|
65
|
+
...(row.retry_at ? { retryAt: timestamp(row.retry_at) } : {}),
|
|
61
66
|
input: row.input,
|
|
62
67
|
createdAt: timestamp(row.created_at),
|
|
63
68
|
...(row.sent_at ? { sentAt: timestamp(row.sent_at) } : {}),
|
|
@@ -20,6 +20,7 @@ export type TriggerRow = {
|
|
|
20
20
|
remaining_occurrences: number | null;
|
|
21
21
|
source: string | null;
|
|
22
22
|
event: string | null;
|
|
23
|
+
endpoint_id: string | null;
|
|
23
24
|
secret_configured: boolean;
|
|
24
25
|
created_at: Date | string;
|
|
25
26
|
updated_at: Date | string;
|
|
@@ -42,6 +43,8 @@ export type RunRow = {
|
|
|
42
43
|
workflow_id: string;
|
|
43
44
|
target_id: string;
|
|
44
45
|
status: AutomationRun['status'];
|
|
46
|
+
attempts: number;
|
|
47
|
+
retry_at: Date | string | null;
|
|
45
48
|
input: AutomationRun['input'];
|
|
46
49
|
created_at: Date | string;
|
|
47
50
|
sent_at: Date | string | null;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AutomationCrudRepository } from '../crud.js';
|
|
2
2
|
import { type SupabaseRpcClient } from './rpc.js';
|
|
3
|
-
type Operations = Pick<AutomationCrudRepository, 'createWorkflow' | 'getWorkflow' | 'updateWorkflow' | 'deleteWorkflow' | 'listRuns' | 'getRun'>;
|
|
3
|
+
type Operations = Pick<AutomationCrudRepository, 'createWorkflow' | 'getWorkflow' | 'updateWorkflow' | 'deleteWorkflow' | 'listRuns' | 'getRun' | 'runAutomationNow'>;
|
|
4
4
|
export declare function workflowRunRepository(client: SupabaseRpcClient): Operations;
|
|
5
5
|
export {};
|
|
@@ -37,5 +37,15 @@ export function workflowRunRepository(client) {
|
|
|
37
37
|
});
|
|
38
38
|
return row ? run(row) : null;
|
|
39
39
|
},
|
|
40
|
+
runAutomationNow: async (userId, automationId, input, idempotencyKey, now) => {
|
|
41
|
+
const rows = await call(client, 'enqueue_amalgm_manual_run', {
|
|
42
|
+
p_user_id: userId,
|
|
43
|
+
p_automation_id: automationId,
|
|
44
|
+
p_input: input,
|
|
45
|
+
p_idempotency_key: idempotencyKey,
|
|
46
|
+
p_now: now.toISOString(),
|
|
47
|
+
});
|
|
48
|
+
return rows[0] ? run(rows[0]) : null;
|
|
49
|
+
},
|
|
40
50
|
};
|
|
41
51
|
}
|
|
@@ -43,6 +43,7 @@ function run(row) {
|
|
|
43
43
|
input: row.input,
|
|
44
44
|
status: row.status,
|
|
45
45
|
attempts: row.attempts,
|
|
46
|
+
...(row.retry_at ? { retryAt: timestamp(row.retry_at) } : {}),
|
|
46
47
|
leaseToken: row.lease_token,
|
|
47
48
|
leaseExpiresAt: timestamp(row.lease_expires_at),
|
|
48
49
|
createdAt: timestamp(row.created_at),
|
|
@@ -56,6 +57,7 @@ function run(row) {
|
|
|
56
57
|
function update(value) {
|
|
57
58
|
return {
|
|
58
59
|
status: value.status,
|
|
60
|
+
...(value.retryAt === undefined ? {} : { retryAt: value.retryAt }),
|
|
59
61
|
...(value.output === undefined ? {} : { output: value.output }),
|
|
60
62
|
...(value.error === undefined ? {} : { error: value.error }),
|
|
61
63
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AutomationStore,
|
|
1
|
+
import type { AutomationStore, AutomationRun, DueCronTrigger, RunInput, StoredEventTrigger } from './types.js';
|
|
2
2
|
export interface SupabaseRpcClient {
|
|
3
3
|
rpc(functionName: string, arguments_: Record<string, unknown>): PromiseLike<{
|
|
4
4
|
data: unknown;
|
|
@@ -11,13 +11,10 @@ export declare class SupabaseStore implements AutomationStore {
|
|
|
11
11
|
#private;
|
|
12
12
|
readonly client: SupabaseRpcClient;
|
|
13
13
|
constructor(client: SupabaseRpcClient);
|
|
14
|
-
|
|
14
|
+
eventTrigger(endpointId: string): Promise<StoredEventTrigger | null>;
|
|
15
15
|
dueCronTriggers(now: Date): Promise<DueCronTrigger[]>;
|
|
16
16
|
enqueueEvent(trigger: StoredEventTrigger, input: Extract<RunInput, {
|
|
17
17
|
kind: 'event';
|
|
18
|
-
}>, now: Date): Promise<AutomationRun | null>;
|
|
18
|
+
}>, now: Date, deliveryKey: string | null): Promise<AutomationRun | null>;
|
|
19
19
|
enqueueCron(trigger: DueCronTrigger, nextRunAt: string, now: Date): Promise<AutomationRun | null>;
|
|
20
|
-
pendingRuns(target: AutomationTarget): Promise<AutomationRun[]>;
|
|
21
|
-
markSent(runId: string, target: AutomationTarget, now: Date): Promise<boolean>;
|
|
22
|
-
updateRun(target: AutomationTarget, runId: string, update: RunUpdate): Promise<AutomationRun | null>;
|
|
23
20
|
}
|
|
@@ -22,13 +22,14 @@ export class SupabaseStore {
|
|
|
22
22
|
constructor(client) {
|
|
23
23
|
this.client = client;
|
|
24
24
|
}
|
|
25
|
-
async
|
|
26
|
-
const rows = await this.#call('
|
|
27
|
-
|
|
28
|
-
p_target_id: target.targetId,
|
|
25
|
+
async eventTrigger(endpointId) {
|
|
26
|
+
const rows = await this.#call('resolve_amalgm_webhook', {
|
|
27
|
+
p_endpoint_id: endpointId,
|
|
29
28
|
});
|
|
30
|
-
|
|
29
|
+
const row = rows[0];
|
|
30
|
+
return row ? {
|
|
31
31
|
kind: 'event',
|
|
32
|
+
endpointId: row.endpoint_id,
|
|
32
33
|
userId: row.user_id,
|
|
33
34
|
automationId: row.automation_id,
|
|
34
35
|
triggerId: row.trigger_id,
|
|
@@ -36,7 +37,7 @@ export class SupabaseStore {
|
|
|
36
37
|
source: row.source,
|
|
37
38
|
event: row.event,
|
|
38
39
|
secret: row.secret,
|
|
39
|
-
}
|
|
40
|
+
} : null;
|
|
40
41
|
}
|
|
41
42
|
async dueCronTriggers(now) {
|
|
42
43
|
const rows = await this.#call('list_due_amalgm_crons', { p_now: now.toISOString() });
|
|
@@ -52,46 +53,24 @@ export class SupabaseStore {
|
|
|
52
53
|
...(row.remaining_occurrences === null ? {} : { remainingOccurrences: row.remaining_occurrences }),
|
|
53
54
|
}));
|
|
54
55
|
}
|
|
55
|
-
enqueueEvent(trigger, input, now) {
|
|
56
|
-
return this.#enqueue(trigger, input, now, null);
|
|
56
|
+
enqueueEvent(trigger, input, now, deliveryKey) {
|
|
57
|
+
return this.#enqueue(trigger, input, now, null, deliveryKey);
|
|
57
58
|
}
|
|
58
59
|
enqueueCron(trigger, nextRunAt, now) {
|
|
59
60
|
return this.#enqueue(trigger, {
|
|
60
61
|
kind: 'cron',
|
|
61
62
|
scheduledFor: trigger.nextRunAt,
|
|
62
|
-
}, now, nextRunAt);
|
|
63
|
-
}
|
|
64
|
-
async pendingRuns(target) {
|
|
65
|
-
const rows = await this.#call('list_amalgm_pending_runs', {
|
|
66
|
-
p_user_id: target.userId,
|
|
67
|
-
p_target_id: target.targetId,
|
|
68
|
-
});
|
|
69
|
-
return rows.map(automationRun);
|
|
70
|
-
}
|
|
71
|
-
markSent(runId, target, now) {
|
|
72
|
-
return this.#call('mark_amalgm_run_sent', {
|
|
73
|
-
p_run_id: runId,
|
|
74
|
-
p_user_id: target.userId,
|
|
75
|
-
p_target_id: target.targetId,
|
|
76
|
-
p_now: now.toISOString(),
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
async updateRun(target, runId, update) {
|
|
80
|
-
const rows = await this.#call('update_amalgm_run', {
|
|
81
|
-
p_run_id: runId,
|
|
82
|
-
p_user_id: target.userId,
|
|
83
|
-
p_target_id: target.targetId,
|
|
84
|
-
p_update: update,
|
|
85
|
-
});
|
|
86
|
-
return rows[0] ? automationRun(rows[0]) : null;
|
|
63
|
+
}, now, nextRunAt, null);
|
|
87
64
|
}
|
|
88
|
-
async #enqueue(trigger, input, now, nextRunAt) {
|
|
65
|
+
async #enqueue(trigger, input, now, nextRunAt, deliveryKey) {
|
|
89
66
|
const rows = await this.#call('enqueue_amalgm_run', {
|
|
90
67
|
p_kind: trigger.kind,
|
|
91
68
|
p_user_id: trigger.userId,
|
|
92
69
|
p_automation_id: trigger.automationId,
|
|
93
70
|
p_trigger_id: trigger.triggerId,
|
|
94
71
|
p_target_id: trigger.targetId,
|
|
72
|
+
p_endpoint_id: trigger.kind === 'event' ? trigger.endpointId : null,
|
|
73
|
+
p_delivery_key: deliveryKey,
|
|
95
74
|
p_expected_next_run_at: trigger.kind === 'cron' ? trigger.nextRunAt : null,
|
|
96
75
|
p_next_run_at: nextRunAt,
|
|
97
76
|
p_input: input,
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Automation, AutomationCrud, AutomationRun, CreateAutomation, CreateScheduleTrigger, CreateWebhookTrigger, CreateWorkflow, ListAutomations, ListRuns, Page, Trigger, UpdateAutomation, UpdateScheduleTrigger, UpdateWebhookTrigger, UpdateWorkflow, Workflow } from './contract.js';
|
|
2
|
+
export interface AutomationView {
|
|
3
|
+
automation: Automation;
|
|
4
|
+
triggers: Trigger[];
|
|
5
|
+
workflow: Workflow | null;
|
|
6
|
+
runs?: Page<AutomationRun>;
|
|
7
|
+
}
|
|
8
|
+
export interface CreateAutomationDefinition extends CreateAutomation {
|
|
9
|
+
schedules?: CreateScheduleTrigger[];
|
|
10
|
+
webhooks?: CreateWebhookTrigger[];
|
|
11
|
+
workflow?: CreateWorkflow;
|
|
12
|
+
}
|
|
13
|
+
interface Changes<Create extends {
|
|
14
|
+
id?: string;
|
|
15
|
+
}, Update> {
|
|
16
|
+
create?: Create[];
|
|
17
|
+
update?: Array<{
|
|
18
|
+
id: string;
|
|
19
|
+
patch: Update;
|
|
20
|
+
}>;
|
|
21
|
+
delete?: string[];
|
|
22
|
+
}
|
|
23
|
+
export type WorkflowChange = {
|
|
24
|
+
create: CreateWorkflow;
|
|
25
|
+
} | {
|
|
26
|
+
update: UpdateWorkflow;
|
|
27
|
+
} | {
|
|
28
|
+
delete: true;
|
|
29
|
+
};
|
|
30
|
+
export interface UpdateAutomationDefinition {
|
|
31
|
+
patch?: UpdateAutomation;
|
|
32
|
+
schedules?: Changes<CreateScheduleTrigger, UpdateScheduleTrigger>;
|
|
33
|
+
webhooks?: Changes<CreateWebhookTrigger, UpdateWebhookTrigger>;
|
|
34
|
+
workflow?: WorkflowChange;
|
|
35
|
+
}
|
|
36
|
+
export interface AutomationToolSurface {
|
|
37
|
+
create(input: CreateAutomationDefinition): Promise<AutomationView>;
|
|
38
|
+
list(query?: ListAutomations): Promise<Page<Automation>>;
|
|
39
|
+
get(automationId: string, runs?: false | ListRuns): Promise<AutomationView>;
|
|
40
|
+
update(automationId: string, input: UpdateAutomationDefinition): Promise<AutomationView>;
|
|
41
|
+
delete(automationId: string): Promise<{
|
|
42
|
+
deleted: string;
|
|
43
|
+
}>;
|
|
44
|
+
runNow(automationId: string, input?: Parameters<AutomationCrud['runs']['runNow']>[1]): Promise<AutomationRun>;
|
|
45
|
+
}
|
|
46
|
+
export declare function createAutomationToolSurface(sdk: AutomationCrud): AutomationToolSurface;
|
|
47
|
+
export {};
|