JavaScript Ergonomics
Currently, there are two issues with the JavaScript / TypeScript bindings that make development inconvenient for IC developers.
- Options
- Optional nested values in JavaScript are denoted idiomatically using the Optional Chaining operator (?.)
- Our generated pattern, of a null value being represented as
[]does not align with expectations of developers, as Arrays are used to represent ordered data.
- Variants
- For Variants such as Result.Result, we get back a typing that is not ideal for JavaScript, and requires developers to add a significant amount of boilerplate and error handling.
- a type of
{ ok : () } | { err: string }cannot be safely accessed asresult.okorresult.err, but requires the user to write something like
if ('ok' in result){
// continue
}
else if ('err' in result) {
// handle result.err
}
* This code is unsafe, and is problematic for TypeScript in particular. Instanceof is also not reliable for determining which case is represented.
Describe the solution you'd like
- Replace options with Optional Chaining
- may require additional support in the JS Agent for converting null back to the candid type
- Handle variants with a "kind" attribute that will always be present and checkable in variants
Optional nested values in JavaScript are denoted idiomatically using the Optional Chaining operator (?.)
I don't quite follow. The problem with nested opt is which values to use to represent them; ?. is an operator on values.
Which values would you use for null, opt null and opt 5 at type opt opt int?
I can see that the ability to use a switch over a variant would be desirable. But can you elaborate why this code is "unsafe and problematic" in TypeScript?
How would you suggest to represent argumentless variants like variant {a; b}, or more interestingly, mixed ones like variant {a; b : nat}? TypeScript would idiomatically represent the former as "a" | "b", but that representation would not allow using a single switch for the latter.
Which values would you use for
null,opt nullandopt 5at typeopt opt int?
null would be null. opt null would be undefined | null. opt 5 would be undefined | number | bigint, and I think I would flatten nested opts to simply undefined | <T>. We can still nest them appropriately as arrays over the wire, but at the end of the day there either is a value at the end of the chain, or there isn't. Unless I am failing to imagine a case here
I can see that the ability to use a switch over a variant would be desirable. But can you elaborate why this code is "unsafe and problematic" in TypeScript?
instanceof interacts poorly with JavaScript imports, and a compatible interface from a different package will not always yield consistent results. Different versions of @dfinity/agent and @dfinity/principal can end up with instanceof Principal not behaving as expected. This is why we provide methods like Principal.prototype.isPrincipal(). I wouldn't forbid someone from using it, but relying on instanceof for non-native types is a pattern that will tend to introduce bugs for devs.
How would you suggest to represent argumentless variants like
variant {a; b}, or more interestingly, mixed ones likevariant {a; b : nat}? TypeScript would idiomatically represent the former as"a" | "b", but that representation would not allow using a single switch for the latter.
I would represent argument-less variants as strings of the variant name. This handles mixed variants well enough, and allows for strong type inspection into example.kind
type exampleType = { kind: "a" } | { kind: {b: bigint} }

After discussing with @chenyan-dfinity , we agreed that nested opts should be typed the same as they are now, with nested empty arrays