Skip to content

[Bug]: 10.0.0 types inferred as any break compilation #2341

Open

Description

@agross

I'm sure that

  • This issue is still present in the current beta version of this adapter
  • There is no other (open) issue with the same topic (use the search!)
  • This issue is not described in the adapter documentation / FAQ (read the docs!)

Script type

TypeScript

The problem

My scripts heavily rely on rxjs. I need to use rxjs 6.x because newer versions don't work with ioBroker.javascript.

ioBroker.javascript 9.3.1 with rxjs@6 installed works fine with this piece of simplistic code:

import { Observable } from 'rxjs';

new Observable<string>(observer => { })

The code compiles and runs, despite the observer type not being inferred.

Image

Not an issue for me because I write my scripts in VS Code:

Image

With ioBroker.javascript 10, the same code no longer compiles:

TypeScript compilation failed:
new Observable<string>(observer => { });
                       ^
ERROR: Parameter 'observer' implicitly has an 'any' type.

With explicit types, compilation does succeed (VS Code accepts it too, so does tsc):

import { Observable, Subscriber } from 'rxjs';

new Observable<string>((observer: Subscriber<string>) => {});

Why does type inference no longer work and now cause compiler errors?

iobroker.current.log (in debug mode!)

No response

Version of nodejs

22.22

Version of ioBroker js-controller

7.2.2

Version of adapter

10.0.0

Activity

  1. changed the title [-][Bug]: 10.0.0 breaks rxjs compatibility[/-] [+][Bug]: 10.0.0 types inferred as `any` break compilation with rxjs[/+] on Aug 6, 2026
  2. agross commented on Aug 6, 2026

    @agross
    ContributorAuthor

    I ran Codex on this:


    Cause confirmed: TypeScript 6 default change, not rxjs regression.

    • v10 upgraded TypeScript 5.9.3 → 6.0.3 in package.json:57, via PR Typescript6 #2335.
    • TypeScript 6 enables strict by default. This implicitly enables noImplicitAny. Official TypeScript 6 notes.
    • Adapter compiler options in src/lib/typescriptSettings.ts:7 set neither strict nor noImplicitAny.
    • Live runtime confirmed effective values: strict=true, noImplicitAny=true.
    • rxjs inference was already failing under v9—the editor displayed observer: any. v9 tolerated it because strict mode defaulted off. v10 rejects same existing any.

    Live reproduction produced TS7006. Adding strict: false made TS7006 disappear. Original test script restored and running.

    Compatibility fix: explicitly add strict: false to tsCompilerOptions. Narrower fix: noImplicitAny: false, but other newly enabled strict checks would remain. No repository files changed.


    I prefer a fix to get type inference working.

  3. changed the title [-][Bug]: 10.0.0 types inferred as `any` break compilation with rxjs[/-] [+][Bug]: 10.0.0 types inferred as `any` break compilation[/+] on Aug 6, 2026
  4. agross commented on Aug 6, 2026

    @agross
    ContributorAuthor

    I have the necessary changes required to fix my scripts in my fork:

    master...agross:ioBroker.javascript:issue-2341

    In the long term, I'd rather fix #928 and think about how to implement global scripts, regular scripts, and additional node_modules with default TypeScript compilation. Please share your thoughts.

  5. technyon commented on Aug 8, 2026

    @technyon

    Wouldn't it make sense to make this user configurable. strict can be enabled by default, but should be easy to disable. I have a lot of scripts to fix, which I use not only for me but also for friends and family. It would be a lot of work to go through all of that.

  6. GermanBluefox commented on Aug 15, 2026

    @GermanBluefox
    Contributor

    Wouldn't it make sense to make this user configurable. strict can be enabled by default, but should be easy to disable. I have a lot of scripts to fix, which I use not only for me but also for friends and family. It would be a lot of work to go through all of that.

    Already done

  7. added a commit that references this issue on Aug 15, 2026
  8. agross commented on Aug 15, 2026

    @agross
    ContributorAuthor

    Wouldn't it also make sense to rethink Typescript compilation to properly support type inference and modern packages?

  9. GermanBluefox commented on Aug 15, 2026

    @GermanBluefox
    Contributor

    Wouldn't it also make sense to rethink Typescript compilation to properly support type inference and modern packages?

    Both halves of that question are now addressed — and it turned out not to need a rethink of the
    compilation. Details, in case they are useful.

    Type inference

    This was not a TypeScript 6 problem. The declarations of a library never reached the compiler intact,
    so everything imported from one was any; strict only made that visible as an error. Three things
    were wrong in resolveTypings:

    • the declaration files were placed under the name the library has on disk, while the
      package.json next to them went under the name the scripts import. The js-controller installs
      script libraries under an adapter-scoped alias, so those were two different directories and
      TypeScript could never connect them.
    • the entry point was wrapped in declare module "<pkg>" { … }. Inside an ambient module declaration
      the relative paths a barrel file re-exports from are not resolved, so every exported name collapsed
      to any. It is only applied now where it actually helps — declarations that are not a module.
    • the regex that follows the imports inside a .d.ts had no m flag, so exactly one import per file
      was followed, and only if it happened to be on the first line. For rxjs 6 that collected 6 of its
      ~800 declaration files. Side effect imports (import "./x";) were not followed at all — which is
      what @iobroker/types consists of, so the ioBroker.* types were missing as well.

    Modern packages

    You are right that this was broken, and it is what #928 is about on the type side. resolveTypings
    only read the legacy types/typings fields. rxjs 7 declares "types": "index.d.ts" although no
    such file exists — the real declarations are named by the exports map — so resolution gave up and
    the adapter fell back to declare module "rxjs";, i.e. the whole library as any.

    Four changes were needed, all in that one function:

    1. the entry point is taken from exports["."].types (including the nested require/import
      conditions) when the legacy field is missing or points at a file that is not there. A candidate
      only counts if it really exists.
    2. the declarations are laid out around the entry point rather than around the package.json, so
      moduleResolution: node10 finds it even when they live in a subdirectory. Where declarations
      reach above the entry point, the original layout is kept — moving them would break the relative
      imports between them.
    3. the package.json handed to TypeScript describes that layout instead of the one on disk. This one
      was not obvious: with the original manifest the compile failed with Cannot find module 'rxjs',
      without it the same files worked. TypeScript refuses to resolve a package whose exports map it
      cannot follow under node10 — and those paths do not exist in the virtual file system anyway.
    4. the package.json is located on disk instead of through require.resolve("<pkg>/package.json").
      That goes through Node's module resolution, and a package whose exports map does not list
      ./package.json makes it throw. rxjs 7 happens to export it; many packages do not.

    Result

    Verified with strict: true, so nothing is hidden by a silent any:

    declaration files inference const bad: number = new Observable<string>() o.next(42) on Observable<string>
    rxjs 6 6 → 65 works rejected rejected
    rxjs 7 none (was declare module) → 190 works rejected rejected

    The two negative probes matter as much as the positive one: they are what tells a properly typed
    library apart from one that is silently any, and they are checked to fail on the type error rather
    than on "cannot find module".

    There is a regression test for all of it, with a fixture shaped like rxjs 7 — legacy stub, real
    declarations in a subdirectory, exports map — plus the alias installation path.

    What this does not cover

    moduleResolution stays at node10, because virtual-tsc forces it and it cannot be configured
    from the adapter. That is fine for resolving a package's main entry, as above. Subpath entry points
    (rxjs/operators) go through a separate resolveTypings call and land in the case where the layout
    is deliberately left alone — I have not verified those. And this is the type side only; whether newer
    rxjs also runs is a separate question I did not touch.

    Verified with strict: true, so nothing is hidden by a silent any:

    declaration files inference const bad: number = new Observable<string>() o.next(42) on Observable<string>
    rxjs 6 6 → 65 works rejected rejected
    rxjs 7 none (was declare module) → 190 works rejected rejected

    The two negative probes matter as much as the positive one: they are what tells a properly typed
    library apart from one that is silently any, and they are checked to fail on the type error rather
    than on "cannot find module".

    There is a regression test for all of it, with a fixture shaped like rxjs 7 — legacy stub, real
    declarations in a subdirectory, exports map — plus the alias installation path.

    What this does not cover

    moduleResolution stays at node10, because virtual-tsc forces it and it cannot be configured
    from the adapter. That is fine for resolving a package's main entry, as above. Subpath entry points
    (rxjs/operators) go through a separate resolveTypings call and land in the case where the layout
    is deliberately left alone — I have not verified those. And this is the type side only; whether newer
    rxjs also runs is a separate question I did not touch.

    So the honest answer to your question: rethinking the compilation would still be the cleaner long-term
    route if we ever need real node16/bundler semantics, but neither type inference nor modern
    packages require it.

  10. added a commit that references this issue on Aug 16, 2026
  11. agross commented on Aug 17, 2026

    @agross
    ContributorAuthor

    One additional scenario does not appear to be covered: when both global and non-global scripts import the same library (using rxjs as an example), the generated code fails with a duplicate declaration:

    2026-08-17 09:22:12.034  - error: javascript.0 (1061528) script.js.Automation.Bind_Amp_To_TV: compile failed at: script.js.Automation.Bind_Amp_To_TV:197
    2026-08-17 09:22:12.035  - error: javascript.0 (1061528) script.js.Automation.Bind_Amp_To_TV: const rxjs_1 = require("rxjs");
    2026-08-17 09:22:12.035  - error: javascript.0 (1061528) script.js.Automation.Bind_Amp_To_TV:       ^
    2026-08-17 09:22:12.035  - error: javascript.0 (1061528) script.js.Automation.Bind_Amp_To_TV: SyntaxError: Identifier 'rxjs_1' has already been declared
    2026-08-17 09:22:12.035  - error: javascript.0 (1061528) script.js.Automation.Bind_Amp_To_TV:     at new Script (node:vm:117:7)
    2026-08-17 09:22:12.035  - error: javascript.0 (1061528) script.js.Automation.Bind_Amp_To_TV:     at JavaScript.createVM (/opt/iobroker/node_modules/iobroker.javascript/src/main.ts:2841:25)
    2026-08-17 09:22:12.035  - error: javascript.0 (1061528) script.js.Automation.Bind_Amp_To_TV:     at JavaScript.prepareScript (/opt/iobroker/node_modules/iobroker.javascript/src/main.ts:3464:62)
    

    My fork includes a fix for this case in these two commits:

    Could you please include this scenario when evaluating the proposed changes?

  12. added a commit that references this issue on Aug 24, 2026
  13. GermanBluefox commented on Aug 30, 2026

    @GermanBluefox
    Contributor

    One additional scenario does not appear to be covered: when both global and non-global scripts import the same library (using rxjs as an example), the generated code fails with a duplicate declaration:

    2026-08-17 09:22:12.034  - error: javascript.0 (1061528) script.js.Automation.Bind_Amp_To_TV: compile failed at: script.js.Automation.Bind_Amp_To_TV:197
    2026-08-17 09:22:12.035  - error: javascript.0 (1061528) script.js.Automation.Bind_Amp_To_TV: const rxjs_1 = require("rxjs");
    2026-08-17 09:22:12.035  - error: javascript.0 (1061528) script.js.Automation.Bind_Amp_To_TV:       ^
    2026-08-17 09:22:12.035  - error: javascript.0 (1061528) script.js.Automation.Bind_Amp_To_TV: SyntaxError: Identifier 'rxjs_1' has already been declared
    2026-08-17 09:22:12.035  - error: javascript.0 (1061528) script.js.Automation.Bind_Amp_To_TV:     at new Script (node:vm:117:7)
    2026-08-17 09:22:12.035  - error: javascript.0 (1061528) script.js.Automation.Bind_Amp_To_TV:     at JavaScript.createVM (/opt/iobroker/node_modules/iobroker.javascript/src/main.ts:2841:25)
    2026-08-17 09:22:12.035  - error: javascript.0 (1061528) script.js.Automation.Bind_Amp_To_TV:     at JavaScript.prepareScript (/opt/iobroker/node_modules/iobroker.javascript/src/main.ts:3464:62)
    

    My fork includes a fix for this case in these two commits:

    Could you please include this scenario when evaluating the proposed changes?

    Am I understand you right you make something like:

    GLOBAL_SCRIPT
    const rxjs = require('rxjs');
    ...
    
    Custom SCRIPT
    const rxjs = require('rxjs');
    
    
    

    ?
    If yes, why you import rxjs 2 times?

  14. agross commented on Aug 30, 2026

    @agross
    ContributorAuthor

    The example you've given, or even multiple global scripts importing rxjs. Every script is considered isolated from the editor's perspective, so you need to import in every script. You would need to do the same when developing in a normal Javascript project outside of iobroker. In addition, I also compile the scripts using Typescript "project files" to emulate iobroker's global script behavior in e.g. GitHub actions: https://github.com/agross/iobroker-scripts/blob/master/home/Automation/Alarm.tsconfig.json

  15. agross commented on Oct 8, 2026

    @agross
    ContributorAuthor

    @GermanBluefox Can you please look at this? It's preventing me from upgrading the adapter.

    Using the same imports in global scripts and regular scripts should work as before.

  16. agross commented on Oct 11, 2026

    @agross
    ContributorAuthor

    @GermanBluefox My fork includes the fix, rebased on master.

  17. GermanBluefox commented on Oct 16, 2026

    @GermanBluefox
    Contributor

    23. @GermanBluefox My fork includes the fix, rebased on master.

    Can you make a PR for that?

  18. GermanBluefox commented on Oct 16, 2026

    @GermanBluefox
    Contributor

    @agross Thanks for the fix! The PR is here: #2408

    It contains your two commits (Isolate compiled TypeScript module bindings and Isolate TypeScript bindings between global scripts), with you as the author. I left out the first two commits of your branch, because they only changed the old build/src output and master already has the typings part. Your fixtures were also not needed: the new tests in test/testTypeScript.js use a simple ambient module declaration instead.

    The PR description lists a few behaviour changes that come with the wrapping (strict mode, no hoisting across scripts, exported const/let of global scripts now visible). If you can, please test the branch fix/ts-import-collision-2341 with your scripts.

  19. agross commented on Oct 17, 2026

    @agross
    ContributorAuthor

    @GermanBluefox Thanks, I installed the adapter from the branch and recompiled all scripts. No errors. LGTM!

  20. GermanBluefox commented on Oct 17, 2026

    @GermanBluefox
    Contributor

    Thanks for testing! I will merge #2408 and it will be in the next release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions