Loading...
Loading...
Enforces user-facing data portability in an offline-first app — backup/restore (exact, machine round-trippable) kept strictly separate from export/report (human, lossy, never a restore source); a versioned envelope carrying formatVersion, schemaVersion, appVersion, exportedAtUtc and a payload checksum so a restore refuses a file from a newer app instead of corrupting the database; restore as an all-or-nothing import into a staging database swapped in only after validation; canonical values in machine formats (integer minor units, SI integers, ISO-8601 UTC, stable ids) and never localized numerals; RFC 4180 CSV quoting plus the =/+/-/@ formula-injection escape; streaming writes published by atomic rename; the share sheet behind an injected Gateway; and an export→import→export round-trip test on a hostile fixture. Use when adding or changing export, backup, import, restore, share, or CSV/JSON/PDF output, writing an import validator or merge-vs-replace policy, or handling a picked file.
npx skill4agent add zakariaf/flutter-skills data-export-and-restoreVACUUM INTOpersistence-driftreferences/formats-and-encoding.mdreferences/restore-and-merge-policy.mdformatVersionschemaVersionappVersionexportedAtUtcschemaVersionrun-migration١٢٫٥12.512,5updatedAtUtcIOSinkStringShareGatewayFileExportGatewayservice-boundary-and-native=+-@Result<ExportArtifact, ExportFailure>Result<RestoreReport, RestoreFailure>release-and-store-shipping/// The only shape restore accepts. Version fields come FIRST so a truncated or
/// foreign file is rejected by the header rather than by a mid-parse exception.
({
int formatVersion, // this envelope's own shape — bump independently of the DB
int schemaVersion, // the DB schema the payload was written from
String appVersion, // provenance for support, never a compatibility check
DateTime exportedAtUtc, // UTC, from the injected Clock
String payloadSha256, // checksum over the payload bytes, verified before parsing
Map<String, Object?> payload,
});formatVersionschemaVersionexamples/backup_envelope.dartFuture<Result<RestoreReport, RestoreFailure>> restore(File picked) async {
// 1. Header and integrity BEFORE anything is parsed or opened.
final envelope = readEnvelope(picked);
switch (envelope) {
case Err(:final failure): return Err(failure); // malformed / bad checksum
case Ok(:final value) when value.schemaVersion > kSchemaVersion:
return const Err(RestoreFailure.newerThanApp()); // refuse, never guess
case Ok():
break;
}
// 2. Import into a STAGING database — the live one is never touched yet.
final staging = await openStagingDatabase();
final imported = await staging.transaction(() => _importAll(envelope.value.payload));
if (imported case Err(:final failure)) {
await staging.close();
await deleteStagingFiles(); // user's data still intact
return Err(failure);
}
// 3. Migrate the staged copy forward through the SAME path as the app database.
// 4. Close every handle, then publish by rename, keeping the old file until the
// new one opens cleanly. See references/restore-and-merge-policy.md.
return publishStagedDatabase(staging);
}| Need | Format | Restorable? |
|---|---|---|
| Full-fidelity backup the app reads back | Envelope + JSON payload (or a copied DB file) | Yes — the only one |
| Rows for a spreadsheet or another tool | CSV, RFC 4180, canonical values | No |
| Something a human reads, prints, or files | PDF, fully localized and formatted | No |
value-objects-money-and-unitsi18n-rtl-l10npersistence-driftFile.copy'test1'schemaVersionschemaVersion=cmd()a,b"c\ndStringStringBufferformatVersionschemaVersionappVersionexportedAtUtcschemaVersionResultpersistence-driftFile.copyrun-migrationerror-handling-typed-resultsResultFailurevalue-objects-money-and-unitsi18n-rtl-l10nservice-boundary-and-nativeShareGatewayrelease-and-store-shippingtesting-strategyClockdart:ioIOSinkFile.renamepath_providershare_plussharePositionOriginfile_selectorcrypto