mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-18 08:03:11 +00:00
readLastSyncMap
This commit is contained in:
@@ -1,5 +1,70 @@
|
||||
import { DraftService, type UserDraftItemKind } from './gen'
|
||||
|
||||
/**
|
||||
* Per-draft last-sync map persisted under a single localStorage key. Lets
|
||||
* the next `save_draft` (or a downstream UI) know the server's clock at
|
||||
* our most recent successful sync for `(workspace, itemKind, path)` —
|
||||
* without doing a network round-trip first.
|
||||
*
|
||||
* Shape: `Record<draftKey, { lastSync: ISO-8601 string }>`. The map is
|
||||
* intentionally one storage slot for the whole tab; reading is one
|
||||
* `localStorage.getItem` + `JSON.parse`, not one per entry.
|
||||
*/
|
||||
const DRAFT_LAST_SYNC_KEY = 'userdraft/draftLastSync'
|
||||
|
||||
type DraftLastSyncEntry = { lastSync: string }
|
||||
type DraftLastSyncMap = Record<string, DraftLastSyncEntry>
|
||||
|
||||
/** Must match `mapKey` in `userDraft.svelte.ts` so the two files agree
|
||||
* on what identifies a draft. */
|
||||
function draftKey(workspace: string, itemKind: UserDraftItemKind, path: string): string {
|
||||
return `${workspace}/${itemKind}/${path}`
|
||||
}
|
||||
|
||||
function readLastSyncMap(): DraftLastSyncMap {
|
||||
try {
|
||||
const raw = localStorage.getItem(DRAFT_LAST_SYNC_KEY)
|
||||
if (!raw) return {}
|
||||
const parsed = JSON.parse(raw)
|
||||
// Defensive: a corrupt slot (wrong type, array, null) shouldn't
|
||||
// crash the syncer — reset to an empty map.
|
||||
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
||||
return parsed as DraftLastSyncMap
|
||||
}
|
||||
return {}
|
||||
} catch (e) {
|
||||
console.error('UserDraftDbSyncer: draftLastSync read failed', e)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
function writeLastSyncMap(map: DraftLastSyncMap): void {
|
||||
try {
|
||||
localStorage.setItem(DRAFT_LAST_SYNC_KEY, JSON.stringify(map))
|
||||
} catch (e) {
|
||||
console.error('UserDraftDbSyncer: draftLastSync write failed', e)
|
||||
}
|
||||
}
|
||||
|
||||
function setLastSync(
|
||||
workspace: string,
|
||||
itemKind: UserDraftItemKind,
|
||||
path: string,
|
||||
lastSync: string
|
||||
): void {
|
||||
// Read-modify-write is fine: each step is synchronous, so two saves
|
||||
// for different keys can't interleave their updates within a tab.
|
||||
const map = readLastSyncMap()
|
||||
map[draftKey(workspace, itemKind, path)] = { lastSync }
|
||||
writeLastSyncMap(map)
|
||||
}
|
||||
|
||||
function clearLastSync(workspace: string, itemKind: UserDraftItemKind, path: string): void {
|
||||
const map = readLastSyncMap()
|
||||
delete map[draftKey(workspace, itemKind, path)]
|
||||
writeLastSyncMap(map)
|
||||
}
|
||||
|
||||
export type UserDraftDbSyncerSaveOpts = {
|
||||
workspace: string
|
||||
itemKind: UserDraftItemKind
|
||||
@@ -9,6 +74,12 @@ export type UserDraftDbSyncerSaveOpts = {
|
||||
value: unknown | null
|
||||
}
|
||||
|
||||
export type UserDraftLastSyncQuery = {
|
||||
workspace: string
|
||||
itemKind: UserDraftItemKind
|
||||
path: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Server-side persistence for `UserDraft`. `UserDraft` owns the localStorage
|
||||
* cache; this module forwards each write through to `POST /drafts/save_draft`
|
||||
@@ -21,12 +92,23 @@ export type UserDraftDbSyncerSaveOpts = {
|
||||
*
|
||||
* NOTE: every save currently uses `force: true`, so the server copy is
|
||||
* unconditionally overwritten. This is intentional for the first cut —
|
||||
* we'll thread `last_sync` through once the client side is settled.
|
||||
* we'll thread `last_sync` through (using `getLastSync` below) once the
|
||||
* client side is settled.
|
||||
*/
|
||||
export const UserDraftDbSyncer = {
|
||||
/**
|
||||
* Read the recorded last-sync timestamp (ISO string) for a draft.
|
||||
* Returns `undefined` when we've never successfully synced this
|
||||
* `(workspace, itemKind, path)` — a fresh save should be treated as
|
||||
* a first-time push.
|
||||
*/
|
||||
getLastSync(opts: UserDraftLastSyncQuery): string | undefined {
|
||||
return readLastSyncMap()[draftKey(opts.workspace, opts.itemKind, opts.path)]?.lastSync
|
||||
},
|
||||
|
||||
async save(opts: UserDraftDbSyncerSaveOpts): Promise<void> {
|
||||
try {
|
||||
await DraftService.saveDraft({
|
||||
const resp = await DraftService.saveDraft({
|
||||
workspace: opts.workspace,
|
||||
kind: opts.itemKind,
|
||||
path: opts.path,
|
||||
@@ -35,6 +117,14 @@ export const UserDraftDbSyncer = {
|
||||
force: true
|
||||
}
|
||||
})
|
||||
// On a successful delete, drop the recorded last-sync so the
|
||||
// next save starts fresh. On an upsert, remember the server's
|
||||
// timestamp as our baseline for future conflict checks.
|
||||
if (opts.value === null) {
|
||||
clearLastSync(opts.workspace, opts.itemKind, opts.path)
|
||||
} else {
|
||||
setLastSync(opts.workspace, opts.itemKind, opts.path, resp.current_timestamp)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('UserDraftDbSyncer.save failed', e)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user