@amalgm/automations 0.2.2 → 0.2.4

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 (46) hide show
  1. package/AXIOMS.md +31 -23
  2. package/PURPOSE.md +16 -10
  3. package/README.md +30 -2
  4. package/dist/host/config.d.ts +2 -0
  5. package/dist/host/config.js +8 -0
  6. package/dist/host/main.js +4 -10
  7. package/dist/host/server.js +1 -1
  8. package/dist/src/automations.d.ts +12 -3
  9. package/dist/src/automations.js +18 -16
  10. package/dist/src/cli.d.ts +1 -1
  11. package/dist/src/cli.js +5 -2
  12. package/dist/src/client.js +1 -0
  13. package/dist/src/contract.d.ts +11 -4
  14. package/dist/src/crud/context.d.ts +3 -1
  15. package/dist/src/crud/context.js +2 -1
  16. package/dist/src/crud/repository.d.ts +17 -6
  17. package/dist/src/crud/runs.d.ts +1 -1
  18. package/dist/src/crud/runs.js +24 -1
  19. package/dist/src/crud/triggers.js +18 -9
  20. package/dist/src/crud.d.ts +4 -2
  21. package/dist/src/crud.js +5 -2
  22. package/dist/src/events-http.d.ts +3 -5
  23. package/dist/src/events-http.js +18 -26
  24. package/dist/src/http.js +4 -1
  25. package/dist/src/index.d.ts +4 -4
  26. package/dist/src/index.js +2 -2
  27. package/dist/src/mcp.js +9 -1
  28. package/dist/src/run-contract.d.ts +6 -0
  29. package/dist/src/schema.d.ts +29 -13
  30. package/dist/src/schema.js +10 -2
  31. package/dist/src/supabase-crud/mappers.d.ts +3 -2
  32. package/dist/src/supabase-crud/mappers.js +3 -0
  33. package/dist/src/supabase-crud/rows.d.ts +1 -0
  34. package/dist/src/supabase-crud/workflow-runs.d.ts +1 -1
  35. package/dist/src/supabase-crud/workflow-runs.js +10 -0
  36. package/dist/src/supabase-store.d.ts +3 -3
  37. package/dist/src/supabase-store.js +13 -10
  38. package/dist/src/tool-surface.d.ts +1 -0
  39. package/dist/src/tool-surface.js +1 -0
  40. package/dist/src/types.d.ts +7 -8
  41. package/dist/src/webhook.d.ts +15 -1
  42. package/dist/src/webhook.js +86 -0
  43. package/package.json +1 -1
  44. package/skills/automations/SKILL.md +12 -4
  45. package/supabase/migrations/20260831010000_public_webhook_endpoints.sql +287 -0
  46. package/supabase/migrations/20260831020000_manual_run_admission.sql +68 -0
@@ -3,7 +3,7 @@ import { parseCreateScheduleTrigger, parseCreateWebhookTrigger, parsePageQuery,
3
3
  import { nextCronAt } from '../schedule.js';
4
4
  import { id, newId, page, schedule } from '../validation.js';
5
5
  export function triggerOperations(context) {
6
- const { principal, repository } = context;
6
+ const { principal, repository, webhooks } = context;
7
7
  const unique = async (automationId, triggerId) => {
8
8
  const exists = (await repository.listTriggers(principal.userId, automationId))
9
9
  .some((trigger) => trigger.id === triggerId);
@@ -14,7 +14,7 @@ export function triggerOperations(context) {
14
14
  list: async (automationId) => {
15
15
  context.read();
16
16
  await context.exists(automationId);
17
- return repository.listTriggers(principal.userId, automationId);
17
+ return (await repository.listTriggers(principal.userId, automationId)).map((trigger) => (trigger.kind === 'webhook' ? publicWebhook(trigger, webhooks) : trigger));
18
18
  },
19
19
  schedule: {
20
20
  create: async (automationId, input) => {
@@ -80,31 +80,36 @@ export function triggerOperations(context) {
80
80
  const parsed = parseCreateWebhookTrigger(input);
81
81
  if (parsed.id)
82
82
  await unique(automationId, parsed.id);
83
- return repository.createWebhookTrigger(principal.userId, automationId, {
83
+ return publicWebhook(await repository.createWebhookTrigger(principal.userId, automationId, {
84
84
  id: parsed.id ? id(parsed.id, 'Webhook trigger id') : newId('webhook'),
85
85
  source: parsed.source || '*',
86
86
  event: parsed.event || '*',
87
- secret: parsed.secret,
87
+ secret: parsed.secret ?? null,
88
88
  enabled: parsed.enabled !== false,
89
- });
89
+ endpointId: webhooks.createId(),
90
+ }), webhooks);
90
91
  },
91
92
  list: async (automationId, query = {}) => {
92
93
  context.read();
93
94
  await context.exists(automationId);
94
- return repository.listWebhookTriggers(principal.userId, automationId, page(parsePageQuery(query)));
95
+ const result = await repository.listWebhookTriggers(principal.userId, automationId, page(parsePageQuery(query)));
96
+ return { ...result, items: result.items.map((trigger) => publicWebhook(trigger, webhooks)) };
95
97
  },
96
98
  get: async (automationId, triggerId) => {
97
99
  context.read();
98
100
  await context.exists(automationId);
99
- return repository.getWebhookTrigger(principal.userId, automationId, id(triggerId, 'Webhook trigger id'));
101
+ const trigger = await repository.getWebhookTrigger(principal.userId, automationId, id(triggerId, 'Webhook trigger id'));
102
+ return trigger ? publicWebhook(trigger, webhooks) : null;
100
103
  },
101
104
  update: async (automationId, triggerId, patch) => {
102
105
  context.write();
103
106
  await context.exists(automationId);
104
- const updated = await repository.updateWebhookTrigger(principal.userId, automationId, id(triggerId, 'Webhook trigger id'), parseUpdateWebhookTrigger(patch));
107
+ const parsed = parseUpdateWebhookTrigger(patch);
108
+ const { rotateUrl, ...changes } = parsed;
109
+ const updated = await repository.updateWebhookTrigger(principal.userId, automationId, id(triggerId, 'Webhook trigger id'), { ...changes, ...(rotateUrl ? { endpointId: webhooks.createId() } : {}) });
105
110
  if (!updated)
106
111
  throw new NotFoundError('Webhook trigger');
107
- return updated;
112
+ return publicWebhook(updated, webhooks);
108
113
  },
109
114
  delete: async (automationId, triggerId) => {
110
115
  context.write();
@@ -116,3 +121,7 @@ export function triggerOperations(context) {
116
121
  },
117
122
  };
118
123
  }
124
+ function publicWebhook(trigger, webhooks) {
125
+ const { endpointId, ...publicTrigger } = trigger;
126
+ return { ...publicTrigger, webhookUrl: webhooks.url(endpointId) };
127
+ }
@@ -1,9 +1,11 @@
1
1
  import type { AutomationCrud, AutomationCrudService as AutomationCrudServiceContract, AutomationPrincipal } from './contract.js';
2
2
  import type { AutomationCrudRepository } from './crud/repository.js';
3
- export type { AutomationCrudRepository, NormalizedListAutomations, NormalizedListRuns, StoredScheduleCreate, StoredScheduleUpdate, } from './crud/repository.js';
3
+ import { WebhookEndpoints } from './webhook.js';
4
+ export type { AutomationCrudRepository, NormalizedListAutomations, NormalizedListRuns, StoredScheduleCreate, StoredScheduleUpdate, StoredWebhookCreate, StoredWebhookUpdate, WebhookTriggerRecord, } from './crud/repository.js';
4
5
  export declare class AutomationCrudService implements AutomationCrudServiceContract {
6
+ #private;
5
7
  private readonly repository;
6
8
  private readonly clock;
7
- constructor(repository: AutomationCrudRepository, clock?: () => Date);
9
+ constructor(repository: AutomationCrudRepository, clock?: () => Date, webhooks?: WebhookEndpoints);
8
10
  for(principal: AutomationPrincipal): AutomationCrud;
9
11
  }
package/dist/src/crud.js CHANGED
@@ -3,15 +3,18 @@ import { createContext } from './crud/context.js';
3
3
  import { runOperations } from './crud/runs.js';
4
4
  import { triggerOperations } from './crud/triggers.js';
5
5
  import { workflowOperations } from './crud/workflow.js';
6
+ import { WebhookEndpoints } from './webhook.js';
6
7
  export class AutomationCrudService {
7
8
  repository;
8
9
  clock;
9
- constructor(repository, clock = () => new Date()) {
10
+ #webhooks;
11
+ constructor(repository, clock = () => new Date(), webhooks) {
10
12
  this.repository = repository;
11
13
  this.clock = clock;
14
+ this.#webhooks = webhooks ?? new WebhookEndpoints('http://automations.local', Buffer.from('local-automations-webhook-key-32-bytes-minimum'));
12
15
  }
13
16
  for(principal) {
14
- const context = createContext(this.repository, principal, this.clock);
17
+ const context = createContext(this.repository, principal, this.clock, this.#webhooks);
15
18
  return {
16
19
  automations: automationOperations(context),
17
20
  triggers: triggerOperations(context),
@@ -1,19 +1,17 @@
1
1
  import { Automations } from './automations.js';
2
- import type { AutomationTarget } from './types.js';
2
+ import { type WebhookEndpoints } from './webhook.js';
3
3
  export interface AutomationEventReceipt {
4
4
  receivedAt: string;
5
- targetId: string;
6
5
  source: string;
7
6
  event: string;
8
- runIds: string[];
7
+ runId?: string;
9
8
  status: 'pending' | 'unmatched';
10
9
  }
11
10
  export interface AutomationEventsApiConfig {
12
11
  delivery: Pick<Automations, 'receiveEvent'>;
13
- target(request: Request): Promise<AutomationTarget>;
12
+ endpoints: Pick<WebhookEndpoints, 'resolve'>;
14
13
  now?: () => Date;
15
14
  maxBodyBytes?: number;
16
- recentLimit?: number;
17
15
  }
18
16
  export type AutomationEventsApi = (request: Request) => Promise<Response>;
19
17
  export declare function createAutomationEventsApi(config: AutomationEventsApiConfig): AutomationEventsApi;
@@ -1,55 +1,47 @@
1
- import { Automations, EventRejectedError } from './automations.js';
2
- import { eventReference } from './webhook.js';
1
+ import { Automations, EventEndpointNotFoundError, EventRejectedError } from './automations.js';
2
+ import { webhookDeliveryKey } from './webhook.js';
3
3
  // Engine reference: amalgm-engine/runtime/scripts/amalgm-mcp/events/ingress.js.
4
- // The 2 MiB transport bound and safe recent-events door are preserved. The
5
- // authority changes deliberately: success now means matching runs are durable
6
- // in Supabase, never that an envelope was written to a local SQLite inbox.
4
+ // The 2 MiB transport bound is preserved. Success means the matching run is
5
+ // durable in Supabase, never that an envelope was written to a local inbox.
7
6
  const DEFAULT_MAX_BODY_BYTES = 2 * 1024 * 1024;
8
- const DEFAULT_RECENT_LIMIT = 200;
9
7
  export function createAutomationEventsApi(config) {
10
- const recent = [];
11
8
  const now = config.now ?? (() => new Date());
12
9
  const maxBodyBytes = positiveInteger(config.maxBodyBytes, DEFAULT_MAX_BODY_BYTES);
13
- const recentLimit = positiveInteger(config.recentLimit, DEFAULT_RECENT_LIMIT);
14
10
  return async (request) => {
15
11
  const pathname = new URL(request.url).pathname;
16
- if (pathname !== '/events')
12
+ const token = pathname.match(/^\/e\/([^/]+)$/)?.[1];
13
+ if (!token)
17
14
  return json(404, { error: 'not found' });
18
- if (request.method === 'GET')
19
- return json(200, { events: recent.slice(-50) });
20
15
  if (request.method !== 'POST')
21
16
  return json(405, { error: 'method not allowed' });
22
17
  try {
18
+ const endpointId = config.endpoints.resolve(token);
19
+ if (!endpointId)
20
+ throw new EventEndpointNotFoundError();
23
21
  const body = await readLimitedBody(request, maxBodyBytes);
24
22
  const payload = parsePayload(body);
25
23
  const headers = Object.fromEntries(request.headers.entries());
26
- const target = await config.target(request);
27
24
  const receivedAt = now();
28
- const runs = await config.delivery.receiveEvent({
29
- target,
25
+ const admission = await config.delivery.receiveEvent({
26
+ endpointId,
30
27
  headers,
31
28
  body,
32
29
  payload,
30
+ deliveryKey: webhookDeliveryKey(headers, payload),
33
31
  now: receivedAt,
34
32
  });
35
- const firstInput = runs[0]?.input;
36
- const reference = firstInput?.kind === 'event'
37
- ? { source: firstInput.source, event: firstInput.event }
38
- : eventReference(headers, payload);
39
33
  const receipt = {
40
34
  receivedAt: receivedAt.toISOString(),
41
- targetId: target.targetId,
42
- source: reference.source,
43
- event: reference.event,
44
- runIds: runs.map(({ id }) => id),
45
- status: runs.length ? 'pending' : 'unmatched',
35
+ source: admission.source,
36
+ event: admission.event,
37
+ ...(admission.run ? { runId: admission.run.id } : {}),
38
+ status: admission.run ? 'pending' : 'unmatched',
46
39
  };
47
- recent.push(receipt);
48
- if (recent.length > recentLimit)
49
- recent.splice(0, recent.length - recentLimit);
50
40
  return json(202, { ok: true, accepted: true, ...receipt });
51
41
  }
52
42
  catch (error) {
43
+ if (error instanceof EventEndpointNotFoundError)
44
+ return json(404, { error: error.message });
53
45
  if (error instanceof EventRejectedError)
54
46
  return json(401, { error: error.message });
55
47
  if (error instanceof EventHttpError)
package/dist/src/http.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AutomationError } from './errors.js';
2
- import { parseCreateAutomation, parseCreateScheduleTrigger, parseCreateWebhookTrigger, parseCreateWorkflow, parseListAutomations, parseListRuns, parsePageQuery, parseUpdateAutomation, parseUpdateScheduleTrigger, parseUpdateWebhookTrigger, parseUpdateWorkflow, } from './schema.js';
2
+ import { parseCreateAutomation, parseCreateScheduleTrigger, parseCreateWebhookTrigger, parseCreateWorkflow, parseListAutomations, parseListRuns, parseRunAutomationNow, parsePageQuery, parseUpdateAutomation, parseUpdateScheduleTrigger, parseUpdateWebhookTrigger, parseUpdateWorkflow, } from './schema.js';
3
3
  export function createAutomationApi(options) {
4
4
  return async (request) => {
5
5
  try {
@@ -85,6 +85,9 @@ export function createAutomationApi(options) {
85
85
  }
86
86
  }
87
87
  if (parts[3] === 'runs') {
88
+ if (parts.length === 4 && method === 'POST') {
89
+ return response(202, await sdk.runs.runNow(automationId, parseRunAutomationNow(await body(request))));
90
+ }
88
91
  if (parts.length === 4 && method === 'GET') {
89
92
  return response(200, await sdk.runs.list(automationId, parseListRuns({
90
93
  ...query,
@@ -5,11 +5,11 @@ export { createAutomationApi, type AuthenticateAutomationRequest, type Automatio
5
5
  export { runAutomationCli } from './cli.js';
6
6
  export { createAutomationMcpServer } from './mcp.js';
7
7
  export { SupabaseAutomationCrudRepository, type SupabaseRpcClient } from './supabase-crud.js';
8
- export type { Automation, AutomationCrud, AutomationCrudService as AutomationCrudServiceContract, AutomationPrincipal, AutomationPlan, AutomationRunJournal, AutomationStepRun, AutomationStepStatus, AutomationRun, AutomationScope, CreateAutomation, CreateScheduleTrigger, CreateWebhookTrigger, CreateWorkflow, Json, ListAutomations, ListRuns, Page, PageQuery, RunStatus, ScheduleTrigger, Trigger, ToolActionStep, UpdateAutomation, UpdateScheduleTrigger, UpdateWebhookTrigger, UpdateWorkflow, WebhookTrigger, Workflow, } from './contract.js';
9
- export { Automations, EventRejectedError } from './automations.js';
8
+ export type { Automation, AutomationCrud, AutomationCrudService as AutomationCrudServiceContract, AutomationPrincipal, AutomationPlan, AutomationRunJournal, AutomationStepRun, AutomationStepStatus, AutomationRun, AutomationScope, CreateAutomation, CreateScheduleTrigger, CreateWebhookTrigger, CreateWorkflow, Json, ListAutomations, ListRuns, Page, PageQuery, RunStatus, RunAutomationNow, ScheduleTrigger, Trigger, ToolActionStep, UpdateAutomation, UpdateScheduleTrigger, UpdateWebhookTrigger, UpdateWorkflow, WebhookTrigger, Workflow, } from './contract.js';
9
+ export { Automations, EventEndpointNotFoundError, EventRejectedError } from './automations.js';
10
10
  export { createAutomationEventsApi, type AutomationEventReceipt, type AutomationEventsApi, type AutomationEventsApiConfig, } from './events-http.js';
11
11
  export { nextCronAt, validateCron } from './schedule.js';
12
- export { eventReference, eventReferences, matchesEvent, normalizeHeaders, verifyEventSecret, } from './webhook.js';
12
+ export { eventReference, eventReferences, matchesEvent, normalizeHeaders, verifyEventSecret, webhookDeliveryKey, WebhookEndpoints, } from './webhook.js';
13
13
  export { SupabaseStore } from './supabase-store.js';
14
14
  export { SupabaseMachineRunRepository, type MachineRpcClient } from './supabase-machine.js';
15
15
  export { createMachineRuns } from './machine.js';
@@ -20,4 +20,4 @@ export { AutomationRunExecutor, automationPlan, type AutomationActionPort } from
20
20
  export { createAutomationMachineRunner, type AutomationMachineRunnerOptions, } from './runner.js';
21
21
  export { compiledAutomationPlan } from './plan.js';
22
22
  export type { SupabaseRpcClient as SupabaseStoreRpcClient } from './supabase-store.js';
23
- export type { AutomationLog, AutomationRun as AutomationRunRecord, AutomationStore, AutomationTarget, DueCronTrigger, RunInput, StoredEventTrigger, } from './types.js';
23
+ export type { AutomationLog, AutomationRun as AutomationRunRecord, AutomationStore, DueCronTrigger, RunInput, StoredEventTrigger, } from './types.js';
package/dist/src/index.js CHANGED
@@ -12,10 +12,10 @@ export { createAutomationApi } from './http.js';
12
12
  export { runAutomationCli } from './cli.js';
13
13
  export { createAutomationMcpServer } from './mcp.js';
14
14
  export { SupabaseAutomationCrudRepository } from './supabase-crud.js';
15
- export { Automations, EventRejectedError } from './automations.js';
15
+ export { Automations, EventEndpointNotFoundError, EventRejectedError } from './automations.js';
16
16
  export { createAutomationEventsApi, } from './events-http.js';
17
17
  export { nextCronAt, validateCron } from './schedule.js';
18
- export { eventReference, eventReferences, matchesEvent, normalizeHeaders, verifyEventSecret, } from './webhook.js';
18
+ export { eventReference, eventReferences, matchesEvent, normalizeHeaders, verifyEventSecret, webhookDeliveryKey, WebhookEndpoints, } from './webhook.js';
19
19
  export { SupabaseStore } from './supabase-store.js';
20
20
  export { SupabaseMachineRunRepository } from './supabase-machine.js';
21
21
  export { createMachineRuns } from './machine.js';
package/dist/src/mcp.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { z } from 'zod/v3';
3
- import { createAutomationSchema, createScheduleTriggerSchema, createWebhookTriggerSchema, createWorkflowSchema, identifierSchema, listAutomationsSchema, listRunsSchema, updateAutomationSchema, updateScheduleTriggerSchema, updateWebhookTriggerSchema, updateWorkflowSchema, } from './schema.js';
3
+ import { createAutomationSchema, createScheduleTriggerSchema, createWebhookTriggerSchema, createWorkflowSchema, identifierSchema, listAutomationsSchema, listRunsSchema, runAutomationNowSchema, updateAutomationSchema, updateScheduleTriggerSchema, updateWebhookTriggerSchema, updateWorkflowSchema, } from './schema.js';
4
4
  import { createAutomationToolSurface } from './tool-surface.js';
5
5
  export { createAutomationToolSurface };
6
6
  const resultSchema = { result: z.unknown() };
@@ -46,6 +46,14 @@ export function createAutomationMcpServer(sdk) {
46
46
  register(server, 'amalgm_automations_delete', 'Delete automation', 'Delete an automation and its current triggers and workflow. Historical runs remain.', {
47
47
  automation_id: identifierSchema,
48
48
  }, destructive(), ({ automation_id }) => tools.delete(automation_id));
49
+ register(server, 'amalgm_automations_run_now', 'Run automation now', 'Durably admit one manual run using the automation\'s current enabled configuration and compiled workflow. The selected machine executes it through the normal pending-run queue; this call never executes the workflow inline.', {
50
+ automation_id: identifierSchema,
51
+ input: runAutomationNowSchema.shape.input,
52
+ idempotency_key: runAutomationNowSchema.shape.idempotencyKey,
53
+ }, write(false), ({ automation_id, input, idempotency_key }) => tools.runNow(automation_id, {
54
+ ...(input === undefined ? {} : { input }),
55
+ ...(idempotency_key === undefined ? {} : { idempotencyKey: idempotency_key }),
56
+ }));
49
57
  return server;
50
58
  }
51
59
  function register(server, name, title, description, inputSchema, annotations, handler) {
@@ -44,3 +44,9 @@ export interface AutomationRun {
44
44
  export interface ListRuns extends PageQuery {
45
45
  status?: RunStatus;
46
46
  }
47
+ export interface RunAutomationNow {
48
+ /** Trigger payload recorded with the immutable run snapshot. */
49
+ input?: Json;
50
+ /** Reusing this key for the same automation returns the same admitted run. */
51
+ idempotencyKey?: string;
52
+ }
@@ -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>;
@@ -173,15 +178,15 @@ export declare const createWorkflowSchema: z.ZodObject<{
173
178
  }, "strict", z.ZodTypeAny, {
174
179
  script: string;
175
180
  name?: string | undefined;
176
- compiled?: Json | undefined;
177
181
  id?: string | undefined;
182
+ compiled?: Json | undefined;
178
183
  allowlist?: Json | undefined;
179
184
  limits?: Json | undefined;
180
185
  }, {
181
186
  script: string;
182
187
  name?: string | undefined;
183
- compiled?: Json | undefined;
184
188
  id?: string | undefined;
189
+ compiled?: Json | undefined;
185
190
  allowlist?: Json | undefined;
186
191
  limits?: Json | undefined;
187
192
  }>;
@@ -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;
@@ -54,13 +54,14 @@ export const createWebhookTriggerSchema = z.object({
54
54
  id: identifierSchema.optional(),
55
55
  source: text(200, 'Webhook source').optional(),
56
56
  event: text(200, 'Webhook event').optional(),
57
- 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(),
58
58
  enabled: z.boolean().optional(),
59
59
  }).strict();
60
60
  export const updateWebhookTriggerSchema = z.object({
61
61
  source: text(200, 'Webhook source').optional(),
62
62
  event: text(200, 'Webhook event').optional(),
63
- 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(),
64
65
  enabled: z.boolean().optional(),
65
66
  }).strict().refine((patch) => Object.keys(patch).length > 0, 'Webhook trigger update must include a change');
66
67
  export const createWorkflowSchema = z.object({
@@ -81,6 +82,10 @@ export const updateWorkflowSchema = z.object({
81
82
  export const listRunsSchema = pageQuerySchema.extend({
82
83
  status: z.enum(['pending', 'sent', 'running', 'completed', 'failed']).optional(),
83
84
  }).strict();
85
+ export const runAutomationNowSchema = z.object({
86
+ input: jsonSchema.optional(),
87
+ idempotencyKey: z.string().min(1).max(500).optional(),
88
+ }).strict();
84
89
  export function parseCreateAutomation(value) {
85
90
  return parse(createAutomationSchema, value);
86
91
  }
@@ -120,6 +125,9 @@ export function parsePageQuery(value) {
120
125
  export function parseListRuns(value) {
121
126
  return parse(listRunsSchema, value);
122
127
  }
128
+ export function parseRunAutomationNow(value) {
129
+ return parse(runAutomationNowSchema, value);
130
+ }
123
131
  export function parse(schema, value) {
124
132
  const result = schema.safeParse(value);
125
133
  if (result.success)
@@ -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),
@@ -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;
@@ -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
  }
@@ -1,4 +1,4 @@
1
- import type { AutomationStore, AutomationTarget, AutomationRun, DueCronTrigger, RunInput, 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,10 +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
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,22 +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
+ }, now, nextRunAt, null);
63
64
  }
64
- async #enqueue(trigger, input, now, nextRunAt) {
65
+ async #enqueue(trigger, input, now, nextRunAt, deliveryKey) {
65
66
  const rows = await this.#call('enqueue_amalgm_run', {
66
67
  p_kind: trigger.kind,
67
68
  p_user_id: trigger.userId,
68
69
  p_automation_id: trigger.automationId,
69
70
  p_trigger_id: trigger.triggerId,
70
71
  p_target_id: trigger.targetId,
72
+ p_endpoint_id: trigger.kind === 'event' ? trigger.endpointId : null,
73
+ p_delivery_key: deliveryKey,
71
74
  p_expected_next_run_at: trigger.kind === 'cron' ? trigger.nextRunAt : null,
72
75
  p_next_run_at: nextRunAt,
73
76
  p_input: input,
@@ -41,6 +41,7 @@ export interface AutomationToolSurface {
41
41
  delete(automationId: string): Promise<{
42
42
  deleted: string;
43
43
  }>;
44
+ runNow(automationId: string, input?: Parameters<AutomationCrud['runs']['runNow']>[1]): Promise<AutomationRun>;
44
45
  }
45
46
  export declare function createAutomationToolSurface(sdk: AutomationCrud): AutomationToolSurface;
46
47
  export {};
@@ -69,6 +69,7 @@ export function createAutomationToolSurface(sdk) {
69
69
  await sdk.automations.delete(automationId);
70
70
  return { deleted: automationId };
71
71
  },
72
+ runNow: (automationId, input = {}) => sdk.runs.runNow(automationId, input),
72
73
  };
73
74
  return Object.freeze(surface);
74
75
  }