From b85c32fcce6d5188bdc677155a933620efac7375 Mon Sep 17 00:00:00 2001 From: Till JS Date: Fri, 27 Mar 2026 11:50:43 +0100 Subject: [PATCH] feat(todo): wire up browser sync with Go server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Auth store starts/stops sync on login/logout - LocalStore queues all existing records for initial sync (guest→auth transition) - LocalCollection.queueAllForSync() creates pending inserts for all local records - Skips initial queue if sync cursor exists (already synced before) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../apps/web/src/lib/stores/auth.svelte.ts | 7 ++++ packages/local-store/src/collection.ts | 24 ++++++++++++++ packages/local-store/src/store.ts | 32 ++++++++++++++++++- 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/apps/todo/apps/web/src/lib/stores/auth.svelte.ts b/apps/todo/apps/web/src/lib/stores/auth.svelte.ts index 3befa4db2..5a5819b21 100644 --- a/apps/todo/apps/web/src/lib/stores/auth.svelte.ts +++ b/apps/todo/apps/web/src/lib/stores/auth.svelte.ts @@ -6,6 +6,7 @@ import { browser } from '$app/environment'; import { initializeWebAuth, type UserData } from '@manacore/shared-auth'; import { apiClient } from '$lib/api/client'; +import { todoStore } from '$lib/data/local-store'; // Default URLs for local development only const DEV_AUTH_URL = 'http://localhost:3001'; @@ -221,6 +222,9 @@ export const authStore = { apiClient.setAccessToken(token); } + // Start syncing local data to server + todoStore.startSync(() => authStore.getValidToken()); + return { success: true }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; @@ -264,6 +268,9 @@ export const authStore = { * Sign out */ async signOut() { + // Stop syncing before clearing auth + todoStore.stopSync(); + const authService = getAuthService(); if (!authService) { user = null; diff --git a/packages/local-store/src/collection.ts b/packages/local-store/src/collection.ts index fcd00ed9f..09cf3475b 100644 --- a/packages/local-store/src/collection.ts +++ b/packages/local-store/src/collection.ts @@ -281,6 +281,30 @@ export class LocalCollection { return this._table.where('updatedAt').above(since).toArray(); } + /** + * Queue all existing local records as pending inserts. + * Used for initial sync after login — ensures guest data gets pushed to server. + */ + async queueAllForSync(): Promise { + const allRecords = await this._table.filter((r) => !r.deletedAt).toArray(); + let count = 0; + + await this._db.transaction('rw', [this._db._pendingChanges], async () => { + for (const record of allRecords) { + await this._db._pendingChanges.add({ + collection: this.name, + recordId: record.id, + op: 'insert', + data: record as unknown as Record, + createdAt: new Date().toISOString(), + }); + count++; + } + }); + + return count; + } + // ─── Internal ─────────────────────────────────────────────── private async _trackChange( diff --git a/packages/local-store/src/store.ts b/packages/local-store/src/store.ts index 59fe45e5f..da3cdd4a5 100644 --- a/packages/local-store/src/store.ts +++ b/packages/local-store/src/store.ts @@ -147,7 +147,10 @@ export class LocalStore { this._syncEngine.registerCollection(col); } - this._syncEngine.start(); + // Queue existing local data for initial sync (guest → authenticated transition) + this._queueInitialSync().then(() => { + this._syncEngine?.start(); + }); } /** @@ -203,6 +206,33 @@ export class LocalStore { await this.db.reset(); } + /** + * Queue all existing local records for sync if this is the first sync. + * Handles the guest→authenticated transition: local data gets pushed to server. + */ + private async _queueInitialSync(): Promise { + // Check if we've synced before — if any collection has a cursor, skip + for (const [name] of this._collections) { + const cursor = await this.db.getSyncCursor(name); + if (cursor !== '1970-01-01T00:00:00.000Z') { + // Already synced before — pending changes from writes will handle it + return; + } + } + + // First sync: queue all local records as pending inserts + let total = 0; + for (const col of this._collections.values()) { + const count = await col.queueAllForSync(); + total += count; + } + + if (total > 0) { + // eslint-disable-next-line no-console + console.log(`[LocalStore] Queued ${total} local records for initial sync`); + } + } + /** * Close the database connection. */