@hotmeshio/hotmesh
Version:
Permanent-Memory Workflows & AI Agents
226 lines (225 loc) • 7.83 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.NatsStreamService = void 0;
const index_1 = require("../../index");
const key_1 = require("../../../../modules/key");
const enums_1 = require("../../../../modules/enums");
const utils_1 = require("../../../../modules/utils");
class NatsStreamService extends index_1.StreamService {
constructor(streamClient, storeClient, config = {}) {
super(streamClient, storeClient, config);
this.jetstream = streamClient.jetstream();
}
async init(namespace, appId, logger) {
this.namespace = namespace;
this.logger = logger;
this.appId = appId;
this.jsm = await this.jetstream.jetstreamManager();
}
mintKey(type, params) {
if (!this.namespace)
throw new Error('namespace not set');
return key_1.KeyService.mintKey(this.namespace, type, {
...params,
appId: this.appId,
});
}
transact() {
return {};
}
async createStream(streamName) {
try {
const config = {
name: streamName,
subjects: [`${streamName}.*`],
retention: 'workqueue',
storage: 'memory',
num_replicas: 1,
};
await this.jsm.streams.add(config);
return true;
}
catch (error) {
this.logger.error(`Error creating stream ${streamName}`, { error });
throw error;
}
}
async deleteStream(streamName) {
try {
await this.jsm.streams.delete(streamName);
return true;
}
catch (error) {
this.logger.error(`Error deleting stream ${streamName}`, { error });
throw error;
}
}
async createConsumerGroup(streamName, groupName) {
try {
const consumerConfig = {
durable_name: groupName,
deliver_group: groupName,
ack_policy: 'explicit',
ack_wait: 30 * 1000,
max_deliver: 10,
};
await this.jsm.consumers.add(streamName, consumerConfig);
return true;
}
catch (error) {
this.logger.error(`Error creating consumer group ${groupName} for stream ${streamName}`, { error });
throw error;
}
}
async deleteConsumerGroup(streamName, groupName) {
try {
await this.jsm.consumers.delete(streamName, groupName);
return true;
}
catch (error) {
this.logger.error(`Error deleting consumer group ${groupName} for stream ${streamName}`, { error });
throw error;
}
}
async publishMessages(streamName, messages, options) {
try {
const publishPromises = messages.map(async (message) => {
const subject = `${streamName}.message`;
const ack = await this.jetstream.publish(subject, Buffer.from(message));
return ack;
});
const acks = await Promise.all(publishPromises);
return acks.map((ack) => ack.seq.toString());
}
catch (error) {
this.logger.error(`Error publishing messages to ${streamName}`, {
error,
});
throw error;
}
}
async consumeMessages(streamName, groupName, consumerName, options) {
try {
const consumer = await this.jetstream.consumers.get(streamName, groupName);
const messages = [];
const fetchOptions = {
max_messages: options?.batchSize || 1,
expires: options?.blockTimeout || enums_1.HMSH_BLOCK_TIME_MS,
};
const fetchedMessages = await consumer.fetch(fetchOptions);
for await (const msg of fetchedMessages) {
messages.push({
id: msg.seq.toString(),
data: (0, utils_1.parseStreamMessage)(msg.string()),
});
}
return messages;
}
catch (error) {
this.logger.error(`Error consuming messages from ${streamName}`, {
error,
});
throw error;
}
}
async ackAndDelete(streamName, groupName, messageIds) {
try {
await this.acknowledgeMessages(streamName, groupName, messageIds);
return messageIds.length;
}
catch (error) {
this.logger.error(`Error in ack and delete for stream ${streamName}`, {
error,
});
throw error;
}
}
async acknowledgeMessages(streamName, groupName, messageIds, options) {
//no-op
return messageIds.length;
}
async deleteMessages(streamName, groupName, messageIds, options) {
try {
await Promise.all(messageIds.map((id) => this.jsm.streams.deleteMessage(streamName, parseInt(id))));
return messageIds.length;
}
catch (error) {
this.logger.error(`Error deleting messages from ${streamName}`, {
error,
});
throw error;
}
}
async retryMessages(streamName, groupName, options) {
return [];
}
async getStreamStats(streamName) {
try {
const info = await this.jsm.streams.info(streamName);
return {
messageCount: info.state.messages,
};
}
catch (error) {
this.logger.error(`Error getting stats for ${streamName}`, { error });
throw error;
}
}
async getStreamDepth(streamName) {
try {
const info = await this.jsm.streams.info(streamName);
return info.state.messages;
}
catch (error) {
this.logger.error(`Error getting depth for ${streamName}`, { error });
throw error;
}
}
async getStreamDepths(streamNames) {
try {
const results = await Promise.all(streamNames.map(async ({ stream }) => ({
stream,
depth: await this.getStreamDepth(stream),
})));
return results;
}
catch (error) {
this.logger.error('Error getting multiple stream depths', { error });
throw error;
}
}
async trimStream(streamName, options) {
try {
// Retrieve the current stream info
const streamInfo = await this.jsm.streams.info(streamName);
const config = { ...streamInfo.config }; // Clone to avoid direct mutation
// Apply new limits based on options
if (options.maxLen !== undefined) {
config.max_msgs = options.maxLen;
}
if (options.maxAge !== undefined) {
config.max_age = options.maxAge * 1e9; // Convert maxAge to nanoseconds
}
// Update the stream with the modified config
await this.jsm.streams.update(streamName, config);
return 0; // Trimming is applied automatically based on updated config
}
catch (error) {
this.logger.error(`Error trimming stream ${streamName}`, { error });
throw error;
}
}
getProviderSpecificFeatures() {
return {
supportsBatching: true,
supportsDeadLetterQueue: true,
supportsOrdering: true,
supportsTrimming: true,
supportsRetry: false,
supportsNotifications: false,
maxMessageSize: 1024 * 1024,
maxBatchSize: 256,
};
}
}
exports.NatsStreamService = NatsStreamService;