From 818c4029f3ed8e01db0b73f667a6c7f7cb068394 Mon Sep 17 00:00:00 2001 From: Max Hauser Date: Tue, 2 Apr 2024 15:18:39 +0200 Subject: [PATCH] removed binary states (#2655) * removed binary states * rm main.ts unexisting method * fix linter * delete all binary states on setup first --- README.md | 3 - packages/adapter/src/lib/_Types.ts | 19 - packages/adapter/src/lib/adapter/adapter.ts | 453 +----------------- packages/adapter/src/lib/adapter/validator.ts | 8 +- packages/cli/src/lib/cli/cliStates.ts | 81 +--- packages/cli/src/lib/cli/messages.ts | 2 +- packages/cli/src/lib/setup.ts | 7 - packages/cli/src/lib/setup/setupSetup.ts | 67 ++- packages/common/src/lib/common/tools.ts | 12 +- packages/controller/src/lib/objects.ts | 10 - packages/controller/src/main.ts | 53 +- packages/controller/test/lib/testFiles.ts | 125 ----- .../controller/test/lib/testHelperStates.ts | 1 - .../src/lib/states/statesInMemFileDB.js | 34 +- .../src/lib/states/statesInMemServerRedis.js | 18 +- .../src/lib/states/statesInMemServerRedis.js | 18 +- .../src/lib/states/statesInRedisClient.ts | 68 --- packages/types-dev/index.d.ts | 3 - packages/types-dev/objects.d.ts | 2 +- packages/types-public/index.test-d.ts | 8 - 20 files changed, 105 insertions(+), 887 deletions(-) diff --git a/README.md b/README.md index 9c3d27af7a..4f1ffd2f9a 100644 --- a/README.md +++ b/README.md @@ -1162,9 +1162,6 @@ Following adapter methods support maintenance mode: - adapter.getForeignState - adapter.delForeignState -- adapter.setBinaryState -- adapter.getBinaryState -- adapter.delBinaryState ``` *** Do not use this mode for any other purposes except sanitizing/cleaning/repairing of existing DBs (Object and States)*** diff --git a/packages/adapter/src/lib/_Types.ts b/packages/adapter/src/lib/_Types.ts index 0c40c48807..3b524c4516 100644 --- a/packages/adapter/src/lib/_Types.ts +++ b/packages/adapter/src/lib/_Types.ts @@ -384,13 +384,6 @@ export interface InternalSubscribeOptions { callback?: ioBroker.ErrorCallback; } -export interface InternalSetBinaryStateOptions { - id: string; - options?: Record | null; - binary: Buffer; - callback?: ioBroker.SetStateCallback; -} - export interface InternalAddChannelToEnumOptions { enumName: string; addTo: string; @@ -443,18 +436,6 @@ export interface InternalGetStatesOptions { callback: ioBroker.GetStatesCallback; } -export interface InternalGetBinaryStateOption { - id: string; - options: Record; - callback?: ioBroker.GetBinaryStateCallback; -} - -export interface InternalDelBinaryStateOptions { - id: string; - options: Record; - callback?: ioBroker.ErrorCallback; -} - export interface InternalDeleteDeviceOptions { deviceName: string; callback?: ioBroker.ErrorCallback; diff --git a/packages/adapter/src/lib/adapter/adapter.ts b/packages/adapter/src/lib/adapter/adapter.ts index 57e94d6f84..454d2bb1ff 100644 --- a/packages/adapter/src/lib/adapter/adapter.ts +++ b/packages/adapter/src/lib/adapter/adapter.ts @@ -60,7 +60,6 @@ import type { InternalCheckPasswordOptions, InternalCreateDeviceOptions, InternalCreateStateOptions, - InternalDelBinaryStateOptions, InternalDeleteChannelFromEnumOptions, InternalDeleteChannelOptions, InternalDeleteDeviceOptions, @@ -71,7 +70,6 @@ import type { InternalDestroySessionOptions, InternalFormatDateOptions, InternalGetAdapterObjectsOptions, - InternalGetBinaryStateOption, InternalGetCertificatesOptions, InternalGetChannelsOfOptions, InternalGetDevicesOptions, @@ -90,7 +88,6 @@ import type { InternalGetUserIDOptions, InternalSendToHostOptions, InternalSendToOptions, - InternalSetBinaryStateOptions, InternalSetObjectOptions, InternalSetPasswordOptions, InternalSetSessionOptions, @@ -275,46 +272,6 @@ export interface AdapterClass { subscribeStatesAsync(pattern: Pattern, options?: unknown): Promise; /** Subscribe from changes of states in this instance */ unsubscribeStatesAsync(pattern: Pattern, options?: unknown): Promise; - /** - * Writes a binary state into Redis. The ID will not be prefixed with the adapter namespace. - * - * @deprecated Please use `writeFile` instead of binary states - */ - setForeignBinaryStateAsync(id: string, binary: Buffer, options?: unknown): ioBroker.SetStatePromise; - - /** - * Despite the naming convention, this method doesn't prepend the adapter namespace. Use setForeignBinaryStateAsync instead. - * Writes a binary state into Redis - * - * @deprecated Please use `writeFile` instead of binary states - */ - setBinaryStateAsync(id: string, binary: Buffer, options?: unknown): ioBroker.SetStatePromise; - - /** - * @deprecated Please use `readFile` instead of binary states - */ - getForeignBinaryStateAsync(id: string, options?: unknown): ioBroker.GetBinaryStatePromise; - /** - * Despite the naming convention, this method doesn't prepend the adapter namespace. Use getForeignBinaryStateAsync instead. - * Reads a binary state from Redis - * - * @deprecated Please use `readFile` instead of binary states - */ - getBinaryStateAsync(id: string, options?: unknown): ioBroker.GetBinaryStatePromise; - /** - * Deletes a binary state from the states DB. The ID will not be prefixed with the adapter namespace. - * - * @deprecated Please use `delFile` instead of binary states - */ - delForeignBinaryStateAsync(id: string, options?: unknown): Promise; - - /** - * Despite the naming convention, this method doesn't prepend the adapter namespace. Use delForeignBinaryStateAsync instead. - * Deletes a binary state from the states DB - * - * @deprecated Please use `delFile` instead of binary states - */ - delBinaryStateAsync(id: string, options?: unknown): Promise; /** * Helper function that looks for first free TCP port starting with the given one. */ @@ -1200,55 +1157,6 @@ export class AdapterClass extends EventEmitter { */ this.unsubscribeStatesAsync = tools.promisify(this.unsubscribeStates, this); - /** - * Promise-version of `Adapter.setBinaryState` - * - * @param id of state - * @param binary data - * @param options optional - */ - this.setForeignBinaryStateAsync = tools.promisify(this.setForeignBinaryState, this); - - /** - * Async version of setBinaryState - * - * @param id of state - * @param binary data - * @param options optional - */ - this.setBinaryStateAsync = tools.promisify(this.setBinaryState, this); - - /** - * Promise-version of `Adapter.getBinaryState` - * - * - */ - this.getForeignBinaryStateAsync = tools.promisify(this.getForeignBinaryState, this); - - /** - * Promisified version of getBinaryState - * - * @param id The state ID - * @param options optional - */ - this.getBinaryStateAsync = tools.promisify(this.getBinaryState, this); - - /** - * Promise-version of `Adapter.delForeignBinaryState` - * - * @param id - * @param options - */ - this.delForeignBinaryStateAsync = tools.promisify(this.delForeignBinaryState, this); - - /** - * Promise-version of `Adapter.delBinaryState` - * - * @param id - * @param options - */ - this.delBinaryStateAsync = tools.promisify(this.delBinaryState, this); - this.setExecutableCapabilities = tools.setExecutableCapabilities; this._init(); } @@ -2860,8 +2768,8 @@ export class AdapterClass extends EventEmitter { * * @param id of the object * @param obj The object to set - * @param [options] - * @param [callback] + * @param options optional user context + * @param callback optional callback */ private async _setObjectWithDefaultValue( id: string, @@ -4583,11 +4491,7 @@ export class AdapterClass extends EventEmitter { } if (obj.type === 'state') { try { - if ('binary' in obj) { - await this.delBinaryStateAsync(id, options); - } else { - await this.delForeignStateAsync(id, options); - } + await this.delForeignStateAsync(id, options); } catch { // Ignore } @@ -10018,357 +9922,6 @@ export class AdapterClass extends EventEmitter { }); } - setForeignBinaryState(id: string, binary: Buffer, callback: ioBroker.SetStateCallback): void; - setForeignBinaryState(id: string, binary: Buffer, options: unknown, callback: ioBroker.SetStateCallback): void; - - /** - * Write binary block into redis, e.g. image - * - * @param id of state - * @param binary data - * @param options optional - * @param callback - * @deprecated Please use `writeFile` instead of binary states - */ - setForeignBinaryState(id: unknown, binary: unknown, options: unknown, callback?: unknown): any { - this._logger.info( - `${ - this.namespaceLog - } Information for Developer: Binary States are deprecated and will be removed in js-controller 5.1, please migrate to Files (${ - id as string - })` - ); - - if (typeof options === 'function') { - callback = options; - options = {}; - } - - Validator.assertString(id, 'id'); - Validator.assertOptionalCallback(callback, 'callback'); - Validator.assertBuffer(binary, 'binary'); - if (options !== null && options !== undefined) { - Validator.assertObject(options, 'options'); - } - - return this._setForeignBinaryState({ id, binary, options, callback }); - } - - private async _setForeignBinaryState(_options: InternalSetBinaryStateOptions): Promise { - const { id, binary, callback } = _options; - let { options } = _options; - - try { - this._utils.validateId(id, true, options); - } catch (err) { - return tools.maybeCallbackWithError(callback, err); - } - - if (this.performStrictObjectChecks) { - // obj needs to exist and has to be of type "file" - custom check for binary state - try { - if (!this.#objects) { - this._logger.info( - `${this.namespaceLog} setBinaryState not processed because Objects database not connected` - ); - return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED); - } - - const obj = await this.#objects.getObjectAsync(id); - - // at first check object existence - if (!obj) { - this._logger.warn( - `${this.namespaceLog} Binary state "${id}" has no existing object, this might lead to an error in future versions` - ); - return; - } - - // for a state object we require common.type to exist - if (obj.common && obj.common.type) { - if (obj.common.type !== 'file') { - this._logger.info( - `${this.namespaceLog} Binary state object has to be type "file" but is "${obj.common.type}"` - ); - } - } - } catch (e) { - this._logger.warn( - `${this.namespaceLog} Could not perform strict object check of binary state ${id}: ${e.message}` - ); - } - } - - if (!this.#states) { - // if states is no longer existing, we do not need to unsubscribe - this._logger.info( - `${this.namespaceLog} setBinaryState not processed because States database not connected` - ); - return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED); - } - - // we need at least user or group for checkStates - if no given assume admin - if (!options || !options.user) { - options = options || {}; - options.user = SYSTEM_ADMIN_USER; - } - - if (options.user !== SYSTEM_ADMIN_USER) { - // always read according object to set the binary flag - let obj; - try { - obj = (await this._checkStates(id, options, 'setState')).objs[0]; - } catch (e) { - return tools.maybeCallbackWithError(callback, e); - } - - if (obj && !('binary' in obj)) { - // @ts-expect-error probably need to adjust types - obj.binary = true; - - if (!this.#objects) { - this._logger.info( - `${this.namespaceLog} setBinaryState not processed because Objects database not connected` - ); - return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED); - } - - this.#objects.setObject(id, obj, err => { - if (err) { - return tools.maybeCallbackWithError(callback, err); - } else { - if (!this.#states) { - // if states is no longer existing, we do not need to unsubscribe - this._logger.info( - `${this.namespaceLog} setBinaryState not processed because States database not connected` - ); - return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED); - } - - this.outputCount++; - this.#states.setBinaryState(id, binary, callback); - } - }); - } else { - if (!this.#states) { - // if states is no longer existing, we do not need to unsubscribe - this._logger.info( - `${this.namespaceLog} setBinaryState not processed because States database not connected` - ); - return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED); - } - - this.outputCount++; - this.#states.setBinaryState(id, binary, callback); - } - } else { - this.outputCount++; - this.#states.setBinaryState(id, binary, callback); - } - } - - setBinaryState(id: string, binary: Buffer, callback: ioBroker.SetStateCallback): void; - setBinaryState(id: string, binary: Buffer, options: unknown, callback: ioBroker.SetStateCallback): void; - - /** - * Same as setForeignBinaryState but prefixes the own namespace to the id - * - * @param id of state - * @param binary data - * @param options optional - * @param callback - * @deprecated Please use `writeFile` instead of binary states - */ - setBinaryState(id: any, binary: any, options: any, callback?: any): void { - // we just keep any types here, because setForeign method will validate - id = this._utils.fixId(id, false); - return this.setForeignBinaryState(id, binary, options, callback); - } - - getForeignBinaryState(id: string, callback: ioBroker.GetBinaryStateCallback): void; - getForeignBinaryState(id: string, options: unknown, callback: ioBroker.GetBinaryStateCallback): void; - - /** - * Read a binary block from redis, e.g. an image - * - * @param id The state ID - * @param options optional - * @param callback - * @deprecated Please use `readFile` instead of binary states - */ - getForeignBinaryState(id: unknown, options: unknown, callback?: unknown): any { - this._logger.info( - `${ - this.namespaceLog - } Information for Developer: Binary States are deprecated and will be removed in js-controller 5.1, please migrate to Files (${ - id as string - })` - ); - - if (typeof options === 'function') { - callback = options; - options = {}; - } - - Validator.assertString(id, 'id'); - Validator.assertOptionalCallback(callback, 'callback'); - if (options !== null && options !== undefined) { - Validator.assertObject(options, 'options'); - } - - return this._getForeignBinaryState({ id, options: options || {}, callback }); - } - - private async _getForeignBinaryState(_options: InternalGetBinaryStateOption): Promise { - const { id, options, callback } = _options; - - if (!this.#states) { - // if states is no longer existing, we do not need to unsubscribe - this._logger.info( - `${this.namespaceLog} getBinaryState not processed because States database not connected` - ); - return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED); - } - - try { - this._utils.validateId(id, true, options); - } catch (err) { - return tools.maybeCallbackWithError(callback, err); - } - - // we need at least user or group for checkStates - if no given assume admin - if (!options.user) { - options.user = SYSTEM_ADMIN_USER; - } - // always read according object to set the binary flag - let obj: ioBroker.StateObject; - - try { - obj = (await this._checkStates(id, options, 'getState')).objs[0]; - } catch (e) { - return tools.maybeCallbackWithError(callback, e); - } - - this.#states!.getBinaryState(id, (err, data) => { - if (!err && data && obj && !('binary' in obj)) { - // @ts-expect-error type adjustment needed? - obj.binary = true; - this.#objects!.setObject(id, obj, err => { - if (err) { - return tools.maybeCallbackWithError(callback, err); - } else { - return tools.maybeCallbackWithError(callback, null, data); - } - }); - } else { - // if no buffer, and state marked as not binary - if (!err && !data && obj && !('binary' in obj)) { - return tools.maybeCallbackWithError(callback, 'State is not binary'); - } else { - return tools.maybeCallbackWithError(callback, err, data); - } - } - }); - } - getBinaryState(id: string, callback: ioBroker.GetBinaryStateCallback): void; - getBinaryState(id: string, options: unknown, callback: ioBroker.GetBinaryStateCallback): void; - - /** - * Same as getForeignBinaryState but prefixes the own namespace to the id - * - * @param id The state ID - * @param options optional - * @param callback - * @depreacted Please use `readFile` instead of binary states - */ - getBinaryState(id: any, options: any, callback?: any): any { - // we use any types here, because validation takes place in foreign method - id = this._utils.fixId(id); - return this.getForeignBinaryState(id, options, callback); - } - - delForeignBinaryState(id: string, callback?: ioBroker.ErrorCallback): void; - delForeignBinaryState(id: string, options: unknown, callback?: ioBroker.ErrorCallback): void; - - /** - * Deletes binary state - * - * @param id - * @param options - * @param callback - * @deprecated Please use `delFile` instead of binary states - */ - delForeignBinaryState(id: unknown, options: unknown, callback?: unknown): any { - this._logger.info( - `${ - this.namespaceLog - } Information for Developer: Binary States are deprecated and will be removed in js-controller 5.1, please migrate to Files (${ - id as string - })` - ); - - if (typeof options === 'function') { - callback = options; - options = {}; - } - - Validator.assertString(id, 'id'); - Validator.assertOptionalCallback(callback, 'callback'); - if (options !== null && options !== undefined) { - Validator.assertObject(options, 'options'); - } - - return this._delForeignBinaryState({ id, options: options || {}, callback }); - } - - private async _delForeignBinaryState(_options: InternalDelBinaryStateOptions): Promise { - const { id, options, callback } = _options; - - if (!this.#states) { - // if states is no longer existing, we do not need to unsubscribe - this._logger.info( - `${this.namespaceLog} delBinaryState not processed because States database not connected` - ); - return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED); - } - - try { - this._utils.validateId(id, true, options); - } catch (err) { - return tools.maybeCallbackWithError(callback, err); - } - - if (options.user && options.user !== SYSTEM_ADMIN_USER) { - try { - await this._checkStates(id, options, 'delState'); - } catch (e) { - return tools.maybeCallbackWithError(callback, e); - } - - this.#states!.delBinaryState(id, callback); - } else { - this.#states.delBinaryState(id, callback); - } - } - - delBinaryState(id: string, callback?: ioBroker.ErrorCallback): void; - delBinaryState(id: string, options: unknown, callback?: ioBroker.ErrorCallback): void; - - /** - * Deletes binary state but prefixes the own namespace to the id - * - * @param id - * @param options - * @param callback - * @deprecated Please use `delFile` instead of binary states - */ - delBinaryState(id: any, options: any, callback?: any): any { - // we use any types here, because validation takes place in foreign method - // TODO: call fixId as soon as adapters are migrated to setForeignBinaryState - // id = this._utils.fixId(id, false); - return this.delForeignBinaryState(id, options, callback); - } - getPluginInstance(name: string): ioBroker.Plugin | null; /** diff --git a/packages/adapter/src/lib/adapter/validator.ts b/packages/adapter/src/lib/adapter/validator.ts index dc6cc0c033..a07b72a18d 100644 --- a/packages/adapter/src/lib/adapter/validator.ts +++ b/packages/adapter/src/lib/adapter/validator.ts @@ -82,18 +82,12 @@ export class Validator { if (state.val !== null) { // now check if a type is correct, null is always allowed - if (obj.common.type === 'file') { - // file has to be set with setBinaryState - this.log.warn( - `${this.namespaceLog} State to set for "${id}" has to be written with setBinaryState/Async, because its object is of type "file"` - ); - } else if ( + if ( !( (obj.common.type === 'mixed' && typeof state.val !== 'object') || (obj.common.type !== 'object' && obj.common.type === typeof state.val) || (obj.common.type === 'array' && typeof state.val === 'string') || (obj.common.type === 'json' && typeof state.val === 'string') || - (obj.common.type === 'file' && typeof state.val === 'string') || (obj.common.type === 'object' && typeof state.val === 'string') ) ) { diff --git a/packages/cli/src/lib/cli/cliStates.ts b/packages/cli/src/lib/cli/cliStates.ts index ca6c09afe3..c2bf055077 100644 --- a/packages/cli/src/lib/cli/cliStates.ts +++ b/packages/cli/src/lib/cli/cliStates.ts @@ -1,15 +1,13 @@ import { tools } from '@iobroker/js-controller-common'; import { CLICommand, type CLICommandOptions } from './cliCommand.js'; import type { Client as ObjectsClient } from '@iobroker/db-objects-redis'; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const CLI = require('./messages.js'); -// eslint-disable-next-line @typescript-eslint/no-var-requires -const { formatValue } = require('./cliTools'); +import * as CLI from '@/lib/cli/messages.js'; +import { formatValue } from '@/lib/cli/cliTools'; import * as rl from 'readline-sync'; const ALIAS_STARTS_WITH = 'alias.'; -type ResultTransform = (input: any) => any; +type ResultTransform = (input: ioBroker.State) => string; /** Command iobroker state ... */ export class CLIStates extends CLICommand { @@ -20,7 +18,7 @@ export class CLIStates extends CLICommand { /** * Executes a command * - * @param args + * @param args parsed cli args */ execute(args: any[]): void { const { callback, pretty, showHelp } = this.options; @@ -45,9 +43,6 @@ export class CLIStates extends CLICommand { case 'getvalue': resultTransform = obj => (obj ? formatValue(obj.val, pretty) : 'null'); return this.get_(args, resultTransform); - case 'getBinary': - case 'getbinary': - return this._getBinary(args); case 'set': return this.set_(args); case 'chmod': @@ -125,51 +120,11 @@ export class CLIStates extends CLICommand { return !!(obj && ('binary' in obj || (obj.common && 'type' in obj.common && obj.common.type === 'file'))); } - /** - * Get and show binary state - * - * @param args - */ - private _getBinary(args: any[]): void { - const { callback, dbConnect } = this.options; - const id = args[1]; - - console.warn( - `Binary States are deprecated and will be removed in js-controller 5.1, please migrate to Files (${id})` - ); - - dbConnect(async params => { - const { states } = params; - - try { - // @ts-expect-error #1917 - const state = await states.getBinaryState(id); - - if (!state) { - CLI.error.stateNotFound(id); - return void callback(1); - } - - if (Buffer.isBuffer(state)) { - // console.log would append new lines which will mess up the result - process.stdout.write(state, this.options.encoding); - return void callback(0); - } else { - CLI.error.stateNotBinary(id); - return void callback(1); - } - } catch (e) { - CLI.error.unknown(e); - return void callback(1); - } - }); - } - /** * Returns the value of a state * - * @param args - * @param resultTransform + * @param args parsed cli arguments + * @param resultTransform transform function for result */ get_(args: any[], resultTransform: ResultTransform): void { const { callback, dbConnect } = this.options; @@ -191,7 +146,7 @@ export class CLIStates extends CLICommand { typeof targetObj.common.alias.id.read === 'string' ? targetObj.common.alias.id.read : targetObj.common.alias.id; - objects.getObject(aliasId, async (err, sourceObj) => { + objects.getObject(aliasId, async (_err, sourceObj) => { // read target try { if (await this._isBinary(aliasId, objects, targetObj)) { @@ -220,7 +175,7 @@ export class CLIStates extends CLICommand { return void callback(0); }); } else { - CLI.error.unknown(err || `Alias ${id} has no target`); + CLI.error.unknown(err?.message || `Alias ${id} has no target`); return void callback(1); // ? } }); @@ -245,7 +200,9 @@ export class CLIStates extends CLICommand { } /** - * @param args + * Set state in database + * + * @param args parsed cli arguments */ set_(args: any[]): void { const { callback, dbConnect, showHelp } = this.options; @@ -268,7 +225,7 @@ export class CLIStates extends CLICommand { const newVal = ack === undefined ? { val, ack: false } : { val, ack: !!ack }; if (id.startsWith(ALIAS_STARTS_WITH)) { - objects.getObject(id, async (err, obj) => { + objects.getObject(id, async (_err, obj) => { if (await this._isBinary(id, objects, obj)) { CLI.error.stateBinarySetUnsupported(id); return void callback(1); @@ -282,7 +239,7 @@ export class CLIStates extends CLICommand { objects.getObject(aliasId, (err, targetObj) => { if (err) { - CLI.error.unknown(err); + CLI.error.unknown(err.message); return void callback(1); // access error } if (!obj && !force) { @@ -290,7 +247,7 @@ export class CLIStates extends CLICommand { return void callback(1); // object not exists } - if (obj && obj.common && obj.common.type) { + if (obj?.common?.type) { if (obj.common.type === 'string') { newVal.val = newVal.val.toString(); } else if (obj.common.type === 'number') { @@ -319,7 +276,7 @@ export class CLIStates extends CLICommand { }), err => { if (err) { - CLI.error.unknown(err); + CLI.error.unknown(err.message); return void callback(1); // ? } else { CLI.success.stateUpdated(id, val, !!ack); @@ -336,7 +293,7 @@ export class CLIStates extends CLICommand { } else { objects.getObject(id, async (err, obj) => { if (err) { - CLI.error.unknown(err); + CLI.error.unknown(err.message); return void callback(1); // access error } @@ -367,7 +324,7 @@ export class CLIStates extends CLICommand { states.setState(id, newVal, err => { if (err) { - CLI.error.unknown(err); + CLI.error.unknown(err.message); return void callback(1); // ? } else { CLI.success.stateUpdated(id, val, !!ack); @@ -382,7 +339,7 @@ export class CLIStates extends CLICommand { /** * Deletes a state * - * @param args + * @param args parsed cli arguments */ delete(args: any[]): void { const { callback, dbConnect } = this.options; @@ -397,7 +354,7 @@ export class CLIStates extends CLICommand { states.delState(id, err => { if (err) { - CLI.error.stateNotFound(id, err); + CLI.error.stateNotFound(id, err.message); return void callback(3); } else { CLI.success.stateDeleted(id); diff --git a/packages/cli/src/lib/cli/messages.ts b/packages/cli/src/lib/cli/messages.ts index 7b24484de5..4d25d614c8 100644 --- a/packages/cli/src/lib/cli/messages.ts +++ b/packages/cli/src/lib/cli/messages.ts @@ -22,7 +22,7 @@ const errorMessages = Object.freeze({ invalidJSONValue: () => `The given value is not valid JSON.`, unknownCommand: (prefix: string, command: string) => `Unknown command "${prefix} ${command}"!`, - requiredArgumentMissing: (argName: string, exampleCommand: string) => + requiredArgumentMissing: (argName: string, exampleCommand?: string) => `The required argument "${argName}" is missing!` + (exampleCommand ? ` Example: "${exampleCommand}"` : ''), noInstancesFound: (adapter: string) => `Cannot find any instances of "${adapter}"!`, diff --git a/packages/cli/src/lib/setup.ts b/packages/cli/src/lib/setup.ts index 23d025da36..1598955601 100644 --- a/packages/cli/src/lib/setup.ts +++ b/packages/cli/src/lib/setup.ts @@ -261,13 +261,6 @@ function initYargs(): yargs.Argv { type: 'boolean' } }) - .command('getBinary ', 'Get binary state, specified by id', { - encoding: { - describe: 'Encoding for the binary state, like utf-8, ascii, hex, base64, binary', - type: 'string', - default: 'binary' - } - }) .command('getValue ', 'Get state value, specified by id', {}) .command('set []', 'Set state, specified by id', {}) .command('del ', 'Delete state, specified by id', {}) diff --git a/packages/cli/src/lib/setup/setupSetup.ts b/packages/cli/src/lib/setup/setupSetup.ts index 69ad2504e9..d699515b89 100644 --- a/packages/cli/src/lib/setup/setupSetup.ts +++ b/packages/cli/src/lib/setup/setupSetup.ts @@ -141,8 +141,8 @@ export class Setup { /** * Called after io-package objects are created (hence object view functionalities are now available) * - * @param systemConfig - * @param callback + * @param systemConfig the system config object + * @param callback callback function */ async setupReady( systemConfig: ioBroker.SystemConfigObject | undefined | null, @@ -157,7 +157,12 @@ export class Setup { throw new Error('Objects not set up, call setupObjects first'); } - await this._ensureAdaptersPerHostObject(); + try { + await this._ensureAdaptersPerHostObject(); + } catch (e) { + console.error(`Could not ensure that adapters object for this host exists: ${e.message}`); + } + await this._cleanupInstallation(); // special methods which are only there on objects server @@ -298,8 +303,8 @@ Please DO NOT copy files manually into ioBroker storage directories!` /** * Creates objects and does object related cleanup * - * @param callback - * @param checkCertificateOnly + * @param callback callback function + * @param checkCertificateOnly if only certificate check is desired */ async setupObjects(callback: () => void, checkCertificateOnly?: boolean): Promise { const { states: _states, objects: _objects } = await dbConnectAsync(false, this.params); @@ -1102,6 +1107,13 @@ Please DO NOT copy files manually into ioBroker storage directories!` * Perform multiple cleanup operations, to clean up inconsistent states due to past bugs or edge case errors */ private async _cleanupInstallation(): Promise { + console.log('Clean up binary states ...'); + try { + await this._cleanupBinaryStates(); + } catch (e) { + console.error(`Cannot clean up binary states: ${e.message}`); + } + console.log('Clean up invalid group assignments ...'); try { await this._cleanupInvalidGroupAssignments(); @@ -1230,6 +1242,51 @@ Please DO NOT copy files manually into ioBroker storage directories!` } } + /** + * Removes all binary state related objects and states + */ + private async _cleanupBinaryStates(): Promise { + if (!this.objects) { + throw new Error('Objects not set up, call setupObjects first'); + } + + if (!this.states) { + throw new Error('States not set up, call setupObjects first'); + } + + const hostsView = await this.objects.getObjectViewAsync('system', 'host', { + startkey: SYSTEM_HOST_PREFIX, + endkey: `${SYSTEM_HOST_PREFIX}\u9999` + }); + + const hostIds = hostsView.rows.map(row => row.id); + + for (const hostId of hostIds) { + const zipId = `${hostId}.zip`; + const zipFolderExists = await this.objects.objectExists(zipId); + + if (!zipFolderExists) { + continue; + } + + await this.objects.delObject(zipId); + console.log(`Deleted object "${zipId}" during binary state clean up`); + } + + const statesView = await this.objects.getObjectViewAsync('system', 'state', { + startkey: '', + endkey: '\u9999' + }); + + for (const row of statesView.rows) { + if ((row.value.common.type as ioBroker.CommonType | 'file') === 'file') { + await this.objects.delObject(row.id); + await this.states.delState(row.id); + console.log(`Deleted object "${row.id}" during binary state clean up`); + } + } + } + /** * Removes non-existing users from groups */ diff --git a/packages/common/src/lib/common/tools.ts b/packages/common/src/lib/common/tools.ts index 210cd7d1f2..ff69bd8d16 100644 --- a/packages/common/src/lib/common/tools.ts +++ b/packages/common/src/lib/common/tools.ts @@ -2887,7 +2887,7 @@ export function validateGeneralObjectProperties(obj: any, extend?: boolean): voi if (obj.type === 'state') { // if an object type indicates a state, check that `common.type` matches - const allowedStateTypes = ['number', 'string', 'boolean', 'array', 'object', 'mixed', 'file', 'json']; + const allowedStateTypes = ['number', 'string', 'boolean', 'array', 'object', 'mixed', 'json']; if (!allowedStateTypes.includes(obj.common.type)) { throw new Error( `obj.common.type has an invalid value (${ @@ -2933,11 +2933,6 @@ export function validateGeneralObjectProperties(obj: any, extend?: boolean): voi // ensure, that default value has correct type if (obj.common.def !== undefined && obj.common.def !== null) { - if (obj.common.type === 'file') { - // defaults are set via setState but would need setBinaryState - throw new Error('Default value is not supported for type "file"'); - } - // else do what strictObjectChecks does for val if ( !( @@ -2945,13 +2940,12 @@ export function validateGeneralObjectProperties(obj: any, extend?: boolean): voi (obj.common.type !== 'object' && obj.common.type === typeof obj.common.def) || (obj.common.type === 'array' && typeof obj.common.def === 'string') || (obj.common.type === 'json' && typeof obj.common.def === 'string') || - (obj.common.type === 'file' && typeof obj.common.def === 'string') || (obj.common.type === 'object' && typeof obj.common.def === 'string') ) ) { - // types can be 'number', 'string', 'boolean', 'array', 'object', 'mixed', 'file', 'json'; + // types can be 'number', 'string', 'boolean', 'array', 'object', 'mixed', 'json'; // 'array', 'object', 'json' need to be string - if (['object', 'json', 'file', 'array'].includes(obj.common.type)) { + if (['object', 'json', 'array'].includes(obj.common.type)) { throw new Error( `Default value has to be stringified but received type "${typeof obj.common.def}"` ); diff --git a/packages/controller/src/lib/objects.ts b/packages/controller/src/lib/objects.ts index 587e52a464..275091f654 100644 --- a/packages/controller/src/lib/objects.ts +++ b/packages/controller/src/lib/objects.ts @@ -316,16 +316,6 @@ export function getHostObjects(options: GetHostOptions): TaskObject[] { native: {} }); - objs.push({ - _id: `${id}.zip`, - type: 'folder', - common: { - name: 'ZIP files', - desc: 'Files for download' - }, - native: {} - }); - objs.push({ _id: `${id}.logLevel`, type: 'state', diff --git a/packages/controller/src/main.ts b/packages/controller/src/main.ts index 3bb6e4cd63..28be6a8969 100644 --- a/packages/controller/src/main.ts +++ b/packages/controller/src/main.ts @@ -599,10 +599,7 @@ function createStates(onConnect: () => void): void { clearTimeout(statesDisconnectTimeout); statesDisconnectTimeout = null; } - // logs and cleanups are only handled by the main controller process - if (!compactGroupController) { - deleteAllZipPackages(); - } + initMessageQueue(); startAliveInterval(); @@ -1918,30 +1915,6 @@ async function getVersionFromHost(hostId: ioBroker.ObjectIDs.Host): Promise { - for (const id of list) { - try { - await states!.delBinaryState(id); - } catch { - //ignore - } - } -} - -/** - * This function deletes all ZIP packages that were not downloaded. - * ZIP Package is a temporary file that should be deleted straight after it is downloaded and if it still exists, so clear it - */ -async function deleteAllZipPackages(): Promise { - const list = await states!.getKeys(`${hostObjectPrefix}.zip.*`); - await _deleteAllZipPackages(list!); -} - async function startAdapterUpload(): Promise { if (!uploadTasks.length) { return; @@ -2682,24 +2655,14 @@ async function processMessage(msg: ioBroker.SendableMessage): Promise { - if (err) { - sendTo(msg.from, msg.command, { error: err.message }, msg.callback); - } else { - sendTo( - msg.from, - msg.command, - `${hostObjectPrefix}.zip.${msg.message.link}`, - msg.callback - ); - } - }); } } else { sendTo(msg.from, msg.command, { data: base64 }, msg.callback); diff --git a/packages/controller/test/lib/testFiles.ts b/packages/controller/test/lib/testFiles.ts index ddf21d0502..4fb4798ea2 100644 --- a/packages/controller/test/lib/testFiles.ts +++ b/packages/controller/test/lib/testFiles.ts @@ -4,131 +4,6 @@ export function register(it: Mocha.TestFunction, expect: Chai.ExpectStatic, cont const testName = `${context.name} ${context.adapterShortName} files: `; const testId = `testFilesObject.0`; - // setBinaryState - it(testName + 'setForeignBinaryState', async () => { - const objId = `${context.adapter.namespace}.testSetForeignBinaryState`; - // create an object of type file first - await context.adapter.setForeignObjectAsync(objId, { - type: 'state', - common: { - role: 'state', - name: objId, - read: true, - write: true, - type: 'file' - }, - native: {} - }); - - // now we write a binary state - await context.adapter.setForeignBinaryStateAsync(objId, Buffer.from('1234')); - }); - - it(testName + 'setBinaryState', async () => { - const objId = `${context.adapter.namespace}.testSetBinaryState`; - - const receivedPromise = new Promise(resolve => { - context.onAdapterStateChanged = (id, state) => { - if (id === objId) { - if (typeof state !== 'object') { - throw new Error(`Expected object, but got ${typeof state}`); - } - // @ts-expect-error binary states will be removed soon - if (!state?.binary) { - throw new Error(`Binary flag does not set`); - } - if (state.val !== null) { - throw new Error(`Value is not null`); - } - expect(state.ack).to.be.true; - resolve(); - } - }; - }); - - // create an object of type file first - await context.adapter.setForeignObjectAsync(objId, { - type: 'state', - common: { - role: 'state', - name: objId, - read: true, - write: true, - type: 'file' - }, - native: {} - }); - - await context.adapter.subscribeForeignStatesAsync(objId); - - context.adapter.setBinaryStateAsync(objId, Buffer.from('1234')); - await receivedPromise; - - await context.adapter.unsubscribeForeignStatesAsync(objId); - }); - - it(testName + 'delBinaryState', async () => { - const objId = `${context.adapter.namespace}.testSetBinaryState`; - - const receivedPromise = new Promise(resolve => { - context.onAdapterStateChanged = (id, state) => { - if (id === objId) { - expect(state).to.be.equal(null); - resolve(); - } - }; - }); - - await context.adapter.subscribeForeignStatesAsync(objId); - - context.adapter.delBinaryStateAsync(objId); - await receivedPromise; - - await context.adapter.unsubscribeForeignStatesAsync(objId); - }); - - it(testName + 'getForeignBinaryState', async () => { - const objId = `${context.adapter.namespace}.testGetForeignBinaryState`; - // create an object of type file first - await context.adapter.setForeignObjectAsync(objId, { - type: 'state', - common: { - role: 'state', - name: objId, - read: true, - write: true, - type: 'file' - }, - native: {} - }); - - // now we write a binary state - await context.adapter.setForeignBinaryStateAsync(objId, Buffer.from('1234')); - const state = await context.adapter.getForeignBinaryStateAsync(objId); - expect(state!.toString('utf-8')).to.be.equal('1234'); - }); - - it(testName + 'getBinaryState', async () => { - const objId = `${context.adapter.namespace}.testGetBinaryState`; - // create an object of type file first - await context.adapter.setForeignObjectAsync(objId, { - type: 'state', - common: { - role: 'state', - name: objId, - read: true, - write: true, - type: 'file' - }, - native: {} - }); - - // now we write a binary state - await context.adapter.setBinaryStateAsync(objId, Buffer.from('1234')); - const state = await context.adapter.getBinaryStateAsync(objId); - expect(state!.toString('utf-8')).to.be.equal('1234'); - }); - it(testName + 'writeFile with binary content and subscription', async () => { const objId = `vis.0`; const fileName = 'testFile.bin'; diff --git a/packages/controller/test/lib/testHelperStates.ts b/packages/controller/test/lib/testHelperStates.ts index 615055a60a..2c81df6ee8 100644 --- a/packages/controller/test/lib/testHelperStates.ts +++ b/packages/controller/test/lib/testHelperStates.ts @@ -27,7 +27,6 @@ export function register(it: Mocha.TestFunction, expect: Chai.ExpectStatic, cont // createState // deleteDevice - // setBinaryState it(testName + 'requireLog should activate corresponding state', async () => { // default should be false or non-existent let state = await context.states.getState(`system.adapter.${context.adapter.namespace}.logging`); diff --git a/packages/db-states-file/src/lib/states/statesInMemFileDB.js b/packages/db-states-file/src/lib/states/statesInMemFileDB.js index 9c9090e0e9..f933c9ee0f 100644 --- a/packages/db-states-file/src/lib/states/statesInMemFileDB.js +++ b/packages/db-states-file/src/lib/states/statesInMemFileDB.js @@ -213,14 +213,6 @@ class StatesInMemoryFileDB extends InMemoryFileDB { this.log.silly(`${this.namespace} memory publish ${id} ${JSON.stringify(obj)}`); this.publishAll('state', id, obj); }); - } else if ((obj || obj === 0) && typeof obj !== 'object') { - // it is binary state - setImmediate(() => { - const event = { val: null, binary: true, size: obj.length, ack: true }; - // publish event in states - this.log.silly(`${this.namespace} memory publish ${id} ${JSON.stringify(event)}`); - this.publishAll('state', id, event); - }); } if (!this.stateTimer) { @@ -237,10 +229,8 @@ class StatesInMemoryFileDB extends InMemoryFileDB { const state = this.dataset[id]; if (state) { - const isBinary = Buffer.isBuffer(state); delete this.dataset[id]; - - !isBinary && setImmediate(() => this.publishAll('state', id, null)); + setImmediate(() => this.publishAll('state', id, null)); } if (!this.stateTimer) { @@ -340,28 +330,6 @@ class StatesInMemoryFileDB extends InMemoryFileDB { delete this.session[id]; } } - - // needed by Server - _setBinaryState(id, data) { - if (!Buffer.isBuffer(data)) { - data = Buffer.from(data); - } - this.dataset[id] = data; - - // If data === undefined, the state was just created and not filled with value - if (data !== undefined) { - setImmediate(() => { - const event = { val: null, binary: true, size: data.byteLength, ack: true }; - // publish event in states - this.log.silly(`${this.namespace} memory publish ${id} ${JSON.stringify(event)}`); - this.publishAll('state', id, event); - }); - } - - if (!this.stateTimer) { - this.stateTimer = setTimeout(() => this.saveState(), this.writeFileInterval); - } - } } module.exports = StatesInMemoryFileDB; diff --git a/packages/db-states-file/src/lib/states/statesInMemServerRedis.js b/packages/db-states-file/src/lib/states/statesInMemServerRedis.js index 925c6c9f25..b2f3da34c6 100644 --- a/packages/db-states-file/src/lib/states/statesInMemServerRedis.js +++ b/packages/db-states-file/src/lib/states/statesInMemServerRedis.js @@ -270,14 +270,7 @@ class StatesInMemoryServer extends StatesInMemoryFileDB { const { id, namespace } = this._normalizeId(data[0]); if (namespace === this.namespaceStates) { try { - let state; - try { - state = JSON.parse(data[1].toString('utf-8')); - } catch { - // No JSON, so handle as binary data and set as Buffer - this._setBinaryState(id, data[1]); - return void handler.sendString(responseId, 'OK'); - } + const state = JSON.parse(data[1].toString('utf-8')); this._setStateDirect(id, state); handler.sendString(responseId, 'OK'); } catch (err) { @@ -299,13 +292,8 @@ class StatesInMemoryServer extends StatesInMemoryFileDB { const { id, namespace } = this._normalizeId(data[0]); if (namespace === this.namespaceStates) { try { - let state; - try { - state = JSON.parse(data[2].toString('utf-8')); - } catch { - // No JSON, so handle as binary data and set as Buffer - state = data[2]; - } + const state = JSON.parse(data[2].toString('utf-8')); + const expire = parseInt(data[1].toString('utf-8'), 10); if (isNaN(expire)) { return void handler.sendError( diff --git a/packages/db-states-jsonl/src/lib/states/statesInMemServerRedis.js b/packages/db-states-jsonl/src/lib/states/statesInMemServerRedis.js index f866282b30..0f914a13dc 100644 --- a/packages/db-states-jsonl/src/lib/states/statesInMemServerRedis.js +++ b/packages/db-states-jsonl/src/lib/states/statesInMemServerRedis.js @@ -272,14 +272,7 @@ class StatesInMemoryServer extends StatesInMemoryJsonlDB { const { id, namespace } = this._normalizeId(data[0]); if (namespace === this.namespaceStates) { try { - let state; - try { - state = JSON.parse(data[1].toString('utf-8')); - } catch { - // No JSON, so handle as binary data and set as Buffer - this._setBinaryState(id, data[1]); - return void handler.sendString(responseId, 'OK'); - } + const state = JSON.parse(data[1].toString('utf-8')); this._setStateDirect(id, state); handler.sendString(responseId, 'OK'); } catch (err) { @@ -301,13 +294,8 @@ class StatesInMemoryServer extends StatesInMemoryJsonlDB { const { id, namespace } = this._normalizeId(data[0]); if (namespace === this.namespaceStates) { try { - let state; - try { - state = JSON.parse(data[2].toString('utf-8')); - } catch { - // No JSON, so handle as binary data and set as Buffer - state = data[2]; - } + const state = JSON.parse(data[2].toString('utf-8')); + const expire = parseInt(data[1].toString('utf-8'), 10); if (isNaN(expire)) { return void handler.sendError( diff --git a/packages/db-states-redis/src/lib/states/statesInRedisClient.ts b/packages/db-states-redis/src/lib/states/statesInRedisClient.ts index b00a3e20bb..328ed024cc 100644 --- a/packages/db-states-redis/src/lib/states/statesInRedisClient.ts +++ b/packages/db-states-redis/src/lib/states/statesInRedisClient.ts @@ -1420,74 +1420,6 @@ export class StateRedisClient { } } - async setBinaryState(id: string, data: Buffer, callback?: ioBroker.ErrorCallback): Promise { - if (!id || typeof id !== 'string') { - return tools.maybeCallbackWithError(callback, `invalid id ${JSON.stringify(id)}`); - } - - if (!this.client) { - return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED); - } - - if (!Buffer.isBuffer(data)) { - data = Buffer.from(data); - } - - try { - await this.client.set(this.namespaceRedis + id, data); - // for back compatibility send normal state, but with the flag "binary" - await this.client.publish( - this.namespaceRedis + id, - JSON.stringify({ val: null, binary: true, size: data.byteLength, ack: true }) - ); - return tools.maybeCallback(callback); - } catch (e) { - return tools.maybeCallbackWithRedisError(callback, e); - } - } - - async getBinaryState( - id: string, - callback: (err: Error | undefined | null, state?: Buffer) => void - ): Promise { - if (!id || typeof id !== 'string') { - return tools.maybeCallbackWithError(callback, `invalid id ${JSON.stringify(id)}`); - } - - if (!this.client) { - return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED); - } - - let data; - try { - data = await this.client.getBuffer(this.namespaceRedis + id); - return tools.maybeCallbackWithError(callback, null, data); - } catch (e) { - return tools.maybeCallbackWithRedisError(callback, e); - } - } - - async delBinaryState( - id: string, - callback?: (err: Error | undefined | null, id?: string) => void - ): Promise { - if (!id || typeof id !== 'string') { - return tools.maybeCallbackWithError(callback, `invalid id ${JSON.stringify(id)}`); - } - - if (!this.client) { - return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED); - } - - try { - await this.client.del(this.namespaceRedis + id); - await this.client.publish(this.namespaceRedis + id, JSON.stringify(null)); - return tools.maybeCallbackWithError(callback, null, id); - } catch (e) { - return tools.maybeCallbackWithRedisError(callback, e, id); - } - } - /** * Returns the protocol version from DB * diff --git a/packages/types-dev/index.d.ts b/packages/types-dev/index.d.ts index aa11bfa8fa..a9adbbb150 100644 --- a/packages/types-dev/index.d.ts +++ b/packages/types-dev/index.d.ts @@ -400,9 +400,6 @@ declare global { type GetStatesCallback = (err?: Error | null, states?: Record) => void; type GetStatesPromise = Promise>; - type GetBinaryStateCallback = (err?: Error | null, state?: Buffer) => void; - type GetBinaryStatePromise = Promise>; - type SetStateCallback = (err?: Error | null, id?: string) => void; type SetStatePromise = Promise>; diff --git a/packages/types-dev/objects.d.ts b/packages/types-dev/objects.d.ts index 85b5b1e0d0..7f50c66d15 100644 --- a/packages/types-dev/objects.d.ts +++ b/packages/types-dev/objects.d.ts @@ -160,7 +160,7 @@ declare global { /** For objects, we require the English language to be present */ type StringOrTranslated = string | Translated; - type CommonType = 'number' | 'string' | 'boolean' | 'array' | 'object' | 'mixed' | 'file'; + type CommonType = 'number' | 'string' | 'boolean' | 'array' | 'object' | 'mixed'; interface ObjectCommon { /** The name of this object as a simple string or an object with translations */ diff --git a/packages/types-public/index.test-d.ts b/packages/types-public/index.test-d.ts index 12f6eb3142..64c563a96e 100644 --- a/packages/types-public/index.test-d.ts +++ b/packages/types-public/index.test-d.ts @@ -180,10 +180,6 @@ adapter.getState('state.id', (err, state) => state && state.from.toLowerCase()); adapter.getStateAsync('state.id').then(state => state && state.from.toLowerCase()); adapter.getForeignState('state.id', (err, state) => state && state.from.toLowerCase()); adapter.getForeignStateAsync('state.id').then(state => state && state.from.toLowerCase()); -adapter.getBinaryState('state.id', (err, state) => state && state.writeUInt16BE(0, 0)); -adapter.getBinaryStateAsync('state.id').then(state => state && state.writeUInt16BE(0, 0)); -adapter.getForeignBinaryState('state.id', (err, state) => state && state.writeUInt16BE(0, 0)); -adapter.getForeignBinaryStateAsync('state.id').then(state => state && state.writeUInt16BE(0, 0)); adapter.setObject('obj.id', { type: 'device', common: { name: 'foo' }, native: {} }); adapter.setObject('obj.id', { type: 'device', common: { name: 'foo' }, native: {} }, (_err, _id) => {}); @@ -598,10 +594,6 @@ adapter.setStateChanged('id', null); adapter.setForeignStateChanged('id', null); adapter.setStateChangedAsync('id', null); adapter.setForeignStateChangedAsync('id', null); -adapter.delBinaryState('id'); -adapter.delBinaryStateAsync('id').then(() => null); -adapter.delForeignBinaryState('id'); -adapter.delForeignBinaryStateAsync('id').then(() => null); // Objects and arrays are not valid state values // @ts-expect-error