Releases: markuskimius/mkio
Release list
mkio 1.6.0
mkio 1.6.0 stops browsers serving stale files. mkio's files went out with ETag and Last-Modified but no Cache-Control, so a browser chose a lifetime itself — typically a tenth of the file's age — and reused its copy without asking: an old stylesheet for days after a deploy, an old /mkio.js against a new server after pip install -U mkio. A minor release: one new config key and one new route form. No change to the wire protocol, the CLI, the clients, the expression language or the on-disk layout.
Changed
- Every HTTP response now carries
Cache-Control: no-cacheunless its handler set aCache-Controlof its own —[static]and[config]files, the index page,/mkio.js,/mkio-expr.js, the API. The browser still stores the file; it revalidates on each use, and the validators make that a bodyless304. WebSocket handshakes are left alone.
Added
-
cache_control(top-level, default"no-cache") sets the value for the whole server.cache_control = ""sends no header — the behaviour before this release. Checked at load: a string with no control characters. -
A
[static]or[config]route may be a table in place of the directory, overriding the global value for the files it serves:[static] "/" = "./static" "/assets" = { path = "./dist/assets", cache_control = "public, max-age=31536000, immutable" } [config] "/config" = { path = "./configs", cache_control = "no-store" }
With
"/"the value covers the index page and/static/*. It is not applied to the route's errors: a404under/assetsgoes out with the global value, so a missing file is not remembered for a year. The plain string form is unchanged. Long lifetimes are for fingerprinted file names only; anything under a stable name,index.htmlabove all, should stay onno-cache.
Precedence: the handler's own header, then the route, then the global key.
See the README section "HTTP caching".
mkio 1.5.0
mkio 1.5.0 moves the expression language to version 2: word operators, in, duration literals, COUNT and function forms of the aggregates. A minor release: every version-1 expression means what it did, since each addition gives meaning to text that was a syntax error. No change to config, the wire protocol, the CLI or the on-disk layout.
Added — language (Python and /mkio-expr.js, pinned by 95 new shared cases)
and,or,not,in,not in, case-insensitive. They are contextual, not reserved: a bare word is an operator only where the grammar had no use for a name, so fields calledinornotparse as they always did.nothas Python's precedence, betweenandand the comparisons, sonot a > bnegates the comparison where!a > bis a type error.x in yisCONTAINS(y, x): member of an array, substring of a string, key of a map, FALSE for NULL. It sits with the comparisons and, like them, does not chain.- Duration literals
500ms 2s 1.5m 1h 1devaluate to seconds, so durations add, scale and compare like any number. COUNT(xs, fn)(withoutfn, the non-NULL elements), and function forms of the aggregates:SUM(trades, t -> t.qty), likewiseAVG,MIN,MAX. The list-only and varargs forms are unchanged.
Added — host tools (Python only)
parse_prefix(text, pos, stop=)parses the one expression that starts attext[pos]and returns it with the offset where it ended, for grammars that embed expressions between their own keywords.stop=["or timeout"]names phrases that end the expression where a word operator would continue it.field_paths(ast)lists every dotted path read from the scope, resolving lambda parameters and LET names (MAP(trades, t -> t.px)readstrades.*.px);check_fields(ast, schema)returns every path the schema does not know. A missing key is NULL at run time, so a misspelt column fails silently; this finds it first.register_library(..., default=False)keeps a library out of every environment that does not name it;Env(extra=[...])adds it to the defaults.
Changed
LANGUAGE_VERSIONmoves from"1"to"2", and the_mkioreply'sexprwith it. The compatibility check comparesexprexactly, as before: a client that asks for language 1 is told it no longer matches. mkui vendors the JavaScript file and the cases and follows in 1.10.0.NOTis refused as a function name, sincenot (x)is the operator.- A plain
pytestnow tests the source tree whatever mkio is installed.
See the README section on the expression language and its generated function tables.
mkio 1.4.0
mkio 1.4.0 makes the command line answer a request for help. A minor release: new --help / -h flags and a help command, all additive. No change to config, the wire protocol, the clients, the expression language or the on-disk layout.
Added
mkio --help,-handmkio helpprint every command, grouped into server and client commands, and exit 0. Baremkioprints the same and still exits 1.mkio <command> --help(or-h, ormkio help <command>) describes one command: synopsis, each option, notes and examples.mkio archive --helpcovers both the row mode and the history-only mode.- All help output fits 80 columns; long synopses fold between options rather than running to 130.
Fixed
--helpwas an error everywhere: the top level printedError: expected a command, got '--help'and exited 1, and every command rejected it as an unknown option.mkio check --helptook--helpfor the url and tried to connect to it.mkio dbupdate --bogusandmkio init --bogusanswered "this command takes no options" above a usage line listing their options; they now name the options they take.- The top-level usage had drifted from the commands: it omitted
dbupdate --keep-redo, showedsubpubwith a single topic, and gaveinitno description. Help, error usage lines and the flags each command accepts now come from one table, so they cannot drift again.
Changed
- An unknown command prints its did-you-mean hint and
Run 'mkio --help'instead of the whole usage under it. - An argument error prints the command's usage line, folded to 80 columns, and
Run 'mkio <command> --help' for details. - A command given too few arguments prints its own help and exits 1.
Errors still go to stdout with exit 1. See the README section "Help".
mkio 1.3.0
mkio 1.3.0 keeps a service's live updates flowing when one peer is slow or gone. A minor release: two config keys and one envelope field, all additive, with wire protocol 1.2 → 1.3. No change to existing config, commands, the expression language or the on-disk layout.
Fixed
- A service listener sent each change to its subscribers in turn and awaited every send, and websockets had no heartbeat. One peer that stopped reading — a laptop asleep with a page open, a frozen tab — held up every other subscriber of that service once its socket buffers filled, which takes real traffic, so it showed up only after volume. When that peer then disconnected,
on_unsubscribereplaced the subscriber list while the listener was suspended in the send,self._subscribers.remove(sub)raisedValueErroragainst the new list, and nothing guarded the listener task. It died silently: snapshots kept working, but live updates never came again — for any subscriber, across page reloads — until the server was restarted. Seen from mkfix as a blotter that "stops updating" after many orders and fills. - Listeners in all three services now log what an event raises and go on; dead subscribers are pruned by identity against the current list.
- Query subscribers are pending from before the snapshot read, so a change committed while the snapshot is read, sent or paged is buffered rather than lost; a buffered insert of a row the snapshot carried arrives as an update. Stream and SubPub register right after the snapshot send.
- Query pagination state is found by
(ws, subid): two pages that both name a subscriptionmkui-table-3no longer clobber each other's page sequence.
Added
- Per-connection outbox (
OutboxWebSocket): sends queue on the connection and return, and one task per connection writes, so a listener never waits on a peer. Covers query, stream, subpub, monitors and custom services. ws_send_buffer_mb(default 16): a backlog past it closes that connection with 1013 so the client takes a fresh snapshot. Frames over a sixteenth of the buffer — snapshots — are not counted, or a large snapshot would trip the limit on the first update behind it and the reconnect would loop.ws_heartbeat_s(default 30, 0 = off) drops peers that stop answering.- A full change queue no longer drops silently:
ChangeBusmarks it and the service resyncs — SubPub and a Query over joined tables re-run their SQL and publish what differs; a plain Query and a Stream (buffer reloaded) reset their subscribers. - A reset is a nack with
"code": "reset". The JS and Python clients answer it by subscribing again, three times in a row at most, instead of deleting the subscription.
See the README section "Slow peers, lost places and resets".
Install: pip install mkio==1.3.0 — https://pypi.org/project/mkio/1.3.0/
mkio 1.2.1
mkio 1.2.1 stops without waiting out a stranded reader. A patch release: no change to the config format, wire protocol, commands, clients, expression language or on-disk layout.
Fixed
Database.stop()ran the finalwal_checkpoint(TRUNCATE)before closing the read connection. The checkpoint waits for every reader to leave its snapshot, up to SQLite's 5 s busy timeout, and a query cancelled at shutdown while itsexecute()was queued to the reader's thread left its cursor alive in that thread's locals, holding a snapshot until the connection closed.stop(), and so Ctrl+C, then took several seconds at random — seen on Windows 11 with mkfix, whose engine writes session state during shutdown. The read connection now closes before the checkpoint; the write connection still closes last. The on-disk result is unchanged: every commit present,integrity_checkclean, no-walleft behind.TestStopWithStrandedReaderguards it.
Install: pip install mkio==1.2.1 — https://pypi.org/project/mkio/1.2.1/
mkio 1.2.0
mkio 1.2.0 adds mkio --version. A minor release: a new CLI flag, with no change to the config format, wire protocol, existing commands, clients, expression language or on-disk layout.
Added
mkio --version(or-V) prints the installed version, asmkio 1.2.0, and exits 0. It works alongside--tracebackand is listed in the usage text. When mkio runs from a source tree that is not installed, it printsmkio dev.
Install: pip install mkio==1.2.0 — https://pypi.org/project/mkio/1.2.0/
mkio 1.1.1
mkio 1.1.1 makes Ctrl+C stop the server at once on Windows. A patch release: no change to the config format, wire protocol, CLI, clients, expression language or on-disk layout.
Fixed
- On Windows, 1.1.0's selector loop took seconds to stop on Ctrl+C, and would have taken minutes with no client connected. A signal there only sets a flag, and
select()is not interrupted by one, so the runner's SIGINT handler ran only once the next timer or packet ended the wait.mkio.app.WindowsSelectorEventLoopis the selector loop plus the wiring the Proactor loop has: its self-pipe is registered as the signal wakeup descriptor at construction and unregistered on close, so the C-level handler's byte ends the wait itself.loop_factoryhands it out wherever it chose the selector loop on Windows.
Install: pip install mkio==1.1.1 — https://pypi.org/project/mkio/1.1.1/
mkio 1.1.0
mkio 1.1.0 adds the top-level event_loop config key and runs on asyncio's selector loop on Windows by default. A minor release: an addition to the config format, no change to the wire protocol, CLI, clients, expression language or on-disk layout.
Added
event_loopconfig key:"auto"(default),"selector","proactor"(Windows only, for servers that need more than the 512 socketsselect()handles there) or"uvloop"."proactor"is refused at config load anywhere but Windows.mkio.app.loop_factory(event_loop, platform)resolves the key: uvloop where installed, else the selector loop on Windows, else asyncio's default.
Changed
MkioApp.run()builds its loop throughasyncio.Runner(loop_factory=...). uvloop is no longer installed as a process-wide policy (set_event_loop_policyis deprecated in Python 3.14), so a plainasyncio.new_event_loop()afterrun()is still asyncio's own.
Fixed
- On Windows,
run()printed aConnectionResetError: [WinError 10054]traceback for every connection a browser opened ahead of a page load and dropped. The Proactor loop tears a transport down with ashutdown()the peer's reset makes Windows refuse; the selector loop closes the socket quietly. The server kept working either way; the noise was the bug.
Install: pip install mkio==1.1.0 — https://pypi.org/project/mkio/1.1.0/
mkio 1.0.1
mkio 1.0.1 runs on Windows 11 with the standard CPython 3.11+ interpreter. A patch release: no change to the config format, wire protocol, CLI, clients, expression language or on-disk layout.
Fixed
MkioApp.run()andmkio servebound the port and then exited on Windows, because the asyncio ProactorEventLoop has noadd_signal_handler.run()now falls back to the cancellationasyncio.run()delivers on Ctrl+C, stops the server and returns normally, the same as after SIGINT on Unix.- A drive-rooted seed path such as
C:/data/seed.csvwas treated as relative to the config file. AnythingPath.is_absolute()accepts is now absolute, and a.\prefix means the current directory like./. - The CLI's colored
subpub/stream/query/monitoroutput printed raw escape sequences in the legacy Windows console. The console is switched to virtual-terminal mode on startup (Windows Terminal already rendered them). - The
[config]route's traversal guard compared path strings by prefix, so a sibling directory such as/configs2passed as/configs. It now usesPath.is_relative_to.
uvloop stays Unix-only; pip install mkio[fast] skips it on Windows and run() tolerates its absence.
Install: pip install mkio==1.0.1 — https://pypi.org/project/mkio/1.0.1/
mkio 1.0.0
mkio 1.0.0 marks the public surface as stable. From this release on, mkio follows Semantic Versioning strictly: removing or changing the meaning of anything on the covered surface is a major bump, backward-compatible additions are minor, fixes are patch.
Covered surface (see the README's new Versioning section for the full list)
- The TOML config format and the meaning of its keys
- The WebSocket protocol, HTTP endpoints, and the
protocolversion reported by_mkio - The
mkioCLI: commands, arguments, flags, exit codes - The Python exports of
mkioandmkio.client, and the JS clients served at/mkio.jsand/mkio-expr.js - The expression language, pinned by
tests/expr_cases.json - The on-disk layout:
_mkio_ref,_mkio_version, the__historysuffix, archive CSV and manifest formats
Not covered: properties marked Unstable (MkioApp.db, .writer, .change_bus, .services), _-prefixed internals, exact log and error text, and human-oriented CLI output.
Compatibility note: the _mkio version check uses caret semantics, so a client expecting mkio = "0.10.0" reports incompatible against a 1.0.0 server. Update the expectation to 1.0.0.
No functional changes since 0.10.0.
Install: pip install mkio==1.0.0 — https://pypi.org/project/mkio/1.0.0/