@runic-artifex/views-effect
catchCasefunctionfunction catchCase<A, E, R, const H extends CaseHandlers<E>>(self: Effect.Effect<A, E, R>, cases: [ CaseName<E> ] extends [ never ] ? never : H & Readonly<Record<Exclude<keyof H, CaseName<E>>, never>>): Effect.Effect<A | Effect.Success<ReturnType<H[keyof H]>>, Exclude<E, CaseError<E>> | Effect.Error<ReturnType<H[keyof H]>>, R | Effect.Services<ReturnType<H[keyof H]>>>;Handles every case of a declared
$casefailure in the error channel: theViewDomainFailureof acommandoroperationwhose .NET command declares a[RunicUnion]failure. A missing or unknown case is a type error, and the Effect must have such a failure; other errors pass through.const saved = catchCase(command(() => editor.save()), { titleRequired: () => Effect.succeed("A note needs a title."), titleTaken: taken => Effect.succeed(`"${taken.existingTitle}" exists.`), });commandfunctionfunction command<A>(run: () => PromiseLike<A>): Effect.Effect<CommandValue<A>, ViewError | ViewDomainFailureOf<BridgeOutcomeFailure<A>>>;Runs a command, setter, query or checked write. Bridge failures become tagged errors; anything else, such as a
RangeErrorfor an invalid argument, is a defect. A command that declares a failure succeeds with its value and fails withViewDomainFailurefor its declared failure. Interruption abandons the reply only: .NET keeps running a command it received.yield* command(() => editor.save());CommandValuetype aliastype CommandValue<A> = A extends BridgeOutcome<unknown, unknown> ? A extends { readonly ok: true; readonly value: infer V; } ? V : never : A;The success value of a command: for one that declares a failure, the value of its
BridgeOutcome, otherwise what the client method resolves to.connectfunctionfunction connect<C extends ViewClient>(source: ViewConnector<C> | (() => PromiseLike<C>)): Effect.Effect<C, ViewError, Scope.Scope>;Connects a generated client for the lifetime of the current
Scopeand disposes it when the scope closes. Pass a generatedconnect<Name>function or a page reference from a content property.const editor = yield* connect(connectEditor);createEffectActionfunctionfunction createEffectAction<Args extends readonly unknown[], A, E, R = never>(program: (...args: Args) => Effect.Effect<A, E, R>, ...[options]: [ R ] extends [ never ] ? [ options?: EffectActionOptions<A, E, R> ] : [ options: EffectActionOptions<A, E, R> & { readonly runFork: (effect: Effect.Effect<A, E, R>) => Fiber.Fiber<A, E>; } ]): EffectAction<Args, A, E>;Creates the framework-neutral runner behind
useEffectAction. Framework bindings rendercurrent;runreplaces an older run, as a search box or a repeated button press expects.EffectActioninterfaceA latest-wins Effect workflow projected into framework-neutral state.
EffectActionOptionsinterfaceEffectActionStateinterfaceOne consistent reading of an
EffectAction. A new object is published for every change.EffectActionStatustype aliastype EffectActionStatus = "idle" | "running" | "success" | "failure" | "interrupted";No documentation.
followViewportfunctionfunction followViewport<A, E, R>(controller: CollectionViewportController, request: (range: ViewportRange) => Effect.Effect<A, E, R>): Effect.Effect<void, E, R>;Sends each new row range of a viewport controller to .NET, interrupting the request for a range the user already scrolled past. Pass a command, or an
operation, whose interruption cancels it in .NET:yield* followViewport(controller, range => operation(() => rows.startSetViewport(range)));Runs until interrupted, and fails with the first failure of
request.fromBridgeErrorfunctionfunction fromBridgeError(error: BridgeError): ViewError;Maps a
BridgeErrorto its tagged error. An unknown future kind maps toViewCommandFailed.operationfunctionfunction operation<T, F = never>(start: (requestId: string) => PromiseLike<BridgeOperation<T, F>>, options: RetryOperationOptions): Effect.Effect<T, ViewOperationError | ViewDomainFailureOf<F>>; function operation<T, F = never>(start: () => PromiseLike<BridgeOperation<T, F>>, options?: OperationOptions): Effect.Effect<T, ViewOperationError | ViewDomainFailureOf<F>>;Starts a .NET operation and waits for its terminal status. When the Effect ends without a terminal status, because it was interrupted (including by
Effect.timeoutor between retries), timed out, or failed to observe the operation after its last attempt, it sends the cancellation request for the operation it started before it completes. The start request itself is not interruptible: an interruption waits for .NET to answer it, so an admitted operation is never left running unobserved. Retrying is only offered with an explicit request ID, which makes a repeated start return the operation already running instead of starting another. Between attempts the operation keeps running:yield* operation(() => editor.startSave(), { timeout: "10 seconds" }); yield* operation(id => editor.startSaveWithRequestId(id), { requestId: crypto.randomUUID(), retry: Schedule.exponential("200 millis").pipe(Schedule.take(3)), });OperationOptionsinterfaceRetryOperationOptionsinterfacestatesfunctionfunction states<S>(client: ViewClient<S>, options?: StatesOptions): Stream.Stream<S>;The client's current state, then each accepted state. States are snapshots, so a slow consumer skips to the latest one. A state re-read after an unusable change or a reconnect arrives as an ordinary state; the stream does not fail. It never ends on its own: interrupt it, or close the scope that connected the client.
StatesOptionsinterfaceViewBridgeTimeoutclassA Bridge exists but did not connect in time (
timeout).ViewCancelledclass.NET cancelled the call (
cancelled).ViewCommandFailedclassThe .NET handler threw, or the reply was invalid (
failed).detailholds the exception type, message and stack when .NET runs in development.ViewDisconnectedclassThe client was disposed, the Bridge session changed, or the transport is closed (
disconnected).ViewDomainFailureclassThe declared failure of a command or operation (
[RunicFailure]in .NET):failureis the value the generated client decoded, such as{ $case: "titleRequired" }. Handle it withEffect.catchTag("ViewDomainFailure", ...)or, for a$caseunion, with catchCase .ViewDomainFailureOftype aliastype ViewDomainFailureOf<F> = [ F ] extends [ never ] ? never : ViewDomainFailure<F>;The
ViewDomainFailurea command or operation with failure typeFadds; nothing whenFisnever.ViewErrortype aliastype ViewError = ViewUnavailable | ViewDisconnected | ViewBridgeTimeout | ViewRejected | ViewCancelled | ViewCommandFailed;The failures of a command, setter, query or connection.
ViewFailureFieldsinterfaceFields shared by the errors that map one
BridgeErrorkind.ViewOperationCancelledclassThe operation ended as
cancelled, by this client or another one.ViewOperationErrortype aliastype ViewOperationError = ViewError | ViewOperationUncertain | ViewOperationFailed | ViewOperationCancelled | ViewOperationTimedOut;The failures of an operation: starting it, observing it, or its terminal status.
ViewOperationFailedclassThe operation ended as
failed, or succeeded without a deliverable result.ViewOperationTimedOutclassThe
timeoutoption passed and the operation was cancelled, or .NET reported a client-sidetimedOutstatus.ViewOperationUncertainclassThe client could not observe whether .NET admitted or completed an operation, or .NET no longer knows its outcome (
expiredorunknown). Starting it again with the same request ID returns the existing operation instead of a second one.viewportChangesfunctionfunction viewportChanges(controller: CollectionViewportController): Stream.Stream<CollectionViewport>;The controller's current viewport, then each change. A slow consumer skips to the latest one.
ViewportRangeinterfaceThe rows a viewport request asks .NET for.
ViewRejectedclass.NET refused the call, for example a command whose
CanExecuteis false (rejected).No host installed
window.__runicBridge, for example a page opened from a plain Vite server (unavailable).
@runic-artifex/views-effect/react
EffectActionHandleinterfaceuseEffectActionfunctionfunction useEffectAction<Args extends readonly unknown[], A, E, R = never>(program: (...args: Args) => Effect.Effect<A, E, R>, ...[options]: [ R ] extends [ never ] ? [ options?: EffectActionOptions<A, E, R> ] : [ options: EffectActionOptions<A, E, R> & { readonly runFork: (effect: Effect.Effect<A, E, R>) => Fiber.Fiber<A, E>; } ]): EffectActionHandle<Args, A, E>;Runs an Effect workflow from a component and renders its status, value and typed error. A new run interrupts the previous one, and unmounting interrupts the current run, which cancels an interrupted operation in .NET.
const save = useEffectAction(() => operation(() => client!.startSave(), { timeout: "10 seconds" })); <button disabled={!client || save.pending} onClick={() => void save.run()}>Save</button> {save.error?._tag === "ViewOperationTimedOut" && <p role="alert">Saving took too long.</p>}The latest
programandrunForkare used whenrunis called, so they may close over the latest props.