@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.
Files changed (41) hide show
  1. package/AXIOMS.md +32 -0
  2. package/PURPOSE.md +35 -0
  3. package/README.md +130 -0
  4. package/dist/src/automations.d.ts +27 -0
  5. package/dist/src/automations.js +199 -0
  6. package/dist/src/cli-main.d.ts +2 -0
  7. package/dist/src/cli-main.js +21 -0
  8. package/dist/src/cli.d.ts +10 -0
  9. package/dist/src/cli.js +183 -0
  10. package/dist/src/client.d.ts +7 -0
  11. package/dist/src/client.js +85 -0
  12. package/dist/src/contract.d.ts +176 -0
  13. package/dist/src/contract.js +1 -0
  14. package/dist/src/crud.d.ts +32 -0
  15. package/dist/src/crud.js +241 -0
  16. package/dist/src/errors.d.ts +17 -0
  17. package/dist/src/errors.js +32 -0
  18. package/dist/src/http.d.ts +7 -0
  19. package/dist/src/http.js +154 -0
  20. package/dist/src/index.d.ts +14 -0
  21. package/dist/src/index.js +18 -0
  22. package/dist/src/mcp-main.d.ts +2 -0
  23. package/dist/src/mcp-main.js +17 -0
  24. package/dist/src/mcp.d.ts +3 -0
  25. package/dist/src/mcp.js +88 -0
  26. package/dist/src/schedule.d.ts +2 -0
  27. package/dist/src/schedule.js +10 -0
  28. package/dist/src/schema.d.ts +236 -0
  29. package/dist/src/schema.js +119 -0
  30. package/dist/src/supabase-crud.d.ts +44 -0
  31. package/dist/src/supabase-crud.js +271 -0
  32. package/dist/src/supabase-store.d.ts +26 -0
  33. package/dist/src/supabase-store.js +123 -0
  34. package/dist/src/types.d.ts +128 -0
  35. package/dist/src/types.js +1 -0
  36. package/dist/src/validation.d.ts +11 -0
  37. package/dist/src/validation.js +69 -0
  38. package/dist/src/webhook.d.ts +18 -0
  39. package/dist/src/webhook.js +60 -0
  40. package/package.json +66 -0
  41. package/supabase/migrations/20260802000000_create_automations.sql +609 -0
@@ -0,0 +1,119 @@
1
+ import { z } from 'zod/v3';
2
+ import { ValidationError } from './errors.js';
3
+ import { IDENTIFIER } from './validation.js';
4
+ const maxPageSize = 100;
5
+ const identifierMessage = 'Identifier is invalid';
6
+ const text = (maximum, label) => z.string()
7
+ .max(maximum, `${label} is too long`)
8
+ .refine((value) => value.trim().length > 0, `${label} is required`);
9
+ export const identifierSchema = z.string().regex(IDENTIFIER, identifierMessage);
10
+ export const pageQuerySchema = z.object({
11
+ limit: z.number().int().min(1).max(maxPageSize).optional(),
12
+ offset: z.number().int().min(0).optional(),
13
+ }).strict();
14
+ const jsonSchema = z.lazy(() => z.union([
15
+ z.null(),
16
+ z.boolean(),
17
+ z.number().finite(),
18
+ z.string(),
19
+ z.array(jsonSchema),
20
+ z.record(jsonSchema),
21
+ ]));
22
+ export const createAutomationSchema = z.object({
23
+ id: identifierSchema.optional(),
24
+ targetId: identifierSchema,
25
+ name: text(500, 'Automation name').optional(),
26
+ description: text(10_000, 'Automation description').optional(),
27
+ enabled: z.boolean().optional(),
28
+ }).strict();
29
+ export const updateAutomationSchema = z.object({
30
+ targetId: identifierSchema.optional(),
31
+ name: text(500, 'Automation name').nullable().optional(),
32
+ description: text(10_000, 'Automation description').nullable().optional(),
33
+ enabled: z.boolean().optional(),
34
+ }).strict().refine((patch) => Object.keys(patch).length > 0, 'Automation update must include a change');
35
+ export const listAutomationsSchema = pageQuerySchema.extend({
36
+ targetId: identifierSchema.optional(),
37
+ enabled: z.boolean().optional(),
38
+ }).strict();
39
+ export const createScheduleTriggerSchema = z.object({
40
+ id: identifierSchema.optional(),
41
+ cron: text(200, 'Schedule cron'),
42
+ timezone: text(200, 'Schedule timezone').optional(),
43
+ enabled: z.boolean().optional(),
44
+ }).strict();
45
+ export const updateScheduleTriggerSchema = z.object({
46
+ cron: text(200, 'Schedule cron').optional(),
47
+ timezone: text(200, 'Schedule timezone').optional(),
48
+ enabled: z.boolean().optional(),
49
+ }).strict().refine((patch) => Object.keys(patch).length > 0, 'Schedule trigger update must include a change');
50
+ export const createWebhookTriggerSchema = z.object({
51
+ id: identifierSchema.optional(),
52
+ source: text(200, 'Webhook source').optional(),
53
+ event: text(200, 'Webhook event').optional(),
54
+ secret: z.string().min(16, 'Webhook secret must be at least 16 characters').max(10_000, 'Webhook secret is too long'),
55
+ enabled: z.boolean().optional(),
56
+ }).strict();
57
+ export const updateWebhookTriggerSchema = z.object({
58
+ source: text(200, 'Webhook source').optional(),
59
+ event: text(200, 'Webhook event').optional(),
60
+ secret: z.string().min(16, 'Webhook secret must be at least 16 characters').max(10_000, 'Webhook secret is too long').optional(),
61
+ enabled: z.boolean().optional(),
62
+ }).strict().refine((patch) => Object.keys(patch).length > 0, 'Webhook trigger update must include a change');
63
+ export const createWorkflowSchema = z.object({
64
+ id: identifierSchema.optional(),
65
+ name: text(500, 'Workflow name').optional(),
66
+ script: text(1_000_000, 'Workflow script'),
67
+ compiled: jsonSchema.optional(),
68
+ allowlist: jsonSchema.optional(),
69
+ limits: jsonSchema.optional(),
70
+ }).strict();
71
+ export const updateWorkflowSchema = z.object({
72
+ name: text(500, 'Workflow name').nullable().optional(),
73
+ script: text(1_000_000, 'Workflow script').optional(),
74
+ compiled: jsonSchema.nullable().optional(),
75
+ allowlist: jsonSchema.nullable().optional(),
76
+ limits: jsonSchema.nullable().optional(),
77
+ }).strict().refine((patch) => Object.keys(patch).length > 0, 'Workflow update must include a change');
78
+ export const listRunsSchema = pageQuerySchema.extend({
79
+ status: z.enum(['pending', 'sent', 'running', 'completed', 'failed']).optional(),
80
+ }).strict();
81
+ export function parseCreateAutomation(value) {
82
+ return parse(createAutomationSchema, value);
83
+ }
84
+ export function parseUpdateAutomation(value) {
85
+ return parse(updateAutomationSchema, value);
86
+ }
87
+ export function parseListAutomations(value) {
88
+ return parse(listAutomationsSchema, value);
89
+ }
90
+ export function parseCreateScheduleTrigger(value) {
91
+ return parse(createScheduleTriggerSchema, value);
92
+ }
93
+ export function parseUpdateScheduleTrigger(value) {
94
+ return parse(updateScheduleTriggerSchema, value);
95
+ }
96
+ export function parseCreateWebhookTrigger(value) {
97
+ return parse(createWebhookTriggerSchema, value);
98
+ }
99
+ export function parseUpdateWebhookTrigger(value) {
100
+ return parse(updateWebhookTriggerSchema, value);
101
+ }
102
+ export function parseCreateWorkflow(value) {
103
+ return parse(createWorkflowSchema, value);
104
+ }
105
+ export function parseUpdateWorkflow(value) {
106
+ return parse(updateWorkflowSchema, value);
107
+ }
108
+ export function parsePageQuery(value) {
109
+ return parse(pageQuerySchema, value);
110
+ }
111
+ export function parseListRuns(value) {
112
+ return parse(listRunsSchema, value);
113
+ }
114
+ export function parse(schema, value) {
115
+ const result = schema.safeParse(value);
116
+ if (result.success)
117
+ return result.data;
118
+ throw new ValidationError(result.error.issues[0]?.message || 'Input is invalid');
119
+ }
@@ -0,0 +1,44 @@
1
+ import type { Automation, AutomationRun, CreateAutomation, CreateScheduleTrigger, CreateWebhookTrigger, CreateWorkflow, Page, ScheduleTrigger, Trigger, UpdateAutomation, UpdateScheduleTrigger, UpdateWebhookTrigger, UpdateWorkflow, WebhookTrigger, Workflow } from './contract.js';
2
+ import type { AutomationCrudRepository, NormalizedListAutomations, NormalizedListRuns } from './crud.js';
3
+ export interface SupabaseRpcClient {
4
+ rpc(functionName: string, arguments_: Record<string, unknown>): PromiseLike<{
5
+ data: unknown;
6
+ error: {
7
+ code?: string;
8
+ message?: string;
9
+ } | null;
10
+ }>;
11
+ }
12
+ export declare class SupabaseAutomationCrudRepository implements AutomationCrudRepository {
13
+ #private;
14
+ private readonly client;
15
+ constructor(client: SupabaseRpcClient);
16
+ createAutomation(userId: string, input: Required<CreateAutomation>): Promise<Automation>;
17
+ listAutomations(userId: string, query: NormalizedListAutomations): Promise<Page<Automation>>;
18
+ getAutomation(userId: string, automationId: string): Promise<Automation | null>;
19
+ updateAutomation(userId: string, automationId: string, patch: UpdateAutomation): Promise<Automation | null>;
20
+ deleteAutomation(userId: string, automationId: string): Promise<boolean>;
21
+ createScheduleTrigger(userId: string, automationId: string, input: Required<CreateScheduleTrigger>): Promise<ScheduleTrigger>;
22
+ listScheduleTriggers(userId: string, automationId: string, query: Required<{
23
+ limit?: number;
24
+ offset?: number;
25
+ }>): Promise<Page<ScheduleTrigger>>;
26
+ getScheduleTrigger(userId: string, automationId: string, triggerId: string): Promise<ScheduleTrigger | null>;
27
+ updateScheduleTrigger(userId: string, automationId: string, triggerId: string, patch: UpdateScheduleTrigger): Promise<ScheduleTrigger | null>;
28
+ deleteScheduleTrigger(userId: string, automationId: string, triggerId: string): Promise<boolean>;
29
+ createWebhookTrigger(userId: string, automationId: string, input: Required<CreateWebhookTrigger>): Promise<WebhookTrigger>;
30
+ listWebhookTriggers(userId: string, automationId: string, query: Required<{
31
+ limit?: number;
32
+ offset?: number;
33
+ }>): Promise<Page<WebhookTrigger>>;
34
+ getWebhookTrigger(userId: string, automationId: string, triggerId: string): Promise<WebhookTrigger | null>;
35
+ updateWebhookTrigger(userId: string, automationId: string, triggerId: string, patch: UpdateWebhookTrigger): Promise<WebhookTrigger | null>;
36
+ deleteWebhookTrigger(userId: string, automationId: string, triggerId: string): Promise<boolean>;
37
+ listTriggers(userId: string, automationId: string): Promise<Trigger[]>;
38
+ createWorkflow(userId: string, automationId: string, input: Required<CreateWorkflow>): Promise<Workflow>;
39
+ getWorkflow(userId: string, automationId: string): Promise<Workflow | null>;
40
+ updateWorkflow(userId: string, automationId: string, patch: UpdateWorkflow): Promise<Workflow | null>;
41
+ deleteWorkflow(userId: string, automationId: string): Promise<boolean>;
42
+ listRuns(userId: string, automationId: string, query: NormalizedListRuns): Promise<Page<AutomationRun>>;
43
+ getRun(userId: string, automationId: string, runId: string): Promise<AutomationRun | null>;
44
+ }
@@ -0,0 +1,271 @@
1
+ import { ConflictError } from './errors.js';
2
+ export class SupabaseAutomationCrudRepository {
3
+ client;
4
+ constructor(client) {
5
+ this.client = client;
6
+ }
7
+ async createAutomation(userId, input) {
8
+ return automation(await this.#call('create_amalgm_automation', {
9
+ p_user_id: userId,
10
+ p_automation: input,
11
+ }));
12
+ }
13
+ async listAutomations(userId, query) {
14
+ const result = await this.#call('list_amalgm_automations', {
15
+ p_user_id: userId,
16
+ p_target_id: query.targetId || null,
17
+ p_enabled: query.enabled ?? null,
18
+ p_limit: query.limit,
19
+ p_offset: query.offset,
20
+ });
21
+ return paged(result, automation, query);
22
+ }
23
+ async getAutomation(userId, automationId) {
24
+ const row = await this.#call('get_amalgm_automation', {
25
+ p_user_id: userId,
26
+ p_automation_id: automationId,
27
+ });
28
+ return row ? automation(row) : null;
29
+ }
30
+ async updateAutomation(userId, automationId, patch) {
31
+ const row = await this.#call('update_amalgm_automation', {
32
+ p_user_id: userId,
33
+ p_automation_id: automationId,
34
+ p_patch: patch,
35
+ });
36
+ return row ? automation(row) : null;
37
+ }
38
+ deleteAutomation(userId, automationId) {
39
+ return this.#call('delete_amalgm_automation', {
40
+ p_user_id: userId,
41
+ p_automation_id: automationId,
42
+ });
43
+ }
44
+ async createScheduleTrigger(userId, automationId, input) {
45
+ return schedule(await this.#call('create_amalgm_schedule_trigger', {
46
+ p_user_id: userId,
47
+ p_automation_id: automationId,
48
+ p_trigger: input,
49
+ }));
50
+ }
51
+ async listScheduleTriggers(userId, automationId, query) {
52
+ return this.#listTriggers('list_amalgm_schedule_triggers', userId, automationId, query, schedule);
53
+ }
54
+ async getScheduleTrigger(userId, automationId, triggerId) {
55
+ const row = await this.#call('get_amalgm_schedule_trigger', {
56
+ p_user_id: userId,
57
+ p_automation_id: automationId,
58
+ p_trigger_id: triggerId,
59
+ });
60
+ return row ? schedule(row) : null;
61
+ }
62
+ async updateScheduleTrigger(userId, automationId, triggerId, patch) {
63
+ const row = await this.#call('update_amalgm_schedule_trigger', {
64
+ p_user_id: userId,
65
+ p_automation_id: automationId,
66
+ p_trigger_id: triggerId,
67
+ p_patch: patch,
68
+ });
69
+ return row ? schedule(row) : null;
70
+ }
71
+ deleteScheduleTrigger(userId, automationId, triggerId) {
72
+ return this.#call('delete_amalgm_schedule_trigger', {
73
+ p_user_id: userId,
74
+ p_automation_id: automationId,
75
+ p_trigger_id: triggerId,
76
+ });
77
+ }
78
+ async createWebhookTrigger(userId, automationId, input) {
79
+ return webhook(await this.#call('create_amalgm_webhook_trigger', {
80
+ p_user_id: userId,
81
+ p_automation_id: automationId,
82
+ p_trigger: input,
83
+ }));
84
+ }
85
+ async listWebhookTriggers(userId, automationId, query) {
86
+ return this.#listTriggers('list_amalgm_webhook_triggers', userId, automationId, query, webhook);
87
+ }
88
+ async getWebhookTrigger(userId, automationId, triggerId) {
89
+ const row = await this.#call('get_amalgm_webhook_trigger', {
90
+ p_user_id: userId,
91
+ p_automation_id: automationId,
92
+ p_trigger_id: triggerId,
93
+ });
94
+ return row ? webhook(row) : null;
95
+ }
96
+ async updateWebhookTrigger(userId, automationId, triggerId, patch) {
97
+ const row = await this.#call('update_amalgm_webhook_trigger', {
98
+ p_user_id: userId,
99
+ p_automation_id: automationId,
100
+ p_trigger_id: triggerId,
101
+ p_patch: patch,
102
+ });
103
+ return row ? webhook(row) : null;
104
+ }
105
+ deleteWebhookTrigger(userId, automationId, triggerId) {
106
+ return this.#call('delete_amalgm_webhook_trigger', {
107
+ p_user_id: userId,
108
+ p_automation_id: automationId,
109
+ p_trigger_id: triggerId,
110
+ });
111
+ }
112
+ async listTriggers(userId, automationId) {
113
+ const rows = await this.#call('list_amalgm_triggers', {
114
+ p_user_id: userId,
115
+ p_automation_id: automationId,
116
+ });
117
+ return rows.map((row) => row.kind === 'schedule' ? schedule(row) : webhook(row));
118
+ }
119
+ async createWorkflow(userId, automationId, input) {
120
+ return workflow(await this.#call('create_amalgm_workflow', {
121
+ p_user_id: userId,
122
+ p_automation_id: automationId,
123
+ p_workflow: input,
124
+ }));
125
+ }
126
+ async getWorkflow(userId, automationId) {
127
+ const row = await this.#call('get_amalgm_workflow', {
128
+ p_user_id: userId,
129
+ p_automation_id: automationId,
130
+ });
131
+ return row ? workflow(row) : null;
132
+ }
133
+ async updateWorkflow(userId, automationId, patch) {
134
+ const row = await this.#call('update_amalgm_workflow', {
135
+ p_user_id: userId,
136
+ p_automation_id: automationId,
137
+ p_patch: patch,
138
+ });
139
+ return row ? workflow(row) : null;
140
+ }
141
+ deleteWorkflow(userId, automationId) {
142
+ return this.#call('delete_amalgm_workflow', {
143
+ p_user_id: userId,
144
+ p_automation_id: automationId,
145
+ });
146
+ }
147
+ async listRuns(userId, automationId, query) {
148
+ const result = await this.#call('list_amalgm_runs', {
149
+ p_user_id: userId,
150
+ p_automation_id: automationId,
151
+ p_status: query.status || null,
152
+ p_limit: query.limit,
153
+ p_offset: query.offset,
154
+ });
155
+ return paged(result, run, query);
156
+ }
157
+ async getRun(userId, automationId, runId) {
158
+ const row = await this.#call('get_amalgm_run', {
159
+ p_user_id: userId,
160
+ p_automation_id: automationId,
161
+ p_run_id: runId,
162
+ });
163
+ return row ? run(row) : null;
164
+ }
165
+ async #listTriggers(functionName, userId, automationId, query, mapper) {
166
+ const result = await this.#call(functionName, {
167
+ p_user_id: userId,
168
+ p_automation_id: automationId,
169
+ p_limit: query.limit,
170
+ p_offset: query.offset,
171
+ });
172
+ return paged(result, mapper, query);
173
+ }
174
+ async #call(functionName, arguments_) {
175
+ const { data, error } = await this.client.rpc(functionName, arguments_);
176
+ if (error) {
177
+ if (error.code === '23505' || /duplicate key|unique constraint/i.test(error.message || '')) {
178
+ throw new ConflictError(resource(functionName));
179
+ }
180
+ throw new Error(error.message || `Supabase function ${functionName} failed`);
181
+ }
182
+ return data;
183
+ }
184
+ }
185
+ function resource(functionName) {
186
+ if (functionName.includes('workflow'))
187
+ return 'Workflow';
188
+ if (functionName.includes('trigger'))
189
+ return 'Trigger';
190
+ if (functionName.includes('automation'))
191
+ return 'Automation';
192
+ return 'Automation resource';
193
+ }
194
+ function automation(row) {
195
+ return {
196
+ id: row.id,
197
+ targetId: row.target_id,
198
+ ...(row.name ? { name: row.name } : {}),
199
+ ...(row.description ? { description: row.description } : {}),
200
+ enabled: row.enabled,
201
+ createdAt: timestamp(row.created_at),
202
+ updatedAt: timestamp(row.updated_at),
203
+ };
204
+ }
205
+ function schedule(row) {
206
+ return {
207
+ id: row.id,
208
+ automationId: row.automation_id,
209
+ kind: 'schedule',
210
+ enabled: row.enabled,
211
+ cron: row.cron,
212
+ timezone: row.timezone,
213
+ createdAt: timestamp(row.created_at),
214
+ updatedAt: timestamp(row.updated_at),
215
+ };
216
+ }
217
+ function webhook(row) {
218
+ return {
219
+ id: row.id,
220
+ automationId: row.automation_id,
221
+ kind: 'webhook',
222
+ enabled: row.enabled,
223
+ source: row.source,
224
+ event: row.event,
225
+ secretConfigured: row.secret_configured,
226
+ createdAt: timestamp(row.created_at),
227
+ updatedAt: timestamp(row.updated_at),
228
+ };
229
+ }
230
+ function workflow(row) {
231
+ return {
232
+ id: row.id,
233
+ automationId: row.automation_id,
234
+ ...(row.name ? { name: row.name } : {}),
235
+ script: row.script,
236
+ ...(row.compiled !== null ? { compiled: row.compiled } : {}),
237
+ ...(row.allowlist !== null ? { allowlist: row.allowlist } : {}),
238
+ ...(row.limits !== null ? { limits: row.limits } : {}),
239
+ createdAt: timestamp(row.created_at),
240
+ updatedAt: timestamp(row.updated_at),
241
+ };
242
+ }
243
+ function run(row) {
244
+ return {
245
+ id: row.id,
246
+ automationId: row.automation_id,
247
+ triggerId: row.trigger_id,
248
+ workflowId: row.workflow_id,
249
+ targetId: row.target_id,
250
+ status: row.status,
251
+ input: row.input,
252
+ createdAt: timestamp(row.created_at),
253
+ ...(row.sent_at ? { sentAt: timestamp(row.sent_at) } : {}),
254
+ ...(row.started_at ? { startedAt: timestamp(row.started_at) } : {}),
255
+ ...(row.finished_at ? { finishedAt: timestamp(row.finished_at) } : {}),
256
+ ...(row.output !== null ? { output: row.output } : {}),
257
+ ...(row.error !== null ? { error: row.error } : {}),
258
+ };
259
+ }
260
+ function paged(result, mapper, query) {
261
+ return {
262
+ items: result.items.map(mapper),
263
+ total: result.total,
264
+ limit: query.limit,
265
+ offset: query.offset,
266
+ hasMore: query.offset + result.items.length < result.total,
267
+ };
268
+ }
269
+ function timestamp(value) {
270
+ return new Date(value).toISOString();
271
+ }
@@ -0,0 +1,26 @@
1
+ import type { AutomationStore, AutomationTarget, AutomationRun, DueCronTrigger, PreparedAutomation, RunInput, RunUpdate, StoredEventTrigger } from './types.js';
2
+ export interface SupabaseRpcClient {
3
+ rpc(functionName: string, arguments_: Record<string, unknown>): PromiseLike<{
4
+ data: unknown;
5
+ error: {
6
+ message?: string;
7
+ } | null;
8
+ }>;
9
+ }
10
+ export declare class SupabaseStore implements AutomationStore {
11
+ #private;
12
+ readonly client: SupabaseRpcClient;
13
+ constructor(client: SupabaseRpcClient);
14
+ saveAutomation(automation: PreparedAutomation): Promise<void>;
15
+ deleteAutomation(userId: string, automationId: string): Promise<boolean>;
16
+ eventTriggers(target: AutomationTarget): Promise<StoredEventTrigger[]>;
17
+ dueCronTriggers(now: Date): Promise<DueCronTrigger[]>;
18
+ enqueueEvent(trigger: StoredEventTrigger, input: Extract<RunInput, {
19
+ kind: 'event';
20
+ }>, now: Date): Promise<AutomationRun | null>;
21
+ enqueueCron(trigger: DueCronTrigger, nextRunAt: string, now: Date): Promise<AutomationRun | null>;
22
+ pendingRuns(target: AutomationTarget): Promise<AutomationRun[]>;
23
+ listRuns(userId: string, automationId?: string): Promise<AutomationRun[]>;
24
+ markSent(runId: string, target: AutomationTarget, now: Date): Promise<boolean>;
25
+ updateRun(target: AutomationTarget, runId: string, update: RunUpdate): Promise<AutomationRun | null>;
26
+ }
@@ -0,0 +1,123 @@
1
+ function automationRun(row) {
2
+ return {
3
+ id: row.id,
4
+ userId: row.user_id,
5
+ targetId: row.target_id,
6
+ automationId: row.automation_id,
7
+ triggerId: row.trigger_id,
8
+ workflowId: row.workflow_id,
9
+ automation: row.automation_payload,
10
+ input: row.input,
11
+ status: row.status,
12
+ createdAt: new Date(row.created_at).toISOString(),
13
+ ...(row.sent_at ? { sentAt: new Date(row.sent_at).toISOString() } : {}),
14
+ ...(row.started_at ? { startedAt: new Date(row.started_at).toISOString() } : {}),
15
+ ...(row.finished_at ? { finishedAt: new Date(row.finished_at).toISOString() } : {}),
16
+ ...(row.output !== null ? { output: row.output } : {}),
17
+ ...(row.error !== null ? { error: row.error } : {}),
18
+ };
19
+ }
20
+ export class SupabaseStore {
21
+ client;
22
+ constructor(client) {
23
+ this.client = client;
24
+ }
25
+ async saveAutomation(automation) {
26
+ await this.#call('save_amalgm_automation', { p_automation: automation });
27
+ }
28
+ deleteAutomation(userId, automationId) {
29
+ return this.#call('delete_amalgm_automation', {
30
+ p_user_id: userId,
31
+ p_automation_id: automationId,
32
+ });
33
+ }
34
+ async eventTriggers(target) {
35
+ const rows = await this.#call('list_amalgm_event_triggers', {
36
+ p_user_id: target.userId,
37
+ p_target_id: target.targetId,
38
+ });
39
+ return rows.map((row) => ({
40
+ kind: 'event',
41
+ userId: row.user_id,
42
+ automationId: row.automation_id,
43
+ triggerId: row.trigger_id,
44
+ targetId: row.target_id,
45
+ source: row.source,
46
+ event: row.event,
47
+ secret: row.secret,
48
+ }));
49
+ }
50
+ async dueCronTriggers(now) {
51
+ const rows = await this.#call('list_due_amalgm_crons', { p_now: now.toISOString() });
52
+ return rows.map((row) => ({
53
+ kind: 'cron',
54
+ userId: row.user_id,
55
+ automationId: row.automation_id,
56
+ triggerId: row.trigger_id,
57
+ targetId: row.target_id,
58
+ cron: row.cron,
59
+ timezone: row.timezone,
60
+ nextRunAt: new Date(row.next_run_at).toISOString(),
61
+ }));
62
+ }
63
+ enqueueEvent(trigger, input, now) {
64
+ return this.#enqueue(trigger, input, now, null);
65
+ }
66
+ enqueueCron(trigger, nextRunAt, now) {
67
+ return this.#enqueue(trigger, {
68
+ kind: 'cron',
69
+ scheduledFor: trigger.nextRunAt,
70
+ }, now, nextRunAt);
71
+ }
72
+ async pendingRuns(target) {
73
+ const rows = await this.#call('list_amalgm_pending_runs', {
74
+ p_user_id: target.userId,
75
+ p_target_id: target.targetId,
76
+ });
77
+ return rows.map(automationRun);
78
+ }
79
+ async listRuns(userId, automationId) {
80
+ const rows = await this.#call('list_amalgm_runs', {
81
+ p_user_id: userId,
82
+ p_automation_id: automationId || null,
83
+ });
84
+ return rows.map(automationRun);
85
+ }
86
+ markSent(runId, target, now) {
87
+ return this.#call('mark_amalgm_run_sent', {
88
+ p_run_id: runId,
89
+ p_user_id: target.userId,
90
+ p_target_id: target.targetId,
91
+ p_now: now.toISOString(),
92
+ });
93
+ }
94
+ async updateRun(target, runId, update) {
95
+ const rows = await this.#call('update_amalgm_run', {
96
+ p_run_id: runId,
97
+ p_user_id: target.userId,
98
+ p_target_id: target.targetId,
99
+ p_update: update,
100
+ });
101
+ return rows[0] ? automationRun(rows[0]) : null;
102
+ }
103
+ async #enqueue(trigger, input, now, nextRunAt) {
104
+ const rows = await this.#call('enqueue_amalgm_run', {
105
+ p_kind: trigger.kind,
106
+ p_user_id: trigger.userId,
107
+ p_automation_id: trigger.automationId,
108
+ p_trigger_id: trigger.triggerId,
109
+ p_target_id: trigger.targetId,
110
+ p_expected_next_run_at: trigger.kind === 'cron' ? trigger.nextRunAt : null,
111
+ p_next_run_at: nextRunAt,
112
+ p_input: input,
113
+ p_now: now.toISOString(),
114
+ });
115
+ return rows[0] ? automationRun(rows[0]) : null;
116
+ }
117
+ async #call(functionName, arguments_) {
118
+ const { data, error } = await this.client.rpc(functionName, arguments_);
119
+ if (error)
120
+ throw new Error(error.message || `Supabase function ${functionName} failed`);
121
+ return data;
122
+ }
123
+ }