@amalgm/automations 0.1.0
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 +32 -0
- package/PURPOSE.md +35 -0
- package/README.md +130 -0
- package/dist/src/automations.d.ts +27 -0
- package/dist/src/automations.js +199 -0
- package/dist/src/cli-main.d.ts +2 -0
- package/dist/src/cli-main.js +21 -0
- package/dist/src/cli.d.ts +10 -0
- package/dist/src/cli.js +183 -0
- package/dist/src/client.d.ts +7 -0
- package/dist/src/client.js +85 -0
- package/dist/src/contract.d.ts +176 -0
- package/dist/src/contract.js +1 -0
- package/dist/src/crud.d.ts +32 -0
- package/dist/src/crud.js +241 -0
- package/dist/src/errors.d.ts +17 -0
- package/dist/src/errors.js +32 -0
- package/dist/src/http.d.ts +7 -0
- package/dist/src/http.js +154 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.js +18 -0
- package/dist/src/mcp-main.d.ts +2 -0
- package/dist/src/mcp-main.js +17 -0
- package/dist/src/mcp.d.ts +3 -0
- package/dist/src/mcp.js +88 -0
- package/dist/src/schedule.d.ts +2 -0
- package/dist/src/schedule.js +10 -0
- package/dist/src/schema.d.ts +236 -0
- package/dist/src/schema.js +119 -0
- package/dist/src/supabase-crud.d.ts +44 -0
- package/dist/src/supabase-crud.js +271 -0
- package/dist/src/supabase-store.d.ts +26 -0
- package/dist/src/supabase-store.js +123 -0
- package/dist/src/types.d.ts +128 -0
- package/dist/src/types.js +1 -0
- package/dist/src/validation.d.ts +11 -0
- package/dist/src/validation.js +69 -0
- package/dist/src/webhook.d.ts +18 -0
- package/dist/src/webhook.js +60 -0
- package/package.json +66 -0
- package/supabase/migrations/20260802000000_create_automations.sql +609 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { AutomationError } from './errors.js';
|
|
2
|
+
export function createAutomationClient(options) {
|
|
3
|
+
const baseUrl = options.baseUrl.replace(/\/$/, '');
|
|
4
|
+
const request = createRequester(baseUrl, options.authorization, options.fetch || globalThis.fetch);
|
|
5
|
+
return {
|
|
6
|
+
automations: {
|
|
7
|
+
create: (input) => request('/v1/automations', 'POST', input),
|
|
8
|
+
list: (query = {}) => request(`/v1/automations${queryString(query)}`, 'GET'),
|
|
9
|
+
get: (automationId) => request(`/v1/automations/${part(automationId)}`, 'GET', undefined, true),
|
|
10
|
+
update: (automationId, patch) => request(`/v1/automations/${part(automationId)}`, 'PATCH', patch),
|
|
11
|
+
delete: (automationId) => request(`/v1/automations/${part(automationId)}`, 'DELETE'),
|
|
12
|
+
},
|
|
13
|
+
triggers: {
|
|
14
|
+
list: (automationId) => request(`/v1/automations/${part(automationId)}/triggers`, 'GET'),
|
|
15
|
+
schedule: {
|
|
16
|
+
create: (automationId, input) => request(schedulePath(automationId), 'POST', input),
|
|
17
|
+
list: (automationId, query = {}) => request(`${schedulePath(automationId)}${queryString(query)}`, 'GET'),
|
|
18
|
+
get: (automationId, triggerId) => request(`${schedulePath(automationId)}/${part(triggerId)}`, 'GET', undefined, true),
|
|
19
|
+
update: (automationId, triggerId, patch) => request(`${schedulePath(automationId)}/${part(triggerId)}`, 'PATCH', patch),
|
|
20
|
+
delete: (automationId, triggerId) => request(`${schedulePath(automationId)}/${part(triggerId)}`, 'DELETE'),
|
|
21
|
+
},
|
|
22
|
+
webhook: {
|
|
23
|
+
create: (automationId, input) => request(webhookPath(automationId), 'POST', input),
|
|
24
|
+
list: (automationId, query = {}) => request(`${webhookPath(automationId)}${queryString(query)}`, 'GET'),
|
|
25
|
+
get: (automationId, triggerId) => request(`${webhookPath(automationId)}/${part(triggerId)}`, 'GET', undefined, true),
|
|
26
|
+
update: (automationId, triggerId, patch) => request(`${webhookPath(automationId)}/${part(triggerId)}`, 'PATCH', patch),
|
|
27
|
+
delete: (automationId, triggerId) => request(`${webhookPath(automationId)}/${part(triggerId)}`, 'DELETE'),
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
workflow: {
|
|
31
|
+
create: (automationId, input) => request(workflowPath(automationId), 'POST', input),
|
|
32
|
+
get: (automationId) => request(workflowPath(automationId), 'GET', undefined, true),
|
|
33
|
+
update: (automationId, patch) => request(workflowPath(automationId), 'PATCH', patch),
|
|
34
|
+
delete: (automationId) => request(workflowPath(automationId), 'DELETE'),
|
|
35
|
+
},
|
|
36
|
+
runs: {
|
|
37
|
+
list: (automationId, query = {}) => request(`${runsPath(automationId)}${queryString(query)}`, 'GET'),
|
|
38
|
+
get: (automationId, runId) => request(`${runsPath(automationId)}/${part(runId)}`, 'GET', undefined, true),
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function createRequester(baseUrl, authorization, fetch) {
|
|
43
|
+
return async (path, method, body, nullable = false) => {
|
|
44
|
+
const token = await authorization();
|
|
45
|
+
const response = await fetch(`${baseUrl}${path}`, {
|
|
46
|
+
method,
|
|
47
|
+
headers: {
|
|
48
|
+
accept: 'application/json',
|
|
49
|
+
...(body === undefined ? {} : { 'content-type': 'application/json' }),
|
|
50
|
+
...(token ? { authorization: token } : {}),
|
|
51
|
+
},
|
|
52
|
+
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
53
|
+
});
|
|
54
|
+
if (response.status === 204)
|
|
55
|
+
return undefined;
|
|
56
|
+
const payload = await response.json().catch(() => ({}));
|
|
57
|
+
if (response.status === 404 && nullable)
|
|
58
|
+
return null;
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
throw new AutomationError(payload.code === 'conflict' || payload.code === 'forbidden' || payload.code === 'internal' || payload.code === 'not_found'
|
|
61
|
+
? payload.code
|
|
62
|
+
: response.status >= 500 ? 'internal' : 'validation', payload.error || `Automations API returned ${response.status}`);
|
|
63
|
+
}
|
|
64
|
+
return payload;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function part(value) {
|
|
68
|
+
return encodeURIComponent(value);
|
|
69
|
+
}
|
|
70
|
+
function schedulePath(automationId) {
|
|
71
|
+
return `/v1/automations/${part(automationId)}/triggers/schedules`;
|
|
72
|
+
}
|
|
73
|
+
function webhookPath(automationId) {
|
|
74
|
+
return `/v1/automations/${part(automationId)}/triggers/webhooks`;
|
|
75
|
+
}
|
|
76
|
+
function workflowPath(automationId) {
|
|
77
|
+
return `/v1/automations/${part(automationId)}/workflow`;
|
|
78
|
+
}
|
|
79
|
+
function runsPath(automationId) {
|
|
80
|
+
return `/v1/automations/${part(automationId)}/runs`;
|
|
81
|
+
}
|
|
82
|
+
function queryString(query) {
|
|
83
|
+
const entries = Object.entries(query).filter(([, value]) => value !== undefined);
|
|
84
|
+
return entries.length === 0 ? '' : `?${new URLSearchParams(entries.map(([key, value]) => [key, String(value)])).toString()}`;
|
|
85
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
export type Json = null | boolean | number | string | Json[] | {
|
|
2
|
+
[key: string]: Json;
|
|
3
|
+
};
|
|
4
|
+
export type AutomationScope = 'automations:read' | 'automations:write' | 'runs:read' | '*';
|
|
5
|
+
/** Identity resolved by Amalgm before the SDK is bound to a caller. */
|
|
6
|
+
export interface AutomationPrincipal {
|
|
7
|
+
userId: string;
|
|
8
|
+
scopes: readonly AutomationScope[];
|
|
9
|
+
/** Omit to permit every target owned by this principal. */
|
|
10
|
+
targetIds?: readonly string[];
|
|
11
|
+
}
|
|
12
|
+
export interface PageQuery {
|
|
13
|
+
limit?: number;
|
|
14
|
+
offset?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface Page<T> {
|
|
17
|
+
items: T[];
|
|
18
|
+
total: number;
|
|
19
|
+
limit: number;
|
|
20
|
+
offset: number;
|
|
21
|
+
hasMore: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface Automation {
|
|
24
|
+
id: string;
|
|
25
|
+
targetId: string;
|
|
26
|
+
name?: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
enabled: boolean;
|
|
29
|
+
createdAt: string;
|
|
30
|
+
updatedAt: string;
|
|
31
|
+
}
|
|
32
|
+
export interface CreateAutomation {
|
|
33
|
+
id?: string;
|
|
34
|
+
targetId: string;
|
|
35
|
+
name?: string;
|
|
36
|
+
description?: string;
|
|
37
|
+
enabled?: boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface UpdateAutomation {
|
|
40
|
+
targetId?: string;
|
|
41
|
+
name?: string | null;
|
|
42
|
+
description?: string | null;
|
|
43
|
+
enabled?: boolean;
|
|
44
|
+
}
|
|
45
|
+
export interface ListAutomations extends PageQuery {
|
|
46
|
+
targetId?: string;
|
|
47
|
+
enabled?: boolean;
|
|
48
|
+
}
|
|
49
|
+
interface TriggerBase {
|
|
50
|
+
id: string;
|
|
51
|
+
automationId: string;
|
|
52
|
+
enabled: boolean;
|
|
53
|
+
createdAt: string;
|
|
54
|
+
updatedAt: string;
|
|
55
|
+
}
|
|
56
|
+
export interface ScheduleTrigger extends TriggerBase {
|
|
57
|
+
kind: 'schedule';
|
|
58
|
+
cron: string;
|
|
59
|
+
timezone: string;
|
|
60
|
+
}
|
|
61
|
+
export interface WebhookTrigger extends TriggerBase {
|
|
62
|
+
kind: 'webhook';
|
|
63
|
+
source: string;
|
|
64
|
+
event: string;
|
|
65
|
+
secretConfigured: boolean;
|
|
66
|
+
}
|
|
67
|
+
export type Trigger = ScheduleTrigger | WebhookTrigger;
|
|
68
|
+
export interface CreateScheduleTrigger {
|
|
69
|
+
id?: string;
|
|
70
|
+
cron: string;
|
|
71
|
+
timezone?: string;
|
|
72
|
+
enabled?: boolean;
|
|
73
|
+
}
|
|
74
|
+
export interface UpdateScheduleTrigger {
|
|
75
|
+
cron?: string;
|
|
76
|
+
timezone?: string;
|
|
77
|
+
enabled?: boolean;
|
|
78
|
+
}
|
|
79
|
+
export interface CreateWebhookTrigger {
|
|
80
|
+
id?: string;
|
|
81
|
+
source?: string;
|
|
82
|
+
event?: string;
|
|
83
|
+
secret: string;
|
|
84
|
+
enabled?: boolean;
|
|
85
|
+
}
|
|
86
|
+
export interface UpdateWebhookTrigger {
|
|
87
|
+
source?: string;
|
|
88
|
+
event?: string;
|
|
89
|
+
secret?: string;
|
|
90
|
+
enabled?: boolean;
|
|
91
|
+
}
|
|
92
|
+
export interface Workflow {
|
|
93
|
+
id: string;
|
|
94
|
+
automationId: string;
|
|
95
|
+
name?: string;
|
|
96
|
+
script: string;
|
|
97
|
+
compiled?: Json;
|
|
98
|
+
allowlist?: Json;
|
|
99
|
+
limits?: Json;
|
|
100
|
+
createdAt: string;
|
|
101
|
+
updatedAt: string;
|
|
102
|
+
}
|
|
103
|
+
export interface CreateWorkflow {
|
|
104
|
+
id?: string;
|
|
105
|
+
name?: string;
|
|
106
|
+
script: string;
|
|
107
|
+
compiled?: Json;
|
|
108
|
+
allowlist?: Json;
|
|
109
|
+
limits?: Json;
|
|
110
|
+
}
|
|
111
|
+
export interface UpdateWorkflow {
|
|
112
|
+
name?: string | null;
|
|
113
|
+
script?: string;
|
|
114
|
+
compiled?: Json | null;
|
|
115
|
+
allowlist?: Json | null;
|
|
116
|
+
limits?: Json | null;
|
|
117
|
+
}
|
|
118
|
+
export type RunStatus = 'pending' | 'sent' | 'running' | 'completed' | 'failed';
|
|
119
|
+
export interface AutomationRun {
|
|
120
|
+
id: string;
|
|
121
|
+
automationId: string;
|
|
122
|
+
triggerId: string;
|
|
123
|
+
workflowId: string;
|
|
124
|
+
targetId: string;
|
|
125
|
+
status: RunStatus;
|
|
126
|
+
input: Json;
|
|
127
|
+
createdAt: string;
|
|
128
|
+
sentAt?: string;
|
|
129
|
+
startedAt?: string;
|
|
130
|
+
finishedAt?: string;
|
|
131
|
+
output?: Json;
|
|
132
|
+
error?: string;
|
|
133
|
+
}
|
|
134
|
+
export interface ListRuns extends PageQuery {
|
|
135
|
+
status?: RunStatus;
|
|
136
|
+
}
|
|
137
|
+
export interface AutomationCrud {
|
|
138
|
+
readonly automations: {
|
|
139
|
+
create(input: CreateAutomation): Promise<Automation>;
|
|
140
|
+
list(query?: ListAutomations): Promise<Page<Automation>>;
|
|
141
|
+
get(automationId: string): Promise<Automation | null>;
|
|
142
|
+
update(automationId: string, patch: UpdateAutomation): Promise<Automation>;
|
|
143
|
+
delete(automationId: string): Promise<void>;
|
|
144
|
+
};
|
|
145
|
+
readonly triggers: {
|
|
146
|
+
list(automationId: string): Promise<Trigger[]>;
|
|
147
|
+
readonly schedule: {
|
|
148
|
+
create(automationId: string, input: CreateScheduleTrigger): Promise<ScheduleTrigger>;
|
|
149
|
+
list(automationId: string, query?: PageQuery): Promise<Page<ScheduleTrigger>>;
|
|
150
|
+
get(automationId: string, triggerId: string): Promise<ScheduleTrigger | null>;
|
|
151
|
+
update(automationId: string, triggerId: string, patch: UpdateScheduleTrigger): Promise<ScheduleTrigger>;
|
|
152
|
+
delete(automationId: string, triggerId: string): Promise<void>;
|
|
153
|
+
};
|
|
154
|
+
readonly webhook: {
|
|
155
|
+
create(automationId: string, input: CreateWebhookTrigger): Promise<WebhookTrigger>;
|
|
156
|
+
list(automationId: string, query?: PageQuery): Promise<Page<WebhookTrigger>>;
|
|
157
|
+
get(automationId: string, triggerId: string): Promise<WebhookTrigger | null>;
|
|
158
|
+
update(automationId: string, triggerId: string, patch: UpdateWebhookTrigger): Promise<WebhookTrigger>;
|
|
159
|
+
delete(automationId: string, triggerId: string): Promise<void>;
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
readonly workflow: {
|
|
163
|
+
create(automationId: string, input: CreateWorkflow): Promise<Workflow>;
|
|
164
|
+
get(automationId: string): Promise<Workflow | null>;
|
|
165
|
+
update(automationId: string, patch: UpdateWorkflow): Promise<Workflow>;
|
|
166
|
+
delete(automationId: string): Promise<void>;
|
|
167
|
+
};
|
|
168
|
+
readonly runs: {
|
|
169
|
+
list(automationId: string, query?: ListRuns): Promise<Page<AutomationRun>>;
|
|
170
|
+
get(automationId: string, runId: string): Promise<AutomationRun | null>;
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
export interface AutomationCrudService {
|
|
174
|
+
for(principal: AutomationPrincipal): AutomationCrud;
|
|
175
|
+
}
|
|
176
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Automation, AutomationCrud, AutomationCrudService as AutomationCrudServiceContract, AutomationPrincipal, AutomationRun, CreateAutomation, CreateScheduleTrigger, CreateWebhookTrigger, CreateWorkflow, ListAutomations, ListRuns, Page, PageQuery, ScheduleTrigger, Trigger, UpdateAutomation, UpdateScheduleTrigger, UpdateWebhookTrigger, UpdateWorkflow, WebhookTrigger, Workflow } from './contract.js';
|
|
2
|
+
export interface AutomationCrudRepository {
|
|
3
|
+
createAutomation(userId: string, input: Required<CreateAutomation>): Promise<Automation>;
|
|
4
|
+
listAutomations(userId: string, query: NormalizedListAutomations): Promise<Page<Automation>>;
|
|
5
|
+
getAutomation(userId: string, automationId: string): Promise<Automation | null>;
|
|
6
|
+
updateAutomation(userId: string, automationId: string, patch: UpdateAutomation): Promise<Automation | null>;
|
|
7
|
+
deleteAutomation(userId: string, automationId: string): Promise<boolean>;
|
|
8
|
+
createScheduleTrigger(userId: string, automationId: string, input: Required<CreateScheduleTrigger>): Promise<ScheduleTrigger>;
|
|
9
|
+
listScheduleTriggers(userId: string, automationId: string, query: Required<PageQuery>): Promise<Page<ScheduleTrigger>>;
|
|
10
|
+
getScheduleTrigger(userId: string, automationId: string, triggerId: string): Promise<ScheduleTrigger | null>;
|
|
11
|
+
updateScheduleTrigger(userId: string, automationId: string, triggerId: string, patch: UpdateScheduleTrigger): Promise<ScheduleTrigger | null>;
|
|
12
|
+
deleteScheduleTrigger(userId: string, automationId: string, triggerId: string): Promise<boolean>;
|
|
13
|
+
createWebhookTrigger(userId: string, automationId: string, input: Required<CreateWebhookTrigger>): Promise<WebhookTrigger>;
|
|
14
|
+
listWebhookTriggers(userId: string, automationId: string, query: Required<PageQuery>): Promise<Page<WebhookTrigger>>;
|
|
15
|
+
getWebhookTrigger(userId: string, automationId: string, triggerId: string): Promise<WebhookTrigger | null>;
|
|
16
|
+
updateWebhookTrigger(userId: string, automationId: string, triggerId: string, patch: UpdateWebhookTrigger): Promise<WebhookTrigger | null>;
|
|
17
|
+
deleteWebhookTrigger(userId: string, automationId: string, triggerId: string): Promise<boolean>;
|
|
18
|
+
listTriggers(userId: string, automationId: string): Promise<Trigger[]>;
|
|
19
|
+
createWorkflow(userId: string, automationId: string, input: Required<CreateWorkflow>): Promise<Workflow>;
|
|
20
|
+
getWorkflow(userId: string, automationId: string): Promise<Workflow | null>;
|
|
21
|
+
updateWorkflow(userId: string, automationId: string, patch: UpdateWorkflow): Promise<Workflow | null>;
|
|
22
|
+
deleteWorkflow(userId: string, automationId: string): Promise<boolean>;
|
|
23
|
+
listRuns(userId: string, automationId: string, query: NormalizedListRuns): Promise<Page<AutomationRun>>;
|
|
24
|
+
getRun(userId: string, automationId: string, runId: string): Promise<AutomationRun | null>;
|
|
25
|
+
}
|
|
26
|
+
export type NormalizedListAutomations = Required<PageQuery> & Omit<ListAutomations, keyof PageQuery>;
|
|
27
|
+
export type NormalizedListRuns = Required<PageQuery> & Omit<ListRuns, keyof PageQuery>;
|
|
28
|
+
export declare class AutomationCrudService implements AutomationCrudServiceContract {
|
|
29
|
+
private readonly repository;
|
|
30
|
+
constructor(repository: AutomationCrudRepository);
|
|
31
|
+
for(principal: AutomationPrincipal): AutomationCrud;
|
|
32
|
+
}
|
package/dist/src/crud.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { ConflictError, ForbiddenError, NotFoundError } from './errors.js';
|
|
2
|
+
import { parseCreateAutomation, parseCreateScheduleTrigger, parseCreateWebhookTrigger, parseCreateWorkflow, parseListAutomations, parseListRuns, parsePageQuery, parseUpdateAutomation, parseUpdateScheduleTrigger, parseUpdateWebhookTrigger, parseUpdateWorkflow, } from './schema.js';
|
|
3
|
+
import { id, newId, page, requiredText, schedule, } from './validation.js';
|
|
4
|
+
export class AutomationCrudService {
|
|
5
|
+
repository;
|
|
6
|
+
constructor(repository) {
|
|
7
|
+
this.repository = repository;
|
|
8
|
+
}
|
|
9
|
+
for(principal) {
|
|
10
|
+
assertPrincipal(principal);
|
|
11
|
+
const read = () => assertScope(principal, 'automations:read');
|
|
12
|
+
const write = () => assertScope(principal, 'automations:write');
|
|
13
|
+
const runsRead = () => assertScope(principal, 'runs:read');
|
|
14
|
+
const exists = async (automationId) => {
|
|
15
|
+
const automation = await this.repository.getAutomation(principal.userId, id(automationId, 'Automation id'));
|
|
16
|
+
if (!automation)
|
|
17
|
+
throw new NotFoundError('Automation');
|
|
18
|
+
};
|
|
19
|
+
return {
|
|
20
|
+
automations: {
|
|
21
|
+
create: async (input) => {
|
|
22
|
+
write();
|
|
23
|
+
const parsed = parseCreateAutomation(input);
|
|
24
|
+
const targetId = parsed.targetId;
|
|
25
|
+
assertTarget(principal, targetId);
|
|
26
|
+
if (parsed.id && await this.repository.getAutomation(principal.userId, id(parsed.id, 'Automation id'))) {
|
|
27
|
+
throw new ConflictError('Automation');
|
|
28
|
+
}
|
|
29
|
+
return this.repository.createAutomation(principal.userId, {
|
|
30
|
+
id: parsed.id ? id(parsed.id, 'Automation id') : newId('automation'),
|
|
31
|
+
targetId,
|
|
32
|
+
name: parsed.name || '',
|
|
33
|
+
description: parsed.description || '',
|
|
34
|
+
enabled: parsed.enabled !== false,
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
list: async (query = {}) => {
|
|
38
|
+
read();
|
|
39
|
+
const parsed = parseListAutomations(query);
|
|
40
|
+
if (parsed.targetId)
|
|
41
|
+
assertTarget(principal, parsed.targetId);
|
|
42
|
+
return this.repository.listAutomations(principal.userId, {
|
|
43
|
+
...page(parsed),
|
|
44
|
+
...(parsed.targetId ? { targetId: id(parsed.targetId, 'targetId') } : {}),
|
|
45
|
+
...(parsed.enabled === undefined ? {} : { enabled: parsed.enabled }),
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
get: async (automationId) => {
|
|
49
|
+
read();
|
|
50
|
+
return this.repository.getAutomation(principal.userId, id(automationId, 'Automation id'));
|
|
51
|
+
},
|
|
52
|
+
update: async (automationId, patch) => {
|
|
53
|
+
write();
|
|
54
|
+
const parsed = parseUpdateAutomation(patch);
|
|
55
|
+
if (parsed.targetId !== undefined) {
|
|
56
|
+
assertTarget(principal, parsed.targetId);
|
|
57
|
+
}
|
|
58
|
+
const updated = await this.repository.updateAutomation(principal.userId, id(automationId, 'Automation id'), parsed);
|
|
59
|
+
if (!updated)
|
|
60
|
+
throw new NotFoundError('Automation');
|
|
61
|
+
return updated;
|
|
62
|
+
},
|
|
63
|
+
delete: async (automationId) => {
|
|
64
|
+
write();
|
|
65
|
+
const deleted = await this.repository.deleteAutomation(principal.userId, id(automationId, 'Automation id'));
|
|
66
|
+
if (!deleted)
|
|
67
|
+
throw new NotFoundError('Automation');
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
triggers: {
|
|
71
|
+
list: async (automationId) => {
|
|
72
|
+
read();
|
|
73
|
+
await exists(automationId);
|
|
74
|
+
return this.repository.listTriggers(principal.userId, automationId);
|
|
75
|
+
},
|
|
76
|
+
schedule: {
|
|
77
|
+
create: async (automationId, input) => {
|
|
78
|
+
write();
|
|
79
|
+
await exists(automationId);
|
|
80
|
+
const parsed = parseCreateScheduleTrigger(input);
|
|
81
|
+
const timezone = parsed.timezone || 'UTC';
|
|
82
|
+
schedule(parsed.cron, timezone);
|
|
83
|
+
if (parsed.id && (await this.repository.listTriggers(principal.userId, automationId))
|
|
84
|
+
.some((trigger) => trigger.id === parsed.id))
|
|
85
|
+
throw new ConflictError('Trigger');
|
|
86
|
+
return this.repository.createScheduleTrigger(principal.userId, automationId, {
|
|
87
|
+
id: parsed.id ? id(parsed.id, 'Schedule trigger id') : newId('schedule'),
|
|
88
|
+
cron: parsed.cron,
|
|
89
|
+
timezone,
|
|
90
|
+
enabled: parsed.enabled !== false,
|
|
91
|
+
});
|
|
92
|
+
},
|
|
93
|
+
list: async (automationId, query = {}) => {
|
|
94
|
+
read();
|
|
95
|
+
await exists(automationId);
|
|
96
|
+
return this.repository.listScheduleTriggers(principal.userId, automationId, page(parsePageQuery(query)));
|
|
97
|
+
},
|
|
98
|
+
get: async (automationId, triggerId) => {
|
|
99
|
+
read();
|
|
100
|
+
await exists(automationId);
|
|
101
|
+
return this.repository.getScheduleTrigger(principal.userId, automationId, id(triggerId, 'Schedule trigger id'));
|
|
102
|
+
},
|
|
103
|
+
update: async (automationId, triggerId, patch) => {
|
|
104
|
+
write();
|
|
105
|
+
await exists(automationId);
|
|
106
|
+
const parsed = parseUpdateScheduleTrigger(patch);
|
|
107
|
+
if (parsed.cron !== undefined || parsed.timezone !== undefined) {
|
|
108
|
+
const existing = await this.repository.getScheduleTrigger(principal.userId, automationId, triggerId);
|
|
109
|
+
if (!existing)
|
|
110
|
+
throw new NotFoundError('Schedule trigger');
|
|
111
|
+
schedule(parsed.cron ?? existing.cron, parsed.timezone ?? existing.timezone);
|
|
112
|
+
}
|
|
113
|
+
const updated = await this.repository.updateScheduleTrigger(principal.userId, automationId, id(triggerId, 'Schedule trigger id'), parsed);
|
|
114
|
+
if (!updated)
|
|
115
|
+
throw new NotFoundError('Schedule trigger');
|
|
116
|
+
return updated;
|
|
117
|
+
},
|
|
118
|
+
delete: async (automationId, triggerId) => {
|
|
119
|
+
write();
|
|
120
|
+
await exists(automationId);
|
|
121
|
+
const deleted = await this.repository.deleteScheduleTrigger(principal.userId, automationId, id(triggerId, 'Schedule trigger id'));
|
|
122
|
+
if (!deleted)
|
|
123
|
+
throw new NotFoundError('Schedule trigger');
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
webhook: {
|
|
127
|
+
create: async (automationId, input) => {
|
|
128
|
+
write();
|
|
129
|
+
await exists(automationId);
|
|
130
|
+
const parsed = parseCreateWebhookTrigger(input);
|
|
131
|
+
if (parsed.id && (await this.repository.listTriggers(principal.userId, automationId))
|
|
132
|
+
.some((trigger) => trigger.id === parsed.id))
|
|
133
|
+
throw new ConflictError('Trigger');
|
|
134
|
+
return this.repository.createWebhookTrigger(principal.userId, automationId, {
|
|
135
|
+
id: parsed.id ? id(parsed.id, 'Webhook trigger id') : newId('webhook'),
|
|
136
|
+
source: parsed.source || '*',
|
|
137
|
+
event: parsed.event || '*',
|
|
138
|
+
secret: parsed.secret,
|
|
139
|
+
enabled: parsed.enabled !== false,
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
list: async (automationId, query = {}) => {
|
|
143
|
+
read();
|
|
144
|
+
await exists(automationId);
|
|
145
|
+
return this.repository.listWebhookTriggers(principal.userId, automationId, page(parsePageQuery(query)));
|
|
146
|
+
},
|
|
147
|
+
get: async (automationId, triggerId) => {
|
|
148
|
+
read();
|
|
149
|
+
await exists(automationId);
|
|
150
|
+
return this.repository.getWebhookTrigger(principal.userId, automationId, id(triggerId, 'Webhook trigger id'));
|
|
151
|
+
},
|
|
152
|
+
update: async (automationId, triggerId, patch) => {
|
|
153
|
+
write();
|
|
154
|
+
await exists(automationId);
|
|
155
|
+
const parsed = parseUpdateWebhookTrigger(patch);
|
|
156
|
+
const updated = await this.repository.updateWebhookTrigger(principal.userId, automationId, id(triggerId, 'Webhook trigger id'), parsed);
|
|
157
|
+
if (!updated)
|
|
158
|
+
throw new NotFoundError('Webhook trigger');
|
|
159
|
+
return updated;
|
|
160
|
+
},
|
|
161
|
+
delete: async (automationId, triggerId) => {
|
|
162
|
+
write();
|
|
163
|
+
await exists(automationId);
|
|
164
|
+
const deleted = await this.repository.deleteWebhookTrigger(principal.userId, automationId, id(triggerId, 'Webhook trigger id'));
|
|
165
|
+
if (!deleted)
|
|
166
|
+
throw new NotFoundError('Webhook trigger');
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
workflow: {
|
|
171
|
+
create: async (automationId, input) => {
|
|
172
|
+
write();
|
|
173
|
+
await exists(automationId);
|
|
174
|
+
if (await this.repository.getWorkflow(principal.userId, automationId)) {
|
|
175
|
+
throw new ConflictError('Workflow');
|
|
176
|
+
}
|
|
177
|
+
const parsed = parseCreateWorkflow(input);
|
|
178
|
+
return this.repository.createWorkflow(principal.userId, automationId, {
|
|
179
|
+
id: parsed.id ? id(parsed.id, 'Workflow id') : newId('workflow'),
|
|
180
|
+
name: parsed.name || '',
|
|
181
|
+
script: parsed.script,
|
|
182
|
+
compiled: parsed.compiled ?? null,
|
|
183
|
+
allowlist: parsed.allowlist ?? null,
|
|
184
|
+
limits: parsed.limits ?? null,
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
get: async (automationId) => {
|
|
188
|
+
read();
|
|
189
|
+
await exists(automationId);
|
|
190
|
+
return this.repository.getWorkflow(principal.userId, automationId);
|
|
191
|
+
},
|
|
192
|
+
update: async (automationId, patch) => {
|
|
193
|
+
write();
|
|
194
|
+
await exists(automationId);
|
|
195
|
+
const parsed = parseUpdateWorkflow(patch);
|
|
196
|
+
const updated = await this.repository.updateWorkflow(principal.userId, automationId, parsed);
|
|
197
|
+
if (!updated)
|
|
198
|
+
throw new NotFoundError('Workflow');
|
|
199
|
+
return updated;
|
|
200
|
+
},
|
|
201
|
+
delete: async (automationId) => {
|
|
202
|
+
write();
|
|
203
|
+
await exists(automationId);
|
|
204
|
+
if (!await this.repository.deleteWorkflow(principal.userId, automationId)) {
|
|
205
|
+
throw new NotFoundError('Workflow');
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
runs: {
|
|
210
|
+
list: async (automationId, query = {}) => {
|
|
211
|
+
runsRead();
|
|
212
|
+
const parsed = parseListRuns(query);
|
|
213
|
+
return this.repository.listRuns(principal.userId, id(automationId, 'Automation id'), {
|
|
214
|
+
...page(parsed),
|
|
215
|
+
...(parsed.status ? { status: parsed.status } : {}),
|
|
216
|
+
});
|
|
217
|
+
},
|
|
218
|
+
get: async (automationId, runId) => {
|
|
219
|
+
runsRead();
|
|
220
|
+
return this.repository.getRun(principal.userId, id(automationId, 'Automation id'), id(runId, 'Run id'));
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function assertPrincipal(principal) {
|
|
227
|
+
requiredText(principal.userId, 'Authenticated user id');
|
|
228
|
+
if (!Array.isArray(principal.scopes))
|
|
229
|
+
throw new ForbiddenError();
|
|
230
|
+
principal.targetIds?.forEach((targetId) => id(targetId, 'Authorized target id'));
|
|
231
|
+
}
|
|
232
|
+
function assertScope(principal, scope) {
|
|
233
|
+
if (!principal.scopes.includes('*') && !principal.scopes.includes(scope)) {
|
|
234
|
+
throw new ForbiddenError(`This credential requires ${scope}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function assertTarget(principal, targetId) {
|
|
238
|
+
if (principal.targetIds && !principal.targetIds.includes(targetId)) {
|
|
239
|
+
throw new ForbiddenError('This credential cannot access that target');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type AutomationErrorCode = 'conflict' | 'forbidden' | 'internal' | 'not_found' | 'validation';
|
|
2
|
+
export declare class AutomationError extends Error {
|
|
3
|
+
readonly code: AutomationErrorCode;
|
|
4
|
+
constructor(code: AutomationErrorCode, message: string);
|
|
5
|
+
}
|
|
6
|
+
export declare class ValidationError extends AutomationError {
|
|
7
|
+
constructor(message: string);
|
|
8
|
+
}
|
|
9
|
+
export declare class ForbiddenError extends AutomationError {
|
|
10
|
+
constructor(message?: string);
|
|
11
|
+
}
|
|
12
|
+
export declare class ConflictError extends AutomationError {
|
|
13
|
+
constructor(resource: string);
|
|
14
|
+
}
|
|
15
|
+
export declare class NotFoundError extends AutomationError {
|
|
16
|
+
constructor(resource: string);
|
|
17
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export class AutomationError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
constructor(code, message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.code = code;
|
|
6
|
+
this.name = 'AutomationError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export class ValidationError extends AutomationError {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super('validation', message);
|
|
12
|
+
this.name = 'ValidationError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class ForbiddenError extends AutomationError {
|
|
16
|
+
constructor(message = 'This credential cannot access Automations') {
|
|
17
|
+
super('forbidden', message);
|
|
18
|
+
this.name = 'ForbiddenError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class ConflictError extends AutomationError {
|
|
22
|
+
constructor(resource) {
|
|
23
|
+
super('conflict', `${resource} already exists`);
|
|
24
|
+
this.name = 'ConflictError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export class NotFoundError extends AutomationError {
|
|
28
|
+
constructor(resource) {
|
|
29
|
+
super('not_found', `${resource} was not found`);
|
|
30
|
+
this.name = 'NotFoundError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AutomationCrudService, AutomationPrincipal } from './contract.js';
|
|
2
|
+
export type AuthenticateAutomationRequest = (request: Request) => Promise<AutomationPrincipal>;
|
|
3
|
+
export type AutomationApi = (request: Request) => Promise<Response>;
|
|
4
|
+
export declare function createAutomationApi(options: {
|
|
5
|
+
service: AutomationCrudService;
|
|
6
|
+
authenticate: AuthenticateAutomationRequest;
|
|
7
|
+
}): AutomationApi;
|