Skip to content

design(core): an untyped DisplayReactor or CandidDisplayReactor types validator args and results as unknown, while an untyped Reactor gives any #700

Description

@b3hr4d

What happens

A reactor created without a service type parameter uses BaseActor, whose
methods take and return any. The raw transform passes that any through;
the display transform turns it into unknown:

Type (untyped reactor, method "transfer") Resolves to
ReactorArgs<BaseActor, "transfer", "candid"> any
ReactorArgs<BaseActor, "transfer", "display"> unknown
ReactorReturnOk<BaseActor, "transfer", "candid"> any
ReactorReturnOk<BaseActor, "transfer", "display"> unknown
DisplayOf<any> unknown

DisplayOf (packages/core/src/display/types.ts:238) is a chain of
conditional types. With any as the checked type, TypeScript takes every
branch and unions the results, and the union collapses to unknown.

So on an untyped CandidDisplayReactor (the usual way to use it, since its
service comes from Candid fetched at runtime) or an untyped DisplayReactor:

  • registerValidator("transfer", ([input]) => ...) fails:
    TS2345 Type 'unknown' is not assignable to type '[any]'.
  • callMethod results are unknown, so <div>{balance}</div> fails with
    TS2322 Type 'unknown' is not assignable to type 'ReactNode'.
  • The same calls on an untyped CandidReactor or Reactor compile, because
    there everything is any.

The docs and JSDoc were written as if the display side were any too: the
validator examples on packages/candid/candiddisplayreactor.mdx (lines 185 and
319) and in packages/candid/src/display-reactor.ts:62, and the React example
at candiddisplayreactor.mdx:393, all fail on this. Branch docs/site-candid
(site5) adds casts so they compile under the current types. This issue asks
which types are intended.

Repro

import type { BaseActor, ReactorArgs, ReactorReturnOk, DisplayOf } from "@ic-reactor/core"
// Each line errors, and the message shows the resolved type.
export const a: [ReactorArgs<BaseActor, "transfer", "candid">] = 1 // '[any]'
export const b: [ReactorArgs<BaseActor, "transfer", "display">] = 1 // '[unknown]'
export const c: [ReactorReturnOk<BaseActor, "transfer", "candid">] = 1 // '[any]'
export const d: [ReactorReturnOk<BaseActor, "transfer", "display">] = 1 // '[unknown]'
export const e: [DisplayOf<any>] = 1 // '[unknown]'

Output: Type 'number' is not assignable to type '[any]' for a and c,
'[unknown]' for b, d and e
(scratchpad/site/consumer/src/typeprobes/untyped-display-short.ts, strict,
TypeScript 6.0.3, built @ic-reactor/core 3.12.5).

Options

A. Keep unknown and document it. No code change. The display side of an
untyped reactor stays honest: nothing is known about the Candid, so callers
narrow or cast. The docs say so and show the two ways out: a cast to the
display shape a validator checks, or a service type parameter
(new CandidDisplayReactor<MyService>(...)) when the service is known at
build time. site5's branch already does the casts.

B. Make DisplayOf<any> resolve to any. Add an IsAny<T> check at the
top of DisplayOf (and so of DisplayResultOf). Untyped display reactors
would then behave like untyped raw reactors, and the current examples would
compile without casts. Every existing program still compiles, since any
accepts whatever unknown did, but code on untyped display reactors loses
the checks unknown forces today, and the change shows in the published
.d.ts.

C. Make both sides unknown. Consistent and strict, but it breaks
existing code that uses an untyped Reactor/CandidReactor result as
any. A major-version change.

Recommendation

A. The unknown is the safer type for values whose shape is only known at
run time, and the inconsistency with the raw side is explained by BaseActor's
any, not by a display-side bug. If the maintainer prefers the examples to
read without casts, B is a small, compile-compatible change, and the casts in
site5 would then be removable.

Acceptance

  • The maintainer picks A, B or C.
  • A: the candid display reactor page says an untyped reactor's validator args and results are unknown, and shows the service type parameter.
  • B: a type test pins ReactorArgs<BaseActor, M, "display"> and ReactorReturnOk<BaseActor, M, "display"> to any, and the docs casts are removed.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions