diff --git a/packages/core/src/api/index.ts b/packages/core/src/api/index.ts index 86adc4cde..272997403 100644 --- a/packages/core/src/api/index.ts +++ b/packages/core/src/api/index.ts @@ -58,6 +58,7 @@ import { IFileStorage, IStorage, KVStorageAccessor, + PendingSyncItemsAccessor, StorageAccessor } from "../interfaces.js"; import TokenManager from "./token-manager.js"; @@ -85,6 +86,7 @@ import { LazyPromise } from "../utils/lazy-promise.js"; import { InboxApiKeys } from "./inbox-api-keys.js"; import { Circle } from "./circle.js"; import { Wrapped } from "./wrapped.js"; +import { PendingSyncItems } from "../database/pending-sync-items.js"; type EventSourceConstructor = new ( uri: string, @@ -164,6 +166,8 @@ class Database { private _kv = new KVStorage(this.databaseReady.promise); kv: KVStorageAccessor = () => this._kv; + private _pendingSyncItems = new PendingSyncItems(this.databaseReady.promise); + pendingSyncItems: PendingSyncItemsAccessor = () => this._pendingSyncItems; private _config: ConfigStorage = new ConfigStorage( this.databaseReady.promise ); diff --git a/packages/core/src/api/sync/index.ts b/packages/core/src/api/sync/index.ts index 07f16fb6a..abced08c2 100644 --- a/packages/core/src/api/sync/index.ts +++ b/packages/core/src/api/sync/index.ts @@ -306,6 +306,19 @@ export class Sync { } async stop(options: SyncOptions) { + const pendingInboxItems = await this.db + .pendingSyncItems() + .getByType("inbox-item"); + if (pendingInboxItems.length > 0) { + const items = pendingInboxItems.map( + (item) => JSON.parse(item.data) as SyncInboxItem + ); + await handleInboxItems(items, this.db); + await this.db + .pendingSyncItems() + .remove(pendingInboxItems.map((item) => item.id)); + } + if ( (options.type === "send" || options.type === "full") && (await this.collector.hasUnsyncedChanges()) @@ -584,7 +597,18 @@ export class Sync { return false; } - await handleInboxItems(inboxItems, this.db); + /** + * We will process the inbox items after sync is completed. + * TODO: do this for other items as well + */ + for (const item of inboxItems) { + await this.db.pendingSyncItems().add({ + id: item.id, + type: "inbox-item", + data: JSON.stringify(item), + dateCreated: Date.now() + }); + } return true; } diff --git a/packages/core/src/database/index.ts b/packages/core/src/database/index.ts index 550bc2bab..693e2f836 100644 --- a/packages/core/src/database/index.ts +++ b/packages/core/src/database/index.ts @@ -50,6 +50,7 @@ import { Monograph, Note, Notebook, + PendingSyncItem, Relation, Reminder, SessionContentItem, @@ -97,6 +98,7 @@ export interface DatabaseSchema { } export type RawDatabaseSchema = DatabaseSchema & { + pendingsyncitems: PendingSyncItem; kv: { key: string; value?: string | null; diff --git a/packages/core/src/database/migrations.ts b/packages/core/src/database/migrations.ts index 826bfcc98..0711440bb 100644 --- a/packages/core/src/database/migrations.ts +++ b/packages/core/src/database/migrations.ts @@ -496,6 +496,18 @@ export class NNMigrationProvider implements MigrationProvider { .addColumn("errorContext", "text") .execute(); } + }, + "a-2026-07-06": { + async up(db) { + await db.schema + .createTable("pendingsyncitems") + .ifNotExists() + .addColumn("id", "text", (c) => c.primaryKey().unique().notNull()) + .addColumn("type", "text") + .addColumn("data", "text") + .addColumn("dateCreated", "integer") + .execute(); + } } }; } diff --git a/packages/core/src/database/pending-sync-items.ts b/packages/core/src/database/pending-sync-items.ts new file mode 100644 index 000000000..5fd4d8f1b --- /dev/null +++ b/packages/core/src/database/pending-sync-items.ts @@ -0,0 +1,53 @@ +/* +This file is part of the Notesnook project (https://notesnook.com/) + +Copyright (C) 2023 Streetwriters (Private) Limited + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +import { LazyDatabaseAccessor, RawDatabaseSchema } from "./index.js"; +import { PendingSyncItem } from "../types.js"; + +export class PendingSyncItems { + private readonly db: LazyDatabaseAccessor; + constructor(db: LazyDatabaseAccessor) { + this.db = db as unknown as LazyDatabaseAccessor; + } + + async add(item: PendingSyncItem) { + await this.db.then((db) => + db.replaceInto("pendingsyncitems").values(item).execute() + ); + } + + async getByType(type: PendingSyncItem["type"]) { + const result = await this.db.then((db) => + db + .selectFrom("pendingsyncitems") + .where("type", "==", type) + .selectAll() + .execute() + ); + return result as PendingSyncItem[]; + } + + async remove(ids: string[]) { + if (ids.length === 0) return; + + await this.db.then((db) => + db.deleteFrom("pendingsyncitems").where("id", "in", ids).execute() + ); + } +} diff --git a/packages/core/src/interfaces.ts b/packages/core/src/interfaces.ts index 616040e0c..33efd460a 100644 --- a/packages/core/src/interfaces.ts +++ b/packages/core/src/interfaces.ts @@ -25,6 +25,7 @@ import { } from "@notesnook/crypto"; import { KVStorage } from "./database/kv.js"; import { ConfigStorage } from "./database/config.js"; +import { PendingSyncItems } from "./database/pending-sync-items.js"; export type Output = TOutputFormat extends "uint8array" ? Uint8Array : string; @@ -145,4 +146,5 @@ export interface IFileStorage { export type StorageAccessor = () => IStorage; export type KVStorageAccessor = () => KVStorage; export type ConfigStorageAccessor = () => ConfigStorage; +export type PendingSyncItemsAccessor = () => PendingSyncItems; export type CompressorAccessor = () => Promise; diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 4cf8f2903..cb0eab953 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -19,6 +19,7 @@ along with this program. If not, see . import { Cipher } from "@notesnook/crypto"; import { isCipher } from "./utils/index.js"; +import { SyncableItemType } from "./api/sync/types.js"; export type TimeFormat = "12-hour" | "24-hour"; export type DayFormat = "short" | "long"; @@ -547,6 +548,13 @@ export interface InboxItemHistory extends BaseItem<"inboxitemhistory"> { errorContext?: string; } +export interface PendingSyncItem { + id: string; + type: SyncableItemType | "inbox-item"; + data: string; + dateCreated: number; +} + export type Match = { prefix: string; match: string;