516 lines
20 KiB
JavaScript
516 lines
20 KiB
JavaScript
|
|
/**
|
||
|
|
* Base Storage Adapter
|
||
|
|
* Provides common functionality for all storage adapters
|
||
|
|
*/
|
||
|
|
import { BaseStorageAdapter } from './adapters/baseStorageAdapter.js';
|
||
|
|
// Common directory/prefix names
|
||
|
|
// Option A: Entity-Based Directory Structure
|
||
|
|
export const ENTITIES_DIR = 'entities';
|
||
|
|
export const NOUNS_VECTOR_DIR = 'entities/nouns/vectors';
|
||
|
|
export const NOUNS_METADATA_DIR = 'entities/nouns/metadata';
|
||
|
|
export const VERBS_VECTOR_DIR = 'entities/verbs/vectors';
|
||
|
|
export const VERBS_METADATA_DIR = 'entities/verbs/metadata';
|
||
|
|
export const INDEXES_DIR = 'indexes';
|
||
|
|
export const METADATA_INDEX_DIR = 'indexes/metadata';
|
||
|
|
// Legacy paths - kept for backward compatibility during migration
|
||
|
|
export const NOUNS_DIR = 'nouns'; // Legacy: now maps to entities/nouns/vectors
|
||
|
|
export const VERBS_DIR = 'verbs'; // Legacy: now maps to entities/verbs/vectors
|
||
|
|
export const METADATA_DIR = 'metadata'; // Legacy: now maps to entities/nouns/metadata
|
||
|
|
export const NOUN_METADATA_DIR = 'noun-metadata'; // Legacy: now maps to entities/nouns/metadata
|
||
|
|
export const VERB_METADATA_DIR = 'verb-metadata'; // Legacy: now maps to entities/verbs/metadata
|
||
|
|
export const INDEX_DIR = 'index'; // Legacy - kept for backward compatibility
|
||
|
|
export const SYSTEM_DIR = '_system'; // System config & metadata indexes
|
||
|
|
export const STATISTICS_KEY = 'statistics';
|
||
|
|
// Migration version to track compatibility
|
||
|
|
export const STORAGE_SCHEMA_VERSION = 3; // v3: Entity-Based Directory Structure (Option A)
|
||
|
|
// Configuration flag to enable new directory structure
|
||
|
|
export const USE_ENTITY_BASED_STRUCTURE = true; // Set to true to use Option A structure
|
||
|
|
/**
|
||
|
|
* Get the appropriate directory path based on configuration
|
||
|
|
*/
|
||
|
|
export function getDirectoryPath(entityType, dataType) {
|
||
|
|
if (USE_ENTITY_BASED_STRUCTURE) {
|
||
|
|
// Option A: Entity-Based Structure
|
||
|
|
if (entityType === 'noun') {
|
||
|
|
return dataType === 'vector' ? NOUNS_VECTOR_DIR : NOUNS_METADATA_DIR;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
return dataType === 'vector' ? VERBS_VECTOR_DIR : VERBS_METADATA_DIR;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
// Legacy structure
|
||
|
|
if (entityType === 'noun') {
|
||
|
|
return dataType === 'vector' ? NOUNS_DIR : METADATA_DIR;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
return dataType === 'vector' ? VERBS_DIR : VERB_METADATA_DIR;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Base storage adapter that implements common functionality
|
||
|
|
* This is an abstract class that should be extended by specific storage adapters
|
||
|
|
*/
|
||
|
|
export class BaseStorage extends BaseStorageAdapter {
|
||
|
|
constructor() {
|
||
|
|
super(...arguments);
|
||
|
|
this.isInitialized = false;
|
||
|
|
this.readOnly = false;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Ensure the storage adapter is initialized
|
||
|
|
*/
|
||
|
|
async ensureInitialized() {
|
||
|
|
if (!this.isInitialized) {
|
||
|
|
await this.init();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Save a noun to storage
|
||
|
|
*/
|
||
|
|
async saveNoun(noun) {
|
||
|
|
await this.ensureInitialized();
|
||
|
|
return this.saveNoun_internal(noun);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get a noun from storage
|
||
|
|
*/
|
||
|
|
async getNoun(id) {
|
||
|
|
await this.ensureInitialized();
|
||
|
|
return this.getNoun_internal(id);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get nouns by noun type
|
||
|
|
* @param nounType The noun type to filter by
|
||
|
|
* @returns Promise that resolves to an array of nouns of the specified noun type
|
||
|
|
*/
|
||
|
|
async getNounsByNounType(nounType) {
|
||
|
|
await this.ensureInitialized();
|
||
|
|
return this.getNounsByNounType_internal(nounType);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Delete a noun from storage
|
||
|
|
*/
|
||
|
|
async deleteNoun(id) {
|
||
|
|
await this.ensureInitialized();
|
||
|
|
return this.deleteNoun_internal(id);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Save a verb to storage
|
||
|
|
*/
|
||
|
|
async saveVerb(verb) {
|
||
|
|
await this.ensureInitialized();
|
||
|
|
// Extract the lightweight HNSWVerb data
|
||
|
|
const hnswVerb = {
|
||
|
|
id: verb.id,
|
||
|
|
vector: verb.vector,
|
||
|
|
connections: verb.connections || new Map()
|
||
|
|
};
|
||
|
|
// Extract and save the metadata separately
|
||
|
|
const metadata = {
|
||
|
|
sourceId: verb.sourceId || verb.source,
|
||
|
|
targetId: verb.targetId || verb.target,
|
||
|
|
source: verb.source || verb.sourceId,
|
||
|
|
target: verb.target || verb.targetId,
|
||
|
|
type: verb.type || verb.verb,
|
||
|
|
verb: verb.verb || verb.type,
|
||
|
|
weight: verb.weight,
|
||
|
|
metadata: verb.metadata,
|
||
|
|
data: verb.data,
|
||
|
|
createdAt: verb.createdAt,
|
||
|
|
updatedAt: verb.updatedAt,
|
||
|
|
createdBy: verb.createdBy,
|
||
|
|
embedding: verb.embedding
|
||
|
|
};
|
||
|
|
// Save both the HNSWVerb and metadata
|
||
|
|
await this.saveVerb_internal(hnswVerb);
|
||
|
|
await this.saveVerbMetadata(verb.id, metadata);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get a verb from storage
|
||
|
|
*/
|
||
|
|
async getVerb(id) {
|
||
|
|
await this.ensureInitialized();
|
||
|
|
const hnswVerb = await this.getVerb_internal(id);
|
||
|
|
if (!hnswVerb) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
return this.convertHNSWVerbToGraphVerb(hnswVerb);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Convert HNSWVerb to GraphVerb by combining with metadata
|
||
|
|
*/
|
||
|
|
async convertHNSWVerbToGraphVerb(hnswVerb) {
|
||
|
|
try {
|
||
|
|
const metadata = await this.getVerbMetadata(hnswVerb.id);
|
||
|
|
if (!metadata) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
// Create default timestamp if not present
|
||
|
|
const defaultTimestamp = {
|
||
|
|
seconds: Math.floor(Date.now() / 1000),
|
||
|
|
nanoseconds: (Date.now() % 1000) * 1000000
|
||
|
|
};
|
||
|
|
// Create default createdBy if not present
|
||
|
|
const defaultCreatedBy = {
|
||
|
|
augmentation: 'unknown',
|
||
|
|
version: '1.0'
|
||
|
|
};
|
||
|
|
return {
|
||
|
|
id: hnswVerb.id,
|
||
|
|
vector: hnswVerb.vector,
|
||
|
|
sourceId: metadata.sourceId,
|
||
|
|
targetId: metadata.targetId,
|
||
|
|
source: metadata.source,
|
||
|
|
target: metadata.target,
|
||
|
|
verb: metadata.verb,
|
||
|
|
type: metadata.type,
|
||
|
|
weight: metadata.weight || 1.0,
|
||
|
|
metadata: metadata.metadata || {},
|
||
|
|
createdAt: metadata.createdAt || defaultTimestamp,
|
||
|
|
updatedAt: metadata.updatedAt || defaultTimestamp,
|
||
|
|
createdBy: metadata.createdBy || defaultCreatedBy,
|
||
|
|
data: metadata.data,
|
||
|
|
embedding: hnswVerb.vector
|
||
|
|
};
|
||
|
|
}
|
||
|
|
catch (error) {
|
||
|
|
console.error(`Failed to convert HNSWVerb to GraphVerb for ${hnswVerb.id}:`, error);
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Internal method for loading all verbs - used by performance optimizations
|
||
|
|
* @internal - Do not use directly, use getVerbs() with pagination instead
|
||
|
|
*/
|
||
|
|
async _loadAllVerbsForOptimization() {
|
||
|
|
await this.ensureInitialized();
|
||
|
|
// Only use this for internal optimizations when safe
|
||
|
|
const result = await this.getVerbs({
|
||
|
|
pagination: { limit: Number.MAX_SAFE_INTEGER }
|
||
|
|
});
|
||
|
|
// Convert GraphVerbs back to HNSWVerbs for internal use
|
||
|
|
const hnswVerbs = [];
|
||
|
|
for (const graphVerb of result.items) {
|
||
|
|
const hnswVerb = {
|
||
|
|
id: graphVerb.id,
|
||
|
|
vector: graphVerb.vector,
|
||
|
|
connections: new Map()
|
||
|
|
};
|
||
|
|
hnswVerbs.push(hnswVerb);
|
||
|
|
}
|
||
|
|
return hnswVerbs;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get verbs by source
|
||
|
|
*/
|
||
|
|
async getVerbsBySource(sourceId) {
|
||
|
|
await this.ensureInitialized();
|
||
|
|
// Use the paginated getVerbs method with source filter
|
||
|
|
const result = await this.getVerbs({
|
||
|
|
filter: { sourceId }
|
||
|
|
});
|
||
|
|
return result.items;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get verbs by target
|
||
|
|
*/
|
||
|
|
async getVerbsByTarget(targetId) {
|
||
|
|
await this.ensureInitialized();
|
||
|
|
// Use the paginated getVerbs method with target filter
|
||
|
|
const result = await this.getVerbs({
|
||
|
|
filter: { targetId }
|
||
|
|
});
|
||
|
|
return result.items;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get verbs by type
|
||
|
|
*/
|
||
|
|
async getVerbsByType(type) {
|
||
|
|
await this.ensureInitialized();
|
||
|
|
// Use the paginated getVerbs method with type filter
|
||
|
|
const result = await this.getVerbs({
|
||
|
|
filter: { verbType: type }
|
||
|
|
});
|
||
|
|
return result.items;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Internal method for loading all nouns - used by performance optimizations
|
||
|
|
* @internal - Do not use directly, use getNouns() with pagination instead
|
||
|
|
*/
|
||
|
|
async _loadAllNounsForOptimization() {
|
||
|
|
await this.ensureInitialized();
|
||
|
|
// Only use this for internal optimizations when safe
|
||
|
|
const result = await this.getNouns({
|
||
|
|
pagination: { limit: Number.MAX_SAFE_INTEGER }
|
||
|
|
});
|
||
|
|
return result.items;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get nouns with pagination and filtering
|
||
|
|
* @param options Pagination and filtering options
|
||
|
|
* @returns Promise that resolves to a paginated result of nouns
|
||
|
|
*/
|
||
|
|
async getNouns(options) {
|
||
|
|
await this.ensureInitialized();
|
||
|
|
// Set default pagination values
|
||
|
|
const pagination = options?.pagination || {};
|
||
|
|
const limit = pagination.limit || 100;
|
||
|
|
const offset = pagination.offset || 0;
|
||
|
|
const cursor = pagination.cursor;
|
||
|
|
// Optimize for common filter cases to avoid loading all nouns
|
||
|
|
if (options?.filter) {
|
||
|
|
// If filtering by nounType only, use the optimized method
|
||
|
|
if (options.filter.nounType &&
|
||
|
|
!options.filter.service &&
|
||
|
|
!options.filter.metadata) {
|
||
|
|
const nounType = Array.isArray(options.filter.nounType)
|
||
|
|
? options.filter.nounType[0]
|
||
|
|
: options.filter.nounType;
|
||
|
|
// Get nouns by type directly
|
||
|
|
const nounsByType = await this.getNounsByNounType_internal(nounType);
|
||
|
|
// Apply pagination
|
||
|
|
const paginatedNouns = nounsByType.slice(offset, offset + limit);
|
||
|
|
const hasMore = offset + limit < nounsByType.length;
|
||
|
|
// Set next cursor if there are more items
|
||
|
|
let nextCursor = undefined;
|
||
|
|
if (hasMore && paginatedNouns.length > 0) {
|
||
|
|
const lastItem = paginatedNouns[paginatedNouns.length - 1];
|
||
|
|
nextCursor = lastItem.id;
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
items: paginatedNouns,
|
||
|
|
totalCount: nounsByType.length,
|
||
|
|
hasMore,
|
||
|
|
nextCursor
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// For more complex filtering or no filtering, use a paginated approach
|
||
|
|
// that avoids loading all nouns into memory at once
|
||
|
|
try {
|
||
|
|
// First, try to get a count of total nouns (if the adapter supports it)
|
||
|
|
let totalCount = undefined;
|
||
|
|
try {
|
||
|
|
// This is an optional method that adapters may implement
|
||
|
|
if (typeof this.countNouns === 'function') {
|
||
|
|
totalCount = await this.countNouns(options?.filter);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
catch (countError) {
|
||
|
|
// Ignore errors from count method, it's optional
|
||
|
|
console.warn('Error getting noun count:', countError);
|
||
|
|
}
|
||
|
|
// Check if the adapter has a paginated method for getting nouns
|
||
|
|
if (typeof this.getNounsWithPagination === 'function') {
|
||
|
|
// Use the adapter's paginated method
|
||
|
|
const result = await this.getNounsWithPagination({
|
||
|
|
limit,
|
||
|
|
cursor,
|
||
|
|
filter: options?.filter
|
||
|
|
});
|
||
|
|
// Apply offset if needed (some adapters might not support offset)
|
||
|
|
const items = result.items.slice(offset);
|
||
|
|
return {
|
||
|
|
items,
|
||
|
|
totalCount: result.totalCount || totalCount,
|
||
|
|
hasMore: result.hasMore,
|
||
|
|
nextCursor: result.nextCursor
|
||
|
|
};
|
||
|
|
}
|
||
|
|
// Storage adapter does not support pagination
|
||
|
|
console.error('Storage adapter does not support pagination. The deprecated getAllNouns_internal() method has been removed. Please implement getNounsWithPagination() in your storage adapter.');
|
||
|
|
return {
|
||
|
|
items: [],
|
||
|
|
totalCount: 0,
|
||
|
|
hasMore: false
|
||
|
|
};
|
||
|
|
}
|
||
|
|
catch (error) {
|
||
|
|
console.error('Error getting nouns with pagination:', error);
|
||
|
|
return {
|
||
|
|
items: [],
|
||
|
|
totalCount: 0,
|
||
|
|
hasMore: false
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get verbs with pagination and filtering
|
||
|
|
* @param options Pagination and filtering options
|
||
|
|
* @returns Promise that resolves to a paginated result of verbs
|
||
|
|
*/
|
||
|
|
async getVerbs(options) {
|
||
|
|
await this.ensureInitialized();
|
||
|
|
// Set default pagination values
|
||
|
|
const pagination = options?.pagination || {};
|
||
|
|
const limit = pagination.limit || 100;
|
||
|
|
const offset = pagination.offset || 0;
|
||
|
|
const cursor = pagination.cursor;
|
||
|
|
// Optimize for common filter cases to avoid loading all verbs
|
||
|
|
if (options?.filter) {
|
||
|
|
// If filtering by sourceId only, use the optimized method
|
||
|
|
if (options.filter.sourceId &&
|
||
|
|
!options.filter.verbType &&
|
||
|
|
!options.filter.targetId &&
|
||
|
|
!options.filter.service &&
|
||
|
|
!options.filter.metadata) {
|
||
|
|
const sourceId = Array.isArray(options.filter.sourceId)
|
||
|
|
? options.filter.sourceId[0]
|
||
|
|
: options.filter.sourceId;
|
||
|
|
// Get verbs by source directly
|
||
|
|
const verbsBySource = await this.getVerbsBySource_internal(sourceId);
|
||
|
|
// Apply pagination
|
||
|
|
const paginatedVerbs = verbsBySource.slice(offset, offset + limit);
|
||
|
|
const hasMore = offset + limit < verbsBySource.length;
|
||
|
|
// Set next cursor if there are more items
|
||
|
|
let nextCursor = undefined;
|
||
|
|
if (hasMore && paginatedVerbs.length > 0) {
|
||
|
|
const lastItem = paginatedVerbs[paginatedVerbs.length - 1];
|
||
|
|
nextCursor = lastItem.id;
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
items: paginatedVerbs,
|
||
|
|
totalCount: verbsBySource.length,
|
||
|
|
hasMore,
|
||
|
|
nextCursor
|
||
|
|
};
|
||
|
|
}
|
||
|
|
// If filtering by targetId only, use the optimized method
|
||
|
|
if (options.filter.targetId &&
|
||
|
|
!options.filter.verbType &&
|
||
|
|
!options.filter.sourceId &&
|
||
|
|
!options.filter.service &&
|
||
|
|
!options.filter.metadata) {
|
||
|
|
const targetId = Array.isArray(options.filter.targetId)
|
||
|
|
? options.filter.targetId[0]
|
||
|
|
: options.filter.targetId;
|
||
|
|
// Get verbs by target directly
|
||
|
|
const verbsByTarget = await this.getVerbsByTarget_internal(targetId);
|
||
|
|
// Apply pagination
|
||
|
|
const paginatedVerbs = verbsByTarget.slice(offset, offset + limit);
|
||
|
|
const hasMore = offset + limit < verbsByTarget.length;
|
||
|
|
// Set next cursor if there are more items
|
||
|
|
let nextCursor = undefined;
|
||
|
|
if (hasMore && paginatedVerbs.length > 0) {
|
||
|
|
const lastItem = paginatedVerbs[paginatedVerbs.length - 1];
|
||
|
|
nextCursor = lastItem.id;
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
items: paginatedVerbs,
|
||
|
|
totalCount: verbsByTarget.length,
|
||
|
|
hasMore,
|
||
|
|
nextCursor
|
||
|
|
};
|
||
|
|
}
|
||
|
|
// If filtering by verbType only, use the optimized method
|
||
|
|
if (options.filter.verbType &&
|
||
|
|
!options.filter.sourceId &&
|
||
|
|
!options.filter.targetId &&
|
||
|
|
!options.filter.service &&
|
||
|
|
!options.filter.metadata) {
|
||
|
|
const verbType = Array.isArray(options.filter.verbType)
|
||
|
|
? options.filter.verbType[0]
|
||
|
|
: options.filter.verbType;
|
||
|
|
// Get verbs by type directly
|
||
|
|
const verbsByType = await this.getVerbsByType_internal(verbType);
|
||
|
|
// Apply pagination
|
||
|
|
const paginatedVerbs = verbsByType.slice(offset, offset + limit);
|
||
|
|
const hasMore = offset + limit < verbsByType.length;
|
||
|
|
// Set next cursor if there are more items
|
||
|
|
let nextCursor = undefined;
|
||
|
|
if (hasMore && paginatedVerbs.length > 0) {
|
||
|
|
const lastItem = paginatedVerbs[paginatedVerbs.length - 1];
|
||
|
|
nextCursor = lastItem.id;
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
items: paginatedVerbs,
|
||
|
|
totalCount: verbsByType.length,
|
||
|
|
hasMore,
|
||
|
|
nextCursor
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// For more complex filtering or no filtering, use a paginated approach
|
||
|
|
// that avoids loading all verbs into memory at once
|
||
|
|
try {
|
||
|
|
// First, try to get a count of total verbs (if the adapter supports it)
|
||
|
|
let totalCount = undefined;
|
||
|
|
try {
|
||
|
|
// This is an optional method that adapters may implement
|
||
|
|
if (typeof this.countVerbs === 'function') {
|
||
|
|
totalCount = await this.countVerbs(options?.filter);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
catch (countError) {
|
||
|
|
// Ignore errors from count method, it's optional
|
||
|
|
console.warn('Error getting verb count:', countError);
|
||
|
|
}
|
||
|
|
// Check if the adapter has a paginated method for getting verbs
|
||
|
|
if (typeof this.getVerbsWithPagination === 'function') {
|
||
|
|
// Use the adapter's paginated method
|
||
|
|
const result = await this.getVerbsWithPagination({
|
||
|
|
limit,
|
||
|
|
cursor,
|
||
|
|
filter: options?.filter
|
||
|
|
});
|
||
|
|
// Apply offset if needed (some adapters might not support offset)
|
||
|
|
const items = result.items.slice(offset);
|
||
|
|
return {
|
||
|
|
items,
|
||
|
|
totalCount: result.totalCount || totalCount,
|
||
|
|
hasMore: result.hasMore,
|
||
|
|
nextCursor: result.nextCursor
|
||
|
|
};
|
||
|
|
}
|
||
|
|
// Storage adapter does not support pagination
|
||
|
|
console.error('Storage adapter does not support pagination. The deprecated getAllVerbs_internal() method has been removed. Please implement getVerbsWithPagination() in your storage adapter.');
|
||
|
|
return {
|
||
|
|
items: [],
|
||
|
|
totalCount: 0,
|
||
|
|
hasMore: false
|
||
|
|
};
|
||
|
|
}
|
||
|
|
catch (error) {
|
||
|
|
console.error('Error getting verbs with pagination:', error);
|
||
|
|
return {
|
||
|
|
items: [],
|
||
|
|
totalCount: 0,
|
||
|
|
hasMore: false
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Delete a verb from storage
|
||
|
|
*/
|
||
|
|
async deleteVerb(id) {
|
||
|
|
await this.ensureInitialized();
|
||
|
|
return this.deleteVerb_internal(id);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Helper method to convert a Map to a plain object for serialization
|
||
|
|
*/
|
||
|
|
mapToObject(map, valueTransformer = (v) => v) {
|
||
|
|
const obj = {};
|
||
|
|
for (const [key, value] of map.entries()) {
|
||
|
|
obj[key.toString()] = valueTransformer(value);
|
||
|
|
}
|
||
|
|
return obj;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Save statistics data to storage (public interface)
|
||
|
|
* @param statistics The statistics data to save
|
||
|
|
*/
|
||
|
|
async saveStatistics(statistics) {
|
||
|
|
return this.saveStatisticsData(statistics);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get statistics data from storage (public interface)
|
||
|
|
* @returns Promise that resolves to the statistics data or null if not found
|
||
|
|
*/
|
||
|
|
async getStatistics() {
|
||
|
|
return this.getStatisticsData();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
//# sourceMappingURL=baseStorage.js.map
|