@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.
Files changed (64) hide show
  1. package/AXIOMS.md +39 -17
  2. package/PURPOSE.md +26 -11
  3. package/README.md +33 -4
  4. package/dist/host/config.d.ts +2 -0
  5. package/dist/host/config.js +8 -0
  6. package/dist/host/main.js +10 -5
  7. package/dist/host/server.d.ts +2 -0
  8. package/dist/host/server.js +25 -7
  9. package/dist/src/automations.d.ts +15 -9
  10. package/dist/src/automations.js +33 -76
  11. package/dist/src/cli.d.ts +1 -1
  12. package/dist/src/cli.js +5 -2
  13. package/dist/src/client.d.ts +1 -0
  14. package/dist/src/client.js +4 -2
  15. package/dist/src/contract.d.ts +11 -31
  16. package/dist/src/crud/context.d.ts +3 -1
  17. package/dist/src/crud/context.js +2 -1
  18. package/dist/src/crud/repository.d.ts +17 -6
  19. package/dist/src/crud/runs.d.ts +1 -1
  20. package/dist/src/crud/runs.js +24 -1
  21. package/dist/src/crud/triggers.js +18 -9
  22. package/dist/src/crud.d.ts +4 -2
  23. package/dist/src/crud.js +5 -2
  24. package/dist/src/events-http.d.ts +3 -5
  25. package/dist/src/events-http.js +22 -26
  26. package/dist/src/executor.d.ts +12 -3
  27. package/dist/src/executor.js +163 -44
  28. package/dist/src/http.js +8 -1
  29. package/dist/src/index.d.ts +6 -4
  30. package/dist/src/index.js +6 -4
  31. package/dist/src/machine-client.d.ts +1 -0
  32. package/dist/src/machine-client.js +2 -0
  33. package/dist/src/machine.d.ts +2 -1
  34. package/dist/src/machine.js +6 -1
  35. package/dist/src/mcp.d.ts +3 -0
  36. package/dist/src/mcp.js +61 -50
  37. package/dist/src/plan.d.ts +3 -0
  38. package/dist/src/plan.js +48 -0
  39. package/dist/src/run-contract.d.ts +52 -0
  40. package/dist/src/run-contract.js +1 -0
  41. package/dist/src/run-journal.d.ts +8 -0
  42. package/dist/src/run-journal.js +56 -0
  43. package/dist/src/runner.d.ts +14 -0
  44. package/dist/src/runner.js +70 -0
  45. package/dist/src/schema.d.ts +31 -15
  46. package/dist/src/schema.js +27 -4
  47. package/dist/src/supabase-crud/mappers.d.ts +3 -2
  48. package/dist/src/supabase-crud/mappers.js +5 -0
  49. package/dist/src/supabase-crud/rows.d.ts +3 -0
  50. package/dist/src/supabase-crud/workflow-runs.d.ts +1 -1
  51. package/dist/src/supabase-crud/workflow-runs.js +10 -0
  52. package/dist/src/supabase-machine.js +2 -0
  53. package/dist/src/supabase-store.d.ts +3 -6
  54. package/dist/src/supabase-store.js +13 -34
  55. package/dist/src/tool-surface.d.ts +47 -0
  56. package/dist/src/tool-surface.js +126 -0
  57. package/dist/src/types.d.ts +8 -24
  58. package/dist/src/webhook.d.ts +15 -1
  59. package/dist/src/webhook.js +86 -0
  60. package/package.json +3 -3
  61. package/skills/automations/SKILL.md +29 -16
  62. package/supabase/migrations/20260830010000_durable_step_retries.sql +332 -0
  63. package/supabase/migrations/20260831010000_public_webhook_endpoints.sql +287 -0
  64. 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
+ }
@@ -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
- source?: string | undefined;
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
- source?: string | undefined;
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;
@@ -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
- return parse(createWorkflowSchema, value);
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
- return parse(updateWorkflowSchema, value);
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, WebhookTrigger, Workflow } from '../contract.js';
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): WebhookTrigger;
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, AutomationTarget, AutomationRun, DueCronTrigger, RunInput, RunUpdate, StoredEventTrigger } from './types.js';
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
- eventTriggers(target: AutomationTarget): Promise<StoredEventTrigger[]>;
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 eventTriggers(target) {
26
- const rows = await this.#call('list_amalgm_event_triggers', {
27
- p_user_id: target.userId,
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
- return rows.map((row) => ({
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 {};