3

I was trying out something like

import R from 'ramda'
import fs from 'fs'
import path from 'path'
import {promisify} from 'util'

const readFile = promisify(fs.readFile)

export async function discoverPackageInfo(): Promise<{
  name: string,
  version: string
  description: string
}> {
  return readFile(path.join(__dirname, '..', 'package.json'))
    .then(b => b.toString())
    .then(JSON.parse)
    .then(R.pick([
      'name',
      'description',
      'version',
    ]))
}

But I got

src/file.ts:13:3 - error TS2322: Type '{ name: string; version: string; description: string; } | Pick<any, never>' is not assignable to type '{ name: string; version: string; description: string; }'.
  Type 'Pick<any, never>' is missing the following properties from type '{ name: string; version: string; description: string; }': name, version, description

 13   return readFile(path.join(__dirname, '..', 'package.json'))
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 14     .then(b => b.toString())
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
... 
 19       'version',
    ~~~~~~~~~~~~~~~~
 20     ]))
    ~~~~~~~

What am I doing wrong?

pkoch
  • 2,682
  • 1
  • 19
  • 17
  • Yes, I know that using `async` and `.then` is not too kosher. – pkoch Dec 26 '19 at 00:40
  • `async` is actually unnecessary here. The code works without it – apokryfos Dec 26 '19 at 10:25
  • Correct. However, I'm structuring my code to have async on anything that's not CPU-only in order to force to think about things right. Ends up being an exercise in structure and annotation, and I've been finding it helps me a lot. – pkoch Dec 27 '19 at 12:47

1 Answers1

3

JSON.parse returns any. The return type after R.pick resolves like this:

.then(R.pick([ 'name', 'description', 'version' ])) 
=>
type Return = Pick<any, Inner> // resolves to {}; any stems from JSON.parse
type Inner = Exclude<keyof any,
    Exclude<keyof any, "name" | "description" | "version">> // never

You want { name: string, version: string description: string }, but get {}. To fix that, assert the desired type explicitly for JSON.parse (I named it MyThing):

readFile(path.join(__dirname, "..", "package.json"))
  .then(b => b.toString())
  .then(s => JSON.parse(s) as MyThing)
  .then(R.pick(["name", "description", "version"]));

Parsing can be made more type-safe, e.g. by using assertion functions / type guards:

export async function discoverPackageInfo(): Promise<MyThing> {
  return readFile(...).then(...)
    .then(safeParse(assertMyThing)) // <-- change
    .then(R.pick(...));
}

const safeParse = <T>(assertFn: (o: any) => asserts o is T) => (s: string) => {
  const parsed = JSON.parse(s);
  assertFn(parsed);
  return parsed;
}

function assertMyThing(o: any): asserts o is MyThing {
  if (!("name" in o) || !("version" in o) || !("description" in o))
    throw Error();
}

Playground (external type imports in the playground might take a bit to load, otherwise paste in your own environment)

ford04
  • 66,267
  • 20
  • 199
  • 171
  • Great help, thank you! Is there a more standard/packaged way of doing this? Like, generating an assert function from a type definition? I haven't gone through the handbook (I'm just learning through exercise) and I'd appreciate any pointers! – pkoch Dec 27 '19 at 12:43
  • 1
    I am not sure of a generator for assertion functions. But there are a lot of auto generating type guard libraries out there, personally I find [io-ts](https://www.npmjs.com/package/io-ts) and [typescript-is](https://www.npmjs.com/package/typescript-is) appealing! And seems to be actively maintained and supported. You also can have a look at [these](https://stackoverflow.com/questions/14425568/interface-type-check-with-typescript) [posts](https://stackoverflow.com/questions/59480160/typescript-check-object-by-type-or-interface-at-runtime-with-typeguards-in-2020/59483318) for a starting point. – ford04 Dec 27 '19 at 13:06
  • 1
    Btw: if you then wanted to use assertion functions based on generated type guards, conversion should be no problem, like [this](https://www.typescriptlang.org/play/#code/C4TwDgpgBAYg9nKBeKBvKBDAXFAzsAJwEsA7AcygF8BYAKDoHoG84BbaUSKMgVwwIAmdAGY8SAY2BE4JKEVzw4ACmE4MJEAEocwublgI0dKCagEIwHgVkAiDDbmzhdGvVpMoPXBwAW8qKISUjJQwIjiMgBuEATAoeDQvPwCUAC0AHyYuN6x0k5iknkiBcGySYIAKnAAgtkxwAA8FekqJDhKcGoamsiZiP4VADRQnZjdanWx+v36FUa0pnK6SgCEwiQdmj3APgRwAO5QJBCHAKIEewRKmi50dAIQ4gA2-NARJPgjXSB05QJVtRywCU8kUwzgN1ocCAA). – ford04 Dec 27 '19 at 16:41