@runic-artifex/views-effect

Version 0.7.0-preview.5. Read from the published package.

@runic-artifex/views-effect

catchCase function
function 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 $case failure in the error channel: the ViewDomainFailure of a command or operation whose .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.`),
});
command function
function 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 RangeError for an invalid argument, is a defect. A command that declares a failure succeeds with its value and fails with ViewDomainFailure for its declared failure. Interruption abandons the reply only: .NET keeps running a command it received.

yield* command(() => editor.save());
CommandValue type alias
type 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.

connect function
function 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 Scope and disposes it when the scope closes. Pass a generated connect<Name> function or a page reference from a content property.

const editor = yield* connect(connectEditor);
createEffectAction function
function 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 render current; run replaces an older run, as a search box or a repeated button press expects.

EffectAction interface

A latest-wins Effect workflow projected into framework-neutral state.

EffectActionOptions interface
EffectActionState interface

One consistent reading of an EffectAction. A new object is published for every change.

EffectActionStatus type alias
type EffectActionStatus = "idle" | "running" | "success" | "failure" | "interrupted";

No documentation.

followViewport function
function 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.

fromBridgeError function
function fromBridgeError(error: BridgeError): ViewError;

Maps a BridgeError to its tagged error. An unknown future kind maps to ViewCommandFailed.

operation function
function 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.timeout or 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)),
});
OperationOptions interface
RetryOperationOptions interface
states function
function 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.

StatesOptions interface
ViewBridgeTimeout class

A Bridge exists but did not connect in time (timeout).

ViewCancelled class

.NET cancelled the call (cancelled).

ViewCommandFailed class

The .NET handler threw, or the reply was invalid (failed). detail holds the exception type, message and stack when .NET runs in development.

ViewDisconnected class

The client was disposed, the Bridge session changed, or the transport is closed (disconnected).

ViewDomainFailure class

The declared failure of a command or operation ([RunicFailure] in .NET): failure is the value the generated client decoded, such as { $case: "titleRequired" }. Handle it with Effect.catchTag("ViewDomainFailure", ...) or, for a $case union, with catchCase .

ViewDomainFailureOf type alias
type ViewDomainFailureOf<F> = [
    F
] extends [
    never
] ? never : ViewDomainFailure<F>;

The ViewDomainFailure a command or operation with failure type F adds; nothing when F is never.

ViewError type alias
type ViewError = ViewUnavailable | ViewDisconnected | ViewBridgeTimeout | ViewRejected | ViewCancelled | ViewCommandFailed;

The failures of a command, setter, query or connection.

ViewFailureFields interface

Fields shared by the errors that map one BridgeError kind.

ViewOperationCancelled class

The operation ended as cancelled, by this client or another one.

ViewOperationError type alias
type ViewOperationError = ViewError | ViewOperationUncertain | ViewOperationFailed | ViewOperationCancelled | ViewOperationTimedOut;

The failures of an operation: starting it, observing it, or its terminal status.

ViewOperationFailed class

The operation ended as failed, or succeeded without a deliverable result.

ViewOperationTimedOut class

The timeout option passed and the operation was cancelled, or .NET reported a client-side timedOut status.

ViewOperationUncertain class

The client could not observe whether .NET admitted or completed an operation, or .NET no longer knows its outcome (expired or unknown). Starting it again with the same request ID returns the existing operation instead of a second one.

viewportChanges function
function viewportChanges(controller: CollectionViewportController): Stream.Stream<CollectionViewport>;

The controller's current viewport, then each change. A slow consumer skips to the latest one.

ViewportRange interface

The rows a viewport request asks .NET for.

ViewRejected class

.NET refused the call, for example a command whose CanExecute is false (rejected).

ViewUnavailable class

No host installed window.__runicBridge, for example a page opened from a plain Vite server (unavailable).

@runic-artifex/views-effect/react

EffectActionHandle interface
useEffectAction function
function 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 program and runFork are used when run is called, so they may close over the latest props.