@amalgm/automations 0.2.2 → 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 +29 -23
- package/PURPOSE.md +15 -10
- package/README.md +28 -2
- package/dist/host/config.d.ts +2 -0
- package/dist/host/config.js +8 -0
- package/dist/host/main.js +4 -10
- package/dist/host/server.js +1 -1
- package/dist/src/automations.d.ts +12 -3
- package/dist/src/automations.js +21 -12
- package/dist/src/cli.d.ts +1 -1
- package/dist/src/cli.js +5 -2
- package/dist/src/client.js +1 -0
- package/dist/src/contract.d.ts +11 -4
- 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 +18 -26
- package/dist/src/http.js +4 -1
- package/dist/src/index.d.ts +4 -4
- package/dist/src/index.js +2 -2
- package/dist/src/mcp.js +9 -1
- package/dist/src/run-contract.d.ts +6 -0
- package/dist/src/schema.d.ts +29 -13
- package/dist/src/schema.js +10 -2
- package/dist/src/supabase-crud/mappers.d.ts +3 -2
- package/dist/src/supabase-crud/mappers.js +3 -0
- package/dist/src/supabase-crud/rows.d.ts +1 -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-store.d.ts +3 -3
- package/dist/src/supabase-store.js +13 -10
- package/dist/src/tool-surface.d.ts +1 -0
- package/dist/src/tool-surface.js +1 -0
- package/dist/src/types.d.ts +7 -8
- package/dist/src/webhook.d.ts +15 -1
- package/dist/src/webhook.js +86 -0
- package/package.json +1 -1
- package/skills/automations/SKILL.md +12 -4
- package/supabase/migrations/20260831010000_public_webhook_endpoints.sql +287 -0
- 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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
+
}
|
package/dist/src/crud.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
7
|
+
runId?: string;
|
|
9
8
|
status: 'pending' | 'unmatched';
|
|
10
9
|
}
|
|
11
10
|
export interface AutomationEventsApiConfig {
|
|
12
11
|
delivery: Pick<Automations, 'receiveEvent'>;
|
|
13
|
-
|
|
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;
|
package/dist/src/events-http.js
CHANGED
|
@@ -1,55 +1,47 @@
|
|
|
1
|
-
import { Automations, EventRejectedError } from './automations.js';
|
|
2
|
-
import {
|
|
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
|
|
5
|
-
//
|
|
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
|
-
|
|
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
|
|
29
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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,
|
package/dist/src/index.d.ts
CHANGED
|
@@ -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,
|
|
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
|
+
}
|
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>;
|
|
@@ -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;
|
package/dist/src/schema.js
CHANGED
|
@@ -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,
|
|
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),
|
|
@@ -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,
|
|
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
|
-
|
|
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
|
|
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,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 {};
|
package/dist/src/tool-surface.js
CHANGED
|
@@ -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
|
}
|