Changelog
All notable changes to taskwire — published as taskwire on both PyPI and npm — will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[0.4.1] - 2026-10-20
Changed
- The optional peer dependencies
@dynamicforms/vuetify-inputsand@dynamicforms/vuetify-modal-form-kitare^0.15.0and^0.11.0, the releases whose error codes carry their namespaces.
[0.4.0] - 2026-10-20
Added
ERROR_NAMESPACE(taskwire) andERROR_NAMESPACE_FASTAPI_VIEWSETS(fastapi-viewsets), exported fromtaskwirein Python and TypeScript: the namespaces of the error codes taskwire declares and forwards. The Error codes guide documents the code grammar, every code taskwire writes and mapping namespaces to translation keys.- Code constants exported from
taskwirein both languages:DISPATCH_FAILED,NOT_EXECUTED,DIALOG_TIMEOUT,INTERRUPTED,HTTP_STATUS,UNHANDLED_EXCEPTION,FAILEDandCANCELLED. The TypeScriptDISPATCH_FAILED,NOT_EXECUTEDandHTTP_STATUShold the namespaced codes. exception_name()/exceptionName(): the snake case of a class name, the same in both languages, andunhandled_exception_error()(Python).taskwire.rest.NOT_FOUND,REQUESTED,ALREADY_TERMINAL,HOLDS_AN_UNCOLLECTED_RESULT,NO_UNCOLLECTED_RESULTandALREADY_ANSWERED: the RESTdetailcodes.- The
422of an invalid dialog reply carriescodetaskwire.invalid_reply(INVALID_REPLY) andparams.reasonwith what failed;detailkeeps the English text.
Changed
Breaking: every error code taskwire writes carries the
taskwire.namespace, in store documents, pushes, REST responses and the conformance fixtures.message.keyof each equals its code:Old New dispatch_failedtaskwire.dispatch_failednot_executedtaskwire.not_executeddialog_timeouttaskwire.dialog_timeoutinterruptedtaskwire.interruptedhttp_statustaskwire.http_statusfailed(TaskwireMiddleware.on_transition(), executor error without a code)taskwire.failedthe exception's class name, e.g. ValueErrortaskwire.unhandled_exception,message.params.exception: "value_error"Breaking: the REST
detailcodes carry the namespace:not_found,requested,already_terminal,holds_an_uncollected_result,no_uncollected_result,already_answeredandcancelledaretaskwire.not_foundand so on.Breaking:
WIRE_VERSIONis2. Envelopes, snapshots, registers and thewatchreply carry"v": 2.OperationFailed.codeof acancelledoperation istaskwire.cancelled, of afaileddocument without anerrortaskwire.failed; both werefailed.The
error.codeTaskwireMiddlewarewrites for a deferred viewset call the executor failed around is fastapi-viewsets' code, verbatim:fastapi-viewsets.action_failed.The Python extras
viewsetsandmuxwsrequiredynamicforms-fastapi-viewsets>=0.12.0; the optional npm peer@dynamicforms/fastapi-viewsetsis ^0.12.0.
Fixed
register_taskwire_rest's endpoints answer an error with the bodytaskwire.restbuilds. The409and410of a dialog reply carry the settleddialog(TW-DLG-010), over REST and over muxws; previously the body helddetailonly.- The OpenAPI schema of the six endpoints declares each error status with
ErrorBody. RedisStorebuilt from a URL keeps a reference to the task that closes a loop's client when the loop ends. Previously a garbage collection while the loop ran could destroy that task, which dropped the loop's client without closing it and left its connection open; the loop's next call opened a second client.RedisStore.aclose()cancels that task of the running loop along with closing its client.
[0.3.2] - 2026-10-19
Fixed
RedisStorebuilt from a URL closes and forgets the Redis client of an event loop when that loop's tasks are cancelled at its end. Previously every loop that used the store, such as eachasyncio.run(),async_to_sync()or synchronousoperation(), left its client and an open connection in the process until it exited.- The synchronous forms of
operation()and of tracked calls cancel the remaining tasks of their private loop before closing it, asasyncio.run()does.
[0.3.1] - 2026-10-19
Fixed
- Every entry in the npm export map declares its types per module system:
importresolves to a.d.ts,requireto a.d.cts. Resolvers that read conditions only find the declarations oftaskwire/vue,taskwire/vuetify,taskwire/muxws,taskwire/fastapi-viewsetsandtaskwire/conformance. - The declaration files resolve under
moduleResolution: node16andnodenext: every relative specifier in them carries its extension. Previouslytaskwire/muxwsand the other subpaths exported nothing there.
[0.3.0] - 2026-10-18
Added
tw.busy,tw.onBusyChange(),tw.attachCalls(source)andtw.retryNow(): the calls a page waits on, from every attachedCallSource, as oneBusyState— whether a non-background call is older thanprogressUi.delay, the progress of the oldest such call whose operation reports it, and whether a call has gone unansweredunreachableAftertimes. A listed call whose key is a UUID is followed astw.trackCall()follows one. taskwire renders nothing from it on its own.axiosCallSource(http): the requests of an axios instance as aCallSource; a request config withbackground: trueis a background call.configure_taskwire({ unreachableAfter }), 5 by default.DialogPresenter: the framework-free half of a dialog adapter - which open question to present, in which tab and when (TW-TAB-003), and whether a returned button is an answer. An adapter supplies onlypresent(operation, dialog);useTaskwireDialogsisDialogPresenterwith the modal kit.taskwire/fastapi-viewsets:viewsetCallSource(), every fastapi-viewsets proxy call as aCallSource, with fastapi-viewsets'retryNow().- Pushes over fastapi-viewsets events:
taskwire.contrib.viewsets_events.register_taskwire_events()publishes every envelope as a fastapi-viewsets event, to a namespace topic or, for a private operation's progress, to the topic of the socket that started it. Its authorizer admits a namespace topic to callers of that namespace and a connection topic to that socket only, onsubscribeand onpoll; its delivery filter appliespush_filterand the socket's overrides. The fastapi-viewsets event store replacesRedisReaderandtwx:{ns}, and a worker publishes through it. taskwire/fastapi-viewsets:attachViewsetEvents(tw, peer),detachViewsetEvents(tw)andViewsetEventsTransport, which receive taskwire's pushes through the peer's fastapi-viewsets event client without importingtaskwire/muxwsor installing a stream handler.- The
watchreply carriestopicswhile the server carries taskwire over fastapi-viewsets events. TaskwireTransport.crosses_processes; with a transport that sets it,RedisStore.publishdispatches in process andstart_taskwire_reader()starts no reader.configure_worker(store, transport=None)accepts a transport whosecrosses_processesis True.ClientTransport.onResync, on which the client reads the register and every polled token once.taskwire/vue:useBusy(client?),tw.busyas a ref.taskwire/vuetify:useBusyDialog(options?), one modal through the modal kit whiletw.busyis visible, andTaskwireBusy, its default body, with Try now while a call is unreachable.TaskwireMiddleware.executor_started(): configures the executor's process withtaskwire.configure_executor().TaskwireMiddleware.on_transition(): ends the operation of a deferred call the executor ended outside its execution —interrupted, ordialog_timeoutwith an open question, for a hard time limit, a termination or a lost process;dispatch_failedfor a call stillqueuedthat will not run;failedwithaction_failedfor a failure around the execution.- The reserved error code
interrupted,retryable: true, withmessage.params.reason: the executor ended the work after it started. taskwire.end_call(call, error, *, queued_only=False, asking_error=None): ends the operation of a call whose execution ended without a terminal write.taskwire.calls.interrupted_error(reason)anddialog_timeout_error()build the two errors.taskwire.configure_executor(*, store=None, transport=None): configures a process that executes work handed to it, with a transport that pushes to no socket.configure_workerapplies it.
Changed
- The
viewsetsandmuxwsextras requiredynamicforms-fastapi-viewsets>=0.11.1.taskwire.contrib.viewsetsandtaskwire.contrib.viewsets_eventsimport no Celery: the process that executes deferred actions callstaskwire.configure()andtrack_viewset_calls(). TaskwireMiddleware(timeout_errors=...)defaults to(ActionInterruptedError,). AnActionInterruptedErrorends the operationinterrupted(dialog_timeoutwith an open question); the web process leaves the operation of a call that ended with it, or with anyActionOutcomeUnknownError(ResultReaderStoppedErrorincluded), to the executor's process.- An execution whose executor process exits writes no terminal state;
on_transition()ends it. mark_failedwithdraws the operation's open dialogs.connection_idandCONNECTION_TAGare defined intaskwire.delivery;taskwire.contrib.muxwsre-exports both.- The optional peer dependency
@dynamicforms/fastapi-viewsetsrequires ^0.11.0, and@dynamicforms/vuetify-modal-form-kitrequires ^0.10.0.taskwire/vuetifyimports@dynamicforms/vuetify-inputs^0.14.2, a new optional peer dependency that the modal kit requires as well. package.jsondeclares"sideEffects": false, so a bundler drops every module of the package the application does not import.- The declaration files under
dist/follow the source layout: a feature with UI code has its declarations underdist/<feature>/base/anddist/<feature>/ui/, and shared UI declarations underdist/ui/.dist/composables/anddist/vuetify/are gone. The entry declarations (dist/index.d.ts,dist/vue.d.ts,dist/vuetify.d.ts,dist/muxws.d.ts,dist/fastapi-viewsets.d.ts,dist/conformance.d.ts) and every published subpath are unchanged.
Fixed
RedisStorebuilt from a URL opens one Redis client per event loop that uses it. A store used on a second loop of a process - a prefork child forked after its supervisor used the store, each synchronousoperation()- no longer fails with a client bound to another loop.RedisStore.await_dialogblocks inBLPOPfor at most half the client'ssocket_timeout, so redis-py's read timeout (5 s by default in redis-py 8) no longer fails a pendingask()withTimeoutError.useTaskwireDialogsstates the buttons of a dialog without a mapped component asActions, as the modal kit'sactionsoption takes them, and answers nothing when the modal settles with the kit's own Close action.
Removed
TaskwireProgressDialog.useBusyDialog()presents slow calls;TaskwireOperationslists operations.
[0.2.0] - 2026-10-16
Added
- Tracked calls: a request or command that carries
X-Taskwire-Token, or a UUIDIdempotency-Keyon a method other thanGET,HEADandOPTIONS, is an operation under that token, reported fromqueuedto its terminal state.taskwire.callsholds the framework-free primitives —call_token,Call,receive_call,execute_call,hand_off,status_error— andts/call-tracking.tstheir TypeScript twinscallToken,receiveCall,executeCall,callFromCarriedandstatusError. - Adapters for tracked calls:
taskwire.contrib.asgi.track_calls,taskwire.contrib.wsgi.track_callsandtrackFetchfor a WHATWGfetchhandler. taskwire.contrib.viewsets.TaskwireMiddleware, the binding for fastapi-viewsets 0.9.0: it receives a call in the command middleware chain and executes it where the action executes, in the web process or in the Celery worker.track_viewset_calls()lists it and the context processor.@action_configuration({TaskwireMiddleware: {"result_kind": kind}})declares an action's result kind and{TaskwireMiddleware: False}leaves it untracked.OperationScope.hand_off()(Python) andOperationScope.handOff()/failWith()(TypeScript).- The reserved error codes
not_executedandhttp_status. tw.trackCall(call, handler, options?)follows the operation undercall.keyand removes the handler when the operation ends;tw.awaitCall(call, handler, options?)does the same and resolves with the call's outcome.KeyedCall<R>is the type ofcall.tw.headers(): the headers a request made now carries, for an HTTP client other than axios.attachMuxwsregisters an open interceptor on the peer: every stream opened on it carriesX-Taskwire-Connectionand, insiderun(),X-Taskwire-Token.detachMuxwsremoves it.taskwire/vuetify:TaskwireOperations, a list of every operation intw.registerwith select, cancel, collect and dismiss; andTaskwireProgressDialog, a dialog overtw.operationsthat shows no operation ending insideprogressUi.delay.
Changed
register_taskwire_resttracks the application's viewset calls: it listsTaskwireMiddlewareunless called withtrack_calls=False. A viewset action called with a token runs insideoperation()for that token, which ends when the action returns.taskwire_context_processorputscall_token()of the request undercontext["taskwire_token"].DISPATCH_FAILEDandDIALOG_TIMEOUTare defined intaskwire.calls;taskwire.contrib.celeryre-exports them.- The
viewsetsandmuxwsextras requiredynamicforms-fastapi-viewsets >= 0.9.0, and themuxwsextra requiresmuxws >= 0.4.1. The npm package's optional peer dependencies require@dynamicforms/fastapi-viewsets ^0.9.1andmuxws ^0.4.1. MuxwsClientTransportpasses a stream whose headers carry:eventto fastapi-viewsets'handleEventStream, so one peer carries taskwire's pushes and the fastapi-viewsets event client's events.
Removed
taskwire.contrib.celery.wrap_sync_runner,register_taskwire_celery_viewset,TOKEN_KWARGandSESSION_KWARG. fastapi-viewsets 0.9.0 has no Celery kwargs or dispatch hooks; acelery_viewsetaction is reported byTaskwireMiddlewarein the worker.
[0.1.0] - 2026-08-23
Added
- Initial release: progress reporting and awaitable dialogs for long-running operations, one protocol implemented three ways — a local in-process transport, REST, and a WebSocket transport over
muxws— with the store as the single source of truth and a push as only an accelerator. taskwire.operation()reports progress, nests subtasks, and asks the caller a question mid-run; cancellation raises inside the operation by default.- Collectable results: an operation can park a result for the caller to collect or dismiss instead of returning it inline.
RedisStorefor a cross-process deployment, and a Celery worker entry point (wrap_sync_runner) for tracking a task dispatched to a worker.taskwire.contrib.viewsets.register_taskwire_rest()publishes the REST API as a fastapi-viewsets viewset, over REST and overmuxwsfrom one registration, including a context processor and Celery viewset hooks for tracking a Celery-dispatched viewset action.taskwire.contrib.asgimounts the same protocol with nothing installed but Python, for a framework taskwire has no adapter for.- The TypeScript package ships the whole operating half, not just a client:
ts/store.ts,ts/reporter.tsandts/operation-scope.tsmake a browser-only deployment first-class. - Vue components (
taskwire/vue) and Vuetify components (taskwire/vuetify) for rendering the register, dialogs and a footer progress bar.