Search code examples
javascriptnode.jstypescriptupgrade

In TypeScript 5.6+, `Buffer` is not assignable to `ArrayBufferView` or `Uint8Array | DataView`


Trying to upgrade to TypeScript 5.6 Beta, I get the following error messages in node_modules. I didn't make any other change, so what should I do to fix these errors?

node_modules/@types/node/buffer.d.ts:632:19 - error TS2430: Interface 'Buffer' incorrectly extends interface 'Uint8Array'.
  The types returned by 'reverse()' are incompatible between these types.
    Type 'this' is not assignable to type 'Uint8Array'.
      Type 'Buffer' is not assignable to type 'Uint8Array'.
        The types returned by 'slice(...).entries()' are incompatible between these types.
          Type 'IterableIterator<[number, number]>' is missing the following properties from type 'BuiltinIterator<[number, number], undefined, any>': map, filter, take, drop, and 8 more.

632         interface Buffer extends Uint8Array {
                      ~~~~~~
node_modules/@types/node/fs/promises.d.ts:56:66 - error TS2344: Type 'Buffer' does not satisfy the constraint 'ArrayBufferView'.
  Type 'Buffer' is not assignable to type 'Uint8Array | DataView'.
    Type 'Buffer' is not assignable to type 'Uint8Array'.
      The types returned by 'entries()' are incompatible between these types.
        Type 'IterableIterator<[number, number]>' is missing the following properties from type 'BuiltinIterator<[number, number], undefined, any>': map, filter, take, drop, and 8 more.

56     interface FileReadOptions<T extends NodeJS.ArrayBufferView = Buffer> {
                                                                    ~~~~~~
node_modules/@types/node/fs/promises.d.ts:238:49 - error TS2344: Type 'Buffer' does not satisfy the constraint 'ArrayBufferView'.
  Type 'Buffer' is not assignable to type 'Uint8Array | DataView'.
    Type 'Buffer' is not assignable to type 'Uint8Array'.
      The types returned by 'entries()' are incompatible between these types.
        Type 'IterableIterator<[number, number]>' is missing the following properties from type 'BuiltinIterator<[number, number], undefined, any>': map, filter, take, drop, and 8 more.

238         read<T extends NodeJS.ArrayBufferView = Buffer>(options?: FileReadOptions<T>): Promise<FileReadResult<T>>;
                                                    ~~~~~~

Note: when I used a newer version of TypeScript 5.6, mentions of BuiltinIterator<[number, number], ...> seem replaced with ArrayIterator<number>.


Solution

  • Short Answer

    You need a new version of the Node.js types.

    npm update @types/node --save
    
    # or if you need a specific version
    npm install -D @types/node@16
    npm install -D @types/node@18
    npm install -D @types/node@20
    npm install -D @types/node@22
    

    Long Answer

    First, IterableIterator describes a type that is both Iterable (it can create an Iterator by calling someValue[Symbol.iterator()]), and is itself an Iterator (you can call things like next() on it over and over until it's out of values).

    Iterables (and IterableIterators) are nice because they can be used in all sorts of places in JavaScript - but a lot of people found themselves missing methods on Arrays like map, filter, etc. So a recent proposal was brought forward in JavaScript/ECMAScript to bring many useful methods from Array (plus a few more). You can adapt any existing Iterables into this new type with Iterable.from:

    Iterator.from(...).filter(someFunction);
    

    Now whenever a built-in method, or a generator, produces one of these IterableIterators, it is backed by the methods on Iterator.prototype, and you can call something like map, filter, etc.

    new Uint8Array(100).entries().map(x => x)
    

    Notice though that we're talking about a new runtime value called Iterator. You can reference Iterator and Iterator.prototype as actual values in JavaScript. This is a bit awkward since TypeScript defines its own thing called Iterator that exists purely for type-checking. So due to this unfortunate name clash, TypeScript needs to introduce a separate type to describe these built-in iterable iterators.

    TypeScript 5.6 introduces a new type called IteratorObject (and a few subtypes like ArrayIterator). Lots of built-in collections and methods produce this type, so many methods had to be updated to produce it.

    So how does this cause problems in Node.js? Well in @types/node, Buffer is basically a subtype of Uint8Array. Buffer unnecessarily redeclared the entries() method in a way that copied the old signature:

        /**
         * Returns an array of key, value pairs for every entry in the array
         */
        entries(): IterableIterator<[number, number]>;
    }
    

    However, in TypeScript 5.6, Uint8Array has been updated to use a signature like:

        entries(): ArrayIterator<number>;
    }
    

    This caused the two to diverge in slightly incompatible ways.

    So recently, @types/node has been updated to avoid the issue entirely.