UNPKG

@hotmeshio/hotmesh

Version:

Permanent-Memory Workflows & AI Agents

147 lines (143 loc) 5.36 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getNotificationChannelName = exports.deploySchema = void 0; async function deploySchema(streamClient, appId, logger) { const client = 'connect' in streamClient && 'release' in streamClient ? await streamClient.connect() : streamClient; const releaseClient = 'release' in streamClient; try { const lockId = getAdvisoryLockId(appId); const lockResult = await client.query('SELECT pg_try_advisory_lock($1) AS locked', [lockId]); if (lockResult.rows[0].locked) { await client.query('BEGIN'); const schemaName = appId.replace(/[^a-zA-Z0-9_]/g, '_'); const tableName = `${schemaName}.streams`; await createTables(client, schemaName, tableName); await createNotificationTriggers(client, schemaName, tableName); await client.query('COMMIT'); await client.query('SELECT pg_advisory_unlock($1)', [lockId]); } else { throw new Error('Table deployment in progress by another process.'); } } catch (error) { logger.error('Error deploying schema', { error }); throw error; } finally { if (releaseClient) { await client.release(); } } } exports.deploySchema = deploySchema; function getAdvisoryLockId(appId) { return hashStringToInt(appId); } function hashStringToInt(str) { let hash = 0; for (let i = 0; i < str.length; i++) { hash = (hash << 5) - hash + str.charCodeAt(i); hash |= 0; // Convert to 32-bit integer } return Math.abs(hash); } async function createTables(client, schemaName, tableName) { await client.query(`CREATE SCHEMA IF NOT EXISTS ${schemaName};`); // Main table creation with partitions await client.query(` CREATE TABLE IF NOT EXISTS ${tableName} ( id BIGSERIAL, stream_name TEXT NOT NULL, group_name TEXT NOT NULL DEFAULT 'ENGINE', message TEXT NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW(), reserved_at TIMESTAMPTZ, reserved_by TEXT, expired_at TIMESTAMPTZ, PRIMARY KEY (stream_name, id) ) PARTITION BY HASH (stream_name); `); for (let i = 0; i < 8; i++) { const partitionTableName = `${schemaName}.streams_part_${i}`; await client.query(` CREATE TABLE IF NOT EXISTS ${partitionTableName} PARTITION OF ${tableName} FOR VALUES WITH (modulus 8, remainder ${i}); `); } // Index for active messages await client.query(` CREATE INDEX IF NOT EXISTS idx_streams_active_messages ON ${tableName} (group_name, stream_name, reserved_at, id) WHERE reserved_at IS NULL AND expired_at IS NULL; `); // Optimized index for the simplified fetchMessages query await client.query(` CREATE INDEX IF NOT EXISTS idx_streams_message_fetch ON ${tableName} (stream_name, group_name, id) WHERE expired_at IS NULL; `); // Index for expired messages await client.query(` CREATE INDEX IF NOT EXISTS idx_streams_expired_at ON ${tableName} (expired_at); `); // New index for stream stats optimization await client.query(` CREATE INDEX IF NOT EXISTS idx_stream_name_expired_at ON ${tableName} (stream_name) WHERE expired_at IS NULL; `); // TODO: revisit this index when solving automated cleanup // Optional index for querying by creation time, if needed // await client.query(` // CREATE INDEX IF NOT EXISTS idx_streams_created_at // ON ${tableName} (created_at); // `); } async function createNotificationTriggers(client, schemaName, tableName) { // Create the notification function await client.query(` CREATE OR REPLACE FUNCTION ${schemaName}.notify_new_stream_message() RETURNS TRIGGER AS $$ DECLARE channel_name TEXT; payload JSON; BEGIN -- Create channel name: stream_{stream_name}_{group_name} -- Truncate if too long (PostgreSQL channel names limited to 63 chars) channel_name := 'stream_' || NEW.stream_name || '_' || NEW.group_name; IF length(channel_name) > 63 THEN channel_name := left(channel_name, 63); END IF; -- Create payload with message details payload := json_build_object( 'id', NEW.id, 'stream_name', NEW.stream_name, 'group_name', NEW.group_name, 'created_at', extract(epoch from NEW.created_at) ); -- Send notification PERFORM pg_notify(channel_name, payload::text); RETURN NEW; END; $$ LANGUAGE plpgsql; `); // Create trigger only on the main table - it will automatically apply to all partitions await client.query(` DROP TRIGGER IF EXISTS notify_stream_insert ON ${tableName}; CREATE TRIGGER notify_stream_insert AFTER INSERT ON ${tableName} FOR EACH ROW EXECUTE FUNCTION ${schemaName}.notify_new_stream_message(); `); } function getNotificationChannelName(streamName, groupName) { const channelName = `stream_${streamName}_${groupName}`; // PostgreSQL channel names are limited to 63 characters return channelName.length > 63 ? channelName.substring(0, 63) : channelName; } exports.getNotificationChannelName = getNotificationChannelName;