NDJSON Import System#
Overview#
The NDJSON import system handles bulk data ingestion for Sure via SureImport, the only import type that supports chunked, API-driven uploads. It is built around two core models — SureImport and ImportSession — and is exposed through the REST API at POST /api/v1/import_sessions .
The flow is two-phase: upload chunks → publish. The API controller is Api::V1::ImportSessionsController with three write actions: create, create_chunk, and publish .
Routes :
POST /api/v1/import_sessions
GET /api/v1/import_sessions/:id
POST /api/v1/import_sessions/:id/chunks → create_chunk
POST /api/v1/import_sessions/:id/publish
NDJSON Format#
Each line in the file is a JSON object with "type" and "data" keys. Validation checks only the first line . Supported entity types :
Account, Balance, Category, Tag, Merchant, RecurringTransaction, Transaction, Transfer, RejectedTransfer, Trade, Holding, Valuation, Budget, BudgetCategory, Rule
Allowed content types: application/x-ndjson, application/ndjson, application/json, application/octet-stream, text/plain .
Session Creation & Idempotency#
POST /api/v1/import_sessions calls ImportSession.create_or_find_for!, which implements idempotency via an optional client_session_id. If the same client_session_id is submitted again, the existing session is returned rather than creating a duplicate . If expected_chunks differs from the previously stored value, a ConflictError is raised .
The session has four statuses: pending, importing, complete, failed .
Chunked Upload#
Each chunk is uploaded via POST /api/v1/import_sessions/:id/chunks, calling attach_chunk!:
- Sequence validation —
sequencemust be a positive integer and must not exceedexpected_chunksif set . - SHA-256 checksum — computed on the raw content at upload time and stored on the
SureImportrecord . - Idempotency — chunks can be re-uploaded. If the same
sequenceorclient_chunk_idalready exists, the system checks that the checksum matches; mismatches raiseConflictError. - Retry finalization — on re-upload, the file is re-attached only if the stored file is missing or its checksum differs .
Chunks are accepted only while the session is in pending or failed state; uploading to a session that is importing or complete raises a ConflictError .
Each chunk is stored as a SureImport record with an attached ndjson_file (Active Storage) .
Both a file multipart upload and a raw_file_content string body are accepted at the controller layer .
File Size & Row Count Limits#
| Limit | Default | Env Override |
|---|---|---|
| Max chunk size | 10 MB | SURE_IMPORT_MAX_NDJSON_SIZE_MB |
| Max total rows | 100,000 | SURE_IMPORT_MAX_ROWS |
Defaults defined in SureImport. Both are overridable at runtime via environment variables . The controller enforces the size limit per chunk before attaching . The row limit is enforced at publish time by summing rows_count across all chunks .
Publishing#
POST /api/v1/import_sessions/:id/publish calls publish_later:
sync_chunk_row_counts!— downloads each chunk and re-counts rows .validate_publishable_chunks!— asserts at least one chunk exists, total rows ≤ limit, and that actual chunk sequences exactly match the declaredexpected_chunksrange .- Transitions session to
importingand enqueuesImportSessionJobon the:high_priorityqueue.
ImportSessionJob calls publish synchronously, which iterates chunks in sequence order, calling import! on each SureImport and delegating to Family::DataImporter. After all chunks succeed, status transitions to complete and a family sync is enqueued .
If any chunk fails, status rolls back to failed and error details are stored .
Readback Verification#
After each chunk is imported, SureImport records a readback verification: it snapshots entity counts before and after import, compares expected vs. actual deltas, and stores the result as not_verified → matched / mismatch / failed . The verification payload is accessible on the import record .
Key Files#
| File | Role |
|---|---|
app/models/import_session.rb | Session lifecycle, chunk attachment, sequence/checksum validation, publish orchestration |
app/models/sure_import.rb | Per-chunk import model, size/row limits, NDJSON parsing, readback verification |
app/controllers/api/v1/import_sessions_controller.rb | REST API: create session, upload chunk, publish |
app/controllers/import/uploads_controller.rb | UI-based single-file upload (enforces same MAX_NDJSON_SIZE) |
app/jobs/import_session_job.rb | Background job that calls publish on the session |