@amalgm/automations 0.1.2 → 0.2.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.
Files changed (65) hide show
  1. package/AXIOMS.md +11 -0
  2. package/PURPOSE.md +10 -3
  3. package/README.md +60 -113
  4. package/dist/host/auth.d.ts +11 -0
  5. package/dist/host/auth.js +89 -0
  6. package/dist/host/config.d.ts +8 -0
  7. package/dist/host/config.js +29 -0
  8. package/dist/host/index.d.ts +3 -0
  9. package/dist/host/index.js +3 -0
  10. package/dist/host/main.d.ts +1 -0
  11. package/dist/host/main.js +47 -0
  12. package/dist/host/server.d.ts +11 -0
  13. package/dist/host/server.js +53 -0
  14. package/dist/src/automations.js +4 -1
  15. package/dist/src/client.d.ts +3 -1
  16. package/dist/src/client.js +6 -4
  17. package/dist/src/contract.d.ts +17 -1
  18. package/dist/src/crud/automations.d.ts +3 -0
  19. package/dist/src/crud/automations.js +55 -0
  20. package/dist/src/crud/context.d.ts +13 -0
  21. package/dist/src/crud/context.js +34 -0
  22. package/dist/src/crud/repository.d.ts +35 -0
  23. package/dist/src/crud/repository.js +1 -0
  24. package/dist/src/crud/runs.d.ts +3 -0
  25. package/dist/src/crud/runs.js +19 -0
  26. package/dist/src/crud/triggers.d.ts +3 -0
  27. package/dist/src/crud/triggers.js +118 -0
  28. package/dist/src/crud/workflow.d.ts +3 -0
  29. package/dist/src/crud/workflow.js +42 -0
  30. package/dist/src/crud.d.ts +3 -33
  31. package/dist/src/crud.js +10 -237
  32. package/dist/src/executor.d.ts +16 -0
  33. package/dist/src/executor.js +76 -0
  34. package/dist/src/index.d.ts +8 -2
  35. package/dist/src/index.js +5 -0
  36. package/dist/src/machine-client.d.ts +7 -0
  37. package/dist/src/machine-client.js +30 -0
  38. package/dist/src/machine-http.d.ts +5 -0
  39. package/dist/src/machine-http.js +40 -0
  40. package/dist/src/machine.d.ts +41 -0
  41. package/dist/src/machine.js +43 -0
  42. package/dist/src/mcp.js +2 -2
  43. package/dist/src/schema.d.ts +8 -0
  44. package/dist/src/schema.js +2 -0
  45. package/dist/src/supabase-crud/automations.d.ts +5 -0
  46. package/dist/src/supabase-crud/automations.js +36 -0
  47. package/dist/src/supabase-crud/mappers.d.ts +8 -0
  48. package/dist/src/supabase-crud/mappers.js +81 -0
  49. package/dist/src/supabase-crud/rows.d.ts +56 -0
  50. package/dist/src/supabase-crud/rows.js +1 -0
  51. package/dist/src/supabase-crud/rpc.d.ts +10 -0
  52. package/dist/src/supabase-crud/rpc.js +19 -0
  53. package/dist/src/supabase-crud/triggers.d.ts +5 -0
  54. package/dist/src/supabase-crud/triggers.js +46 -0
  55. package/dist/src/supabase-crud/workflow-runs.d.ts +5 -0
  56. package/dist/src/supabase-crud/workflow-runs.js +41 -0
  57. package/dist/src/supabase-crud.d.ts +5 -41
  58. package/dist/src/supabase-crud.js +4 -267
  59. package/dist/src/supabase-machine.d.ts +16 -0
  60. package/dist/src/supabase-machine.js +63 -0
  61. package/dist/src/supabase-store.js +1 -0
  62. package/dist/src/types.d.ts +1 -0
  63. package/package.json +11 -2
  64. package/skills/automations/SKILL.md +52 -0
  65. package/supabase/migrations/20260829010000_bounded_schedules_and_machine_claims.sql +311 -0
@@ -0,0 +1,55 @@
1
+ import { ConflictError, NotFoundError } from '../errors.js';
2
+ import { parseCreateAutomation, parseListAutomations, parseUpdateAutomation } from '../schema.js';
3
+ import { id, newId, page } from '../validation.js';
4
+ import { assertTarget } from './context.js';
5
+ export function automationOperations(context) {
6
+ const { principal, repository } = context;
7
+ return {
8
+ create: async (input) => {
9
+ context.write();
10
+ const parsed = parseCreateAutomation(input);
11
+ assertTarget(principal, parsed.targetId);
12
+ if (parsed.id && await repository.getAutomation(principal.userId, id(parsed.id, 'Automation id'))) {
13
+ throw new ConflictError('Automation');
14
+ }
15
+ return repository.createAutomation(principal.userId, {
16
+ id: parsed.id ? id(parsed.id, 'Automation id') : newId('automation'),
17
+ targetId: parsed.targetId,
18
+ name: parsed.name || '',
19
+ description: parsed.description || '',
20
+ enabled: parsed.enabled !== false,
21
+ });
22
+ },
23
+ list: async (query = {}) => {
24
+ context.read();
25
+ const parsed = parseListAutomations(query);
26
+ if (parsed.targetId)
27
+ assertTarget(principal, parsed.targetId);
28
+ return repository.listAutomations(principal.userId, {
29
+ ...page(parsed),
30
+ ...(parsed.targetId ? { targetId: id(parsed.targetId, 'targetId') } : {}),
31
+ ...(parsed.enabled === undefined ? {} : { enabled: parsed.enabled }),
32
+ });
33
+ },
34
+ get: async (automationId) => {
35
+ context.read();
36
+ return repository.getAutomation(principal.userId, id(automationId, 'Automation id'));
37
+ },
38
+ update: async (automationId, patch) => {
39
+ context.write();
40
+ const parsed = parseUpdateAutomation(patch);
41
+ if (parsed.targetId !== undefined)
42
+ assertTarget(principal, parsed.targetId);
43
+ const updated = await repository.updateAutomation(principal.userId, id(automationId, 'Automation id'), parsed);
44
+ if (!updated)
45
+ throw new NotFoundError('Automation');
46
+ return updated;
47
+ },
48
+ delete: async (automationId) => {
49
+ context.write();
50
+ const deleted = await repository.deleteAutomation(principal.userId, id(automationId, 'Automation id'));
51
+ if (!deleted)
52
+ throw new NotFoundError('Automation');
53
+ },
54
+ };
55
+ }
@@ -0,0 +1,13 @@
1
+ import type { AutomationPrincipal } from '../contract.js';
2
+ import type { AutomationCrudRepository } from './repository.js';
3
+ export interface CrudContext {
4
+ repository: AutomationCrudRepository;
5
+ principal: AutomationPrincipal;
6
+ clock: () => Date;
7
+ read(): void;
8
+ write(): void;
9
+ runsRead(): void;
10
+ exists(automationId: string): Promise<void>;
11
+ }
12
+ export declare function createContext(repository: AutomationCrudRepository, principal: AutomationPrincipal, clock: () => Date): CrudContext;
13
+ export declare function assertTarget(principal: AutomationPrincipal, targetId: string): void;
@@ -0,0 +1,34 @@
1
+ import { ForbiddenError, NotFoundError } from '../errors.js';
2
+ import { id, requiredText } from '../validation.js';
3
+ export function createContext(repository, principal, clock) {
4
+ assertPrincipal(principal);
5
+ return {
6
+ repository,
7
+ principal,
8
+ clock,
9
+ read: () => assertScope(principal, 'automations:read'),
10
+ write: () => assertScope(principal, 'automations:write'),
11
+ runsRead: () => assertScope(principal, 'runs:read'),
12
+ exists: async (automationId) => {
13
+ const found = await repository.getAutomation(principal.userId, id(automationId, 'Automation id'));
14
+ if (!found)
15
+ throw new NotFoundError('Automation');
16
+ },
17
+ };
18
+ }
19
+ export function assertTarget(principal, targetId) {
20
+ if (principal.targetIds && !principal.targetIds.includes(targetId)) {
21
+ throw new ForbiddenError('This credential cannot access that target');
22
+ }
23
+ }
24
+ function assertPrincipal(principal) {
25
+ requiredText(principal.userId, 'Authenticated user id');
26
+ if (!Array.isArray(principal.scopes))
27
+ throw new ForbiddenError();
28
+ principal.targetIds?.forEach((targetId) => id(targetId, 'Authorized target id'));
29
+ }
30
+ function assertScope(principal, scope) {
31
+ if (!principal.scopes.includes('*') && !principal.scopes.includes(scope)) {
32
+ throw new ForbiddenError(`This credential requires ${scope}`);
33
+ }
34
+ }
@@ -0,0 +1,35 @@
1
+ import type { Automation, AutomationRun, CreateAutomation, CreateScheduleTrigger, CreateWebhookTrigger, CreateWorkflow, ListAutomations, ListRuns, Page, PageQuery, ScheduleTrigger, Trigger, UpdateAutomation, UpdateScheduleTrigger, UpdateWebhookTrigger, UpdateWorkflow, WebhookTrigger, Workflow } from '../contract.js';
2
+ export type StoredScheduleCreate = Omit<Required<CreateScheduleTrigger>, 'maxOccurrences'> & {
3
+ maxOccurrences: number | null;
4
+ nextRunAt: string;
5
+ };
6
+ export type StoredScheduleUpdate = UpdateScheduleTrigger & {
7
+ nextRunAt?: string;
8
+ remainingOccurrences?: number | null;
9
+ };
10
+ export type NormalizedListAutomations = Required<PageQuery> & Omit<ListAutomations, keyof PageQuery>;
11
+ export type NormalizedListRuns = Required<PageQuery> & Omit<ListRuns, keyof PageQuery>;
12
+ export interface AutomationCrudRepository {
13
+ createAutomation(userId: string, input: Required<CreateAutomation>): Promise<Automation>;
14
+ listAutomations(userId: string, query: NormalizedListAutomations): Promise<Page<Automation>>;
15
+ getAutomation(userId: string, automationId: string): Promise<Automation | null>;
16
+ updateAutomation(userId: string, automationId: string, patch: UpdateAutomation): Promise<Automation | null>;
17
+ deleteAutomation(userId: string, automationId: string): Promise<boolean>;
18
+ createScheduleTrigger(userId: string, automationId: string, input: StoredScheduleCreate): Promise<ScheduleTrigger>;
19
+ listScheduleTriggers(userId: string, automationId: string, query: Required<PageQuery>): Promise<Page<ScheduleTrigger>>;
20
+ getScheduleTrigger(userId: string, automationId: string, triggerId: string): Promise<ScheduleTrigger | null>;
21
+ updateScheduleTrigger(userId: string, automationId: string, triggerId: string, patch: StoredScheduleUpdate): Promise<ScheduleTrigger | null>;
22
+ deleteScheduleTrigger(userId: string, automationId: string, triggerId: string): Promise<boolean>;
23
+ createWebhookTrigger(userId: string, automationId: string, input: Required<CreateWebhookTrigger>): Promise<WebhookTrigger>;
24
+ listWebhookTriggers(userId: string, automationId: string, query: Required<PageQuery>): Promise<Page<WebhookTrigger>>;
25
+ getWebhookTrigger(userId: string, automationId: string, triggerId: string): Promise<WebhookTrigger | null>;
26
+ updateWebhookTrigger(userId: string, automationId: string, triggerId: string, patch: UpdateWebhookTrigger): Promise<WebhookTrigger | null>;
27
+ deleteWebhookTrigger(userId: string, automationId: string, triggerId: string): Promise<boolean>;
28
+ listTriggers(userId: string, automationId: string): Promise<Trigger[]>;
29
+ createWorkflow(userId: string, automationId: string, input: Required<CreateWorkflow>): Promise<Workflow>;
30
+ getWorkflow(userId: string, automationId: string): Promise<Workflow | null>;
31
+ updateWorkflow(userId: string, automationId: string, patch: UpdateWorkflow): Promise<Workflow | null>;
32
+ deleteWorkflow(userId: string, automationId: string): Promise<boolean>;
33
+ listRuns(userId: string, automationId: string, query: NormalizedListRuns): Promise<Page<AutomationRun>>;
34
+ getRun(userId: string, automationId: string, runId: string): Promise<AutomationRun | null>;
35
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import type { AutomationCrud } from '../contract.js';
2
+ import type { CrudContext } from './context.js';
3
+ export declare function runOperations(context: CrudContext): AutomationCrud['runs'];
@@ -0,0 +1,19 @@
1
+ import { parseListRuns } from '../schema.js';
2
+ import { id, page } from '../validation.js';
3
+ export function runOperations(context) {
4
+ const { principal, repository } = context;
5
+ return {
6
+ list: async (automationId, query = {}) => {
7
+ context.runsRead();
8
+ const parsed = parseListRuns(query);
9
+ return repository.listRuns(principal.userId, id(automationId, 'Automation id'), {
10
+ ...page(parsed),
11
+ ...(parsed.status ? { status: parsed.status } : {}),
12
+ });
13
+ },
14
+ get: async (automationId, runId) => {
15
+ context.runsRead();
16
+ return repository.getRun(principal.userId, id(automationId, 'Automation id'), id(runId, 'Run id'));
17
+ },
18
+ };
19
+ }
@@ -0,0 +1,3 @@
1
+ import type { AutomationCrud } from '../contract.js';
2
+ import type { CrudContext } from './context.js';
3
+ export declare function triggerOperations(context: CrudContext): AutomationCrud['triggers'];
@@ -0,0 +1,118 @@
1
+ import { ConflictError, NotFoundError } from '../errors.js';
2
+ import { parseCreateScheduleTrigger, parseCreateWebhookTrigger, parsePageQuery, parseUpdateScheduleTrigger, parseUpdateWebhookTrigger, } from '../schema.js';
3
+ import { nextCronAt } from '../schedule.js';
4
+ import { id, newId, page, schedule } from '../validation.js';
5
+ export function triggerOperations(context) {
6
+ const { principal, repository } = context;
7
+ const unique = async (automationId, triggerId) => {
8
+ const exists = (await repository.listTriggers(principal.userId, automationId))
9
+ .some((trigger) => trigger.id === triggerId);
10
+ if (exists)
11
+ throw new ConflictError('Trigger');
12
+ };
13
+ return {
14
+ list: async (automationId) => {
15
+ context.read();
16
+ await context.exists(automationId);
17
+ return repository.listTriggers(principal.userId, automationId);
18
+ },
19
+ schedule: {
20
+ create: async (automationId, input) => {
21
+ context.write();
22
+ await context.exists(automationId);
23
+ const parsed = parseCreateScheduleTrigger(input);
24
+ const timezone = parsed.timezone || 'UTC';
25
+ schedule(parsed.cron, timezone);
26
+ if (parsed.id)
27
+ await unique(automationId, parsed.id);
28
+ return repository.createScheduleTrigger(principal.userId, automationId, {
29
+ id: parsed.id ? id(parsed.id, 'Schedule trigger id') : newId('schedule'),
30
+ cron: parsed.cron,
31
+ timezone,
32
+ enabled: parsed.enabled !== false,
33
+ maxOccurrences: parsed.maxOccurrences ?? null,
34
+ nextRunAt: nextCronAt(parsed.cron, timezone, context.clock()),
35
+ });
36
+ },
37
+ list: async (automationId, query = {}) => {
38
+ context.read();
39
+ await context.exists(automationId);
40
+ return repository.listScheduleTriggers(principal.userId, automationId, page(parsePageQuery(query)));
41
+ },
42
+ get: async (automationId, triggerId) => {
43
+ context.read();
44
+ await context.exists(automationId);
45
+ return repository.getScheduleTrigger(principal.userId, automationId, id(triggerId, 'Schedule trigger id'));
46
+ },
47
+ update: async (automationId, triggerId, patch) => {
48
+ context.write();
49
+ await context.exists(automationId);
50
+ const parsed = parseUpdateScheduleTrigger(patch);
51
+ let stored = parsed.maxOccurrences === undefined
52
+ ? { ...parsed }
53
+ : { ...parsed, remainingOccurrences: parsed.maxOccurrences };
54
+ if (parsed.cron !== undefined || parsed.timezone !== undefined) {
55
+ const current = await repository.getScheduleTrigger(principal.userId, automationId, triggerId);
56
+ if (!current)
57
+ throw new NotFoundError('Schedule trigger');
58
+ const cron = parsed.cron ?? current.cron;
59
+ const timezone = parsed.timezone ?? current.timezone;
60
+ schedule(cron, timezone);
61
+ stored = { ...stored, nextRunAt: nextCronAt(cron, timezone, context.clock()) };
62
+ }
63
+ const updated = await repository.updateScheduleTrigger(principal.userId, automationId, id(triggerId, 'Schedule trigger id'), stored);
64
+ if (!updated)
65
+ throw new NotFoundError('Schedule trigger');
66
+ return updated;
67
+ },
68
+ delete: async (automationId, triggerId) => {
69
+ context.write();
70
+ await context.exists(automationId);
71
+ const deleted = await repository.deleteScheduleTrigger(principal.userId, automationId, id(triggerId, 'Schedule trigger id'));
72
+ if (!deleted)
73
+ throw new NotFoundError('Schedule trigger');
74
+ },
75
+ },
76
+ webhook: {
77
+ create: async (automationId, input) => {
78
+ context.write();
79
+ await context.exists(automationId);
80
+ const parsed = parseCreateWebhookTrigger(input);
81
+ if (parsed.id)
82
+ await unique(automationId, parsed.id);
83
+ return repository.createWebhookTrigger(principal.userId, automationId, {
84
+ id: parsed.id ? id(parsed.id, 'Webhook trigger id') : newId('webhook'),
85
+ source: parsed.source || '*',
86
+ event: parsed.event || '*',
87
+ secret: parsed.secret,
88
+ enabled: parsed.enabled !== false,
89
+ });
90
+ },
91
+ list: async (automationId, query = {}) => {
92
+ context.read();
93
+ await context.exists(automationId);
94
+ return repository.listWebhookTriggers(principal.userId, automationId, page(parsePageQuery(query)));
95
+ },
96
+ get: async (automationId, triggerId) => {
97
+ context.read();
98
+ await context.exists(automationId);
99
+ return repository.getWebhookTrigger(principal.userId, automationId, id(triggerId, 'Webhook trigger id'));
100
+ },
101
+ update: async (automationId, triggerId, patch) => {
102
+ context.write();
103
+ await context.exists(automationId);
104
+ const updated = await repository.updateWebhookTrigger(principal.userId, automationId, id(triggerId, 'Webhook trigger id'), parseUpdateWebhookTrigger(patch));
105
+ if (!updated)
106
+ throw new NotFoundError('Webhook trigger');
107
+ return updated;
108
+ },
109
+ delete: async (automationId, triggerId) => {
110
+ context.write();
111
+ await context.exists(automationId);
112
+ const deleted = await repository.deleteWebhookTrigger(principal.userId, automationId, id(triggerId, 'Webhook trigger id'));
113
+ if (!deleted)
114
+ throw new NotFoundError('Webhook trigger');
115
+ },
116
+ },
117
+ };
118
+ }
@@ -0,0 +1,3 @@
1
+ import type { AutomationCrud } from '../contract.js';
2
+ import type { CrudContext } from './context.js';
3
+ export declare function workflowOperations(context: CrudContext): AutomationCrud['workflow'];
@@ -0,0 +1,42 @@
1
+ import { ConflictError, NotFoundError } from '../errors.js';
2
+ import { parseCreateWorkflow, parseUpdateWorkflow } from '../schema.js';
3
+ import { id, newId } from '../validation.js';
4
+ export function workflowOperations(context) {
5
+ const { principal, repository } = context;
6
+ return {
7
+ create: async (automationId, input) => {
8
+ context.write();
9
+ await context.exists(automationId);
10
+ if (await repository.getWorkflow(principal.userId, automationId))
11
+ throw new ConflictError('Workflow');
12
+ const parsed = parseCreateWorkflow(input);
13
+ return repository.createWorkflow(principal.userId, automationId, {
14
+ id: parsed.id ? id(parsed.id, 'Workflow id') : newId('workflow'),
15
+ name: parsed.name || '',
16
+ script: parsed.script,
17
+ compiled: parsed.compiled ?? null,
18
+ allowlist: parsed.allowlist ?? null,
19
+ limits: parsed.limits ?? null,
20
+ });
21
+ },
22
+ get: async (automationId) => {
23
+ context.read();
24
+ await context.exists(automationId);
25
+ return repository.getWorkflow(principal.userId, automationId);
26
+ },
27
+ update: async (automationId, patch) => {
28
+ context.write();
29
+ await context.exists(automationId);
30
+ const updated = await repository.updateWorkflow(principal.userId, automationId, parseUpdateWorkflow(patch));
31
+ if (!updated)
32
+ throw new NotFoundError('Workflow');
33
+ return updated;
34
+ },
35
+ delete: async (automationId) => {
36
+ context.write();
37
+ await context.exists(automationId);
38
+ if (!await repository.deleteWorkflow(principal.userId, automationId))
39
+ throw new NotFoundError('Workflow');
40
+ },
41
+ };
42
+ }
@@ -1,36 +1,6 @@
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 type StoredScheduleCreate = Required<CreateScheduleTrigger> & {
3
- nextRunAt: string;
4
- };
5
- export type StoredScheduleUpdate = UpdateScheduleTrigger & {
6
- nextRunAt?: string;
7
- };
8
- export interface AutomationCrudRepository {
9
- createAutomation(userId: string, input: Required<CreateAutomation>): Promise<Automation>;
10
- listAutomations(userId: string, query: NormalizedListAutomations): Promise<Page<Automation>>;
11
- getAutomation(userId: string, automationId: string): Promise<Automation | null>;
12
- updateAutomation(userId: string, automationId: string, patch: UpdateAutomation): Promise<Automation | null>;
13
- deleteAutomation(userId: string, automationId: string): Promise<boolean>;
14
- createScheduleTrigger(userId: string, automationId: string, input: StoredScheduleCreate): Promise<ScheduleTrigger>;
15
- listScheduleTriggers(userId: string, automationId: string, query: Required<PageQuery>): Promise<Page<ScheduleTrigger>>;
16
- getScheduleTrigger(userId: string, automationId: string, triggerId: string): Promise<ScheduleTrigger | null>;
17
- updateScheduleTrigger(userId: string, automationId: string, triggerId: string, patch: StoredScheduleUpdate): Promise<ScheduleTrigger | null>;
18
- deleteScheduleTrigger(userId: string, automationId: string, triggerId: string): Promise<boolean>;
19
- createWebhookTrigger(userId: string, automationId: string, input: Required<CreateWebhookTrigger>): Promise<WebhookTrigger>;
20
- listWebhookTriggers(userId: string, automationId: string, query: Required<PageQuery>): Promise<Page<WebhookTrigger>>;
21
- getWebhookTrigger(userId: string, automationId: string, triggerId: string): Promise<WebhookTrigger | null>;
22
- updateWebhookTrigger(userId: string, automationId: string, triggerId: string, patch: UpdateWebhookTrigger): Promise<WebhookTrigger | null>;
23
- deleteWebhookTrigger(userId: string, automationId: string, triggerId: string): Promise<boolean>;
24
- listTriggers(userId: string, automationId: string): Promise<Trigger[]>;
25
- createWorkflow(userId: string, automationId: string, input: Required<CreateWorkflow>): Promise<Workflow>;
26
- getWorkflow(userId: string, automationId: string): Promise<Workflow | null>;
27
- updateWorkflow(userId: string, automationId: string, patch: UpdateWorkflow): Promise<Workflow | null>;
28
- deleteWorkflow(userId: string, automationId: string): Promise<boolean>;
29
- listRuns(userId: string, automationId: string, query: NormalizedListRuns): Promise<Page<AutomationRun>>;
30
- getRun(userId: string, automationId: string, runId: string): Promise<AutomationRun | null>;
31
- }
32
- export type NormalizedListAutomations = Required<PageQuery> & Omit<ListAutomations, keyof PageQuery>;
33
- export type NormalizedListRuns = Required<PageQuery> & Omit<ListRuns, keyof PageQuery>;
1
+ import type { AutomationCrud, AutomationCrudService as AutomationCrudServiceContract, AutomationPrincipal } from './contract.js';
2
+ import type { AutomationCrudRepository } from './crud/repository.js';
3
+ export type { AutomationCrudRepository, NormalizedListAutomations, NormalizedListRuns, StoredScheduleCreate, StoredScheduleUpdate, } from './crud/repository.js';
34
4
  export declare class AutomationCrudService implements AutomationCrudServiceContract {
35
5
  private readonly repository;
36
6
  private readonly clock;
package/dist/src/crud.js CHANGED
@@ -1,7 +1,8 @@
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
- import { nextCronAt } from './schedule.js';
1
+ import { automationOperations } from './crud/automations.js';
2
+ import { createContext } from './crud/context.js';
3
+ import { runOperations } from './crud/runs.js';
4
+ import { triggerOperations } from './crud/triggers.js';
5
+ import { workflowOperations } from './crud/workflow.js';
5
6
  export class AutomationCrudService {
6
7
  repository;
7
8
  clock;
@@ -10,240 +11,12 @@ export class AutomationCrudService {
10
11
  this.clock = clock;
11
12
  }
12
13
  for(principal) {
13
- assertPrincipal(principal);
14
- const read = () => assertScope(principal, 'automations:read');
15
- const write = () => assertScope(principal, 'automations:write');
16
- const runsRead = () => assertScope(principal, 'runs:read');
17
- const exists = async (automationId) => {
18
- const automation = await this.repository.getAutomation(principal.userId, id(automationId, 'Automation id'));
19
- if (!automation)
20
- throw new NotFoundError('Automation');
21
- };
14
+ const context = createContext(this.repository, principal, this.clock);
22
15
  return {
23
- automations: {
24
- create: async (input) => {
25
- write();
26
- const parsed = parseCreateAutomation(input);
27
- const targetId = parsed.targetId;
28
- assertTarget(principal, targetId);
29
- if (parsed.id && await this.repository.getAutomation(principal.userId, id(parsed.id, 'Automation id'))) {
30
- throw new ConflictError('Automation');
31
- }
32
- return this.repository.createAutomation(principal.userId, {
33
- id: parsed.id ? id(parsed.id, 'Automation id') : newId('automation'),
34
- targetId,
35
- name: parsed.name || '',
36
- description: parsed.description || '',
37
- enabled: parsed.enabled !== false,
38
- });
39
- },
40
- list: async (query = {}) => {
41
- read();
42
- const parsed = parseListAutomations(query);
43
- if (parsed.targetId)
44
- assertTarget(principal, parsed.targetId);
45
- return this.repository.listAutomations(principal.userId, {
46
- ...page(parsed),
47
- ...(parsed.targetId ? { targetId: id(parsed.targetId, 'targetId') } : {}),
48
- ...(parsed.enabled === undefined ? {} : { enabled: parsed.enabled }),
49
- });
50
- },
51
- get: async (automationId) => {
52
- read();
53
- return this.repository.getAutomation(principal.userId, id(automationId, 'Automation id'));
54
- },
55
- update: async (automationId, patch) => {
56
- write();
57
- const parsed = parseUpdateAutomation(patch);
58
- if (parsed.targetId !== undefined) {
59
- assertTarget(principal, parsed.targetId);
60
- }
61
- const updated = await this.repository.updateAutomation(principal.userId, id(automationId, 'Automation id'), parsed);
62
- if (!updated)
63
- throw new NotFoundError('Automation');
64
- return updated;
65
- },
66
- delete: async (automationId) => {
67
- write();
68
- const deleted = await this.repository.deleteAutomation(principal.userId, id(automationId, 'Automation id'));
69
- if (!deleted)
70
- throw new NotFoundError('Automation');
71
- },
72
- },
73
- triggers: {
74
- list: async (automationId) => {
75
- read();
76
- await exists(automationId);
77
- return this.repository.listTriggers(principal.userId, automationId);
78
- },
79
- schedule: {
80
- create: async (automationId, input) => {
81
- write();
82
- await exists(automationId);
83
- const parsed = parseCreateScheduleTrigger(input);
84
- const timezone = parsed.timezone || 'UTC';
85
- schedule(parsed.cron, timezone);
86
- if (parsed.id && (await this.repository.listTriggers(principal.userId, automationId))
87
- .some((trigger) => trigger.id === parsed.id))
88
- throw new ConflictError('Trigger');
89
- return this.repository.createScheduleTrigger(principal.userId, automationId, {
90
- id: parsed.id ? id(parsed.id, 'Schedule trigger id') : newId('schedule'),
91
- cron: parsed.cron,
92
- timezone,
93
- enabled: parsed.enabled !== false,
94
- nextRunAt: nextCronAt(parsed.cron, timezone, this.clock()),
95
- });
96
- },
97
- list: async (automationId, query = {}) => {
98
- read();
99
- await exists(automationId);
100
- return this.repository.listScheduleTriggers(principal.userId, automationId, page(parsePageQuery(query)));
101
- },
102
- get: async (automationId, triggerId) => {
103
- read();
104
- await exists(automationId);
105
- return this.repository.getScheduleTrigger(principal.userId, automationId, id(triggerId, 'Schedule trigger id'));
106
- },
107
- update: async (automationId, triggerId, patch) => {
108
- write();
109
- await exists(automationId);
110
- const parsed = parseUpdateScheduleTrigger(patch);
111
- let storedPatch = parsed;
112
- if (parsed.cron !== undefined || parsed.timezone !== undefined) {
113
- const existing = await this.repository.getScheduleTrigger(principal.userId, automationId, triggerId);
114
- if (!existing)
115
- throw new NotFoundError('Schedule trigger');
116
- const cron = parsed.cron ?? existing.cron;
117
- const timezone = parsed.timezone ?? existing.timezone;
118
- schedule(cron, timezone);
119
- storedPatch = { ...parsed, nextRunAt: nextCronAt(cron, timezone, this.clock()) };
120
- }
121
- const updated = await this.repository.updateScheduleTrigger(principal.userId, automationId, id(triggerId, 'Schedule trigger id'), storedPatch);
122
- if (!updated)
123
- throw new NotFoundError('Schedule trigger');
124
- return updated;
125
- },
126
- delete: async (automationId, triggerId) => {
127
- write();
128
- await exists(automationId);
129
- const deleted = await this.repository.deleteScheduleTrigger(principal.userId, automationId, id(triggerId, 'Schedule trigger id'));
130
- if (!deleted)
131
- throw new NotFoundError('Schedule trigger');
132
- },
133
- },
134
- webhook: {
135
- create: async (automationId, input) => {
136
- write();
137
- await exists(automationId);
138
- const parsed = parseCreateWebhookTrigger(input);
139
- if (parsed.id && (await this.repository.listTriggers(principal.userId, automationId))
140
- .some((trigger) => trigger.id === parsed.id))
141
- throw new ConflictError('Trigger');
142
- return this.repository.createWebhookTrigger(principal.userId, automationId, {
143
- id: parsed.id ? id(parsed.id, 'Webhook trigger id') : newId('webhook'),
144
- source: parsed.source || '*',
145
- event: parsed.event || '*',
146
- secret: parsed.secret,
147
- enabled: parsed.enabled !== false,
148
- });
149
- },
150
- list: async (automationId, query = {}) => {
151
- read();
152
- await exists(automationId);
153
- return this.repository.listWebhookTriggers(principal.userId, automationId, page(parsePageQuery(query)));
154
- },
155
- get: async (automationId, triggerId) => {
156
- read();
157
- await exists(automationId);
158
- return this.repository.getWebhookTrigger(principal.userId, automationId, id(triggerId, 'Webhook trigger id'));
159
- },
160
- update: async (automationId, triggerId, patch) => {
161
- write();
162
- await exists(automationId);
163
- const parsed = parseUpdateWebhookTrigger(patch);
164
- const updated = await this.repository.updateWebhookTrigger(principal.userId, automationId, id(triggerId, 'Webhook trigger id'), parsed);
165
- if (!updated)
166
- throw new NotFoundError('Webhook trigger');
167
- return updated;
168
- },
169
- delete: async (automationId, triggerId) => {
170
- write();
171
- await exists(automationId);
172
- const deleted = await this.repository.deleteWebhookTrigger(principal.userId, automationId, id(triggerId, 'Webhook trigger id'));
173
- if (!deleted)
174
- throw new NotFoundError('Webhook trigger');
175
- },
176
- },
177
- },
178
- workflow: {
179
- create: async (automationId, input) => {
180
- write();
181
- await exists(automationId);
182
- if (await this.repository.getWorkflow(principal.userId, automationId)) {
183
- throw new ConflictError('Workflow');
184
- }
185
- const parsed = parseCreateWorkflow(input);
186
- return this.repository.createWorkflow(principal.userId, automationId, {
187
- id: parsed.id ? id(parsed.id, 'Workflow id') : newId('workflow'),
188
- name: parsed.name || '',
189
- script: parsed.script,
190
- compiled: parsed.compiled ?? null,
191
- allowlist: parsed.allowlist ?? null,
192
- limits: parsed.limits ?? null,
193
- });
194
- },
195
- get: async (automationId) => {
196
- read();
197
- await exists(automationId);
198
- return this.repository.getWorkflow(principal.userId, automationId);
199
- },
200
- update: async (automationId, patch) => {
201
- write();
202
- await exists(automationId);
203
- const parsed = parseUpdateWorkflow(patch);
204
- const updated = await this.repository.updateWorkflow(principal.userId, automationId, parsed);
205
- if (!updated)
206
- throw new NotFoundError('Workflow');
207
- return updated;
208
- },
209
- delete: async (automationId) => {
210
- write();
211
- await exists(automationId);
212
- if (!await this.repository.deleteWorkflow(principal.userId, automationId)) {
213
- throw new NotFoundError('Workflow');
214
- }
215
- },
216
- },
217
- runs: {
218
- list: async (automationId, query = {}) => {
219
- runsRead();
220
- const parsed = parseListRuns(query);
221
- return this.repository.listRuns(principal.userId, id(automationId, 'Automation id'), {
222
- ...page(parsed),
223
- ...(parsed.status ? { status: parsed.status } : {}),
224
- });
225
- },
226
- get: async (automationId, runId) => {
227
- runsRead();
228
- return this.repository.getRun(principal.userId, id(automationId, 'Automation id'), id(runId, 'Run id'));
229
- },
230
- },
16
+ automations: automationOperations(context),
17
+ triggers: triggerOperations(context),
18
+ workflow: workflowOperations(context),
19
+ runs: runOperations(context),
231
20
  };
232
21
  }
233
22
  }
234
- function assertPrincipal(principal) {
235
- requiredText(principal.userId, 'Authenticated user id');
236
- if (!Array.isArray(principal.scopes))
237
- throw new ForbiddenError();
238
- principal.targetIds?.forEach((targetId) => id(targetId, 'Authorized target id'));
239
- }
240
- function assertScope(principal, scope) {
241
- if (!principal.scopes.includes('*') && !principal.scopes.includes(scope)) {
242
- throw new ForbiddenError(`This credential requires ${scope}`);
243
- }
244
- }
245
- function assertTarget(principal, targetId) {
246
- if (principal.targetIds && !principal.targetIds.includes(targetId)) {
247
- throw new ForbiddenError('This credential cannot access that target');
248
- }
249
- }