MOBILE-3977 core: Implement async instance pattern
parent
a041471205
commit
7a2a8c3e98
|
@ -13,7 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { CoreConstants } from '@/core/constants';
|
import { CoreConstants } from '@/core/constants';
|
||||||
import { CorePromisedValue } from '@classes/promised-value';
|
import { asyncInstance } from '@/core/utils/async-instance';
|
||||||
import { SQLiteDB, SQLiteDBRecordValues } from '@classes/sqlitedb';
|
import { SQLiteDB, SQLiteDBRecordValues } from '@classes/sqlitedb';
|
||||||
import { CoreConfigProvider } from '@services/config';
|
import { CoreConfigProvider } from '@services/config';
|
||||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
|
@ -34,7 +34,7 @@ export class CoreDatabaseTableProxy<
|
||||||
> extends CoreDatabaseTable<DBRecord, PrimaryKeyColumn, PrimaryKey> {
|
> extends CoreDatabaseTable<DBRecord, PrimaryKeyColumn, PrimaryKey> {
|
||||||
|
|
||||||
protected config: CoreDatabaseConfiguration;
|
protected config: CoreDatabaseConfiguration;
|
||||||
protected target: CorePromisedValue<CoreDatabaseTable<DBRecord, PrimaryKeyColumn>> = new CorePromisedValue();
|
protected target = asyncInstance<CoreDatabaseTable<DBRecord, PrimaryKeyColumn>>();
|
||||||
protected environmentObserver?: CoreEventObserver;
|
protected environmentObserver?: CoreEventObserver;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -68,81 +68,63 @@ export class CoreDatabaseTableProxy<
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async all(conditions?: Partial<DBRecord>): Promise<DBRecord[]> {
|
async all(conditions?: Partial<DBRecord>): Promise<DBRecord[]> {
|
||||||
const target = await this.target;
|
return this.target.all(conditions);
|
||||||
|
|
||||||
return target.all(conditions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async find(conditions: Partial<DBRecord>): Promise<DBRecord> {
|
async find(conditions: Partial<DBRecord>): Promise<DBRecord> {
|
||||||
const target = await this.target;
|
return this.target.find(conditions);
|
||||||
|
|
||||||
return target.find(conditions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async findByPrimaryKey(primaryKey: PrimaryKey): Promise<DBRecord> {
|
async findByPrimaryKey(primaryKey: PrimaryKey): Promise<DBRecord> {
|
||||||
const target = await this.target;
|
return this.target.findByPrimaryKey(primaryKey);
|
||||||
|
|
||||||
return target.findByPrimaryKey(primaryKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async reduce<T>(reducer: CoreDatabaseReducer<DBRecord, T>, conditions?: CoreDatabaseConditions<DBRecord>): Promise<T> {
|
async reduce<T>(reducer: CoreDatabaseReducer<DBRecord, T>, conditions?: CoreDatabaseConditions<DBRecord>): Promise<T> {
|
||||||
const target = await this.target;
|
return this.target.reduce<T>(reducer, conditions);
|
||||||
|
|
||||||
return target.reduce<T>(reducer, conditions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async insert(record: DBRecord): Promise<void> {
|
async insert(record: DBRecord): Promise<void> {
|
||||||
const target = await this.target;
|
return this.target.insert(record);
|
||||||
|
|
||||||
return target.insert(record);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async update(updates: Partial<DBRecord>, conditions?: Partial<DBRecord>): Promise<void> {
|
async update(updates: Partial<DBRecord>, conditions?: Partial<DBRecord>): Promise<void> {
|
||||||
const target = await this.target;
|
return this.target.update(updates, conditions);
|
||||||
|
|
||||||
return target.update(updates, conditions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async updateWhere(updates: Partial<DBRecord>, conditions: CoreDatabaseConditions<DBRecord>): Promise<void> {
|
async updateWhere(updates: Partial<DBRecord>, conditions: CoreDatabaseConditions<DBRecord>): Promise<void> {
|
||||||
const target = await this.target;
|
return this.target.updateWhere(updates, conditions);
|
||||||
|
|
||||||
return target.updateWhere(updates, conditions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async delete(conditions?: Partial<DBRecord>): Promise<void> {
|
async delete(conditions?: Partial<DBRecord>): Promise<void> {
|
||||||
const target = await this.target;
|
return this.target.delete(conditions);
|
||||||
|
|
||||||
return target.delete(conditions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async deleteByPrimaryKey(primaryKey: PrimaryKey): Promise<void> {
|
async deleteByPrimaryKey(primaryKey: PrimaryKey): Promise<void> {
|
||||||
const target = await this.target;
|
return this.target.deleteByPrimaryKey(primaryKey);
|
||||||
|
|
||||||
return target.deleteByPrimaryKey(primaryKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -174,18 +156,18 @@ export class CoreDatabaseTableProxy<
|
||||||
* Update underlying target instance.
|
* Update underlying target instance.
|
||||||
*/
|
*/
|
||||||
protected async updateTarget(): Promise<void> {
|
protected async updateTarget(): Promise<void> {
|
||||||
const oldTarget = this.target.value;
|
const oldTarget = this.target.instance;
|
||||||
const newTarget = this.createTarget();
|
const newTarget = this.createTarget();
|
||||||
|
|
||||||
if (oldTarget) {
|
if (oldTarget) {
|
||||||
await oldTarget.destroy();
|
await oldTarget.destroy();
|
||||||
|
|
||||||
this.target.reset();
|
this.target.resetInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
await newTarget.initialize();
|
await newTarget.initialize();
|
||||||
|
|
||||||
this.target.resolve(newTarget);
|
this.target.setInstance(newTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -42,6 +42,8 @@ import { Translate } from '@singletons';
|
||||||
import { CoreIonLoadingElement } from './ion-loading';
|
import { CoreIonLoadingElement } from './ion-loading';
|
||||||
import { CoreLang } from '@services/lang';
|
import { CoreLang } from '@services/lang';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { asyncInstance, AsyncInstance } from '../utils/async-instance';
|
||||||
|
import { CoreDatabaseTable } from './database/database-table';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* QR Code type enumeration.
|
* QR Code type enumeration.
|
||||||
|
@ -104,6 +106,7 @@ export class CoreSite {
|
||||||
// Rest of variables.
|
// Rest of variables.
|
||||||
protected logger: CoreLogger;
|
protected logger: CoreLogger;
|
||||||
protected db?: SQLiteDB;
|
protected db?: SQLiteDB;
|
||||||
|
protected cacheTable: AsyncInstance<CoreDatabaseTable<CoreSiteWSCacheRecord>>;
|
||||||
protected cleanUnicode = false;
|
protected cleanUnicode = false;
|
||||||
protected lastAutoLogin = 0;
|
protected lastAutoLogin = 0;
|
||||||
protected offlineDisabled = false;
|
protected offlineDisabled = false;
|
||||||
|
@ -137,6 +140,7 @@ export class CoreSite {
|
||||||
) {
|
) {
|
||||||
this.logger = CoreLogger.getInstance('CoreSite');
|
this.logger = CoreLogger.getInstance('CoreSite');
|
||||||
this.siteUrl = CoreUrlUtils.removeUrlParams(this.siteUrl); // Make sure the URL doesn't have params.
|
this.siteUrl = CoreUrlUtils.removeUrlParams(this.siteUrl); // Make sure the URL doesn't have params.
|
||||||
|
this.cacheTable = asyncInstance(() => CoreSites.getCacheTable(this));
|
||||||
this.setInfo(infos);
|
this.setInfo(infos);
|
||||||
this.calculateOfflineDisabled();
|
this.calculateOfflineDisabled();
|
||||||
|
|
||||||
|
@ -926,15 +930,14 @@ export class CoreSite {
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = this.getCacheId(method, data);
|
const id = this.getCacheId(method, data);
|
||||||
const cacheTable = await CoreSites.getCacheTable(this);
|
|
||||||
let entry: CoreSiteWSCacheRecord | undefined;
|
let entry: CoreSiteWSCacheRecord | undefined;
|
||||||
|
|
||||||
if (preSets.getCacheUsingCacheKey || (emergency && preSets.getEmergencyCacheUsingCacheKey)) {
|
if (preSets.getCacheUsingCacheKey || (emergency && preSets.getEmergencyCacheUsingCacheKey)) {
|
||||||
const entries = await cacheTable.all({ key: preSets.cacheKey });
|
const entries = await this.cacheTable.all({ key: preSets.cacheKey });
|
||||||
|
|
||||||
if (!entries.length) {
|
if (!entries.length) {
|
||||||
// Cache key not found, get by params sent.
|
// Cache key not found, get by params sent.
|
||||||
entry = await cacheTable.findByPrimaryKey({ id });
|
entry = await this.cacheTable.findByPrimaryKey({ id });
|
||||||
} else {
|
} else {
|
||||||
if (entries.length > 1) {
|
if (entries.length > 1) {
|
||||||
// More than one entry found. Search the one with same ID as this call.
|
// More than one entry found. Search the one with same ID as this call.
|
||||||
|
@ -946,7 +949,7 @@ export class CoreSite {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
entry = await cacheTable.findByPrimaryKey({ id });
|
entry = await this.cacheTable.findByPrimaryKey({ id });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry === undefined) {
|
if (entry === undefined) {
|
||||||
|
@ -991,14 +994,13 @@ export class CoreSite {
|
||||||
*/
|
*/
|
||||||
async getComponentCacheSize(component: string, componentId?: number): Promise<number> {
|
async getComponentCacheSize(component: string, componentId?: number): Promise<number> {
|
||||||
const params: Array<string | number> = [component];
|
const params: Array<string | number> = [component];
|
||||||
const cacheTable = await CoreSites.getCacheTable(this);
|
|
||||||
let extraClause = '';
|
let extraClause = '';
|
||||||
if (componentId !== undefined && componentId !== null) {
|
if (componentId !== undefined && componentId !== null) {
|
||||||
params.push(componentId);
|
params.push(componentId);
|
||||||
extraClause = ' AND componentId = ?';
|
extraClause = ' AND componentId = ?';
|
||||||
}
|
}
|
||||||
|
|
||||||
return cacheTable.reduce(
|
return this.cacheTable.reduce(
|
||||||
{
|
{
|
||||||
sql: 'SUM(length(data))',
|
sql: 'SUM(length(data))',
|
||||||
js: (size, record) => size + record.data.length,
|
js: (size, record) => size + record.data.length,
|
||||||
|
@ -1031,7 +1033,6 @@ export class CoreSite {
|
||||||
// Since 3.7, the expiration time contains the time the entry is modified instead of the expiration time.
|
// Since 3.7, the expiration time contains the time the entry is modified instead of the expiration time.
|
||||||
// We decided to reuse this field to prevent modifying the database table.
|
// We decided to reuse this field to prevent modifying the database table.
|
||||||
const id = this.getCacheId(method, data);
|
const id = this.getCacheId(method, data);
|
||||||
const cacheTable = await CoreSites.getCacheTable(this);
|
|
||||||
const entry = {
|
const entry = {
|
||||||
id,
|
id,
|
||||||
data: JSON.stringify(response),
|
data: JSON.stringify(response),
|
||||||
|
@ -1049,7 +1050,7 @@ export class CoreSite {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await cacheTable.insert(entry);
|
await this.cacheTable.insert(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1064,12 +1065,11 @@ export class CoreSite {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
protected async deleteFromCache(method: string, data: any, preSets: CoreSiteWSPreSets, allCacheKey?: boolean): Promise<void> {
|
protected async deleteFromCache(method: string, data: any, preSets: CoreSiteWSPreSets, allCacheKey?: boolean): Promise<void> {
|
||||||
const id = this.getCacheId(method, data);
|
const id = this.getCacheId(method, data);
|
||||||
const cacheTable = await CoreSites.getCacheTable(this);
|
|
||||||
|
|
||||||
if (allCacheKey) {
|
if (allCacheKey) {
|
||||||
await cacheTable.delete({ key: preSets.cacheKey });
|
await this.cacheTable.delete({ key: preSets.cacheKey });
|
||||||
} else {
|
} else {
|
||||||
await cacheTable.deleteByPrimaryKey({ id });
|
await this.cacheTable.deleteByPrimaryKey({ id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1087,13 +1087,12 @@ export class CoreSite {
|
||||||
}
|
}
|
||||||
|
|
||||||
const params = { component };
|
const params = { component };
|
||||||
const cacheTable = await CoreSites.getCacheTable(this);
|
|
||||||
|
|
||||||
if (componentId) {
|
if (componentId) {
|
||||||
params['componentId'] = componentId;
|
params['componentId'] = componentId;
|
||||||
}
|
}
|
||||||
|
|
||||||
await cacheTable.delete(params);
|
await this.cacheTable.delete(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1128,9 +1127,7 @@ export class CoreSite {
|
||||||
this.logger.debug('Invalidate all the cache for site: ' + this.id);
|
this.logger.debug('Invalidate all the cache for site: ' + this.id);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const cacheTable = await CoreSites.getCacheTable(this);
|
await this.cacheTable.update({ expirationTime: 0 });
|
||||||
|
|
||||||
await cacheTable.update({ expirationTime: 0 });
|
|
||||||
} finally {
|
} finally {
|
||||||
CoreEvents.trigger(CoreEvents.WS_CACHE_INVALIDATED, {}, this.getId());
|
CoreEvents.trigger(CoreEvents.WS_CACHE_INVALIDATED, {}, this.getId());
|
||||||
}
|
}
|
||||||
|
@ -1149,9 +1146,7 @@ export class CoreSite {
|
||||||
|
|
||||||
this.logger.debug('Invalidate cache for key: ' + key);
|
this.logger.debug('Invalidate cache for key: ' + key);
|
||||||
|
|
||||||
const cacheTable = await CoreSites.getCacheTable(this);
|
await this.cacheTable.update({ expirationTime: 0 }, { key });
|
||||||
|
|
||||||
await cacheTable.update({ expirationTime: 0 }, { key });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1185,9 +1180,7 @@ export class CoreSite {
|
||||||
|
|
||||||
this.logger.debug('Invalidate cache for key starting with: ' + key);
|
this.logger.debug('Invalidate cache for key starting with: ' + key);
|
||||||
|
|
||||||
const cacheTable = await CoreSites.getCacheTable(this);
|
await this.cacheTable.updateWhere({ expirationTime: 0 }, {
|
||||||
|
|
||||||
await cacheTable.updateWhere({ expirationTime: 0 }, {
|
|
||||||
sql: 'key LIKE ?',
|
sql: 'key LIKE ?',
|
||||||
sqlParams: [key],
|
sqlParams: [key],
|
||||||
js: record => !!record.key?.startsWith(key),
|
js: record => !!record.key?.startsWith(key),
|
||||||
|
@ -1266,9 +1259,7 @@ export class CoreSite {
|
||||||
* @return Promise resolved with the total size of all data in the cache table (bytes)
|
* @return Promise resolved with the total size of all data in the cache table (bytes)
|
||||||
*/
|
*/
|
||||||
async getCacheUsage(): Promise<number> {
|
async getCacheUsage(): Promise<number> {
|
||||||
const cacheTable = await CoreSites.getCacheTable(this);
|
return this.cacheTable.reduce({
|
||||||
|
|
||||||
return cacheTable.reduce({
|
|
||||||
sql: 'SUM(length(data))',
|
sql: 'SUM(length(data))',
|
||||||
js: (size, record) => size + record.data.length,
|
js: (size, record) => size + record.data.length,
|
||||||
jsInitialValue: 0,
|
jsInitialValue: 0,
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { makeSingleton } from '@singletons';
|
||||||
import { CoreConstants } from '../constants';
|
import { CoreConstants } from '../constants';
|
||||||
import { CoreEvents } from '@singletons/events';
|
import { CoreEvents } from '@singletons/events';
|
||||||
import { CoreDatabaseTable } from '@classes/database/database-table';
|
import { CoreDatabaseTable } from '@classes/database/database-table';
|
||||||
import { CorePromisedValue } from '@classes/promised-value';
|
import { asyncInstance } from '../utils/async-instance';
|
||||||
|
|
||||||
declare module '@singletons/events' {
|
declare module '@singletons/events' {
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ export class CoreConfigProvider {
|
||||||
|
|
||||||
static readonly ENVIRONMENT_UPDATED = 'environment_updated';
|
static readonly ENVIRONMENT_UPDATED = 'environment_updated';
|
||||||
|
|
||||||
protected table: CorePromisedValue<CoreDatabaseTable<ConfigDBEntry, 'name'>> = new CorePromisedValue();
|
protected table = asyncInstance<CoreDatabaseTable<ConfigDBEntry, 'name'>>();
|
||||||
protected defaultEnvironment?: EnvironmentConfig;
|
protected defaultEnvironment?: EnvironmentConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -67,7 +67,7 @@ export class CoreConfigProvider {
|
||||||
|
|
||||||
await table.initialize();
|
await table.initialize();
|
||||||
|
|
||||||
this.table.resolve(table);
|
this.table.setInstance(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -77,9 +77,7 @@ export class CoreConfigProvider {
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async delete(name: string): Promise<void> {
|
async delete(name: string): Promise<void> {
|
||||||
const table = await this.table;
|
await this.table.deleteByPrimaryKey({ name });
|
||||||
|
|
||||||
await table.deleteByPrimaryKey({ name });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -91,8 +89,7 @@ export class CoreConfigProvider {
|
||||||
*/
|
*/
|
||||||
async get<T>(name: string, defaultValue?: T): Promise<T> {
|
async get<T>(name: string, defaultValue?: T): Promise<T> {
|
||||||
try {
|
try {
|
||||||
const table = await this.table;
|
const record = await this.table.findByPrimaryKey({ name });
|
||||||
const record = await table.findByPrimaryKey({ name });
|
|
||||||
|
|
||||||
return record.value;
|
return record.value;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -112,9 +109,7 @@ export class CoreConfigProvider {
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async set(name: string, value: number | string): Promise<void> {
|
async set(name: string, value: number | string): Promise<void> {
|
||||||
const table = await this.table;
|
await this.table.insert({ name, value });
|
||||||
|
|
||||||
await table.insert({ name, value });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { CorePromisedValue } from '@classes/promised-value';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a wrapper to hold an asynchronous instance.
|
||||||
|
*
|
||||||
|
* @param lazyConstructor Constructor to use the first time the instance is needed.
|
||||||
|
* @returns Asynchronous instance wrapper.
|
||||||
|
*/
|
||||||
|
function createAsyncInstanceWrapper<T>(lazyConstructor?: () => T | Promise<T>): AsyncInstanceWrapper<T> {
|
||||||
|
let promisedInstance: CorePromisedValue<T> | null = null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
get instance() {
|
||||||
|
return promisedInstance?.value ?? undefined;
|
||||||
|
},
|
||||||
|
async getInstance() {
|
||||||
|
if (!promisedInstance) {
|
||||||
|
promisedInstance = new CorePromisedValue();
|
||||||
|
|
||||||
|
if (lazyConstructor) {
|
||||||
|
const instance = await lazyConstructor();
|
||||||
|
|
||||||
|
promisedInstance.resolve(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return promisedInstance;
|
||||||
|
},
|
||||||
|
async getProperty(property) {
|
||||||
|
const instance = await this.getInstance();
|
||||||
|
|
||||||
|
return instance[property];
|
||||||
|
},
|
||||||
|
setInstance(instance) {
|
||||||
|
if (!promisedInstance) {
|
||||||
|
promisedInstance = new CorePromisedValue();
|
||||||
|
} else if (promisedInstance.isSettled()) {
|
||||||
|
promisedInstance.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
promisedInstance.resolve(instance);
|
||||||
|
},
|
||||||
|
resetInstance() {
|
||||||
|
if (!promisedInstance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
promisedInstance.reset();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronous instance wrapper.
|
||||||
|
*/
|
||||||
|
export interface AsyncInstanceWrapper<T> {
|
||||||
|
instance?: T;
|
||||||
|
getInstance(): Promise<T>;
|
||||||
|
getProperty<P extends keyof T>(property: P): Promise<T[P]>;
|
||||||
|
setInstance(instance: T): void;
|
||||||
|
resetInstance(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronous version of a method.
|
||||||
|
*/
|
||||||
|
export type AsyncMethod<T> =
|
||||||
|
T extends (...args: infer Params) => infer Return
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
? T extends (...args: Params) => Promise<any>
|
||||||
|
? T
|
||||||
|
: (...args: Params) => Promise<Return>
|
||||||
|
: never;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronous instance.
|
||||||
|
*
|
||||||
|
* All methods are converted to their asynchronous version, and properties are available asynchronously using
|
||||||
|
* the getProperty method.
|
||||||
|
*/
|
||||||
|
export type AsyncInstance<T> = AsyncInstanceWrapper<T> & {
|
||||||
|
[k in keyof T]: AsyncMethod<T[k]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an asynchronous instance proxy, where all methods will be callable directly but will become asynchronous. If the
|
||||||
|
* underlying instance hasn't been set, methods will be resolved once it is.
|
||||||
|
*
|
||||||
|
* @param lazyConstructor Constructor to use the first time the instance is needed.
|
||||||
|
* @returns Asynchronous instance.
|
||||||
|
*/
|
||||||
|
export function asyncInstance<T>(lazyConstructor?: () => T | Promise<T>): AsyncInstance<T> {
|
||||||
|
const wrapper = createAsyncInstanceWrapper<T>(lazyConstructor);
|
||||||
|
|
||||||
|
return new Proxy(wrapper, {
|
||||||
|
get: (target, property, receiver) => {
|
||||||
|
if (property in target) {
|
||||||
|
return Reflect.get(target, property, receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
return async (...args: unknown[]) => {
|
||||||
|
const instance = await wrapper.getInstance();
|
||||||
|
|
||||||
|
return instance[property](...args);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}) as AsyncInstance<T>;
|
||||||
|
}
|
Loading…
Reference in New Issue