anon-identity
Version:
Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure
448 lines • 16.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ActivityIndex = void 0;
const types_1 = require("./types");
class ActivityIndex {
constructor() {
this.index = new Map();
this.agentIndex = new Map(); // agentDID -> activity IDs
this.serviceIndex = new Map(); // serviceDID -> activity IDs
this.typeIndex = new Map(); // type -> activity IDs
this.statusIndex = new Map(); // status -> activity IDs
this.dateIndex = new Map(); // YYYY-MM-DD -> activity IDs
this.scopeIndex = new Map(); // scope -> activity IDs
}
/**
* Index a single activity for fast searching
*/
async indexActivity(activity) {
const entry = {
id: activity.id,
agentDID: activity.agentDID,
parentDID: activity.parentDID,
timestamp: activity.timestamp,
type: activity.type,
serviceDID: activity.serviceDID,
status: activity.status,
scopes: activity.scopes,
sessionId: activity.sessionId,
ipfsHash: activity.ipfsHash,
year: activity.timestamp.getFullYear(),
month: activity.timestamp.getMonth() + 1,
day: activity.timestamp.getDate(),
hour: activity.timestamp.getHours()
};
// Store in main index
this.index.set(activity.id, entry);
// Update secondary indexes
this.addToIndex(this.agentIndex, activity.agentDID, activity.id);
this.addToIndex(this.serviceIndex, activity.serviceDID, activity.id);
this.addToIndex(this.typeIndex, activity.type, activity.id);
this.addToIndex(this.statusIndex, activity.status, activity.id);
// Date index (YYYY-MM-DD format)
const dateKey = `${entry.year}-${entry.month.toString().padStart(2, '0')}-${entry.day.toString().padStart(2, '0')}`;
this.addToIndex(this.dateIndex, dateKey, activity.id);
// Scope index
activity.scopes.forEach(scope => {
this.addToIndex(this.scopeIndex, scope, activity.id);
});
}
/**
* Index multiple activities in batch
*/
async indexActivities(activities) {
for (const activity of activities) {
await this.indexActivity(activity);
}
}
/**
* Search activities based on query parameters
*/
async search(query) {
let candidateIds = new Set();
let isFirstFilter = true;
// Apply filters to get candidate activity IDs
if (query.agentDID) {
const agentIds = this.agentIndex.get(query.agentDID) || new Set();
candidateIds = this.intersectSets(candidateIds, agentIds, isFirstFilter);
isFirstFilter = false;
}
if (query.parentDID) {
const parentIds = new Set();
for (const [id, entry] of this.index.entries()) {
if (entry.parentDID === query.parentDID) {
parentIds.add(id);
}
}
candidateIds = this.intersectSets(candidateIds, parentIds, isFirstFilter);
isFirstFilter = false;
}
if (query.serviceDID) {
const serviceIds = this.serviceIndex.get(query.serviceDID) || new Set();
candidateIds = this.intersectSets(candidateIds, serviceIds, isFirstFilter);
isFirstFilter = false;
}
if (query.types && query.types.length > 0) {
const typeIds = new Set();
query.types.forEach(type => {
const ids = this.typeIndex.get(type) || new Set();
ids.forEach(id => typeIds.add(id));
});
candidateIds = this.intersectSets(candidateIds, typeIds, isFirstFilter);
isFirstFilter = false;
}
if (query.status && query.status.length > 0) {
const statusIds = new Set();
query.status.forEach(status => {
const ids = this.statusIndex.get(status) || new Set();
ids.forEach(id => statusIds.add(id));
});
candidateIds = this.intersectSets(candidateIds, statusIds, isFirstFilter);
isFirstFilter = false;
}
if (query.scopes && query.scopes.length > 0) {
const scopeIds = new Set();
query.scopes.forEach(scope => {
const ids = this.scopeIndex.get(scope) || new Set();
ids.forEach(id => scopeIds.add(id));
});
candidateIds = this.intersectSets(candidateIds, scopeIds, isFirstFilter);
isFirstFilter = false;
}
if (query.sessionId) {
const sessionIds = new Set();
for (const [id, entry] of this.index.entries()) {
if (entry.sessionId === query.sessionId) {
sessionIds.add(id);
}
}
candidateIds = this.intersectSets(candidateIds, sessionIds, isFirstFilter);
isFirstFilter = false;
}
// If no filters applied, get all activities
if (isFirstFilter) {
candidateIds = new Set(this.index.keys());
}
// Apply date range filter
if (query.dateRange) {
candidateIds = this.filterByDateRange(candidateIds, query.dateRange.start, query.dateRange.end);
}
// Convert to activities and sort
const activities = this.getActivitiesFromIds(candidateIds);
const sortedActivities = this.sortActivities(activities, query.sortBy, query.sortOrder);
// Apply pagination
const offset = query.offset || 0;
const limit = query.limit || 100;
const total = sortedActivities.length;
const paginatedActivities = sortedActivities.slice(offset, offset + limit);
return {
activities: paginatedActivities,
total,
offset,
limit,
hasMore: offset + limit < total
};
}
/**
* Get activity summary for an agent
*/
async getActivitySummary(agentDID, period, startDate) {
const agentIds = this.agentIndex.get(agentDID) || new Set();
const activities = this.getActivitiesFromIds(agentIds);
if (activities.length === 0) {
throw new Error(`No activities found for agent: ${agentDID}`);
}
// Determine period boundaries
const now = new Date();
const periodStart = startDate || this.getPeriodStart(now, period);
const periodEnd = this.getPeriodEnd(periodStart, period);
// Filter activities by period
const periodActivities = activities.filter(activity => activity.timestamp >= periodStart && activity.timestamp <= periodEnd);
// Calculate statistics
const byType = {};
const byStatus = {};
const byService = {};
const scopeUsage = {};
let totalDuration = 0;
let durationsCount = 0;
let errorCount = 0;
periodActivities.forEach(activity => {
// Count by type
byType[activity.type] = (byType[activity.type] || 0) + 1;
// Count by status
byStatus[activity.status] = (byStatus[activity.status] || 0) + 1;
// Count by service
const serviceEntry = this.index.get(activity.id);
if (serviceEntry) {
byService[serviceEntry.serviceDID] = (byService[serviceEntry.serviceDID] || 0) + 1;
}
// Count scope usage
activity.scopes.forEach(scope => {
scopeUsage[scope] = (scopeUsage[scope] || 0) + 1;
});
// Calculate duration stats
if (activity.duration) {
totalDuration += activity.duration;
durationsCount++;
}
// Count errors
if (activity.status === types_1.ActivityStatus.FAILED || activity.status === types_1.ActivityStatus.DENIED) {
errorCount++;
}
});
// Find peak hour
const hourCounts = {};
periodActivities.forEach(activity => {
const hour = activity.timestamp.getHours();
hourCounts[hour] = (hourCounts[hour] || 0) + 1;
});
const peakHour = Object.entries(hourCounts)
.sort(([, a], [, b]) => b - a)[0]?.[0];
// Find most used service and scope
const mostUsedService = Object.entries(byService)
.sort(([, a], [, b]) => b - a)[0]?.[0];
const mostUsedScope = Object.entries(scopeUsage)
.sort(([, a], [, b]) => b - a)[0]?.[0];
const parentDID = activities[0]?.parentDID || '';
return {
agentDID,
parentDID,
period: {
start: periodStart,
end: periodEnd,
type: period
},
totalActivities: periodActivities.length,
byType,
byStatus,
byService,
scopeUsage,
averageDuration: durationsCount > 0 ? totalDuration / durationsCount : 0,
errorRate: periodActivities.length > 0 ? errorCount / periodActivities.length : 0,
peakHour: peakHour ? `${peakHour}:00` : undefined,
mostUsedService,
mostUsedScope
};
}
/**
* Get activities by multiple agent DIDs
*/
async getActivitiesByAgents(agentDIDs) {
const allIds = new Set();
agentDIDs.forEach(agentDID => {
const agentIds = this.agentIndex.get(agentDID) || new Set();
agentIds.forEach(id => allIds.add(id));
});
return this.getActivitiesFromIds(allIds);
}
/**
* Get activities by date range
*/
async getActivitiesByDateRange(start, end) {
const allIds = new Set(this.index.keys());
const filteredIds = this.filterByDateRange(allIds, start, end);
return this.getActivitiesFromIds(filteredIds);
}
/**
* Get index statistics
*/
getStats() {
const activities = Array.from(this.index.values());
if (activities.length === 0) {
return {
totalActivities: 0,
agentCount: 0,
serviceCount: 0,
dateRange: {
earliest: new Date(),
latest: new Date()
},
byType: {},
byStatus: {}
};
}
const agents = new Set(activities.map(a => a.agentDID));
const services = new Set(activities.map(a => a.serviceDID));
const timestamps = activities.map(a => a.timestamp);
const earliest = new Date(Math.min(...timestamps.map(t => t.getTime())));
const latest = new Date(Math.max(...timestamps.map(t => t.getTime())));
const byType = {};
const byStatus = {};
activities.forEach(activity => {
byType[activity.type] = (byType[activity.type] || 0) + 1;
byStatus[activity.status] = (byStatus[activity.status] || 0) + 1;
});
return {
totalActivities: activities.length,
agentCount: agents.size,
serviceCount: services.size,
dateRange: { earliest, latest },
byType,
byStatus
};
}
/**
* Remove activity from index
*/
async removeActivity(activityId) {
const entry = this.index.get(activityId);
if (!entry) {
return false;
}
// Remove from main index
this.index.delete(activityId);
// Remove from secondary indexes
this.removeFromIndex(this.agentIndex, entry.agentDID, activityId);
this.removeFromIndex(this.serviceIndex, entry.serviceDID, activityId);
this.removeFromIndex(this.typeIndex, entry.type, activityId);
this.removeFromIndex(this.statusIndex, entry.status, activityId);
// Remove from date index
const dateKey = `${entry.year}-${entry.month.toString().padStart(2, '0')}-${entry.day.toString().padStart(2, '0')}`;
this.removeFromIndex(this.dateIndex, dateKey, activityId);
// Remove from scope indexes
entry.scopes.forEach(scope => {
this.removeFromIndex(this.scopeIndex, scope, activityId);
});
return true;
}
/**
* Clear all indexed data
*/
clear() {
this.index.clear();
this.agentIndex.clear();
this.serviceIndex.clear();
this.typeIndex.clear();
this.statusIndex.clear();
this.dateIndex.clear();
this.scopeIndex.clear();
}
// Helper methods
addToIndex(index, key, activityId) {
if (!index.has(key)) {
index.set(key, new Set());
}
index.get(key).add(activityId);
}
removeFromIndex(index, key, activityId) {
const set = index.get(key);
if (set) {
set.delete(activityId);
if (set.size === 0) {
index.delete(key);
}
}
}
intersectSets(set1, set2, isFirst) {
if (isFirst) {
return new Set(set2);
}
const result = new Set();
for (const item of set1) {
if (set2.has(item)) {
result.add(item);
}
}
return result;
}
filterByDateRange(ids, start, end) {
const result = new Set();
for (const id of ids) {
const entry = this.index.get(id);
if (entry && entry.timestamp >= start && entry.timestamp <= end) {
result.add(id);
}
}
return result;
}
getActivitiesFromIds(ids) {
const activities = [];
for (const id of ids) {
const entry = this.index.get(id);
if (entry) {
// Convert IndexEntry back to AgentActivity
const activity = {
id: entry.id,
agentDID: entry.agentDID,
parentDID: entry.parentDID,
timestamp: entry.timestamp,
type: entry.type,
serviceDID: entry.serviceDID,
status: entry.status,
scopes: entry.scopes,
details: {}, // Details not stored in index
sessionId: entry.sessionId,
ipfsHash: entry.ipfsHash
};
activities.push(activity);
}
}
return activities;
}
sortActivities(activities, sortBy, sortOrder) {
const order = sortOrder || 'desc';
const field = sortBy || 'timestamp';
return activities.sort((a, b) => {
let comparison = 0;
switch (field) {
case 'timestamp':
comparison = a.timestamp.getTime() - b.timestamp.getTime();
break;
case 'type':
comparison = a.type.localeCompare(b.type);
break;
case 'service':
comparison = a.serviceDID.localeCompare(b.serviceDID);
break;
}
return order === 'asc' ? comparison : -comparison;
});
}
getPeriodStart(date, period) {
const start = new Date(date);
switch (period) {
case 'hour':
start.setMinutes(0, 0, 0);
break;
case 'day':
start.setHours(0, 0, 0, 0);
break;
case 'week':
start.setHours(0, 0, 0, 0);
start.setDate(start.getDate() - start.getDay());
break;
case 'month':
start.setDate(1);
start.setHours(0, 0, 0, 0);
break;
case 'year':
start.setMonth(0, 1);
start.setHours(0, 0, 0, 0);
break;
}
return start;
}
getPeriodEnd(startDate, period) {
const end = new Date(startDate);
switch (period) {
case 'hour':
end.setHours(end.getHours() + 1);
break;
case 'day':
end.setDate(end.getDate() + 1);
break;
case 'week':
end.setDate(end.getDate() + 7);
break;
case 'month':
end.setMonth(end.getMonth() + 1);
break;
case 'year':
end.setFullYear(end.getFullYear() + 1);
break;
}
return end;
}
}
exports.ActivityIndex = ActivityIndex;
//# sourceMappingURL=activity-index.js.map