mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-01-23 02:36:05 +00:00
feat(pfapi): outline single model versions and migrations
This commit is contained in:
parent
98e081eb44
commit
ac6003900b
7 changed files with 122 additions and 24 deletions
|
|
@ -1,10 +1,18 @@
|
|||
import { AllSyncModels, ModelCfgs } from '../pfapi.model';
|
||||
import {
|
||||
AllSyncModels,
|
||||
ModelCfgs,
|
||||
ModelCfgToModelCtrl,
|
||||
ModelVersionMap,
|
||||
} from '../pfapi.model';
|
||||
import { pfLog } from '../util/log';
|
||||
import { ImpossibleError, ModelMigrationError } from '../errors/errors';
|
||||
import { Pfapi } from '../pfapi';
|
||||
|
||||
export class MigrationService<MD extends ModelCfgs> {
|
||||
constructor(private _pfapiMain: Pfapi<MD>) {}
|
||||
constructor(
|
||||
private _pfapiMain: Pfapi<MD>,
|
||||
public m: ModelCfgToModelCtrl<MD>,
|
||||
) {}
|
||||
|
||||
async checkAndMigrateLocalDB(): Promise<void> {
|
||||
const meta = await this._pfapiMain.metaModel.load();
|
||||
|
|
@ -15,6 +23,7 @@ export class MigrationService<MD extends ModelCfgs> {
|
|||
const r = await this.migrate(
|
||||
meta.crossModelVersion,
|
||||
await this._pfapiMain.getAllSyncModelData(true),
|
||||
meta.modelVersions,
|
||||
);
|
||||
if (r.wasMigrated) {
|
||||
const { dataAfter, versionAfter } = r;
|
||||
|
|
@ -45,6 +54,7 @@ export class MigrationService<MD extends ModelCfgs> {
|
|||
async migrate(
|
||||
dataInCrossModelVersion: number,
|
||||
dataIn: AllSyncModels<MD>,
|
||||
modelVersionMap: ModelVersionMap,
|
||||
): Promise<{
|
||||
dataAfter: AllSyncModels<MD>;
|
||||
versionAfter: number;
|
||||
|
|
@ -52,6 +62,8 @@ export class MigrationService<MD extends ModelCfgs> {
|
|||
}> {
|
||||
const cfg = this._pfapiMain.cfg;
|
||||
const codeModelVersion = cfg?.crossModelVersion;
|
||||
// const singleModelsToMigrate = Object.keys(modelVersionMap).filter();
|
||||
|
||||
if (
|
||||
typeof codeModelVersion !== 'number' ||
|
||||
dataInCrossModelVersion === codeModelVersion
|
||||
|
|
@ -106,6 +118,7 @@ export class MigrationService<MD extends ModelCfgs> {
|
|||
migrationsToRun.forEach((migrateFn) => {
|
||||
migratedData = migrateFn(migratedData);
|
||||
});
|
||||
|
||||
return {
|
||||
dataAfter: migratedData,
|
||||
versionAfter: codeModelVersion,
|
||||
|
|
@ -115,12 +128,25 @@ export class MigrationService<MD extends ModelCfgs> {
|
|||
pfLog(0, `Migration functions failed to execute`, { error });
|
||||
throw new ModelMigrationError('Error running migration functions', error);
|
||||
}
|
||||
|
||||
// TODO single model migration
|
||||
// const modelIds = Object.keys(this.m);
|
||||
// for (const modelId of modelIds) {
|
||||
// const modelCtrl = this.m[modelId];
|
||||
//
|
||||
// }
|
||||
}
|
||||
|
||||
// private _getSingleModelIdsToMigrate(modelVersionMap: ModelVersionMap): string[] {
|
||||
// return Object.keys(modelVersionMap).filter((modelId) => {
|
||||
// const modelCtrl = this.m[modelId];
|
||||
// // if (!modelCtrl) {
|
||||
// // throw new ImpossibleError(`Model controller not found for ${modelId}`);
|
||||
// // }
|
||||
// return modelCtrl.modelCfg.modelVersion > modelVersionMap[modelId];
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// private _migrateSingleModels(migratedData: any) {
|
||||
// const modelIds = Object.keys(this.m);
|
||||
// for (const modelId of modelIds) {
|
||||
// const modelCtrl = this.m[modelId];
|
||||
// // TODO fix
|
||||
// // @ts-ignore
|
||||
// migratedData[modelId] = modelCtrl.migrate(migratedData, modelVersionMap[modelId]);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,10 +39,19 @@ export class ModelCtrl<MT extends ModelBase> {
|
|||
save(
|
||||
data: MT,
|
||||
p?: { isUpdateRevAndLastUpdate: boolean; isIgnoreDBLock?: boolean },
|
||||
sourceModelVersion?: number,
|
||||
): Promise<unknown> {
|
||||
this._inMemoryData = data;
|
||||
pfLog(2, `${ModelCtrl.name}.${this.save.name}()`, this.modelId, p, data);
|
||||
|
||||
if (typeof sourceModelVersion === 'string') {
|
||||
sourceModelVersion = +sourceModelVersion;
|
||||
}
|
||||
if (typeof sourceModelVersion === 'number') {
|
||||
console.log(sourceModelVersion);
|
||||
data = this.migrate(data, sourceModelVersion);
|
||||
}
|
||||
|
||||
// Validate data if validator is available
|
||||
if (this.modelCfg.validate && !this.modelCfg.validate(data).success) {
|
||||
if (this.modelCfg.repair) {
|
||||
|
|
@ -67,6 +76,32 @@ export class ModelCtrl<MT extends ModelBase> {
|
|||
return this._db.save(this.modelId, data, isIgnoreDBLock);
|
||||
}
|
||||
|
||||
// TODO
|
||||
/**
|
||||
* Saves the model data to database
|
||||
* @param data Model data to save
|
||||
* @returns migrated data
|
||||
*/
|
||||
migrate<T>(data: MT | T, sourceModelVersion: number): MT {
|
||||
if (
|
||||
typeof this.modelCfg.migrations === 'object' &&
|
||||
this.modelCfg.migrations !== null
|
||||
) {
|
||||
Object.keys(this.modelCfg.migrations)
|
||||
.sort()
|
||||
.forEach((key) => {
|
||||
const version = +key;
|
||||
console.log(version);
|
||||
|
||||
if (version > sourceModelVersion) {
|
||||
alert('MIGRATE');
|
||||
console.log('migration script version', version);
|
||||
data = this.modelCfg.migrations;
|
||||
}
|
||||
});
|
||||
}
|
||||
return data as MT;
|
||||
}
|
||||
/**
|
||||
* Updates part of the model data
|
||||
* @param data Partial data to update
|
||||
|
|
|
|||
|
|
@ -21,18 +21,19 @@ type SerializableArray = Array<Serializable>;
|
|||
|
||||
export type ModelBase = SerializableObject | SerializableArray | unknown;
|
||||
|
||||
export type ModelMigrateFn<T> = <F>(modelData: F | T) => T;
|
||||
export interface ModelMigrations<T> {
|
||||
[version: number]: ModelMigrateFn<T>;
|
||||
}
|
||||
|
||||
export interface ModelCfg<T extends ModelBase> {
|
||||
modelVersion: number;
|
||||
isLocalOnly?: boolean;
|
||||
// migrations?: {
|
||||
// [version: string]: (arg: T) => T;
|
||||
// };
|
||||
// TODO fix typing
|
||||
// migrations?: Record<string, (arg: T) => T>;
|
||||
isAlwaysReApplyOldMigrations?: boolean;
|
||||
debounceDbWrite?: number;
|
||||
isMainFileModel?: boolean;
|
||||
|
||||
migrations?: ModelMigrations<T>;
|
||||
validate?: <R>(data: R | T) => IValidation<R | T>;
|
||||
repair?: <R>(data: R | unknown | any) => T;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
ModelBase,
|
||||
ModelCfgs,
|
||||
ModelCfgToModelCtrl,
|
||||
ModelVersionMap,
|
||||
PfapiBaseCfg,
|
||||
PrivateCfgByProviderId,
|
||||
} from './pfapi.model';
|
||||
|
|
@ -96,7 +97,7 @@ export class Pfapi<const MD extends ModelCfgs> {
|
|||
sp.privateCfg = new SyncProviderPrivateCfgStore(sp.id, this.db, this.ev);
|
||||
});
|
||||
|
||||
this.migrationService = new MigrationService<MD>(this);
|
||||
this.migrationService = new MigrationService<MD>(this, this.m);
|
||||
|
||||
this._syncService = new SyncService<MD>(
|
||||
this.m,
|
||||
|
|
@ -267,6 +268,7 @@ export class Pfapi<const MD extends ModelCfgs> {
|
|||
return await this.importAllSycModelData({
|
||||
data: backup.data,
|
||||
crossModelVersion: backup.crossModelVersion,
|
||||
modelVersionMap: backup.modelVersions,
|
||||
// TODO maybe also make model versions work
|
||||
isBackupData: true,
|
||||
isAttemptRepair: true,
|
||||
|
|
@ -277,6 +279,7 @@ export class Pfapi<const MD extends ModelCfgs> {
|
|||
async importAllSycModelData({
|
||||
data,
|
||||
crossModelVersion,
|
||||
modelVersionMap = {},
|
||||
isAttemptRepair = false,
|
||||
isBackupData = false,
|
||||
isSkipLegacyWarnings = false,
|
||||
|
|
@ -284,6 +287,7 @@ export class Pfapi<const MD extends ModelCfgs> {
|
|||
}: {
|
||||
data: AllSyncModels<MD>;
|
||||
crossModelVersion: number;
|
||||
modelVersionMap?: ModelVersionMap;
|
||||
isAttemptRepair?: boolean;
|
||||
isBackupData?: boolean;
|
||||
isSkipLegacyWarnings?: boolean;
|
||||
|
|
@ -291,7 +295,11 @@ export class Pfapi<const MD extends ModelCfgs> {
|
|||
}): Promise<void> {
|
||||
pfLog(2, `${this.importAllSycModelData.name}()`, { data, cfg: this.cfg });
|
||||
|
||||
const { dataAfter } = await this.migrationService.migrate(crossModelVersion, data);
|
||||
const { dataAfter } = await this.migrationService.migrate(
|
||||
crossModelVersion,
|
||||
data,
|
||||
modelVersionMap,
|
||||
);
|
||||
data = dataAfter;
|
||||
|
||||
if (this.cfg?.validate) {
|
||||
|
|
@ -345,10 +353,14 @@ export class Pfapi<const MD extends ModelCfgs> {
|
|||
throw new ModelIdWithoutCtrlError(modelId, modelData);
|
||||
}
|
||||
|
||||
return modelCtrl.save(modelData, {
|
||||
isUpdateRevAndLastUpdate: false,
|
||||
isIgnoreDBLock: true,
|
||||
});
|
||||
return modelCtrl.save(
|
||||
modelData,
|
||||
{
|
||||
isUpdateRevAndLastUpdate: false,
|
||||
isIgnoreDBLock: true,
|
||||
},
|
||||
modelVersionMap[modelId],
|
||||
);
|
||||
});
|
||||
await Promise.all(promises);
|
||||
this.db.unlock();
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {
|
|||
MainModelData,
|
||||
ModelCfgs,
|
||||
ModelCfgToModelCtrl,
|
||||
ModelVersionMap,
|
||||
RemoteMeta,
|
||||
RevMap,
|
||||
} from '../pfapi.model';
|
||||
|
|
@ -155,18 +156,24 @@ export class ModelSyncService<MD extends ModelCfgs> {
|
|||
*
|
||||
* @param toUpdate - Array of model IDs to update
|
||||
* @param toDelete - Array of model IDs to delete
|
||||
* @param modelVersionMap - Map of the model versions
|
||||
* @param dataMap - Map of model data indexed by model ID
|
||||
* @returns Promise resolving once all operations are complete
|
||||
*/
|
||||
async updateLocalUpdated(
|
||||
toUpdate: string[],
|
||||
toDelete: string[],
|
||||
modelVersionMap: ModelVersionMap,
|
||||
dataMap: { [key: string]: unknown },
|
||||
): Promise<unknown> {
|
||||
return await Promise.all([
|
||||
...toUpdate.map((modelId) =>
|
||||
// NOTE: needs to be cast to a generic type, since dataMap is a generic object
|
||||
this._updateLocal(modelId, dataMap[modelId] as ExtractModelCfgType<MD[string]>),
|
||||
this._updateLocal(
|
||||
modelId,
|
||||
dataMap[modelId] as ExtractModelCfgType<MD[string]>,
|
||||
modelVersionMap[modelId],
|
||||
),
|
||||
),
|
||||
...toDelete.map((modelId) => this._removeLocal(modelId)),
|
||||
]);
|
||||
|
|
@ -179,6 +186,7 @@ export class ModelSyncService<MD extends ModelCfgs> {
|
|||
*/
|
||||
async updateLocalMainModelsFromRemoteMetaFile(remote: RemoteMeta): Promise<void> {
|
||||
const mainModelData = remote.mainModelData;
|
||||
|
||||
if (typeof mainModelData === 'object' && mainModelData !== null) {
|
||||
pfLog(
|
||||
2,
|
||||
|
|
@ -193,6 +201,7 @@ export class ModelSyncService<MD extends ModelCfgs> {
|
|||
{
|
||||
isUpdateRevAndLastUpdate: false,
|
||||
},
|
||||
remote.modelVersions[modelId],
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
@ -275,13 +284,15 @@ export class ModelSyncService<MD extends ModelCfgs> {
|
|||
*
|
||||
* @param modelId - The ID of the model to update
|
||||
* @param modelData - The data to update the model with
|
||||
* @param sourceModelVersion
|
||||
* @private
|
||||
*/
|
||||
private async _updateLocal<T extends keyof MD>(
|
||||
modelId: T,
|
||||
modelData: ExtractModelCfgType<MD[T]>,
|
||||
sourceModelVersion?: number,
|
||||
): Promise<void> {
|
||||
await this.m[modelId].save(modelData);
|
||||
await this.m[modelId].save(modelData, undefined, sourceModelVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -377,7 +377,12 @@ export class SyncService<const MD extends ModelCfgs> {
|
|||
isSkipLegacyWarnings: false,
|
||||
});
|
||||
} else {
|
||||
await this._modelSyncService.updateLocalUpdated(toUpdate, toDelete, dataMap);
|
||||
await this._modelSyncService.updateLocalUpdated(
|
||||
toUpdate,
|
||||
toDelete,
|
||||
remote.modelVersions,
|
||||
dataMap,
|
||||
);
|
||||
await this._modelSyncService.updateLocalMainModelsFromRemoteMetaFile(remote);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -100,10 +100,18 @@ export const PFAPI_MODEL_CFGS: PfapiAllModelCfg = {
|
|||
},
|
||||
|
||||
project: {
|
||||
modelVersion: 1.2,
|
||||
modelVersion: 1.4,
|
||||
defaultData: initialProjectState,
|
||||
isMainFileModel: true,
|
||||
validate: appDataValidators.project,
|
||||
migrations: {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
1.4: (data) => {
|
||||
alert('Migrating project data to new version');
|
||||
throw new Error('aaa');
|
||||
return data as ProjectState;
|
||||
},
|
||||
},
|
||||
},
|
||||
tag: {
|
||||
modelVersion: 1,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue