From 36a06a9e3d2591aa16f08e436381662075b17778 Mon Sep 17 00:00:00 2001 From: Kfir Strikovsky Date: Sun, 1 Feb 2026 11:21:41 +0200 Subject: [PATCH 1/3] added automation schema --- src/core/resources/function/config.ts | 4 +- src/core/resources/function/deploy.ts | 2 +- src/core/resources/function/schema.ts | 80 +++++++++++++++++++++++++-- 3 files changed, 77 insertions(+), 9 deletions(-) diff --git a/src/core/resources/function/config.ts b/src/core/resources/function/config.ts index c47de2aa..385250a3 100644 --- a/src/core/resources/function/config.ts +++ b/src/core/resources/function/config.ts @@ -2,7 +2,7 @@ import { dirname, join } from "node:path"; import { globby } from "globby"; import { FUNCTION_CONFIG_FILE } from "@/core/consts.js"; import { readJsonFile, pathExists } from "@/core/utils/fs.js"; -import { FunctionConfigSchema, FunctionSchema } from "@/core/resources/function/schema.js"; +import { FunctionConfigSchema, FunctionLocalSchema } from "@/core/resources/function/schema.js"; import type { FunctionConfig, Function } from "@/core/resources/function/schema.js"; import { SchemaValidationError, FileNotFoundError } from "@/core/errors.js"; @@ -36,7 +36,7 @@ export async function readFunction(configPath: string): Promise { }); const functionData = { ...config, entryPath, files }; - const result = FunctionSchema.safeParse(functionData); + const result = FunctionLocalSchema.safeParse(functionData); if (!result.success) { throw new SchemaValidationError(`Invalid function in ${configPath}`, result.error); } diff --git a/src/core/resources/function/deploy.ts b/src/core/resources/function/deploy.ts index 5dd0d8e7..748bf3ae 100644 --- a/src/core/resources/function/deploy.ts +++ b/src/core/resources/function/deploy.ts @@ -17,7 +17,7 @@ export async function pushFunctions( functions: Function[] ): Promise { if (functions.length === 0) { - return { deployed: [], deleted: [], errors: null }; + return { deployed: [], deleted: [], skipped: [], errors: null }; } const functionsWithCode = await Promise.all(functions.map(loadFunctionCode)); diff --git a/src/core/resources/function/schema.ts b/src/core/resources/function/schema.ts index b8386598..0c6ee75b 100644 --- a/src/core/resources/function/schema.ts +++ b/src/core/resources/function/schema.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -const FunctionNameSchema = z +export const FunctionNameSchema = z .string() .trim() .min(1, "Function name cannot be empty") @@ -11,37 +11,105 @@ const FunctionFileSchema = z.object({ content: z.string(), }); +// Base fields shared by all automation types +const AutomationBaseSchema = z.object({ + name: z.string().min(1, "Automation name cannot be empty"), + description: z.string().nullable().optional(), + function_args: z.record(z.string(), z.unknown()).nullable().optional(), + is_active: z.boolean().optional().default(true), +}); + +// One-time scheduled automation +const ScheduledOneTimeSchema = AutomationBaseSchema.extend({ + type: z.literal("scheduled"), + schedule_mode: z.literal("one-time"), + one_time_date: z.string().min(1, "One-time date is required for one-time schedules"), +}); + +// Recurring cron scheduled automation +const ScheduledCronSchema = AutomationBaseSchema.extend({ + type: z.literal("scheduled"), + schedule_mode: z.literal("recurring"), + schedule_type: z.literal("cron"), + cron_expression: z.string().min(1, "Cron expression is required for cron schedules"), + ends_type: z.enum(["never", "on", "after"]).optional().default("never"), + ends_on_date: z.string().nullable().optional(), + ends_after_count: z.number().int().positive().nullable().optional(), +}); + +// Recurring simple scheduled automation +const ScheduledSimpleSchema = AutomationBaseSchema.extend({ + type: z.literal("scheduled"), + schedule_mode: z.literal("recurring"), + schedule_type: z.literal("simple"), + repeat_unit: z.enum(["minutes", "hours", "days", "weeks", "months"]), + repeat_interval: z.number().int().positive().optional(), + start_time: z.string().nullable().optional(), + repeat_on_days: z.array(z.number().int().min(0).max(6)).nullable().optional(), + repeat_on_day_of_month: z.number().int().min(1).max(31).nullable().optional(), + ends_type: z.enum(["never", "on", "after"]).optional().default("never"), + ends_on_date: z.string().nullable().optional(), + ends_after_count: z.number().int().positive().nullable().optional(), +}); + +// Entity event automation +const EntityAutomationSchema = AutomationBaseSchema.extend({ + type: z.literal("entity"), + entity_name: z.string().min(1, "Entity name cannot be empty"), + event_types: z + .array(z.enum(["create", "update", "delete"])) + .min(1, "At least one event type is required"), +}); + +// Union of all automation types +export const AutomationSchema = z.union([ + ScheduledOneTimeSchema, + ScheduledCronSchema, + ScheduledSimpleSchema, + EntityAutomationSchema, +]); + export const FunctionConfigSchema = z.object({ name: FunctionNameSchema, entry: z.string().min(1, "Entry point cannot be empty"), + automations: z.array(AutomationSchema).optional(), }); -export const FunctionSchema = FunctionConfigSchema.extend({ +// Function with local file paths (used when reading from filesystem) +export const FunctionLocalSchema = FunctionConfigSchema.extend({ entryPath: z.string().min(1, "Entry path cannot be empty"), files: z.array(z.string()).min(1, "Function must have at least one file"), }); -export const FunctionDeploySchema = z.object({ +// Function with file contents (used for API payload) +export const FunctionPayloadSchema = z.object({ name: FunctionNameSchema, entry: z.string().min(1), files: z.array(FunctionFileSchema).min(1, "Function must have at least one file"), + automations: z.array(AutomationSchema).optional(), }); export const DeployFunctionsResponseSchema = z.object({ deployed: z.array(z.string()), deleted: z.array(z.string()), + skipped: z.array(z.string()), errors: z .array(z.object({ name: z.string(), message: z.string() })) .nullable(), }); +export type Automation = z.infer; export type FunctionConfig = z.infer; -export type Function = z.infer; +export type FunctionLocal = z.infer; export type FunctionFile = z.infer; -export type FunctionDeploy = z.infer; +export type FunctionPayload = z.infer; export type DeployFunctionsResponse = z.infer; -export type FunctionWithCode = Omit & { +// Alias for backward compatibility +export type Function = FunctionLocal; +export type FunctionDeploy = FunctionPayload; + +export type FunctionWithCode = Omit & { files: FunctionFile[]; }; From debd591a047b9e58135171add1acff78895ad142 Mon Sep 17 00:00:00 2001 From: Kfir Strikovsky Date: Sun, 1 Feb 2026 11:41:03 +0200 Subject: [PATCH 2/3] fix schema --- src/core/resources/function/api.ts | 1 + src/core/resources/function/schema.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/resources/function/api.ts b/src/core/resources/function/api.ts index 1f78592e..513e47af 100644 --- a/src/core/resources/function/api.ts +++ b/src/core/resources/function/api.ts @@ -8,6 +8,7 @@ function toDeployPayloadItem(fn: FunctionWithCode) { name: fn.name, entry: fn.entry, files: fn.files, + automations: fn.automations }; } diff --git a/src/core/resources/function/schema.ts b/src/core/resources/function/schema.ts index 0c6ee75b..d03652c2 100644 --- a/src/core/resources/function/schema.ts +++ b/src/core/resources/function/schema.ts @@ -92,7 +92,7 @@ export const FunctionPayloadSchema = z.object({ export const DeployFunctionsResponseSchema = z.object({ deployed: z.array(z.string()), deleted: z.array(z.string()), - skipped: z.array(z.string()), + skipped: z.array(z.string()).optional().nullable(), errors: z .array(z.object({ name: z.string(), message: z.string() })) .nullable(), From e81c4854e176fab625b4cf0fc689ccb1ab809007 Mon Sep 17 00:00:00 2001 From: Kfir Strikovsky Date: Thu, 5 Feb 2026 10:47:31 +0200 Subject: [PATCH 3/3] lint issues --- src/core/resources/function/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/resources/function/api.ts b/src/core/resources/function/api.ts index 7e8953d4..03974785 100644 --- a/src/core/resources/function/api.ts +++ b/src/core/resources/function/api.ts @@ -12,7 +12,7 @@ function toDeployPayloadItem(fn: FunctionWithCode) { name: fn.name, entry: fn.entry, files: fn.files, - automations: fn.automations + automations: fn.automations, }; }