UNPKG

@hotmeshio/hotmesh

Version:

Permanent-Memory Workflows & AI Agents

164 lines (141 loc) 5.32 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getTimeNotifySql = void 0; /** * Time-aware notification system for PostgreSQL * This system minimizes polling by using LISTEN/NOTIFY for time-based task awakening * * Exported as a function that returns the SQL with schema placeholder replaced. */ function getTimeNotifySql(schema) { return ` -- Time-aware notification system for PostgreSQL -- This system minimizes polling by using LISTEN/NOTIFY for time-based task awakening -- Function to calculate the next awakening time from the sorted set CREATE OR REPLACE FUNCTION ${schema}.get_next_awakening_time(app_key TEXT) RETURNS TIMESTAMP WITH TIME ZONE AS $$ DECLARE next_score DOUBLE PRECISION; next_time TIMESTAMP WITH TIME ZONE; BEGIN -- Get the earliest (lowest score) entry from the time range ZSET SELECT score INTO next_score FROM ${schema}.task_schedules WHERE key = app_key AND (expiry IS NULL OR expiry > NOW()) ORDER BY score ASC LIMIT 1; IF next_score IS NULL THEN RETURN NULL; END IF; -- Convert epoch milliseconds to timestamp next_time := to_timestamp(next_score / 1000.0); -- Only return if it's in the future IF next_time > NOW() THEN RETURN next_time; END IF; RETURN NULL; END; $$ LANGUAGE plpgsql; -- Function to schedule a notification for the next awakening time CREATE OR REPLACE FUNCTION ${schema}.schedule_time_notification( app_id TEXT, new_awakening_time TIMESTAMP WITH TIME ZONE DEFAULT NULL ) RETURNS VOID AS $$ DECLARE channel_name TEXT; current_next_time TIMESTAMP WITH TIME ZONE; app_key TEXT; BEGIN -- Build the time range key for this app app_key := app_id || ':time_range'; channel_name := 'time_hooks_' || app_id; -- Get the current next awakening time current_next_time := ${schema}.get_next_awakening_time(app_key); -- If we have a specific new awakening time, check if it's earlier IF new_awakening_time IS NOT NULL THEN IF current_next_time IS NULL OR new_awakening_time < current_next_time THEN current_next_time := new_awakening_time; END IF; END IF; -- If there's a next awakening time, schedule immediate notification -- The application will handle the timing logic IF current_next_time IS NOT NULL THEN PERFORM pg_notify(channel_name, json_build_object( 'type', 'time_schedule_updated', 'app_id', app_id, 'next_awakening', extract(epoch from current_next_time) * 1000, 'updated_at', extract(epoch from NOW()) * 1000 )::text); END IF; END; $$ LANGUAGE plpgsql; -- Function to notify when time hooks are ready CREATE OR REPLACE FUNCTION ${schema}.notify_time_hooks_ready(app_id TEXT) RETURNS VOID AS $$ DECLARE channel_name TEXT; BEGIN channel_name := 'time_hooks_' || app_id; PERFORM pg_notify(channel_name, json_build_object( 'type', 'time_hooks_ready', 'app_id', app_id, 'ready_at', extract(epoch from NOW()) * 1000 )::text); END; $$ LANGUAGE plpgsql; -- Trigger function for when time hooks are added/updated CREATE OR REPLACE FUNCTION ${schema}.on_time_hook_change() RETURNS TRIGGER AS $$ DECLARE app_id_extracted TEXT; awakening_time TIMESTAMP WITH TIME ZONE; BEGIN -- Extract app_id from the key (assumes format: app_id:time_range) app_id_extracted := split_part(NEW.key, ':time_range', 1); -- Convert the score (epoch milliseconds) to timestamp awakening_time := to_timestamp(NEW.score / 1000.0); -- Schedule notification for this new awakening time PERFORM ${schema}.schedule_time_notification(app_id_extracted, awakening_time); RETURN NEW; END; $$ LANGUAGE plpgsql; -- Trigger function for when time hooks are removed CREATE OR REPLACE FUNCTION ${schema}.on_time_hook_remove() RETURNS TRIGGER AS $$ DECLARE app_id_extracted TEXT; BEGIN -- Extract app_id from the key app_id_extracted := split_part(OLD.key, ':time_range', 1); -- Recalculate and notify about the schedule update PERFORM ${schema}.schedule_time_notification(app_id_extracted); RETURN OLD; END; $$ LANGUAGE plpgsql; -- Create triggers on the sorted_set table for time hooks -- Note: These will be created per app schema -- Drop existing triggers first to avoid conflicts DROP TRIGGER IF EXISTS trg_time_hook_insert ON ${schema}.task_schedules; DROP TRIGGER IF EXISTS trg_time_hook_update ON ${schema}.task_schedules; DROP TRIGGER IF EXISTS trg_time_hook_delete ON ${schema}.task_schedules; -- Create new triggers CREATE TRIGGER trg_time_hook_insert AFTER INSERT ON ${schema}.task_schedules FOR EACH ROW WHEN (NEW.key LIKE '%:time_range') EXECUTE FUNCTION ${schema}.on_time_hook_change(); CREATE TRIGGER trg_time_hook_update AFTER UPDATE ON ${schema}.task_schedules FOR EACH ROW WHEN (NEW.key LIKE '%:time_range') EXECUTE FUNCTION ${schema}.on_time_hook_change(); CREATE TRIGGER trg_time_hook_delete AFTER DELETE ON ${schema}.task_schedules FOR EACH ROW WHEN (OLD.key LIKE '%:time_range') EXECUTE FUNCTION ${schema}.on_time_hook_remove(); `; } exports.getTimeNotifySql = getTimeNotifySql;