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
What happens
A reactor created without a service type parameter uses
BaseActor, whosemethods take and return
any. The raw transform passes thatanythrough;the display transform turns it into
unknown:"transfer")ReactorArgs<BaseActor, "transfer", "candid">anyReactorArgs<BaseActor, "transfer", "display">unknownReactorReturnOk<BaseActor, "transfer", "candid">anyReactorReturnOk<BaseActor, "transfer", "display">unknownDisplayOf<any>unknownDisplayOf(packages/core/src/display/types.ts:238) is a chain ofconditional types. With
anyas the checked type, TypeScript takes everybranch and unions the results, and the union collapses to
unknown.So on an untyped
CandidDisplayReactor(the usual way to use it, since itsservice comes from Candid fetched at runtime) or an untyped
DisplayReactor:registerValidator("transfer", ([input]) => ...)fails:TS2345
Type 'unknown' is not assignable to type '[any]'.callMethodresults areunknown, so<div>{balance}</div>fails withTS2322
Type 'unknown' is not assignable to type 'ReactNode'.CandidReactororReactorcompile, becausethere everything is
any.The docs and JSDoc were written as if the display side were
anytoo: thevalidator examples on
packages/candid/candiddisplayreactor.mdx(lines 185 and319) and in
packages/candid/src/display-reactor.ts:62, and the React exampleat
candiddisplayreactor.mdx:393, all fail on this. Branchdocs/site-candid(site5) adds casts so they compile under the current types. This issue asks
which types are intended.
Repro
Output:
Type 'number' is not assignable to type '[any]'foraandc,'[unknown]'forb,dande(
scratchpad/site/consumer/src/typeprobes/untyped-display-short.ts, strict,TypeScript 6.0.3, built
@ic-reactor/core3.12.5).Options
A. Keep
unknownand document it. No code change. The display side of anuntyped 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 atbuild time. site5's branch already does the casts.
B. Make
DisplayOf<any>resolve toany. Add anIsAny<T>check at thetop of
DisplayOf(and so ofDisplayResultOf). Untyped display reactorswould then behave like untyped raw reactors, and the current examples would
compile without casts. Every existing program still compiles, since
anyaccepts whatever
unknowndid, but code on untyped display reactors losesthe checks
unknownforces today, and the change shows in the published.d.ts.C. Make both sides
unknown. Consistent and strict, but it breaksexisting code that uses an untyped
Reactor/CandidReactorresult asany. A major-version change.Recommendation
A. The
unknownis the safer type for values whose shape is only known atrun time, and the inconsistency with the raw side is explained by
BaseActor'sany, not by a display-side bug. If the maintainer prefers the examples toread without casts, B is a small, compile-compatible change, and the casts in
site5 would then be removable.
Acceptance
unknown, and shows the service type parameter.ReactorArgs<BaseActor, M, "display">andReactorReturnOk<BaseActor, M, "display">toany, and the docs casts are removed.