Skip to content

Latest commit

 

History

History
189 lines (173 loc) · 9.95 KB

export-format.md

File metadata and controls

189 lines (173 loc) · 9.95 KB

Lexicon export format

A Lexicon export is the whole dictionary - groups, types with their fields, items, links, alarms, cards, Boards, Study Plans and, optionally, the files that values refer to - as one UTF-8 JSON document. Every client writes and reads the same format:

Where Export Import
Desktop File -> Export... File -> Import...
Web File -> Export... File -> Import...
Android Settings, Export… Settings, Import…
Server machine LexiconServer export --output FILE [--with-files] LexiconServer import --input FILE
REST GET /api/v1/export?blobs=true POST /api/v1/import

The command line works on the database file directly, so it can export a dictionary while the server runs; SQLite coordinates the two connections. With --with-files, a missing, unreadable or damaged referenced file makes the export fail instead of producing an incomplete document. When --output names an existing file, the command writes and syncs a temporary file in the same directory before replacing it; a failed write leaves the previous export in place.

An export is for moving a dictionary, not for backing up a large one. With files included, every file is read into memory, grows by a third as base64 and becomes part of one JSON document, which is also built in memory; the REST import accepts at most --max-blob-bytes (64 MB by default). That is fine for notes and a few images, and wrong for gigabytes of PDFs or video. To keep such a dictionary safe, use the server's automatic backups (see server.md): they copy the database and keep the files as files, shared between backups.

The document

{
  "format": "lexicon-export",
  "version": 10,
  "exportedAt": "2026-09-22T08:00:00Z",
  "groups": [
    { "id": 1, "name": "Default", "description": "...", "position": 0 },
    { "id": 2, "name": "Maths", "description": "", "position": 1 }
  ],
  "types": [
    {
      "id": 3, "groupId": 2, "groupName": "Maths", "name": "Concept", "description": "",
      "fields": [
        { "id": 4, "itemTypeId": 3, "name": "Difficulty",
          "description": "How difficult this concept is.", "dataType": "Enum",
          "position": 0, "enumOptions": ["easy", "hard"] }
      ]
    }
  ],
  "items": [
    { "id": 7, "groupId": 2, "itemTypeId": 3, "title": "Monoid", "fieldValues": { "4": "hard" }, "...": "..." }
  ],
  "links": [
    { "fromItemId": 7, "toItemId": 8, "linkType": "IsA", "position": 1, "customValue": "" }
  ],
  "alarms": [
    { "id": 4, "title": "Dentist", "description": "Bring the card.", "firesAt": "2026-10-02T08:30:00Z",
      "dismissedAt": null, "repeatDays": 7, "itemId": 7, "asap": true, "group": "Health" }
  ],
  "cards": [
    { "id": 12, "itemId": 7, "question": "What is a monoid?", "answer": "A semigroup with a unit.",
      "successCount": 4, "failureCount": 2, "lastAttempt": "2026-09-24T14:00:00Z" }
  ],
  "studyPlans": [
    { "id": 1, "item": "Effective Modern C++", "group": "Programming", "type": "Book", "unitType": "Page",
      "currentProgress": 50, "startDate": "2026-09-27", "endDate": "2026-11-30",
      "note": "Study carefully", "firstUnit": 1, "lastUnit": 334,
      "studyDaysMask": 127, "customUnit": "" }
  ],
  "boards": [
    { "id": 1, "name": "Main", "content": "# Team Board\n\n- Ship it", "revision": 5 },
    { "id": 2, "name": "Work", "content": "# Work", "revision": 2 }
  ],
  "blobs": [
    { "hash": "6c7dbba2...99d98ca", "data": "iVBORw0KGgo..." }
  ]
}
  • format and version come first in meaning: an import refuses a document whose format is not lexicon-export or whose version it does not know, before anything is written. This document describes version 10.

  • Versions. The version goes up whenever a reader of the previous one would import a new document by leaving part of it out - silently losing data - rather than refusing it:

    Version Written by Adds
    1 Lexicon before cards groups, types, fields, items, links, alarms, files
    2 Lexicon with cards cards
    3 Lexicon with recurring alarms repeatDays and linked itemId on alarms
    4 Lexicon with the Board singleton Markdown board
    5 Lexicon with described fields description on item fields
    6 Lexicon with named Boards all named Markdown documents in boards
    7 Lexicon with organized alarms asap and group on alarms
    8 Lexicon with foreign-key fields ForeignKey and targetItemTypeId on fields
    9 Lexicon with Study Plans studyPlans with progress, calendar dates, unit ranges and weekdays
    10 Lexicon with Study Plan groups plain text group on studyPlans

    A reader takes every version up to its own and refuses a newer one: an older Lexicon says it does not know version 3 instead of importing it without the recurrence and item link. A version 1 document has no cards; the few that development builds wrote with cards have them read all the same.

  • Groups, types, fields, items and links have the shapes of the REST API, with the same symbolic enum names. Read-only parts such as groupName, itemTypeName and revision are written but ignored on import.

  • The ids are those of the exporting database. They only connect the records of one document: groupId, itemTypeId, the keys of fieldValues, fromItemId and toItemId refer to them. A ForeignKey field's targetItemTypeId and its stored item IDs are mapped to imported IDs.

  • alarms holds every alarm, with firesAt and dismissedAt in UTC, so an alarm that was dismissed does not ring again after an import. repeatDays is 0 for a one-time alarm or 1–365 for a recurring interval. An optional itemId links to an exported item and is mapped to its imported ID. asap marks an alarm as needing attention as soon as possible; group is an optional free-text grouping label. Documents written before alarms existed have no alarms; they import as before.

  • cards holds every card with its itemId, question, answer and statistics - successCount, failureCount and lastAttempt (UTC, or null for a card never answered) - so a quiz history survives the move. The counts are whole numbers and never negative, and lastAttempt is given exactly when one of them is above zero. Documents written before cards existed have no cards; they import as before, with none.

  • studyPlans holds each independent plan as plain UTF-8 item and optional group text, with symbolic type and unitType. currentProgress is the last completed absolute unit, or 0 when none is complete. Date endpoints are inclusive local calendar dates; the Monday-to-Sunday bit mask defaults to 127. Older exports have no plans or no Group text. Reimporting an unchanged plan does not duplicate it.

  • blobs is present when files were included. data is standard base64 with padding; hash is the SHA-256 of the decoded bytes, the value Blob fields store and the part after the colon of an Image value (image/png:<hash>).

Exports contain the current dictionary, not prior item versions or Trash. A server backup's lexicon.db and Blob files retain that history.

Importing

An import merges the document into the dictionary, in one unit of work: if any part fails, nothing is written.

  • Groups are matched by name; a missing one is created.
  • Types are matched by name, ignoring ASCII case, within their scope - all groups, or the matched group. Fields are matched by name within their type. A field that exists but holds another kind of value keeps its data type, and the values meant for it are left out with a warning. Existing types and fields are never changed, so an import cannot clear values.
  • Items already present - the same group, title and disambiguation - are left exactly as they are. Every other item is created with its values, properties, aliases, tags, flags, status, understanding, pinned state and content. A value that does not fit its field here, or whose file is neither in the document nor in this database, is left out with a warning.
  • Links are created where at least one end is an item this import created, unless the same link is already there. Links between two items that were already present are theirs to keep as they are.
  • Files in blobs are stored under their SHA-256; one whose bytes do not match its hash is left out.
  • Alarms are created unless an alarm with the same title already goes off at the same moment.
  • Cards are created for the items this import created, attached to them by their new IDs and with their statistics as exported. An item that was already present keeps the cards it has and gets none from the file. Cards are never merged: two cards asking the same question are two cards. A card of an item missing from the file is left out with a warning; a card with a blank question or answer, a negative count, a lastAttempt that is not a UTC time, or answers without a lastAttempt (or the other way round) fails the import.
  • Boards are matched by name ignoring ASCII case. A missing named Board is created; content fills an empty Board, while different existing content is kept and reported as a warning. Import therefore never destroys a Board already in use. A version 4 or 5 singleton board is imported as Main.

Importing the same document twice therefore changes nothing the second time. The import answers with a report:

{ "groupsCreated": 1, "typesCreated": 2, "fieldsCreated": 3, "itemsCreated": 3,
  "itemsSkipped": 0, "linksCreated": 2, "blobsImported": 1, "alarmsCreated": 0,
  "cardsCreated": 4, "boardsImported": 2, "warnings": [] }

An import is a merge, not a restore: it never deletes, renames or edits what is already in the dictionary. To restore a dictionary exactly, import into an empty database - or back up and restore the database file itself (see server.md).