# Limits

  • One process. The lock enforces it. A lockfile nobody is holding is taken over on POSIX and respected on Windows, where it is deleted by hand.
  • Everything in memory. The file is read once at startup and rewritten whole on every dump. Thousands of rows, not millions.
  • Ids are auto-incrementing ints, never recycled. See API.
  • No queries. all() and filter it yourself.
  • The file is never re-read while the process runs. Editing it under a running store changes nothing, and the next dump overwrites your edit.
  • IsPassword encrypts nothing. The value is written in the clear, like any other string.
  • FileHint stores the path, not the file. The core validates the extension, which the text of the value settles by itself, and asks the filesystem nothing — so a row whose file was moved, deleted or grown comes back exactly as it was stored. Whether the file is still there is a question for whoever opens it.
  • int | float is ambiguous in a hand-edited file. A bare 3 loads as an int; write 3.0 if you meant a float.
  • A date is YYYY-MM-DD and a time is HH:MM[:SS], and no other spelling loads. date.fromisoformat accepts far more than that, such as "20260101" or "2026-W01-1"; a file holding one of those is refused at load with the core's own message rather than read and rewritten behind your back. Write the canonical form.
  • A wrong Python type whose text is valid transport is rewritten, not refused. With a field due: date, Task(due="2026-08-01") is accepted and the row ends up holding date(2026, 8, 1).
  • get() and all() hand back the store's own rows, not copies. Mutating what you get reaches inside, unvalidated. Build a new instance and put it.
  • The debounce has no ceiling. Writes arriving faster than debounce keep pushing the dump back for as long as they keep arriving.
  • A failed dump is retried silently. Nothing logs it; it surfaces at close(), or as a traceback from the exit hook. The library imposes no logger.
  • Upgrading pytypehint or Python can move the fingerprints. A test pins them, and a move is declared as a breaking change, never absorbed — an absorbed one would silently rename every database.
  • A row the file cannot carry is refused at add. See the guarantee.
  • Duplicate row ids in a hand-edited file: the last one wins. json.loads drops the earlier ones before the store sees there were two, and there is no defence short of parsing the file ourselves.
  • Around 120 levels of nesting on Python 3.11, around 245 on 3.12 and later. Deeper than that, opening the store raises RecursionError — loudly, and before the lockfile is taken, so nothing is left behind. The gap between the two is the interpreter's, not the store's: before 3.12 every comprehension on the way down takes a stack frame of its own. The core itself compiles to about 247 on either.
  • Rows are rebuilt one by one on load. As an order of magnitude, 5 000 rows of an eight-field dataclass take about half a second to reopen and 1.7 MB on disk; adding them costs about a tenth of a millisecond each.