mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 14:21:46 +00:00
feat: complete offline write support with mutation queue + runtime SW cache config
- Add offline CRUD to todoRepo, budgetRepo, reservationRepo, accommodationRepo, dayRepo, tripRepo, fileRepo with optimistic Dexie writes and mutation queue - Wire all store slices (todo, budget, reservations, files, dayNotes, assignments, tripStore) through repos for offline-aware writes - Cover archive/unarchive, file toggleStar/update/delete, assignment create/delete, day title/notes update offline paths - Migrate service worker from generateSW to injectManifest (custom sw.ts) with runtime-configurable api-data (7d/500) and map-tiles (30d/1000) cache policies - Add Settings → Offline cache configuration UI with save/reset and live SW postMessage - Extend mutationQueue flush to cover all writable Dexie tables
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* SW cache configuration — shared between the service worker and the main thread.
|
||||
* Uses a dedicated 'trek-sw-config' IndexedDB database (separate from trek-offline)
|
||||
* so the SW can read it without needing to know the full trek-offline schema versions.
|
||||
*/
|
||||
import Dexie, { type Table } from 'dexie';
|
||||
|
||||
export interface SwCacheConfig {
|
||||
apiTtlDays: number;
|
||||
apiMaxEntries: number;
|
||||
tilesTtlDays: number;
|
||||
tilesMaxEntries: number;
|
||||
}
|
||||
|
||||
export const DEFAULT_SW_CONFIG: SwCacheConfig = {
|
||||
apiTtlDays: 7,
|
||||
apiMaxEntries: 500,
|
||||
tilesTtlDays: 30,
|
||||
tilesMaxEntries: 1000,
|
||||
};
|
||||
|
||||
export const SW_CONFIG_BOUNDS = {
|
||||
ttlMin: 1,
|
||||
ttlMax: 365,
|
||||
entriesMin: 10,
|
||||
entriesMax: 5000,
|
||||
};
|
||||
|
||||
export function validateSwConfig(raw: Partial<SwCacheConfig>): SwCacheConfig {
|
||||
const clamp = (v: unknown, min: number, max: number, def: number): number => {
|
||||
const n = Number(v);
|
||||
return Number.isFinite(n) && n > 0 ? Math.max(min, Math.min(max, Math.round(n))) : def;
|
||||
};
|
||||
return {
|
||||
apiTtlDays: clamp(raw.apiTtlDays, SW_CONFIG_BOUNDS.ttlMin, SW_CONFIG_BOUNDS.ttlMax, DEFAULT_SW_CONFIG.apiTtlDays),
|
||||
apiMaxEntries: clamp(raw.apiMaxEntries, SW_CONFIG_BOUNDS.entriesMin, SW_CONFIG_BOUNDS.entriesMax, DEFAULT_SW_CONFIG.apiMaxEntries),
|
||||
tilesTtlDays: clamp(raw.tilesTtlDays, SW_CONFIG_BOUNDS.ttlMin, SW_CONFIG_BOUNDS.ttlMax, DEFAULT_SW_CONFIG.tilesTtlDays),
|
||||
tilesMaxEntries:clamp(raw.tilesMaxEntries, SW_CONFIG_BOUNDS.entriesMin, SW_CONFIG_BOUNDS.entriesMax, DEFAULT_SW_CONFIG.tilesMaxEntries),
|
||||
};
|
||||
}
|
||||
|
||||
// ── Dedicated IDB for SW config ───────────────────────────────────────────────
|
||||
|
||||
interface SwConfigRow extends SwCacheConfig {
|
||||
id: 'singleton';
|
||||
updatedAt: number;
|
||||
}
|
||||
|
||||
class SwConfigDb extends Dexie {
|
||||
config!: Table<SwConfigRow, 'singleton'>;
|
||||
constructor() {
|
||||
super('trek-sw-config');
|
||||
this.version(1).stores({ config: 'id' });
|
||||
}
|
||||
}
|
||||
|
||||
let _db: SwConfigDb | null = null;
|
||||
|
||||
function getDb(): SwConfigDb {
|
||||
if (!_db) _db = new SwConfigDb();
|
||||
return _db;
|
||||
}
|
||||
|
||||
export async function readSwConfigFromIDB(): Promise<SwCacheConfig | null> {
|
||||
try {
|
||||
const row = await getDb().config.get('singleton');
|
||||
return row ? validateSwConfig(row) : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveSwConfig(cfg: SwCacheConfig): Promise<void> {
|
||||
const validated = validateSwConfig(cfg);
|
||||
await getDb().config.put({ id: 'singleton', ...validated, updatedAt: Date.now() });
|
||||
}
|
||||
|
||||
export async function loadSwConfig(): Promise<SwCacheConfig> {
|
||||
return (await readSwConfigFromIDB()) ?? { ...DEFAULT_SW_CONFIG };
|
||||
}
|
||||
Reference in New Issue
Block a user