Ben Wainwright
Ben Wainwright

Reputation: 4651

How to correctly type indexed global object

I'm currently experimenting with converting a large monolithic JavaScript application to TypeScript. I'm having problems with how to correctly type a particular module and I wondered if anyone knows how to do it.

Please note that the original code was not written by me - I am simply trying to convert an existing application, so if there is anything wrong with the JavaScript, telling me that isn't going to be useful

Here is the original code for the module (thankfully its quite small).

const CACHE_CONTROL_HEADERS = Symbol.for('cache control headers')

if (!global[CACHE_CONTROL_HEADERS]) {
  global[CACHE_CONTROL_HEADERS] = [];
}

module.exports = global[CACHE_CONTROL_HEADERS];

Now I've established that TypeScript does not support using symbols as index signatures, so I know I need to cast the first line. I also believe I need to change the last line to ES6 module format, so that leaves me with this:

const CACHE_CONTROL_HEADERS = (Symbol.for(
  "cache control headers"
) as unknown) as string;

if (!global[CACHE_CONTROL_HEADERS]) {
  global[CACHE_CONTROL_HEADERS] = [];
}

export default global[CACHE_CONTROL_HEADERS];

This now leaves me with the following compile error:

index.ts:6:3 - error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Global & typeof globalThis'.
  No index signature with a parameter of type 'string' was found on type 'Global & typeof globalThis'.

Now after a bit of googling, I've found various approaches to augmenting the type of the global object, and I've attempted to put the following into a file alongside the module file:

declare module NodeJS {
  interface Global {
    [key: string]: string;
  }
}

This unfortunately doesn't change the error, and my editor also throws up some other errors on the index signature, e.g.

Property 'Array' of type 'ArrayConstructor' is not assignable to string index type 'string[]'

At this point, I'm truly stuck. If anyone can help me turn this tiny file into TypeScript, I'd be more than grateful!

Upvotes: 0

Views: 545

Answers (2)

phry
phry

Reputation: 44256

You don't need an index signature for one extra property - and you can use symbols as properties just fine:

For Window:

export {}
declare global {
  interface Window {
    [CACHE_CONTROL_HEADERS]: WhatEverTypeYouWantThisToHave;
  }
}

const CACHE_CONTROL_HEADERS = Symbol.for("foo")
interface WhatEverTypeYouWantThisToHave {
  x: ""
}

window[CACHE_CONTROL_HEADERS].x //works

And for Global:

export {};

interface WhatEverTypeYouWantThisToHave {
  x: string;
}

declare global {
  module NodeJS {
    interface Global {
      [CACHE_CONTROL_HEADERS]: WhatEverTypeYouWantThisToHave;
    }
  }
}

const CACHE_CONTROL_HEADERS = Symbol.for("foo");

const x: string = global[CACHE_CONTROL_HEADERS].x;

// let's provoke another error to prove this file is parsed
const y: number = global[CACHE_CONTROL_HEADERS].x;

Upvotes: -1

mrgrain
mrgrain

Reputation: 441

No pretty, but you could cast every usage of global to a custom type, that extends the original typeof global:

type CacheControlGlobal = typeof global & {
  [CACHE_CONTROL_HEADERS]: string[];
};

(global as CacheControlGlobal)[CACHE_CONTROL_HEADERS] = [];

Here's the complete playground (caveat that global doesn't work, please copy to a local environment).

Extending the existing type will not only make the code work, but also allows you to keep other typed features working, like auto-completion. The types are also similar enough that you don't have to cast via unknown, which helps with readability.

Upvotes: 3

Related Questions