refactor: integrate pfapi into oplog 3

This commit is contained in:
Johannes Millan 2026-01-07 14:10:05 +01:00
parent db990b7018
commit 6017da13c7
53 changed files with 129 additions and 182 deletions

View file

@ -8,7 +8,7 @@ import { JiraCfg } from '../src/app/features/issue/providers/jira/jira.model';
import { AppDataCompleteLegacy, SyncGetRevResult } from '../src/app/imex/sync/sync.model';
import { Task } from '../src/app/features/tasks/task.model';
import { LocalBackupMeta } from '../src/app/imex/local-backup/local-backup.model';
import { AppDataComplete } from '../src/app/sync/model-config';
import { AppDataComplete } from '../src/app/op-log/model/model-config';
import {
PluginNodeScriptRequest,
PluginNodeScriptResult,

View file

@ -2,10 +2,10 @@ import { inject, Injectable } from '@angular/core';
import { AppDataCompleteLegacy } from '../../imex/sync/sync.model';
import { T } from '../../t.const';
import { TranslateService } from '@ngx-translate/core';
import { isDataRepairPossible } from '../../sync/validation/is-data-repair-possible.util';
import { getLastValidityError } from '../../sync/validation/is-related-model-data-valid';
import { isDataRepairPossible } from '../../op-log/validation/is-data-repair-possible.util';
import { getLastValidityError } from '../../op-log/validation/is-related-model-data-valid';
import { IS_ELECTRON } from '../../app.constants';
import { AppDataComplete } from '../../sync/model-config';
import { AppDataComplete } from '../../op-log/model/model-config';
import { Log } from '../log';
@Injectable({

View file

@ -4,7 +4,7 @@ import { ConfigFormSection, SyncConfig } from '../global-config.model';
import { LegacySyncProvider } from '../../../imex/sync/legacy-sync-provider.model';
import { IS_ANDROID_WEB_VIEW } from '../../../util/is-android-web-view';
import { IS_ELECTRON } from '../../../app.constants';
import { fileSyncDroid, fileSyncElectron } from '../../../sync/model-config';
import { fileSyncDroid, fileSyncElectron } from '../../../op-log/model/model-config';
import { FormlyFieldConfig } from '@ngx-formly/core';
/**

View file

@ -2,7 +2,7 @@ import { globalConfigReducer, initialGlobalConfigState } from './global-config.r
import { loadAllData } from '../../../root-store/meta/load-all-data.action';
import { GlobalConfigState } from '../global-config.model';
import { LegacySyncProvider } from '../../../imex/sync/legacy-sync-provider.model';
import { AppDataComplete } from '../../../sync/model-config';
import { AppDataComplete } from '../../../op-log/model/model-config';
describe('GlobalConfigReducer', () => {
describe('loadAllData action', () => {

View file

@ -6,7 +6,7 @@ import {
syncTimeTracking,
} from './time-tracking.actions';
import { loadAllData } from '../../../root-store/meta/load-all-data.action';
import { AppDataComplete } from '../../../sync/model-config';
import { AppDataComplete } from '../../../op-log/model/model-config';
import { TaskCopy } from '../../tasks/task.model';
import { WorkContextType } from '../../work-context/work-context.model';

View file

@ -6,7 +6,7 @@ import {
import { createFeature, createReducer, on } from '@ngrx/store';
import { TimeTrackingState } from '../time-tracking.model';
import { loadAllData } from '../../../root-store/meta/load-all-data.action';
import { AppDataComplete } from '../../../sync/model-config';
import { AppDataComplete } from '../../../op-log/model/model-config';
import { roundTsToMinutes } from '../../../util/round-ts-to-minutes';
import { TODAY_TAG } from '../../tag/tag.const';

View file

@ -19,7 +19,7 @@ import { MatIcon } from '@angular/material/icon';
import { MatButton } from '@angular/material/button';
import { MatTooltip } from '@angular/material/tooltip';
import { TranslatePipe } from '@ngx-translate/core';
import { AppDataComplete } from '../../sync/model-config';
import { AppDataComplete } from '../../op-log/model/model-config';
import { BackupService } from '../../sync/backup.service';
import { IS_ANDROID_WEB_VIEW } from '../../util/is-android-web-view';
import { first } from 'rxjs/operators';

View file

@ -11,7 +11,7 @@ import { StateSnapshotService } from '../../sync/state-snapshot.service';
import { BackupService } from '../../sync/backup.service';
import { T } from '../../t.const';
import { TranslateService } from '@ngx-translate/core';
import { AppDataComplete } from '../../sync/model-config';
import { AppDataComplete } from '../../op-log/model/model-config';
import { SnackService } from '../../core/snack/snack.service';
import { Log } from '../../core/log';

View file

@ -3,7 +3,7 @@ import { SnackService } from '../../core/snack/snack.service';
import { SyncProviderId } from '../../sync/providers/provider.const';
import { SuperSyncProvider } from '../../sync/providers/super-sync/super-sync';
import { RestoreCapable, RestorePoint } from '../../sync/providers/provider.interface';
import { AppDataComplete } from '../../sync/model-config';
import { AppDataComplete } from '../../op-log/model/model-config';
import { T } from '../../t.const';
import { SyncProviderManager } from '../../sync/provider-manager.service';
import { BackupService } from '../../sync/backup.service';

View file

@ -73,7 +73,7 @@ export const isArchiveAffectingAction = (action: Action): action is PersistentAc
*
* ## Why This Architecture
*
* Archive data is stored in IndexedDB (via PFAPI), not in NgRx state. Previously,
* Archive data is stored in IndexedDB, not in NgRx state. Previously,
* archive operations were duplicated across multiple effect files. This handler
* consolidates all archive logic to:
*
@ -108,15 +108,13 @@ export class ArchiveOperationHandler {
//
// Some services use lazyInject() to break circular dependency chains:
// DataInitService -> OperationLogHydratorService -> OperationApplierService
// -> ArchiveOperationHandler -> ArchiveService/TaskArchiveService -> PfapiService
// DataInitService also injects PfapiService directly, causing the cycle.
// -> ArchiveOperationHandler -> ArchiveService/TaskArchiveService
//
// ArchiveDbAdapter is used for direct IndexedDB access to break the PfapiService
// dependency for archive operations (_handleFlushYoungToOld, _handleLoadAllData).
// This avoids the circular dependency while providing the same functionality.
// ArchiveDbAdapter is used for direct IndexedDB access for archive operations
// (_handleFlushYoungToOld, _handleLoadAllData) to avoid potential circular deps.
//
// Other services still use lazyInject because they have their own complex
// dependency chains through PfapiService that would require deeper refactoring.
// dependency chains that would require deeper refactoring.
// ═══════════════════════════════════════════════════════════════════════════
private _injector = inject(Injector);
private _archiveDbAdapter = inject(ArchiveDbAdapter);
@ -366,7 +364,7 @@ export class ArchiveOperationHandler {
* This operation is deterministic - given the same timestamp, it produces
* the same result on all clients.
*
* Uses ArchiveDbAdapter which directly accesses IndexedDB without PFAPI's lock.
* Uses ArchiveDbAdapter which directly accesses IndexedDB.
*/
private async _handleCompressArchive(action: PersistentAction): Promise<void> {
const { oneYearAgoTimestamp } = action as ReturnType<typeof compressArchive>;
@ -473,12 +471,12 @@ export class ArchiveOperationHandler {
* Fixes bug where SYNC_IMPORT updated NgRx state but never persisted archive
* data to IndexedDB on remote client, causing data loss on restart.
*
* @localBehavior SKIP - Archive written by PfapiService._updateModelCtrlCaches()
* @remoteBehavior Executes - Uses ArchiveDbAdapter for direct IndexedDB access (bypasses PfapiService)
* @localBehavior SKIP - Archive written by local backup import flow
* @remoteBehavior Executes - Uses ArchiveDbAdapter for direct IndexedDB access
*/
private async _handleLoadAllData(action: PersistentAction): Promise<void> {
if (!action.meta?.isRemote) {
return; // Local: already written by PfapiService._updateModelCtrlCaches
return; // Local: already written by local backup import flow
}
const loadAllDataAction = action as unknown as ReturnType<typeof loadAllData>;

View file

@ -11,8 +11,8 @@ import { InjectionToken } from '@angular/core';
* - SYNC_WAIT_TIMEOUT_MS (40s) - Max wait for ongoing sync to complete
* - INITIAL_SYNC_DELAY_MS (500ms) - Delay before triggering initial sync
*
* **Meta Sync Lock** (`src/app/pfapi/api/sync/meta-sync.service.ts`):
* - LOCK_TTL_MS (5 min) - Lock TTL for distributed lock on remote metadata
* **Meta Sync Lock** (`src/app/sync/providers/webdav/webdav.ts`):
* - LOCK_TTL_MS (5 min) - Lock TTL for distributed lock on remote metadata (WebDAV)
*/
/**

View file

@ -1,5 +1,5 @@
import { PFLog } from '../../core/log';
import { DecompressError } from '../errors/sync-errors';
import { DecompressError } from '../../sync/errors/sync-errors';
import {
compressWithGzip,
compressWithGzipToString,

View file

@ -1,4 +1,4 @@
import { CompressError, DecompressError } from '../errors/sync-errors';
import { CompressError, DecompressError } from '../../sync/errors/sync-errors';
import { PFLog } from '../../core/log';
/**

View file

@ -1,5 +1,5 @@
import { PFLog } from '../../core/log';
import { JsonParseError } from '../errors/sync-errors';
import { JsonParseError } from '../../sync/errors/sync-errors';
import { EncryptAndCompressHandlerService } from './encrypt-and-compress-handler.service';
import { getErrorTxt } from '../../util/get-error-text';

View file

@ -1,16 +1,19 @@
import { extractSyncFileStateFromPrefix, getSyncFilePrefix } from './sync-file-prefix';
import {
extractSyncFileStateFromPrefix,
getSyncFilePrefix,
} from '../../sync/util/sync-file-prefix';
import { PFLog } from '../../core/log';
import { decrypt, encrypt } from './encryption';
import {
DecryptError,
DecryptNoPasswordError,
JsonParseError,
} from '../errors/sync-errors';
} from '../../sync/errors/sync-errors';
import {
compressWithGzipToString,
decompressGzipFromString,
} from './compression-handler';
import { EncryptAndCompressCfg } from '../sync.types';
import { EncryptAndCompressCfg } from '../../sync/sync.types';
export class EncryptAndCompressHandlerService {
private static readonly L = 'EncryptAndCompressHandlerService';

View file

@ -1,57 +1,60 @@
import { AllModelData, ModelCfg, ModelCfgs } from './sync.types';
import { DataRepairNotPossibleError } from './errors/sync-errors';
import { Dropbox } from './providers/dropbox/dropbox';
import { ProjectState } from '../features/project/project.model';
import { MenuTreeState } from '../features/menu-tree/store/menu-tree.model';
import { GlobalConfigState } from '../features/config/global-config.model';
import { Reminder } from '../features/reminder/reminder.model';
import { AllModelData, ModelCfg, ModelCfgs } from '../../sync/sync.types';
import { DataRepairNotPossibleError } from '../../sync/errors/sync-errors';
import { Dropbox } from '../../sync/providers/dropbox/dropbox';
import { ProjectState } from '../../features/project/project.model';
import { MenuTreeState } from '../../features/menu-tree/store/menu-tree.model';
import { GlobalConfigState } from '../../features/config/global-config.model';
import { Reminder } from '../../features/reminder/reminder.model';
import {
plannerInitialState,
PlannerState,
} from '../features/planner/store/planner.reducer';
import { BoardsState, initialBoardsState } from '../features/boards/store/boards.reducer';
import { NoteState } from '../features/note/note.model';
import { IssueProviderState } from '../features/issue/issue.model';
import { MetricState } from '../features/metric/metric.model';
import { TaskState } from '../features/tasks/task.model';
import { TagState } from '../features/tag/tag.model';
import { SimpleCounterState } from '../features/simple-counter/simple-counter.model';
import { TaskRepeatCfgState } from '../features/task-repeat-cfg/task-repeat-cfg.model';
import { initialProjectState } from '../features/project/store/project.reducer';
import { DEFAULT_GLOBAL_CONFIG } from '../features/config/default-global-config.const';
import { initialNoteState } from '../features/note/store/note.reducer';
import { issueProviderInitialState } from '../features/issue/store/issue-provider.reducer';
import { initialMetricState } from '../features/metric/store/metric.reducer';
import { initialTaskState } from '../features/tasks/store/task.reducer';
import { initialTagState } from '../features/tag/store/tag.reducer';
import { initialSimpleCounterState } from '../features/simple-counter/store/simple-counter.reducer';
import { initialTaskRepeatCfgState } from '../features/task-repeat-cfg/store/task-repeat-cfg.reducer';
import { DROPBOX_APP_KEY } from '../imex/sync/dropbox/dropbox.const';
import { Webdav } from './providers/webdav/webdav';
import { SuperSyncProvider } from './providers/super-sync/super-sync';
import { isDataRepairPossible } from './validation/is-data-repair-possible.util';
import { dataRepair } from './validation/data-repair';
import { LocalFileSyncElectron } from './providers/local-file/local-file-sync-electron';
import { IS_ELECTRON } from '../app.constants';
import { IS_ANDROID_WEB_VIEW } from '../util/is-android-web-view';
import { LocalFileSyncAndroid } from './providers/local-file/local-file-sync-android';
import { environment } from '../../environments/environment';
} from '../../features/planner/store/planner.reducer';
import {
BoardsState,
initialBoardsState,
} from '../../features/boards/store/boards.reducer';
import { NoteState } from '../../features/note/note.model';
import { IssueProviderState } from '../../features/issue/issue.model';
import { MetricState } from '../../features/metric/metric.model';
import { TaskState } from '../../features/tasks/task.model';
import { TagState } from '../../features/tag/tag.model';
import { SimpleCounterState } from '../../features/simple-counter/simple-counter.model';
import { TaskRepeatCfgState } from '../../features/task-repeat-cfg/task-repeat-cfg.model';
import { initialProjectState } from '../../features/project/store/project.reducer';
import { DEFAULT_GLOBAL_CONFIG } from '../../features/config/default-global-config.const';
import { initialNoteState } from '../../features/note/store/note.reducer';
import { issueProviderInitialState } from '../../features/issue/store/issue-provider.reducer';
import { initialMetricState } from '../../features/metric/store/metric.reducer';
import { initialTaskState } from '../../features/tasks/store/task.reducer';
import { initialTagState } from '../../features/tag/store/tag.reducer';
import { initialSimpleCounterState } from '../../features/simple-counter/store/simple-counter.reducer';
import { initialTaskRepeatCfgState } from '../../features/task-repeat-cfg/store/task-repeat-cfg.reducer';
import { DROPBOX_APP_KEY } from '../../imex/sync/dropbox/dropbox.const';
import { Webdav } from '../../sync/providers/webdav/webdav';
import { SuperSyncProvider } from '../../sync/providers/super-sync/super-sync';
import { isDataRepairPossible } from '../validation/is-data-repair-possible.util';
import { dataRepair } from '../validation/data-repair';
import { LocalFileSyncElectron } from '../../sync/providers/local-file/local-file-sync-electron';
import { IS_ELECTRON } from '../../app.constants';
import { IS_ANDROID_WEB_VIEW } from '../../util/is-android-web-view';
import { LocalFileSyncAndroid } from '../../sync/providers/local-file/local-file-sync-android';
import { environment } from '../../../environments/environment';
import {
ArchiveModel,
TimeTrackingState,
} from '../features/time-tracking/time-tracking.model';
import { initialTimeTrackingState } from '../features/time-tracking/store/time-tracking.reducer';
import { appDataValidators, validateFull } from './validation/validation-fn';
import { fixEntityStateConsistency } from '../util/check-fix-entity-state-consistency';
} from '../../features/time-tracking/time-tracking.model';
import { initialTimeTrackingState } from '../../features/time-tracking/store/time-tracking.reducer';
import { appDataValidators, validateFull } from '../validation/validation-fn';
import { fixEntityStateConsistency } from '../../util/check-fix-entity-state-consistency';
import { IValidation } from 'typia';
import { PFLog } from '../core/log';
import { PFLog } from '../../core/log';
import {
initialPluginMetaDataState,
initialPluginUserDataState,
PluginMetaDataState,
PluginUserDataState,
} from '../plugins/plugin-persistence.model';
import { menuTreeInitialState } from '../features/menu-tree/store/menu-tree.reducer';
} from '../../plugins/plugin-persistence.model';
import { menuTreeInitialState } from '../../features/menu-tree/store/menu-tree.reducer';
export const CROSS_MODEL_VERSION = 4.5 as const;

View file

@ -11,7 +11,7 @@ import {
import { CURRENT_SCHEMA_VERSION } from './schema-migration.service';
import { OperationLogEntry } from '../core/operation.types';
import { OpLog } from '../../core/log';
import { MODEL_CONFIGS } from '../../sync/model-config';
import { MODEL_CONFIGS } from '../model/model-config';
const MS_PER_DAY = 24 * 60 * 60 * 1000;

View file

@ -67,9 +67,8 @@ export class OperationLogHydratorService {
const [, , hasBackup] = await Promise.all([
// Check for pending remote ops from crashed sync (touches 'ops' store)
this.recoveryService.recoverPendingRemoteOps(),
// Migrate vector clock from pf.META_MODEL to SUP_OPS.vector_clock if needed
// (touches 'vector_clock' store). One-time migration for DB version 1 to 2.
this._migrateVectorClockFromPfapiIfNeeded(),
// Legacy migration placeholder - kept for future DB migrations if needed
this._runLegacyMigrationIfNeeded(),
// A.7.12: Check for interrupted migration (touches 'state_cache' store)
this.opLogStore.hasStateCacheBackup(),
]);
@ -330,9 +329,8 @@ export class OperationLogHydratorService {
OpLog.normal('OperationLogHydratorService: Full replay complete.');
}
// Sync PFAPI vector clock with SUP_OPS to ensure consistency
// This recovers from any failed PFAPI updates during previous operations
await this._syncPfapiVectorClock();
// Legacy cleanup placeholder - kept for future maintenance operations if needed
await this._runLegacyCleanupIfNeeded();
// Retry any failed remote ops from previous conflict resolution attempts
// Now that state is fully hydrated, dependencies might be resolved
@ -518,11 +516,11 @@ export class OperationLogHydratorService {
}
/**
* Legacy method - previously synced vector clock to PFAPI meta model.
* Now a no-op since PFAPI layer was removed.
* Legacy cleanup placeholder.
* Kept for future maintenance operations if needed.
*/
private async _syncPfapiVectorClock(): Promise<void> {
// No-op: PFAPI layer was removed, vector clock is managed by SUP_OPS only
private async _runLegacyCleanupIfNeeded(): Promise<void> {
// No-op: placeholder for future cleanup operations
}
/**
@ -583,10 +581,10 @@ export class OperationLogHydratorService {
}
/**
* Legacy method - previously migrated vector clock from PFAPI meta model.
* Now a no-op since PFAPI layer was removed.
* Legacy migration placeholder.
* Kept for future DB migrations if needed.
*/
private async _migrateVectorClockFromPfapiIfNeeded(): Promise<void> {
// No-op: PFAPI layer was removed, no legacy migration needed
private async _runLegacyMigrationIfNeeded(): Promise<void> {
// No-op: placeholder for future migrations
}
}

View file

@ -12,13 +12,13 @@ import {
} from './dialog-legacy-migration/dialog-legacy-migration.component';
import { loadAllData } from '../../root-store/meta/load-all-data.action';
import { download } from '../../util/download';
import { validateFull } from '../../sync/validation/validation-fn';
import { isDataRepairPossible } from '../../sync/validation/is-data-repair-possible.util';
import { dataRepair } from '../../sync/validation/data-repair';
import { validateFull } from '../validation/validation-fn';
import { isDataRepairPossible } from '../validation/is-data-repair-possible.util';
import { dataRepair } from '../validation/data-repair';
import { uuidv7 } from '../../util/uuid-v7';
import { ActionType, Operation, OpType } from '../core/operation.types';
import { CURRENT_SCHEMA_VERSION } from './schema-migration.service';
import { AppDataComplete } from '../../sync/model-config';
import { AppDataComplete } from '../model/model-config';
/**
* Service to check for valid operation log state during startup and migrate

View file

@ -9,7 +9,7 @@ import { Operation, OpType, ActionType } from '../core/operation.types';
import { uuidv7 } from '../../util/uuid-v7';
import { PENDING_OPERATION_EXPIRY_MS } from '../core/operation-log.const';
import { OpLog } from '../../core/log';
import { AppDataComplete } from '../../sync/model-config';
import { AppDataComplete } from '../model/model-config';
/**
* Handles crash recovery and data restoration for the operation log system.

View file

@ -11,7 +11,7 @@ import { Operation, OpType, ActionType } from '../core/operation.types';
import { uuidv7 } from '../../util/uuid-v7';
import { incrementVectorClock, mergeVectorClocks } from '../../core/util/vector-clock';
import { OpLog } from '../../core/log';
import { AppDataComplete } from '../../sync/model-config';
import { AppDataComplete } from '../model/model-config';
/**
* Handles hydration after remote sync downloads.

View file

@ -4,7 +4,7 @@ import { SyncOperation } from '../../sync/providers/provider.interface';
import { DecryptError } from '../../sync/errors/sync-errors';
import { ActionType } from '../core/operation.types';
import { mockEncrypt, mockDecrypt } from '../testing/helpers/mock-encryption.helper';
import { ENCRYPT_FN, DECRYPT_FN } from '../../sync/util/encryption.token';
import { ENCRYPT_FN, DECRYPT_FN } from '../encryption/encryption.token';
describe('OperationEncryptionService', () => {
let service: OperationEncryptionService;

View file

@ -1,5 +1,5 @@
import { inject, Injectable } from '@angular/core';
import { ENCRYPT_FN, DECRYPT_FN } from '../../sync/util/encryption.token';
import { ENCRYPT_FN, DECRYPT_FN } from '../encryption/encryption.token';
import { SyncOperation } from '../../sync/providers/provider.interface';
import { DecryptError } from '../../sync/errors/sync-errors';

View file

@ -9,7 +9,7 @@ import {
ServerSyncOperation,
SnapshotUploadResponse,
} from '../../../../sync/providers/provider.interface';
import { EncryptAndCompressHandlerService } from '../../../../sync/util/encrypt-and-compress-handler.service';
import { EncryptAndCompressHandlerService } from '../../../encryption/encrypt-and-compress-handler.service';
import { EncryptAndCompressCfg } from '../../../../sync/sync.types';
import { CompactOperation } from '../../../../core/persistence/operation-log/compact/compact-operation.types';
import {

View file

@ -12,7 +12,7 @@
*
* Usage with Jasmine spyOn (recommended):
* ```typescript
* import * as encryptionModule from '../../sync/util/encryption';
* import * as encryptionModule from '../../encryption/encryption';
* import { mockEncrypt, mockDecrypt } from './mock-encryption.helper';
*
* beforeEach(() => {

View file

@ -41,7 +41,7 @@ import { resetTestUuidCounter } from './helpers/test-client.helper';
import { LockService } from '../../sync/lock.service';
import { SchemaMigrationService } from '../../store/schema-migration.service';
import { mockDecrypt, mockEncrypt } from '../helpers/mock-encryption.helper';
import { ENCRYPT_FN, DECRYPT_FN } from '../../../sync/util/encryption.token';
import { ENCRYPT_FN, DECRYPT_FN } from '../../encryption/encryption.token';
import { TranslateService } from '@ngx-translate/core';
import { SuperSyncStatusService } from '../../sync/super-sync-status.service';
import { ServerMigrationService } from '../../sync/server-migration.service';

View file

@ -1,4 +1,4 @@
import { AppDataComplete } from '../../sync/model-config';
import { AppDataComplete } from '../model/model-config';
import { IValidation } from 'typia';
import { DEFAULT_GLOBAL_CONFIG } from '../../features/config/default-global-config.const';
import { PFLog } from '../../core/log';

View file

@ -12,7 +12,7 @@ import {
TaskRepeatCfg,
} from '../../features/task-repeat-cfg/task-repeat-cfg.model';
import { IssueProvider } from '../../features/issue/issue.model';
import { AppDataComplete } from '../../sync/model-config';
import { AppDataComplete } from '../model/model-config';
import { dirtyDeepCopy } from '../../util/dirtyDeepCopy';
const FAKE_PROJECT_ID = 'FAKE_PROJECT_ID';

View file

@ -10,7 +10,7 @@ import { unique } from '../../util/unique';
import { TODAY_TAG } from '../../features/tag/tag.const';
import { TaskRepeatCfgCopy } from '../../features/task-repeat-cfg/task-repeat-cfg.model';
import { IssueProvider } from '../../features/issue/issue.model';
import { AppDataComplete } from '../../sync/model-config';
import { AppDataComplete } from '../model/model-config';
import { INBOX_PROJECT } from '../../features/project/project.const';
import { autoFixTypiaErrors } from './auto-fix-typia-errors';
import { IValidation } from 'typia';

View file

@ -1,5 +1,5 @@
import { AppDataCompleteLegacy } from '../../imex/sync/sync.model';
import { AppDataComplete } from '../../sync/model-config';
import { AppDataComplete } from '../model/model-config';
export const isDataRepairPossible = (
data: AppDataCompleteLegacy | AppDataComplete,

View file

@ -1,4 +1,4 @@
import { AppDataComplete } from '../../sync/model-config';
import { AppDataComplete } from '../model/model-config';
import { PFLog } from '../../core/log';
import { TODAY_TAG } from '../../features/tag/tag.const';

View file

@ -1,6 +1,6 @@
import { devError } from '../../util/dev-error';
import { environment } from '../../../environments/environment';
import { AppDataComplete } from '../../sync/model-config';
import { AppDataComplete } from '../model/model-config';
import { PFLog } from '../../core/log';
import { MenuTreeKind } from '../../features/menu-tree/store/menu-tree.model';
import { TODAY_TAG } from '../../features/tag/tag.const';

View file

@ -25,7 +25,7 @@ import {
addProjectToAppData,
addTagToAppData,
} from './state-validity-test-utils';
import { AppDataComplete } from '../../sync/model-config';
import { AppDataComplete } from '../model/model-config';
import { RootState } from '../../root-store/root-state';
import { TaskSharedActions } from '../../root-store/meta/task-shared.actions';
import { WorkContextType } from '../../features/work-context/work-context.model';

View file

@ -3,7 +3,7 @@
* These helpers construct valid AppDataComplete states and
* validate them using the same validation functions used in production.
*/
import { AppDataComplete } from '../../sync/model-config';
import { AppDataComplete } from '../model/model-config';
import { validateFull } from './validation-fn';
import { initialTaskState } from '../../features/tasks/store/task.reducer';
import { initialProjectState } from '../../features/project/store/project.reducer';

View file

@ -1,16 +1,16 @@
import { inject, Injectable } from '@angular/core';
import { IValidation } from 'typia';
import { validateFull } from '../../sync/validation/validation-fn';
import { validateFull } from './validation-fn';
// TEMPORARILY DISABLED - repair is disabled for debugging
// import { dataRepair } from '../../sync/validation/data-repair';
// import { isDataRepairPossible } from '../../sync/validation/is-data-repair-possible.util';
// import { dataRepair } from './data-repair';
// import { isDataRepairPossible } from './is-data-repair-possible.util';
import { RepairSummary } from '../core/operation.types';
import { OpLog } from '../../core/log';
// DISABLED: Repair system is non-functional
// import { RepairOperationService } from './repair-operation.service';
import { StateSnapshotService } from '../../sync/state-snapshot.service';
// DISABLED: Repair system is non-functional
// import { PfapiService } from '../../sync/sync.service';
// import { SyncService } from '../../sync/sync.service';
// import { loadAllData } from '../../root-store/meta/load-all-data.action';
/* DISABLED: Repair system helper types and functions - unused while repair is disabled
@ -56,7 +56,7 @@ export interface ValidateAndRepairResult {
/**
* Service for validating and repairing application state.
* Wraps PFAPI's validation (Typia + cross-model) and repair functionality.
* Wraps validation (Typia + cross-model) and repair functionality.
*
* Validation happens at key checkpoints:
* - Checkpoint B: After loading snapshot during hydration

View file

@ -21,8 +21,8 @@ import { BoardsState } from '../../features/boards/store/boards.reducer';
import { IssueProviderState } from '../../features/issue/issue.model';
import { MetricState } from '../../features/metric/metric.model';
import { GlobalConfigState } from '../../features/config/global-config.model';
import { AppDataComplete } from '../../sync/model-config';
import { ValidationResult } from '../sync.types';
import { AppDataComplete } from '../model/model-config';
import { ValidationResult } from '../../sync/sync.types';
import { PFLog } from '../../core/log';
import {
PluginMetaDataState,

View file

@ -1,53 +0,0 @@
import { Injectable, inject } from '@angular/core';
import { createEffect, ofType } from '@ngrx/effects';
import { Store } from '@ngrx/store';
import { tap, withLatestFrom } from 'rxjs/operators';
import {
upsertPluginUserData,
deletePluginUserData,
upsertPluginMetadata,
deletePluginMetadata,
} from './plugin.actions';
import { selectPluginUserDataFeatureState } from './plugin-user-data.reducer';
import { selectPluginMetadataFeatureState } from './plugin-metadata.reducer';
import { LOCAL_ACTIONS } from '../../util/local-actions.token';
/**
* Plugin effects for persistence.
*
* Note: In the operation-log architecture, persistence happens automatically
* through the operation capture meta-reducer. These effects are kept as no-ops
* for potential future use (e.g., side effects other than persistence).
*/
@Injectable()
export class PluginEffects {
private _actions$ = inject(LOCAL_ACTIONS);
private _store = inject(Store);
// Note: Persistence is now handled by the operation-log meta-reducer.
// These effects are kept as placeholders for potential non-persistence side effects.
persistPluginUserData$ = createEffect(
() =>
this._actions$.pipe(
ofType(upsertPluginUserData, deletePluginUserData),
withLatestFrom(this._store.select(selectPluginUserDataFeatureState)),
tap(([_, _state]) => {
// No-op: Persistence handled by operation-log
}),
),
{ dispatch: false },
);
persistPluginMetadata$ = createEffect(
() =>
this._actions$.pipe(
ofType(upsertPluginMetadata, deletePluginMetadata),
withLatestFrom(this._store.select(selectPluginMetadataFeatureState)),
tap(([_, _state]) => {
// No-op: Persistence handled by operation-log
}),
),
{ dispatch: false },
);
}

View file

@ -92,7 +92,6 @@ import {
PLUGIN_METADATA_FEATURE_NAME,
pluginMetadataReducer,
} from '../plugins/store/plugin-metadata.reducer';
import { PluginEffects } from '../plugins/store/plugin.effects';
import {
REMINDER_FEATURE_NAME,
reminderReducer,
@ -172,7 +171,6 @@ import {
StoreModule.forFeature(PLUGIN_USER_DATA_FEATURE_NAME, pluginUserDataReducer),
StoreModule.forFeature(PLUGIN_METADATA_FEATURE_NAME, pluginMetadataReducer),
EffectsModule.forFeature([PluginEffects]),
StoreModule.forFeature(REMINDER_FEATURE_NAME, reminderReducer),
StoreModule.forFeature(ARCHIVE_YOUNG_FEATURE_NAME, archiveYoungReducer),
StoreModule.forFeature(ARCHIVE_OLD_FEATURE_NAME, archiveOldReducer),

View file

@ -1,6 +1,6 @@
import { createAction, props } from '@ngrx/store';
import { AppDataCompleteLegacy } from '../../imex/sync/sync.model';
import { AppDataComplete } from '../../sync/model-config';
import { AppDataComplete } from '../../op-log/model/model-config';
export const loadAllData = createAction(
'[SP_ALL] Load(import) all data',

View file

@ -10,11 +10,15 @@ import { CURRENT_SCHEMA_VERSION } from '../op-log/store/schema-migration.service
import { incrementVectorClock } from '../core/util/vector-clock';
import { uuidv7 } from '../util/uuid-v7';
import { loadAllData } from '../root-store/meta/load-all-data.action';
import { validateFull } from './validation/validation-fn';
import { dataRepair } from './validation/data-repair';
import { isDataRepairPossible } from './validation/is-data-repair-possible.util';
import { validateFull } from '../op-log/validation/validation-fn';
import { dataRepair } from '../op-log/validation/data-repair';
import { isDataRepairPossible } from '../op-log/validation/is-data-repair-possible.util';
import { PFLog } from '../core/log';
import { AppDataComplete, CROSS_MODEL_VERSION, AllModelConfig } from './model-config';
import {
AppDataComplete,
CROSS_MODEL_VERSION,
AllModelConfig,
} from '../op-log/model/model-config';
import { CompleteBackup } from './sync.types';
/**

View file

@ -1155,7 +1155,7 @@ describe('SuperSyncProvider', () => {
): Promise<{ base64Gzip: string; headers: Record<string, string>; url: string }> {
// Instead of actually calling CapacitorHttp, return what would be sent
const { compressWithGzipToString } = await import(
'../../util/compression-handler'
'../../../op-log/encryption/compression-handler'
);
const base64Gzip = await compressWithGzipToString(jsonPayload);
const baseUrl = cfg.baseUrl.replace(/\/$/, '');

View file

@ -21,7 +21,7 @@ import { SyncLog } from '../../../core/log';
import {
compressWithGzip,
compressWithGzipToString,
} from '../../util/compression-handler';
} from '../../../op-log/encryption/compression-handler';
import { IS_ANDROID_WEB_VIEW } from '../../../util/is-android-web-view';
const LAST_SERVER_SEQ_KEY_PREFIX = 'super_sync_last_server_seq_';

View file

@ -21,7 +21,7 @@ import { selectTaskRepeatCfgFeatureState } from '../features/task-repeat-cfg/sto
import { selectTimeTrackingState } from '../features/time-tracking/store/time-tracking.selectors';
import { AllSyncModels } from './sync.types';
import { AllModelConfig } from '../sync/model-config';
import { AllModelConfig } from '../op-log/model/model-config';
import { environment } from '../../environments/environment';
import { ArchiveModel } from '../features/time-tracking/time-tracking.model';
import { initialTimeTrackingState } from '../features/time-tracking/store/time-tracking.reducer';

View file

@ -1,5 +1,4 @@
// Barrel export for sync module - replaces pfapi/api
// This provides backward compatibility for existing imports
// Barrel export for sync module - providers and related types
// Types from sync.types.ts
export {
@ -59,6 +58,3 @@ export { DropboxPrivateCfg } from './providers/dropbox/dropbox';
// VectorClock from core
export { VectorClock } from '../core/util/vector-clock';
// Legacy type stubs for backward compatibility
export type ModelCfgToModelCtrl<T> = T;

View file

@ -1,6 +1,6 @@
import { DEFAULT_GLOBAL_CONFIG } from '../features/config/default-global-config.const';
import { createEmptyEntity } from './create-empty-entity';
import { AppDataComplete } from '../sync/model-config';
import { AppDataComplete } from '../op-log/model/model-config';
import { initialTimeTrackingState } from '../features/time-tracking/store/time-tracking.reducer';
export const createAppDataCompleteMock = (): AppDataComplete => ({