MOBILE-3821 core: Configure lazy cache lifetime

main
Noel De Martin 2022-02-17 13:52:40 +01:00
parent 37bde23418
commit 1b24f0955d
8 changed files with 139 additions and 56 deletions

View File

@ -14,10 +14,11 @@
import { CoreConstants } from '@/core/constants'; import { CoreConstants } from '@/core/constants';
import { asyncInstance } from '@/core/utils/async-instance'; import { asyncInstance } from '@/core/utils/async-instance';
import { SQLiteDB, SQLiteDBRecordValues } from '@classes/sqlitedb'; import { SQLiteDBRecordValues } from '@classes/sqlitedb';
import { CoreConfig, CoreConfigProvider } from '@services/config'; import { CoreConfig, CoreConfigProvider } from '@services/config';
import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { import {
CoreDatabaseConfiguration,
CoreDatabaseReducer, CoreDatabaseReducer,
CoreDatabaseTable, CoreDatabaseTable,
CoreDatabaseConditions, CoreDatabaseConditions,
@ -40,7 +41,8 @@ export class CoreDatabaseTableProxy<
PrimaryKey extends GetDBRecordPrimaryKey<DBRecord, PrimaryKeyColumn> = GetDBRecordPrimaryKey<DBRecord, PrimaryKeyColumn> PrimaryKey extends GetDBRecordPrimaryKey<DBRecord, PrimaryKeyColumn> = GetDBRecordPrimaryKey<DBRecord, PrimaryKeyColumn>
> extends CoreDatabaseTable<DBRecord, PrimaryKeyColumn, PrimaryKey> { > extends CoreDatabaseTable<DBRecord, PrimaryKeyColumn, PrimaryKey> {
protected config: CoreDatabaseConfiguration; protected readonly DEFAULT_CACHING_STRATEGY = CoreDatabaseCachingStrategy.None;
protected target = asyncInstance<CoreDatabaseTable<DBRecord, PrimaryKeyColumn>>(); protected target = asyncInstance<CoreDatabaseTable<DBRecord, PrimaryKeyColumn>>();
protected environmentObserver?: CoreEventObserver; protected environmentObserver?: CoreEventObserver;
protected targetConstructors: Record< protected targetConstructors: Record<
@ -52,21 +54,12 @@ export class CoreDatabaseTableProxy<
[CoreDatabaseCachingStrategy.None]: CoreDatabaseTable, [CoreDatabaseCachingStrategy.None]: CoreDatabaseTable,
}; };
constructor(
config: Partial<CoreDatabaseConfiguration>,
database: SQLiteDB,
tableName: string,
primaryKeyColumns?: PrimaryKeyColumn[],
) {
super(database, tableName, primaryKeyColumns);
this.config = { ...this.getConfigDefaults(), ...config };
}
/** /**
* @inheritdoc * @inheritdoc
*/ */
async initialize(): Promise<void> { async initialize(): Promise<void> {
await super.initialize();
this.environmentObserver = CoreEvents.on(CoreConfigProvider.ENVIRONMENT_UPDATED, async () => { this.environmentObserver = CoreEvents.on(CoreConfigProvider.ENVIRONMENT_UPDATED, async () => {
if (!(await this.shouldUpdateTarget())) { if (!(await this.shouldUpdateTarget())) {
return; return;
@ -82,9 +75,23 @@ export class CoreDatabaseTableProxy<
* @inheritdoc * @inheritdoc
*/ */
async destroy(): Promise<void> { async destroy(): Promise<void> {
await super.destroy();
this.environmentObserver?.off(); this.environmentObserver?.off();
} }
/**
* @inheritdoc
*/
matchesConfig(config: Partial<CoreDatabaseConfiguration>): boolean {
const thisDebug = this.config.debug ?? false;
const thisCachingStrategy = this.config.cachingStrategy ?? this.DEFAULT_CACHING_STRATEGY;
const otherDebug = config.debug ?? false;
const otherCachingStrategy = config.cachingStrategy ?? this.DEFAULT_CACHING_STRATEGY;
return super.matchesConfig(config) && thisDebug === otherDebug && thisCachingStrategy === otherCachingStrategy;
}
/** /**
* @inheritdoc * @inheritdoc
*/ */
@ -172,24 +179,12 @@ export class CoreDatabaseTableProxy<
return this.target.deleteByPrimaryKey(primaryKey); return this.target.deleteByPrimaryKey(primaryKey);
} }
/**
* Get default configuration values.
*
* @returns Config defaults.
*/
protected getConfigDefaults(): CoreDatabaseConfiguration {
return {
cachingStrategy: CoreDatabaseCachingStrategy.None,
debug: false,
};
}
/** /**
* Get database configuration to use at runtime. * Get database configuration to use at runtime.
* *
* @returns Database configuration. * @returns Database configuration.
*/ */
protected async getRuntimeConfig(): Promise<CoreDatabaseConfiguration> { protected async getRuntimeConfig(): Promise<Partial<CoreDatabaseConfiguration>> {
await CoreConfig.ready(); await CoreConfig.ready();
return { return {
@ -228,7 +223,8 @@ export class CoreDatabaseTableProxy<
const originalTarget = target instanceof CoreDebugDatabaseTable ? target.getTarget() : target; const originalTarget = target instanceof CoreDebugDatabaseTable ? target.getTarget() : target;
return (config.debug && target === originalTarget) return (config.debug && target === originalTarget)
|| originalTarget?.constructor !== this.targetConstructors[config.cachingStrategy]; || originalTarget?.constructor !== this.targetConstructors[config.cachingStrategy ?? this.DEFAULT_CACHING_STRATEGY]
|| !originalTarget.matchesConfig(config);
} }
/** /**
@ -238,7 +234,7 @@ export class CoreDatabaseTableProxy<
*/ */
protected async createTarget(): Promise<CoreDatabaseTable<DBRecord, PrimaryKeyColumn>> { protected async createTarget(): Promise<CoreDatabaseTable<DBRecord, PrimaryKeyColumn>> {
const config = await this.getRuntimeConfig(); const config = await this.getRuntimeConfig();
const table = this.createTable(config.cachingStrategy); const table = this.createTable(config);
return config.debug ? new CoreDebugDatabaseTable(table) : table; return config.debug ? new CoreDebugDatabaseTable(table) : table;
} }
@ -246,23 +242,29 @@ export class CoreDatabaseTableProxy<
/** /**
* Create a database table using the given caching strategy. * Create a database table using the given caching strategy.
* *
* @param cachingStrategy Caching strategy. * @param config Database configuration.
* @returns Database table. * @returns Database table.
*/ */
protected createTable(cachingStrategy: CoreDatabaseCachingStrategy): CoreDatabaseTable<DBRecord, PrimaryKeyColumn> { protected createTable(config: Partial<CoreDatabaseConfiguration>): CoreDatabaseTable<DBRecord, PrimaryKeyColumn> {
const DatabaseTable = this.targetConstructors[cachingStrategy]; const DatabaseTable = this.targetConstructors[config.cachingStrategy ?? this.DEFAULT_CACHING_STRATEGY];
return new DatabaseTable(this.database, this.tableName, this.primaryKeyColumns); return new DatabaseTable(config, this.database, this.tableName, this.primaryKeyColumns);
} }
} }
/** declare module '@classes/database/database-table' {
* Database proxy configuration.
/**
* Augment CoreDatabaseConfiguration interface with data specific to this class.
*
* @see https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
*/ */
export interface CoreDatabaseConfiguration { export interface CoreDatabaseConfiguration {
cachingStrategy: CoreDatabaseCachingStrategy; cachingStrategy: CoreDatabaseCachingStrategy;
debug: boolean; debug: boolean;
}
} }
/** /**

View File

@ -24,16 +24,30 @@ export class CoreDatabaseTable<
PrimaryKey extends GetDBRecordPrimaryKey<DBRecord, PrimaryKeyColumn> = GetDBRecordPrimaryKey<DBRecord, PrimaryKeyColumn> PrimaryKey extends GetDBRecordPrimaryKey<DBRecord, PrimaryKeyColumn> = GetDBRecordPrimaryKey<DBRecord, PrimaryKeyColumn>
> { > {
protected config: Partial<CoreDatabaseConfiguration>;
protected database: SQLiteDB; protected database: SQLiteDB;
protected tableName: string; protected tableName: string;
protected primaryKeyColumns: PrimaryKeyColumn[]; protected primaryKeyColumns: PrimaryKeyColumn[];
constructor(database: SQLiteDB, tableName: string, primaryKeyColumns?: PrimaryKeyColumn[]) { constructor(
config: Partial<CoreDatabaseConfiguration>,
database: SQLiteDB,
tableName: string,
primaryKeyColumns?: PrimaryKeyColumn[],
) {
this.config = config;
this.database = database; this.database = database;
this.tableName = tableName; this.tableName = tableName;
this.primaryKeyColumns = primaryKeyColumns ?? ['id'] as PrimaryKeyColumn[]; this.primaryKeyColumns = primaryKeyColumns ?? ['id'] as PrimaryKeyColumn[];
} }
/**
* Get database configuration.
*/
getConfig(): Partial<CoreDatabaseConfiguration> {
return this.config;
}
/** /**
* Get database connection. * Get database connection.
* *
@ -75,6 +89,17 @@ export class CoreDatabaseTable<
// Nothing to destroy by default, override this method if necessary. // Nothing to destroy by default, override this method if necessary.
} }
/**
* Check whether the table matches the given configuration for the values that concern it.
*
* @param config Database config.
* @returns Whether the table matches the given configuration.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
matchesConfig(config: Partial<CoreDatabaseConfiguration>): boolean {
return true;
}
/** /**
* Get records matching the given conditions. * Get records matching the given conditions.
* *
@ -336,6 +361,13 @@ export class CoreDatabaseTable<
} }
/**
* Database configuration.
*/
export interface CoreDatabaseConfiguration {
// This definition is augmented in subclasses.
}
/** /**
* CoreDatabaseTable constructor. * CoreDatabaseTable constructor.
*/ */
@ -346,6 +378,7 @@ export type CoreDatabaseTableConstructor<
> = { > = {
new ( new (
config: Partial<CoreDatabaseConfiguration>,
database: SQLiteDB, database: SQLiteDB,
tableName: string, tableName: string,
primaryKeyColumns?: PrimaryKeyColumn[] primaryKeyColumns?: PrimaryKeyColumn[]

View File

@ -37,7 +37,7 @@ export class CoreDebugDatabaseTable<
protected logger: CoreLogger; protected logger: CoreLogger;
constructor(target: CoreDatabaseTable<DBRecord, PrimaryKeyColumn, PrimaryKey>) { constructor(target: CoreDatabaseTable<DBRecord, PrimaryKeyColumn, PrimaryKey>) {
super(target.getDatabase(), target.getTableName(), target.getPrimaryKeyColumns()); super(target.getConfig(), target.getDatabase(), target.getTableName(), target.getPrimaryKeyColumns());
this.target = target; this.target = target;
this.logger = CoreLogger.getInstance(`CoreDatabase[${this.tableName}]`); this.logger = CoreLogger.getInstance(`CoreDatabase[${this.tableName}]`);
@ -53,7 +53,9 @@ export class CoreDebugDatabaseTable<
/** /**
* @inheritdoc * @inheritdoc
*/ */
initialize(): Promise<void> { async initialize(): Promise<void> {
await super.initialize();
this.logger.log('initialize', this.target); this.logger.log('initialize', this.target);
return this.target.initialize(); return this.target.initialize();
@ -62,7 +64,9 @@ export class CoreDebugDatabaseTable<
/** /**
* @inheritdoc * @inheritdoc
*/ */
destroy(): Promise<void> { async destroy(): Promise<void> {
await super.destroy();
this.logger.log('destroy'); this.logger.log('destroy');
return this.target.destroy(); return this.target.destroy();

View File

@ -40,6 +40,8 @@ export class CoreEagerDatabaseTable<
* @inheritdoc * @inheritdoc
*/ */
async initialize(): Promise<void> { async initialize(): Promise<void> {
await super.initialize();
const records = await super.getMany(); const records = await super.getMany();
this.records = records.reduce((data, record) => { this.records = records.reduce((data, record) => {

View File

@ -14,7 +14,13 @@
import { CoreError } from '@classes/errors/error'; import { CoreError } from '@classes/errors/error';
import { SQLiteDBRecordValues } from '@classes/sqlitedb'; import { SQLiteDBRecordValues } from '@classes/sqlitedb';
import { CoreDatabaseTable, CoreDatabaseConditions, GetDBRecordPrimaryKey, CoreDatabaseQueryOptions } from './database-table'; import {
CoreDatabaseConfiguration,
CoreDatabaseTable,
CoreDatabaseConditions,
GetDBRecordPrimaryKey,
CoreDatabaseQueryOptions,
} from './database-table';
/** /**
* Wrapper used to improve performance by caching records that are used often for faster read operations. * Wrapper used to improve performance by caching records that are used often for faster read operations.
@ -28,7 +34,38 @@ export class CoreLazyDatabaseTable<
PrimaryKey extends GetDBRecordPrimaryKey<DBRecord, PrimaryKeyColumn> = GetDBRecordPrimaryKey<DBRecord, PrimaryKeyColumn> PrimaryKey extends GetDBRecordPrimaryKey<DBRecord, PrimaryKeyColumn> = GetDBRecordPrimaryKey<DBRecord, PrimaryKeyColumn>
> extends CoreDatabaseTable<DBRecord, PrimaryKeyColumn, PrimaryKey> { > extends CoreDatabaseTable<DBRecord, PrimaryKeyColumn, PrimaryKey> {
protected readonly DEFAULT_CACHE_LIFETIME = 60000;
protected records: Record<string, DBRecord | null> = {}; protected records: Record<string, DBRecord | null> = {};
protected interval?: number;
/**
* @inheritdoc
*/
async initialize(): Promise<void> {
await super.initialize();
this.interval = window.setInterval(() => (this.records = {}), this.config.lazyCacheLifetime ?? this.DEFAULT_CACHE_LIFETIME);
}
/**
* @inheritdoc
*/
async destroy(): Promise<void> {
await super.destroy();
this.interval && window.clearInterval(this.interval);
}
/**
* @inheritdoc
*/
matchesConfig(config: Partial<CoreDatabaseConfiguration>): boolean {
const thisCacheLifetime = this.config.lazyCacheLifetime ?? this.DEFAULT_CACHE_LIFETIME;
const otherCacheLifetime = config.lazyCacheLifetime ?? this.DEFAULT_CACHE_LIFETIME;
return super.matchesConfig(config) && thisCacheLifetime === otherCacheLifetime;
}
/** /**
* @inheritdoc * @inheritdoc
@ -152,3 +189,16 @@ export class CoreLazyDatabaseTable<
} }
} }
declare module '@classes/database/database-table' {
/**
* Augment CoreDatabaseConfiguration interface with data specific to this table.
*
* @see https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
*/
export interface CoreDatabaseConfiguration {
lazyCacheLifetime: number;
}
}

View File

@ -13,12 +13,8 @@
// limitations under the License. // limitations under the License.
import { mock, mockSingleton } from '@/testing/utils'; import { mock, mockSingleton } from '@/testing/utils';
import { CoreDatabaseSorting, CoreDatabaseTable } from '@classes/database/database-table'; import { CoreDatabaseConfiguration, CoreDatabaseSorting, CoreDatabaseTable } from '@classes/database/database-table';
import { import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy';
CoreDatabaseCachingStrategy,
CoreDatabaseConfiguration,
CoreDatabaseTableProxy,
} from '@classes/database/database-table-proxy';
import { SQLiteDB } from '@classes/sqlitedb'; import { SQLiteDB } from '@classes/sqlitedb';
import { CoreConfig } from '@services/config'; import { CoreConfig } from '@services/config';

View File

@ -56,12 +56,8 @@ import { CoreAjaxError } from '@classes/errors/ajaxerror';
import { CoreAjaxWSError } from '@classes/errors/ajaxwserror'; import { CoreAjaxWSError } from '@classes/errors/ajaxwserror';
import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins'; import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins';
import { CorePromisedValue } from '@classes/promised-value'; import { CorePromisedValue } from '@classes/promised-value';
import { CoreDatabaseTable } from '@classes/database/database-table'; import { CoreDatabaseConfiguration, CoreDatabaseTable } from '@classes/database/database-table';
import { import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy';
CoreDatabaseCachingStrategy,
CoreDatabaseConfiguration,
CoreDatabaseTableProxy,
} from '@classes/database/database-table-proxy';
import { asyncInstance, AsyncInstance } from '../utils/async-instance'; import { asyncInstance, AsyncInstance } from '../utils/async-instance';
import { CoreConfig } from './config'; import { CoreConfig } from './config';

View File

@ -17,7 +17,7 @@ import { CoreMainMenuLocalizedCustomItem } from '@features/mainmenu/services/mai
import { CoreSitesDemoSiteData } from '@services/sites'; import { CoreSitesDemoSiteData } from '@services/sites';
import { OpenFileAction } from '@services/utils/utils'; import { OpenFileAction } from '@services/utils/utils';
import { CoreLoginSiteSelectorListMethod } from '@features/login/services/login-helper'; import { CoreLoginSiteSelectorListMethod } from '@features/login/services/login-helper';
import { CoreDatabaseConfiguration } from '@classes/database/database-table-proxy'; import { CoreDatabaseConfiguration } from '@classes/database/database-table';
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */