Commit Graph
15 Commits
Author SHA1 Message Date
dsql 4ba768a3bf release: 1.0.0
first stable release. pre-1.0.0 verification complete: all surviving MED regressions and
gaps resolved and independently re-fired, tree audited clean across the suite.

Signed-off-by: disqualifier <dev@disqualifier.me>
v1.0.0
2026-07-09 18:53:15 -04:00
dsql 68be8f7101 fix: _pid_alive treats an out-of-C-int-range pid as not alive (no OverflowError)
os.kill(pid, 0) raises OverflowError for a pid too large for a C int, which _pid_alive did
not catch; a foreign <base>.<huge-number>.tmp file in the store dir then made every stale-tmp
sweep (so every _save) crash. an over-range pid can't name a live process, so it's treated as
not-alive like ProcessLookupError.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-06 17:17:39 -04:00
dsql 98f7190f18 fix: _save no longer resolves the store's realpath twice per call
_save() already resolves target = realpath(self.file) for the write path;
_sweep_stale_tmp then independently recomputed the same realpath to derive its
match basename. _sweep_stale_tmp now takes that resolved basename as a
parameter instead of re-resolving it, cutting one syscall per save. the sweep
itself still runs on every save (unchanged) - gating it to first-save-per-
instance would change when a crash-orphaned tmp from before this process
started actually gets swept, so that part is left alone.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-06 00:18:16 -04:00
dsql ddf76d8241 fix: clear() and stale-tmp sweep resolve the real symlink target and match only <base>.<pid>.tmp
clear() removed the raw path, so on a symlinked store it unlinked only the
symlink while the real data file kept every value - clear() returned True
but the data resurfaced if the symlink was recreated. It now realpaths the
target first, mirroring _save()'s symlink-safe writes.

_sweep_stale_tmp globbed {base}.*.tmp, which crosses dots and can match an
unrelated neighbor file (deleting it outside the lib's own artifacts), and
interpolated the store's basename unescaped, so glob metacharacters in the
filename (e.g. state[prod].json) either missed the store's own orphans or
cross-matched a different store's. The sweep now lists the directory and
matches a re.escape'd regex anchored to the exact <base>.<pid>.tmp shape
_save() creates.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 19:05:46 -04:00
dsql 5e3eb4e704 refactor: derive __version__ from package metadata (single source)
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 16:58:41 -04:00
dsql 8381b2ecbd docs: compress prose/module docstrings, em-dash->hyphen (de-bloat wave 1)
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 00:10:19 -04:00
dsql 5c2fdd80f9 fix: str-only keys, symlink-safe atomic writes, strict JSON, stale-tmp sweep
_load() masked permission/IO errors as an empty store via os.path.exists();
now raises FileNotFoundError only, propagating real failures. Non-str keys
silently never round-tripped through get() since JSON object keys are always
strings; set()/get() now raise ValueError on a non-str key. _save() clobbered
a symlinked store file with os.replace(); now realpaths the target first.
json.dumps() allows NaN/Infinity by default, producing invalid JSON for
strict readers; now allow_nan=False. A whitespace-only file raised while a
zero-byte file returned {}; both now return {}. Orphaned .<pid>.tmp files
from a hard crash of a dead process are swept on save. JSON (de)serialization
now runs via asyncio.to_thread instead of on the event loop.

Signed-off-by: disqualifier <dev@disqualifier.me>
v0.2.0
2026-07-02 23:23:17 -04:00
dsql e68e0b9ccf chore: ignore .claude/ dir (CLAUDE.md now lives under .claude/)
Signed-off-by: disqualifier <dev@disqualifier.me>
v0.1.1
2026-06-29 21:54:14 -04:00
dsql 1e364fcfdb fix: clear() treats a concurrent delete as success; explicit utf-8; durability prose
clear() handles FileNotFoundError as success (the goal state — no file — is reached)
instead of returning False. read/write open with explicit encoding='utf-8'. atomic-write
prose scoped to process-crash safety (NOT power-loss durability — no fsync), in module,
README, and CLAUDE.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 21:35:23 -04:00
dsql 8747b61705 docs: pin install line to release, note unpinned-latest option
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 18:13:30 -04:00
dsql 22e91d2b2d docs: show unpinned install line; note tag-pinning for reproducibility
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 18:07:15 -04:00
dsql 5ee0292dcb docs: document _load's ValueError-on-non-object-JSON in the error contract (v0.1.1)
README + CLAUDE.md error contract now note that _load raises ValueError on valid-but-
non-object JSON (bare list/number/string/null) in addition to JSONDecodeError on a
corrupt file, matching the module docstring (L1).

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 17:57:37 -04:00
dsql e29c523c12 fix: _load raises on valid-but-non-object JSON
a store file holding valid JSON that is not an object (null/number/string/array) returned a non-dict, breaking get/set/delete/get_all. now raises ValueError, matching the documented corrupt-file contract.

Signed-off-by: disqualifier <dev@disqualifier.me>
v0.1.0
2026-06-28 17:18:28 -04:00
dsql 8f60f6e17b add package: pyproject + src
AioKV: async file-backed key-value store for single-process local state
(persist-forever, not a cache — no TTL/eviction). atomic writes via
temp-file + os.replace, every read and write under one asyncio.Lock,
blocking fs calls off the loop via asyncio.to_thread. set(key) defaults
to int(time.time()) for seen/did-X-at-T. aiocache alias kept for
back-compat. src/ layout, hatchling, aiofiles dep.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-24 21:36:43 -04:00
dsql 019955dad3 init: async file-backed kv store for single-process local state
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-24 19:54:04 -04:00