Skip to content

feat: add .taskora data package export and import - #235

Merged
MaplumeX merged 2 commits into
mainfrom
feat/data-import
Oct 16, 2026
Merged

MaplumeX merged 2 commits into
mainfrom
feat/data-import

Conversation

@MaplumeX

Copy link
Copy Markdown
Owner

Summary

Replaces the old Prisma-row JSON export with a versioned .taskora data package (zip) and adds a full import as copy pipeline on the hub.

Previously users could export but never import: backups could not be restored, and accounts/hubs could not be migrated. The old export was also incomplete (no attachments, no calendar subscriptions) and bound to raw Prisma columns, so schema changes silently broke old files.

Data package format (format 1)

manifest.json   { format, exportId, exportedAt, hubVersion, includesBlobs, counts }
data.json       { entities: { <SyncEntity>: [{ id, fields }] }, preferences, calendarSubscriptions }
blobs/<sha256>  attachment contents (when includesBlobs)
  • Entities are stored in the sync wire format (via serializeRow(...).fields, no clocks), so ENTITIES stays the single source of truth.
  • format is an integer. Imports migrate older formats step by step; newer formats are rejected with unsupported-format.
  • Excluded: device, refresh tokens, agent config (API keys), assistant sessions/approvals, sync internals, calendar pull state, and user credentials/email.
  • Legacy taskora-export-*.json files are recognized as format 0 and migrated on import.

Changes

Backend

  • New DataPackageModule:
    • GET /users/me/export?blobs=0|1 — streams the zip via yazl; blobs are read lazily and not recompressed, so hub memory does not grow with attachment size. Missing blobs are skipped.
    • POST /users/me/import/preview — raw-byte upload (bypasses the JSON body parser and its 4 MB limit), stored in a per-user temp dir with a 1-hour TTL; returns uploadId, manifest summary and alreadyImportedAt.
    • POST /users/me/import — { uploadId, importPreferences }.
  • buildImportPlan (pure function, no DB access): remaps every id to a new UUID, rewrites references from REFERENCE_FIELDS plus repeatSourceId and project-heading.projectId, handles dangling references with compact semantics, merges tags by title path, and appends top-level items to the end of each container using positionsBetween (including the feed key space).
  • Blobs are content-addressed and written before the transaction; a hash mismatch fails the whole import with blob-mismatch. All rows are written in a single writeAsHub transaction, so the operation is all-or-nothing and produces change events for devices.
  • New DataImport Prisma model + migration to detect repeated imports by exportId.
  • Per-user in-process lock: concurrent imports return 409.
  • HUB_VERSION extracted into common/hub-version.ts, shared with the health controller.
  • Removed the old UsersService.exportData and GET /users/me/export JSON endpoint.

Frontend / API

  • exportData(includeBlobs) now downloads a Blob; previewImport / importData hooks added, with upload progress. Import invalidates all queries and re-hydrates preferences when requested.
  • Settings → Data: "Include attachments" toggle and an import flow (choose file → upload + preview → confirm → result summary), implemented for both desktop and mobile layouts.
  • DataImportDialog handles progress, the already-imported warning, the preferences switch, per-entity counts, skip reasons and error-code messages.
  • i18n for en and zh.

Shared

  • New data-package.ts with the format constant, manifest/data types, error codes, skip reasons and import stats, used by both backend and UI.

Tests

  • data-package.import-plan.spec.ts — id remapping, dangling references, orphaned children, tag path merging, position ordering, feed interleaving, missing blobs, calendar dedup, format upgrade/rejection.
  • data-package.export.e2e-spec.ts — zip structure, wire fields, blob dedup, blobs=0, exclusion of secrets, per-user isolation.
  • data-package.import.e2e-spec.ts — round-trip equality (including blob bytes), non-destructive import into a populated account, atomic rollback, error codes, duplicate-import notice, cross-user uploadId, legacy format 0 import, and that devices can pull imported rows.
  • SettingsData.test.tsx — preview → import with the preferences toggle → result, duplicate notice, error text.

Notes

  • Import is additive only: nothing is overwritten or deleted; areas and projects are never merged, and only tags with an identical title path are merged into existing ones.
  • Import runs on the hub, not the device, so the whole thing is atomic and blobs do not go through the device upload queue.
  • Not yet manually verified: native file selection for .taskora and <a download> saving on desktop (Tauri) and Android.
  • Docs updated in CONTEXT.md ("Data Package", "Import as Copy") and the feature spec/issues under .scratch/data-import/.

pnpm -r --parallel ran the api and backend pretest scripts, which rebuild
engine's dist while frontend/desktop/mobile/ui tests already import it,
so a test could load a half-written module (LocalReplica is not a
constructor). Build shared, engine and the Prisma client once up front
and run the package tests with pre/post scripts disabled.
@MaplumeX
MaplumeX merged commit a2d1742 into main Oct 16, 2026
4 checks passed
@MaplumeX
MaplumeX deleted the feat/data-import branch October 16, 2026 12:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant