diff --git a/README.md b/README.md index a00d95d5b..96013a2d6 100644 --- a/README.md +++ b/README.md @@ -859,28 +859,29 @@ See [openhab-js : actions.NotificationBuilder](https://openhab.github.io/openhab ### Cache -The cache namespace provides both a private and a shared cache that can be used to set and retrieve objects that will be persisted between subsequent runs of the same or between scripts. +The cache namespace provides both a private and a shared cache that can be used to set and retrieve data that will be persisted between subsequent runs of the same or between scripts. The private cache can only be accessed by the same script and is cleared when the script is unloaded. -You can use it to e.g. store timers or counters between subsequent runs of that script. +You can use it to store both primitives and objects, e.g. store timers or counters between subsequent runs of that script. When a script is unloaded and its cache is cleared, all timers (see [`createTimer`](#createtimer)) stored in its private cache are automatically cancelled. The shared cache is shared across all rules and scripts, it can therefore be accessed from any automation language. The access to every key is tracked and the key is removed when all scripts that ever accessed that key are unloaded. If that key stored a timer, the timer will be cancelled. +You can use it to store **only primitives**, as storing objects is not thread-safe and can cause script execution failures. See [openhab-js : cache](https://openhab.github.io/openhab-js/cache.html) for full API documentation. - cache : object - .private - - .get(key, defaultSupplier) ⇒ Object | null - - .put(key, value) ⇒ Previous Object | null - - .remove(key) ⇒ Previous Object | null + - .get(key, defaultSupplier) ⇒ * | null + - .put(key, value) ⇒ Previous * | null + - .remove(key) ⇒ Previous * | null - .exists(key) ⇒ boolean - .shared - - .get(key, defaultSupplier) ⇒ Object | null - - .put(key, value) ⇒ Previous Object | null - - .remove(key) ⇒ Previous Object | null + - .get(key, defaultSupplier) ⇒ * | null + - .put(key, value) ⇒ Previous * | null + - .remove(key) ⇒ Previous * | null - .exists(key) ⇒ boolean The `defaultSupplier` provided function will return a default value if a specified key is not already associated with a value. @@ -888,19 +889,17 @@ The `defaultSupplier` provided function will return a default value if a specifi **Example** *(Get a previously set value with a default value (times = 0))* ```js -var counter = cache.private.get('counter', () => ({ 'times': 0 })); -console.log('Count', counter.times++); +var counter = cache.shared.get('counter', () => 0); +console.log('Counter: ' + counter); ``` -**Example** *(Get a previously set object)* +**Example** *(Get a previously set value, modify and store it)* ```js var counter = cache.private.get('counter'); -if (counter === null) { - counter = { times: 0 }; - cache.private.put('counter', counter); -} -console.log('Count', counter.times++); +counter++; +console.log('Counter: ' + counter); +cache.private.put('counter', counter); ``` ### Time diff --git a/src/cache.js b/src/cache.js index e7f6a5cb0..30bd79773 100644 --- a/src/cache.js +++ b/src/cache.js @@ -10,12 +10,18 @@ const { privateCache, sharedCache } = require('@runtime/cache'); * The {@link JSCache} can be used by to share information between subsequent runs of the same script or between scripts (depending on implementation). */ class JSCache { + #valueCache; + /** * @param {*} valueCacheImpl an implementation of the Java {@link https://github.com/openhab/openhab-core/blob/main/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/shared/ValueCache.java ValueCache} interface * @hideconstructor */ constructor (valueCacheImpl) { - this._valueCache = valueCacheImpl; + this.#valueCache = valueCacheImpl; + } + + #isSharedCache () { + return this.#valueCache === sharedCache; } /** @@ -27,9 +33,9 @@ class JSCache { */ get (key, defaultSupplier) { if (typeof defaultSupplier === 'function') { - return this._valueCache.get(key, defaultSupplier); + return this.#valueCache.get(key, defaultSupplier); } else { - return this._valueCache.get(key); + return this.#valueCache.get(key); } } @@ -41,7 +47,10 @@ class JSCache { * @returns {*|null} the previous value associated with the key, or null if there was no mapping for key */ put (key, value) { - return this._valueCache.put(key, value); + if (typeof value === 'object' && this.#isSharedCache()) { + console.warn(`Do not use the shared cache to store the object with key '${key}', as it is not thread-safe and can cause script execution failures. Only store primitives in the shared cache!`); + } + return this.#valueCache.put(key, value); } /** @@ -51,7 +60,7 @@ class JSCache { * @returns {*|null} the previous value associated with the key or null if there was no mapping for key */ remove (key) { - return this._valueCache.remove(key); + return this.#valueCache.remove(key); } /** @@ -61,7 +70,7 @@ class JSCache { * @returns {boolean} whether the key has a mapping */ exists (key) { - return this._valueCache.get(key) !== null; + return this.#valueCache.get(key) !== null; } } @@ -71,6 +80,8 @@ module.exports = { * The access to every key is tracked and the key is removed when all scripts that ever accessed that key are unloaded. * If the key that has been auto-removed stored a timer, that timer is cancelled. * + * Do not use the shared cache to store objects, as it is not thread-safe and can cause script execution failures. + * * @memberof cache * @type JSCache */ diff --git a/types/cache.d.ts b/types/cache.d.ts index 0e3ea49df..925fbf759 100644 --- a/types/cache.d.ts +++ b/types/cache.d.ts @@ -7,7 +7,6 @@ export class JSCache { * @hideconstructor */ constructor(valueCacheImpl: any); - _valueCache: any; /** * Returns the value to which the specified key is mapped. * @@ -38,6 +37,7 @@ export class JSCache { * @returns {boolean} whether the key has a mapping */ exists(key: string): boolean; + #private; } export declare const shared: JSCache; declare const _private: JSCache; diff --git a/types/cache.d.ts.map b/types/cache.d.ts.map index a8afbc8e2..8e29cf392 100644 --- a/types/cache.d.ts.map +++ b/types/cache.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.js"],"names":[],"mappings":"AAQA;;GAEG;AACH;IACE;;;OAGG;IACH,iCAEC;IADC,iBAAiC;IAGnC;;;;;;OAMG;IACH,SAJW,MAAM,+BAEJ,MAAE,IAAI,CAQlB;IAED;;;;;;OAMG;IACH,SAJW,MAAM,eAEJ,MAAE,IAAI,CAIlB;IAED;;;;;OAKG;IACH,YAHW,MAAM,GACJ,MAAE,IAAI,CAIlB;IAED;;;;;OAKG;IACH,YAHW,MAAM,GACJ,OAAO,CAInB;CACF"} \ No newline at end of file +{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.js"],"names":[],"mappings":"AAQA;;GAEG;AACH;IAGE;;;OAGG;IACH,iCAEC;IAMD;;;;;;OAMG;IACH,SAJW,MAAM,+BAEJ,MAAE,IAAI,CAQlB;IAED;;;;;;OAMG;IACH,SAJW,MAAM,eAEJ,MAAE,IAAI,CAOlB;IAED;;;;;OAKG;IACH,YAHW,MAAM,GACJ,MAAE,IAAI,CAIlB;IAED;;;;;OAKG;IACH,YAHW,MAAM,GACJ,OAAO,CAInB;;CACF"} \ No newline at end of file diff --git a/types/items/metadata/itemchannellink.d.ts b/types/items/metadata/itemchannellink.d.ts index f5f45d220..66359413c 100644 --- a/types/items/metadata/itemchannellink.d.ts +++ b/types/items/metadata/itemchannellink.d.ts @@ -33,7 +33,7 @@ export type Item = { sendCommandIfDifferent(value: any): boolean; sendIncreaseCommand(value: any): boolean; sendDecreaseCommand(value: any): boolean; - "__#5@#getToggleState"(): "PAUSE" | "PLAY" | "OPEN" | "CLOSED" | "ON" | "OFF"; + "__#6@#getToggleState"(): "PAUSE" | "PLAY" | "OPEN" | "CLOSED" | "ON" | "OFF"; sendToggleCommand(): void; postToggleUpdate(): void; postUpdate(value: any): void; diff --git a/types/items/metadata/metadata.d.ts b/types/items/metadata/metadata.d.ts index 0ee9463b3..2c5447ffb 100644 --- a/types/items/metadata/metadata.d.ts +++ b/types/items/metadata/metadata.d.ts @@ -24,7 +24,7 @@ export type Item = { sendCommandIfDifferent(value: any): boolean; sendIncreaseCommand(value: any): boolean; sendDecreaseCommand(value: any): boolean; - "__#5@#getToggleState"(): "PAUSE" | "PLAY" | "OPEN" | "CLOSED" | "ON" | "OFF"; + "__#6@#getToggleState"(): "PAUSE" | "PLAY" | "OPEN" | "CLOSED" | "ON" | "OFF"; sendToggleCommand(): void; postToggleUpdate(): void; postUpdate(value: any): void; diff --git a/types/quantity.d.ts b/types/quantity.d.ts index d62b301cd..c9d7d224f 100644 --- a/types/quantity.d.ts +++ b/types/quantity.d.ts @@ -37,7 +37,7 @@ export type Item = { sendCommandIfDifferent(value: any): boolean; sendIncreaseCommand(value: any): boolean; sendDecreaseCommand(value: any): boolean; - "__#5@#getToggleState"(): "PAUSE" | "PLAY" | "OPEN" | "CLOSED" | "ON" | "OFF"; + "__#6@#getToggleState"(): "PAUSE" | "PLAY" | "OPEN" | "CLOSED" | "ON" | "OFF"; sendToggleCommand(): void; postToggleUpdate(): void; postUpdate(value: any): void; diff --git a/types/rules/operation-builder.d.ts b/types/rules/operation-builder.d.ts index 3eb14f0cd..ed6bbd794 100644 --- a/types/rules/operation-builder.d.ts +++ b/types/rules/operation-builder.d.ts @@ -33,7 +33,7 @@ export type Item = { sendCommandIfDifferent(value: any): boolean; sendIncreaseCommand(value: any): boolean; sendDecreaseCommand(value: any): boolean; - "__#5@#getToggleState"(): "PAUSE" | "PLAY" | "OPEN" | "CLOSED" | "ON" | "OFF"; + "__#6@#getToggleState"(): "PAUSE" | "PLAY" | "OPEN" | "CLOSED" | "ON" | "OFF"; sendToggleCommand(): void; postToggleUpdate(): void; postUpdate(value: any): void; diff --git a/types/triggers.d.ts b/types/triggers.d.ts index 6e5944295..46e0a8777 100644 --- a/types/triggers.d.ts +++ b/types/triggers.d.ts @@ -33,7 +33,7 @@ export type Item = { sendCommandIfDifferent(value: any): boolean; sendIncreaseCommand(value: any): boolean; sendDecreaseCommand(value: any): boolean; - "__#5@#getToggleState"(): "PAUSE" | "PLAY" | "OPEN" | "CLOSED" | "ON" | "OFF"; + "__#6@#getToggleState"(): "PAUSE" | "PLAY" | "OPEN" | "CLOSED" | "ON" | "OFF"; sendToggleCommand(): void; postToggleUpdate(): void; postUpdate(value: any): void;