permamind
Version:
An MCP server that provides an immortal memory layer for AI agents and clients
176 lines (175 loc) • 5.57 kB
JavaScript
/**
* VIP-01 Result Processing Utilities
*
* Client-side processing functions to ensure VIP-01 compliance
* including sorting, limiting, and metadata generation.
*/
/**
* Apply additional client-side filtering for complex queries
* This is a fallback when server-side filtering is insufficient
*/
export function applyClientSideFiltering(events, filter) {
let filtered = [...events];
// Apply since filter (client-side fallback)
if (filter.since) {
filtered = filtered.filter((event) => {
const timestamp = extractTimestamp(event);
return timestamp > filter.since;
});
}
// Apply until filter (client-side fallback)
if (filter.until) {
filtered = filtered.filter((event) => {
const timestamp = extractTimestamp(event);
return timestamp < filter.until;
});
}
// Apply text search (client-side fallback)
if (filter.search) {
const searchText = filter.search.toLowerCase();
filtered = filtered.filter((event) => {
if (event && typeof event === "object") {
const eventObj = event;
const content = eventObj.Content || eventObj.content;
if (typeof content === "string") {
return content.toLowerCase().includes(searchText);
}
}
return false;
});
}
return filtered;
}
/**
* Extract timestamp from an event object
*/
export function extractTimestamp(event) {
if (!event || typeof event !== "object") {
return 0;
}
const timestampedEvent = event;
if (!timestampedEvent.Timestamp) {
return 0;
}
const timestamp = timestampedEvent.Timestamp;
if (typeof timestamp === "number") {
return timestamp;
}
if (typeof timestamp === "string") {
const parsed = parseInt(timestamp, 10);
return isNaN(parsed) ? 0 : parsed;
}
return 0;
}
/**
* Extract all timestamps from events array
*/
export function extractTimestamps(events) {
return events.map(extractTimestamp).filter((timestamp) => timestamp > 0);
}
/**
* Generate VIP-01 filter statistics for debugging/monitoring
*/
export function generateFilterStats(filter, originalCount, finalCount) {
const parameters = [];
let filterType = "complex";
let optimizationHint = "full_scan";
// Analyze filter parameters
if (filter.ids) {
parameters.push("ids");
filterType = "direct_lookup";
optimizationHint = "index_by_id";
}
if (filter.authors && filter.authors.length === 1) {
parameters.push("single_author");
filterType = "author_lookup";
optimizationHint = "index_by_author";
}
else if (filter.authors && filter.authors.length > 1) {
parameters.push("multi_author");
}
if (filter.kinds && filter.kinds.length === 1) {
parameters.push("single_kind");
if (filterType === "complex") {
filterType = "kind_lookup";
optimizationHint = "index_by_kind";
}
}
else if (filter.kinds && filter.kinds.length > 1) {
parameters.push("multi_kind");
}
if (filter.tags) {
parameters.push("tags");
}
if (filter.search) {
parameters.push("search");
filterType = "text_search";
}
if (filter.since || filter.until) {
parameters.push("time_range");
}
const efficiency = originalCount > 0 ? (originalCount - finalCount) / originalCount : 0;
return {
efficiency: Math.round(efficiency * 100) / 100,
filterType,
optimizationHint,
parameters,
};
}
/**
* Validate if events are properly sorted (newest first)
*/
export function isProperlysorted(events) {
const timestamps = extractTimestamps(events);
for (let i = 1; i < timestamps.length; i++) {
if (timestamps[i - 1] < timestamps[i]) {
return false; // Found an older event before a newer one
}
}
return true;
}
/**
* Process raw events according to VIP-01 specification
*/
export function processVIP01Results(events, filter, options = {}) {
const { enableClientLimiting = true, enableSorting = true, includeMetadata = false, } = options;
let processedEvents = [...events];
const originalCount = events.length;
// Sort by timestamp (newest first) as per VIP-01 specification
if (enableSorting) {
processedEvents = sortByTimestamp(processedEvents);
}
// Apply client-side limiting as fallback
let hasMore = false;
if (enableClientLimiting &&
filter.limit &&
processedEvents.length > filter.limit) {
processedEvents = processedEvents.slice(0, filter.limit);
hasMore = true;
}
const result = {
events: processedEvents,
};
// Add metadata if requested
if (includeMetadata) {
result.totalCount = originalCount;
result.hasMore = hasMore;
const timestamps = extractTimestamps(processedEvents);
if (timestamps.length > 0) {
result.newestTimestamp = Math.max(...timestamps);
result.oldestTimestamp = Math.min(...timestamps);
}
}
return result;
}
/**
* Sort events by timestamp (newest first)
*/
export function sortByTimestamp(events) {
return events.sort((a, b) => {
const timestampA = extractTimestamp(a);
const timestampB = extractTimestamp(b);
// Sort newest first (descending order)
return timestampB - timestampA;
});
}