solo 1.0.0
solo: ^1.0.0 copied to clipboard
One job at a time: sequential jobs with exclusive state ownership, declarative rules and cooperative cancellation. Pure Dart.
1.0.0 #
This package re-exports async_job whole, so the changes of the core are this
package's too: see
the async_job changelog.
Breaking changes #
ctx.waitofasync_job, deprecated in favour ofctx.abandonablein 0.3.0, is removed.Policy.replaceis renamedPolicy.keepLatest. The name says what stays in the queue: the latest of the queued jobs with that key. A removed job still endsCancelled(replaced)with aReplacedCancelReason, andAccumulationPolicy.replacekeeps its name.HandlerCancelReasonofasync_jobis renamedBodyCancelReason, and itsnameisbodyinstead ofhandler.SoloContext.eachreturnsSoloJob<void>, asSoloContext.childdoes. A class that implementsSoloContextitself returns that type too.
Added #
Solo.debugprintsdropwith the job and itsCancelledwhen the rules of its working type turn a queued job down at its start.
Documentation #
- The README says what each kind of change comes in from 1.0.0 on: a fix in a
patch release, an addition in a minor one, a break in a major one, and that
solocomes out with each minor release ofasync_job, whose engine protocol it builds on. - The README names the analyzer plugin
solo_lintsinInstall, and the page of common mistakes says that the plugin reports most of them. - The diagnostics page says what its observer prints, and that an observer whose log leaves the device prints only what the application chose to send.
- The quick start of the README fits one screen: a controller, a load, and a
refresh cancelled before its answer, with what the program prints;
example/bin/quick_start.dartruns it. The controller with its key, policy, state handlers and expected errors, which the guides build on, opens the jobs page as The profile controller. - The jobs page ends with A tap that does not wait for a reload: the likes of a feed in a controller of their own, so that a like does not wait behind a reload of the feed.
- The README says that a new value of an enum or a new subclass of a sealed
class comes in a major release, names the enums and sealed classes of the
package, and says who
package:solo/listeners.dartis for. - The README says that a job is not a
Future, and that a job the rules turn down at its start endsCancelledwith aRulesCancelReason, notFailed. - The reference page lists
cancelAllwithforceandreason, the fields ofSoloPending,SoloPhaseandSoloUnansweredHandler. - The pages of cancellation, resources and errors give the whole route of an
error nobody answers for:
onUnanswered, thenSolo.unansweredHandleror the zone. The errors page nameskeepWhileasked at the start among the start rules, and says where the error goes on in a Flutter app. - The pages of jobs, streams, children, accumulation, camera and mistakes say
what a reader of the examples needs: that
queue,lastJobWhereandcurrentare protected, thatctx.eachreturns aSoloJob<void>, what_Opis, why an accumulator lives in alate finalfield, which line of the camera cancels the job, and that a catch publishing a failure state gains nothing on a cancelled job. - The cancellation page says that an
uncancellablesection holds a deadline as well, and that a job made withcancellable: falsetakes notimeout: a call that gives both throwsArgumentError.
0.6.0 #
Breaking changes #
- Dart 3.10.0 is the lowest SDK the package supports, up from 3.6.0.
Solo.job,Solo.enqueue,Solo.collectandSolo.accumulatetake the namedisExpectedError. A subclass that overrides one of them must accept it.
Added #
isExpectedErrornames the errors a job expects:onErrorhears them withJobErrorAnswer.never, and nobody is asked to answer for them, while a bug still goes to the answerers and the zone. See Naming the expected errors.
0.5.0 #
Breaking changes #
Solo.onErrorandSoloObserver.onErrortake{required JobErrorAnswer answer}, asJobObserver.onErrorof the core does. See Reporting an error.SoloContextgainschild, thectx.childof the core: it makes a job of the same controller with the working type andkeepWhileof the job that made it. Code that holds only aJobContextmakes with it a child the controller accepts, where aJob.deferredis refused. A class that implementsSoloContextitself adds it. See Children and chains and theasync_jobchangelog.
Documentation #
- A delayed start shows how to start a job later
without holding the queue, with a wait that
cancelAllandclosetake back. - A notice the screen accepts shows news that waits on the screen until the user closes it, without holding the work of the controller.
0.4.0 #
This package re-exports async_job whole, so the changes of the core are this
package's too: see
the async_job changelog.
Documentation #
- When a group is ready, which group an event joins, and how a group's job
starts, is cancelled, fails and ends on
close()left Event accumulation for Accumulation rules.
0.3.0 #
This package re-exports async_job whole, so the changes of the core are this
package's too: see
the async_job changelog.
Breaking changes #
What to write instead, with the 0.2.0 name on the left; each has its entry
below.
0.2.0 |
0.3.0 |
|---|---|
SoloBase<S> |
Solo<S> |
Solo<S>, the class with stream |
Solo<S> with SoloStream |
controller.state |
controller.currentState |
onError: of run and job |
ifFailed: |
onCancel: of run and job |
ifCancelled: |
run(body) |
enqueue(body) |
add(job) |
enqueueJob(job) |
Job<T> of a helper handed to ctx.run |
SoloJob<T> |
onChange(previous, current) |
onChange(transition) |
SoloObserver.onChange(solo, previous, current) |
SoloObserver.onChange(solo, transition) |
SoloQueue.lastWhere |
Solo.lastJobWhere |
Job.ignore() |
Job.ignoreFailure() |
JobBase.debug |
Job.debug |
Solo.onErroris a notice, and the newSolo.onUnansweredanswers for an error nobody else has: one no outcome carries, and aFailednobody observes. To keep such an error out of the zone, setSolo.unansweredHandler, aSoloUnansweredHandler, or overrideonUnanswered: settingSolo.observer, or overridingonErrorwithout callingsuper, no longer does it. AFailednobody observes goes to the handler or to the overriddenonUnanswered, a microtask after the job ends, and reaches the zone, as in0.2.0, only when neither is set. So does the failure of acanStartthat threw. See Answering for an error.Job.ignore()is renamed toJob.ignoreFailure()inasync_job.JobBase.debugisJob.debuginasync_job, which no longer exportsJobBasefrom its main library.- A job is continued once, by one
thenor by handles ofshared(), inasync_job: a secondthenon a job ofenqueuethrows aStateError. A jobPolicy.droppablemay hand to another caller, and the job of an accumulator group, refuse a plainthenfrom the first call: every caller continues it through a handle ofshared(). See Queue and policies and Start, cancellation and errors. - The state handlers of
enqueueandjobare renamed:onErrortoifFailed,onCanceltoifCancelled. SoloBaseis renamed toSolo, and the formerSolo— the class that carried the broadcast stream — becomes the mixinSoloStream. A controller whose.streamis read addswith SoloStream. See Observing state.Solo.runis renamed toenqueue, andSolo.addtoenqueueJob.runof the controller put a job at the tail of the queue, whilectx.runstarts a child at once, past the queue;addshared its name with theaddof an accumulator, which takes an event. See Creating and scheduling jobs.job,enqueueJob,enqueue,collectandaccumulateare@protected. Give the controller a method for each operation called from outside. See Creating and scheduling jobs.SoloStream.publishis@protected.- The hooks
onStart,onFinish,onError,onUnanswered,onLog,onChangeandonCloseare@protected. SoloJobis aChildJob, the typectx.runandctx.runAllofasync_jobtake now. A job ofjob(...)held as aJobno longer compiles there: type the helper that makes itSoloJob, asSoloJob<void> _recordPath(String path) => job(...). A continuation ofthen, whichctx.runrefused with anArgumentError, no longer compiles there either. See Chaining completed work.- The controller's synchronous read is
currentState, notstate. A body readsctx.state. See WhycurrentStateand notstate. - The state of a closed controller is final. Once the engine has finished
closing,
externalSetStatethrows aStateError: the newSolo.isFinishedis the guard, and the new hookSolo.onCloseis the last place a write goes through. See externalSetState. Solo.closetakes aSoloCloseMode. The default,SoloCloseMode.cancel, is whatclosealways did;SoloCloseMode.drainruns what is already queued instead of dropping it. See Three ways to stop.- The change hooks take a
SoloTransition<S>instead of a pair of states:Solo.onChange(transition)andSoloObserver.onChange(solo, transition). - A subclass with a member named like a new member of
Solostops compiling, or overrides it. The new instance members arecurrentState,isFinished,isDraining,pending,enqueue,enqueueJob,onClose,onUnanswered,addListener,removeListener,hasListenersandonListenerError. - A class that implements
SoloContextitself addsabandonable,heldCancellation,pause,runAllandstart, the new members ofJobContextinasync_job, itsruntakesdisposeanddiscard, and itseachtakeskeyanddescribe. Policy.droppablerefuses a key held by a job of another result type with anArgumentError, and refuses before it takes the new job. See A key for each request.- A job dropped by
Policy.droppableends with the newDuplicateCancelReason, not aManualCancelReason. - A job that
Policy.replaceorPolicy.restartputs a newer one in place of ends with the newReplacedCancelReason, not aManualCancelReason. collectandaccumulatedefault toAccumulationPolicy.join, notadjacent. A job of another kind queued between two events is no longer a boundary; nameadjacentwhere it has to be one. See Commands where only the last one counts.AccumulationPolicy.replacemoves the waiting group's job to the tail instead of building a new job and cancelling the old one. Every event of one group now shares its handle and outcome under every policy.SoloQueue.lastWhereis gone.Solo.lastJobWherefinds the same job and looks at the running one as well.cancelAllandSoloQueue.remove,removeWhereandcleartake areason.SoloQueue.jobsandSolo.currenthand outSoloJob<Object?>, and the predicates ofSoloQueue.removeWhereandSolo.lastJobWheretake one. A predicate written forJob<Object?>compiles as it did; an override ofcurrentorlastJobWhereand a class that implementsSoloQueuetake the new types.job,enqueue,collectandaccumulatetaketimeout. An override passes it on tosuper, orenqueue(timeout: ...)loses its deadline without a word.collectandaccumulatetakeifFailedandifCancelled. An override passes them on tosuper, or the groups of its accumulators lose their state handlers without a word.
Deprecated #
ctx.waitinasync_jobis deprecated in favour ofctx.abandonableand goes away in the next release.
Changes you will see on upgrade #
Errors that 0.2.0 lost, or kept to the hooks, now reach
Solo.unansweredHandler, or, with no handler set, the zone the job was created
in. In a test that zone is the test's, and the test fails:
- an error no outcome carries, in an application that sets
Solo.observeror overridesonErrorwithout callingsuper; - the failure of the body of a job that ends
Cancelled: an operation that stops at a token the job cancels throughctx.onCancelthrows one on every cancellation.Job.isCancellationErrornames the errors a cancellation causes, once, inmain, and they reachonErrorand go no further;job.ignoreFailure()silences the failure of that job alone; - the late failure of an action the body walked away from through
.timeoutorFuture.any, arriving once the job is over; - the failure of a source's own cleanup when
ctx.eachlets go of a stream.
A job that gives up through ctx.check() inside a call ctx.abandonable has
let go of no longer reaches onError as a failure.
onError of the controller and of the observer hears each failure and each
Cancelled inside a ParallelWaitError in a call of its own, where 0.2.0
handed it the whole error once. A Failed with such an error that nobody
observes reaches Solo.unansweredHandler, or the zone, failure by failure and
without its cancellations, where 0.2.0 passed the zone the whole error. See
Answering for an error.
A dropped duplicate prints Cancelled(duplicate), a job replaced under
Policy.replace or Policy.restart prints Cancelled(replaced), and several
error messages are reworded.
The stack trace of a job its rules cancelled is taken where the rules noticed.
ctx.each asks the rules of the job before it starts the child, as ctx.state
does. After the job's own emit of a state they refuse, it ends the job
Cancelled by the rules, and ifCancelled does not run, as at any checkpoint
of the rules. 0.2.0 handed back a child the same rules had refused to start:
the stream was never listened to, a job that did not read the child ended
Done, and one that read its .value ended cancelled by the child, with
ifCancelled run. See
Processing a stream.
The body of a queued job runs in the zone the job was created in, with its
deadline and its onStart hooks, where 0.2.0 ran them in the zone of
whatever woke the queue, often another call's: a body awaiting a future of its
own zone waited for good when the future failed while the body ran under a
different runZonedGuarded. See
Creating and scheduling jobs.
keepWhile may not set the state: a write while it answers throws a
StateError, and the rule throws with it, as any rule that throws. 0.2.0 let
the write through and asked the rule again about the new state, and a rule that
wrote every time it was asked ran out of stack. canStart may still write: it
is asked once.
An ifFailed or ifCancelled handler that hands back the state it was given,
or a const the state already holds, publishes nothing and leaves in place a
write it made itself, where 0.2.0 published that state again as a change.
The lines of Solo.debug about a job being queued begin with enqueue, not
add.
Added #
ctx.startofasync_jobstarts a child the body does not wait for. The rules of the controller decide as they do forctx.run: a child they turn away endsCancelled(rules), andstartreturns as usual; acanStartthat throws throws fromstart. A failure of the child goes toonUnanswered.timeoutgives a job of the controller a deadline of its own.job,enqueue,collectandaccumulatetake it; when it runs out, the job endsCancelled(timeout)with the newTimeoutCancelReasonofasync_job. See A deadline of a job.collectandaccumulatetakeifFailedandifCancelled, the state handlers ofjob. A group that wrote a state of its own while it ran takes it back through them when it fails or is cancelled; it calls one of them at most once, however many events it holds. See Start, cancellation and errors.Job.isCancellationErrorofasync_jobnames the errors a cancellation causes. A failure of a cancelled job that it names reachesonErrorand goes no further: neitheronUnanswerednorSolo.unansweredHandlerhears it. See The token.Job.whenFinishedofasync_jobhears the end of a job without observing its outcome. A listener gets the outcome, and a failure nobody observes is still answered for. See Jobs and results.ctx.eachof a controller takes thekeyanddescribeofasync_jobto name its child. Thekeyis a name only: queue policies govern root jobs, and the child is not one.SoloObserver.allmakes one observer of several forSolo.observer. See A cancellation that never lands.Solocarries its own listeners:addListenerandremoveListener, and the protectedhasListenersandonListenerErrorbeside them. See Observing state.Solo.pending: what is holding the controller, for aclosethat has not come back, ornullwhen nothing does. It is aSoloPending, one of three: aSoloPendingJobwith itsSoloPhase, aSoloPendingQueueor aSoloPendingStream. See What is holding the controller.AccumulationTiming.throttletakesstartAtOnce. WithstartAtOnce: falsethe interval is counted before the first group as well.AccumulationTiminghas==,hashCodeand atoStringthat prints the kind, the duration andstartAtOncein the shape of the constructor.package:solo/listeners.dart:Listeners, the list of listeners a notifier walks, for a package that builds a delivery of its own on top ofsolo.Solo.debugnames the running job on the line of a job queued while it runs:while Job(outer) is running: it starts after Job(outer) ends; the line of a duplicate thatPolicy.droppablehands back says so too, and a duplicate of the running job endswhich is running: if awaited from inside Job(x), it waits for itself.
Fixed #
- A state
canStartwrites while it answers is held toWandkeepWhile: the job started under it even when it no longer matched them. Policy.replaceandPolicy.restartqueue the new job before they cancel the jobs it replaces. A job of the same key thatonFinishorwhenCancelledqueued as a replaced job ended came in ahead of the new job, and both ran: the last request lost to the one before it.- A child that its own
canStartcancels while it answers yes reaches its parent as its cancellation, as a refusal does: the parent endedFailedwith aStateErrorthat the child had already finished. - A child that a start rule of its own runs again is refused with a
StateError: the parent stopped waiting for the child, whose body went on running, and the child'sonDisposenever ran. Policy.droppableandSolo.lastJobWhereno longer hand back a job that is not going to do the work:cancelAll()and a fresh request a line later answered the second call with the handle the first had just cancelled.cancelAlland the removals ofSoloQueuereach the job the engine is about to start: called from that job's start rules, they missed the job, which started anyway.Policy.droppable,replaceandrestartreach the job the engine is about to start: called from that job's start rules with its key, they missed it, and two jobs of one key ran one after the other.- A
publishthat throws no longer holds back the changes queued behind it, and the rules of the running jobs are asked all the same: a job the new state rejects is cancelled. - A predicate of
SoloQueue.removeWhereorSolo.lastJobWherethat removes a job no longer walks into aConcurrentModificationError. - A finished job lets go of its body, of its state handlers and of the job that ran it: one kept handle held the whole tree of finished jobs it came out of.
- A job one controller's
ctx.unattendedwork starts on another controller starts in the zone the work was started from: a bareunawaitederror in its body reached theonErrorof the controller whose job started the work. - A
Cancelleda hook of the controller or of the observer throws no longer reaches the zone.
Documentation #
- The README is a starting page, and the reference moved to
doc/, one page per subject: Jobs and the queue, State, Cancellation, Resources and cleanup, Children and chains, Streams, Diagnostics, Errors, Testing and Camera example. The Flutter section went toflutter_solo. - Two pages to look things up: Reference, every member of
Solo, its context, jobs, queue and accumulators and every parameter ofenqueue,job,collectandaccumulatewith the section that explains it, and Common mistakes, seven mistakes and the right way for each. - One recommendation of
0.2.0is replaced: a source of external states is stopped inonClose, not beforeclose, and a callback that can still arrive after that guards its write withisFinished. - The camera example no longer sticks in
Preparingwhen an opening is cancelled half way. - The camera example no longer publishes
Readyover a failure the hardware reports while the camera opens. - Each scenario of solo and bloc, side by side ends with a line of what solo gives.
0.2.0 #
The first published release. 0.1.0 never left the tree, so nothing below is a migration anybody has to make; what changed since it is at the end, for a tree that followed the package before it went out.
-
Add
onErrorandonCancelstate handlers torunandjob. They apply state after children and cleanup, before the next queued job, while preserving the failed or cancelled outcome. Incompatible external state changes suppress correction, including during cleanup and for descendants. The quick start now uses sealed loading/result/error states. -
Add
AccumulationTiming.debounceand.throttletocollectandaccumulate. Debounce seals a group after a pause between events; throttle spaces actual group starts. Waiting groups let ready jobs pass, while handlers, children and cleanup remain sequential.collectretains every event;accumulateretains themergeresult. Closing cancels timing timers and queued groups without a final batch. -
Breaking:
ctx.run(child)returns the child's result asFuture<T>. Replaceawait ctx.run(child).valuewithawait ctx.run(child); keep the original child handle to cancel it or inspect its outcome. After success,runchecks the parent's cancellation and state rules before returning. Concurrent starts must handle or explicitly ignore the returned future. Controllerrunandctx.eachstill return job handles immediately. -
Add
collectandaccumulatefactories returningSoloAccumulator: gather events in a list or merge them into one value before execution.AccumulationPolicyselects adjacent grouping, replacement at the queue's tail with data transfer, or joining an existing queued group. Groups use ordinarySoloJoblifecycle, state rules and cancellation. -
Re-export
Job.thenandChainCancelReasonfrom the core. Continuations support cancellation chains and have their ownJobContext; they do not inherit controller state, rules, observers or a queue slot. -
SoloBase<S>engine: one root job at a time, exclusive state ownership. -
Solo<S>with a broadcaststream, delivered on the next microtask. -
Jobs as
asyncbodies with a working typeW,canStartandkeepWhilerules,cancellable: false. -
Keep a replacement cancelled by a synchronous listener out of the queue, so it cannot absorb a later
droppablejob. -
Requires
async_job: ^0.2.0:job.whenCancelled(callback)registers a synchronous listener receivingCancelledand returns an unregister function. -
Job<T>handle:done,value,outcome,whenCancelled,cancel,ignore. -
Outcome<T>:Done,Failed,CancelledwithCancelReason. -
A
Failedoutcome nobody observed goes to the zone that created the job, the way Dart reports an unhandledFutureerror. Readingdoneorvalue, or callingignore, counts as observing it;Cancellednever reaches the zone. -
JobContextof the kernel:check,wait,join,uncancellable,unattended,onCancel,run,each,log. -
SoloContext<S, W>on top of it, where the state lives:state,stateAs,emit. -
A body does not
awaiton its own: every call goes through the context, and the member it picks says what a cancellation does to that call.waitends the waiting and lets the action run on;joinwaits for all of the action and gives up afterwards, so a device call is never left mid-flight;uncancellableholds the cancellation for the length of the call and gives up once the step is over;unattendeddoes not wait at all and hands the work to the engine, which hears it fail even after the job is over. -
ctx.unattended(action)for work a body starts and does not wait for. It runs in an error zone of its own, and whatever it leaves uncaught — now, or long after the job is over — reachesonErrorinstead of the process. -
Behaviour change. With no
SoloBase.observerand no override ofSoloBase.onError, an error with nowhere else to go no longer stops in the empty hook: it goes to the zone the job was created in, the way the core reports one when a job has no observer. That covers a disposer, anonCancelcallback, a late failure of an abandoned call or of unattended work, and acanStartorkeepWhilethat threw instead of answering while the job runs. ACancelledis the one exception and never goes there. Install an observer, or override the hook, and the route is yours again; callsuper.onError(...)from the override to keep it. A rule that throws before the job starts is not on this route at all: it becomes aFailedoutcome and reaches the zone as any unobserved failure does, whatever the observer. -
A cleanup stack instead of a
finallyin the body:ctx.onDisposereleases whatever the outcome,ctx.onDiscardonly when the job does not endDone, andwaitandjointake the same two asdisposeanddiscardfor the value they hand over — a connection or a file an abandoned action opened is still closed. The engine unwinds the stack after the children and before the outcome, andclosewaits for it;ctx.disown(value)takes a registration back when the body hands the value over itself. -
JobContext.uncancellableiswait's counterpart: it runs a step that cannot be taken back — a payment on its way to the server — with the cancellation held until the step is over, andclosewaits for it. -
Breaking:
ctx.each(stream, (child, event) { ... })returns a childJob<void>immediately. Await.valuefor a throwing result, inspect.done, or cancel the subscription separately through.cancel(). The callback receives aSoloContext<S, W>retaining the parent's working type andkeepWhile, without inheritingcanStart. The parent waits for this child even without an explicit await, and observers see the child. Parent cancellation cascades to it. -
Cancelling
eachremoves the subscription immediately, then waits for the running callback before child completion and cleanup. Use child context checkpoints to respond to cancellation; a plainawaitcan delay cancellation andclose(). Source cleanup from subscription cancellation is still not awaited.eachis now a context method and followsrun's restrictions on when and where children can start. -
JobContext.onCancelhands a cancellation to something that can really stop — a device's cancel token, an HTTP abort.waitends the waiting, not the work. -
Child jobs via
ctx.run; a parent finishes after its children. -
SoloQueuewithremove,removeWhere,clear,lastWhere; policiessequential,droppable,replace,restart. -
externalSetStatefor hardware listeners; every change re-evaluates the rules of running jobs. -
SoloObserverand instance hooks;SoloBase.debugengine tracing. -
A hook that throws changes nothing: the engine hands its error to the current zone and carries on, and the hook next to it is still called.
Since 0.1.0 #
- The job kernel now lives in
package:async_joband is re-exported whole:package:solo/solo.dartstays the only import. Code using the old reason API must migrate to the reason classes. The job's lifecycle, context, outcomes and observer moved to the kernel; the state, queue and rules stay insolo. CancelReasonis an extensible class hierarchy:ManualCancelReason,ParentCancelReasonandHandlerCancelReasonfrom the core, plusRulesCancelReasonandClosedCancelReasonfromsolo. Inspect types;nameis only a log label, with no equality by name. Custom subclasses can carry arbitrary data throughjob.cancel(reason: reason). Parent and child propagation retain the original cancellation incause.Cancelled.by({reason, started, description, stackTrace})is public: an engine of a domain builds cancellations with a reason of its own.JobContext<S, W>is renamed toSoloContext<S, W>, and the nameJobContextnow belongs to the interface of the kernel: cancellation, the waiting family and children, without the state.SoloContextimplements it and addsstate,stateAsandemit. Job bodies do not write the name, so code written against the README keeps compiling.- The job and its context are split along the seam the kernel will be taken out
at:
JobBase<T>andJobContextBasecarry the lifecycle, the waiting family, the children and the outcome, and the solo subclasses add the state, the rules and the queue. Nothing moves in the public API of a controller. - The list of children a job waits for now shrinks as they finish: a long-lived
job starting children in a loop no longer grows one entry per child. The
description of a parent's own outcome is unchanged — the link from an outcome
to the child that carried it lives in an
Expando, so a child without a key still shows in it. - A value a body returned after its cancellation had already arrived is
released rather than dropped: the body registers it on the cleanup stack, the
outcome stays the cancellation, and
closewaits for the release. - A job says who may adopt it:
ctx.run(child)refuses a job of another controller withArgumentErrorand a job still waiting in the queue withStateError, and a job ofsolois refused by a context of the bare kernel. A child without an observer of its own inherits the parent's. JobBase.debugtraces the life of a job,SoloBase.debugthe queue, the state and the closing. Set both to follow both.JobObserveris the observer of a single job:onStart,onFinish,onErrorandonLog, without the controller in the signatures. A controller feeds its ownSoloObserverand its instance hooks from it, so nothing changes for a user ofsolo.isQueuedmoves fromJob<T>to the newSoloJob<T>, returned byjob,addandrun. The queue is the controller's, not the job's.JobStatusis public and has three values: a queued job iscreateduntil it starts, andisQueuedis computed from the queue itself — insidecanStarta job taken from the queue is no longer queued.
0.1.0 #
Never published.