Javascript rules written in Typescript

Hello everyone,

I am currently still using openhab 3.4.4 and would like to finally update to the latest version.
Unfortunately, this is currently not possible as I cannot migrate my rules.
I think I have a small comprehension problem and I hope you can help me to solve it.

But now to the main problem:

I migrated all my DSL rules to openhab-js (4.0.0) a long time ago. So that I could solve more complex problems, I used object-oriented Typescript to build the rules. This also worked well with corresponding build scripts.

Now I would like to switch to Openhab 4.1 with openhab-js 4.9.0. But that doesn’t seem to be so easy. In the course of the migration, I also wanted to use the Quantity data type. This is where the problems started


I’d better post the code from a minimal test project:

The error after importing the test rule:

09:19:24.748 [DEBUG] [g.internal.OpenhabGraalJSScriptEngine] - Initializing GraalJS script engine...
09:19:24.758 [DEBUG] [g.internal.OpenhabGraalJSScriptEngine] - Injecting ThreadsafeTimers into the JS runtime...
09:19:24.760 [DEBUG] [g.internal.OpenhabGraalJSScriptEngine] - Evaluating cached global script...
09:19:24.764 [DEBUG] [g.internal.OpenhabGraalJSScriptEngine] - Successfully initialized GraalJS script engine.
09:19:24.778 [ERROR] [ab.automation.script.javascript.stack] - Failed to execute script:
org.graalvm.polyglot.PolyglotException: TypeError: Cannot load CommonJS module: 'openhab/types/quantity'
        at com.oracle.truffle.polyglot.PolyglotMapAndFunction.apply(PolyglotMapAndFunction.java:46) ~[?:?]
...

The index.ts file:

import "openhab"
import { Quantity, getQuantity } from "openhab/types/quantity";

let myTestVariable: Quantity=getQuantity("0 kWh");
console.log(myTestVariable.toString());

The index.js file created by tsc:

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
require("openhab");
const quantity_1 = require("openhab/types/quantity");
let myTestVariable = (0, quantity_1.getQuantity)("0 kWh");
console.log(myTestVariable.toString());

tsconfig.json:

{
  "include": ["./src/**/*"],
  "compilerOptions": {
    "target": "es2021",
    "module": "CommonJS",
    "rootDir": "./src",
    "moduleResolution": "node",
    "baseUrl": "./",
    "typeRoots": [],
    "outDir": "./dist/",
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}

At least the package.json:

{
  "name": "openhab-build-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "rm -r ./dist/* ; ./node_modules/typescript/bin/tsc --build && cp -r ./node_modules ./dist/",
    "clean": "./node_modules/typescript/bin/tsc --build --clean"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "typescript": "^5.4.5"
  },
  "dependencies": {
    "openhab": "^4.9.0"
  }
}

The imports from the types folder should of course not end up in index.js.
Therefore I have listed the types folder of the openhab package in the typeRoots property of tsconfig.json:

{
  //...
  "typeRoots": ["./node_modules/@types", "./node_modules/openhab/types"]
}

But then i get a build error:

error TS2688: Cannot find type definition file for 'items'.
  The file is in the program because:
    Entry point for implicit type library 'items'

Found 1 error.

I am grateful to anyone who can help me find a solution. :grinning:

Hello Andreas,

I think I found a solution :+1:

The problem is, that code-wise Quantity is the name under which the factory method getQuantity is exported, but type-wise Quantity is the name of the actual class Quantity.

A wrong type definition caused the TypeScript compiler not to “realize” this situation: in the main .d.ts file, the Quantity namespace is declared as

export const Quantity: typeof import("./quantity");

but it should be

export const Quantity: typeof import("./quantity").getQuantity;

I have pushed this fix to the openhab-js repo, see Fix type definition for Quantity namespace · openhab/openhab-js@ee0a59a · GitHub.

Can you please manually edit node_modules/openhab/types/openhab-js.d.ts to try out if it now works for you?

FYI instead of using typescript itself, you can also write your rules in plain JS and add // @ts-check to its top. This enables type checks for the use of the openhab API inside your IDE, but does not require you to write TypeScript.

1 Like

I also moved from TypeScript back to plain JS. In VSC no big difference anymore.

1 Like

:+1:

As long as enough JSDoc is added to the code, VS Code and WebStorm are really good at type checking vanilla JS.
For my personal library, I also have type defs generated from JSDoc, this allows a great IntelliSense when writing scripts.

But if one wants to use TypeScript, no problem. The problem wasn’t related to TypeScript being used by rather wrong type defs.

But I like Typescript
 :face_with_peeking_eye:

I have been writing web apps with the “good old” Javascript and ExtJS for years and came to Typescript two years ago.
And what should I say, I like it. The explicit types, decorators, interfaces, etc.
It just feels better to me (almost a bit like C# or Java).

But never mind:
@florian: I have implemented your change in my test project but unfortunately the behavior is still the same. Without the type roots in the tsconfig.json the code compiles but the includes of the types are still included in the javascript and the type error exception is still thrown.

If I add the types from the openhab package to the type roots, I still get the tsc error message “error TS2688: Cannot find type definition file for ‘items’.”

In the meantime, I have already tried to recreate the type declaration files using dts-gen.
But this fails due to the unresolvable Java.type() calls.

Any more ideas?

In the worst case I have to migrate the rules to Javascript.

However, I would like to avoid this if possible. My rules have become quite complex and large over the years (20,000+ lines of code I think).
I don’t really want to have to rewrite and, above all, test them again.

No problem :+1:

Hmm, I don’t understand why, because when adding // @ts-check to a vanilla JS script in VS Code, type definitions for items work there.

If you send me enough information (tsconfig, package.json to install the right dep versions, the script itself) I can try if I can find a solution. Just PM over the community.

Hello Florian,

I had a little time to test again today and I think I have found a solution.

The declaration files from the openhab package can apparently not be used with typescript.

To solve the problem I did the following:

  1. I copied your types files into my project (./types)
  2. in the tsconfig I deactivated the TypeRoots again
  3. in the declaration files I have introduced module declarations.
  4. i have replaced the relative imports to other declaration files with typescript imports to the JS modules.
  5. the Java types are declared in an extra file.
  6. in the code itself, I import the JS modules directly without going via index.js

For example the items.d.ts

declare module 'openhab/src/items/items' { 
//export type ItemMetadata = import('../items/metadata/metadata').ItemMetadata;
//export type Quantity = import('../quantity').Quantity;
//import ItemSemantics = require("./item-semantics");
import { ItemHistory } from "openhab/src/items/item-history";
import { ItemMetadata } from "openhab/src/items/metadata/metadata";
import { Quantity } from "openhab/src/quantity";
import { ItemSemantics } from "openhab/src/items/item-semantics";
...

And the test source code looks like this:

import { Quantity, getQuantity } from "openhab/src/quantity"
import * as items from "openhab/src/items/items"

const myTestVariable:Quantity=getQuantity("0 kWh");
console.log(myTestVariable.toString())

//List all data points with historization
items.getItems()
    .filter(item=> item.history.lastUpdate() != null)
    .forEach(item => console.log(item.name));

With these changes, the typescript code is transpiled and can also be executed in openhab.
I am sure that this is not all correct, but at least it works.
In the next step I will move the types into a separate private npm package and test them further.

If anyone is interested in the declarations, I will be glad to publish them.

1 Like