Added export/import features
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import Dexie from 'dexie';
|
||||
import { BackupError, buildBackup, EXPORT_FORMAT, EXPORT_VERSION, parseBackup } from './backup.js';
|
||||
import { DAY, localDateKey } from './time.js';
|
||||
|
||||
/**
|
||||
@@ -238,3 +239,65 @@ export async function cleanupOldDays(retentionDays) {
|
||||
});
|
||||
return keys.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an imported dataset.
|
||||
*
|
||||
* `replace` wipes the local days and entries first and adopts the file's settings.
|
||||
* `merge` adds only days the device does not already have and leaves settings
|
||||
* alone — merging two devices' settings is meaningless, and keeping the local copy
|
||||
* of a day the device already knows about never loses work that was done here.
|
||||
*
|
||||
* @param {{days: Array, entries: Array, settings: object|null}} payload
|
||||
* @param {'merge'|'replace'} mode
|
||||
* @returns {Promise<{added: number, skipped: number, entries: number}>}
|
||||
*/
|
||||
export function importBackup(payload, mode) {
|
||||
if (mode !== 'merge' && mode !== 'replace') {
|
||||
throw new BackupError('Mode d’import inconnu.');
|
||||
}
|
||||
// Validate again at the write boundary and turn Svelte state proxies into plain
|
||||
// records. IndexedDB cannot structured-clone a proxy (DataCloneError).
|
||||
const { days, entries, settings } = parseBackup(JSON.stringify({
|
||||
...payload,
|
||||
format: EXPORT_FORMAT,
|
||||
version: EXPORT_VERSION,
|
||||
}));
|
||||
return db.transaction('rw', db.days, db.entries, db.settings, async () => {
|
||||
if (mode === 'replace') {
|
||||
await db.days.clear();
|
||||
await db.entries.clear();
|
||||
await db.settings.clear();
|
||||
}
|
||||
|
||||
const present = new Set((await db.days.toArray()).map((d) => d.date));
|
||||
const incoming = days.filter((d) => !present.has(d.date));
|
||||
const wanted = new Set(incoming.map((d) => d.date));
|
||||
const incomingEntries = entries
|
||||
.filter((e) => wanted.has(e.date))
|
||||
.map(({ id, ...rest }) => rest);
|
||||
|
||||
if (incoming.length) await db.days.bulkAdd(incoming);
|
||||
if (incomingEntries.length) await db.entries.bulkAdd(incomingEntries);
|
||||
|
||||
if (mode === 'replace' && settings) {
|
||||
await db.settings.put({ key: 'app', value: settings });
|
||||
}
|
||||
|
||||
return {
|
||||
added: incoming.length,
|
||||
skipped: days.length - incoming.length,
|
||||
entries: incomingEntries.length,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** A consistent snapshot of all three stores, even while another tab writes. */
|
||||
export function exportBackup() {
|
||||
return db.transaction('r', db.days, db.entries, db.settings, async () => {
|
||||
const [days, entries, settings] = await Promise.all([
|
||||
db.days.toArray(), db.entries.toArray(), readSettings(),
|
||||
]);
|
||||
return buildBackup({ days, entries, settings });
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user