@amalgm/automations 0.1.2 → 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/AXIOMS.md +27 -11
- package/PURPOSE.md +13 -3
- package/README.md +60 -113
- package/dist/host/auth.d.ts +11 -0
- package/dist/host/auth.js +89 -0
- package/dist/host/config.d.ts +9 -0
- package/dist/host/config.js +30 -0
- package/dist/host/index.d.ts +3 -0
- package/dist/host/index.js +3 -0
- package/dist/host/main.d.ts +1 -0
- package/dist/host/main.js +48 -0
- package/dist/host/server.d.ts +12 -0
- package/dist/host/server.js +54 -0
- package/dist/src/automations.js +4 -1
- package/dist/src/client.d.ts +3 -1
- package/dist/src/client.js +6 -4
- package/dist/src/contract.d.ts +19 -2
- package/dist/src/crud/automations.d.ts +3 -0
- package/dist/src/crud/automations.js +55 -0
- package/dist/src/crud/context.d.ts +14 -0
- package/dist/src/crud/context.js +43 -0
- package/dist/src/crud/repository.d.ts +35 -0
- package/dist/src/crud/repository.js +1 -0
- package/dist/src/crud/runs.d.ts +3 -0
- package/dist/src/crud/runs.js +19 -0
- package/dist/src/crud/triggers.d.ts +3 -0
- package/dist/src/crud/triggers.js +118 -0
- package/dist/src/crud/workflow.d.ts +3 -0
- package/dist/src/crud/workflow.js +42 -0
- package/dist/src/crud.d.ts +3 -33
- package/dist/src/crud.js +10 -237
- package/dist/src/executor.d.ts +16 -0
- package/dist/src/executor.js +76 -0
- package/dist/src/index.d.ts +8 -2
- package/dist/src/index.js +5 -0
- package/dist/src/machine-client.d.ts +7 -0
- package/dist/src/machine-client.js +30 -0
- package/dist/src/machine-http.d.ts +5 -0
- package/dist/src/machine-http.js +40 -0
- package/dist/src/machine.d.ts +41 -0
- package/dist/src/machine.js +43 -0
- package/dist/src/mcp.js +3 -3
- package/dist/src/schema.d.ts +50 -42
- package/dist/src/schema.js +3 -1
- package/dist/src/supabase-crud/automations.d.ts +5 -0
- package/dist/src/supabase-crud/automations.js +36 -0
- package/dist/src/supabase-crud/mappers.d.ts +8 -0
- package/dist/src/supabase-crud/mappers.js +81 -0
- package/dist/src/supabase-crud/rows.d.ts +56 -0
- package/dist/src/supabase-crud/rows.js +1 -0
- package/dist/src/supabase-crud/rpc.d.ts +10 -0
- package/dist/src/supabase-crud/rpc.js +19 -0
- package/dist/src/supabase-crud/triggers.d.ts +5 -0
- package/dist/src/supabase-crud/triggers.js +46 -0
- package/dist/src/supabase-crud/workflow-runs.d.ts +5 -0
- package/dist/src/supabase-crud/workflow-runs.js +41 -0
- package/dist/src/supabase-crud.d.ts +5 -41
- package/dist/src/supabase-crud.js +4 -267
- package/dist/src/supabase-machine.d.ts +16 -0
- package/dist/src/supabase-machine.js +63 -0
- package/dist/src/supabase-store.js +1 -0
- package/dist/src/types.d.ts +1 -0
- package/package.json +12 -3
- package/skills/automations/SKILL.md +52 -0
- package/supabase/migrations/20260829010000_bounded_schedules_and_machine_claims.sql +311 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
ALTER TABLE amalgm_automation_triggers
|
|
2
|
+
ADD COLUMN IF NOT EXISTS next_run_at timestamptz,
|
|
3
|
+
ADD COLUMN IF NOT EXISTS max_occurrences integer
|
|
4
|
+
CHECK (max_occurrences BETWEEN 1 AND 100000),
|
|
5
|
+
ADD COLUMN IF NOT EXISTS remaining_occurrences integer
|
|
6
|
+
CHECK (remaining_occurrences BETWEEN 0 AND 100000),
|
|
7
|
+
ADD CONSTRAINT amalgm_schedule_occurrence_budget CHECK (
|
|
8
|
+
(max_occurrences IS NULL AND remaining_occurrences IS NULL)
|
|
9
|
+
OR (
|
|
10
|
+
kind = 'schedule'
|
|
11
|
+
AND max_occurrences IS NOT NULL
|
|
12
|
+
AND remaining_occurrences IS NOT NULL
|
|
13
|
+
AND remaining_occurrences <= max_occurrences
|
|
14
|
+
)
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
UPDATE amalgm_automation_triggers
|
|
18
|
+
SET next_run_at = now()
|
|
19
|
+
WHERE kind = 'schedule' AND next_run_at IS NULL;
|
|
20
|
+
|
|
21
|
+
ALTER TABLE amalgm_automation_runs
|
|
22
|
+
ADD COLUMN IF NOT EXISTS lease_token uuid,
|
|
23
|
+
ADD COLUMN IF NOT EXISTS lease_expires_at timestamptz,
|
|
24
|
+
ADD COLUMN IF NOT EXISTS attempts integer NOT NULL DEFAULT 0
|
|
25
|
+
CHECK (attempts BETWEEN 0 AND 5);
|
|
26
|
+
|
|
27
|
+
CREATE INDEX IF NOT EXISTS amalgm_automation_runs_claimable
|
|
28
|
+
ON amalgm_automation_runs(user_id, target_id, status, lease_expires_at, created_at, id)
|
|
29
|
+
WHERE status IN ('pending', 'sent', 'running');
|
|
30
|
+
|
|
31
|
+
CREATE OR REPLACE FUNCTION amalgm_trigger_json(p_trigger amalgm_automation_triggers)
|
|
32
|
+
RETURNS jsonb
|
|
33
|
+
LANGUAGE sql
|
|
34
|
+
IMMUTABLE
|
|
35
|
+
SET search_path = public
|
|
36
|
+
AS $$
|
|
37
|
+
SELECT jsonb_strip_nulls(jsonb_build_object(
|
|
38
|
+
'id', p_trigger.id,
|
|
39
|
+
'automation_id', p_trigger.automation_id,
|
|
40
|
+
'kind', p_trigger.kind,
|
|
41
|
+
'enabled', p_trigger.enabled,
|
|
42
|
+
'cron', p_trigger.cron,
|
|
43
|
+
'timezone', p_trigger.timezone,
|
|
44
|
+
'next_run_at', p_trigger.next_run_at,
|
|
45
|
+
'max_occurrences', p_trigger.max_occurrences,
|
|
46
|
+
'remaining_occurrences', p_trigger.remaining_occurrences,
|
|
47
|
+
'source', p_trigger.source,
|
|
48
|
+
'event', p_trigger.event,
|
|
49
|
+
'secret_configured', p_trigger.secret IS NOT NULL,
|
|
50
|
+
'created_at', p_trigger.created_at,
|
|
51
|
+
'updated_at', p_trigger.updated_at
|
|
52
|
+
));
|
|
53
|
+
$$;
|
|
54
|
+
|
|
55
|
+
CREATE OR REPLACE FUNCTION create_amalgm_schedule_trigger(
|
|
56
|
+
p_user_id uuid,
|
|
57
|
+
p_automation_id text,
|
|
58
|
+
p_trigger jsonb
|
|
59
|
+
) RETURNS jsonb
|
|
60
|
+
LANGUAGE plpgsql
|
|
61
|
+
SECURITY DEFINER
|
|
62
|
+
SET search_path = public
|
|
63
|
+
AS $$
|
|
64
|
+
DECLARE
|
|
65
|
+
v_row public.amalgm_automation_triggers;
|
|
66
|
+
BEGIN
|
|
67
|
+
INSERT INTO public.amalgm_automation_triggers (
|
|
68
|
+
user_id, automation_id, id, kind, enabled, cron, timezone, next_run_at,
|
|
69
|
+
max_occurrences, remaining_occurrences
|
|
70
|
+
)
|
|
71
|
+
SELECT p_user_id, p_automation_id, p_trigger->>'id', 'schedule',
|
|
72
|
+
(p_trigger->>'enabled')::boolean, p_trigger->>'cron', p_trigger->>'timezone',
|
|
73
|
+
(p_trigger->>'nextRunAt')::timestamptz,
|
|
74
|
+
(p_trigger->>'maxOccurrences')::integer,
|
|
75
|
+
(p_trigger->>'maxOccurrences')::integer
|
|
76
|
+
WHERE EXISTS (
|
|
77
|
+
SELECT 1 FROM public.amalgm_automations
|
|
78
|
+
WHERE user_id = p_user_id AND id = p_automation_id
|
|
79
|
+
)
|
|
80
|
+
RETURNING * INTO v_row;
|
|
81
|
+
IF NOT FOUND THEN RETURN NULL; END IF;
|
|
82
|
+
RETURN public.amalgm_trigger_json(v_row);
|
|
83
|
+
END;
|
|
84
|
+
$$;
|
|
85
|
+
|
|
86
|
+
CREATE OR REPLACE FUNCTION update_amalgm_schedule_trigger(
|
|
87
|
+
p_user_id uuid,
|
|
88
|
+
p_automation_id text,
|
|
89
|
+
p_trigger_id text,
|
|
90
|
+
p_patch jsonb
|
|
91
|
+
) RETURNS jsonb
|
|
92
|
+
LANGUAGE plpgsql
|
|
93
|
+
SECURITY DEFINER
|
|
94
|
+
SET search_path = public
|
|
95
|
+
AS $$
|
|
96
|
+
DECLARE
|
|
97
|
+
v_row public.amalgm_automation_triggers;
|
|
98
|
+
BEGIN
|
|
99
|
+
UPDATE public.amalgm_automation_triggers SET
|
|
100
|
+
cron = CASE WHEN p_patch ? 'cron' THEN p_patch->>'cron' ELSE cron END,
|
|
101
|
+
timezone = CASE WHEN p_patch ? 'timezone' THEN p_patch->>'timezone' ELSE timezone END,
|
|
102
|
+
next_run_at = CASE WHEN p_patch ? 'nextRunAt'
|
|
103
|
+
THEN (p_patch->>'nextRunAt')::timestamptz ELSE next_run_at END,
|
|
104
|
+
max_occurrences = CASE WHEN p_patch ? 'maxOccurrences'
|
|
105
|
+
THEN (p_patch->>'maxOccurrences')::integer ELSE max_occurrences END,
|
|
106
|
+
remaining_occurrences = CASE WHEN p_patch ? 'remainingOccurrences'
|
|
107
|
+
THEN (p_patch->>'remainingOccurrences')::integer ELSE remaining_occurrences END,
|
|
108
|
+
enabled = CASE WHEN p_patch ? 'enabled' THEN (p_patch->>'enabled')::boolean ELSE enabled END,
|
|
109
|
+
updated_at = now()
|
|
110
|
+
WHERE user_id = p_user_id AND automation_id = p_automation_id
|
|
111
|
+
AND id = p_trigger_id AND kind = 'schedule'
|
|
112
|
+
RETURNING * INTO v_row;
|
|
113
|
+
IF NOT FOUND THEN RETURN NULL; END IF;
|
|
114
|
+
RETURN public.amalgm_trigger_json(v_row);
|
|
115
|
+
END;
|
|
116
|
+
$$;
|
|
117
|
+
|
|
118
|
+
DROP FUNCTION IF EXISTS list_due_amalgm_crons(timestamptz);
|
|
119
|
+
CREATE FUNCTION list_due_amalgm_crons(p_now timestamptz)
|
|
120
|
+
RETURNS TABLE (
|
|
121
|
+
user_id uuid,
|
|
122
|
+
automation_id text,
|
|
123
|
+
trigger_id text,
|
|
124
|
+
target_id text,
|
|
125
|
+
cron text,
|
|
126
|
+
timezone text,
|
|
127
|
+
next_run_at timestamptz,
|
|
128
|
+
remaining_occurrences integer
|
|
129
|
+
)
|
|
130
|
+
LANGUAGE sql
|
|
131
|
+
SECURITY DEFINER
|
|
132
|
+
SET search_path = public
|
|
133
|
+
AS $$
|
|
134
|
+
SELECT t.user_id, t.automation_id, t.id, a.target_id,
|
|
135
|
+
t.cron, t.timezone, t.next_run_at, t.remaining_occurrences
|
|
136
|
+
FROM public.amalgm_automation_triggers t
|
|
137
|
+
JOIN public.amalgm_automations a
|
|
138
|
+
ON a.user_id = t.user_id AND a.id = t.automation_id
|
|
139
|
+
WHERE t.kind = 'schedule' AND t.next_run_at <= p_now
|
|
140
|
+
AND (t.remaining_occurrences IS NULL OR t.remaining_occurrences > 0)
|
|
141
|
+
AND t.enabled AND a.enabled
|
|
142
|
+
ORDER BY t.next_run_at, t.user_id, t.automation_id, t.id;
|
|
143
|
+
$$;
|
|
144
|
+
|
|
145
|
+
CREATE OR REPLACE FUNCTION enqueue_amalgm_run(
|
|
146
|
+
p_kind text,
|
|
147
|
+
p_user_id uuid,
|
|
148
|
+
p_automation_id text,
|
|
149
|
+
p_trigger_id text,
|
|
150
|
+
p_target_id text,
|
|
151
|
+
p_expected_next_run_at timestamptz,
|
|
152
|
+
p_next_run_at timestamptz,
|
|
153
|
+
p_input jsonb,
|
|
154
|
+
p_now timestamptz
|
|
155
|
+
) RETURNS SETOF amalgm_automation_runs
|
|
156
|
+
LANGUAGE plpgsql
|
|
157
|
+
SECURITY DEFINER
|
|
158
|
+
SET search_path = public
|
|
159
|
+
AS $$
|
|
160
|
+
DECLARE
|
|
161
|
+
v_inserted integer := 0;
|
|
162
|
+
BEGIN
|
|
163
|
+
IF p_kind = 'cron' THEN
|
|
164
|
+
PERFORM 1
|
|
165
|
+
FROM public.amalgm_automation_triggers t
|
|
166
|
+
JOIN public.amalgm_automations a
|
|
167
|
+
ON a.user_id = t.user_id AND a.id = t.automation_id
|
|
168
|
+
WHERE t.user_id = p_user_id AND t.automation_id = p_automation_id
|
|
169
|
+
AND t.id = p_trigger_id AND a.target_id = p_target_id
|
|
170
|
+
AND t.kind = 'schedule' AND t.next_run_at = p_expected_next_run_at
|
|
171
|
+
AND (t.remaining_occurrences IS NULL OR t.remaining_occurrences > 0)
|
|
172
|
+
AND t.enabled AND a.enabled
|
|
173
|
+
FOR UPDATE OF t;
|
|
174
|
+
ELSIF p_kind = 'event' THEN
|
|
175
|
+
PERFORM 1
|
|
176
|
+
FROM public.amalgm_automation_triggers t
|
|
177
|
+
JOIN public.amalgm_automations a
|
|
178
|
+
ON a.user_id = t.user_id AND a.id = t.automation_id
|
|
179
|
+
WHERE t.user_id = p_user_id AND t.automation_id = p_automation_id
|
|
180
|
+
AND t.id = p_trigger_id AND a.target_id = p_target_id
|
|
181
|
+
AND t.kind = 'webhook' AND t.enabled AND a.enabled;
|
|
182
|
+
ELSE
|
|
183
|
+
RAISE EXCEPTION 'Invalid automation trigger kind';
|
|
184
|
+
END IF;
|
|
185
|
+
IF NOT FOUND THEN RETURN; END IF;
|
|
186
|
+
|
|
187
|
+
RETURN QUERY
|
|
188
|
+
INSERT INTO public.amalgm_automation_runs (
|
|
189
|
+
user_id, target_id, automation_id, trigger_id, workflow_id,
|
|
190
|
+
automation_payload, input, status, created_at
|
|
191
|
+
)
|
|
192
|
+
SELECT a.user_id, a.target_id, a.id, t.id, w.id,
|
|
193
|
+
jsonb_strip_nulls(jsonb_build_object(
|
|
194
|
+
'id', a.id, 'targetId', a.target_id, 'name', a.name,
|
|
195
|
+
'description', a.description, 'enabled', a.enabled,
|
|
196
|
+
'trigger', jsonb_strip_nulls(jsonb_build_object(
|
|
197
|
+
'id', t.id,
|
|
198
|
+
'kind', CASE WHEN t.kind = 'schedule' THEN 'cron' ELSE 'event' END,
|
|
199
|
+
'enabled', t.enabled, 'cron', t.cron, 'timezone', t.timezone,
|
|
200
|
+
'source', t.source, 'event', t.event
|
|
201
|
+
)),
|
|
202
|
+
'workflow', jsonb_strip_nulls(jsonb_build_object(
|
|
203
|
+
'id', w.id, 'name', w.name, 'script', w.script,
|
|
204
|
+
'compiled', w.compiled, 'allowlist', w.allowlist, 'limits', w.limits
|
|
205
|
+
))
|
|
206
|
+
)),
|
|
207
|
+
p_input, 'pending', p_now
|
|
208
|
+
FROM public.amalgm_automations a
|
|
209
|
+
JOIN public.amalgm_automation_triggers t
|
|
210
|
+
ON t.user_id = a.user_id AND t.automation_id = a.id
|
|
211
|
+
JOIN public.amalgm_automation_workflows w
|
|
212
|
+
ON w.user_id = a.user_id AND w.automation_id = a.id
|
|
213
|
+
WHERE a.user_id = p_user_id AND a.id = p_automation_id AND t.id = p_trigger_id
|
|
214
|
+
RETURNING *;
|
|
215
|
+
GET DIAGNOSTICS v_inserted = ROW_COUNT;
|
|
216
|
+
|
|
217
|
+
IF p_kind = 'cron' AND v_inserted = 1 THEN
|
|
218
|
+
UPDATE public.amalgm_automation_triggers SET
|
|
219
|
+
next_run_at = p_next_run_at,
|
|
220
|
+
remaining_occurrences = CASE WHEN remaining_occurrences IS NULL
|
|
221
|
+
THEN NULL ELSE remaining_occurrences - 1 END,
|
|
222
|
+
enabled = CASE WHEN remaining_occurrences = 1 THEN false ELSE enabled END,
|
|
223
|
+
updated_at = p_now
|
|
224
|
+
WHERE user_id = p_user_id AND automation_id = p_automation_id
|
|
225
|
+
AND id = p_trigger_id AND kind = 'schedule'
|
|
226
|
+
AND next_run_at = p_expected_next_run_at;
|
|
227
|
+
END IF;
|
|
228
|
+
END;
|
|
229
|
+
$$;
|
|
230
|
+
|
|
231
|
+
CREATE OR REPLACE FUNCTION claim_amalgm_machine_runs(
|
|
232
|
+
p_user_id uuid,
|
|
233
|
+
p_target_id text,
|
|
234
|
+
p_limit integer,
|
|
235
|
+
p_lease_seconds integer
|
|
236
|
+
) RETURNS SETOF amalgm_automation_runs
|
|
237
|
+
LANGUAGE plpgsql
|
|
238
|
+
SECURITY DEFINER
|
|
239
|
+
SET search_path = public
|
|
240
|
+
AS $$
|
|
241
|
+
BEGIN
|
|
242
|
+
UPDATE public.amalgm_automation_runs SET
|
|
243
|
+
status = 'failed', error = 'Machine execution lease exhausted',
|
|
244
|
+
finished_at = now(), lease_token = NULL, lease_expires_at = NULL
|
|
245
|
+
WHERE user_id = p_user_id AND target_id = p_target_id
|
|
246
|
+
AND status IN ('sent', 'running') AND attempts >= 5
|
|
247
|
+
AND lease_expires_at <= now();
|
|
248
|
+
|
|
249
|
+
RETURN QUERY
|
|
250
|
+
WITH candidates AS (
|
|
251
|
+
SELECT id
|
|
252
|
+
FROM public.amalgm_automation_runs
|
|
253
|
+
WHERE user_id = p_user_id AND target_id = p_target_id AND attempts < 5
|
|
254
|
+
AND (
|
|
255
|
+
status = 'pending'
|
|
256
|
+
OR (status IN ('sent', 'running') AND lease_expires_at <= now())
|
|
257
|
+
)
|
|
258
|
+
ORDER BY created_at, id
|
|
259
|
+
FOR UPDATE SKIP LOCKED
|
|
260
|
+
LIMIT LEAST(GREATEST(p_limit, 1), 20)
|
|
261
|
+
)
|
|
262
|
+
UPDATE public.amalgm_automation_runs r SET
|
|
263
|
+
status = 'sent', sent_at = COALESCE(sent_at, now()),
|
|
264
|
+
lease_token = gen_random_uuid(),
|
|
265
|
+
lease_expires_at = now() + make_interval(secs => LEAST(GREATEST(p_lease_seconds, 30), 900)),
|
|
266
|
+
attempts = attempts + 1
|
|
267
|
+
FROM candidates
|
|
268
|
+
WHERE r.id = candidates.id
|
|
269
|
+
RETURNING r.*;
|
|
270
|
+
END;
|
|
271
|
+
$$;
|
|
272
|
+
|
|
273
|
+
CREATE OR REPLACE FUNCTION update_amalgm_claimed_run(
|
|
274
|
+
p_user_id uuid,
|
|
275
|
+
p_target_id text,
|
|
276
|
+
p_run_id uuid,
|
|
277
|
+
p_lease_token uuid,
|
|
278
|
+
p_update jsonb,
|
|
279
|
+
p_now timestamptz
|
|
280
|
+
) RETURNS SETOF amalgm_automation_runs
|
|
281
|
+
LANGUAGE plpgsql
|
|
282
|
+
SECURITY DEFINER
|
|
283
|
+
SET search_path = public
|
|
284
|
+
AS $$
|
|
285
|
+
DECLARE
|
|
286
|
+
v_status text := p_update->>'status';
|
|
287
|
+
BEGIN
|
|
288
|
+
IF v_status NOT IN ('running', 'completed', 'failed') THEN
|
|
289
|
+
RAISE EXCEPTION 'Invalid run status';
|
|
290
|
+
END IF;
|
|
291
|
+
RETURN QUERY
|
|
292
|
+
UPDATE public.amalgm_automation_runs SET
|
|
293
|
+
status = v_status,
|
|
294
|
+
started_at = CASE WHEN v_status = 'running' THEN COALESCE(started_at, p_now) ELSE started_at END,
|
|
295
|
+
finished_at = CASE WHEN v_status IN ('completed', 'failed') THEN p_now ELSE finished_at END,
|
|
296
|
+
output = CASE WHEN p_update ? 'output' THEN p_update->'output' ELSE output END,
|
|
297
|
+
error = CASE WHEN p_update ? 'error' THEN left(p_update->>'error', 1000) ELSE error END,
|
|
298
|
+
lease_token = CASE WHEN v_status = 'running' THEN lease_token ELSE NULL END,
|
|
299
|
+
lease_expires_at = CASE WHEN v_status = 'running' THEN p_now + interval '15 minutes' ELSE NULL END
|
|
300
|
+
WHERE id = p_run_id AND user_id = p_user_id AND target_id = p_target_id
|
|
301
|
+
AND status IN ('sent', 'running') AND lease_token = p_lease_token
|
|
302
|
+
AND lease_expires_at > p_now
|
|
303
|
+
RETURNING *;
|
|
304
|
+
END;
|
|
305
|
+
$$;
|
|
306
|
+
|
|
307
|
+
REVOKE ALL ON FUNCTION claim_amalgm_machine_runs(uuid, text, integer, integer) FROM PUBLIC;
|
|
308
|
+
REVOKE ALL ON FUNCTION update_amalgm_claimed_run(uuid, text, uuid, uuid, jsonb, timestamptz) FROM PUBLIC;
|
|
309
|
+
GRANT EXECUTE ON FUNCTION claim_amalgm_machine_runs(uuid, text, integer, integer) TO service_role;
|
|
310
|
+
GRANT EXECUTE ON FUNCTION update_amalgm_claimed_run(uuid, text, uuid, uuid, jsonb, timestamptz) TO service_role;
|
|
311
|
+
GRANT EXECUTE ON FUNCTION list_due_amalgm_crons(timestamptz) TO service_role;
|