Infer service type from implementation

You may now notice an type error with getPokemon:

Type 'BuildPokeApiUrl | PokemonCollection' is not assignable to type 'never'.

That's because we manually defined the PokeApiImpl interface, which doesn't conform anymore with the new implementation (no dependencies expected):

Every time you see a type issue in the form Type 'Service' is not assignable to type 'never' it probably has to do with missing/outdated dependencies (in this example BuildPokeApiUrl and PokemonCollection)

PokeApi.ts
interface PokeApiImpl {
  /// `getPokemon` has `never` as dependencies
  /// (`never` is the default type when not defined)
  /// 
  /// But our implementation uses `BuildPokeApiUrl` and `PokemonCollection`
  /// ⛔️ Not assignable to `never`!
  readonly getPokemon: Effect.Effect<
    Pokemon,
    FetchError | JsonError | ParseResult.ParseError | ConfigError
  >;
}

export class PokeApi extends Context.Tag("PokeApi")<PokeApi, PokeApiImpl>() {

Having to keep implementation and types in sync is a pain, we do not want to do this. It's faster to just derive the types from the actual implementation.

Good news! We can do this with typescript using typeof.

We first define a make value that contains the implementation of the service, and we provide its type definition when creating the Context service:

PokeApi.ts
const make = {
  getPokemon: Effect.gen(function* () {
    const pokemonCollection = yield* PokemonCollection;
    const buildPokeApiUrl = yield* BuildPokeApiUrl;

    const requestUrl = buildPokeApiUrl({ name: pokemonCollection[0] });

    const response = yield* Effect.tryPromise({
      try: () => fetch(requestUrl),
      catch: () => new FetchError(),
    });

    if (!response.ok) {
      return yield* new FetchError();
    }

    const json = yield* Effect.tryPromise({
      try: () => response.json(),
      catch: () => new JsonError(),
    });

    return yield* Schema.decodeUnknown(Pokemon)(json);
  }),
};

export class PokeApi extends Context.Tag("PokeApi")<PokeApi, typeof make>() {
  static readonly Live = PokeApi.of(make);
}

Now the service type is implementation-driven: we can go ahead and focus on the implementation (and never have to type things manually when the language supports type inference).

This make+typeof pattern is common and recommended in effect.

Since we changed the service type we also need to update the Test implementation.

We are going to fix the tests in an upcoming lesson 🔜