Hi all, wanted to give you a heads-up that we have released a potentially breaking change to @dfinity/agent. We have removed the BigNumber package as a dependency in favor of the BigInt primitive, which will make our total package size a bit smaller and more performant.
Additionally, @dfinity/agent and @dfinity/authentication have improved Readme’s with links to some autogenerated documentation. You can install the latest versions from npm!
Thanks, I’ll put some pressure on the Candid team to make the types update, and I’ll look into the invalid Nat issue. Do you have a bit more context to help me reproduce and write a new test?
The second representation is better, because it allows us to omit optional fields, which is natural in js/ts, but a little bit harder to implement, I believe.
export type Result = {
'ok' : BigNumber
} |
{ 'err' : string };
which is very unhandy
// test.ts
const result = await IncrementActor.incOrThrow(2n as unknown as BigNumber);
if (result.hasOwnProperty('ok')) {
console.log((result as {'ok': BigNumber}).ok);
}
else {
console.error((result as {'err': string}).err);
}
It would be better if:
For each variant kind there will be a separate generated type like
// increment.d.ts
type ResultOk = <type if ok>;
type ResultErr = <type if err>;
type Result = ResultOk | ResultErr;
We know that the interface with Candid can use some quality of life improvements. So far, our principle has been to ship the agent / types with as close of a match to the Candid concepts of types as possible, with JS developers writing a small layer around the actor methods to make things pleasant in the rest of the application.
I’d like to make some nicer inferences, but it definitely comes at a risk of introducing some deeply confusing bugs
with JS developers writing a small layer around the actor methods to make things pleasant
It doesn’t make sense to me, sorry.
How can I do that, for example, for our increment.mo canister? Let’s suppose I want maybeInc() to have the suggested interface (undefined instead of empty array). What exactly should I do?
Thanks. This is not a problem.
The problem is that, if you want to make an interface that doesn’t force anyone to stick with some particular style or pattern, you should make it easy to use by default and flexible enough to fit in any other paradigm. Not ugly and “as close to metal as we can”.
Right now they just force anyone to write exactly the same utilities you’ve mentioned.
If you encode opt A as null | A in TypeScript you’re losing information, because you can’t tell opt null from null for a value of type opt (opt A). I’d say losing data doesn’t meet the “flexible enough” bar. Encoding it with arrays lets you tell them apart as [null] vs [].
For the variant encoding I’d agree that a tag field might make for a nicer API.
You’re right! But do you really want to distinct them?
Maybe it’s not right to guess here, but I can’t imagine a use-case when the distinction between opt(null) (which is null) and null could be useful. It doesn’t even lose it’s type safety.
But most of the time you want a nice ?.field access syntax, and this could be very helpful.
//canister.mo
type Rec = {a : Nat; b : ?Nat;};
public query func getSmth() : ?Rec {...}
// test.ts - good semantics
const smth = await actor.getSmth();
if (smth?.b && smth?.b > 10n) {
doStuff();
}
// test.ts - bad semantics
const smth = await actor.getSmth();
if (smth.length > 0 && smth[0].b.length > 0 && smth[0].b[0] > 10n) {
doStuff();
}
I think that the right path forward might be to provide some utility classes for things like Opts that can maintain full information, but can be cast to friendlier JS primitives.
In the meantime, I’ve merged a fix for the Nat bug 0n, and we should have a build of Candid without BigNumber, hopefully in time for the next dfx release