Building a Chance Mixin in TypeScript
Asked Answered
S

2

6

I am working on a ChanceJS mixin that I plan to distribute via npm.

I am having issues trying to get the correct Interface and Typings defined.

Example of the package: // index.ts

import * as Chance from 'chance';

export interface ITime {
  time(): string;
}

function time() {
  const h = chance.hour({ twentyfour: true });
  const m = chance.minute();
  return `${h}:${m}`;
};

export const time: ITime = {
  time,
};

Example of how someone would consume it:

import * as Chance from 'chance';
import { time } from 'chance-time';

const chance = new Chance();
chance.mixin(time);

chance.time()

The error I am getting is:

Error:(12, 14) TS2345: Argument of type 'ITime' is not assignable to parameter of type 'MixinDescriptor'.
Index signature is missing in type 'ITime'.
Error:(25, 26) TS2339: Property 'time' does not exist on type 'Chance'.
Statue answered 7/5, 2018 at 19:20 Comment(0)
P
6

It looks like the DefinitelyTyped definitions for ChanceJS don't really support what you're trying to do. The ideal fix for this would be to change those typings, but assuming you don't want to do that, type assertions will be your friend.

I don't have ChanceJS installed, so you might need to alter the following code (namespaces etc) to get it working:

const chance = new Chance() as Chance.Chance & ITime;
chance.mixin(time as any); // no error
chance.time(); // no error now I hope

In the first line, the idea is that chance will end up becoming both a Chance and an ITime, since that's what the mixin function does. This will allow the chance.time() line to compile without errors.

In the second line, you are just suppressing the "index signature missing" error. There are other ways around it, but the gist of it is that since ITime is an interface without an index signature, you can't assign it to an interface with an index signature like MixinDescriptor. This is a known and currently intended behavior. The easiest way to deal with it might be to change ITime from an interface to a type.

Finally, your mixin might need fixin', since your time() function implementation refers to a variable named chance which doesn't seem to be defined. I imagine the code throws an error at runtime, but maybe you haven't included all the relevant code or it's just an example. Taking the code at face value, maybe instead of chance you should use this (and keep TypeScript happy by using a this parameter with type Chance)? Like

function time(this: Chance.Chance) {
  const h = this.hour({ twentyfour: true });
  const m = this.minute();
  return `${h}:${m}`;
};

Anyway, that's the closest I can give to an answer without installing ChanceJS. Hope it gets you pointed in the right direction. Good luck!

Picnic answered 8/5, 2018 at 1:5 Comment(3)
Thanks for this great response. I agree the best solution would be to update the typings, but I am not too sure where the typeings needs to be changed to fix this. I am still learning TypeScript, the way I learn is by using it every day and building projects in it. That being said, your solution fixed a lot of the compile errors. Now the only thing I am only getting Error:(11, 32) TS2709: Cannot use namespace 'Chance' as a type. I am assuming this is due to how the typings are built.Statue
Maybe it's Chance.Chance? There's a namespace named Chance with an interface named Chance inside it. What type do you see when you inspect an object you create with new Chance()? It's hard for me to debug unless I install that library and its typings, which I am not in the position to do right now.Picnic
Chance.Chance fixed it!Statue
S
0

The way I have implemented this is as follows. My mixins use a common code base to generate mocks, so I will include it here.

Define common mixins interface, ignore this if you are not going to use it.

interface Mock_I<T> {
  id?: number
  depth?: number
  models?: string[]
  ensure?: string[]
  skip?: string[]
  data?: T
}

Define options for each mixin:

// example of a model that uses this mock
interface ProfileModelBasedMockParamsI extends Mock_I<ProfileModel> {
  notation?: string // let's just say time has this optional param
}

// simple as question asked
interface TimeMockParamsI {
  notation?: string // let's just say time has this optional param
}

Now extend chance with your mixins and create a new interface that you use instead of the Chance.Chance interface:

interface ChanceMixins extends Chance.Chance {
  time(options: TimeMockParamsI): any
  // ... more mixins here as needed
}

Then finally use it (do yarn add -D @types/chance to add chance types if you haven't already):

import Chance from 'chance';
const chance = new Chance() as Chance.Chance & ChanceMixins;

chance.time(); // should work with no issues
chance.time('24'); // should work with no issues
Strickle answered 8/11, 2021 at 12:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.