TypeScript release

TypeScript 4.9 for JavaScript users

  • Released 15 November 2022
  • Examples run with TypeScript 5.9

TypeScript 4.9 was announced on 15 November 2022. Its headline feature, the satisfies operator, is TypeScript syntax; JavaScript files got the same check as the @satisfies tag one release later, in 5.0.

What a .js file does get: the in operator narrows an object to one that has the property, which makes unknown JSON easier to check; an equality comparison with NaN is now an error; and when TypeScript writes JavaScript output from .js files, it keeps every import.

At a glance

ReleaseTypeScript 4.9
Released15 November 2022, the day of its announcement on the TypeScript blog
SourceTypeScript 4.9 release notes
ExamplesEvery example runs through TypeScript 5.9.3 when this site is built.
Previous releaseTypeScript 4.8
Next releaseTypeScript 5.0

What changed for JavaScript

The in operator narrows to an object with that property

Before 4.9, "name" in data only narrowed between types that already listed name. Now it also narrows a type that does not mention the property: after the check, data is known to have a name of type unknown, which you can then test with typeof. That is the usual way to check the result of JSON.parse, which TypeScript types as any; give it an unknown type first and the checks below are required.

package-name.js TypeScript 5.9.3 TypeScript 5.9.3, run 21 October 2026: No errors. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. No errors. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. No errors. checked 21 October 2026
/** @param {unknown} data */
export function packageName(data) {
  if (data && typeof data === "object" && "name" in data && typeof data.name === "string") {
    return data.name;
  }
  return undefined;
}

console.log(packageName(JSON.parse('{"name":"left-pad"}')));
The in operator narrows unknown data to an object with that property.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": [
    "*.js"
  ]
}

Equality checks against not-a-number

Nothing is equal to NaN, not even NaN, so value === NaN is always false. TypeScript 4.9 reports such comparisons (TS2845) and suggests Number.isNaN(value).

parse.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: parse.js(5,7): error TS2845: This condition will always return 'false'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. parse.js(5,7): error TS2845: This condition will always return 'false'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. parse.js(5,7): error TS2845: This condition will always return 'false'. checked 21 October 2026
/** @param {string} input */
export function parsePrice(input) {
  const value = Number(input);
  if (value === NaN) {error TS2845: This condition will always return 'false'.
    return null;
  }
  return Number.isNaN(value) ? null : value;
}
An equality check that can never be true, now reported.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": [
    "*.js"
  ]
}

JavaScript output keeps every import

When TypeScript writes .js output from .js input (allowJs with an outDir), older versions dropped an import that was only used in JSDoc, such as a class named in a @type tag. 4.9 keeps imports in JavaScript files exactly as written. A project that only checks its files (noEmit, the jsconfig.json default) is not affected.

satisfies is TypeScript syntax

The new operator checks a value against a type without changing the value's own type. Written in a .js file, it is a syntax error; the JSDoc form, /** @satisfies {Type} */, arrived in TypeScript 5.0 and is covered on the @satisfies page.

settings.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: settings.js(4,60): error TS8037: Type satisfaction expressions can only be used in TypeScript files. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. settings.js(4,60): error TS8037: Type satisfaction expressions can only be used in TypeScript files. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. settings.js(4,60): error TS8037: Type satisfaction expressions can only be used in TypeScript files. checked 21 October 2026
/** @typedef {{ theme: "light" | "dark", pageSize: number }} Settings */

const settings = { theme: "dark", pageSize: 24 } satisfies Settings;error TS8037: Type satisfaction expressions can only be used in TypeScript files.

export { settings };
The satisfies operator is TypeScript syntax.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

Watch mode uses file system events

tsc --watch now waits for the operating system to report a change instead of polling files, and falls back to polling only where events are not available. The release notes describe the watchOptions settings for the cases where events misbehave.

What did not change

JSDoc tags and the way TypeScript reads them are the same as in 4.8. A project that only checks its files sees no difference from the emit change.

  • The satisfies keyword cannot be written in a .js file; use the tag from 5.0.
  • The 4.8 rule still holds: a type-only name cannot be imported by name in a .js file.

Upgrade notes

From the release notes (the check for NaN is listed as a new feature, the others as breaking changes); the first two can produce new errors in checked JavaScript.

  • Comparisons with NaN are errors (TS2845).
  • Promise.resolve now unwraps nested promises with Awaited, so code that relied on getting any back may see a more precise type.
  • If you write JavaScript output from .js files, check that imports used only in JSDoc are safe to keep at run time.
  • Under moduleResolution: node16, a package's exports now wins over its typesVersions; this matters to library authors who publish types for several TypeScript versions.

Gotchas

  • Number.isNaN(x) and x !== x are the two checks that work; the global isNaN() converts its argument first, so isNaN("abc") is true.

Sources

Last reviewed 20 October 2026.