@hotmeshio/hotmesh
Version:
Permanent-Memory Workflows & AI Agents
342 lines (341 loc) • 10.5 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.arrayToHash = exports.isStreamMessage = exports.parseStreamMessage = exports.s = exports.isValidCron = exports.restoreHierarchy = exports.getValueByPath = exports.getIndexedHash = exports.getSymVal = exports.getSymKey = exports.formatISODate = exports.getTimeSeries = exports.getSubscriptionTopic = exports.findSubscriptionForTrigger = exports.findTopKey = exports.matchesStatus = exports.matchesStatusCode = exports.polyfill = exports.identifyProvider = exports.XSleepFor = exports.sleepImmediate = exports.sleepFor = exports.guid = exports.deterministicRandom = exports.deepCopy = exports.getSystemHealth = exports.hashOptions = void 0;
const os_1 = __importDefault(require("os"));
const crypto_1 = require("crypto");
const nanoid_1 = require("nanoid");
const ms_1 = __importDefault(require("ms"));
const logger_1 = require("../services/logger");
const enums_1 = require("./enums");
const logger = new logger_1.LoggerService('hotmesh', 'utils');
/**
* @private
*/
const hashOptions = (options) => {
const str = JSON.stringify(options);
return (0, crypto_1.createHash)('sha256').update(str).digest('hex');
};
exports.hashOptions = hashOptions;
async function getSystemHealth() {
const totalMemory = os_1.default.totalmem();
const freeMemory = os_1.default.freemem();
const usedMemory = totalMemory - freeMemory;
const systemHealth = {
TotalMemoryGB: `${(totalMemory / 1024 / 1024 / 1024).toFixed(2)} GB`,
FreeMemoryGB: `${(freeMemory / 1024 / 1024 / 1024).toFixed(2)} GB`,
UsedMemoryGB: `${(usedMemory / 1024 / 1024 / 1024).toFixed(2)} GB`,
CPULoad: [],
NetworkStats: [],
};
return systemHealth;
}
exports.getSystemHealth = getSystemHealth;
function deepCopy(obj) {
return JSON.parse(JSON.stringify(obj));
}
exports.deepCopy = deepCopy;
function deterministicRandom(seed) {
const x = Math.sin(seed) * 10000;
return x - Math.floor(x);
}
exports.deterministicRandom = deterministicRandom;
function guid(size = enums_1.HMSH_GUID_SIZE) {
return `H` + (0, nanoid_1.nanoid)(size);
}
exports.guid = guid;
async function sleepFor(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
exports.sleepFor = sleepFor;
function sleepImmediate() {
return new Promise((resolve) => setImmediate(resolve));
}
exports.sleepImmediate = sleepImmediate;
function XSleepFor(ms) {
//can be interrupted with `clearTimeout`
let timerId;
const promise = new Promise((resolve) => {
timerId = setTimeout(resolve, ms);
});
return { promise, timerId };
}
exports.XSleepFor = XSleepFor;
/**
* Identies the provider type based on the provider object. Customers may
* explicitly set the provider type in the configuration. But this is a
* convenience method to automatically identify the provider type.
* @private
*/
function identifyProvider(provider) {
const prototype = Object.getPrototypeOf(provider);
if (provider.Query?.prototype ||
Object.keys(provider).includes('database') ||
prototype.name === 'Pool') {
return 'postgres';
}
else if (provider.toString().toLowerCase().includes('nats')) {
return 'nats';
}
let type = null;
if (Object.keys(provider).includes('connection') ||
!isNaN(provider.totalCount) && !isNaN(provider.idleCount)) {
type = 'postgres';
}
else if (prototype.constructor.toString().includes('NatsConnectionImpl')) {
type = 'nats';
}
return type;
}
exports.identifyProvider = identifyProvider;
/**
* @private
*/
exports.polyfill = {
/**
* `connection` is the generic replacement
*/
providerConfig(obj) {
return obj?.connection ?? obj?.connections;
},
};
/**
* @private
*/
function matchesStatusCode(code, pattern) {
if (typeof pattern === 'string') {
// Convert '*' wildcard to its regex equivalent (\d)
const regexPattern = `^${pattern.replace(/\*/g, '\\d')}$`;
return new RegExp(regexPattern).test(code.toString());
}
return pattern.test(code.toString());
}
exports.matchesStatusCode = matchesStatusCode;
/**
* @private
*/
function matchesStatus(status, targetStatus) {
return status === targetStatus;
}
exports.matchesStatus = matchesStatus;
/**
* @private
*/
function findTopKey(obj, input) {
for (const [key, value] of Object.entries(obj)) {
if (value.hasOwnProperty(input)) {
const parentKey = findTopKey(obj, key.replace(/^\./, ''));
return (parentKey || key).replace(/^\./, '');
}
}
return null;
}
exports.findTopKey = findTopKey;
/**
* @private
*/
function findSubscriptionForTrigger(obj, value) {
for (const [key, itemValue] of Object.entries(obj)) {
if (itemValue === value) {
return key;
}
}
return null;
}
exports.findSubscriptionForTrigger = findSubscriptionForTrigger;
/**
* Get the subscription topic for the flow to which activityId belongs.
* @private
*/
async function getSubscriptionTopic(activityId, store, appVID) {
const appTransitions = await store.getTransitions(appVID);
const appSubscriptions = await store.getSubscriptions(appVID);
const triggerId = findTopKey(appTransitions, activityId);
const topic = findSubscriptionForTrigger(appSubscriptions, triggerId);
return topic;
}
exports.getSubscriptionTopic = getSubscriptionTopic;
/**
* returns the 12-digit format of the iso timestamp (e.g, 202101010000); returns
* an empty string if overridden by the user to not segment by time (infinity).
* @private
*/
function getTimeSeries(granularity) {
if (granularity.toString() === 'infinity') {
return '0';
}
const now = new Date();
const granularityUnit = granularity.slice(-1);
const granularityValue = parseInt(granularity.slice(0, -1), 10);
if (granularityUnit === 'm') {
const minute = Math.floor(now.getMinutes() / granularityValue) * granularityValue;
now.setUTCMinutes(minute, 0, 0);
}
else if (granularityUnit === 'h') {
now.setUTCMinutes(0, 0, 0);
}
return now
.toISOString()
.replace(/:\d\d\..+|-|T/g, '')
.replace(':', '');
}
exports.getTimeSeries = getTimeSeries;
/**
* @private
*/
function formatISODate(input) {
const date = input instanceof Date ? input : new Date(input);
return date.toISOString().replace(/[:TZ-]/g, '');
}
exports.formatISODate = formatISODate;
/**
* @private
*/
function getSymKey(number) {
const alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
const base = alphabet.length;
if (number < 0 || number >= Math.pow(base, 3)) {
throw new Error('Number out of range');
}
const [q1, r1] = divmod(number, base);
const [q2, r2] = divmod(q1, base);
return alphabet[q2] + alphabet[r1] + alphabet[r2];
}
exports.getSymKey = getSymKey;
/**
* @private
*/
function getSymVal(number) {
const alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
const base = alphabet.length;
if (number < 0 || number >= Math.pow(base, 2)) {
throw new Error('Number out of range');
}
const [q, r] = divmod(number, base);
return alphabet[q] + alphabet[r];
}
exports.getSymVal = getSymVal;
/**
* @private
*/
function divmod(m, n) {
return [Math.floor(m / n), m % n];
}
/**
* @private
*/
function getIndexedHash(hash, target) {
const index = hash[target] || 0;
const newHash = { ...hash };
delete newHash[target];
return [index, newHash];
}
exports.getIndexedHash = getIndexedHash;
/**
* @private
*/
function getValueByPath(obj, path) {
const pathParts = path.split('/');
let currentValue = obj;
for (const part of pathParts) {
if (currentValue[part] !== undefined) {
currentValue = currentValue[part];
}
else {
return undefined;
}
}
return currentValue;
}
exports.getValueByPath = getValueByPath;
/**
* @private
*/
function restoreHierarchy(obj) {
const result = {};
for (const key in obj) {
if (obj[key] === undefined)
continue;
const keys = key.split('/');
let current = result;
for (let i = 0; i < keys.length; i++) {
if (i === keys.length - 1) {
current[keys[i]] = obj[key];
}
else {
current[keys[i]] = current[keys[i]] || {};
current = current[keys[i]];
}
}
}
return result;
}
exports.restoreHierarchy = restoreHierarchy;
/**
* @private
*/
function isValidCron(cronExpression) {
const cronRegex = /^(\*|([0-5]?\d)) (\*|([01]?\d|2[0-3])) (\*|([12]?\d|3[01])) (\*|([1-9]|1[0-2])) (\*|([0-6](?:-[0-6])?(?:,[0-6])?))$/;
return cronRegex.test(cronExpression);
}
exports.isValidCron = isValidCron;
/**
* Returns the number of seconds for a string using the milliseconds format
* used by the `ms` npm package as the input.
*/
const s = (input) => {
return (0, ms_1.default)(input) / 1000;
};
exports.s = s;
/**
* @private
*/
const parseStreamMessage = (message) => {
try {
return JSON.parse(message);
}
catch (error) {
logger.error('Error parsing Stream message', { error });
throw error;
}
};
exports.parseStreamMessage = parseStreamMessage;
/**
* @private
*/
const isStreamMessage = (result) => {
return Array.isArray(result) && Array.isArray(result[0]);
};
exports.isStreamMessage = isStreamMessage;
/**
* Transforms an array of arrays to an array of objects.
*/
const arrayToHash = (response) => {
const results = [];
let key;
for (let i = 1; i < response.length; i++) {
// ignore count
const row = response[i];
const result = {};
if (Array.isArray(row)) {
// Check if row is an array
for (let j = 0; j < row.length; j += 2) {
const key = row[j];
const value = row[j + 1];
result[key] = value;
}
if (key) {
result.$ = key;
}
results.push(result);
key = undefined;
}
else {
key = row;
}
}
return results;
};
exports.arrayToHash = arrayToHash;