Global functions for use in rules

Well… just to update on this, I got it to work without having to do any of the npm stuff, don’t ask me how.

All I did was move the common.js into another folder (ready to do the init step), VSCode asked me if I wanted to update the import paths, I said yes and saved the file… no errors. WOW!

So I tested the rule and it worked as expected, I moved the file into the js folder and it auto updated the path, and it still worked, so I am thinking it was a path issue all along.

You must, however, reference the file from the automation level (don’t ask me why this is important, but referencing the file any other way seems to throw an error:

const common = require("../jsr223/common/common.js"); 

I created a common folder to store all my shared libs so the path is not a typo.

Hopefully others will try this and verify it is indeed a working example.

If you are using the JS Scripting add-on you shouldn’t be placing anything under automation/jsr223. That’s a legacy folder for the legacy Nashorn JS add-on, Jython add-on and I think the still very much used and maintained Groovy add-on. All the files JS Scripting uses are expected to be in automation/js.

You have to add the ../jsr223/common part because that’s not a folder that JS Scripting normally looks at for anything. The full path being used here (assuming a Linux install) is /etc/openhab/automation/js/../jsr223/common/common.js.

If you wanted to create a normal library for JS Scripting where you don’t even need to mess around with paths in your rules, you must follow the instructions in the add-on docs to create a module. You only have to do this once though. Once created and installed you can edit your library in place (i.e. automation/js/node_modules/common). And you can use simple imports (i.e. require('common')).

This is one of the dangers of relying upon and replying on a seven year old thread. JS Scripting isn’t JSR223. It doesn’t use the JSR223 folder. It’s not related to JSR223. And none of that is mentioned above because the GraalVM based JS Scripting add-on didn’t exist seven years ago. A recently installed OH instance won’t even have an automation/jsr223 folder unless/until the Nashorn JS, Jython, or Groovy add-ons are installed.

Yes, I moved all the files back into the js folder and now the scripts auto reload when the library is updated, which is what I wanted. Thanks.

I was merely giving another method to achieve the same result without all the hassle of the npm stuff. For example, if you had some constants that never change and need no maintenance you could use the ‘easier’ method and not have to go through a quite complicated process.

In any case, you are right about the issues with old threads and legacy folders, however it’s also worth mentioning that scripts placed in those folders do actually still work as expected (albeit the automatic reloading of scripts with dependant libraries does not), meaning users could use that folder for constants and other fixed libraries using the method outlined above.

It’s also entirely possible that people arriving here as I did from google can now get a better understanding and a possible workaround to get libraries working thanks to our discussion.

I do want to make two things clear though. First, placing JS Scripting libraries anywhere except the automation/js/node_modules is not officially supported and not guaranteed to work forever. And since it’s not supported, any such change would not be announced or treated like a breaking change. This may be slightly simpler now, but it could set you up for a lot of extra work later on.

Secondly, you only have to do the npm thing once. I don’t mean once per change, or once per file, I mean once for all time.

When you have the module installed and configured and initialized correctly, you can add, remove, and modify the library in place as much as you want.

And it’s not really that complicated. You can copy the following to a shell script and run it once, and you are done with the npm stuff forever. You’ll pass the name you want to use for your library as the first argument.

This assumes you are already logged in to your openHAB machine, it’s an installed openHAB, and npm is installed. All of this will be true for an openHABian install. If your OH isn’t installed, change line 21 to the path to your automation/js folder.

createLibrary.sh

#!/bin/bash

# initialize the library
mkdir /tmp/$1
cd /tmp/$1
npm init

echo -e "var someProperty = 'Hello world!'\n" \
        "function someFunction() {\n" \
        "  console.log('Hello from your personal library!');\n" \
        "}\n\n" \
        "module.exports = {\n" \
        "  someProperty,\n" \
        "  someFunction\n" \
        "};" > index.js

# tar the library
npm pack

# install the library
cd /etc/openhab/automation/js
npm install /tmp/$1/$1-1.0.0.tgz

Or you can run these steps manually. index.js is just a copy of the stub from the docs.

When you run this script you’ll be asked a bunch of questions. You can just keep hitting enter until the script completes or provide your own answers. Just do not change the library name nor the version because both of these are hard coded in the script for the next steps.

Example run (I use starship, so my prompt didn’t come over very well in the copy and paste):

󰕈 argus  rich  ~      08:30  
 ./createLibrary.sh test
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (test) 
version: (1.0.0) 
description: 
entry point: (index.js) 
test command: 
git repository: 
keywords: 
author: 
license: (ISC) 
About to write to /tmp/test/package.json:

{
  "name": "test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this OK? (yes) 
npm notice 
npm notice 📦  test@1.0.0
npm notice === Tarball Contents === 
npm notice 175B index.js    
npm notice 200B package.json
npm notice === Tarball Details === 
npm notice name:          test                                    
npm notice version:       1.0.0                                   
npm notice filename:      test-1.0.0.tgz                          
npm notice package size:  361 B                                   
npm notice unpacked size: 375 B                                   
npm notice shasum:        2f11486f0e2e2d088a1b7759d904da2a12698832
npm notice integrity:     sha512-wr/klz8z+xTIn[...]vaHH7OCleJ/ZQ==
npm notice total files:   2                                       
npm notice 
test-1.0.0.tgz

added 1 package, and audited 8 packages in 539ms

found 0 vulnerabilities

󰕈 argus  rich  ~      08:30  
 ls /srv/openhab/conf/automation/js/node_modules/
'@js-joda'/   openhab_rules_tools/   rlk_personal/   test/

󰕈 argus  rich  ~      08:30  
 cat /srv/openhab/conf/automation/js/node_modules/test/index.js
var someProperty = 'Hello world!'
 function someFunction() {
   console.log('Hello from your personal library!');
 }

 module.exports = {
   someProperty,
   someFunction
 };

From this point on I can make any changes to any file in automation/js/node_modules/test and they will be picked up and won’t be removed if I install or update the other libraries. If “Enable dependency tracking” is enabled (it’s enabled by default) any change to a library will cause the rules that require that library to reload and pick up the changes automatically.

index.js is the “entrypoint” for the library. You can put all your stuff in there if you have a small library, or you can modify it to just export your other files.

For example, my library as two files, alerting.js and utils.js. My index.js looks like:

module.exports = {
  get alerting() { return require('./alerting.js') },
  get utils() { return require('./utils.js') }
}

and I use them in a rule using (this is a script condition):

const {alerting} = require('rlk_personal');
return !alerting.isNight();

isNight() is a function exported in alerting.js which returns true when it’s night. What is considered night differs depending on a number of factors like the type of day.

When I change alerting.js, all rules that require alerting will be reloaded. But those rules that only require utils will not because utils didn’t change.