super-productivity/src/app/pfapi/api/model-ctrl/model-ctrl.ts
Johannes Millan de3d1106bd refactor: use PFLog for all logging in pfapi directory
- Replaced all Log.* calls with PFLog.* in pfapi directory
- Modified 24 files with 106 total changes
- All pfapi-related logs now have [pf] context prefix

This ensures consistent context-aware logging for all persistence framework
operations, making it easier to filter and debug data-related issues.
2025-07-10 14:35:56 +02:00

130 lines
3.6 KiB
TypeScript

import { ModelBase, ModelCfg } from '../pfapi.model';
import { Database } from '../db/database';
import { MetaModelCtrl } from './meta-model-ctrl';
import { SyncLog } from '../../../core/log';
import { ModelValidationError } from '../errors/errors';
import { PFLog } from '../../../core/log';
// type ExtractModelType<T extends ModelCfg<unknown>> = T extends ModelCfg<infer U> ? U : never;
/**
* Controller for handling model data operations
* @template MT - Model type that extends ModelBase
*/
export class ModelCtrl<MT extends ModelBase> {
private static readonly L = 'ModelCtrl';
public readonly modelId: string;
public readonly modelCfg: ModelCfg<MT>;
private _inMemoryData: MT | null = null;
private _db: Database;
private _metaModel: MetaModelCtrl;
constructor(
modelId: string,
modelCfg: ModelCfg<MT>,
db: Database,
metaModel: MetaModelCtrl,
) {
this.modelCfg = modelCfg;
this._metaModel = metaModel;
this._db = db;
this.modelId = modelId;
}
/**
* Saves the model data to database
* @param data Model data to save
* @param p
* @returns Promise resolving after save operation
*/
async save(
data: MT,
p?: { isUpdateRevAndLastUpdate: boolean; isIgnoreDBLock?: boolean },
): Promise<unknown> {
this._inMemoryData = data;
SyncLog.normal(`___ ${ModelCtrl.L}.${this.save.name}()`, this.modelId, p, data);
// Validate data if validator is available
if (this.modelCfg.validate) {
const validationResult = this.modelCfg.validate(data);
if (!validationResult.success) {
if (this.modelCfg.repair) {
try {
data = this.modelCfg.repair(data);
} catch (e) {
PFLog.err(e);
throw new ModelValidationError({
id: this.modelId,
data,
validationResult,
e,
});
}
} else {
throw new ModelValidationError({ id: this.modelId, data, validationResult });
}
}
}
// Update revision if requested
const isIgnoreDBLock = !!p?.isIgnoreDBLock;
if (p?.isUpdateRevAndLastUpdate) {
await this._metaModel.updateRevForModel(
this.modelId,
this.modelCfg,
isIgnoreDBLock,
);
}
// Save data to database
return this._db.save(this.modelId, data, isIgnoreDBLock);
}
/**
* Updates part of the model data
* @param data Partial data to update
* @returns Promise resolving after update operation
*/
async partialUpdate(data: Partial<MT>): Promise<unknown> {
if (typeof data !== 'object' || data === null) {
throw new Error(`${ModelCtrl.L}:${this.modelId}: data is not an object`);
}
// Load current data and merge with partial update
const currentData = (await this.load()) || {};
const newData = {
...currentData,
...data,
} as MT;
return this.save(newData);
}
// TODO implement isSkipMigration
/**
* Loads model data from memory cache or database
* @returns Promise resolving to model data
*/
async load(): Promise<MT> {
SyncLog.verbose(`${ModelCtrl.L}.${this.load.name}()`, {
inMemoryData: this._inMemoryData,
});
return (
this._inMemoryData ||
((await this._db.load(this.modelId)) as Promise<MT>) ||
(this.modelCfg.defaultData as MT)
);
}
/**
* Deletes model data from database
* @returns Promise resolving after remove operation
*/
async remove(): Promise<unknown> {
SyncLog.normal(`${ModelCtrl.L}.${this.remove.name}()`, this.modelId);
this._inMemoryData = null;
return this._db.remove(this.modelId);
}
}